33import com .softwareverde .constable .bytearray .ByteArray ;
44import com .softwareverde .constable .bytearray .MutableByteArray ;
55import com .softwareverde .logging .Logger ;
6+ import com .softwareverde .util .Base64Util ;
67import com .softwareverde .util .IoUtil ;
78import com .softwareverde .util .ReflectionUtil ;
89import com .softwareverde .util .Util ;
@@ -26,6 +27,7 @@ class HttpRequestExecutionThread extends Thread {
2627 protected HttpRequest .Callback _callback ;
2728 protected final Integer _redirectCount ;
2829 protected HttpURLConnection _connection ;
30+ protected String _origin = null ;
2931
3032 protected Socket _extractConnectionSocket () {
3133 Object httpConnectionHolder = null ;
@@ -34,28 +36,53 @@ protected Socket _extractConnectionSocket() {
3436 final Object httpClient = ReflectionUtil .getValue (httpConnectionHolder , "http" );
3537 return ReflectionUtil .getValue (httpClient , "serverSocket" );
3638 }
37- catch (final Exception exception1 ) {
39+ catch (final Exception exception ) {
3840 if (httpConnectionHolder == null ) {
39- throw new RuntimeException ("Unable to obtain connection socket via reflection" , exception1 );
41+ throw new RuntimeException ("Unable to obtain connection socket via reflection. " , exception );
4042 }
43+
4144 try {
42- // unable to get standard http server socket, check for OkHttp implementation
45+ // Unable to get standard http server socket, check for OkHttp implementation.
4346 final Object httpEngine = ReflectionUtil .getValue (httpConnectionHolder , "httpEngine" );
4447 final Object streamAllocation = ReflectionUtil .getValue (httpEngine , "streamAllocation" );
4548 final Object realConnection = ReflectionUtil .getValue (streamAllocation , "connection" );
46- return ( Socket ) ReflectionUtil .getValue (realConnection , "socket" );
49+ return ReflectionUtil .getValue (realConnection , "socket" );
4750 }
4851 catch (final Exception exception2 ) {
49- Logger .debug ("Unable to get connection socket (1/2)" , exception1 );
50- Logger .debug ("Unable to get connection socket (2/2)" , exception2 );
51- throw new RuntimeException ("Unable to obtain connection socket via reflection" );
52+ exception2 .addSuppressed (exception );
53+ throw new RuntimeException ("Unable to obtain connection socket via reflection." , exception2 );
5254 }
5355 }
5456 }
5557
56- protected void _configureRequestForWebSocketUpgrade () {
58+ protected String _configureRequestForWebSocketUpgrade (final Boolean isSecureWebSocket ) {
59+ final SecureRandom secureRandom = new SecureRandom ();
60+ final byte [] key = new byte [16 ];
61+ secureRandom .nextBytes (key );
62+
5763 _httpRequest .setAllowWebSocketUpgrade (true );
5864 _httpRequest .setHeader ("Upgrade" , "websocket" );
65+ _httpRequest .setHeader ("Connection" , "upgrade" );
66+
67+ if (isSecureWebSocket ) {
68+ final String wssKey = Base64Util .toBase64String (key );
69+ _httpRequest .setHeader ("Sec-WebSocket-Version" , "13" );
70+ _httpRequest .setHeader ("Sec-WebSocket-Key" , wssKey );
71+ _httpRequest .setHeader ("Sec-WebSocket-Extensions" , "permessage-deflate; client_max_window_bits" );
72+ return wssKey ;
73+ }
74+
75+ return null ;
76+ }
77+
78+ protected ByteArray _readErrorStream (final InputStream inputStream ) throws Exception {
79+ if (inputStream == null ) { return null ; }
80+
81+ // Only attempt to read from the stream if bytes are immediately available without blocking...
82+ // The inputStream type is HttpInputStream which appears to honor InputStream::available().
83+ if (inputStream .available () < 1 ) { return null ; }
84+
85+ return MutableByteArray .wrap (IoUtil .readStreamOrThrow (inputStream ));
5986 }
6087
6188 public HttpRequestExecutionThread (final String httpRequestUrl , final HttpRequest httpRequest , final HttpRequest .Callback callback , final Integer redirectCount ) {
@@ -65,15 +92,26 @@ public HttpRequestExecutionThread(final String httpRequestUrl, final HttpRequest
6592 _redirectCount = redirectCount ;
6693 }
6794
95+ public void setOrigin (final String origin ) {
96+ _origin = origin ;
97+ }
98+
6899 public void run () {
69100 try {
101+ final String wssKey ;
70102 final String urlString ;
71103 {
72- final boolean isWebSocketRequest = _httpRequestUrl .startsWith ("ws://" ) || _httpRequestUrl .startsWith ("wss://" );
73- String requestUrl = _httpRequestUrl ;
74- if (isWebSocketRequest ) {
75- requestUrl = requestUrl .replaceFirst ("ws" , "http" );
76- _configureRequestForWebSocketUpgrade ();
104+ final boolean isSecureWebSocketRequest = _httpRequestUrl .startsWith ("wss://" );
105+ final boolean isWebSocketRequest = _httpRequestUrl .startsWith ("ws://" );
106+ final String requestUrl ;
107+ if (isWebSocketRequest || isSecureWebSocketRequest ) {
108+ requestUrl = _httpRequestUrl .replaceFirst ("ws" , "http" );
109+ final String generatedWssKey = _configureRequestForWebSocketUpgrade (isSecureWebSocketRequest );
110+ wssKey = (_httpRequest .validatesSslCertificates () ? generatedWssKey : null );
111+ }
112+ else {
113+ requestUrl = _httpRequestUrl ;
114+ wssKey = null ;
77115 }
78116 final String queryString = _httpRequest ._queryString ;
79117 if (! Util .isBlank (queryString )) {
@@ -86,7 +124,12 @@ public void run() {
86124
87125 final URL url = new URL (urlString );
88126
89- _connection = (HttpURLConnection ) (url .openConnection ());
127+ if (_origin == null ) {
128+ _origin = (url .getProtocol () + "://" + url .getHost ());
129+ }
130+ _httpRequest .setHeader ("Origin" , _origin );
131+
132+ _connection = (HttpURLConnection ) url .openConnection ();
90133
91134 if (_connection instanceof HttpsURLConnection ) {
92135 final HttpsURLConnection httpsConnection = ((HttpsURLConnection ) _connection );
@@ -131,9 +174,9 @@ public void run() {
131174 if (postData != null ) {
132175 _connection .setDoOutput (true );
133176
134- try (final DataOutputStream out = new DataOutputStream (_connection .getOutputStream ())) {
135- out .write (postData .getBytes ());
136- out .flush ();
177+ try (final DataOutputStream outputStream = new DataOutputStream (_connection .getOutputStream ())) {
178+ outputStream .write (postData .getBytes ());
179+ outputStream .flush ();
137180 }
138181 }
139182 }
@@ -167,10 +210,10 @@ public void run() {
167210 }
168211 }
169212
170- final boolean upgradeToWebSocket = (_httpRequest .allowsWebSocketUpgrade () && HttpRequest .containsUpgradeToWebSocketHeader (responseHeaders ));
213+ final boolean upgradeToWebSocket = (_httpRequest .allowsWebSocketUpgrade () && HttpRequest .containsUpgradeToWebSocketHeader (responseHeaders , wssKey ));
171214
172215 if (! upgradeToWebSocket ) {
173- if (responseCode >= 400 ) {
216+ if ( ( responseCode >= 400 ) || ( responseCode == 101 ) ) { // NOTE: Switching Protocols (101) when upgradeToWebSocket was not expected indicates a problem within the WebSocket handshake.
174217 InputStream errorStream = null ;
175218 { // Attempt to obtain the errorStream, but fallback to the inputStream if errorStream is unavailable.
176219 try {
@@ -185,11 +228,11 @@ public void run() {
185228 }
186229 }
187230
188- httpResponse ._rawResult = (( errorStream != null ) ? MutableByteArray . wrap ( IoUtil . readStreamOrThrow ( errorStream )) : null );
231+ httpResponse ._rawResult = _readErrorStream ( errorStream );
189232 }
190233 else {
191234 final InputStream inputStream = _connection .getInputStream ();
192- httpResponse ._rawResult = (( inputStream != null ) ? MutableByteArray .wrap (IoUtil .readStreamOrThrow (inputStream )) : null );
235+ httpResponse ._rawResult = (inputStream != null ? MutableByteArray .wrap (IoUtil .readStreamOrThrow (inputStream )) : null );
193236 }
194237
195238 // Close Connection
0 commit comments