|
| 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