View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit;
16  
17  import static org.junit.Assert.fail;
18  
19  import java.io.BufferedInputStream;
20  import java.io.ByteArrayInputStream;
21  import java.io.ByteArrayOutputStream;
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.io.Writer;
27  import java.lang.reflect.Field;
28  import java.lang.reflect.Method;
29  import java.net.URL;
30  import java.util.ArrayList;
31  import java.util.HashMap;
32  import java.util.List;
33  import java.util.Map;
34  
35  import javax.servlet.Servlet;
36  import javax.servlet.ServletException;
37  import javax.servlet.http.HttpServlet;
38  import javax.servlet.http.HttpServletRequest;
39  import javax.servlet.http.HttpServletResponse;
40  
41  import org.apache.commons.io.IOUtils;
42  import org.apache.commons.lang3.StringUtils;
43  import org.apache.http.HttpEntity;
44  import org.apache.http.HttpResponse;
45  import org.apache.http.ProtocolVersion;
46  import org.apache.http.StatusLine;
47  import org.apache.http.client.methods.HttpUriRequest;
48  import org.apache.http.entity.StringEntity;
49  import org.apache.http.entity.mime.MultipartEntityBuilder;
50  import org.apache.http.impl.client.HttpClientBuilder;
51  import org.apache.http.message.BasicHttpResponse;
52  import org.apache.http.message.BasicStatusLine;
53  import org.htmlunit.html.HtmlPage;
54  import org.htmlunit.http.HttpStatus;
55  import org.htmlunit.junit.BrowserRunner;
56  import org.htmlunit.util.KeyDataPair;
57  import org.htmlunit.util.MimeType;
58  import org.htmlunit.util.NameValuePair;
59  import org.htmlunit.util.ServletContentWrapper;
60  import org.junit.Test;
61  import org.junit.runner.RunWith;
62  
63  /**
64   * Tests methods in {@link HttpWebConnection}.
65   *
66   * @author David D. Kilzer
67   * @author Marc Guillemot
68   * @author Ahmed Ashour
69   * @author Ronald Brill
70   * @author Carsten Steul
71   */
72  @RunWith(BrowserRunner.class)
73  public class HttpWebConnectionTest extends WebServerTestCase {
74  
75      /**
76       * Assert that the two byte arrays are equal.
77       * @param expected the expected value
78       * @param actual the actual value
79       */
80      public static void assertEquals(final byte[] expected, final byte[] actual) {
81          assertEquals(null, expected, actual, expected.length);
82      }
83  
84      /**
85       * Assert that the two byte arrays are equal.
86       * @param message the message to display on failure
87       * @param expected the expected value
88       * @param actual the actual value
89       * @param length How many characters at the beginning of each byte array will be compared
90       */
91      public static void assertEquals(
92              final String message, final byte[] expected, final byte[] actual, final int length) {
93          if (expected == null && actual == null) {
94              return;
95          }
96          if (expected == null || expected.length < length) {
97              fail(message);
98          }
99          if (actual == null || actual.length < length) {
100             fail(message);
101         }
102         for (int i = 0; i < length; i++) {
103             assertEquals(message, expected[i], actual[i]);
104         }
105     }
106 
107     /**
108      * Assert that the two input streams are the same.
109      * @param expected the expected value
110      * @param actual the actual value
111      * @throws IOException if an IO problem occurs during comparison
112      */
113     public static void assertEquals(final InputStream expected, final InputStream actual) throws IOException {
114         assertEquals(null, expected, actual);
115     }
116 
117     /**
118      * Assert that the two input streams are the same.
119      * @param message the message to display on failure
120      * @param expected the expected value
121      * @param actual the actual value
122      * @throws IOException if an IO problem occurs during comparison
123      */
124     public static void assertEquals(final String message, final InputStream expected,
125             final InputStream actual) throws IOException {
126 
127         if (expected == null && actual == null) {
128             return;
129         }
130 
131         if (expected == null || actual == null) {
132             try {
133                 fail(message);
134             }
135             finally {
136                 try {
137                     if (expected != null) {
138                         expected.close();
139                     }
140                 }
141                 finally {
142                     if (actual != null) {
143                         actual.close();
144                     }
145                 }
146             }
147         }
148 
149         try (InputStream expectedBuf = new BufferedInputStream(expected)) {
150             try (InputStream actualBuf = new BufferedInputStream(actual)) {
151 
152                 final byte[] expectedArray = new byte[2048];
153                 final byte[] actualArray = new byte[2048];
154 
155                 int expectedLength = expectedBuf.read(expectedArray);
156                 while (true) {
157 
158                     final int actualLength = actualBuf.read(actualArray);
159                     assertEquals(message, expectedLength, actualLength);
160 
161                     if (expectedLength == -1) {
162                         break;
163                     }
164 
165                     assertEquals(message, expectedArray, actualArray, expectedLength);
166                     expectedLength = expectedBuf.read(expectedArray);
167                 }
168             }
169         }
170     }
171 
172     /**
173      * Tests Jetty.
174      * @throws Exception on failure
175      */
176     @Test
177     public void jettyProofOfConcept() throws Exception {
178         startWebServer("./");
179 
180         final WebClient client = getWebClient();
181         final Page page = client.getPage(URL_FIRST + "src/test/resources/event_coordinates.html");
182         final WebConnection defaultConnection = client.getWebConnection();
183         assertTrue(
184                 "HttpWebConnection should be the default",
185                 HttpWebConnection.class.isInstance(defaultConnection));
186         assertTrue("Response should be valid HTML", HtmlPage.class.isInstance(page));
187     }
188 
189     /**
190      * Test for feature request 1438216: HttpWebConnection should allow extension to create the HttpClient.
191      * @throws Exception if the test fails
192      */
193     @Test
194     public void designedForExtension() throws Exception {
195         startWebServer("./");
196 
197         final WebClient webClient = getWebClient();
198         final boolean[] tabCalled = {false};
199         final WebConnection myWebConnection = new HttpWebConnection(webClient) {
200             @Override
201             protected HttpClientBuilder createHttpClientBuilder() {
202                 tabCalled[0] = true;
203 
204                 final HttpClientBuilder builder = HttpClientBuilder.create();
205                 builder.setConnectionManagerShared(true);
206                 return builder;
207             }
208         };
209 
210         webClient.setWebConnection(myWebConnection);
211         webClient.getPage(URL_FIRST + "LICENSE.txt");
212         assertTrue("createHttpClient has not been called", tabCalled[0]);
213     }
214 
215     /**
216      * Test that the HttpClient is reinitialised after being shutdown.
217      * @throws Exception if the test fails
218      */
219     @Test
220     public void reinitialiseAfterClosing() throws Exception {
221         startWebServer("./");
222 
223         final WebClient webClient = getWebClient();
224         try (HttpWebConnection webConnection = new HttpWebConnection(webClient)) {
225             webClient.setWebConnection(webConnection);
226             webClient.getPage(URL_FIRST + "LICENSE.txt");
227             webConnection.close();
228             webClient.getPage(URL_FIRST + "pom.xml");
229         }
230     }
231 
232     /**
233      * Test that the right file part is built for a file that doesn't exist.
234      * @throws Exception if the test fails
235      */
236     @Test
237     public void buildFilePart() throws Exception {
238         final String encoding = "ISO8859-1";
239         final KeyDataPair pair = new KeyDataPair("myFile", new File("this/doesnt_exist.txt"), "something",
240                 MimeType.TEXT_PLAIN, encoding);
241         final MultipartEntityBuilder builder = MultipartEntityBuilder.create().setLaxMode();
242         try (HttpWebConnection webConnection = new HttpWebConnection(getWebClient())) {
243             webConnection.buildFilePart(pair, builder);
244         }
245         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
246         builder.build().writeTo(baos);
247         final String part = baos.toString(encoding);
248 
249         final String expected = "--(.*)\r\n"
250                 + "Content-Disposition: form-data; name=\"myFile\"; filename=\"doesnt_exist.txt\"\r\n"
251                 + "Content-Type: text/plain\r\n"
252                 + "\r\n"
253                 + "\r\n"
254                 + "--\\1--\r\n";
255         assertTrue(part, part.matches(expected));
256     }
257 
258     /**
259      * @throws Exception on failure
260      */
261     @Test
262     public void unicode() throws Exception {
263         startWebServer("./");
264         final WebClient client = getWebClient();
265         client.getPage(URL_FIRST + "src/test/resources/event_coordinates.html?param=\u00F6");
266     }
267 
268     /**
269      * @throws Exception if an error occurs
270      */
271     @Test
272     public void emptyPut() throws Exception {
273         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
274         servlets.put("/test", EmptyPutServlet.class);
275         startWebServer("./", null, servlets);
276 
277         final String[] expectedAlerts = {"1"};
278         final WebClient client = getWebClient();
279         client.setAjaxController(new NicelyResynchronizingAjaxController());
280         final List<String> collectedAlerts = new ArrayList<>();
281         client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
282 
283         assertEquals(0, client.getCookieManager().getCookies().size());
284         client.getPage(URL_FIRST + "test");
285         assertEquals(expectedAlerts, collectedAlerts);
286         assertEquals(1, client.getCookieManager().getCookies().size());
287     }
288 
289     /**
290      * Servlet for {@link #emptyPut()}.
291      */
292     public static class EmptyPutServlet extends ServletContentWrapper {
293         /** Constructor. */
294         public EmptyPutServlet() {
295             super(DOCTYPE_HTML
296                 + "<html>\n"
297                 + "<head>\n"
298                 + "  <script>\n"
299                 + "    function test() {\n"
300                 + "      var xhr = window.ActiveXObject?new ActiveXObject('Microsoft.XMLHTTP'):new XMLHttpRequest();\n"
301                 + "      xhr.open('PUT', '" + URL_FIRST + "test" + "', true);\n"
302                 + "      xhr.send();\n"
303                 + "      alert(1);\n"
304                 + "    }\n"
305                 + "  </script>\n"
306                 + "</head>\n"
307                 + "<body onload='test()'></body>\n"
308                 + "</html>");
309         }
310 
311         @Override
312         protected void doGet(final HttpServletRequest request,
313                 final HttpServletResponse response)
314             throws ServletException, IOException {
315             request.getSession().setAttribute("trigger", "session");
316             super.doGet(request, response);
317         }
318     }
319 
320     /**
321      * @throws Exception if the test fails
322      */
323     @Test
324     public void cookiesEnabledAfterDisable() throws Exception {
325         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
326         servlets.put("/test1", Cookie1Servlet.class);
327         servlets.put("/test2", Cookie2Servlet.class);
328         startWebServer("./", null, servlets);
329 
330         final WebClient client = getWebClient();
331 
332         client.getCookieManager().setCookiesEnabled(false);
333         HtmlPage page = client.getPage(URL_FIRST + "test1");
334         assertTrue(page.asNormalizedText().contains("No Cookies"));
335 
336         client.getCookieManager().setCookiesEnabled(true);
337         page = client.getPage(URL_FIRST + "test1");
338         assertTrue(page.asNormalizedText().contains("key1=value1"));
339     }
340 
341     /**
342      * Servlet for {@link #cookiesEnabledAfterDisable()}.
343      */
344     public static class Cookie1Servlet extends HttpServlet {
345 
346         /**
347          * {@inheritDoc}
348          */
349         @Override
350         protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
351             response.addCookie(new javax.servlet.http.Cookie("key1", "value1"));
352             response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
353             final String location = request.getRequestURL().toString().replace("test1", "test2");
354             response.setHeader("Location", location);
355         }
356     }
357 
358     /**
359      * Servlet for {@link #cookiesEnabledAfterDisable()}.
360      */
361     public static class Cookie2Servlet extends HttpServlet {
362 
363         /**
364          * {@inheritDoc}
365          */
366         @Override
367         protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
368             response.setContentType(MimeType.TEXT_HTML);
369             final Writer writer = response.getWriter();
370             if (request.getCookies() == null || request.getCookies().length == 0) {
371                 writer.write("No Cookies");
372             }
373             else {
374                 for (final javax.servlet.http.Cookie c : request.getCookies()) {
375                     writer.write(c.getName() + '=' + c.getValue());
376                 }
377             }
378         }
379     }
380 
381     /**
382      * @throws Exception if the test fails
383      */
384     @Test
385     public void remotePort() throws Exception {
386         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
387         servlets.put("/test", RemotePortServlet.class);
388         startWebServer("./", null, servlets);
389 
390         final WebClient client = getWebClient();
391 
392         String firstPort = null;
393 
394         for (int i = 0; i < 5; i++) {
395             final HtmlPage page = client.getPage(URL_FIRST + "test");
396             final String port = page.asNormalizedText();
397             if (firstPort == null) {
398                 firstPort = port;
399             }
400             assertEquals(firstPort, port);
401         }
402     }
403 
404     /**
405      * Servlet for {@link #remotePort()}.
406      */
407     public static class RemotePortServlet extends HttpServlet {
408 
409         /**
410          * {@inheritDoc}
411          */
412         @Override
413         protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
414             response.setContentType(MimeType.TEXT_HTML);
415             response.getWriter().write(String.valueOf(request.getRemotePort()));
416         }
417     }
418 
419     /**
420      * @throws Exception if an error occurs
421      */
422     @Test
423     public void contentLengthSmallerThanContent() throws Exception {
424         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
425         servlets.put("/contentLengthSmallerThanContent", ContentLengthSmallerThanContentServlet.class);
426         startWebServer("./", null, servlets);
427 
428         final WebClient client = getWebClient();
429         final HtmlPage page = client.getPage(URL_FIRST + "contentLengthSmallerThanContent");
430         assertEquals("visible text", page.asNormalizedText());
431     }
432 
433     /**
434      * Servlet for {@link #contentLengthSmallerThanContent()}.
435      */
436     public static class ContentLengthSmallerThanContentServlet extends ServletContentWrapper {
437 
438         /** Constructor. */
439         public ContentLengthSmallerThanContentServlet() {
440             super(DOCTYPE_HTML
441                 + "<html>\n"
442                 + "<body>\n"
443                 + "  <p>visible text</p>\n"
444                 + "  <p>missing text</p>\n"
445                 + "</body>\n"
446                 + "</html>");
447         }
448 
449         @Override
450         protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
451             throws IOException, ServletException {
452             response.setContentLength(getContent().indexOf("<p>missing text</p>"));
453             super.doGet(request, response);
454         }
455     }
456 
457     /**
458      * @throws Exception if an error occurs
459      */
460     @Test
461     public void contentLengthSmallerThanContentLargeContent() throws Exception {
462         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
463         servlets.put("/contentLengthSmallerThanContent", ContentLengthSmallerThanContentLargeContentServlet.class);
464         startWebServer("./", null, servlets);
465 
466         final WebClient client = getWebClient();
467         final HtmlPage page = client.getPage(URL_FIRST + "contentLengthSmallerThanContent");
468         assertTrue(page.asNormalizedText(), page.asNormalizedText().endsWith("visible text"));
469     }
470 
471     /**
472      * Servlet for {@link #contentLengthSmallerThanContentLargeContent()}.
473      */
474     public static class ContentLengthSmallerThanContentLargeContentServlet extends ServletContentWrapper {
475 
476         /** Constructor. */
477         public ContentLengthSmallerThanContentLargeContentServlet() {
478             super(DOCTYPE_HTML
479                 + "<html>\n"
480                 + "<body>\n"
481                 + "  <p>"
482                 + StringUtils.repeat("HtmlUnit  ", 1024 * 1024)
483                 + "</p>\n"
484                 + "  <p>visible text</p>\n"
485                 + "  <p>missing text</p>\n"
486                 + "</body>\n"
487                 + "</html>");
488         }
489 
490         @Override
491         protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
492             throws IOException, ServletException {
493             response.setContentLength(getContent().indexOf("<p>missing text</p>"));
494             super.doGet(request, response);
495         }
496     }
497 
498     /**
499      * @throws Exception if an error occurs
500      */
501     @Test
502     public void contentLengthLargerThanContent() throws Exception {
503         final String response = "HTTP/1.1 200 OK\r\n"
504                 + "Content-Length: 2000\r\n"
505                 + "Content-Type: text/html\r\n"
506                 + "\r\n"
507                 + DOCTYPE_HTML + "<html><body><p>visible text</p></body></html>";
508 
509         try (PrimitiveWebServer primitiveWebServer = new PrimitiveWebServer(null, response, null)) {
510             final WebClient client = getWebClient();
511 
512             final HtmlPage page = client.getPage("http://localhost:" + primitiveWebServer.getPort());
513             assertEquals("visible text", page.asNormalizedText());
514         }
515     }
516 
517     /**
518      * Test for bug #1861.
519      *
520      * @throws Exception if the test fails
521      */
522     @Test
523     public void userAgent() throws Exception {
524         final WebClient webClient = getWebClient();
525         final HttpWebConnection connection = (HttpWebConnection) webClient.getWebConnection();
526         final HttpClientBuilder builder = connection.getHttpClientBuilder();
527         final String userAgent = get(builder, "userAgent");
528         assertEquals(webClient.getBrowserVersion().getUserAgent(), userAgent);
529     }
530 
531     @SuppressWarnings("unchecked")
532     private static <T> T get(final Object o, final String fieldName) throws Exception {
533         final Field field = o.getClass().getDeclaredField(fieldName);
534         field.setAccessible(true);
535         return (T) field.get(o);
536     }
537 
538     /**
539      * Tests creation of a web response.
540      * @throws Exception if the test fails
541      */
542     @Test
543     public void makeWebResponse() throws Exception {
544         final URL url = new URL("http://htmlunit.sourceforge.net/");
545         final String content = DOCTYPE_HTML + "<html><head></head><body></body></html>";
546         final DownloadedContent downloadedContent = new DownloadedContent.InMemory(content.getBytes());
547         final long loadTime = 500L;
548 
549         final ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 0);
550         final StatusLine statusLine = new BasicStatusLine(protocolVersion, HttpStatus.OK_200, null);
551         final HttpResponse httpResponse = new BasicHttpResponse(statusLine);
552 
553         final HttpEntity responseEntity = new StringEntity(content);
554         httpResponse.setEntity(responseEntity);
555 
556         try (HttpWebConnection connection = new HttpWebConnection(getWebClient())) {
557             final Method method = connection.getClass().getDeclaredMethod("makeWebResponse",
558                     HttpResponse.class, WebRequest.class, DownloadedContent.class, long.class);
559             final WebResponse response = (WebResponse) method.invoke(connection,
560                     httpResponse, new WebRequest(url), downloadedContent, Long.valueOf(loadTime));
561 
562             assertEquals(HttpStatus.OK_200, response.getStatusCode());
563             assertEquals(url, response.getWebRequest().getUrl());
564             assertEquals(loadTime, response.getLoadTime());
565             assertEquals(content, response.getContentAsString());
566             try (InputStream is = response.getContentAsStream()) {
567                 assertEquals(content.getBytes(), IOUtils.toByteArray(is));
568             }
569             try (InputStream is = response.getContentAsStream()) {
570                 assertEquals(new ByteArrayInputStream(content.getBytes()), is);
571             }
572         }
573     }
574 
575     /**
576      * Test for overwriting the
577      * {@link HttpWebConnection#downloadResponse(HttpUriRequest, WebRequest, HttpResponse, long)}
578      * method.
579      *
580      * @throws Exception if the test fails
581      */
582     @Test
583     public void contentBlocking() throws Exception {
584         final byte[] content = new byte[] {77, 44};
585         final List<NameValuePair> headers = new ArrayList<>();
586         headers.add(new NameValuePair("Content-Encoding", "gzip"));
587         headers.add(new NameValuePair(HttpHeader.CONTENT_LENGTH, String.valueOf(content.length)));
588 
589         final MockWebConnection conn = getMockWebConnection();
590         conn.setResponse(URL_FIRST, content, 200, "OK", MimeType.APPLICATION_JSON, headers);
591 
592         startWebServer(conn);
593 
594         final WebClient client = getWebClient();
595         client.setWebConnection(new HttpWebConnection(client) {
596             @Override
597             protected WebResponse downloadResponse(final HttpUriRequest httpMethod,
598                     final WebRequest webRequest, final HttpResponse httpResponse,
599                     final long startTime) {
600 
601                 httpMethod.abort();
602 
603                 final DownloadedContent downloaded = new DownloadedContent.InMemory(null);
604                 final long endTime = System.currentTimeMillis();
605                 final WebResponse response = makeWebResponse(httpResponse, webRequest, downloaded, endTime - startTime);
606                 response.markAsBlocked("test blocking");
607                 return response;
608             }
609         });
610 
611         final UnexpectedPage page = client.getPage(URL_FIRST);
612         assertTrue(page.getWebResponse().wasBlocked());
613         assertEquals("test blocking", page.getWebResponse().getBlockReason());
614     }
615 
616     /**
617      * Test for overwriting the
618      * {@link HttpWebConnection#downloadResponse(HttpUriRequest, WebRequest, HttpResponse, long)}
619      * method.
620      *
621      * @throws Exception if the test fails
622      */
623     @Test
624     public void contentSizeBlocking() throws Exception {
625         stopWebServer();
626 
627         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
628         servlets.put("/big", BigContentServlet.class);
629         startWebServer("./", null, servlets);
630 
631         final WebClient client = getWebClient();
632         client.setWebConnection(new HttpWebConnection(client) {
633             @Override
634             protected WebResponse downloadResponse(final HttpUriRequest httpMethod,
635                     final WebRequest webRequest, final HttpResponse httpResponse,
636                     final long startTime) throws IOException {
637 
638                 final int contentLenght = Integer.parseInt(
639                         httpResponse.getFirstHeader(HttpHeader.CONTENT_LENGTH).getValue());
640 
641                 if (contentLenght < 1_000) {
642                     return super.downloadResponse(httpMethod, webRequest, httpResponse, startTime);
643                 }
644 
645                 httpMethod.abort();
646 
647                 final DownloadedContent downloaded = new DownloadedContent.InMemory(null);
648                 final long endTime = System.currentTimeMillis();
649                 final WebResponse response = makeWebResponse(httpResponse, webRequest, downloaded, endTime - startTime);
650                 response.markAsBlocked("blocking " + contentLenght);
651                 return response;
652             }
653         });
654 
655         final TextPage page = client.getPage(URL_FIRST + "big");
656         assertTrue(page.getWebResponse().wasBlocked());
657         assertEquals("blocking 10240000", page.getWebResponse().getBlockReason());
658         assertTrue("blocks sent " + BigContentServlet.SENT_, BigContentServlet.SENT_ < 5000);
659 
660         BigContentServlet.CANCEL_ = true;
661     }
662 
663     /**
664      * Servlet for bigContent().
665      */
666     public static class BigContentServlet extends HttpServlet {
667 
668         /** Helper. */
669         public static int SENT_;
670         /** Helper. */
671         public static boolean CANCEL_;
672 
673         /**
674          * {@inheritDoc}
675          */
676         @Override
677         protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
678             final int blockSize = 1024;
679             final int blockCount = 10_000;
680 
681             response.setHeader(HttpHeader.CONTENT_LENGTH, String.valueOf(blockSize * blockCount));
682 
683             final byte[] buffer = new byte[blockSize];
684             try (OutputStream out = response.getOutputStream()) {
685                 for (int i = 0; i < blockCount && !CANCEL_; i++) {
686                     SENT_++;
687                     out.write(buffer);
688                 }
689             }
690         }
691     }
692 }