Skip to content

Commit c74e42e

Browse files
committed
Provide a pure JRE implementation of HttpClient
So we can avoid having to take a dependency on Apache HttpClient.
1 parent 19df908 commit c74e42e

File tree

2 files changed

+219
-0
lines changed

2 files changed

+219
-0
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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.internal;
19+
20+
import org.openqa.selenium.remote.http.HttpClient;
21+
import org.openqa.selenium.remote.http.HttpMethod;
22+
import org.openqa.selenium.remote.http.HttpRequest;
23+
import org.openqa.selenium.remote.http.HttpResponse;
24+
25+
import java.io.ByteArrayOutputStream;
26+
import java.io.IOException;
27+
import java.io.InputStream;
28+
import java.io.OutputStream;
29+
import java.net.HttpURLConnection;
30+
import java.net.URL;
31+
import java.util.List;
32+
import java.util.Map;
33+
import java.util.Objects;
34+
35+
public class JreHttpClient implements HttpClient {
36+
37+
private final URL url;
38+
39+
private JreHttpClient(URL url) {
40+
if (!url.getProtocol().toLowerCase().startsWith("http")) {
41+
throw new IllegalArgumentException("Base URL must be an http URL: " + url);
42+
}
43+
this.url = url;
44+
}
45+
46+
@Override
47+
public HttpResponse execute(HttpRequest request, boolean followRedirects) throws IOException {
48+
URL url = new URL(this.url.toString() + request.getUri());
49+
50+
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
51+
try {
52+
connection.setInstanceFollowRedirects(followRedirects);
53+
for (String name : request.getHeaderNames()) {
54+
for (String value : request.getHeaders(name)) {
55+
connection.addRequestProperty(name, value);
56+
}
57+
}
58+
connection.setRequestProperty("Content-length", String.valueOf(request.getContent().length));
59+
connection.setRequestMethod(request.getMethod().toString());
60+
connection.setDoInput(true);
61+
62+
if (request.getMethod() == HttpMethod.POST) {
63+
connection.setDoOutput(true);
64+
try (OutputStream os = connection.getOutputStream()) {
65+
os.write(request.getContent());
66+
}
67+
}
68+
69+
HttpResponse response = new HttpResponse();
70+
response.setStatus(connection.getResponseCode());
71+
72+
for (Map.Entry<String, List<String>> entry : connection.getHeaderFields().entrySet()) {
73+
// HttpURLConnection stores headers in a HashMap. The order they come back is basically
74+
// random.
75+
for (String value : entry.getValue()) {
76+
response.addHeader(entry.getKey(), value);
77+
}
78+
}
79+
80+
InputStream is = connection.getErrorStream();
81+
if (is == null) {
82+
is = connection.getInputStream();
83+
}
84+
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
85+
int count;
86+
byte[] data = new byte[1024];
87+
while ((count = is.read(data, 0, data.length)) != -1) {
88+
bos.write(data, 0, count);
89+
}
90+
bos.flush();
91+
response.setContent(bos.toByteArray());
92+
} finally {
93+
is.close();
94+
}
95+
96+
return response;
97+
} finally {
98+
connection.disconnect();
99+
}
100+
}
101+
102+
@Override
103+
public void close() throws IOException {
104+
105+
}
106+
107+
public static class Factory implements HttpClient.Factory {
108+
109+
@Override
110+
public HttpClient createClient(URL url) {
111+
return new JreHttpClient(Objects.requireNonNull(url, "Base URL must be set"));
112+
}
113+
}
114+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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.internal;
19+
20+
import static org.junit.Assert.assertEquals;
21+
import static org.junit.Assert.assertTrue;
22+
23+
import com.google.common.collect.HashMultimap;
24+
import com.google.common.collect.ImmutableList;
25+
import com.google.common.collect.Multimap;
26+
27+
import org.junit.Test;
28+
import org.openqa.selenium.net.PortProber;
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+
import org.seleniumhq.jetty9.server.Server;
34+
import org.seleniumhq.jetty9.servlet.ServletContextHandler;
35+
import org.seleniumhq.jetty9.servlet.ServletHolder;
36+
37+
import java.io.IOException;
38+
39+
import javax.servlet.ServletException;
40+
import javax.servlet.http.HttpServlet;
41+
import javax.servlet.http.HttpServletRequest;
42+
import javax.servlet.http.HttpServletResponse;
43+
44+
public class JreHttpClientTest {
45+
46+
@Test
47+
public void responseShouldCaptureASingleHeader() throws Exception {
48+
HashMultimap<String, String> headers = HashMultimap.create();
49+
headers.put("Cake", "Delicious");
50+
51+
HttpResponse response = getResponseWithHeaders(headers);
52+
53+
String value = response.getHeader("Cake");
54+
assertEquals("Delicious", value);
55+
}
56+
57+
/**
58+
* The HTTP Spec that it should be
59+
* <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">safe to combine them
60+
* </a>, but things like the <a href="https://www.ietf.org/rfc/rfc2109.txt">cookie spec</a> make
61+
* this hard (notably when a legal value may contain a comma).
62+
*/
63+
@Test
64+
public void responseShouldKeepMultipleHeadersSeparate() throws Exception {
65+
HashMultimap<String, String> headers = HashMultimap.create();
66+
headers.put("Cheese", "Cheddar");
67+
headers.put("Cheese", "Brie, Gouda");
68+
69+
HttpResponse response = getResponseWithHeaders(headers);
70+
71+
ImmutableList<String> values = ImmutableList.copyOf(response.getHeaders("Cheese"));
72+
73+
assertTrue(values.contains("Cheddar"));
74+
assertTrue(values.contains("Brie, Gouda"));
75+
}
76+
77+
private HttpResponse getResponseWithHeaders(final Multimap<String, String> headers)
78+
throws Exception {
79+
Server server = new Server(PortProber.findFreePort());
80+
ServletContextHandler handler = new ServletContextHandler();
81+
handler.setContextPath("");
82+
83+
class Headers extends HttpServlet {
84+
@Override
85+
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
86+
throws ServletException, IOException {
87+
headers.forEach(resp::addHeader);
88+
resp.setContentLengthLong(0);
89+
}
90+
}
91+
ServletHolder holder = new ServletHolder(new Headers());
92+
handler.addServlet(holder, "/*");
93+
94+
server.setHandler(handler);
95+
96+
server.start();
97+
try {
98+
HttpClient client = new JreHttpClient.Factory().createClient(server.getURI().toURL());
99+
HttpRequest request = new HttpRequest(HttpMethod.GET, "/foo");
100+
return client.execute(request, true);
101+
} finally {
102+
server.stop();
103+
}
104+
}
105+
}

0 commit comments

Comments
 (0)