Skip to content

Commit d1cf13c

Browse files
committed
Make HttpRequest support query parameters.
1 parent 979ae96 commit d1cf13c

File tree

7 files changed

+244
-13
lines changed

7 files changed

+244
-13
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ java_library(name = 'remote-lib',
122122
'http/W3CHttpResponseCodec.java',
123123
'internal/ApacheHttpClient.java',
124124
'internal/HttpClientFactory.java',
125+
'internal/HttpUrlBuilder.java',
125126
'internal/JreHttpClient.java',
126127
'internal/OkHttpClient.java',
127128
'internal/JsonToWebElementConverter.java',

java/client/src/org/openqa/selenium/remote/http/HttpRequest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,20 @@
1717

1818
package org.openqa.selenium.remote.http;
1919

20+
import com.google.common.collect.Lists;
21+
import com.google.common.collect.Maps;
22+
import com.google.common.collect.Multimap;
23+
import com.google.common.collect.Multimaps;
24+
25+
import java.util.Collection;
26+
import java.util.Objects;
27+
2028
public class HttpRequest extends HttpMessage {
2129

2230
private final HttpMethod method;
2331
private final String uri;
32+
private final Multimap<String, String> queryParameters = Multimaps.newListMultimap(
33+
Maps.<String, Collection<String>>newHashMap(), Lists::newLinkedList);
2434

2535
public HttpRequest(HttpMethod method, String uri) {
2636
this.method = method;
@@ -34,4 +44,29 @@ public String getUri() {
3444
public HttpMethod getMethod() {
3545
return method;
3646
}
47+
48+
/**
49+
* Get a query parameter. The implementation will take care of decoding the from percent encoding.
50+
*/
51+
public String getQueryParameter(String name) {
52+
return queryParameters.get(name).stream().findFirst().orElse(null);
53+
}
54+
55+
/**
56+
* Set a query parameter, adding to existing values if present. The implementation will ensure
57+
* that the name and value are properly encoded.
58+
*/
59+
public void addQueryParameter(String name, String value) {
60+
queryParameters.put(
61+
Objects.requireNonNull(name, "Name must be set"),
62+
Objects.requireNonNull(value, "Value must be set"));
63+
}
64+
65+
public Iterable<String> getQueryParameterNames() {
66+
return queryParameters.keySet();
67+
}
68+
69+
public Iterable<String> getQueryParameters(String name) {
70+
return queryParameters.get(name);
71+
}
3772
}

java/client/src/org/openqa/selenium/remote/internal/ApacheHttpClient.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ public HttpResponse execute(HttpRequest request) throws IOException {
7373
public HttpResponse execute(HttpRequest request, boolean followRedirects) throws IOException {
7474
HttpContext context = createContext();
7575

76-
String requestUrl = url.toExternalForm().replaceAll("/$", "") + request.getUri();
77-
HttpUriRequest httpMethod = createHttpUriRequest(request.getMethod(), requestUrl);
76+
URL url = HttpUrlBuilder.toUrl(this.url, request);
77+
HttpUriRequest httpMethod = createHttpUriRequest(request.getMethod(), url);
7878
for (String name : request.getHeaderNames()) {
7979
// Skip content length as it is implicitly set when the message entity is set below.
8080
if (!"Content-Length".equalsIgnoreCase(name)) {
@@ -125,14 +125,22 @@ protected HttpContext createContext() {
125125
return new BasicHttpContext();
126126
}
127127

128-
private static HttpUriRequest createHttpUriRequest(HttpMethod method, String url) {
128+
private static HttpUriRequest createHttpUriRequest(HttpMethod method, URL url)
129+
throws IOException {
130+
URI uri = null;
131+
try {
132+
uri = url.toURI();
133+
} catch (URISyntaxException e) {
134+
throw new IOException(e);
135+
}
136+
129137
switch (method) {
130138
case DELETE:
131-
return new HttpDelete(url);
139+
return new HttpDelete(uri);
132140
case GET:
133-
return new HttpGet(url);
141+
return new HttpGet(uri);
134142
case POST:
135-
return new HttpPost(url);
143+
return new HttpPost(uri);
136144
}
137145
throw new AssertionError("Unsupported method: " + method);
138146
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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 java.nio.charset.StandardCharsets.US_ASCII;
21+
22+
import com.google.common.base.Joiner;
23+
24+
import org.openqa.selenium.remote.http.HttpRequest;
25+
26+
import java.io.UncheckedIOException;
27+
import java.io.UnsupportedEncodingException;
28+
import java.net.MalformedURLException;
29+
import java.net.URL;
30+
import java.net.URLEncoder;
31+
import java.util.List;
32+
import java.util.function.Function;
33+
import java.util.stream.Collectors;
34+
import java.util.stream.StreamSupport;
35+
36+
class HttpUrlBuilder {
37+
38+
private final static Function<String, String> QUERY_ENCODE = str -> {
39+
try {
40+
return URLEncoder.encode(str, US_ASCII.toString());
41+
} catch (UnsupportedEncodingException e) {
42+
throw new UncheckedIOException(e);
43+
}
44+
};
45+
46+
private HttpUrlBuilder() {
47+
// Helper class
48+
}
49+
50+
static URL toUrl(URL base, HttpRequest request) throws MalformedURLException {
51+
StringBuilder queryString = new StringBuilder();
52+
Joiner parameters = Joiner.on("&");
53+
54+
List<String>
55+
allParams = StreamSupport.stream(request.getQueryParameterNames().spliterator(), false)
56+
.map(name -> {
57+
String encoded = QUERY_ENCODE.apply(name);
58+
return parameters.join(
59+
StreamSupport.stream(request.getQueryParameters(name).spliterator(), false)
60+
.map(value -> encoded + "=" + QUERY_ENCODE.apply(value))
61+
.collect(Collectors.toList()));
62+
})
63+
.collect(Collectors.toList());
64+
parameters.appendTo(queryString, allParams);
65+
66+
String baseUrl = base.toExternalForm().replaceAll("/$", "") + request.getUri();
67+
if (!queryString.toString().isEmpty()) {
68+
baseUrl += "?" + queryString.toString();
69+
}
70+
71+
return new URL(baseUrl);
72+
}
73+
}

java/client/src/org/openqa/selenium/remote/internal/JreHttpClient.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package org.openqa.selenium.remote.internal;
1919

20+
import static java.nio.charset.StandardCharsets.US_ASCII;
2021
import static java.nio.charset.StandardCharsets.UTF_8;
2122

2223
import org.openqa.selenium.remote.http.HttpClient;
@@ -28,15 +29,26 @@
2829
import java.io.IOException;
2930
import java.io.InputStream;
3031
import java.io.OutputStream;
32+
import java.io.UncheckedIOException;
33+
import java.io.UnsupportedEncodingException;
3134
import java.net.HttpURLConnection;
3235
import java.net.URL;
36+
import java.net.URLEncoder;
3337
import java.util.Base64;
3438
import java.util.List;
3539
import java.util.Map;
3640
import java.util.Objects;
41+
import java.util.function.Function;
3742

3843
public class JreHttpClient implements HttpClient {
3944

45+
private final static Function<String, String> QUERY_ENCODE = str -> {
46+
try {
47+
return URLEncoder.encode(str, US_ASCII.toString());
48+
} catch (UnsupportedEncodingException e) {
49+
throw new UncheckedIOException(e);
50+
}
51+
};
4052
private final URL url;
4153
private final String auth;
4254

@@ -61,7 +73,7 @@ public HttpResponse execute(HttpRequest request) throws IOException {
6173

6274
@Override
6375
public HttpResponse execute(HttpRequest request, boolean followRedirects) throws IOException {
64-
URL url = new URL(this.url.toString() + request.getUri());
76+
URL url = HttpUrlBuilder.toUrl(this.url, request);
6577

6678
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
6779
try {

java/client/src/org/openqa/selenium/remote/internal/OkHttpClient.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.openqa.selenium.remote.http.HttpResponse;
2323

2424
import okhttp3.ConnectionPool;
25+
import okhttp3.HttpUrl;
2526
import okhttp3.MediaType;
2627
import okhttp3.Request;
2728
import okhttp3.RequestBody;
@@ -54,7 +55,20 @@ public HttpResponse execute(HttpRequest request, boolean followRedirects) throws
5455

5556
Request.Builder builder = new Request.Builder();
5657

57-
builder.url(new URL(baseUrl.toString() + request.getUri()));
58+
HttpUrl.Builder url;
59+
try {
60+
url = HttpUrl.parse(baseUrl.toString() + request.getUri()).newBuilder();
61+
} catch (NullPointerException e) {
62+
throw new IOException("Unable to parse URL: " + baseUrl.toString() + request.getUri());
63+
}
64+
65+
for (String name : request.getQueryParameterNames()) {
66+
for (String value : request.getQueryParameters(name)) {
67+
url.addQueryParameter(name, value);
68+
}
69+
}
70+
71+
builder.url(url.build());
5872

5973
for (String name : request.getHeaderNames()) {
6074
for (String value : request.getHeaders(name)) {

java/client/test/org/openqa/selenium/remote/internal/HttpClientTestBase.java

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,31 @@
1818
package org.openqa.selenium.remote.internal;
1919

2020
import static org.junit.Assert.assertEquals;
21+
import static org.junit.Assert.assertNull;
2122
import static org.junit.Assert.assertTrue;
23+
import static org.openqa.selenium.json.Json.MAP_TYPE;
24+
import static org.openqa.selenium.remote.http.HttpMethod.GET;
2225

2326
import com.google.common.collect.HashMultimap;
2427
import com.google.common.collect.ImmutableList;
2528
import com.google.common.collect.Multimap;
2629

2730
import org.junit.Test;
31+
import org.openqa.selenium.json.Json;
32+
import org.openqa.selenium.json.JsonOutput;
2833
import org.openqa.selenium.net.PortProber;
2934
import org.openqa.selenium.remote.http.HttpClient;
30-
import org.openqa.selenium.remote.http.HttpMethod;
3135
import org.openqa.selenium.remote.http.HttpRequest;
3236
import org.openqa.selenium.remote.http.HttpResponse;
3337
import org.seleniumhq.jetty9.server.Server;
3438
import org.seleniumhq.jetty9.servlet.ServletContextHandler;
3539
import org.seleniumhq.jetty9.servlet.ServletHolder;
3640

3741
import java.io.IOException;
42+
import java.io.Writer;
43+
import java.util.Map;
44+
import java.util.stream.Stream;
3845

39-
import javax.servlet.ServletException;
4046
import javax.servlet.http.HttpServlet;
4147
import javax.servlet.http.HttpServletRequest;
4248
import javax.servlet.http.HttpServletResponse;
@@ -76,6 +82,53 @@ public void responseShouldKeepMultipleHeadersSeparate() throws Exception {
7682
assertTrue(values.toString(), values.contains("Brie, Gouda"));
7783
}
7884

85+
@Test
86+
public void shouldAddUrlParameters() throws Exception {
87+
HttpRequest request = new HttpRequest(GET, "/query");
88+
String value = request.getQueryParameter("cheese");
89+
assertNull(value);
90+
91+
request.addQueryParameter("cheese", "brie");
92+
value = request.getQueryParameter("cheese");
93+
assertEquals("brie", value);
94+
}
95+
96+
@Test
97+
public void shouldSendSimpleQueryParameters() throws Exception {
98+
HttpRequest request = new HttpRequest(GET, "/query");
99+
request.addQueryParameter("cheese", "cheddar");
100+
101+
HttpResponse response = getQueryParameterResponse(request);
102+
Map<String, Object> values = new Json().toType(response.getContentString(), MAP_TYPE);
103+
104+
assertEquals(ImmutableList.of("cheddar"), values.get("cheese"));
105+
}
106+
107+
@Test
108+
public void shouldEncodeParameterNamesAndValues() throws Exception {
109+
HttpRequest request = new HttpRequest(GET, "/query");
110+
request.addQueryParameter("cheese type", "tasty cheese");
111+
112+
HttpResponse response = getQueryParameterResponse(request);
113+
Map<String, Object> values = new Json().toType(response.getContentString(), MAP_TYPE);
114+
115+
assertEquals(ImmutableList.of("tasty cheese"), values.get("cheese type"));
116+
}
117+
118+
@Test
119+
public void canAddMoreThanOneQueryParameter() throws Exception {
120+
HttpRequest request = new HttpRequest(GET, "/query");
121+
request.addQueryParameter("cheese", "cheddar");
122+
request.addQueryParameter("cheese", "gouda");
123+
request.addQueryParameter("vegetable", "peas");
124+
125+
HttpResponse response = getQueryParameterResponse(request);
126+
Map<String, Object> values = new Json().toType(response.getContentString(), MAP_TYPE);
127+
128+
assertEquals(ImmutableList.of("cheddar", "gouda"), values.get("cheese"));
129+
assertEquals(ImmutableList.of("peas"), values.get("vegetable"));
130+
}
131+
79132
private HttpResponse getResponseWithHeaders(final Multimap<String, String> headers)
80133
throws Exception {
81134
Server server = new Server(PortProber.findFreePort());
@@ -84,8 +137,7 @@ private HttpResponse getResponseWithHeaders(final Multimap<String, String> heade
84137

85138
class Headers extends HttpServlet {
86139
@Override
87-
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
88-
throws ServletException, IOException {
140+
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
89141
headers.forEach(resp::addHeader);
90142
resp.setContentLengthLong(0);
91143
}
@@ -98,7 +150,43 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
98150
server.start();
99151
try {
100152
HttpClient client = createFactory().createClient(server.getURI().toURL());
101-
HttpRequest request = new HttpRequest(HttpMethod.GET, "/foo");
153+
HttpRequest request = new HttpRequest(GET, "/foo");
154+
return client.execute(request);
155+
} finally {
156+
server.stop();
157+
}
158+
}
159+
160+
private HttpResponse getQueryParameterResponse(HttpRequest request) throws Exception {
161+
Server server = new Server(PortProber.findFreePort());
162+
ServletContextHandler handler = new ServletContextHandler();
163+
handler.setContextPath("");
164+
165+
class Parameters extends HttpServlet {
166+
@Override
167+
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
168+
try (Writer writer = resp.getWriter()) {
169+
JsonOutput json = new Json().newOutput(writer);
170+
json.beginObject();
171+
req.getParameterMap()
172+
.forEach((key, value) -> {
173+
json.name(key);
174+
json.beginArray();
175+
Stream.of(value).forEach(v -> json.write(v, String.class));
176+
json.endArray();
177+
});
178+
json.endObject();
179+
}
180+
}
181+
}
182+
ServletHolder holder = new ServletHolder(new Parameters());
183+
handler.addServlet(holder, "/*");
184+
185+
server.setHandler(handler);
186+
187+
server.start();
188+
try {
189+
HttpClient client = createFactory().createClient(server.getURI().toURL());
102190
return client.execute(request);
103191
} finally {
104192
server.stop();

0 commit comments

Comments
 (0)