diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index 54b82f9ba01..3486c811039 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -49,12 +49,14 @@ import org.eclipse.jetty.client.api.Destination; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; +import org.eclipse.jetty.client.util.FormContentProvider; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; +import org.eclipse.jetty.util.Fields; import org.eclipse.jetty.util.Jetty; import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.SocketAddressResolver; @@ -325,6 +327,30 @@ public class HttpClient extends ContainerLifeCycle return newRequest(uri).send(); } + /** + * Performs a POST request to the specified URI with the given form parameters. + * + * @param uri the URI to POST + * @param fields the fields composing the form name/value pairs + * @return the {@link ContentResponse} for the request + */ + public ContentResponse FORM(String uri, Fields fields) throws InterruptedException, ExecutionException, TimeoutException + { + return FORM(URI.create(uri), fields); + } + + /** + * Performs a POST request to the specified URI with the given form parameters. + * + * @param uri the URI to POST + * @param fields the fields composing the form name/value pairs + * @return the {@link ContentResponse} for the request + */ + public ContentResponse FORM(URI uri, Fields fields) throws InterruptedException, ExecutionException, TimeoutException + { + return POST(uri).content(new FormContentProvider(fields)).send(); + } + /** * Creates a POST request to the specified URI. * diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java new file mode 100644 index 00000000000..7e4784bb8ca --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java @@ -0,0 +1,78 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; + +import org.eclipse.jetty.client.api.ContentProvider; +import org.eclipse.jetty.util.Fields; + +/** + * A {@link ContentProvider} for form uploads with the + * "application/x-www-form-urlencoded" content type. + */ +public class FormContentProvider extends StringContentProvider +{ + public FormContentProvider(Fields fields) + { + this(fields, StandardCharsets.UTF_8); + } + + public FormContentProvider(Fields fields, Charset charset) + { + super("application/x-www-form-urlencoded", convert(fields, charset), charset); + } + + public static String convert(Fields fields) + { + return convert(fields, StandardCharsets.UTF_8); + } + + public static String convert(Fields fields, Charset charset) + { + // Assume 32 chars between name and value. + StringBuilder builder = new StringBuilder(fields.getSize() * 32); + for (Fields.Field field : fields) + { + for (String value : field.getValues()) + { + if (builder.length() > 0) + builder.append("&"); + builder.append(field.getName()).append("=").append(encode(value, charset)); + } + } + return builder.toString(); + } + + private static String encode(String value, Charset charset) + { + try + { + return URLEncoder.encode(value, charset.name()); + } + catch (UnsupportedEncodingException x) + { + throw new UnsupportedCharsetException(charset.name()); + } + } +} diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/TypedContentProviderTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/TypedContentProviderTest.java new file mode 100644 index 00000000000..b8433e8f037 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/util/TypedContentProviderTest.java @@ -0,0 +1,141 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.util; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.AbstractHttpClientServerTest; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.Fields; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Test; + +public class TypedContentProviderTest extends AbstractHttpClientServerTest +{ + public TypedContentProviderTest(SslContextFactory sslContextFactory) + { + super(sslContextFactory); + } + + @Test + public void testFormContentProvider() throws Exception + { + final String name1 = "a"; + final String value1 = "1"; + final String name2 = "b"; + final String value2 = "2"; + final String value3 = "\u20AC"; + + start(new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + Assert.assertEquals("POST", request.getMethod()); + Assert.assertEquals(MimeTypes.Type.FORM_ENCODED.asString(), request.getContentType()); + Assert.assertEquals(value1, request.getParameter(name1)); + String[] values = request.getParameterValues(name2); + Assert.assertNotNull(values); + Assert.assertEquals(2, values.length); + Assert.assertThat(values, Matchers.arrayContainingInAnyOrder(value2, value3)); + } + }); + + Fields fields = new Fields(); + fields.put(name1, value1); + fields.add(name2, value2); + fields.add(name2, value3); + ContentResponse response = client.FORM(scheme + "://localhost:" + connector.getLocalPort(), fields); + + Assert.assertEquals(200, response.getStatus()); + } + + @Test + public void testFormContentProviderWithDifferentContentType() throws Exception + { + final String name1 = "a"; + final String value1 = "1"; + final String name2 = "b"; + final String value2 = "2"; + Fields fields = new Fields(); + fields.put(name1, value1); + fields.add(name2, value2); + final String content = FormContentProvider.convert(fields); + final String contentType = "text/plain;charset=UTF-8"; + + start(new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + Assert.assertEquals("POST", request.getMethod()); + Assert.assertEquals(contentType, request.getContentType()); + Assert.assertEquals(content, IO.toString(request.getInputStream())); + } + }); + + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .method(HttpMethod.POST) + .content(new FormContentProvider(fields)) + .header(HttpHeader.CONTENT_TYPE, contentType) + .send(); + + Assert.assertEquals(200, response.getStatus()); + } + + @Test + public void testTypedContentProviderWithNoContentType() throws Exception + { + final String content = "data"; + + start(new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + Assert.assertEquals("GET", request.getMethod()); + Assert.assertNull(request.getContentType()); + Assert.assertEquals(content, IO.toString(request.getInputStream())); + } + }); + + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .content(new StringContentProvider(null, content, StandardCharsets.UTF_8)) + .send(); + + Assert.assertEquals(200, response.getStatus()); + } +}