1
2
3
4
5
6
7
8
9
10
11
12
13
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
65
66
67
68
69
70
71
72 @RunWith(BrowserRunner.class)
73 public class HttpWebConnectionTest extends WebServerTestCase {
74
75
76
77
78
79
80 public static void assertEquals(final byte[] expected, final byte[] actual) {
81 assertEquals(null, expected, actual, expected.length);
82 }
83
84
85
86
87
88
89
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
109
110
111
112
113 public static void assertEquals(final InputStream expected, final InputStream actual) throws IOException {
114 assertEquals(null, expected, actual);
115 }
116
117
118
119
120
121
122
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
174
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
191
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
217
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
234
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
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
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
291
292 public static class EmptyPutServlet extends ServletContentWrapper {
293
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
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
343
344 public static class Cookie1Servlet extends HttpServlet {
345
346
347
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
360
361 public static class Cookie2Servlet extends HttpServlet {
362
363
364
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
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
406
407 public static class RemotePortServlet extends HttpServlet {
408
409
410
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
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
435
436 public static class ContentLengthSmallerThanContentServlet extends ServletContentWrapper {
437
438
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
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
473
474 public static class ContentLengthSmallerThanContentLargeContentServlet extends ServletContentWrapper {
475
476
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
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
519
520
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
540
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
577
578
579
580
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
618
619
620
621
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
665
666 public static class BigContentServlet extends HttpServlet {
667
668
669 public static int SENT_;
670
671 public static boolean CANCEL_;
672
673
674
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 }