diff --git a/httpclient5-fluent/src/main/java/org/apache/hc/client5/http/fluent/Request.java b/httpclient5-fluent/src/main/java/org/apache/hc/client5/http/fluent/Request.java index afc3900397..6eba5bac5b 100644 --- a/httpclient5-fluent/src/main/java/org/apache/hc/client5/http/fluent/Request.java +++ b/httpclient5-fluent/src/main/java/org/apache/hc/client5/http/fluent/Request.java @@ -170,6 +170,20 @@ public static Request options(final String uri) { return new Request(new BasicClassicHttpRequest(Method.OPTIONS, uri)); } + /** + * @since 5.7 + */ + public static Request query(final URI uri) { + return new Request(new BasicClassicHttpRequest(Method.QUERY, uri)); + } + + /** + * @since 5.7 + */ + public static Request query(final String uri) { + return new Request(new BasicClassicHttpRequest(Method.QUERY, uri)); + } + Request(final ClassicHttpRequest request) { super(); this.request = request; diff --git a/httpclient5-fluent/src/test/java/org/apache/hc/client5/http/fluent/TestRequest.java b/httpclient5-fluent/src/test/java/org/apache/hc/client5/http/fluent/TestRequest.java index 96693e6648..1e3fdc68d1 100644 --- a/httpclient5-fluent/src/test/java/org/apache/hc/client5/http/fluent/TestRequest.java +++ b/httpclient5-fluent/src/test/java/org/apache/hc/client5/http/fluent/TestRequest.java @@ -51,6 +51,7 @@ static Stream data() { Arguments.of("patch", "PATCH"), Arguments.of("post", "POST"), Arguments.of("put", "PUT"), + Arguments.of("query", "QUERY"), Arguments.of("trace", "TRACE") ); } diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/EchoHandler.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/EchoHandler.java index 7ceb218f46..5968725099 100644 --- a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/EchoHandler.java +++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/EchoHandler.java @@ -68,7 +68,8 @@ public void handle(final ClassicHttpRequest request, if (!"GET".equals(method) && !"HEAD".equals(method) && !"POST".equals(method) && - !"PUT".equals(method)) { + !"PUT".equals(method) && + !"QUERY".equals(method)) { throw new MethodNotSupportedException(method + " not supported by " + getClass().getName()); } diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestRedirects.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestRedirects.java index 511c980041..e99a0a0f34 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestRedirects.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestRedirects.java @@ -813,4 +813,62 @@ void testCrossSiteRedirectWithSensitiveHeadersAndLaxRedirectStrategy(final Strin } } -} + @Test + void testQueryRedirectSeeOther() throws Exception { + configureServer(bootstrap -> bootstrap + .setExchangeHandlerDecorator(requestHandler -> new RedirectingDecorator( + requestHandler, + new OldPathRedirectResolver("/oldlocation", "/echo", HttpStatus.SC_SEE_OTHER))) + .register("/echo/*", new EchoHandler())); + final HttpHost target = startServer(); + + final TestClient client = client(); + final HttpClientContext context = HttpClientContext.create(); + + final ClassicHttpRequest httpQuery = ClassicRequestBuilder.create("QUERY") + .setPath("/oldlocation/stuff") + .setEntity(new StringEntity("stuff")) + .build(); + + client.execute(target, httpQuery, context, response -> { + Assertions.assertEquals(HttpStatus.SC_OK, response.getCode()); + EntityUtils.consume(response.getEntity()); + return null; + }); + final HttpRequest reqWrapper = context.getRequest(); + + Assertions.assertEquals(new URIBuilder().setHttpHost(target).setPath("/echo/stuff").build(), + reqWrapper.getUri()); + Assertions.assertEquals("GET", reqWrapper.getMethod()); + } + + @Test + void testQueryRedirectTemporary() throws Exception { + configureServer(bootstrap -> bootstrap + .setExchangeHandlerDecorator(requestHandler -> new RedirectingDecorator( + requestHandler, + new OldPathRedirectResolver("/oldlocation", "/echo", HttpStatus.SC_TEMPORARY_REDIRECT))) + .register("/echo/*", new EchoHandler())); + final HttpHost target = startServer(); + + final TestClient client = client(); + final HttpClientContext context = HttpClientContext.create(); + + final ClassicHttpRequest httpQuery = ClassicRequestBuilder.create("QUERY") + .setPath("/oldlocation/stuff") + .setEntity(new StringEntity("stuff")) + .build(); + + client.execute(target, httpQuery, context, response -> { + Assertions.assertEquals(HttpStatus.SC_OK, response.getCode()); + Assertions.assertEquals("stuff", EntityUtils.toString(response.getEntity())); + return null; + }); + final HttpRequest reqWrapper = context.getRequest(); + + Assertions.assertEquals(new URIBuilder().setHttpHost(target).setPath("/echo/stuff").build(), + reqWrapper.getUri()); + Assertions.assertEquals("QUERY", reqWrapper.getMethod()); + } + +} \ No newline at end of file diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/async/methods/SimpleRequestBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/async/methods/SimpleRequestBuilder.java index a6acd70477..ee0360b05e 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/async/methods/SimpleRequestBuilder.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/async/methods/SimpleRequestBuilder.java @@ -170,6 +170,18 @@ public static SimpleRequestBuilder delete(final String uri) { return new SimpleRequestBuilder(Method.DELETE, uri); } + public static SimpleRequestBuilder query() { + return new SimpleRequestBuilder(Method.QUERY); + } + + public static SimpleRequestBuilder query(final URI uri) { + return new SimpleRequestBuilder(Method.QUERY, uri); + } + + public static SimpleRequestBuilder query(final String uri) { + return new SimpleRequestBuilder(Method.QUERY, uri); + } + public static SimpleRequestBuilder trace() { return new SimpleRequestBuilder(Method.TRACE); } @@ -374,7 +386,7 @@ public SimpleHttpRequest build() { final List parameters = getParameters(); if (parameters != null && !parameters.isEmpty()) { final Charset charsetCopy = getCharset(); - if (bodyCopy == null && (Method.POST.isSame(method) || Method.PUT.isSame(method))) { + if (bodyCopy == null && (Method.POST.isSame(method) || Method.PUT.isSame(method) || Method.QUERY.isSame(method))) { final String content = WWWFormCodec.format( parameters, charsetCopy != null ? charsetCopy : ContentType.APPLICATION_FORM_URLENCODED.getCharset()); diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/classic/methods/HttpQuery.java b/httpclient5/src/main/java/org/apache/hc/client5/http/classic/methods/HttpQuery.java new file mode 100644 index 0000000000..2bd1c782fa --- /dev/null +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/classic/methods/HttpQuery.java @@ -0,0 +1,66 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.classic.methods; + +import java.net.URI; + +/** + * HTTP QUERY method. + * + * @since 5.7 + */ +public class HttpQuery extends HttpUriRequestBase { + + private static final long serialVersionUID = 1L; + + /** + * The method name {@value}. + */ + public static final String METHOD_NAME = "QUERY"; + + /** + * Constructs a new instance initialized with the given URI. + * + * @param uri a non-null request URI. + * @throws IllegalArgumentException if the uri is null. + */ + public HttpQuery(final URI uri) { + super(METHOD_NAME, uri); + } + + /** + * Constructs a new instance initialized with the given URI. + * + * @param uri a non-null request URI. + * @throws IllegalArgumentException if the uri is invalid. + */ + public HttpQuery(final String uri) { + this(URI.create(uri)); + } + +} diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/async/methods/TestSimpleMessageBuilders.java b/httpclient5/src/test/java/org/apache/hc/client5/http/async/methods/TestSimpleMessageBuilders.java index cc326eb469..c13a305609 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/async/methods/TestSimpleMessageBuilders.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/async/methods/TestSimpleMessageBuilders.java @@ -273,4 +273,25 @@ void testPostParameters() { Assertions.assertEquals("p1=v1&p2=v2&p3=v3&p3=v3.1", request.getBody().getBodyText()); } + @Test + void testQueryParameters() { + final SimpleRequestBuilder builder = SimpleRequestBuilder.query(URI.create("https://host:3456/stuff?p0=p0")); + builder.addParameter("p1", "v1"); + builder.addParameters(new BasicNameValuePair("p2", "v2"), new BasicNameValuePair("p3", "v3")); + builder.addParameter(new BasicNameValuePair("p3", "v3.1")); + Assertions.assertEquals("QUERY", builder.getMethod()); + Assertions.assertEquals("https", builder.getScheme()); + Assertions.assertEquals(new URIAuthority("host", 3456), builder.getAuthority()); + Assertions.assertEquals("/stuff?p0=p0", builder.getPath()); + NameValuePairsMatcher.assertSame(builder.getParameters(), + new BasicNameValuePair("p1", "v1"), new BasicNameValuePair("p2", "v2"), + new BasicNameValuePair("p3", "v3"), new BasicNameValuePair("p3", "v3.1")); + final SimpleHttpRequest request = builder.build(); + Assertions.assertEquals("/stuff?p0=p0", request.getPath()); + Assertions.assertNotNull(request.getBody()); + ContentTypeMatcher.assertSameMimeType(request.getBody().getContentType(), + ContentType.APPLICATION_FORM_URLENCODED); + Assertions.assertEquals("p1=v1&p2=v2&p3=v3&p3=v3.1", request.getBody().getBodyText()); + } + } diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/classic/methods/TestHttpRequestBase.java b/httpclient5/src/test/java/org/apache/hc/client5/http/classic/methods/TestHttpRequestBase.java index 7854d8acf2..819ebf143a 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/classic/methods/TestHttpRequestBase.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/classic/methods/TestHttpRequestBase.java @@ -108,6 +108,13 @@ void testBasicHttpDeleteMethodProperties() throws Exception { Assertions.assertEquals(new URI(HOT_URL), httpDelete.getUri()); } + @Test + void testBasicQueryMethodProperties() throws Exception { + final HttpQuery httpQuery = new HttpQuery(HOT_URL); + Assertions.assertEquals("QUERY", httpQuery.getMethod()); + Assertions.assertEquals(new URI(HOT_URL), httpQuery.getUri()); + } + @Test void testGetMethodEmptyURI() throws Exception { @@ -158,6 +165,12 @@ void testDeleteMethodEmptyURI() throws Exception { Assertions.assertEquals(new URI("/"), httpDelete.getUri()); } + @Test + void testQueryMethodEmptyURI() throws Exception { + final HttpQuery httpQuery = new HttpQuery(""); + Assertions.assertEquals(new URI("/"), httpQuery.getUri()); + } + @Test void testTraceMethodSetEntity() { @@ -169,15 +182,15 @@ void testTraceMethodSetEntity() { @Test void testOptionMethodGetAllowedMethods() { final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK, "OK"); - response.addHeader("Allow", "GET, HEAD"); + response.addHeader("Allow", "GET, HEAD, QUERY"); response.addHeader("Allow", "DELETE"); response.addHeader("Content-Length", "128"); final HttpOptions httpOptions = new HttpOptions(""); final Set methods = httpOptions.getAllowedMethods(response); assertAll("Must all pass", () -> assertFalse(methods.isEmpty()), - () -> assertEquals(3, methods.size()), - () -> assertTrue(methods.containsAll(Stream.of("HEAD", "DELETE", "GET") + () -> assertEquals(4, methods.size()), + () -> assertTrue(methods.containsAll(Stream.of("HEAD", "DELETE", "GET", "QUERY") .collect(Collectors.toCollection(HashSet::new)))) ); } diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestDefaultHttpRequestRetryStrategy.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestDefaultHttpRequestRetryStrategy.java index bdc8e94235..0e335cbcda 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestDefaultHttpRequestRetryStrategy.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestDefaultHttpRequestRetryStrategy.java @@ -35,6 +35,7 @@ import java.time.temporal.ChronoUnit; import javax.net.ssl.SSLException; import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpQuery; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.http.utils.DateUtils; @@ -188,6 +189,13 @@ void retryOnNonAbortedRequests() { Assertions.assertTrue(retryStrategy.retryRequest(request, new IOException(), 1, null)); } + @Test + void retryOnQueryRequests() { + final HttpQuery request = new HttpQuery("/"); + + Assertions.assertTrue(retryStrategy.retryRequest(request, new IOException(), 1, null)); + } + @Test void testRetryRequestWithResponseTimeoutAndRetryAfterHeader() { final HttpResponse response = new BasicHttpResponse(429, "Too Many Requests");