Skip to content

Commit

Permalink
[cdp] Add Fetch domain.
Browse files Browse the repository at this point in the history
Major :
1. add all Frame methods
2. Add all Objects
3. Add tests
4. Add some other POJOS for Page and IO
  • Loading branch information
Shay Dratler authored and shs96c committed Sep 10, 2019
1 parent ef225e3 commit 1c0d8f6
Show file tree
Hide file tree
Showing 17 changed files with 926 additions and 241 deletions.
200 changes: 159 additions & 41 deletions java/client/src/org/openqa/selenium/devtools/fetch/Fetch.java
Original file line number Diff line number Diff line change
@@ -1,76 +1,194 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.devtools.fetch;

import static org.openqa.selenium.devtools.ConverterFunctions.map;

import com.google.common.collect.ImmutableMap;

import org.openqa.selenium.Beta;
import org.openqa.selenium.devtools.Command;
import org.openqa.selenium.devtools.DevToolsException;
import org.openqa.selenium.devtools.Event;
import org.openqa.selenium.devtools.io.model.StreamHandle;
import org.openqa.selenium.devtools.fetch.model.AuthChallengeResponse;
import org.openqa.selenium.devtools.fetch.model.AuthRequired;
import org.openqa.selenium.devtools.fetch.model.HeaderEntry;
import org.openqa.selenium.devtools.fetch.model.RequestId;
import org.openqa.selenium.devtools.fetch.model.RequestPattern;
import org.openqa.selenium.devtools.fetch.model.RequestPaused;
import org.openqa.selenium.devtools.fetch.model.ResponseBody;
import org.openqa.selenium.devtools.network.model.ErrorReason;

