View Javadoc
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.google.common.base.Joiner;
8   import com.jcabi.http.mock.MkAnswer;
9   import com.jcabi.http.mock.MkContainer;
10  import com.jcabi.http.mock.MkGrizzlyContainer;
11  import com.jcabi.http.mock.MkQuery;
12  import com.jcabi.http.mock.MkQueryMatchers;
13  import com.jcabi.http.response.RestResponse;
14  import com.jcabi.http.response.XmlResponse;
15  import com.jcabi.http.wire.BasicAuthWire;
16  import com.jcabi.http.wire.UserAgentWire;
17  import jakarta.ws.rs.core.HttpHeaders;
18  import jakarta.ws.rs.core.MediaType;
19  import jakarta.ws.rs.core.UriBuilder;
20  import java.io.ByteArrayInputStream;
21  import java.net.HttpURLConnection;
22  import java.net.URI;
23  import java.net.URLDecoder;
24  import java.net.URLEncoder;
25  import java.nio.charset.StandardCharsets;
26  import org.glassfish.grizzly.http.server.Constants;
27  import org.hamcrest.MatcherAssert;
28  import org.hamcrest.Matchers;
29  import org.junit.jupiter.api.Assertions;
30  import org.junit.jupiter.params.ParameterizedTest;
31  
32  /**
33   * Test case for {@link Request} and its implementations.
34   * @since 1.7
35   */
36  @SuppressWarnings(
37      {
38          "PMD.TooManyMethods",
39          "PMD.AvoidDuplicateLiterals",
40          "PMD.TestClassWithoutTestCases"
41      })
42  final class RequestTest extends RequestTestTemplate {
43  
44      /**
45       * BaseRequest can fetch HTTP request and process HTTP response.
46       * @param type Request type
47       * @throws Exception If something goes wrong inside
48       */
49      @Values
50      @ParameterizedTest
51      void sendsHttpRequestAndProcessesHttpResponse(
52          final Class<? extends Request> type
53      ) throws Exception {
54          final MkContainer container = new MkGrizzlyContainer().next(
55              new MkAnswer.Simple("\u20ac! hello!")
56          ).start();
57          RequestTestTemplate.request(container.home(), type)
58              .uri().path("/helloall").back()
59              .method(Request.GET)
60              .fetch().as(RestResponse.class)
61              .assertBody(Matchers.containsString("\u20ac!"))
62              .assertBody(Matchers.containsString("hello!"))
63              .assertStatus(HttpURLConnection.HTTP_OK);
64          final MkQuery query = container.take();
65          MatcherAssert.assertThat(
66              "should contains 'helloall'",
67              query,
68              MkQueryMatchers.hasPath(Matchers.containsString("helloall"))
69          );
70          MatcherAssert.assertThat(
71              "should be GET method",
72              query.method(),
73              Matchers.equalTo(Request.GET)
74          );
75          container.stop();
76      }
77  
78      /**
79       * BaseRequest can fetch HTTP headers.
80       * @param type Request type
81       * @throws Exception If something goes wrong inside
82       */
83      @Values
84      @ParameterizedTest
85      void sendsHttpRequestWithHeaders(
86          final Class<? extends Request> type
87      ) throws Exception {
88          final MkContainer container = new MkGrizzlyContainer().next(
89              new MkAnswer.Simple("")
90          ).start();
91          RequestTestTemplate.request(container.home(), type)
92              .through(UserAgentWire.class)
93              .uri().path("/foo1").back()
94              .method(Request.GET)
95              .header(HttpHeaders.ACCEPT, "*/*")
96              .fetch().as(RestResponse.class)
97              .assertStatus(HttpURLConnection.HTTP_OK);
98          final MkQuery query = container.take();
99          MatcherAssert.assertThat(
100             "should be accept '*' and user-agent 'jcabi'",
101             query.headers(),
102             Matchers.allOf(
103                 Matchers.hasEntry(
104                     Matchers.equalTo(HttpHeaders.ACCEPT),
105                     Matchers.hasItem(Matchers.containsString("*"))
106                 ),
107                 Matchers.hasEntry(
108                     Matchers.equalTo(HttpHeaders.USER_AGENT),
109                     Matchers.hasItem(Matchers.containsString("jcabi"))
110                 )
111             )
112         );
113         container.stop();
114     }
115 
116     /**
117      * BaseRequest can fetch GET request with query params.
118      * @param type Request type
119      * @throws Exception If something goes wrong inside
120      */
121     @Values
122     @ParameterizedTest
123     void sendsTextWithGetParameters(
124         final Class<? extends Request> type
125     ) throws Exception {
126         final MkContainer container = new MkGrizzlyContainer().next(
127             new MkAnswer.Simple("")
128         ).start();
129         final String value = "some value of this param &^%*;'\"\u20ac\"";
130         RequestTestTemplate.request(container.home(), type)
131             .uri().queryParam("q", value).back()
132             .method(Request.GET)
133             .header(HttpHeaders.ACCEPT, MediaType.TEXT_XML)
134             .fetch().as(RestResponse.class)
135             .assertStatus(HttpURLConnection.HTTP_OK);
136         final MkQuery query = container.take();
137         MatcherAssert.assertThat(
138             "should be ends with '€'",
139             URLDecoder.decode(
140                 query.uri().toString(),
141                 String.valueOf(StandardCharsets.UTF_8)
142             ),
143             Matchers.endsWith("\"€\"")
144         );
145         container.stop();
146     }
147 
148     /**
149      * BaseRequest can fetch body with HTTP POST request.
150      * @param type Request type
151      * @throws Exception If something goes wrong inside
152      */
153     @Values
154     @ParameterizedTest
155     void sendsTextWithPostRequestMatchParam(
156         final Class<? extends Request> type
157     ) throws Exception {
158         final MkContainer container = new MkGrizzlyContainer().next(
159             new MkAnswer.Simple("")
160         ).start();
161         final String value = "some random value of \u20ac param \"&^%*;'\"";
162         RequestTestTemplate.request(container.home(), type)
163             .method(Request.POST)
164             .body().formParam("p", value).back()
165             .header(
166                 HttpHeaders.CONTENT_TYPE,
167                 MediaType.APPLICATION_FORM_URLENCODED
168             )
169             .fetch().as(RestResponse.class)
170             .assertStatus(HttpURLConnection.HTTP_OK);
171         final MkQuery query = container.take();
172         MatcherAssert.assertThat(
173             "should be with param",
174             URLDecoder.decode(query.body(), StandardCharsets.UTF_8.toString()),
175             Matchers.is(String.format("p=%s", value))
176         );
177         container.stop();
178     }
179 
180     /**
181      * BaseRequest can fetch body with HTTP POST request with params.
182      * @param type Request type
183      * @throws Exception If something goes wrong inside
184      */
185     @Values
186     @ParameterizedTest
187     void sendsTextWithPostRequestMatchMultipleParams(
188         final Class<? extends Request> type
189     ) throws Exception {
190         final MkContainer container = new MkGrizzlyContainer().next(
191             new MkAnswer.Simple("")
192         ).start();
193         final String value = "some value of \u20ac param \"&^%*;'\"";
194         final String follow = "other value of \u20ac param \"&^%*;'\"";
195         RequestTestTemplate.request(container.home(), type)
196             .method(Request.POST)
197             .body()
198             .formParam("a", value)
199             .formParam("b", follow)
200             .back()
201             .header(
202                 HttpHeaders.CONTENT_TYPE,
203                 MediaType.APPLICATION_FORM_URLENCODED
204             )
205             .fetch().as(RestResponse.class)
206             .assertStatus(HttpURLConnection.HTTP_OK);
207         final MkQuery query = container.take();
208         MatcherAssert.assertThat(
209             "should be with multiple params",
210             URLDecoder.decode(query.body(), StandardCharsets.UTF_8.toString()),
211             Matchers.is(
212                 String.format("a=%s&b=%s", value, follow)
213             )
214         );
215         container.stop();
216     }
217 
218     /**
219      * BaseRequest can fetch multipart body with HTTP POST request
220      * with single byte param.
221      * @param type Request type
222      * @throws Exception If something goes wrong inside
223      * @checkstyle LineLength (30 lines)
224      */
225     @Values
226     @ParameterizedTest
227     void sendsMultipartPostRequestMatchByteParam(
228         final Class<? extends Request> type
229     ) throws Exception {
230         final MkContainer container = new MkGrizzlyContainer().next(
231             new MkAnswer.Simple("")
232         ).start();
233         final byte[] value = {Byte.parseByte("-122")};
234         RequestTestTemplate.request(container.home(), type)
235             .method(Request.POST)
236             .header(
237                 HttpHeaders.CONTENT_TYPE,
238                 String.format(
239                     "%s; boundary=--xx", MediaType.MULTIPART_FORM_DATA
240                 )
241             )
242             .multipartBody()
243             .formParam("x", value)
244             .back()
245             .fetch().as(RestResponse.class)
246             .assertStatus(HttpURLConnection.HTTP_OK);
247         final MkQuery query = container.take();
248         MatcherAssert.assertThat(
249             "should be match byte param",
250             query.body(),
251             Matchers.is(
252                 Joiner.on(Constants.CRLF).join(
253                     "----xx",
254                     "Content-Disposition: form-data; name=\"x\"; filename=\"binary\"",
255                     RequestTest.steamContentType(),
256                     "",
257                     "�",
258                     "----xx--"
259                 )
260             )
261         );
262         container.stop();
263     }
264 
265     /**
266      * BaseRequest can fetch multipart body with HTTP POST request
267      * with single param.
268      * @param type Request type
269      * @throws Exception If something goes wrong inside
270      * @checkstyle LineLength (30 lines)
271      */
272     @Values
273     @ParameterizedTest
274     void sendsMultipartPostRequestMatchSingleParam(
275         final Class<? extends Request> type
276     ) throws Exception {
277         final MkContainer container = new MkGrizzlyContainer().next(
278             new MkAnswer.Simple("")
279         ).start();
280         final String value = "value of \u20ac part param \"&^%*;'\"";
281         RequestTestTemplate.request(container.home(), type)
282             .method(Request.POST)
283             .header(
284                 HttpHeaders.CONTENT_TYPE,
285                 String.format(
286                     "%s; boundary=--xyz", MediaType.MULTIPART_FORM_DATA
287                 )
288             )
289             .multipartBody()
290             .formParam("c", value)
291             .back()
292             .fetch().as(RestResponse.class)
293             .assertStatus(HttpURLConnection.HTTP_OK);
294         final MkQuery query = container.take();
295         MatcherAssert.assertThat(
296             "should be match single param",
297             query.body(),
298             Matchers.is(
299                 Joiner.on(Constants.CRLF).join(
300                     "----xyz",
301                     "Content-Disposition: form-data; name=\"c\"; filename=\"binary\"",
302                     RequestTest.steamContentType(),
303                     "",
304                     "value of € part param \"&^%*;'\"",
305                     "----xyz--"
306                 )
307             )
308         );
309         container.stop();
310     }
311 
312     /**
313      * BaseRequest can fetch multipart body with HTTP POST request
314      * with two params.
315      * @param type Request type
316      * @throws Exception If something goes wrong inside
317      * @checkstyle LineLength (40 lines)
318      */
319     @Values
320     @ParameterizedTest
321     void sendsMultipartPostRequestMatchTwoParams(
322         final Class<? extends Request> type
323     ) throws Exception {
324         final MkContainer container = new MkGrizzlyContainer().next(
325             new MkAnswer.Simple("")
326         ).start();
327         final String value = "value of \u20ac one param \"&^%*;'\"";
328         final String other = "value of \u20ac two param \"&^%*;'\"";
329         RequestTestTemplate.request(container.home(), type)
330             .method(Request.POST)
331             .header(
332                 HttpHeaders.CONTENT_TYPE,
333                 String.format(
334                     "%s; boundary=xy--", MediaType.MULTIPART_FORM_DATA
335                 )
336             )
337             .multipartBody()
338             .formParam("d", value)
339             .formParam("e", other)
340             .back()
341             .fetch().as(RestResponse.class)
342             .assertStatus(HttpURLConnection.HTTP_OK);
343         final MkQuery query = container.take();
344         final String separator = "--xy--";
345         MatcherAssert.assertThat(
346             "should be match two params",
347             query.body(),
348             Matchers.is(
349                 Joiner.on(Constants.CRLF).join(
350                     separator,
351                     "Content-Disposition: form-data; name=\"d\"; filename=\"binary\"",
352                     RequestTest.steamContentType(),
353                     "",
354                     "value of € one param \"&^%*;'\"",
355                     separator,
356                     "Content-Disposition: form-data; name=\"e\"; filename=\"binary\"",
357                     RequestTest.steamContentType(),
358                     "",
359                     "value of € two param \"&^%*;'\"",
360                     "--xy----"
361                 )
362             )
363         );
364         container.stop();
365     }
366 
367     /**
368      * BaseRequest can fetch body with HTTP POST request.
369      * @param type Request type
370      * @throws Exception If something goes wrong inside
371      */
372     @Values
373     @ParameterizedTest
374     void sendsTextWithPostRequestMatchBody(
375         final Class<? extends Request> type
376     ) throws Exception {
377         final MkContainer container = new MkGrizzlyContainer().next(
378             new MkAnswer.Simple("")
379         ).start();
380         final String value = "\u20ac some body value with \"&^%*;'\"";
381         RequestTestTemplate.request(container.home(), type)
382             .method(Request.POST)
383             .header(
384                 HttpHeaders.CONTENT_TYPE,
385                 MediaType.APPLICATION_FORM_URLENCODED
386             )
387             .body()
388             .set(URLEncoder.encode(value, StandardCharsets.UTF_8.toString()))
389             .back()
390             .fetch().as(RestResponse.class)
391             .assertStatus(HttpURLConnection.HTTP_OK);
392         final MkQuery query = container.take();
393         MatcherAssert.assertThat(
394             "should be match body",
395             URLDecoder.decode(query.body(), StandardCharsets.UTF_8.toString()),
396             Matchers.containsString(value)
397         );
398         container.stop();
399     }
400 
401     /**
402      * BaseRequest can assert HTTP status code value.
403      * @param type Request type
404      * @throws Exception If something goes wrong inside.
405      */
406     @Values
407     @ParameterizedTest
408     void assertsHttpStatus(
409         final Class<? extends Request> type
410     ) throws Exception {
411         final MkContainer container = new MkGrizzlyContainer().next(
412             new MkAnswer.Simple(HttpURLConnection.HTTP_NOT_FOUND, "")
413         ).start();
414         RequestTestTemplate.request(container.home(), type)
415             .method(Request.GET)
416             .fetch().as(RestResponse.class)
417             .assertStatus(HttpURLConnection.HTTP_NOT_FOUND)
418             .assertStatus(
419                 Matchers.equalTo(HttpURLConnection.HTTP_NOT_FOUND)
420             );
421         container.stop();
422     }
423 
424     /**
425      * BaseRequest can assert response body.
426      * @param type Request type
427      * @throws Exception If something goes wrong inside.
428      */
429     @Values
430     @ParameterizedTest
431     void assertsHttpResponseBody(
432         final Class<? extends Request> type
433     ) throws Exception {
434         final MkContainer container = new MkGrizzlyContainer().next(
435             new MkAnswer.Simple("some text \u20ac")
436         ).start();
437         RequestTestTemplate.request(container.home(), type)
438             .method(Request.GET)
439             .fetch().as(RestResponse.class)
440             .assertBody(Matchers.containsString("text \u20ac"))
441             .assertStatus(HttpURLConnection.HTTP_OK);
442         container.stop();
443     }
444 
445     /**
446      * BaseRequest can assert HTTP headers in response.
447      * @param type Request type
448      * @throws Exception If something goes wrong inside.
449      */
450     @Values
451     @ParameterizedTest
452     void assertsHttpHeaders(
453         final Class<? extends Request> type
454     ) throws Exception {
455         final MkContainer container = new MkGrizzlyContainer().next(
456             new MkAnswer.Simple("").withHeader(
457                 HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN
458             )
459         ).start();
460         RequestTestTemplate.request(container.home(), type)
461             .method(Request.GET)
462             .fetch().as(RestResponse.class)
463             .assertStatus(HttpURLConnection.HTTP_OK)
464             .assertHeader(
465                 "absent-for-sure",
466                 Matchers.emptyIterableOf(String.class)
467             )
468             .assertHeader(
469                 HttpHeaders.CONTENT_TYPE,
470                 Matchers.everyItem(
471                     Matchers.containsString(MediaType.TEXT_PLAIN)
472                 )
473             );
474         container.stop();
475     }
476 
477     /**
478      * BaseRequest can assert response body content with XPath query.
479      * @param type Request type
480      * @throws Exception If something goes wrong inside.
481      */
482     @Values
483     @ParameterizedTest
484     void assertsResponseBodyWithXpathQuery(
485         final Class<? extends Request> type
486     ) throws Exception {
487         final MkContainer container = new MkGrizzlyContainer().next(
488             new MkAnswer.Simple("<root><a>\u0443\u0440\u0430!</a></root>")
489         ).start();
490         RequestTestTemplate.request(container.home(), type)
491             .method(Request.GET)
492             .fetch().as(RestResponse.class)
493             .assertStatus(HttpURLConnection.HTTP_OK)
494             .as(XmlResponse.class)
495             .assertXPath("/root/a[contains(.,'!')]");
496         container.stop();
497     }
498 
499     /**
500      * BaseRequest can work with URL returned by ContainerMocker.
501      * @throws Exception If something goes wrong inside
502      */
503     @Values
504     @ParameterizedTest
505     void mockedUrlIsInCorrectFormat() throws Exception {
506         final MkContainer container = new MkGrizzlyContainer().next(
507             new MkAnswer.Simple("")
508         ).start();
509         container.stop();
510         final URI uri = container.home();
511         MatcherAssert.assertThat(
512             "should be correct URI",
513             uri.toString().matches("^http://localhost:\\d+/$"),
514             Matchers.describedAs(uri.toString(), Matchers.is(true))
515         );
516     }
517 
518     /**
519      * BaseRequest can handle unicode in plain text response.
520      * @param type Request type
521      * @throws Exception If something goes wrong inside
522      */
523     @Values
524     @ParameterizedTest
525     void acceptsUnicodeInPlainText(
526         final Class<? extends Request> type
527     ) throws Exception {
528         final MkContainer container = new MkGrizzlyContainer().next(
529             new MkAnswer.Simple("\u0443\u0440\u0430!").withHeader(
530                 HttpHeaders.CONTENT_TYPE, "text/plain;charset=utf-8"
531             )
532         ).start();
533         RequestTestTemplate.request(container.home(), type)
534             .method(Request.GET)
535             .uri().path("/abcdefff").back()
536             .fetch().as(RestResponse.class)
537             .assertBody(Matchers.containsString("\u0443\u0440\u0430"))
538             .assertBody(Matchers.containsString("!"));
539         container.stop();
540     }
541 
542     /**
543      * BaseRequest can handle unicode in XML response.
544      * @param type Request type
545      * @throws Exception If something goes wrong inside
546      */
547     @Values
548     @ParameterizedTest
549     void acceptsUnicodeInXml(
550         final Class<? extends Request> type
551     ) throws Exception {
552         final MkContainer container = new MkGrizzlyContainer().next(
553             new MkAnswer.Simple("<text>\u0443\u0440\u0430!</text>").withHeader(
554                 HttpHeaders.CONTENT_TYPE, "text/xml;charset=utf-8"
555             )
556         ).start();
557         RequestTestTemplate.request(container.home(), type)
558             .method(Request.GET)
559             .uri().path("/barbar").back()
560             .fetch().as(XmlResponse.class)
561             .assertXPath("/text[contains(.,'\u0443\u0440\u0430')]");
562         container.stop();
563     }
564 
565     /**
566      * BaseRequest can use basic authentication scheme.
567      * @param type Request type
568      * @throws Exception If something goes wrong inside
569      */
570     @ParameterizedTest
571     @Values
572     void sendsBasicAuthenticationHeader(
573         final Class<? extends Request> type
574     ) throws Exception {
575         final MkContainer container = new MkGrizzlyContainer().next(
576             new MkAnswer.Simple("")
577         ).start();
578         final URI uri = UriBuilder.fromUri(container.home())
579             .userInfo("user:\u20ac\u20ac").build();
580         RequestTestTemplate.request(uri, type)
581             .through(BasicAuthWire.class)
582             .method(Request.GET)
583             .uri().path("/abcde").back()
584             .fetch().as(RestResponse.class)
585             .assertStatus(HttpURLConnection.HTTP_OK);
586         container.stop();
587         final MkQuery query = container.take();
588         MatcherAssert.assertThat(
589             "should be basic authorization",
590             query.headers(),
591             Matchers.hasEntry(
592                 Matchers.equalTo(HttpHeaders.AUTHORIZATION),
593                 Matchers.hasItem("Basic dXNlcjrigqzigqw=")
594             )
595         );
596     }
597 
598     /**
599      * BaseRequest can fetch GET request twice.
600      * @param type Request type
601      * @throws Exception If something goes wrong inside
602      */
603     @Values
604     @ParameterizedTest
605     void sendsIdenticalHttpRequestTwice(
606         final Class<? extends Request> type
607     ) throws Exception {
608         final MkContainer container = new MkGrizzlyContainer()
609             .next(new MkAnswer.Simple(""))
610             .next(new MkAnswer.Simple(""))
611             .next(new MkAnswer.Simple(""))
612             .start();
613         final Request req = RequestTestTemplate.request(container.home(), type)
614             .uri().path("/foo-X").back()
615             .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_XML);
616         req.method(Request.GET).fetch().as(RestResponse.class)
617             .assertStatus(HttpURLConnection.HTTP_OK);
618         req.method(Request.POST).fetch().as(RestResponse.class)
619             .assertStatus(HttpURLConnection.HTTP_OK);
620         req.method(Request.GET).fetch().as(RestResponse.class)
621             .assertStatus(HttpURLConnection.HTTP_OK);
622         container.stop();
623         MatcherAssert.assertThat(
624             "should be ends with 'foo-X'",
625             container.take(),
626             MkQueryMatchers.hasPath(Matchers.endsWith("foo-X"))
627         );
628     }
629 
630     /**
631      * BaseRequest can return redirect status (without redirecting).
632      * @param type Request type
633      * @throws Exception If something goes wrong inside
634      * @since 0.10
635      */
636     @Values
637     @ParameterizedTest
638     void doesntRedirectWithoutRequest(
639         final Class<? extends Request> type
640     ) throws Exception {
641         final MkContainer container = new MkGrizzlyContainer().next(
642             new MkAnswer.Simple("")
643                 .withStatus(HttpURLConnection.HTTP_SEE_OTHER)
644                 .withHeader(HttpHeaders.LOCATION, "http://www.google.com")
645         ).start();
646         RequestTestTemplate.request(container.home(), type)
647             .fetch().as(RestResponse.class)
648             .assertStatus(HttpURLConnection.HTTP_SEE_OTHER);
649         container.stop();
650     }
651 
652     /**
653      * BaseRequest can fetch body with HTTP POST request.
654      * @param type Request type
655      * @throws Exception If something goes wrong inside
656      */
657     @Values
658     @ParameterizedTest
659     void sendsRequestBodyAsInputStream(
660         final Class<? extends Request> type
661     ) throws Exception {
662         final MkContainer container = new MkGrizzlyContainer().next(
663             new MkAnswer.Simple("")
664         ).start();
665         final String value = "\u20ac body as stream \"&^%*;'\"";
666         final ByteArrayInputStream stream =
667             new ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8));
668         RequestTestTemplate.request(container.home(), type)
669             .method(Request.POST)
670             .header(
671                 HttpHeaders.CONTENT_TYPE,
672                 MediaType.APPLICATION_FORM_URLENCODED
673             )
674             .fetch(stream).as(RestResponse.class)
675             .assertStatus(HttpURLConnection.HTTP_OK);
676         final MkQuery query = container.take();
677         MatcherAssert.assertThat(
678             "should contains body as input stream",
679             query.body(),
680             Matchers.containsString(value)
681         );
682         container.stop();
683     }
684 
685     /**
686      * BaseRequest.fetch(InputStream) throws an exception if the body has been
687      * previously set.
688      * @param type Request type
689      */
690     @Values
691     @ParameterizedTest
692     void fetchThrowsExceptionWhenBodyIsNotEmpty(
693         final Class<? extends Request> type
694     ) {
695         Assertions.assertThrows(
696             IllegalStateException.class,
697             () -> RequestTestTemplate.request(
698                 new URI("http://localhost:78787"),
699                 type
700             )
701                 .method(Request.GET)
702                 .body().set("already set").back()
703                 .fetch(
704                     new ByteArrayInputStream(
705                         "hello".getBytes(StandardCharsets.UTF_8)
706                     )
707                 )
708         );
709     }
710 
711     /**
712      * Content type stream.
713      * @return Content type header.
714      */
715     private static String steamContentType() {
716         return "Content-Type: application/octet-stream";
717     }
718 }