22
22
import java .net .SocketTimeoutException ;
23
23
import java .nio .ByteBuffer ;
24
24
import java .nio .charset .StandardCharsets ;
25
+ import java .util .ArrayList ;
25
26
import java .util .Arrays ;
26
27
import java .util .List ;
27
28
import java .util .concurrent .CountDownLatch ;
28
29
import java .util .concurrent .ExecutionException ;
29
30
import java .util .concurrent .Executor ;
30
31
import java .util .concurrent .TimeUnit ;
32
+ import java .util .concurrent .atomic .AtomicBoolean ;
31
33
import java .util .concurrent .atomic .AtomicLong ;
32
34
import java .util .concurrent .atomic .AtomicReference ;
33
35
import javax .net .ssl .SSLEngine ;
36
38
import javax .net .ssl .SSLHandshakeException ;
37
39
import javax .net .ssl .SSLPeerUnverifiedException ;
38
40
import javax .net .ssl .SSLSocket ;
41
+ import javax .servlet .http .HttpServletRequest ;
42
+ import javax .servlet .http .HttpServletResponse ;
39
43
40
44
import org .eclipse .jetty .client .api .ContentResponse ;
41
45
import org .eclipse .jetty .client .http .HttpClientTransportOverHTTP ;
42
46
import org .eclipse .jetty .http .HttpHeader ;
43
47
import org .eclipse .jetty .http .HttpHeaderValue ;
44
48
import org .eclipse .jetty .http .HttpScheme ;
45
49
import org .eclipse .jetty .http .HttpStatus ;
50
+ import org .eclipse .jetty .io .ArrayByteBufferPool ;
51
+ import org .eclipse .jetty .io .ArrayRetainableByteBufferPool ;
46
52
import org .eclipse .jetty .io .ByteBufferPool ;
47
53
import org .eclipse .jetty .io .ClientConnectionFactory ;
48
54
import org .eclipse .jetty .io .ClientConnector ;
49
55
import org .eclipse .jetty .io .Connection ;
50
56
import org .eclipse .jetty .io .ConnectionStatistics ;
51
57
import org .eclipse .jetty .io .EndPoint ;
58
+ import org .eclipse .jetty .io .RetainableByteBuffer ;
59
+ import org .eclipse .jetty .io .RetainableByteBufferPool ;
52
60
import org .eclipse .jetty .io .ssl .SslClientConnectionFactory ;
53
61
import org .eclipse .jetty .io .ssl .SslConnection ;
54
62
import org .eclipse .jetty .io .ssl .SslHandshakeListener ;
55
63
import org .eclipse .jetty .server .Connector ;
56
64
import org .eclipse .jetty .server .Handler ;
57
65
import org .eclipse .jetty .server .HttpConfiguration ;
58
66
import org .eclipse .jetty .server .HttpConnectionFactory ;
67
+ import org .eclipse .jetty .server .Request ;
59
68
import org .eclipse .jetty .server .SecureRequestCustomizer ;
60
69
import org .eclipse .jetty .server .Server ;
61
70
import org .eclipse .jetty .server .ServerConnector ;
62
71
import org .eclipse .jetty .server .SslConnectionFactory ;
63
72
import org .eclipse .jetty .toolchain .test .Net ;
73
+ import org .eclipse .jetty .util .Pool ;
64
74
import org .eclipse .jetty .util .StringUtil ;
65
75
import org .eclipse .jetty .util .ssl .SslContextFactory ;
66
76
import org .eclipse .jetty .util .thread .ExecutorThreadPool ;
71
81
import org .junit .jupiter .api .Test ;
72
82
import org .junit .jupiter .api .condition .EnabledForJreRange ;
73
83
import org .junit .jupiter .api .condition .JRE ;
84
+ import org .junit .jupiter .params .ParameterizedTest ;
85
+ import org .junit .jupiter .params .provider .ValueSource ;
74
86
87
+ import static org .awaitility .Awaitility .await ;
75
88
import static org .hamcrest .MatcherAssert .assertThat ;
89
+ import static org .hamcrest .Matchers .empty ;
76
90
import static org .hamcrest .Matchers .instanceOf ;
91
+ import static org .hamcrest .Matchers .is ;
77
92
import static org .junit .jupiter .api .Assertions .assertArrayEquals ;
78
93
import static org .junit .jupiter .api .Assertions .assertEquals ;
79
94
import static org .junit .jupiter .api .Assertions .assertNotNull ;
@@ -682,12 +697,7 @@ protected int networkFill(ByteBuffer input) throws IOException
682
697
// Trigger the creation of a new connection, but don't use it.
683
698
ConnectionPoolHelper .tryCreate (connectionPool );
684
699
// Verify that the connection has been created.
685
- while (true )
686
- {
687
- Thread .sleep (50 );
688
- if (connectionPool .getConnectionCount () == 1 )
689
- break ;
690
- }
700
+ await ().atMost (5 , TimeUnit .SECONDS ).until (connectionPool ::getConnectionCount , is (1 ));
691
701
692
702
// Wait for the server to idle timeout the connection.
693
703
Thread .sleep (idleTimeout + idleTimeout / 2 );
@@ -698,6 +708,299 @@ protected int networkFill(ByteBuffer input) throws IOException
698
708
assertEquals (0 , clientBytes .get ());
699
709
}
700
710
711
+ @ Test
712
+ public void testEncryptedInputBufferRepooling () throws Exception
713
+ {
714
+ SslContextFactory .Server serverTLSFactory = createServerSslContextFactory ();
715
+ QueuedThreadPool serverThreads = new QueuedThreadPool ();
716
+ serverThreads .setName ("server" );
717
+ server = new Server (serverThreads );
718
+ var retainableByteBufferPool = new ArrayRetainableByteBufferPool ()
719
+ {
720
+ @ Override
721
+ public Pool <RetainableByteBuffer > poolFor (int capacity , boolean direct )
722
+ {
723
+ return super .poolFor (capacity , direct );
724
+ }
725
+ };
726
+ server .addBean (retainableByteBufferPool );
727
+ HttpConfiguration httpConfig = new HttpConfiguration ();
728
+ httpConfig .addCustomizer (new SecureRequestCustomizer ());
729
+ HttpConnectionFactory http = new HttpConnectionFactory (httpConfig );
730
+ SslConnectionFactory ssl = new SslConnectionFactory (serverTLSFactory , http .getProtocol ())
731
+ {
732
+ @ Override
733
+ protected SslConnection newSslConnection (Connector connector , EndPoint endPoint , SSLEngine engine )
734
+ {
735
+ ByteBufferPool byteBufferPool = connector .getByteBufferPool ();
736
+ RetainableByteBufferPool retainableByteBufferPool = connector .getBean (RetainableByteBufferPool .class );
737
+ return new SslConnection (retainableByteBufferPool , byteBufferPool , connector .getExecutor (), endPoint , engine , isDirectBuffersForEncryption (), isDirectBuffersForDecryption ())
738
+ {
739
+ @ Override
740
+ protected int networkFill (ByteBuffer input ) throws IOException
741
+ {
742
+ int n = super .networkFill (input );
743
+ if (n > 0 )
744
+ throw new IOException ("boom" );
745
+ return n ;
746
+ }
747
+ };
748
+ }
749
+ };
750
+ connector = new ServerConnector (server , 1 , 1 , ssl , http );
751
+ server .addConnector (connector );
752
+ server .setHandler (new EmptyServerHandler ());
753
+ server .start ();
754
+
755
+ SslContextFactory .Client clientTLSFactory = createClientSslContextFactory ();
756
+ ClientConnector clientConnector = new ClientConnector ();
757
+ clientConnector .setSelectors (1 );
758
+ clientConnector .setSslContextFactory (clientTLSFactory );
759
+ QueuedThreadPool clientThreads = new QueuedThreadPool ();
760
+ clientThreads .setName ("client" );
761
+ clientConnector .setExecutor (clientThreads );
762
+ client = new HttpClient (new HttpClientTransportOverHTTP (clientConnector ));
763
+ client .setExecutor (clientThreads );
764
+ client .start ();
765
+
766
+ assertThrows (Exception .class , () -> client .newRequest ("localhost" , connector .getLocalPort ()).scheme (HttpScheme .HTTPS .asString ()).send ());
767
+
768
+ Pool <RetainableByteBuffer > bucket = retainableByteBufferPool .poolFor (16 * 1024 + 1 , ssl .isDirectBuffersForEncryption ());
769
+ assertEquals (1 , bucket .size ());
770
+ assertEquals (1 , bucket .getIdleCount ());
771
+ }
772
+
773
+ @ Test
774
+ public void testEncryptedOutputBufferRepooling () throws Exception
775
+ {
776
+ SslContextFactory .Server serverTLSFactory = createServerSslContextFactory ();
777
+ QueuedThreadPool serverThreads = new QueuedThreadPool ();
778
+ serverThreads .setName ("server" );
779
+ server = new Server (serverThreads );
780
+ List <ByteBuffer > leakedBuffers = new ArrayList <>();
781
+ ArrayByteBufferPool byteBufferPool = new ArrayByteBufferPool ()
782
+ {
783
+ @ Override
784
+ public ByteBuffer acquire (int size , boolean direct )
785
+ {
786
+ ByteBuffer acquired = super .acquire (size , direct );
787
+ leakedBuffers .add (acquired );
788
+ return acquired ;
789
+ }
790
+
791
+ @ Override
792
+ public void release (ByteBuffer buffer )
793
+ {
794
+ leakedBuffers .remove (buffer );
795
+ super .release (buffer );
796
+ }
797
+ };
798
+ server .addBean (byteBufferPool );
799
+ HttpConfiguration httpConfig = new HttpConfiguration ();
800
+ httpConfig .addCustomizer (new SecureRequestCustomizer ());
801
+ HttpConnectionFactory http = new HttpConnectionFactory (httpConfig );
802
+ SslConnectionFactory ssl = new SslConnectionFactory (serverTLSFactory , http .getProtocol ())
803
+ {
804
+ @ Override
805
+ protected SslConnection newSslConnection (Connector connector , EndPoint endPoint , SSLEngine engine )
806
+ {
807
+ ByteBufferPool byteBufferPool = connector .getByteBufferPool ();
808
+ RetainableByteBufferPool retainableByteBufferPool = connector .getBean (RetainableByteBufferPool .class );
809
+ return new SslConnection (retainableByteBufferPool , byteBufferPool , connector .getExecutor (), endPoint , engine , isDirectBuffersForEncryption (), isDirectBuffersForDecryption ())
810
+ {
811
+ @ Override
812
+ protected boolean networkFlush (ByteBuffer output ) throws IOException
813
+ {
814
+ throw new IOException ("bang" );
815
+ }
816
+ };
817
+ }
818
+ };
819
+ connector = new ServerConnector (server , 1 , 1 , ssl , http );
820
+ server .addConnector (connector );
821
+ server .setHandler (new EmptyServerHandler ());
822
+ server .start ();
823
+
824
+ SslContextFactory .Client clientTLSFactory = createClientSslContextFactory ();
825
+ ClientConnector clientConnector = new ClientConnector ();
826
+ clientConnector .setSelectors (1 );
827
+ clientConnector .setSslContextFactory (clientTLSFactory );
828
+ QueuedThreadPool clientThreads = new QueuedThreadPool ();
829
+ clientThreads .setName ("client" );
830
+ clientConnector .setExecutor (clientThreads );
831
+ client = new HttpClient (new HttpClientTransportOverHTTP (clientConnector ));
832
+ client .setExecutor (clientThreads );
833
+ client .start ();
834
+
835
+ assertThrows (Exception .class , () -> client .newRequest ("localhost" , connector .getLocalPort ()).scheme (HttpScheme .HTTPS .asString ()).send ());
836
+
837
+ await ().atMost (5 , TimeUnit .SECONDS ).until (() -> leakedBuffers , is (empty ()));
838
+ }
839
+
840
+ @ ParameterizedTest
841
+ @ ValueSource (booleans = {true , false })
842
+ public void testEncryptedOutputBufferRepoolingAfterNetworkFlushReturnsFalse (boolean close ) throws Exception
843
+ {
844
+ SslContextFactory .Server serverTLSFactory = createServerSslContextFactory ();
845
+ QueuedThreadPool serverThreads = new QueuedThreadPool ();
846
+ serverThreads .setName ("server" );
847
+ server = new Server (serverThreads );
848
+ List <ByteBuffer > leakedBuffers = new ArrayList <>();
849
+ ArrayByteBufferPool byteBufferPool = new ArrayByteBufferPool ()
850
+ {
851
+ @ Override
852
+ public ByteBuffer acquire (int size , boolean direct )
853
+ {
854
+ ByteBuffer acquired = super .acquire (size , direct );
855
+ leakedBuffers .add (acquired );
856
+ return acquired ;
857
+ }
858
+
859
+ @ Override
860
+ public void release (ByteBuffer buffer )
861
+ {
862
+ leakedBuffers .remove (buffer );
863
+ super .release (buffer );
864
+ }
865
+ };
866
+ server .addBean (byteBufferPool );
867
+ HttpConfiguration httpConfig = new HttpConfiguration ();
868
+ httpConfig .addCustomizer (new SecureRequestCustomizer ());
869
+ HttpConnectionFactory http = new HttpConnectionFactory (httpConfig );
870
+ AtomicBoolean failFlush = new AtomicBoolean (false );
871
+ SslConnectionFactory ssl = new SslConnectionFactory (serverTLSFactory , http .getProtocol ())
872
+ {
873
+ @ Override
874
+ protected SslConnection newSslConnection (Connector connector , EndPoint endPoint , SSLEngine engine )
875
+ {
876
+ ByteBufferPool byteBufferPool = connector .getByteBufferPool ();
877
+ RetainableByteBufferPool retainableByteBufferPool = connector .getBean (RetainableByteBufferPool .class );
878
+ return new SslConnection (retainableByteBufferPool , byteBufferPool , connector .getExecutor (), endPoint , engine , isDirectBuffersForEncryption (), isDirectBuffersForDecryption ())
879
+ {
880
+ @ Override
881
+ protected boolean networkFlush (ByteBuffer output ) throws IOException
882
+ {
883
+ if (failFlush .get ())
884
+ return false ;
885
+ return super .networkFlush (output );
886
+ }
887
+ };
888
+ }
889
+ };
890
+ connector = new ServerConnector (server , 1 , 1 , ssl , http );
891
+ server .addConnector (connector );
892
+ server .setHandler (new EmptyServerHandler ()
893
+ {
894
+ @ Override
895
+ protected void service (String target , Request jettyRequest , HttpServletRequest request , HttpServletResponse response )
896
+ {
897
+ failFlush .set (true );
898
+ if (close )
899
+ jettyRequest .getHttpChannel ().getEndPoint ().close ();
900
+ else
901
+ jettyRequest .getHttpChannel ().getEndPoint ().shutdownOutput ();
902
+ }
903
+ });
904
+ server .start ();
905
+
906
+ SslContextFactory .Client clientTLSFactory = createClientSslContextFactory ();
907
+ ClientConnector clientConnector = new ClientConnector ();
908
+ clientConnector .setSelectors (1 );
909
+ clientConnector .setSslContextFactory (clientTLSFactory );
910
+ QueuedThreadPool clientThreads = new QueuedThreadPool ();
911
+ clientThreads .setName ("client" );
912
+ clientConnector .setExecutor (clientThreads );
913
+ client = new HttpClient (new HttpClientTransportOverHTTP (clientConnector ));
914
+ client .setExecutor (clientThreads );
915
+ client .start ();
916
+
917
+ assertThrows (Exception .class , () -> client .newRequest ("localhost" , connector .getLocalPort ()).scheme (HttpScheme .HTTPS .asString ()).send ());
918
+
919
+ await ().atMost (5 , TimeUnit .SECONDS ).until (() -> leakedBuffers , is (empty ()));
920
+ }
921
+
922
+ @ ParameterizedTest
923
+ @ ValueSource (booleans = {true , false })
924
+ public void testEncryptedOutputBufferRepoolingAfterNetworkFlushThrows (boolean close ) throws Exception
925
+ {
926
+ SslContextFactory .Server serverTLSFactory = createServerSslContextFactory ();
927
+ QueuedThreadPool serverThreads = new QueuedThreadPool ();
928
+ serverThreads .setName ("server" );
929
+ server = new Server (serverThreads );
930
+ List <ByteBuffer > leakedBuffers = new ArrayList <>();
931
+ ArrayByteBufferPool byteBufferPool = new ArrayByteBufferPool ()
932
+ {
933
+ @ Override
934
+ public ByteBuffer acquire (int size , boolean direct )
935
+ {
936
+ ByteBuffer acquired = super .acquire (size , direct );
937
+ leakedBuffers .add (acquired );
938
+ return acquired ;
939
+ }
940
+
941
+ @ Override
942
+ public void release (ByteBuffer buffer )
943
+ {
944
+ leakedBuffers .remove (buffer );
945
+ super .release (buffer );
946
+ }
947
+ };
948
+ server .addBean (byteBufferPool );
949
+ HttpConfiguration httpConfig = new HttpConfiguration ();
950
+ httpConfig .addCustomizer (new SecureRequestCustomizer ());
951
+ HttpConnectionFactory http = new HttpConnectionFactory (httpConfig );
952
+ AtomicBoolean failFlush = new AtomicBoolean (false );
953
+ SslConnectionFactory ssl = new SslConnectionFactory (serverTLSFactory , http .getProtocol ())
954
+ {
955
+ @ Override
956
+ protected SslConnection newSslConnection (Connector connector , EndPoint endPoint , SSLEngine engine )
957
+ {
958
+ ByteBufferPool byteBufferPool = connector .getByteBufferPool ();
959
+ RetainableByteBufferPool retainableByteBufferPool = connector .getBean (RetainableByteBufferPool .class );
960
+ return new SslConnection (retainableByteBufferPool , byteBufferPool , connector .getExecutor (), endPoint , engine , isDirectBuffersForEncryption (), isDirectBuffersForDecryption ())
961
+ {
962
+ @ Override
963
+ protected boolean networkFlush (ByteBuffer output ) throws IOException
964
+ {
965
+ if (failFlush .get ())
966
+ throw new IOException ();
967
+ return super .networkFlush (output );
968
+ }
969
+ };
970
+ }
971
+ };
972
+ connector = new ServerConnector (server , 1 , 1 , ssl , http );
973
+ server .addConnector (connector );
974
+ server .setHandler (new EmptyServerHandler ()
975
+ {
976
+ @ Override
977
+ protected void service (String target , Request jettyRequest , HttpServletRequest request , HttpServletResponse response ) throws IOException
978
+ {
979
+ failFlush .set (true );
980
+ if (close )
981
+ jettyRequest .getHttpChannel ().getEndPoint ().close ();
982
+ else
983
+ jettyRequest .getHttpChannel ().getEndPoint ().shutdownOutput ();
984
+ }
985
+ });
986
+ server .start ();
987
+
988
+ SslContextFactory .Client clientTLSFactory = createClientSslContextFactory ();
989
+ ClientConnector clientConnector = new ClientConnector ();
990
+ clientConnector .setSelectors (1 );
991
+ clientConnector .setSslContextFactory (clientTLSFactory );
992
+ QueuedThreadPool clientThreads = new QueuedThreadPool ();
993
+ clientThreads .setName ("client" );
994
+ clientConnector .setExecutor (clientThreads );
995
+ client = new HttpClient (new HttpClientTransportOverHTTP (clientConnector ));
996
+ client .setExecutor (clientThreads );
997
+ client .start ();
998
+
999
+ assertThrows (Exception .class , () -> client .newRequest ("localhost" , connector .getLocalPort ()).scheme (HttpScheme .HTTPS .asString ()).send ());
1000
+
1001
+ await ().atMost (5 , TimeUnit .SECONDS ).until (() -> leakedBuffers , is (empty ()));
1002
+ }
1003
+
701
1004
@ Test
702
1005
public void testNeverUsedConnectionThenClientIdleTimeout () throws Exception
703
1006
{
@@ -780,12 +1083,7 @@ protected int networkFill(ByteBuffer input) throws IOException
780
1083
// Trigger the creation of a new connection, but don't use it.
781
1084
ConnectionPoolHelper .tryCreate (connectionPool );
782
1085
// Verify that the connection has been created.
783
- while (true )
784
- {
785
- Thread .sleep (50 );
786
- if (connectionPool .getConnectionCount () == 1 )
787
- break ;
788
- }
1086
+ await ().atMost (5 , TimeUnit .SECONDS ).until (connectionPool ::getConnectionCount , is (1 ));
789
1087
790
1088
// Wait for the client to idle timeout the connection.
791
1089
Thread .sleep (idleTimeout + idleTimeout / 2 );
0 commit comments