diff --git a/httpcore5/src/main/java/org/apache/hc/core5/annotation/Experimental.java b/httpcore5/src/main/java/org/apache/hc/core5/annotation/Experimental.java index 6669be793..9781ca840 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/annotation/Experimental.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/annotation/Experimental.java @@ -36,7 +36,7 @@ * The field or method to which this annotation is applied is marked as experimental. */ @Documented -@Target({ElementType.METHOD, ElementType.TYPE}) +@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD}) @Retention(RetentionPolicy.CLASS) public @interface Experimental { } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/Method.java b/httpcore5/src/main/java/org/apache/hc/core5/http/Method.java index 731e72d72..787fb405f 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/Method.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/Method.java @@ -29,6 +29,7 @@ import java.util.Locale; +import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.util.Args; /** @@ -91,7 +92,21 @@ public enum Method { /** * The HTTP {@code PATCH} method is unsafe and non-idempotent. */ - PATCH(false, false); + PATCH(false, false), + + /** + * The HTTP {@code QUERY} method is safe and idempotent. + *

+ * {@code QUERY} is a method defined to represent a safe, idempotent request that carries a body. + * This allows clients to send a body while enjoying the {@code GET}-like properties (such as easy caching, + * safe CORS handling [if supported] and bookmarking), as well as the {@code POST}-like properties (such as + * being able to send a query in a richer format, and not being limited by URI length and escaping restrictions). + * + * @since 5.4 + */ + @Experimental + //("QUERY method is still in DRAFT status") + QUERY(true, true); private final boolean safe; private final boolean idempotent; diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/ClassicRequestBuilder.java b/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/ClassicRequestBuilder.java index 5dd0c1f2f..b6619bb54 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/ClassicRequestBuilder.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/io/support/ClassicRequestBuilder.java @@ -33,6 +33,7 @@ import java.util.Arrays; import java.util.List; +import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; @@ -120,6 +121,44 @@ public static ClassicRequestBuilder head(final String uri) { return new ClassicRequestBuilder(Method.HEAD, uri); } + /** + * Initializes a new {@link ClassicRequestBuilder} instance for the {@code QUERY} method. + * + * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. + * + * @since 5.4 + */ + @Experimental + public static ClassicRequestBuilder query() { + return new ClassicRequestBuilder(Method.QUERY); + } + + /** + * Initializes a new {@link ClassicRequestBuilder} instance for the {@code QUERY} method. + * + * @param uri the request URI. + * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. + * + * @since 5.4 + */ + @Experimental + public static ClassicRequestBuilder query(final URI uri) { + return new ClassicRequestBuilder(Method.QUERY, uri); + } + + /** + * Initializes a new {@link ClassicRequestBuilder} instance for the {@code QUERY} method. + * + * @param uri the request URI. + * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. + * + * @since 5.4 + */ + @Experimental + public static ClassicRequestBuilder query(final String uri) { + return new ClassicRequestBuilder(Method.QUERY, uri); + } + public static ClassicRequestBuilder patch() { return new ClassicRequestBuilder(Method.PATCH); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java index b0c6a446b..10bf96d03 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java @@ -168,9 +168,12 @@ public static Header format(final String name, final String... tokens) { } /** - * @since 5.3 + * @since 5.4 */ - public static void parseTokens(final CharSequence src, final ParserCursor cursor, final Consumer consumer) { + public static void parseTokens(final CharSequence src, + final ParserCursor cursor, + final Tokenizer.Delimiter delimiterPredicate, + final Consumer consumer) { Args.notNull(src, "Source"); Args.notNull(cursor, "Cursor"); Args.notNull(consumer, "Consumer"); @@ -179,7 +182,7 @@ public static void parseTokens(final CharSequence src, final ParserCursor cursor if (src.charAt(pos) == ',') { cursor.updatePos(pos + 1); } - final String token = Tokenizer.INSTANCE.parseToken(src, cursor, COMMA); + final String token = Tokenizer.INSTANCE.parseToken(src, cursor, delimiterPredicate); consumer.accept(token); } } @@ -187,31 +190,57 @@ public static void parseTokens(final CharSequence src, final ParserCursor cursor /** * @since 5.3 */ - public static void parseTokens(final Header header, final Consumer consumer) { + public static void parseTokens(final CharSequence src, final ParserCursor cursor, final Consumer consumer) { + parseTokens(src, cursor, COMMA, consumer); + } + + /** + * @since 5.4 + */ + public static void parseTokens(final Header header, + final Tokenizer.Delimiter delimiterPredicate, + final Consumer consumer) { Args.notNull(header, "Header"); if (header instanceof FormattedHeader) { final CharArrayBuffer buf = ((FormattedHeader) header).getBuffer(); final ParserCursor cursor = new ParserCursor(0, buf.length()); cursor.updatePos(((FormattedHeader) header).getValuePos()); - parseTokens(buf, cursor, consumer); + parseTokens(buf, cursor, delimiterPredicate, consumer); } else { final String value = header.getValue(); final ParserCursor cursor = new ParserCursor(0, value.length()); - parseTokens(value, cursor, consumer); + parseTokens(value, cursor, delimiterPredicate, consumer); } } /** * @since 5.3 */ - public static void parseTokens(final MessageHeaders headers, final String headerName, final Consumer consumer) { + public static void parseTokens(final Header header, final Consumer consumer) { + parseTokens(header, COMMA, consumer); + } + + /** + * @since 5.4 + */ + public static void parseTokens(final MessageHeaders headers, + final String headerName, + final Tokenizer.Delimiter delimiterPredicate, + final Consumer consumer) { Args.notNull(headers, "Headers"); final Iterator