import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
* A domain for letting clients substitute browser's network layer with client code.
*/
@Beta
public class Fetch {

/**
* Disables the fetch domain.
*/
public static Command<Void> disable() {
return new Command<>("Fetch.disable", ImmutableMap.of());
}

/**
* Disables the fetch domain.
*/
public static Command<Void> enable(
Optional<List<RequestPattern>> requestPatterns,
Optional<Boolean> handleAuthRequests) {

ImmutableMap.Builder<String, Object> args = ImmutableMap.builder();
requestPatterns.ifPresent(patterns -> args.put("patterns", patterns));
handleAuthRequests.ifPresent(authRequests -> args.put("handleAuthRequests", authRequests));

return new Command<>("Fetch.enable", args.build());
Optional<List<RequestPattern>> patterns, Optional<Boolean> handleAuthRequests) {
final ImmutableMap.Builder<String, Object> params = ImmutableMap.builder();
patterns.ifPresent(p -> params.put("patterns", p));
handleAuthRequests.ifPresent(h -> params.put("handleAuthRequests", h));
return new Command<>("Fetch.enable", params.build());
}

/**
* Causes the request to fail with specified reason.
*/
public static Command<Void> failRequest(RequestId requestId, ErrorReason errorReason) {
return new Command<Void>(
"Fetch.failRequest",
ImmutableMap.of("requestId", requestId, "errorReason", errorReason))
.doesNotSendResponse();
Objects.requireNonNull(requestId, "requestId is required");
Objects.requireNonNull(errorReason, "errorReason is required");
return new Command<>(
"Fetch.failRequest", ImmutableMap.of("requestId", requestId, "errorReason", errorReason));
}

/**
* Provides response to the request.
*/
public static Command<Void> fulfillRequest(
RequestId requestId,
int responseCode,
List<HeaderEntry> responseHeaders,
Optional<String> body,
Optional<String> responsePhrase) {

ImmutableMap.Builder<String, Object> args = ImmutableMap.builder();
args.put("requestId", requestId);
args.put("responseCode", responseCode);
args.put("responseHeaders", responseHeaders);
body.ifPresent(text -> args.put("body", text));
responsePhrase.ifPresent(phrase -> args.put("responsePhrase", phrase));

return new Command<Void>("Fetch.fulfillRequest", args.build()).doesNotSendResponse();
RequestId requestId,
int responseCode,
List<HeaderEntry> responseHeaders,
Optional<String> body,
Optional<String> responsePhrase) {
final ImmutableMap.Builder<String, Object> params = ImmutableMap.builder();
Objects.requireNonNull(requestId, "requestId is required");
if (responseCode < 0) {
throw new DevToolsException("Invalid http responseCode");
}
responseHeaders = validateHeaders(responseHeaders);
params.put("requestId", requestId);
params.put("responseHeaders", responseHeaders);
params.put("responseCode", responseCode);
body.ifPresent(b -> params.put("body", b));
responsePhrase.ifPresent(phrase -> params.put("responsePhrase", phrase));
return new Command<>("Fetch.failRequest", params.build());
}

/**
* Continues the request, optionally modifying some of its parameters.
*/
public static Command<Void> continueRequest(
RequestId requestId,
Optional<String> url,
Optional<String> method,
Optional<String> postData,
Optional<List<HeaderEntry>> headers) {

ImmutableMap.Builder<String, Object> args = ImmutableMap.builder();
args.put("requestId", requestId);
url.ifPresent(u -> args.put("url", u));
method.ifPresent(m -> args.put("method", m));
postData.ifPresent(data -> args.put("postData", data));
headers.ifPresent(h -> args.put("headers", headers));

return new Command<Void>("Fetch.continueRequest", args.build()).doesNotSendResponse();
RequestId requestId,
Optional<String> url,
Optional<String> method,
Optional<String> postData,
Optional<List<HeaderEntry>> headers) {
final ImmutableMap.Builder<String, Object> params = ImmutableMap.builder();
Objects.requireNonNull(requestId, "requestId is required");
params.put("requestId", requestId);
url.ifPresent(s -> params.put("url", s));
method.ifPresent(s -> params.put("method", s));
postData.ifPresent(s -> params.put("postData", s));
headers.ifPresent(h -> params.put("headers", h));
return new Command<>("Fetch.continueRequest", params.build());
}

/**
* Continues a request supplying authChallengeResponse following authRequired event.
*/
public static Command<Void> continueWithAuth(
RequestId requestId, AuthChallengeResponse authChallengeResponse) {
Objects.requireNonNull(requestId, "requestId is required");
Objects.requireNonNull(authChallengeResponse, "authChallengeResponse is required");
return new Command<>(
"Fetch.continueWithAuth",
ImmutableMap.of("requestId", requestId, "authChallengeResponse", authChallengeResponse));
}

/**
* Causes the body of the response to be received from the server and returned as a single string.
* May only be issued for a request that is paused in the Response stage and is mutually exclusive
* with takeResponseBodyForInterceptionAsStream. Calling other methods that affect the request or
* disabling fetch domain before body is received results in an undefined behavior.
*/
public static Command<ResponseBody> getResponseBody(RequestId requestId) {
Objects.requireNonNull(requestId, "requestId is required");
return new Command<>(
"Fetch.getResponseBody",
ImmutableMap.of("requestId", requestId),
map("body", ResponseBody.class));
}

/**
* Returns a handle to the stream representing the response body. The request must be paused in
* the HeadersReceived stage. Note that after this command the request can't be continued as is --
* client either needs to cancel it or to provide the response body. The stream only supports
* sequential read, IO.read will fail if the position is specified. This method is mutually
* exclusive with getResponseBody. Calling other methods that affect the request or disabling
* fetch domain before body is received results in an undefined behavior.
*/
public static Command<StreamHandle> takeResponseBodyAsStream(RequestId requestId) {
Objects.requireNonNull(requestId, "requestId is required");
return new Command<>(
"Fetch.takeResponseBodyAsStream",
ImmutableMap.of("requestId", requestId),
map("stream", StreamHandle.class));
}

/**
* Issued when the domain is enabled and the request URL matches the specified filter. The request
* is paused until the client responds with one of continueRequest, failRequest or fulfillRequest.
* The stage of the request can be determined by presence of responseErrorReason and
* responseStatusCode -- the request is at the response stage if either of these fields is present
* and in the request stage otherwise.
*/
public static Event<RequestPaused> requestPaused() {
return new Event<>("Fetch.requestPaused", input -> input.read(RequestPaused.class));
return new Event<>("Fetch.requestPaused", map("requestId", RequestPaused.class));
}

/**
* Issued when the domain is enabled with handleAuthRequests set to true. The request is paused
* until client responds with continueWithAuth.
*/
public static Event<AuthRequired> authRequired() {
return new Event<>("Fetch.authRequired", map("requestId", AuthRequired.class));
}

/**
* Validators
*/
private static List<HeaderEntry> validateHeaders(List<HeaderEntry> responseHeaders) {
Objects.requireNonNull(responseHeaders, "responseHeaders is required");
if (responseHeaders.isEmpty()) {
throw new DevToolsException("responseHeaders is empty");
}
responseHeaders.forEach(Fetch::validateHeader);
return responseHeaders;
}

private static HeaderEntry validateHeader(HeaderEntry responseHeader) {
return Objects.requireNonNull(responseHeader, "responseHeader is required");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.openqa.selenium.devtools.fetch.model;

import org.openqa.selenium.Beta;
import org.openqa.selenium.json.JsonInput;

import java.util.Objects;

/**
* Authorization challenge for HTTP status code 401 or 407.EXPERIMENTAL
*/
@Beta
public class AuthChallenge {

/**
* Source of the authentication challenge.
*/
private AuthChallengeSource source;
/**
* Origin of the challenger.
*/
private String origin;
/**
* The authentication scheme used, such as basic or digest
*/
private String scheme;
/**
* The realm of the challenge. May be empty.
*/
private String realm;

public AuthChallenge(AuthChallengeSource source, String origin, String scheme,
String realm) {
this.source = source;
this.origin = Objects.requireNonNull(origin, "origin is required");
this.scheme = Objects.requireNonNull(scheme, "scheme is required");
this.realm = Objects.requireNonNull(realm, "realm is required");
}

private static AuthChallenge fromJson(JsonInput input) {
AuthChallengeSource source = null;
String origin = null, scheme = null, realm = null;
while (input.hasNext()) {
switch (input.nextName()) {
case "source":
source = input.read(AuthChallengeSource.class);
break;
case "origin":
origin = input.nextString();
break;
case "scheme":
scheme = input.nextString();
break;
case "realm":
realm = input.nextString();
default:
input.nextString();
break;
}
}
return new AuthChallenge(source, origin, scheme, realm);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.openqa.selenium.devtools.fetch.model;

import org.openqa.selenium.Beta;
import org.openqa.selenium.json.JsonInput;

import java.util.Objects;

/**
* Response to an AuthChallenge.EXPERIMENTAL
*/
@Beta
public class AuthChallengeResponse {

/**
* The decision on what to do in response to the authorization challenge. Default means deferring
* to the default behavior of the net stack, which will likely either the Cancel authentication or
* display a popup dialog box.
*/
private final FetchResponseEnum response;
/**
* The username to provide, possibly empty. Should only be set if response is ProvideCredentials.
*/
private final String username;
/**
* The password to provide, possibly empty. Should only be set if response is ProvideCredentials.
*/
private final String password;

public AuthChallengeResponse(FetchResponseEnum response, String username, String password) {
this.response = Objects.requireNonNull(response, "response is mandatory");
this.username = username;
this.password = password;
}

private static AuthChallengeResponse fromJson(JsonInput input) {
FetchResponseEnum response = input.read(FetchResponseEnum.class);
String username = null, password = null;
while (input.hasNext()) {
switch (input.nextName()) {
case "username":
username = input.nextString();
break;
case "password":
password = input.nextString();
break;
default:
input.skipValue();
break;
}
}
return new AuthChallengeResponse(response, username, password);
}
}

0 comments on commit 1c0d8f6

Please sign in to comment.