Skip to content

Commit

Permalink
Pull the logic for converting Command/Response pairs to and frame HTT…
Browse files Browse the repository at this point in the history
…P request/response pairs

into a common codec instead of duplicating the logic on the client and server.

The client's HttpCommandExecutor is still a bit more complex than it needs to be since some
WebDriver servers (IEDriver) still use a redirect when creating a new session.
  • Loading branch information
jleyba committed May 6, 2014
1 parent fdf34c6 commit 1bd26af
Show file tree
Hide file tree
Showing 32 changed files with 1,669 additions and 1,071 deletions.
58 changes: 15 additions & 43 deletions java/client/src/org/openqa/selenium/remote/CommandInfo.java
Original file line number Diff line number Diff line change
@@ -1,58 +1,30 @@
package org.openqa.selenium.remote;

import org.apache.http.client.methods.HttpUriRequest;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.net.Urls;

import java.net.URL;
import org.openqa.selenium.remote.codec.http.HttpMethod;

public class CommandInfo {

private final String url;
private final HttpVerb verb;
private final HttpMethod method;

/**
* @deprecated Use {@link org.openqa.selenium.remote.CommandInfo(String, HttpMethod)}.
*/
@Deprecated
public CommandInfo(String url, HttpVerb verb) {
this.url = url;
this.verb = verb;
this(url, verb.toHttpMethod());
}

HttpUriRequest getMethod(URL base, Command command) {
StringBuilder urlBuilder = new StringBuilder();

urlBuilder.append(base.toExternalForm().replaceAll("/$", ""));
for (String part : url.split("/")) {
if (part.length() == 0) {
continue;
}

urlBuilder.append("/");
if (part.startsWith(":")) {
String value = get(part.substring(1), command);
if (value != null) {
urlBuilder.append(get(part.substring(1), command));
}
} else {
urlBuilder.append(part);
}
}

return verb.createMethod(urlBuilder.toString());
public CommandInfo(String url, HttpMethod method) {
this.url = url;
this.method = method;
}

private String get(String propertyName, Command command) {
if ("sessionId".equals(propertyName)) {
SessionId id = command.getSessionId();
if (id == null) {
throw new WebDriverException("Session ID may not be null");
}
return id.toString();
}
String getUrl() {
return url;
}

// Attempt to extract the property name from the parameters
Object value = command.getParameters().get(propertyName);
if (value != null) {
return Urls.urlEncode(String.valueOf(value));
}
return null;
HttpMethod getMethod() {
return method;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ public interface DriverCommand {
// Logging API
String GET_AVAILABLE_LOG_TYPES = "getAvailableLogTypes";
String GET_LOG = "getLog";
String GET_SESSION_LOGS = "getSessionLogs";

// Mobile API
String GET_NETWORK_CONNECTION = "getNetworkConnection";
Expand Down
327 changes: 64 additions & 263 deletions java/client/src/org/openqa/selenium/remote/HttpCommandExecutor.java

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions java/client/src/org/openqa/selenium/remote/HttpVerb.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,47 @@
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.openqa.selenium.remote.codec.http.HttpMethod;

/**
* @deprecated Use {@link org.openqa.selenium.remote.codec.http.HttpMethod}.
*/
@Deprecated
public enum HttpVerb {
GET() {
@Override
HttpUriRequest createMethod(String url) {
return new HttpGet(url);
}

@Override
HttpMethod toHttpMethod() {
return HttpMethod.GET;
}
},
POST() {
@Override
HttpUriRequest createMethod(String url) {
return new HttpPost(url);
}

@Override
HttpMethod toHttpMethod() {
return HttpMethod.POST;
}
},
DELETE() {
@Override
HttpUriRequest createMethod(String url) {
return new HttpDelete(url);
}

@Override
HttpMethod toHttpMethod() {
return HttpMethod.DELETE;
}
};

abstract HttpUriRequest createMethod(String url);
abstract HttpMethod toHttpMethod();
}
76 changes: 76 additions & 0 deletions java/client/src/org/openqa/selenium/remote/Responses.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package org.openqa.selenium.remote;

import com.google.common.base.Optional;

import org.json.JSONException;
import org.json.JSONObject;

/**
* Contains factory methods for creating {@link Response} objects.
*/
public class Responses {

private static final ErrorCodes ERROR_CODES = new ErrorCodes();

private Responses() {} // Utility class.

/**
* Creates a response object for a successful command execution.
*
* @param sessionId ID of the session that executed the command.
* @param value the command result value.
* @return the new response object.
*/
public static Response success(SessionId sessionId, Object value) {
Response response = new Response();
response.setSessionId(sessionId != null ? sessionId.toString() : null);
response.setValue(value);
response.setStatus(ErrorCodes.SUCCESS);
response.setState(ErrorCodes.SUCCESS_STRING);
return response;
}

/**
* Creates a response object for a failed command execution.
*
* @param sessionId ID of the session that executed the command.
* @param reason the failure reason.
* @return the new response object.
*/
public static Response failure(SessionId sessionId, Throwable reason) {
Response response = new Response();
response.setSessionId(sessionId != null ? sessionId.toString() : null);
response.setValue(reason);
response.setStatus(ERROR_CODES.toStatusCode(reason));
response.setState(ERROR_CODES.toState(response.getStatus()));
return response;
}

/**
* Creates a response object for a failed command execution.
*
* @param sessionId ID of the session that executed the command.
* @param reason the failure reason.
* @param screenshot a base64 png screenshot to include with the failure.
* @return the new response object.
*/
public static Response failure(
SessionId sessionId, Throwable reason, Optional<String> screenshot) {
Response response = new Response();
response.setSessionId(sessionId != null ? sessionId.toString() : null);
response.setStatus(ERROR_CODES.toStatusCode(reason));
response.setState(ERROR_CODES.toState(response.getStatus()));

if (reason != null) {
String raw = new BeanToJsonConverter().convert(reason);
try {
JSONObject jsonError = new JSONObject(raw);
jsonError.put("screen", screenshot.orNull());
response.setValue(jsonError);
} catch (JSONException e) {
throw new JsonException(e);
}
}
return response;
}
}
2 changes: 2 additions & 0 deletions java/client/src/org/openqa/selenium/remote/build.desc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ java_library(name = "common",
"JsonException.java",
"JsonToBeanConverter.java",
"PropertyMunger.java",
"Responses.java",
"ScreenshotException.java",
"SessionNotFoundException.java",
"SimplePropertyDescriptor.java",
Expand Down Expand Up @@ -107,5 +108,6 @@ java_library(name = "remote",
"//java/client/src/org/openqa/selenium/logging",
"//java/client/src/org/openqa/selenium/io",
"//java/client/src/org/openqa/selenium/net",
"//java/client/src/org/openqa/selenium/remote/codec",
"//third_party/java/apache-httpclient",
])
28 changes: 28 additions & 0 deletions java/client/src/org/openqa/selenium/remote/codec/Codec.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.openqa.selenium.remote.codec;

/**
* Converts an object between two different representations.
*
* @param <S> The raw value type.
* @param <T> The value's encoded type.
*/
public interface Codec<S, T> {

/**
* Encodes an object.
*
* @param raw The object's raw representation.
* @return The encoded object.
* @throws IllegalArgumentException If the object cannot be encoded.
*/
T encode(S raw);

/**
* Decodes an object.
*
* @param encoded The encoded object to decode.
* @return The decoded object.
* @throws IllegalArgumentException If the object cannot be decoded.
*/
S decode(T encoded);
}
26 changes: 26 additions & 0 deletions java/client/src/org/openqa/selenium/remote/codec/CommandCodec.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.openqa.selenium.remote.codec;

import org.openqa.selenium.UnsupportedCommandException;
import org.openqa.selenium.remote.Command;

/**
* Converts {@link Command} objects to and from another representation.
*
* @param <T> The type of an encoded command.
*/
public interface CommandCodec<T> extends Codec<Command, T> {

/**
* @inheritDoc
* @throws UnsupportedCommandException If the command is not supported by this codec.
*/
@Override
T encode(Command command);

/**
* @inheritDoc
* @throws UnsupportedCommandException If the command is not supported by this codec.
*/
@Override
Command decode(T encodedCommand);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.openqa.selenium.remote.codec;

import org.openqa.selenium.remote.Response;

/**
* Converts {@link Response} objects to and from another representation.
*
* @param <T> The type of an encoded response.
*/
public interface ResponseCodec<T> extends Codec<Response, T> {
}
13 changes: 13 additions & 0 deletions java/client/src/org/openqa/selenium/remote/codec/build.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
java_library(
name = "codec",
srcs = [
"*.java",
"http/*.java",
],
deps = [
"//java/client/src/org/openqa/selenium:webdriver-api",
"//java/client/src/org/openqa/selenium/net",
"//java/client/src/org/openqa/selenium/remote:api",
"//java/client/src/org/openqa/selenium/remote:common",
"//third_party/java/guava-libraries",
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package org.openqa.selenium.remote.codec.http;

import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;

import com.google.common.base.Supplier;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.net.MediaType;

import java.nio.charset.Charset;
import java.util.Collection;
import java.util.List;

class HttpMessage {

private final Multimap<String, String> headers = Multimaps.newListMultimap(
Maps.<String, Collection<String>>newHashMap(), new Supplier<List<String>>() {
@Override
public List<String> get() {
return Lists.newLinkedList();
}
});

private byte[] content = new byte[0];

public Iterable<String> getHeaderNames() {
return headers.keySet();
}

public Iterable<String> getHeaders(String name) {
return headers.get(name);
}

public String getHeader(String name) {
Collection<String> values = headers.get(name);
return values.isEmpty() ? null : values.iterator().next();
}

public void setHeader(String name, String value) {
removeHeader(name);
headers.put(name, value);
}

public void addHeader(String name, String value) {
headers.put(name, value);
}

public void removeHeader(String name) {
headers.removeAll(name);
}

public void setContent(byte[] data) {
this.content = data;
}

public byte[] getContent() {
return content;
}

public String getContentString() {
Charset charset = UTF_8;
try {
String contentType = getHeader(CONTENT_TYPE);
if (contentType != null) {
MediaType mediaType = MediaType.parse(contentType);
charset = mediaType.charset().or(UTF_8);
}
} catch (IllegalArgumentException ignored) {
// Do nothing.
}
return new String(content, charset);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.openqa.selenium.remote.codec.http;

public enum HttpMethod {
DELETE,
GET,
POST
}
Loading

0 comments on commit 1bd26af

Please sign in to comment.