it = headers.headerIterator(headerName); while (it.hasNext()) { - parseTokens(it.next(), consumer); + parseTokens(it.next(), delimiterPredicate, consumer); } } + /** + * @since 5.3 + */ + public static void parseTokens(final MessageHeaders headers, final String headerName, final Consumer consumer) { + parseTokens(headers, headerName, COMMA, consumer); + } + public static Set parseTokens(final CharSequence src, final ParserCursor cursor) { Args.notNull(src, "Source"); Args.notNull(cursor, "Cursor"); diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncRequestBuilder.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncRequestBuilder.java index 1c2b99b34..7b16f9774 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncRequestBuilder.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncRequestBuilder.java @@ -33,6 +33,7 @@ import java.util.Arrays; import java.util.List; +import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpHost; @@ -120,6 +121,44 @@ public static AsyncRequestBuilder head(final String uri) { return new AsyncRequestBuilder(Method.HEAD, uri); } + /** + * Initializes a new {@link AsyncRequestBuilder} instance for the {@code QUERY} method. + * + * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. + * + * @since 5.4 + */ + @Experimental + public static AsyncRequestBuilder query() { + return new AsyncRequestBuilder(Method.QUERY); + } + + /** + * Initializes a new {@link AsyncRequestBuilder} instance for the {@code QUERY} method. + * + * @param uri the request URI. + * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. + * + * @since 5.4 + */ + @Experimental + public static AsyncRequestBuilder query(final URI uri) { + return new AsyncRequestBuilder(Method.QUERY, uri); + } + + /** + * Initializes a new {@link AsyncRequestBuilder} instance for the {@code QUERY} method. + * + * @param uri the request URI. + * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. + * + * @since 5.4 + */ + @Experimental + public static AsyncRequestBuilder query(final String uri) { + return new AsyncRequestBuilder(Method.QUERY, uri); + } + public static AsyncRequestBuilder patch() { return new AsyncRequestBuilder(Method.PATCH); } diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestContent.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestContent.java index b8c22f84e..24ee1f164 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestContent.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestContent.java @@ -138,7 +138,7 @@ public void process(final HttpRequest request, final EntityDetails entity, final } private boolean isContentEnclosingMethod(final String method) { - return Method.POST.isSame(method) || Method.PUT.isSame(method) || Method.PATCH.isSame(method); + return Method.POST.isSame(method) || Method.PUT.isSame(method) || Method.PATCH.isSame(method) || Method.QUERY.isSame(method); } /** diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/support/BasicRequestBuilder.java b/httpcore5/src/main/java/org/apache/hc/core5/http/support/BasicRequestBuilder.java index bdc0cdb85..2d2620510 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/support/BasicRequestBuilder.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/support/BasicRequestBuilder.java @@ -33,6 +33,7 @@ import java.util.Arrays; import java.util.List; +import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; @@ -104,6 +105,44 @@ public static BasicRequestBuilder head(final String uri) { return new BasicRequestBuilder(Method.HEAD, uri); } + /** + * Initializes a new {@link BasicRequestBuilder} instance for the {@code QUERY} method. + * + * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. + * + * @since 5.4 + */ + @Experimental + public static BasicRequestBuilder query() { + return new BasicRequestBuilder(Method.QUERY); + } + + /** + * Initializes a new {@link BasicRequestBuilder} instance for the {@code QUERY} method. + * + * @param uri the request URI. + * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. + * + * @since 5.4 + */ + @Experimental + public static BasicRequestBuilder query(final URI uri) { + return new BasicRequestBuilder(Method.QUERY, uri); + } + + /** + * Initializes a new {@link BasicRequestBuilder} instance for the {@code QUERY} method. + * + * @param uri the request URI. + * @see Method#QUERY for more information regarding the properties of the {@code QUERY} method. + * + * @since 5.4 + */ + @Experimental + public static BasicRequestBuilder query(final String uri) { + return new BasicRequestBuilder(Method.QUERY, uri); + } + public static BasicRequestBuilder patch() { return new BasicRequestBuilder(Method.PATCH); } diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicRequestBuilderTest.java b/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicRequestBuilderTest.java index 729c6b89f..685c42bdf 100644 --- a/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicRequestBuilderTest.java +++ b/httpcore5/src/test/java/org/apache/hc/core5/http/io/support/ClassicRequestBuilderTest.java @@ -109,6 +109,19 @@ void head() throws UnknownHostException, URISyntaxException { assertEquals("/localhost", classicRequestBuilder3.getPath()); } + @Test + void query() throws UnknownHostException, URISyntaxException { + final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.query(); + assertEquals(Method.QUERY.name(), classicRequestBuilder.getMethod()); + + final ClassicRequestBuilder classicRequestBuilder1 = ClassicRequestBuilder.query(URIBuilder.localhost().build()); + assertEquals(Method.QUERY.name(), classicRequestBuilder1.getMethod()); + + final ClassicRequestBuilder classicRequestBuilder3 = ClassicRequestBuilder.query("/localhost"); + assertEquals(Method.QUERY.name(), classicRequestBuilder3.getMethod()); + assertEquals("/localhost", classicRequestBuilder3.getPath()); + } + @Test void patch() throws UnknownHostException, URISyntaxException { final ClassicRequestBuilder classicRequestBuilder = ClassicRequestBuilder.patch();