Instance of this interface is returned by {@link Request#body()}, 16 | * and can be modified using one of the methods below. When modification 17 | * is done, method {@code back()} returns a modified instance of 18 | * {@link Request}, for example: 19 | * 20 | *
new JdkRequest("http://my.example.com") 21 | * .header("Content-Type", "application/x-www-form-urlencoded") 22 | * .body() 23 | * .formParam("name", "Jeff Lebowski") 24 | * .formParam("age", "37") 25 | * .formParam("employment", "none") 26 | * .back() // returns a modified instance of Request 27 | * .fetch()28 | * 29 | *
Instances of this interface are immutable and thread-safe.
30 | *
31 | * @since 0.8
32 | */
33 | @Immutable
34 | public interface RequestBody {
35 |
36 | /**
37 | * Get back to the request it's related to.
38 | * @return The request we're in
39 | */
40 | Request back();
41 |
42 | /**
43 | * Get text content.
44 | * @return Content in UTF-8
45 | */
46 | String get();
47 |
48 | /**
49 | * Set text content.
50 | * @param body Body content
51 | * @return New alternated body
52 | */
53 | RequestBody set(String body);
54 |
55 | /**
56 | * Set JSON content.
57 | * @param json JSON object
58 | * @return New alternated body
59 | * @since 0.11
60 | */
61 | RequestBody set(JsonStructure json);
62 |
63 | /**
64 | * Set byte array content.
65 | * @param body Body content
66 | * @return New alternated body
67 | */
68 | RequestBody set(byte[] body);
69 |
70 | /**
71 | * Add form param.
72 | * @param name Query param name
73 | * @param value Value of the query param to set
74 | * @return New alternated body
75 | */
76 | RequestBody formParam(String name, Object value);
77 |
78 | /**
79 | * Add form params.
80 | * @param params Map of params
81 | * @return New alternated body
82 | * @since 0.10
83 | */
84 | RequestBody formParams(Map Instance of this interface is returned by {@link Request#uri()},
15 | * and can be modified using one of the methods below. When modification
16 | * is done, method {@code back()} returns a modified instance of
17 | * {@link Request}, for example:
18 | *
19 | * Instances of this interface are immutable and thread-safe.
28 | *
29 | * @since 0.8
30 | * @checkstyle AbbreviationAsWordInNameCheck (100 lines)
31 | */
32 | @Immutable
33 | public interface RequestURI {
34 |
35 | /**
36 | * Get back to the request it's related to.
37 | * @return The request we're in
38 | */
39 | Request back();
40 |
41 | /**
42 | * Get URI.
43 | * @return The destination it is currently pointing to
44 | */
45 | URI get();
46 |
47 | /**
48 | * Set URI.
49 | * @param uri URI to set
50 | * @return New alternated URI
51 | */
52 | RequestURI set(URI uri);
53 |
54 | /**
55 | * Add query param.
56 | * @param name Query param name
57 | * @param value Value of the query param to set
58 | * @return New alternated URI
59 | */
60 | RequestURI queryParam(String name, Object value);
61 |
62 | /**
63 | * Add query params.
64 | * @param map Map of params to add
65 | * @return New alternated URI
66 | */
67 | RequestURI queryParams(Map You can get this response from one of implementations of {@link Request}:
15 | *
16 | * Instances of this interface are immutable and thread-safe.
21 | *
22 | * @see com.jcabi.http.request.JdkRequest *
23 | * @since 0.8
24 | */
25 | @Immutable
26 | public interface Response {
27 |
28 | /**
29 | * Get back to the request it's related to.
30 | * @return The request we're in
31 | */
32 | Request back();
33 |
34 | /**
35 | * Get status of the response as a positive integer number.
36 | * @return The status code
37 | */
38 | int status();
39 |
40 | /**
41 | * Get status line reason phrase.
42 | * @return The status line reason phrase
43 | */
44 | String reason();
45 |
46 | /**
47 | * Get a collection of all headers.
48 | * @return The headers
49 | */
50 | Map DISCLAIMER:
58 | * The only encoding supported here is UTF-8. If the body of response
59 | * contains any chars that can't be used and should be replaced with
60 | * a "replacement character", a {@link RuntimeException} will be thrown. If
61 | * you need to use some other encodings, use
62 | * {@link #binary()} instead.
63 | *
64 | * @return The body, as a UTF-8 string
65 | */
66 | String body();
67 |
68 | /**
69 | * Raw body as a an array of bytes.
70 | * @return The body, as a UTF-8 string
71 | */
72 | byte[] binary();
73 |
74 | /**
75 | * Convert it to another type, by encapsulation.
76 | * @param type Type to use
77 | * @param An instance of this interface can be used in
17 | * {@link Request#through(Class,Object...)} to decorate
18 | * an existing {@code wire}, for example:
19 | *
20 | * Every {@code Wire} decorator passed to {@code through()} method
28 | * wraps a previously existing one.
29 | *
30 | * @since 0.9
31 | */
32 | @Immutable
33 | //@checkstyle ParameterNumber (16 lines)
34 | public interface Wire {
35 |
36 | /**
37 | * Send request and return response.
38 | * @param req Request
39 | * @param home URI to fetch
40 | * @param method HTTP method
41 | * @param headers Headers
42 | * @param content HTTP body
43 | * @param connect The connect timeout
44 | * @param read The read timeout
45 | * @return Response obtained
46 | * @throws IOException if fails
47 | */
48 | Response send(Request req, String home, String method,
49 | Collection A convenient tool to test your application classes against a web
17 | * service. For example:
18 | *
19 | * Keep in mind that container automatically reserves a new free TCP port
34 | * and works until JVM is shut down. The only way to stop it is to call
35 | * {@link #stop()}.
36 | *
37 | * Since version 0.11 container implements {@link Closeable} and can be
38 | * used in try-with-resource block.
39 | *
40 | * @see Examples
41 | * @since 0.10
42 | */
43 | @SuppressWarnings("PMD.TooManyMethods")
44 | public interface MkContainer extends Closeable {
45 |
46 | /**
47 | * Give this answer on the next request.
48 | * @param answer Next answer to give
49 | * @return This object
50 | */
51 | MkContainer next(MkAnswer answer);
52 |
53 | /**
54 | * Give this answer on the next request if the matcher condition is
55 | * satisfied.
56 | * @param answer Next answer to give
57 | * @param condition The condition to match
58 | * @return This object
59 | */
60 | MkContainer next(MkAnswer answer, Matcher The only dependency you need is (check our latest version available
10 | * at www.rexsl.com):
11 | *
12 | * This package contains implementations of class Request.
10 | * The most popular and easy to use it {@link JdkRequest}.
11 | *
12 | * However, in some situations {@link JdkRequest} falls short and
13 | * {@link ApacheRequest} should be used instead. For example,
14 | * {@link JdkRequest} doesn't support {@code PATCH} HTTP method due to
15 | * a bug in HttpURLConnection.
16 | *
17 | * @since 0.10
18 | */
19 | package com.jcabi.http.request;
20 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/http/response/AbstractResponse.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.http.response;
6 |
7 | import com.jcabi.aspects.Immutable;
8 | import com.jcabi.http.Request;
9 | import com.jcabi.http.Response;
10 | import java.util.List;
11 | import java.util.Map;
12 | import lombok.EqualsAndHashCode;
13 |
14 | /**
15 | * Abstract response.
16 | *
17 | * @since 0.8
18 | */
19 | @Immutable
20 | @EqualsAndHashCode(of = "response")
21 | abstract class AbstractResponse implements Response {
22 |
23 | /**
24 | * Encapsulated response.
25 | */
26 | private final transient Response response;
27 |
28 | /**
29 | * Ctor.
30 | * @param resp Response
31 | */
32 | AbstractResponse(final Response resp) {
33 | this.response = resp;
34 | }
35 |
36 | @Override
37 | public final String toString() {
38 | return this.response.toString();
39 | }
40 |
41 | @Override
42 | public final Request back() {
43 | return this.response.back();
44 | }
45 |
46 | @Override
47 | public final int status() {
48 | return this.response.status();
49 | }
50 |
51 | @Override
52 | public final String reason() {
53 | return this.response.reason();
54 | }
55 |
56 | @Override
57 | public final Map This response decorator is able to parse HTTP response body as an HTML
18 | * document. Example usage:
19 | *
20 | * {@link #body()} will try to output clean HTML even for
27 | * malformed responses. For example:
28 | * The class is immutable and thread-safe.
37 | *
38 | * @see Jsoup website
39 | * @since 1.4
40 | */
41 | @Immutable
42 | @EqualsAndHashCode(callSuper = true)
43 | public final class JsoupResponse extends AbstractResponse {
44 |
45 | /**
46 | * Public ctor.
47 | * @param resp Response
48 | */
49 | public JsoupResponse(final Response resp) {
50 | super(resp);
51 | }
52 |
53 | @Override
54 | public String body() {
55 | final Document html = Jsoup.parse(super.body());
56 | html.outputSettings().syntax(Document.OutputSettings.Syntax.xml);
57 | html.outputSettings().escapeMode(Entities.EscapeMode.xhtml);
58 | return html.html();
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/http/response/XmlResponse.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.http.response;
6 |
7 | import com.jcabi.aspects.Immutable;
8 | import com.jcabi.http.Request;
9 | import com.jcabi.http.Response;
10 | import com.jcabi.immutable.ArrayMap;
11 | import com.jcabi.matchers.XhtmlMatchers;
12 | import com.jcabi.xml.XML;
13 | import com.jcabi.xml.XMLDocument;
14 | import com.jcabi.xml.XPathContext;
15 | import java.net.URI;
16 | import java.util.Map;
17 | import javax.xml.namespace.NamespaceContext;
18 | import lombok.EqualsAndHashCode;
19 |
20 | /**
21 | * XML response.
22 | *
23 | * This response decorator is able to parse HTTP response body as
24 | * an XML document and manipulate with it afterwords, for example:
25 | *
26 | * In HATEOAS
36 | * responses it is convenient to use this decorator's
37 | * method {@link #rel(String)}
38 | * in order to follow the link provided in {@code <link>} XML element,
39 | * for example:
40 | *
41 | * The class is immutable and thread-safe.
50 | *
51 | * @since 0.8
52 | */
53 | @Immutable
54 | @EqualsAndHashCode(callSuper = true)
55 | public final class XmlResponse extends AbstractResponse {
56 |
57 | /**
58 | * Map of namespaces.
59 | */
60 | private final transient ArrayMap This wire will retry a request a certain number of times (default: 5)
27 | * after a short delay when a HTTP response with a status code of 300-399 is
28 | * received. On every next attempt a new URL will be used, according
29 | * to the value of {@code Location} HTTP header of the response.
30 | *
31 | * If the maximum number of retries are reached, the last response
32 | * received is returned to the caller, regardless of its status code.
33 | *
34 | * The class is immutable and thread-safe.
41 | *
42 | * @since 1.6
43 | */
44 | @Immutable
45 | @ToString(of = "origin")
46 | @EqualsAndHashCode(of = { "origin", "max" })
47 | public final class AutoRedirectingWire implements Wire {
48 | /**
49 | * Original wire.
50 | */
51 | private final transient Wire origin;
52 |
53 | /**
54 | * Maximum number of retries to be made.
55 | */
56 | private final transient int max;
57 |
58 | /**
59 | * Public ctor.
60 | * @param wire Original wire
61 | */
62 | public AutoRedirectingWire(final Wire wire) {
63 | this(wire, 5);
64 | }
65 |
66 | /**
67 | * Public ctor.
68 | * @param wire Original wire
69 | * @param retries Maximum number of retries
70 | */
71 | public AutoRedirectingWire(final Wire wire, final int retries) {
72 | this.origin = wire;
73 | this.max = retries;
74 | }
75 |
76 | // @checkstyle ParameterNumber (5 lines)
77 | @Override
78 | public Response send(final Request req, final String home,
79 | final String method,
80 | final Collection This wire converts user info from URI into
30 | * {@code "Authorization"} HTTP header, for example:
31 | *
32 | * In this example, an additional HTTP header {@code Authorization}
38 | * will be added with a value {@code Basic amVmZjoxMjM0NQ==}.
39 | *
40 | * The class is immutable and thread-safe.
41 | *
42 | * @see RFC 2617 "HTTP Authentication: Basic and Digest Access Authentication"
43 | * @since 0.10
44 | */
45 | @Immutable
46 | @ToString(of = "origin")
47 | @EqualsAndHashCode(of = "origin")
48 | public final class BasicAuthWire implements Wire {
49 |
50 | /**
51 | * The Charset to use.
52 | */
53 | private static final Charset CHARSET = StandardCharsets.UTF_8;
54 |
55 | /**
56 | * Original wire.
57 | */
58 | private final transient Wire origin;
59 |
60 | /**
61 | * Public ctor.
62 | * @param wire Original wire
63 | */
64 | public BasicAuthWire(final Wire wire) {
65 | this.origin = wire;
66 | }
67 |
68 | // @checkstyle ParameterNumber (7 lines)
69 | @Override
70 | public Response send(final Request req, final String home,
71 | final String method,
72 | final Collection This wire compresses all provided {@code Cookie} headers into one
28 | * and removes empty cookies, for example:
29 | *
30 | * An actual HTTP request will be sent with just one {@code Cookie}
39 | * header with a value {@code alpha=test; gamma=foo}.
40 | *
41 | * It is highly recommended to use this wire decorator when you're
42 | * working with cookies.
43 | *
44 | * The class is immutable and thread-safe.
45 | *
46 | * @see RFC 2965 "HTTP State Management Mechanism"
47 | * @since 0.10
48 | */
49 | @Immutable
50 | @ToString(of = "origin")
51 | @EqualsAndHashCode(of = "origin")
52 | public final class CookieOptimizingWire implements Wire {
53 |
54 | /**
55 | * Original wire.
56 | */
57 | private final transient Wire origin;
58 |
59 | /**
60 | * Public ctor.
61 | * @param wire Original wire
62 | */
63 | public CookieOptimizingWire(final Wire wire) {
64 | this.origin = wire;
65 | }
66 |
67 | // @checkstyle ParameterNumber (7 lines)
68 | @Override
69 | public Response send(final Request req, final String home,
70 | final String method,
71 | final Collection This decorator can be used when you want to avoid duplicate
16 | * requests to load-sensitive resources and server supports ETags, for example:
17 | *
18 | * Client will automatically detect if server uses ETags and start adding
26 | * corresponding If-None-Match to outgoing requests
27 | *
28 | * Client will take response from the cache if it is present
29 | * or will query resource for that.
30 | *
31 | * The class is immutable and thread-safe.
32 | *
33 | * @since 2.0
34 | */
35 | @ToString
36 | @Immutable
37 | public final class ETagCachingWire extends AbstractHeaderBasedCachingWire {
38 |
39 | /**
40 | * Public ctor.
41 | * @param wire Original wire
42 | */
43 | public ETagCachingWire(final Wire wire) {
44 | super(HttpHeaders.ETAG, HttpHeaders.IF_NONE_MATCH, wire);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/http/wire/FcWire.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.http.wire;
6 |
7 | import com.jcabi.aspects.Immutable;
8 | import com.jcabi.http.Request;
9 | import com.jcabi.http.Response;
10 | import com.jcabi.http.Wire;
11 | import java.io.IOException;
12 | import java.io.InputStream;
13 | import java.net.URI;
14 | import java.util.Collection;
15 | import java.util.Map;
16 | import lombok.EqualsAndHashCode;
17 | import lombok.ToString;
18 |
19 | /**
20 | * Wire that caches GET requests.
21 | *
22 | * This decorator can be used when you want to avoid duplicate
23 | * GET requests to load-sensitive resources, for example:
24 | *
25 | * You can also configure it to flush the entire cache
32 | * on certain request URI's, for example:
33 | *
34 | * The regular expression provided will be used against a string
40 | * constructed as an HTTP method, space, path of the URI together with
41 | * query part.
42 | *
43 | * The class is immutable and thread-safe.
44 | *
45 | * @since 1.16
46 | */
47 | @Immutable
48 | @ToString
49 | @EqualsAndHashCode(of = { "origin", "regex" })
50 | public final class FcWire implements Wire {
51 |
52 | /**
53 | * Cache in files.
54 | */
55 | private final transient FcCache cache;
56 |
57 | /**
58 | * Original wire.
59 | */
60 | private final transient Wire origin;
61 |
62 | /**
63 | * Flushing regular expression.
64 | */
65 | private final transient String regex;
66 |
67 | /**
68 | * Public ctor.
69 | * @param wire Original wire
70 | */
71 | public FcWire(final Wire wire) {
72 | this(wire, "$never");
73 | }
74 |
75 | /**
76 | * Public ctor.
77 | * @param wire Original wire
78 | * @param flsh Flushing regular expression
79 | */
80 | public FcWire(final Wire wire, final String flsh) {
81 | this(wire, flsh, new FcCache());
82 | }
83 |
84 | /**
85 | * Public ctor.
86 | * @param wire Original wire
87 | * @param flsh Flushing regular expression
88 | * @param path Path for the files
89 | */
90 | public FcWire(final Wire wire, final String flsh, final String path) {
91 | this(wire, flsh, new FcCache(path));
92 | }
93 |
94 | /**
95 | * Public ctor.
96 | * @param wire Original wire
97 | * @param flsh Flushing regular expression
98 | * @param fcc Cache
99 | */
100 | public FcWire(final Wire wire, final String flsh, final FcCache fcc) {
101 | this.origin = wire;
102 | this.regex = flsh;
103 | this.cache = fcc;
104 | }
105 |
106 | // @checkstyle ParameterNumber (5 lines)
107 | @Override
108 | public Response send(final Request req, final String home,
109 | final String method,
110 | final Collection It's recommended to use this decorator in production, in order
25 | * to avoid stuck requests:
26 | *
27 | * The class is immutable and thread-safe.
34 | *
35 | * @since 0.10
36 | */
37 | @Immutable
38 | @ToString(of = "origin")
39 | @EqualsAndHashCode(of = "origin")
40 | public final class OneMinuteWire implements Wire {
41 |
42 | /**
43 | * Original wire.
44 | */
45 | private final transient Wire origin;
46 |
47 | /**
48 | * Public ctor.
49 | * @param wire Original wire
50 | */
51 | public OneMinuteWire(final Wire wire) {
52 | this.origin = wire;
53 | }
54 |
55 | // @checkstyle ParameterNumber (5 lines)
56 | @Override
57 | @Timeable(limit = 1, unit = TimeUnit.MINUTES)
58 | public Response send(final Request req, final String home,
59 | final String method,
60 | final Collection This wire retries again (at least three times) if an original one throws
26 | * {@link IOException}:
27 | *
28 | * Since version 1.9 this wire retries also if HTTP status code
35 | * is between 500 and 599.
36 | *
37 | * The class is immutable and thread-safe.
38 | *
39 | * @since 0.10
40 | */
41 | @Immutable
42 | @ToString(of = "origin")
43 | @EqualsAndHashCode(of = "origin")
44 | public final class RetryWire implements Wire {
45 |
46 | /**
47 | * Original wire.
48 | */
49 | private final transient Wire origin;
50 |
51 | /**
52 | * Public ctor.
53 | * @param wire Original wire
54 | */
55 | public RetryWire(final Wire wire) {
56 | this.origin = wire;
57 | }
58 |
59 | // @checkstyle ParameterNumber (13 lines)
60 | @Override
61 | public Response send(final Request req, final String home, final String method,
62 | final Collection This wire ignores :
31 | *
32 | * The class is immutable and thread-safe.
38 | *
39 | * @since 1.10
40 | */
41 | @Immutable
42 | @ToString(of = "origin")
43 | @EqualsAndHashCode(of = "origin")
44 | public final class TrustedWire implements Wire {
45 |
46 | /**
47 | * Trust manager.
48 | */
49 | private static final TrustManager MANAGER = new X509TrustManager() {
50 | @Override
51 | public X509Certificate[] getAcceptedIssuers() {
52 | return new X509Certificate[0];
53 | }
54 |
55 | @Override
56 | public void checkClientTrusted(final X509Certificate[] certs,
57 | final String type) {
58 | // nothing to check here
59 | }
60 |
61 | @Override
62 | public void checkServerTrusted(final X509Certificate[] certs,
63 | final String types) {
64 | // nothing to check here
65 | }
66 | };
67 |
68 | /**
69 | * Original wire.
70 | */
71 | private final transient Wire origin;
72 |
73 | /**
74 | * Public ctor.
75 | * @param wire Original wire
76 | */
77 | public TrustedWire(final Wire wire) {
78 | this.origin = wire;
79 | }
80 |
81 | // @checkstyle ParameterNumber (13 lines)
82 | @Override
83 | public Response send(final Request req, final String home,
84 | final String method,
85 | final Collection This wire adds an extra HTTP header {@code User-Agent} to the request,
27 | * if it's not yet provided, for example:
28 | *
29 | * An actual HTTP request will be sent with {@code User-Agent}
35 | * header with a value {@code ReXSL-0.1/abcdef0 Java/1.6} (for example). It
36 | * is recommended to use this wire decorator when you're working with
37 | * third party RESTful services, to properly identify yourself and avoid
38 | * troubles.
39 | *
40 | * The class is immutable and thread-safe.
41 | *
42 | * @see RFC 2616 section 14.43 "User-Agent"
43 | * @since 0.10
44 | */
45 | @Immutable
46 | @ToString(of = "origin")
47 | @EqualsAndHashCode(of = "origin")
48 | @RequiredArgsConstructor
49 | public final class UserAgentWire implements Wire {
50 |
51 | /**
52 | * Original wire.
53 | */
54 | private final Wire origin;
55 |
56 | /**
57 | * Agent.
58 | */
59 | private final String agent;
60 |
61 | /**
62 | * Public ctor.
63 | * @param wire Original wire
64 | */
65 | public UserAgentWire(final Wire wire) {
66 | this(
67 | wire,
68 | String.format(
69 | "jcabi-%s/%s Java/%s",
70 | Manifests.read("JCabi-Version"),
71 | Manifests.read("JCabi-Build"),
72 | System.getProperty("java.version")
73 | )
74 | );
75 | }
76 |
77 | // @checkstyle ParameterNumber (7 lines)
78 | @Override
79 | public Response send(final Request req, final String home,
80 | final String method,
81 | final Collection This wire makes HTTP request and response details visible in
26 | * log (we're using SLF4J logging facility), for example:
27 | *
28 | * The class is immutable and thread-safe.
35 | *
36 | * @since 0.10
37 | */
38 | @Immutable
39 | @ToString(of = "origin")
40 | @EqualsAndHashCode(of = "origin")
41 | public final class VerboseWire implements Wire {
42 |
43 | /**
44 | * Original wire.
45 | */
46 | private final transient Wire origin;
47 |
48 | /**
49 | * Public ctor.
50 | * @param wire Original wire
51 | */
52 | public VerboseWire(final Wire wire) {
53 | this.origin = wire;
54 | }
55 |
56 | // @checkstyle ParameterNumber (7 lines)
57 | @Override
58 | public Response send(final Request req, final String home,
59 | final String method,
60 | final Collection
16 | * NOTE: This is not threadsafe and access to it should be synchronized.
17 | *
18 | * @since 1.17.1
19 | * @checkstyle ParameterNumberCheck (50 lines)
20 | */
21 | public class MockWire implements Wire {
22 |
23 | /**
24 | * The actual mock object we delegate the
31 | * The given target wire is ignored and Hello world"
32 | )
33 | ).fetch();
34 | MatcherAssert.assertThat(
35 | "should contains normalized response",
36 | new JsoupResponse(resp).body(),
37 | XhtmlMatchers.hasXPaths(
38 | "/xhtml:html/xhtml:head",
39 | "/xhtml:html/xhtml:body/xhtml:p[.=\"Hello world\"]"
40 | )
41 | );
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/http/response/RestResponseITCase.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.http.response;
6 |
7 | import com.jcabi.http.request.JdkRequest;
8 | import java.io.IOException;
9 | import org.hamcrest.MatcherAssert;
10 | import org.hamcrest.Matchers;
11 | import org.junit.jupiter.api.Assertions;
12 | import org.junit.jupiter.api.Test;
13 | import org.junit.jupiter.api.function.Executable;
14 |
15 | /**
16 | * Integration test for {@link RestResponse}.
17 | *
18 | * @since 1.17.5
19 | */
20 | @SuppressWarnings("PMD.AvoidDuplicateLiterals") final class RestResponseITCase {
21 | @Test
22 | void readsCookiesSeveralValues() throws IOException {
23 | final RestResponse resp = new JdkRequest(
24 | "https://httpbin.org/cookies/set?ijk=efg&xyz=abc"
25 | )
26 | .fetch()
27 | .as(RestResponse.class);
28 | Assertions.assertAll(
29 | new Executable() {
30 | @Override
31 | public void execute() {
32 | MatcherAssert.assertThat(
33 | "should contains value 'efg'",
34 | resp.cookie("ijk"),
35 | Matchers.hasProperty("value", Matchers.is("efg"))
36 | );
37 | }
38 | },
39 | new Executable() {
40 | @Override
41 | public void execute() {
42 | MatcherAssert.assertThat(
43 | "should contains value 'abc'",
44 | resp.cookie("xyz"),
45 | Matchers.hasProperty("value", Matchers.is("abc"))
46 | );
47 | }
48 | }
49 | );
50 | }
51 |
52 | @Test
53 | void readsCookies() throws IOException {
54 | MatcherAssert.assertThat(
55 | "should contains value 'bar'",
56 | new JdkRequest("https://httpbin.org/cookies/set?foo=bar")
57 | .fetch()
58 | .as(RestResponse.class)
59 | .cookie("foo"),
60 | Matchers.hasProperty("value", Matchers.is("bar"))
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/http/response/RestResponseTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.http.response;
6 |
7 | import com.jcabi.http.Response;
8 | import com.jcabi.http.request.FakeRequest;
9 | import jakarta.ws.rs.core.HttpHeaders;
10 | import java.net.HttpURLConnection;
11 | import java.net.URI;
12 | import org.hamcrest.Matcher;
13 | import org.hamcrest.MatcherAssert;
14 | import org.hamcrest.Matchers;
15 | import org.junit.jupiter.api.Assertions;
16 | import org.junit.jupiter.api.Test;
17 | import org.junit.jupiter.api.function.Executable;
18 |
19 | /**
20 | * Test case for {@link RestResponse}.
21 | * @since 1.1
22 | */
23 | @SuppressWarnings("PMD.AvoidDuplicateLiterals")
24 | final class RestResponseTest {
25 |
26 | /**
27 | * RestResponse can assert HTTP status.
28 | */
29 | @Test
30 | void assertsHttpStatusCode() {
31 | Assertions.assertThrows(
32 | AssertionError.class,
33 | new Executable() {
34 | @Override
35 | public void execute() throws Throwable {
36 | new RestResponse(
37 | new FakeRequest()
38 | .withStatus(HttpURLConnection.HTTP_OK)
39 | .fetch()
40 | ).assertStatus(HttpURLConnection.HTTP_NOT_FOUND);
41 | }
42 | }
43 | );
44 | }
45 |
46 | /**
47 | * RestResponse can assert HTTP header.
48 | * @throws Exception If something goes wrong inside
49 | */
50 | @Test
51 | @SuppressWarnings("unchecked")
52 | void assertsHttpHeaders() throws Exception {
53 | final String name = "Abc";
54 | final String value = "t66";
55 | final Response rsp = new FakeRequest().withHeader(name, value).fetch();
56 | new RestResponse(rsp).assertHeader(
57 | name,
58 | Matchers.allOf(
59 | Matchers.hasItems(value),
60 | Matcher.class.cast(Matchers.hasSize(1))
61 | )
62 | );
63 | new RestResponse(rsp).assertHeader(
64 | "Something-Else-Which-Is-Absent",
65 | Matcher.class.cast(Matchers.empty())
66 | );
67 | }
68 |
69 | /**
70 | * RestResponse can retrieve a cookie by name.
71 | * @throws Exception If something goes wrong inside
72 | */
73 | @Test
74 | void retrievesCookieByName() throws Exception {
75 | final RestResponse response = new RestResponse(
76 | new FakeRequest()
77 | .withBody(" new JdkRequest("http://my.example.com")
20 | * .header("Accept", "application/json")
21 | * .uri()
22 | * .path("/users")
23 | * .queryParam("name", "Jeff Lebowski")
24 | * .back() // returns a modified instance of Request
25 | * .fetch()
26 | *
27 | * Response response = new JdkRequest("https://www.google.com")
17 | * .header("Accept", "text/html")
18 | * .fetch();
19 | *
20 | * String html = new JdkRequest("http://google.com")
21 | * .through(VerboseWire.class)
22 | * .through(RetryWire.class)
23 | * .header("Accept", "text/html")
24 | * .fetch()
25 | * .body();
26 | *
27 | * MkContainer container = new MkGrizzlyContainer()
20 | * .next(new MkAnswer.Simple(200, "works fine!"))
21 | * .start();
22 | * new JdkRequest(container.home())
23 | * .header("Accept", "text/xml")
24 | * .fetch().as(RestResponse.class)
25 | * .assertStatus(200)
26 | * .assertBody(Matchers.equalTo("works fine!"));
27 | * MatcherAssert.assertThat(
28 | * container.take().method(),
29 | * Matchers.equalTo("GET")
30 | * );
31 | * container.stop();
32 | *
33 | * <depedency>
13 | * <groupId>com.rexsl</groupId>
14 | * <artifactId>rexsl-test</artifactId>
15 | * </dependency>
16 | *
17 | * @see project site
18 | */
19 | package com.jcabi.http;
20 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/http/request/Boundary.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.http.request;
6 |
7 | import com.jcabi.aspects.Immutable;
8 | import com.jcabi.aspects.Loggable;
9 | import java.util.Random;
10 |
11 | /**
12 | * Boundary for content-type multipart/form-data.
13 | * This is a copy of boundary created by Apache HttpComponents HttpClient 4.5.
14 | *
15 | * @since 1.0
16 | */
17 | @Immutable
18 | @Loggable(Loggable.DEBUG)
19 | public final class Boundary {
20 | /**
21 | * The pool of ASCII chars to be used for generating a multipart boundary.
22 | */
23 | private static final char[] MULTIPART_CHARS =
24 | "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
25 | .toCharArray();
26 |
27 | /**
28 | * Generation of pseudorandom numbers.
29 | */
30 | private final transient Random rand;
31 |
32 | /**
33 | * Constructor with new random number generation.
34 | */
35 | public Boundary() {
36 | this(new Random());
37 | }
38 |
39 | /**
40 | * Ctor.
41 | * @param random Random number generation.
42 | */
43 | public Boundary(final Random random) {
44 | this.rand = random;
45 | }
46 |
47 | /**
48 | * Generates random boundary with random size from 30 to 40.
49 | * @return Boundary value.
50 | */
51 | public String value() {
52 | final StringBuilder buffer = new StringBuilder();
53 | final int count = this.rand.nextInt(11) + 30;
54 | for (int index = 0; index < count; ++index) {
55 | buffer.append(
56 | Boundary.MULTIPART_CHARS[
57 | this.rand.nextInt(MULTIPART_CHARS.length)
58 | ]
59 | );
60 | }
61 | return buffer.toString();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/http/request/DefaultResponse.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.http.request;
6 |
7 | import com.jcabi.aspects.Immutable;
8 | import com.jcabi.aspects.Loggable;
9 | import com.jcabi.http.Request;
10 | import com.jcabi.http.RequestBody;
11 | import com.jcabi.http.Response;
12 | import com.jcabi.immutable.Array;
13 | import com.jcabi.log.Logger;
14 | import java.lang.reflect.InvocationTargetException;
15 | import java.nio.charset.StandardCharsets;
16 | import java.util.LinkedList;
17 | import java.util.List;
18 | import java.util.Map;
19 | import java.util.concurrent.ConcurrentHashMap;
20 | import java.util.concurrent.ConcurrentMap;
21 | import lombok.EqualsAndHashCode;
22 |
23 | /**
24 | * Default implementation of {@link com.jcabi.http.Response}.
25 | *
26 | * @since 1.0
27 | */
28 | @Immutable
29 | @EqualsAndHashCode(of = { "req", "code", "phrase", "hdrs", "content" })
30 | @Loggable(Loggable.DEBUG)
31 | public final class DefaultResponse implements Response {
32 |
33 | /**
34 | * UTF-8 error marker.
35 | */
36 | private static final String ERR = "\uFFFD";
37 |
38 | /**
39 | * Request.
40 | */
41 | private final transient Request req;
42 |
43 | /**
44 | * Status code.
45 | */
46 | private final transient int code;
47 |
48 | /**
49 | * Reason phrase.
50 | */
51 | private final transient String phrase;
52 |
53 | /**
54 | * Headers.
55 | */
56 | private final transient Array String body = new JdkRequest("http://my.example.com")
21 | * .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
22 | * .fetch()
23 | * .as(JsoupResponse.class)
24 | * .body();
25 | *
26 | *
29 | *
35 | *
36 | * String name = new JdkRequest("http://my.example.com")
27 | * .header(HttpHeaders.ACCEPT, MediaType.TEXT_XML)
28 | * .fetch()
29 | * .as(XmlResponse.class)
30 | * .assertXPath("/user/name")
31 | * .xml()
32 | * .xpath("/user/name/text()")
33 | * .get(0);
34 | *
35 | * String data = new JdkRequest("http://my.example.com")
42 | * .header(HttpHeaders.ACCEPT, MediaType.TEXT_XML)
43 | * .fetch()
44 | * .as(XmlResponse.class)
45 | * .rel("/user/links/link[@rel='next']/@href")
46 | * .fetch()
47 | * .body();
48 | *
49 | * String html = new JdkRequest("http://goggle.com")
35 | * .through(AutoRedirectingWire.class)
36 | * .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN)
37 | * .fetch()
38 | * .body();
39 | *
40 | * String html = new JdkRequest("http://jeff:12345@example.com")
33 | * .through(BasicAuthWire.class)
34 | * .fetch()
35 | * .body();
36 | *
37 | * String html = new JdkRequest("http://goggle.com")
31 | * .through(CookieOptimizingWire.class)
32 | * .header(HttpHeaders.Cookie, "alpha=test")
33 | * .header(HttpHeaders.Cookie, "beta=")
34 | * .header(HttpHeaders.Cookie, "gamma=foo")
35 | * .fetch()
36 | * .body();
37 | *
38 | * {@code
19 | * String html = new JdkRequest("http://goggle.com")
20 | * .through(ETagCachingWire.class)
21 | * .fetch()
22 | * .body();
23 | * }
24 | *
25 | * String html = new JdkRequest("http://goggle.com")
26 | * .through(FileCachingWire.class)
27 | * .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN)
28 | * .fetch()
29 | * .body();
30 | *
31 | * new JdkRequest(uri)
35 | * .through(CachingWire.class, "GET /save/.*")
36 | * .uri().path("/save/123").back()
37 | * .fetch();
38 | *
39 | * String html = new JdkRequest("http://goggle.com")
28 | * .through(OneMinuteWire.class)
29 | * .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN)
30 | * .fetch()
31 | * .body();
32 | *
33 | * String html = new JdkRequest("http://goggle.com")
29 | * .through(RetryWire.class)
30 | * .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN)
31 | * .fetch()
32 | * .body();
33 | *
34 | * String html = new JdkRequest("http://goggle.com")
33 | * .through(TrustedWire.class)
34 | * .fetch()
35 | * .body();
36 | *
37 | * String html = new JdkRequest("http://goggle.com")
30 | * .through(UserAgentWire.class)
31 | * .fetch()
32 | * .body();
33 | *
34 | * String html = new JdkRequest("http://goggle.com")
29 | * .through(VerboseWire.class)
30 | * .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN)
31 | * .fetch()
32 | * .body();
33 | *
34 | * Wire.send
call to.
25 | */
26 | private static Wire mockDelegate;
27 |
28 | /**
29 | * Creates a new mock wire instance.
30 | * Wire.send
is delegated
32 | * to the static mock delegate.
33 | *
34 | * @param wire The original wire which is ignored
35 | */
36 | @SuppressWarnings("PMD.UnusedFormalParameter")
37 | public MockWire(final Wire wire) {
38 | // Instantiated by a Request implementation, wire is ignored
39 | }
40 |
41 | @Override
42 | public final Response send(final Request req, final String home,
43 | final String method, final CollectionRequest.send
method is delegated to.
59 | *
60 | * @param mock The mock to assert variables passed by the request
61 | * implementation
62 | */
63 | @SuppressWarnings("PMD.DefaultPackage")
64 | static void setMockDelegate(final Wire mock) {
65 | MockWire.mockDelegate = mock;
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/http/RequestSecondITCase.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.http;
6 |
7 | import com.jcabi.http.request.ApacheRequest;
8 | import com.jcabi.http.request.JdkRequest;
9 | import java.net.URI;
10 | import org.junit.jupiter.api.AfterAll;
11 | import org.junit.jupiter.api.BeforeAll;
12 | import org.junit.jupiter.api.Nested;
13 | import org.junit.jupiter.api.TestInstance;
14 | import org.testcontainers.containers.GenericContainer;
15 | import org.testcontainers.junit.jupiter.Testcontainers;
16 | import org.testcontainers.utility.DockerImageName;
17 |
18 | /**
19 | * Integration test for {@link Request}.
20 | *
21 | * @since 1.17.8
22 | */
23 | @SuppressWarnings("PMD.AbstractClassWithoutAbstractMethod")
24 | @TestInstance(TestInstance.Lifecycle.PER_CLASS)
25 | @Testcontainers(disabledWithoutDocker = true)
26 | final class RequestSecondITCase {
27 |
28 | /**
29 | * Container with HttpBin.
30 | */
31 | private final GenericContainer> container = new GenericContainer<>(
32 | DockerImageName.parse("kennethreitz/httpbin")
33 | ).withExposedPorts(80);
34 |
35 | @BeforeAll
36 | void beforeAll() {
37 | this.container.start();
38 | }
39 |
40 | @AfterAll
41 | void tearDown() {
42 | this.container.stop();
43 | }
44 |
45 | /**
46 | * URI of the container.
47 | * @return URI.
48 | */
49 | private URI uri() {
50 | return URI.create(
51 | String.format(
52 | "http://%s:%d",
53 | this.container.getHost(),
54 | this.container.getFirstMappedPort()
55 | )
56 | );
57 | }
58 |
59 | /**
60 | * Test for {@link JdkRequest}.
61 | * @since 1.17.8
62 | */
63 | @Nested
64 | final class JdkRequestITCase extends RequestITCaseTemplate {
65 | JdkRequestITCase() {
66 | super(JdkRequest.class, RequestSecondITCase.this.uri());
67 | }
68 | }
69 |
70 | /**
71 | * Test for {@link ApacheRequest}.
72 | * @since 1.17.8
73 | */
74 | @Nested
75 | final class ApacheRequestITCase extends RequestITCaseTemplate {
76 | ApacheRequestITCase() {
77 | super(ApacheRequest.class, RequestSecondITCase.this.uri());
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/http/RequestTestTemplate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.http;
6 |
7 | import com.jcabi.http.request.ApacheRequest;
8 | import com.jcabi.http.request.JdkRequest;
9 | import java.lang.annotation.Retention;
10 | import java.lang.annotation.RetentionPolicy;
11 | import java.net.URI;
12 | import lombok.SneakyThrows;
13 | import org.junit.jupiter.params.provider.ValueSource;
14 |
15 | /**
16 | * Template for generic tests for {@link Request}.
17 | *
18 | * @since 1.17.4
19 | */
20 | @SuppressWarnings("PMD.AbstractClassWithoutAbstractMethod")
21 | abstract class RequestTestTemplate {
22 | /**
23 | * Annotation for a parameterized test case.
24 | *
25 | * @since 1.17.4
26 | */
27 | @Retention(RetentionPolicy.RUNTIME)
28 | @ValueSource(classes = {ApacheRequest.class, JdkRequest.class})
29 | protected @interface Values {
30 | }
31 |
32 | /**
33 | * Make a request.
34 | * @param uri URI to start with
35 | * @param type Type of the request
36 | * @return Request
37 | */
38 | @SneakyThrows
39 | static Request request(final URI uri, final Class extends Request> type) {
40 | return type.getDeclaredConstructor(URI.class).newInstance(uri);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/http/mock/GrizzlyQueryTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.http.mock;
6 |
7 | import java.io.ByteArrayInputStream;
8 | import java.util.Collections;
9 | import org.glassfish.grizzly.http.server.Request;
10 | import org.hamcrest.MatcherAssert;
11 | import org.hamcrest.Matchers;
12 | import org.junit.jupiter.api.Test;
13 | import org.mockito.Mockito;
14 |
15 | /**
16 | * Test case for {@link GrizzlyQuery}.
17 | * @since 1.13
18 | */
19 | final class GrizzlyQueryTest {
20 |
21 | /**
22 | * GrizzlyQuery can return a body as a byte array.
23 | * @throws Exception if something goes wrong.
24 | */
25 | @Test
26 | void returnsBinaryBody() throws Exception {
27 | final Request request = Mockito.mock(Request.class);
28 | Mockito.when(request.getRequestURI()).thenReturn("http://fake.com");
29 | Mockito.when(request.getHeaderNames()).thenReturn(
30 | Collections.