Permalink
Browse files

Provide a pure JRE implementation of HttpClient

So we can avoid having to take a dependency on Apache
HttpClient.
  • Loading branch information...
shs96c committed Aug 30, 2017
1 parent 19df908 commit c74e42e928492293a2efab2f6943b81515dd1a27
@@ -0,0 +1,114 @@
// 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.remote.internal;
import org.openqa.selenium.remote.http.HttpClient;
import org.openqa.selenium.remote.http.HttpMethod;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class JreHttpClient implements HttpClient {
private final URL url;
private JreHttpClient(URL url) {
if (!url.getProtocol().toLowerCase().startsWith("http")) {
throw new IllegalArgumentException("Base URL must be an http URL: " + url);
}
this.url = url;
}
@Override
public HttpResponse execute(HttpRequest request, boolean followRedirects) throws IOException {
URL url = new URL(this.url.toString() + request.getUri());
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
try {
connection.setInstanceFollowRedirects(followRedirects);
for (String name : request.getHeaderNames()) {
for (String value : request.getHeaders(name)) {
connection.addRequestProperty(name, value);
}
}
connection.setRequestProperty("Content-length", String.valueOf(request.getContent().length));
connection.setRequestMethod(request.getMethod().toString());
connection.setDoInput(true);
if (request.getMethod() == HttpMethod.POST) {
connection.setDoOutput(true);
try (OutputStream os = connection.getOutputStream()) {
os.write(request.getContent());
}
}
HttpResponse response = new HttpResponse();
response.setStatus(connection.getResponseCode());
for (Map.Entry<String, List<String>> entry : connection.getHeaderFields().entrySet()) {
// HttpURLConnection stores headers in a HashMap. The order they come back is basically
// random.
for (String value : entry.getValue()) {
response.addHeader(entry.getKey(), value);
}
}
InputStream is = connection.getErrorStream();
if (is == null) {
is = connection.getInputStream();
}
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
int count;
byte[] data = new byte[1024];
while ((count = is.read(data, 0, data.length)) != -1) {
bos.write(data, 0, count);
}
bos.flush();
response.setContent(bos.toByteArray());
} finally {
is.close();
}
return response;
} finally {
connection.disconnect();
}
}
@Override
public void close() throws IOException {
}
public static class Factory implements HttpClient.Factory {
@Override
public HttpClient createClient(URL url) {
return new JreHttpClient(Objects.requireNonNull(url, "Base URL must be set"));
}
}
}
@@ -0,0 +1,105 @@
// 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.remote.internal;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import org.junit.Test;
import org.openqa.selenium.net.PortProber;
import org.openqa.selenium.remote.http.HttpClient;
import org.openqa.selenium.remote.http.HttpMethod;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;
import org.seleniumhq.jetty9.server.Server;
import org.seleniumhq.jetty9.servlet.ServletContextHandler;
import org.seleniumhq.jetty9.servlet.ServletHolder;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class JreHttpClientTest {
@Test
public void responseShouldCaptureASingleHeader() throws Exception {
HashMultimap<String, String> headers = HashMultimap.create();
headers.put("Cake", "Delicious");
HttpResponse response = getResponseWithHeaders(headers);
String value = response.getHeader("Cake");
assertEquals("Delicious", value);
}
/**
* The HTTP Spec that it should be
* <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">safe to combine them
* </a>, but things like the <a href="https://www.ietf.org/rfc/rfc2109.txt">cookie spec</a> make
* this hard (notably when a legal value may contain a comma).
*/
@Test
public void responseShouldKeepMultipleHeadersSeparate() throws Exception {
HashMultimap<String, String> headers = HashMultimap.create();
headers.put("Cheese", "Cheddar");
headers.put("Cheese", "Brie, Gouda");
HttpResponse response = getResponseWithHeaders(headers);
ImmutableList<String> values = ImmutableList.copyOf(response.getHeaders("Cheese"));
assertTrue(values.contains("Cheddar"));
assertTrue(values.contains("Brie, Gouda"));
}
private HttpResponse getResponseWithHeaders(final Multimap<String, String> headers)
throws Exception {
Server server = new Server(PortProber.findFreePort());
ServletContextHandler handler = new ServletContextHandler();
handler.setContextPath("");
class Headers extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
headers.forEach(resp::addHeader);
resp.setContentLengthLong(0);
}
}
ServletHolder holder = new ServletHolder(new Headers());
handler.addServlet(holder, "/*");
server.setHandler(handler);
server.start();
try {
HttpClient client = new JreHttpClient.Factory().createClient(server.getURI().toURL());
HttpRequest request = new HttpRequest(HttpMethod.GET, "/foo");
return client.execute(request, true);
} finally {
server.stop();
}
}
}

0 comments on commit c74e42e

Please sign in to comment.