406015 - Query parameters and POST queries.

Reworked the way query parameters are handled, making it more
consistent between request.path(...) and request.param(...).

Removed the hardcoding of passing parameters as body in POSTs.
This commit is contained in:
Simone Bordet 2013-05-05 18:47:43 +02:00
parent 07e9574edf
commit 7d5ac2918e
7 changed files with 365 additions and 89 deletions

View File

@ -63,6 +63,7 @@ import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.SocketAddressResolver;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -362,7 +363,7 @@ public class HttpClient extends ContainerLifeCycle
*/
public Request newRequest(String host, int port)
{
return newRequest(URI.create(address("http", host, port)));
return newRequest(address("http", host, port));
}
/**
@ -417,9 +418,11 @@ public class HttpClient extends ContainerLifeCycle
return newRequest;
}
private String address(String scheme, String host, int port)
protected String address(String scheme, String host, int port)
{
return scheme + "://" + host + ":" + port;
StringBuilder result = new StringBuilder();
URIUtil.appendSchemeHostPort(result, scheme, host, port);
return result.toString();
}
/**
@ -900,6 +903,13 @@ public class HttpClient extends ContainerLifeCycle
return encodingField;
}
protected String normalizeHost(String host)
{
if (host != null && host.matches("\\[.*\\]"))
return host.substring(1, host.length() - 1);
return host;
}
protected int normalizePort(String scheme, int port)
{
return port > 0 ? port : HttpScheme.HTTPS.is(scheme) ? 443 : 80;

View File

@ -18,14 +18,10 @@
package org.eclipse.jetty.client;
import java.io.UnsupportedEncodingException;
import java.net.HttpCookie;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@ -35,17 +31,14 @@ import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -172,39 +165,6 @@ public class HttpConnection extends AbstractConnection implements Connection
request.path(path);
}
Fields fields = request.getParams();
if (!fields.isEmpty())
{
StringBuilder params = new StringBuilder();
for (Iterator<Fields.Field> fieldIterator = fields.iterator(); fieldIterator.hasNext();)
{
Fields.Field field = fieldIterator.next();
String[] values = field.values();
for (int i = 0; i < values.length; ++i)
{
if (i > 0)
params.append("&");
params.append(field.name()).append("=");
params.append(urlEncode(values[i]));
}
if (fieldIterator.hasNext())
params.append("&");
}
// POST with no content, send parameters as body
if (method == HttpMethod.POST && request.getContent() == null)
{
request.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString());
request.content(new StringContentProvider(params.toString()));
}
else
{
path += "?";
path += params.toString();
request.path(path);
}
}
// If we are HTTP 1.1, add the Host header
if (version.getVersion() > 10)
{
@ -257,19 +217,6 @@ public class HttpConnection extends AbstractConnection implements Connection
}
}
private String urlEncode(String value)
{
String encoding = "UTF-8";
try
{
return URLEncoder.encode(value, encoding);
}
catch (UnsupportedEncodingException e)
{
throw new UnsupportedCharsetException(encoding);
}
}
public HttpExchange getExchange()
{
return exchange.get();

View File

@ -22,10 +22,12 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.UnsupportedCharsetException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -62,6 +64,7 @@ public class HttpRequest implements Request
private URI uri;
private String scheme;
private String path;
private String query;
private HttpMethod method;
private HttpVersion version;
private long idleTimeout;
@ -80,35 +83,15 @@ public class HttpRequest implements Request
this.client = client;
this.conversation = conversation;
scheme = uri.getScheme();
host = uri.getHost();
host = client.normalizeHost(uri.getHost());
port = client.normalizePort(scheme, uri.getPort());
path = uri.getRawPath();
String query = uri.getRawQuery();
if (query != null)
{
for (String nameValue : query.split("&"))
{
String[] parts = nameValue.split("=");
param(parts[0], parts.length < 2 ? "" : urlDecode(parts[1]));
}
}
query = uri.getRawQuery();
extractParams(query);
this.uri = buildURI();
followRedirects(client.isFollowRedirects());
}
private String urlDecode(String value)
{
String charset = "UTF-8";
try
{
return URLDecoder.decode(value, charset);
}
catch (UnsupportedEncodingException x)
{
throw new UnsupportedCharsetException(charset);
}
}
@Override
public long getConversationID()
{
@ -163,11 +146,24 @@ public class HttpRequest implements Request
@Override
public Request path(String path)
{
this.path = path;
URI uri = URI.create(path);
this.path = uri.getRawPath();
String query = uri.getRawQuery();
if (query != null)
{
this.query = query;
extractParams(query);
}
this.uri = buildURI();
return this;
}
@Override
public String getQuery()
{
return query;
}
@Override
public URI getURI()
{
@ -191,13 +187,14 @@ public class HttpRequest implements Request
public Request param(String name, String value)
{
params.add(name, value);
this.query = buildQuery();
return this;
}
@Override
public Fields getParams()
{
return params;
return new Fields(params, true);
}
@Override
@ -496,12 +493,73 @@ public class HttpRequest implements Request
return aborted;
}
private String buildQuery()
{
StringBuilder result = new StringBuilder();
for (Iterator<Fields.Field> iterator = params.iterator(); iterator.hasNext();)
{
Fields.Field field = iterator.next();
String[] values = field.values();
for (int i = 0; i < values.length; ++i)
{
if (i > 0)
result.append("&");
result.append(field.name()).append("=");
result.append(urlEncode(values[i]));
}
if (iterator.hasNext())
result.append("&");
}
return result.toString();
}
private String urlEncode(String value)
{
String encoding = "UTF-8";
try
{
return URLEncoder.encode(value, encoding);
}
catch (UnsupportedEncodingException e)
{
throw new UnsupportedCharsetException(encoding);
}
}
private void extractParams(String query)
{
if (query != null)
{
for (String nameValue : query.split("&"))
{
String[] parts = nameValue.split("=");
param(parts[0], parts.length < 2 ? "" : urlDecode(parts[1]));
}
}
}
private String urlDecode(String value)
{
String charset = "UTF-8";
try
{
return URLDecoder.decode(value, charset);
}
catch (UnsupportedEncodingException x)
{
throw new UnsupportedCharsetException(charset);
}
}
private URI buildURI()
{
String path = getPath();
String query = getQuery();
if (query != null)
path += "?" + query;
URI result = URI.create(path);
if (!result.isAbsolute())
result = URI.create(getScheme() + "://" + getHost() + ":" + getPort() + path);
result = URI.create(client.address(getScheme(), getHost(), getPort()) + path);
return result;
}

View File

@ -172,7 +172,11 @@ public class HttpSender implements AsyncContentProvider.Listener
{
ContentProvider requestContent = request.getContent();
long contentLength = requestContent == null ? -1 : requestContent.getLength();
requestInfo = new HttpGenerator.RequestInfo(request.getVersion(), request.getHeaders(), contentLength, request.getMethod().asString(), request.getPath());
String path = request.getPath();
String query = request.getQuery();
if (query != null)
path += "?" + query;
requestInfo = new HttpGenerator.RequestInfo(request.getVersion(), request.getHeaders(), contentLength, request.getMethod().asString(), path);
break;
}
case NEED_HEADER:

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.client.api;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.EventListener;
@ -86,18 +87,33 @@ public interface Request
Request method(HttpMethod method);
/**
* @return the path of this request, such as "/"
* @return the path of this request, such as "/" or "/path" - without the query
* @see #getQuery()
*/
String getPath();
/**
* @param path the path of this request, such as "/"
* Specifies the path - and possibly the query - of this request.
* If the query part is specified, parameter values must be properly
* {@link URLEncoder#encode(String, String) UTF-8 URL encoded}.
* For example, if the parameter value is the euro symbol &euro; then the
* query string must be "param=%E2%82%AC".
* For transparent encoding of parameter values, use {@link #param(String, String)}.
*
* @param path the path of this request, such as "/" or "/path?param=1"
* @return this request object
*/
Request path(String path);
/**
* @return the full URI of this request such as "http://host:port/path"
* @return the query string of this request such as "param=1"
* @see #getPath()
* @see #getParams()
*/
String getQuery();
/**
* @return the full URI of this request such as "http://host:port/path?param=1"
*/
URI getURI();
@ -118,6 +134,9 @@ public interface Request
Fields getParams();
/**
* Adds a query parameter with the given name and value.
* The value is {@link URLEncoder#encode(String, String) UTF-8 URL encoded}.
*
* @param name the name of the query parameter
* @param value the value of the query parameter
* @return this request object

View File

@ -20,7 +20,6 @@ package org.eclipse.jetty.client;
import java.io.IOException;
import java.net.Socket;
import java.net.URI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@ -94,8 +93,6 @@ public class ExternalSiteTest
{
Assert.assertTrue(result.isSucceeded());
Assert.assertEquals(200, result.getResponse().getStatus());
URI uri = result.getRequest().getURI();
Assert.assertTrue(uri.getPort() > 0);
latch2.countDown();
}
});

View File

@ -0,0 +1,241 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.client;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.junit.Test;
public class HttpClientURITest extends AbstractHttpClientServerTest
{
public HttpClientURITest(SslContextFactory sslContextFactory)
{
super(sslContextFactory);
}
@Test
public void testIPv6Host() throws Exception
{
start(new EmptyServerHandler());
String host = "::1";
Request request = client.newRequest(host, connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS);
Assert.assertEquals(host, request.getHost());
StringBuilder uri = new StringBuilder();
URIUtil.appendSchemeHostPort(uri, scheme, host, connector.getLocalPort());
Assert.assertEquals(uri.toString(), request.getURI().toString());
Assert.assertEquals(HttpStatus.OK_200, request.send().getStatus());
}
@Test
public void testPath() throws Exception
{
final String path = "/path";
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
Assert.assertEquals(path, request.getRequestURI());
}
});
Request request = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS)
.path(path);
Assert.assertEquals(path, request.getPath());
Assert.assertNull(request.getQuery());
Fields params = request.getParams();
Assert.assertEquals(0, params.size());
Assert.assertTrue(request.getURI().toString().endsWith(path));
ContentResponse response = request.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Test
public void testPathWithQuery() throws Exception
{
String name = "a";
String value = "1";
final String query = name + "=" + value;
final String path = "/path";
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
Assert.assertEquals(path, request.getRequestURI());
Assert.assertEquals(query, request.getQueryString());
}
});
Request request = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS)
.path(path + "?" + query);
Assert.assertEquals(path, request.getPath());
Assert.assertEquals(query, request.getQuery());
Fields params = request.getParams();
Assert.assertEquals(1, params.size());
Assert.assertEquals(value, params.get(name).value());
ContentResponse response = request.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Test
public void testPathWithParam() throws Exception
{
String name = "a";
String value = "1";
final String query = name + "=" + value;
final String path = "/path";
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
Assert.assertEquals(path, request.getRequestURI());
Assert.assertEquals(query, request.getQueryString());
}
});
Request request = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS)
.path(path)
.param(name, value);
Assert.assertEquals(path, request.getPath());
Assert.assertEquals(query, request.getQuery());
Fields params = request.getParams();
Assert.assertEquals(1, params.size());
Assert.assertEquals(value, params.get(name).value());
ContentResponse response = request.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Test
public void testPathWithQueryAndParam() throws Exception
{
String name1 = "a";
String value1 = "1";
String name2 = "b";
String value2 = "2";
final String query = name1 + "=" + value1 + "&" + name2 + "=" + value2;
final String path = "/path";
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
Assert.assertEquals(path, request.getRequestURI());
Assert.assertEquals(query, request.getQueryString());
}
});
Request request = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS)
.path(path + "?" + name1 + "=" + value1)
.param(name2, value2);
Assert.assertEquals(path, request.getPath());
Assert.assertEquals(query, request.getQuery());
Fields params = request.getParams();
Assert.assertEquals(2, params.size());
Assert.assertEquals(value1, params.get(name1).value());
Assert.assertEquals(value2, params.get(name2).value());
ContentResponse response = request.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Test
public void testPathWithQueryAndParamValueEncoded() throws Exception
{
final String name1 = "a";
final String value1 = "\u20AC";
final String encodedValue1 = URLEncoder.encode(value1, "UTF-8");
final String name2 = "b";
final String value2 = "\u00A5";
String encodedValue2 = URLEncoder.encode(value2, "UTF-8");
final String query = name1 + "=" + encodedValue1 + "&" + name2 + "=" + encodedValue2;
final String path = "/path";
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
Assert.assertEquals(path, request.getRequestURI());
Assert.assertEquals(query, request.getQueryString());
Assert.assertEquals(value1, request.getParameter(name1));
Assert.assertEquals(value2, request.getParameter(name2));
}
});
Request request = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS)
.path(path + "?" + name1 + "=" + encodedValue1)
.param(name2, value2);
Assert.assertEquals(path, request.getPath());
Assert.assertEquals(query, request.getQuery());
Fields params = request.getParams();
Assert.assertEquals(2, params.size());
Assert.assertEquals(value1, params.get(name1).value());
Assert.assertEquals(value2, params.get(name2).value());
ContentResponse response = request.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
}