1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package net.grinder.tools.tcpproxy;
23
24 import static net.grinder.testutility.SocketUtilities.findFreePort;
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertNotNull;
27 import static org.junit.Assert.fail;
28 import static org.mockito.Matchers.contains;
29 import static org.mockito.Matchers.isA;
30 import static org.mockito.Matchers.same;
31 import static org.mockito.Mockito.atLeast;
32 import static org.mockito.Mockito.reset;
33 import static org.mockito.Mockito.timeout;
34 import static org.mockito.Mockito.verify;
35 import static org.mockito.Mockito.verifyNoMoreInteractions;
36
37 import java.io.ByteArrayOutputStream;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.io.PrintWriter;
41 import java.io.StringWriter;
42 import java.net.ServerSocket;
43 import java.net.Socket;
44 import java.net.SocketException;
45 import java.net.UnknownHostException;
46 import java.util.Iterator;
47 import java.util.List;
48 import java.util.regex.Matcher;
49 import java.util.regex.Pattern;
50
51 import javax.net.ssl.SSLSocket;
52
53 import net.grinder.common.UncheckedInterruptedException;
54 import net.grinder.testutility.AssertUtilities;
55 import net.grinder.util.StreamCopier;
56 import net.grinder.util.TerminalColour;
57
58 import org.junit.After;
59 import org.junit.Before;
60 import org.junit.Test;
61 import org.mockito.ArgumentCaptor;
62 import org.mockito.Captor;
63 import org.mockito.Mock;
64 import org.mockito.MockitoAnnotations;
65 import org.slf4j.Logger;
66
67
68
69
70
71
72
73 public class TestHTTPProxyTCPProxyEngine {
74
75 private final List<AcceptAndEcho> m_echoers = new java.util.LinkedList<AcceptAndEcho>();
76
77 @Mock private TCPProxyFilter m_requestFilter;
78 @Mock private TCPProxyFilter m_responseFilter;
79 @Captor private ArgumentCaptor<ConnectionDetails> m_connectionDetailsCaptor;
80
81 @Mock private Logger m_logger;
82 private final PrintWriter m_out = new PrintWriter(new StringWriter());
83
84 private EndPoint m_localEndPoint;
85
86 private TCPProxySSLSocketFactory m_sslSocketFactory;
87
88 private EndPoint createFreeLocalEndPoint() throws IOException {
89 return new EndPoint("localhost", findFreePort());
90 }
91
92 @Before public void setUp() throws Exception {
93 MockitoAnnotations.initMocks(this);
94
95 m_localEndPoint = createFreeLocalEndPoint();
96
97 m_sslSocketFactory = new TCPProxySSLSocketFactoryImplementation();
98
99
100 System.setProperty("tcpproxy.connecttimeout", "500");
101 }
102
103 @After public void tearDown() throws Exception {
104 final Iterator<AcceptAndEcho> iterator = m_echoers.iterator();
105
106 while (iterator.hasNext()) {
107 iterator.next().shutdown();
108 }
109 }
110
111 @Test public void testBadLocalPort() throws Exception {
112 try {
113 new HTTPProxyTCPProxyEngine(null,
114 m_requestFilter,
115 m_responseFilter,
116 m_out,
117 m_logger,
118 new EndPoint("fictitious-host", 222),
119 false,
120 1000,
121 null,
122 null);
123 fail("Expected UnknownHostException");
124 }
125 catch (UnknownHostException e) {
126 }
127 }
128
129 @Test public void testTimeOut() throws Exception {
130
131 final TCPProxyEngine engine =
132 new HTTPProxyTCPProxyEngine(m_sslSocketFactory,
133 m_requestFilter,
134 m_responseFilter,
135 m_out,
136 m_logger,
137 m_localEndPoint,
138 false,
139 10,
140 null,
141 null);
142
143
144
145
146 engine.run();
147
148 verify(m_logger).error(contains("Listen time out"));
149 }
150
151
152
153
154
155
156
157
158
159
160
161
162
163 private String readResponse(final Socket clientSocket,
164 String terminalExpression)
165 throws IOException, InterruptedException {
166 final InputStream clientInputStream = clientSocket.getInputStream();
167
168 if (clientSocket instanceof SSLSocket) {
169
170
171
172 clientSocket.getInputStream().read(new byte[0]);
173 }
174
175 while (clientInputStream.available() <= 0) {
176 Thread.sleep(10);
177 }
178
179 final ByteArrayOutputStream response = new ByteArrayOutputStream();
180
181
182
183 final byte[] buffer = new byte[100];
184
185 final Pattern terminalPattern =
186 Pattern.compile(terminalExpression != null ? terminalExpression : ".*");
187
188 while (true) {
189 while (clientInputStream.available() > 0) {
190 final int bytesRead = clientInputStream.read(buffer, 0, buffer.length);
191 response.write(buffer, 0, bytesRead);
192 }
193
194
195
196 final String s = response.toString(System.getProperty("file.encoding"));
197
198 final Matcher matcher = terminalPattern.matcher(s);
199
200 if (matcher.find()) {
201 return s;
202 }
203
204 final long RETRIES = 100;
205
206 for (int i=0; i<RETRIES && clientInputStream.available() == 0; ++i) {
207 Thread.sleep(10);
208 }
209
210 if (clientInputStream.available() == 0) {
211 fail("Stream has been idle for " + (RETRIES * 10/1000d) +
212 " seconds and the terminal expression '" + terminalExpression +
213 "' does not match received data:\n" + s);
214 }
215 }
216 }
217
218 private void waitUntilAllStreamThreadsStopped(AbstractTCPProxyEngine engine)
219 throws InterruptedException {
220
221 for (int i = 0;
222 i < 10 && engine.getStreamThreadGroup().activeCount() > 0;
223 ++i) {
224 Thread.sleep(50);
225 }
226
227 assertEquals("Failed waiting for all stream threads to stop",
228 0, engine.getStreamThreadGroup().activeCount());
229 }
230
231 private void httpProxyEngineBadRequestTests(AbstractTCPProxyEngine engine)
232 throws Exception {
233
234 final Socket clientSocket =
235 new Socket(engine.getListenEndPoint().getHost(),
236 engine.getListenEndPoint().getPort());
237
238 final PrintWriter clientWriter =
239 new PrintWriter(clientSocket.getOutputStream(), true);
240
241 final String message = "This is not a valid HTTP message";
242 clientWriter.print(message);
243 clientWriter.flush();
244
245 final String response = readResponse(clientSocket, null);
246
247 AssertUtilities.assertStartsWith(response, "HTTP/1.0 400 Bad Request");
248 AssertUtilities.assertContainsHeader(response, "Connection", "close");
249 AssertUtilities.assertContainsHeader(response, "Content-Type", "text/html");
250 AssertUtilities.assertContains(response, message);
251
252 clientSocket.close();
253
254 verify(m_logger).error(contains("Failed to determine proxy destination"));
255
256 final Socket clientSocket2 =
257 new Socket(engine.getListenEndPoint().getHost(),
258 engine.getListenEndPoint().getPort());
259
260 clientSocket2.shutdownOutput();
261
262 try {
263 readResponse(clientSocket, null);
264 fail("Expected IOException");
265 }
266 catch (IOException e) {
267 }
268
269 clientSocket2.close();
270
271 verify(m_logger, timeout(10000).times(2))
272 .error(contains("Failed to determine proxy destination"));
273
274 final Socket clientSocket3 =
275 new Socket(engine.getListenEndPoint().getHost(),
276 engine.getListenEndPoint().getPort());
277
278 final byte[] hugeBunchOfCrap = new byte[50000];
279 clientSocket3.getOutputStream().write(hugeBunchOfCrap);
280
281 final String response3 = readResponse(clientSocket3, null);
282
283 AssertUtilities.assertStartsWith(response3, "HTTP/1.0 400 Bad Request");
284 AssertUtilities.assertContainsHeader(response3, "Connection", "close");
285 AssertUtilities.assertContainsHeader(response3, "Content-Type", "text/html");
286
287 clientSocket3.close();
288
289 waitUntilAllStreamThreadsStopped(engine);
290
291 verify(m_logger, timeout(10000))
292 .error(contains("failed to match HTTP message"));
293
294 verifyNoMoreInteractions(m_requestFilter, m_responseFilter);
295 }
296
297 private void httpProxyEngineGoodRequestTests(AbstractTCPProxyEngine engine)
298 throws Exception {
299
300 final AcceptAndEcho echoer = new AcceptAndEcho();
301
302 final Socket clientSocket =
303 new Socket(engine.getListenEndPoint().getHost(),
304 engine.getListenEndPoint().getPort());
305
306 final PrintWriter clientWriter =
307 new PrintWriter(clientSocket.getOutputStream(), true);
308
309 final String message0 =
310 "GET http://" + echoer.getEndPoint() + "/foo HTTP/1.1\r\n" +
311 "foo: bah\r\n" +
312 "\r\n" +
313 "A \u00e0 message";
314 clientWriter.print(message0);
315 clientWriter.flush();
316
317 final String response0 = readResponse(clientSocket, "message$");
318
319 AssertUtilities.assertStartsWith(response0, "GET /foo HTTP/1.1\r\n");
320 AssertUtilities.assertContainsHeader(response0, "foo", "bah");
321 AssertUtilities.assertContainsPattern(response0,
322 "\r\n\r\nA \u00e0 message$");
323
324 verify(m_requestFilter)
325 .connectionOpened(m_connectionDetailsCaptor.capture());
326
327 final ConnectionDetails requestConnectionDetails =
328 m_connectionDetailsCaptor.getValue();
329 assertEquals(echoer.getEndPoint(),
330 requestConnectionDetails.getRemoteEndPoint());
331
332 verify(m_requestFilter).handle(same(requestConnectionDetails),
333 isA(new byte[0].getClass()),
334 isA(Integer.class));
335
336 verify(m_responseFilter)
337 .connectionOpened(m_connectionDetailsCaptor.capture());
338
339 final ConnectionDetails responseConnectionDetails =
340 m_connectionDetailsCaptor.getValue();
341 assertEquals(requestConnectionDetails.getOtherEnd(),
342 responseConnectionDetails);
343
344 verify(m_responseFilter).handle(same(responseConnectionDetails),
345 isA(new byte[0].getClass()),
346 isA(Integer.class));
347
348 final String message1Headers =
349 "POST http://" + echoer.getEndPoint() + "/blah?x=123&y=99 HTTP/1.0\r\n" +
350 "\r\n" +
351 "Another message";
352 clientWriter.print(message1Headers);
353 clientWriter.flush();
354
355 final String message1PostBody = "Some data, lah 0x810x820x830x84 dah";
356 clientWriter.print(message1PostBody);
357 clientWriter.flush();
358
359 final String response1 = readResponse(clientSocket, "dah$");
360
361 AssertUtilities.assertStartsWith(response1,
362 "POST /blah?x=123&y=99 HTTP/1.0\r\n");
363 AssertUtilities.assertContainsPattern(response1,
364 "\r\n\r\nAnother message" +
365 message1PostBody + "$");
366
367
368 clientWriter.print(message1Headers);
369 clientWriter.flush();
370
371 final String response2a = readResponse(clientSocket, "Another message$");
372
373 AssertUtilities.assertStartsWith(response2a,
374 "POST /blah?x=123&y=99 HTTP/1.0\r\n");
375
376 clientWriter.print(message1PostBody);
377 clientWriter.flush();
378
379 final String response2b = readResponse(clientSocket, "dah$");
380 assertNotNull(response2b);
381
382 clientSocket.close();
383
384 waitUntilAllStreamThreadsStopped(engine);
385
386
387
388 verify(m_requestFilter, atLeast(4)).handle(same(requestConnectionDetails),
389 isA(new byte[0].getClass()),
390 isA(Integer.class));
391
392 verify(m_responseFilter, atLeast(4)).handle(same(responseConnectionDetails),
393 isA(new byte[0].getClass()),
394 isA(Integer.class));
395
396 verify(m_requestFilter).connectionClosed(requestConnectionDetails);
397 verify(m_responseFilter).connectionClosed(responseConnectionDetails);
398
399 verifyNoMoreInteractions(m_requestFilter, m_responseFilter);
400 }
401
402 private void httpsProxyEngineGoodRequestTest(AbstractTCPProxyEngine engine)
403 throws Exception {
404
405 final AcceptAndEcho echoer = new SSLAcceptAndEcho();
406
407 final Socket clientPlainSocket =
408 new Socket(engine.getListenEndPoint().getHost(),
409 engine.getListenEndPoint().getPort());
410
411 final PrintWriter clientWriter =
412 new PrintWriter(clientPlainSocket.getOutputStream(), true);
413 clientWriter.print("CONNECT " + echoer.getEndPoint() + "\r\n\r\n");
414 clientWriter.flush();
415
416 final String response = readResponse(clientPlainSocket, "Proxy-agent");
417
418 AssertUtilities.assertStartsWith(response, "HTTP/1.0 200 OK\r\n");
419 AssertUtilities.assertContainsHeader(response,
420 "Proxy-agent",
421 "The Grinder.*");
422
423 final Socket clientSSLSocket =
424 m_sslSocketFactory.createClientSocket(clientPlainSocket,
425 echoer.getEndPoint());
426
427 final PrintWriter secureClientWriter =
428 new PrintWriter(clientSSLSocket.getOutputStream(), true);
429
430
431
432 final String message0 =
433 "GET http://galafray/foo HTTP/1.1\r\n" +
434 "foo: bah\r\n" +
435 "\r\n" +
436 "A \u00e0 message";
437 secureClientWriter.print(message0);
438 secureClientWriter.flush();
439
440 final String response0 = readResponse(clientSSLSocket, "message$");
441
442 AssertUtilities.assertStartsWith(response0,
443 "GET http://galafray/foo HTTP/1.1\r\n");
444 AssertUtilities.assertContainsHeader(response0, "foo", "bah");
445 AssertUtilities.assertContainsPattern(response0,
446 "\r\n\r\nA \u00e0 message$");
447
448 verify(m_requestFilter)
449 .connectionOpened(m_connectionDetailsCaptor.capture());
450
451 final ConnectionDetails requestConnectionDetails =
452 m_connectionDetailsCaptor.getValue();
453 assertEquals(echoer.getEndPoint(),
454 requestConnectionDetails.getRemoteEndPoint());
455
456 verify(m_requestFilter).handle(same(requestConnectionDetails),
457 isA(new byte[0].getClass()),
458 isA(Integer.class));
459
460 verifyNoMoreInteractions(m_requestFilter);
461
462
463 verify(m_responseFilter)
464 .connectionOpened(m_connectionDetailsCaptor.capture());
465
466 final ConnectionDetails responseConnectionDetails =
467 m_connectionDetailsCaptor.getValue();
468 assertEquals(requestConnectionDetails.getOtherEnd(),
469 responseConnectionDetails);
470
471 verify(m_responseFilter).handle(same(responseConnectionDetails),
472 isA(new byte[0].getClass()),
473 isA(Integer.class));
474
475 verifyNoMoreInteractions(m_responseFilter);
476
477 clientSSLSocket.close();
478
479 waitUntilAllStreamThreadsStopped(engine);
480
481 verify(m_requestFilter, timeout(5000))
482 .connectionClosed(requestConnectionDetails);
483
484 verify(m_responseFilter, timeout(5000))
485 .connectionClosed(responseConnectionDetails);
486
487 verifyNoMoreInteractions(m_requestFilter, m_responseFilter);
488 }
489
490 @Test public void testHTTPProxyEngine() throws Exception {
491 final AbstractTCPProxyEngine engine =
492 new HTTPProxyTCPProxyEngine(m_sslSocketFactory,
493 m_requestFilter,
494 m_responseFilter,
495 m_out,
496 m_logger,
497 m_localEndPoint,
498 false,
499 100000,
500 null,
501 null);
502
503 final Thread engineThread = new Thread(engine, "Run engine");
504 engineThread.start();
505
506 verifyNoMoreInteractions(m_requestFilter, m_responseFilter);
507
508 assertEquals(m_localEndPoint, engine.getListenEndPoint());
509 assertNotNull(engine.getSocketFactory());
510 assertEquals(TerminalColour.NONE, engine.getRequestColour());
511 assertEquals(TerminalColour.NONE, engine.getResponseColour());
512
513 httpProxyEngineBadRequestTests(engine);
514 reset(m_requestFilter, m_responseFilter);
515 httpProxyEngineGoodRequestTests(engine);
516 reset(m_requestFilter, m_responseFilter);
517 httpsProxyEngineGoodRequestTest(engine);
518
519 engine.stop();
520 engineThread.join();
521
522
523 engine.stop();
524
525 verifyNoMoreInteractions(m_requestFilter, m_responseFilter);
526 }
527
528 @Test public void testColourHTTPProxyEngine() throws Exception {
529
530 final AbstractTCPProxyEngine engine =
531 new HTTPProxyTCPProxyEngine(m_sslSocketFactory,
532 m_requestFilter,
533 m_responseFilter,
534 m_out,
535 m_logger,
536 m_localEndPoint,
537 true,
538 100000,
539 null,
540 null);
541
542 final Thread engineThread = new Thread(engine, "Run engine");
543 engineThread.start();
544
545 verifyNoMoreInteractions(m_requestFilter, m_responseFilter);
546
547 assertEquals(m_localEndPoint, engine.getListenEndPoint());
548 assertNotNull(engine.getSocketFactory());
549 assertEquals(TerminalColour.RED, engine.getRequestColour());
550 assertEquals(TerminalColour.BLUE, engine.getResponseColour());
551
552 httpProxyEngineBadRequestTests(engine);
553 reset(m_requestFilter, m_responseFilter);
554 httpProxyEngineGoodRequestTests(engine);
555
556 engine.stop();
557 engineThread.join();
558
559
560 engine.stop();
561
562 verifyNoMoreInteractions(m_requestFilter, m_responseFilter);
563 }
564
565 @Test public void testWithChainedHTTPProxy() throws Exception {
566 final AcceptAndEcho echoer = new AcceptAndEcho();
567
568 final EndPoint chainedProxyEndPoint = createFreeLocalEndPoint();
569
570 final AbstractTCPProxyEngine chainedProxy =
571 new HTTPProxyTCPProxyEngine(m_sslSocketFactory,
572 m_requestFilter,
573 m_responseFilter,
574 m_out,
575 m_logger,
576 chainedProxyEndPoint,
577 true,
578 100000,
579 null,
580 null);
581
582 final Thread chainedProxyThread =
583 new Thread(chainedProxy, "Run chained proxy engine");
584 chainedProxyThread.start();
585
586 final AbstractTCPProxyEngine engine =
587 new HTTPProxyTCPProxyEngine(m_sslSocketFactory,
588 new NullFilter(),
589 new NullFilter(),
590 m_out,
591 m_logger,
592 m_localEndPoint,
593 true,
594 100000,
595 chainedProxyEndPoint,
596 null);
597 final Thread engineThread = new Thread(engine, "Run engine");
598 engineThread.start();
599
600 final Socket clientSocket =
601 new Socket(engine.getListenEndPoint().getHost(),
602 engine.getListenEndPoint().getPort());
603
604 final PrintWriter clientWriter =
605 new PrintWriter(clientSocket.getOutputStream(), true);
606
607 final String message0 =
608 "GET http://" + echoer.getEndPoint() + "/ HTTP/1.1\r\n" +
609 "foo: bah\r\n" +
610 "\r\n" +
611 "Proxy me";
612 clientWriter.print(message0);
613 clientWriter.flush();
614
615 final String response0 = readResponse(clientSocket, "Proxy me$");
616
617 AssertUtilities.assertStartsWith(response0, "GET / HTTP/1.1\r\n");
618 AssertUtilities.assertContainsHeader(response0, "foo", "bah");
619 AssertUtilities.assertContainsPattern(response0, "\r\n\r\nProxy me$");
620
621 verify(m_requestFilter)
622 .connectionOpened(m_connectionDetailsCaptor.capture());
623
624 final ConnectionDetails requestConnectionDetails =
625 m_connectionDetailsCaptor.getValue();
626
627 verify(m_requestFilter).handle(same(requestConnectionDetails),
628 isA(new byte[0].getClass()),
629 isA(Integer.class));
630 verifyNoMoreInteractions(m_requestFilter);
631
632 verify(m_responseFilter)
633 .connectionOpened(m_connectionDetailsCaptor.capture());
634
635 final ConnectionDetails responseConnectionDetails =
636 m_connectionDetailsCaptor.getValue();
637
638 verify(m_responseFilter).handle(same(responseConnectionDetails),
639 isA(new byte[0].getClass()),
640 isA(Integer.class));
641 verifyNoMoreInteractions(m_responseFilter);
642
643 chainedProxy.stop();
644 chainedProxyThread.join();
645
646 engine.stop();
647 engineThread.join();
648
649 waitUntilAllStreamThreadsStopped(engine);
650
651 verify(m_requestFilter).connectionClosed(requestConnectionDetails);
652 verify(m_responseFilter).connectionClosed(responseConnectionDetails);
653
654
655 engine.stop();
656
657 verifyNoMoreInteractions(m_requestFilter, m_responseFilter);
658 }
659
660 @Test public void testWithChainedHTTPSProxy() throws Exception {
661 final AcceptAndEcho echoer = new SSLAcceptAndEcho();
662
663 final EndPoint chainedProxyEndPoint = createFreeLocalEndPoint();
664
665 final AbstractTCPProxyEngine chainedProxy =
666 new HTTPProxyTCPProxyEngine(m_sslSocketFactory,
667 m_requestFilter,
668 m_responseFilter,
669 m_out,
670 m_logger,
671 chainedProxyEndPoint,
672 true,
673 100000,
674 null,
675 null);
676
677 final Thread chainedProxyThread =
678 new Thread(chainedProxy, "Run chained proxy engine");
679 chainedProxyThread.start();
680
681 final AbstractTCPProxyEngine engine =
682 new HTTPProxyTCPProxyEngine(m_sslSocketFactory,
683 new NullFilter(),
684 new NullFilter(),
685 m_out,
686 m_logger,
687 m_localEndPoint,
688 true,
689 100000,
690 null,
691 chainedProxyEndPoint);
692
693 final Thread engineThread = new Thread(engine, "Run engine");
694 engineThread.start();
695
696 final Socket clientPlainSocket =
697 new Socket(engine.getListenEndPoint().getHost(),
698 engine.getListenEndPoint().getPort());
699
700 final PrintWriter clientWriter =
701 new PrintWriter(clientPlainSocket.getOutputStream(), true);
702 clientWriter.print("CONNECT " + echoer.getEndPoint() + "\r\n\r\n");
703 clientWriter.flush();
704
705 final String response = readResponse(clientPlainSocket, "Proxy-agent");
706
707 AssertUtilities.assertStartsWith(response, "HTTP/1.0 200 OK\r\n");
708 AssertUtilities.assertContainsHeader(response,
709 "Proxy-agent",
710 "The Grinder.*");
711
712 final Socket clientSSLSocket =
713 m_sslSocketFactory.createClientSocket(clientPlainSocket,
714 echoer.getEndPoint());
715
716 final PrintWriter secureClientWriter =
717 new PrintWriter(clientSSLSocket.getOutputStream(), true);
718
719
720
721 final String message0 =
722 "GET http://galafray/foo HTTP/1.1\r\n" +
723 "foo: bah\r\n" +
724 "\r\n" +
725 "A \u00e0 message";
726 secureClientWriter.print(message0);
727 secureClientWriter.flush();
728
729 final String response0 = readResponse(clientSSLSocket, "message$");
730
731 AssertUtilities.assertStartsWith(response0,
732 "GET http://galafray/foo HTTP/1.1\r\n");
733 AssertUtilities.assertContainsHeader(response0, "foo", "bah");
734 AssertUtilities.assertContainsPattern(response0,
735 "\r\n\r\nA \u00e0 message$");
736
737 verify(m_requestFilter)
738 .connectionOpened(m_connectionDetailsCaptor.capture());
739
740 final ConnectionDetails requestConnectionDetails =
741 m_connectionDetailsCaptor.getValue();
742
743 verify(m_requestFilter).handle(same(requestConnectionDetails),
744 isA(new byte[0].getClass()),
745 isA(Integer.class));
746 verifyNoMoreInteractions(m_requestFilter);
747
748 verify(m_responseFilter)
749 .connectionOpened(m_connectionDetailsCaptor.capture());
750
751 final ConnectionDetails responseConnectionDetails =
752 m_connectionDetailsCaptor.getValue();
753
754 verify(m_responseFilter).handle(same(responseConnectionDetails),
755 isA(new byte[0].getClass()),
756 isA(Integer.class));
757 verifyNoMoreInteractions(m_responseFilter);
758
759 engine.stop();
760 engineThread.join();
761
762 chainedProxy.stop();
763 chainedProxyThread.join();
764
765 waitUntilAllStreamThreadsStopped(engine);
766
767 verify(m_requestFilter).connectionClosed(requestConnectionDetails);
768 verify(m_responseFilter).connectionClosed(responseConnectionDetails);
769
770
771
772
773
774 engine.stop();
775
776 verifyNoMoreInteractions(m_requestFilter, m_responseFilter);
777 }
778
779
780 @Test public void testStopWithBlockedFilterThreads() throws Exception {
781
782 final AcceptAndEcho echoer = new AcceptAndEcho();
783
784
785
786
787
788
789
790
791 final AbstractTCPProxyEngine engine =
792 new HTTPProxyTCPProxyEngine(m_sslSocketFactory,
793 new HungFilter(),
794 m_responseFilter,
795 m_out,
796 m_logger,
797 m_localEndPoint,
798 false,
799 100000,
800 null,
801 null);
802
803 final Thread engineThread = new Thread(engine, "Run engine");
804 engineThread.start();
805
806 final ServerSocket serverSocket = new ServerSocket(0);
807
808 final Socket clientSocket =
809 new Socket(engine.getListenEndPoint().getHost(),
810 engine.getListenEndPoint().getPort());
811
812 final PrintWriter clientWriter =
813 new PrintWriter(clientSocket.getOutputStream(), true);
814
815 final String message0 =
816 "GET http://" + echoer.getEndPoint() + "/foo HTTP/1.1\r\n" +
817 "foo: bah\r\n" +
818 "\r\n" +
819 "A \u00e0 message";
820 clientWriter.print(message0);
821 clientWriter.flush();
822
823
824
825 for (int i = 0;
826 i < 10 && engine.getStreamThreadGroup().activeCount() != 1;
827 ++i) {
828 Thread.sleep(50);
829 }
830
831 assertEquals(1, engine.getStreamThreadGroup().activeCount());
832
833 engine.stop();
834 engineThread.join();
835
836 serverSocket.close();
837
838 waitUntilAllStreamThreadsStopped(engine);
839 }
840
841 private static class HungFilter implements TCPProxyFilter {
842
843 public void connectionClosed(ConnectionDetails connectionDetails)
844 throws FilterException {
845 }
846
847 public void connectionOpened(ConnectionDetails connectionDetails)
848 throws FilterException {
849
850 try {
851 synchronized (this) {
852 wait();
853 }
854 }
855 catch (InterruptedException e) {
856 throw new UncheckedInterruptedException(e);
857 }
858 }
859
860 public byte[] handle(ConnectionDetails connectionDetails,
861 byte[] buffer,
862 int bytesRead) throws FilterException {
863 return null;
864 }
865 }
866
867
868 private class AcceptAndEcho implements Runnable {
869 private final ServerSocket m_serverSocket;
870
871 public AcceptAndEcho() throws IOException {
872 this(new ServerSocket(0));
873 }
874
875 protected AcceptAndEcho(ServerSocket serverSocket) throws IOException {
876 m_serverSocket = serverSocket;
877 new Thread(this, getClass().getName()).start();
878 m_echoers.add(this);
879 }
880
881 public EndPoint getEndPoint() {
882 return EndPoint.serverEndPoint(m_serverSocket);
883 }
884
885 public void run() {
886 try {
887 while (true) {
888 final Socket socket = m_serverSocket.accept();
889
890 new Thread(
891 new StreamCopier(1000, true).getRunnable(socket.getInputStream(),
892 socket.getOutputStream()),
893 "Echo thread").start();
894 }
895 }
896 catch (SocketException e) {
897
898 }
899 catch (IOException e) {
900 fail("Got a " + e.getClass());
901 }
902 }
903
904 public void shutdown() throws IOException {
905 m_serverSocket.close();
906 }
907 }
908
909 private class SSLAcceptAndEcho extends AcceptAndEcho {
910 public SSLAcceptAndEcho() throws IOException {
911 super(
912 m_sslSocketFactory.createServerSocket(createFreeLocalEndPoint(), 0));
913 }
914 }
915 }