Skip to content

Commit 1eb94ab

Browse files
committed
Determine webdriver variant spoken by remote end.
We do this with a pretty simple handshake, that looks for fields specific to each dialect of the wire protocol.
1 parent 8f2ae95 commit 1eb94ab

File tree

3 files changed

+224
-0
lines changed

3 files changed

+224
-0
lines changed

java/client/src/org/openqa/selenium/remote/BUCK

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ java_library(name = 'remote-lib',
6666
'CommandCodec.java',
6767
'CommandInfo.java',
6868
'CoordinatesUtils.java',
69+
'Dialect.java',
6970
'DriverCommand.java',
7071
'ErrorCodes.java',
7172
'ErrorHandler.java',
@@ -75,6 +76,7 @@ java_library(name = 'remote-lib',
7576
'JsonException.java',
7677
'JsonToBeanConverter.java',
7778
'LocalFileDetector.java',
79+
'ProtocolHandshake.java',
7880
'RemoteActionChainExecutor.java',
7981
'RemoteExecuteMethod.java',
8082
'RemoteKeyboard.java',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.remote;
19+
20+
public enum Dialect {
21+
OSS,
22+
W3C;
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.remote;
19+
20+
import static com.google.common.base.Charsets.UTF_8;
21+
import static com.google.common.net.HttpHeaders.CONTENT_LENGTH;
22+
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
23+
import static com.google.common.net.MediaType.JSON_UTF_8;
24+
25+
import com.google.common.base.Preconditions;
26+
27+
import org.openqa.selenium.Capabilities;
28+
import org.openqa.selenium.SessionNotCreatedException;
29+
import org.openqa.selenium.remote.http.HttpClient;
30+
import org.openqa.selenium.remote.http.HttpMethod;
31+
import org.openqa.selenium.remote.http.HttpRequest;
32+
import org.openqa.selenium.remote.http.HttpResponse;
33+
34+
import java.io.IOException;
35+
import java.net.HttpURLConnection;
36+
import java.util.HashMap;
37+
import java.util.Map;
38+
import java.util.Optional;
39+
import java.util.logging.Logger;
40+
41+
public class ProtocolHandshake {
42+
43+
private final static Logger LOG = Logger.getLogger(ProtocolHandshake.class.getName());
44+
45+
public Result createSession(HttpClient client, Command command)
46+
throws IOException {
47+
Capabilities desired = (Capabilities) command.getParameters().get("desiredCapabilities");
48+
Capabilities required = (Capabilities) command.getParameters().get("requiredCapabilities");
49+
50+
// Assume the remote end obeys the robustness principle.
51+
Map<String, Object> parameters = new HashMap<>();
52+
amendW3CParameters(parameters, desired, required);
53+
amendOssParamters(parameters, desired, required);
54+
LOG.info("Attempting bi-dialect session, assuming Postel's Law holds true on the remote end");
55+
Optional<Result> result = createSession(client, parameters);
56+
57+
// Assume a fragile w3c implementation
58+
if (!result.isPresent()) {
59+
parameters = new HashMap<>();
60+
amendW3CParameters(parameters, desired, required);
61+
LOG.info("Falling back to straight W3C remote end connection");
62+
result = createSession(client, parameters);
63+
}
64+
65+
// Assume a fragile OSS webdriver implementation
66+
if (!result.isPresent()) {
67+
parameters = new HashMap<>();
68+
amendOssParamters(parameters, desired, required);
69+
LOG.info("Falling back to original OSS JSON Wire Protocol.");
70+
result = createSession(client, parameters);
71+
}
72+
73+
if (result.isPresent()) {
74+
return result.get();
75+
}
76+
77+
throw new SessionNotCreatedException(
78+
String.format(
79+
"Unable to create new remote session. " +
80+
"desired capabilities = %s, required capabilities = %s",
81+
desired,
82+
required));
83+
}
84+
85+
private Optional<Result> createSession(HttpClient client, Map<String, Object> parameters)
86+
throws IOException {
87+
// Create the http request and send it
88+
HttpRequest request = new HttpRequest(HttpMethod.POST, "/session");
89+
String content = new BeanToJsonConverter().convert(parameters);
90+
byte[] data = content.getBytes(UTF_8);
91+
92+
request.setHeader(CONTENT_LENGTH, String.valueOf(data.length));
93+
request.setHeader(CONTENT_TYPE, JSON_UTF_8.toString());
94+
request.setContent(data);
95+
HttpResponse response = client.execute(request, true);
96+
97+
Map<?, ?> jsonBlob = null;
98+
try {
99+
String resultString = response.getContentString();
100+
jsonBlob = new JsonToBeanConverter().convert(Map.class, resultString);
101+
} catch (JsonException e) {
102+
// Fine. Handle that below
103+
}
104+
105+
if (jsonBlob == null) {
106+
jsonBlob = new HashMap<>();
107+
}
108+
109+
// If the result looks positive, return the result.
110+
Object sessionId = jsonBlob.get("sessionId");
111+
Object value = jsonBlob.get("value");
112+
Object w3cError = jsonBlob.get("error");
113+
Object ossStatus = jsonBlob.get("status");
114+
Map<String, ?> capabilities = null;
115+
if (value != null && value instanceof Map) {
116+
capabilities = (Map<String, ?>) value;
117+
} else if (value != null && value instanceof Capabilities) {
118+
capabilities = ((Capabilities) capabilities).asMap();
119+
}
120+
121+
if (response.getStatus() == HttpURLConnection.HTTP_OK) {
122+
if (sessionId != null && capabilities != null) {
123+
Dialect dialect = ossStatus == null ? Dialect.W3C : Dialect.OSS;
124+
return Optional.of(
125+
new Result(dialect, String.valueOf(sessionId), capabilities));
126+
}
127+
}
128+
129+
// If the result was an error that we believe has to do with the remote end failing to start the
130+
// session, create an exception and throw it.
131+
Response tempResponse = null;
132+
if ("session not created".equals(w3cError)) {
133+
tempResponse = new Response(null);
134+
tempResponse.setStatus(ErrorCodes.SESSION_NOT_CREATED);
135+
tempResponse.setValue(jsonBlob);
136+
} else if (
137+
ossStatus instanceof Number &&
138+
((Number) ossStatus).intValue() == ErrorCodes.SESSION_NOT_CREATED) {
139+
tempResponse = new Response(null);
140+
tempResponse.setStatus(ErrorCodes.SESSION_NOT_CREATED);
141+
tempResponse.setValue(jsonBlob);
142+
}
143+
144+
if (tempResponse != null) {
145+
new ErrorHandler(true).throwIfResponseFailed(tempResponse, 0);
146+
}
147+
148+
// Otherwise, just return empty.
149+
return Optional.empty();
150+
}
151+
152+
private void amendW3CParameters(
153+
Map<String, Object> params,
154+
Capabilities desired,
155+
Capabilities required) {
156+
HashMap<String, Object> caps = new HashMap<>();
157+
caps.put("desiredCapabilities", desired);
158+
caps.put("requiredCapabilities", required);
159+
160+
params.put("capabilities", caps);
161+
}
162+
163+
private void amendOssParamters(
164+
Map<String, Object> params,
165+
Capabilities desired,
166+
Capabilities required) {
167+
params.put("desiredCapabilities", desired);
168+
params.put("requiredCapabilities", required);
169+
}
170+
171+
172+
public class Result {
173+
private final Dialect dialect;
174+
private final Map<String, ?> capabilities;
175+
private final SessionId sessionId;
176+
177+
private Result(Dialect dialect, String sessionId, Map<String, ?> capabilities) {
178+
this.dialect = dialect;
179+
this.sessionId = new SessionId(Preconditions.checkNotNull(sessionId));
180+
this.capabilities = capabilities;
181+
}
182+
183+
public Dialect getDialect() {
184+
return dialect;
185+
}
186+
187+
public Response createResponse() {
188+
Response response = new Response(sessionId);
189+
response.setValue(capabilities);
190+
response.setStatus(ErrorCodes.SUCCESS);
191+
return response;
192+
}
193+
194+
@Override
195+
public String toString() {
196+
return String.format("%s: %s", dialect, capabilities);
197+
}
198+
}
199+
}

0 commit comments

Comments
 (0)