Skip to content

Commit 337b17a

Browse files
authored
Failed WebSocket handshake response now includes body (#432)
1 parent c1c25ca commit 337b17a

File tree

5 files changed

+55
-40
lines changed

5 files changed

+55
-40
lines changed

awscrt/websocket.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -202,14 +202,13 @@ class OnConnectionSetupData:
202202
This is None if the connection failed before receiving an HTTP response.
203203
"""
204204

205-
# TODO: hook this up in C
206-
# handshake_response_body: bytes = None
207-
# """The HTTP response body, if you're interested.
205+
handshake_response_body: bytes = None
206+
"""The HTTP response body, if you're interested.
208207
209-
# This is only present if the server sent a full HTTP response rejecting the handshake.
210-
# It is not present if the connection succeeded,
211-
# or the connection failed for other reasons.
212-
# """
208+
This is only present if the server sent a full HTTP response rejecting the handshake.
209+
It is not present if the connection succeeded,
210+
or the connection failed for other reasons.
211+
"""
213212

214213

215214
@dataclass
@@ -442,21 +441,18 @@ def _on_connection_setup(
442441
error_code,
443442
websocket_binding,
444443
handshake_response_status,
445-
handshake_response_headers):
444+
handshake_response_headers,
445+
handshake_response_body):
446446

447447
cbdata = OnConnectionSetupData()
448448
if error_code:
449449
cbdata.exception = awscrt.exceptions.from_code(error_code)
450450
else:
451451
cbdata.websocket = WebSocket(websocket_binding)
452452

453-
if handshake_response_status != -1:
454-
cbdata.handshake_response_status = handshake_response_status
455-
456-
if handshake_response_headers is not None:
457-
cbdata.handshake_response_headers = handshake_response_headers
458-
459-
# TODO: get C to pass handshake_response_body
453+
cbdata.handshake_response_status = handshake_response_status
454+
cbdata.handshake_response_headers = handshake_response_headers
455+
cbdata.handshake_response_body = handshake_response_body
460456

461457
# Do not let exceptions from the user's callback bubble up any further.
462458
try:

source/websocket.c

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,7 @@
1616
static const char *s_websocket_capsule_name = "aws_websocket";
1717

1818
static void s_websocket_on_connection_setup(
19-
struct aws_websocket *websocket,
20-
int error_code,
21-
int handshake_response_status,
22-
const struct aws_http_header *handshake_response_header_array,
23-
size_t num_handshake_response_headers,
19+
const struct aws_websocket_on_connection_setup_data *setup,
2420
void *user_data);
2521

2622
static void s_websocket_on_connection_shutdown(struct aws_websocket *websocket, int error_code, void *user_data);
@@ -177,15 +173,11 @@ PyObject *aws_py_websocket_client_connect(PyObject *self, PyObject *args) {
177173
* that we can't actually check (and so may not actually work).
178174
*/
179175
static void s_websocket_on_connection_setup(
180-
struct aws_websocket *websocket,
181-
int error_code,
182-
int handshake_response_status,
183-
const struct aws_http_header *handshake_response_header_array,
184-
size_t num_handshake_response_headers,
176+
const struct aws_websocket_on_connection_setup_data *setup,
185177
void *user_data) {
186178

187179
/* sanity check: websocket XOR error_code is set. both cannot be set. both cannot be unset */
188-
AWS_FATAL_ASSERT((websocket != NULL) ^ (error_code != 0));
180+
AWS_FATAL_ASSERT((setup->websocket != NULL) ^ (setup->error_code != 0));
189181

190182
/* userdata is _WebSocketCore */
191183
PyObject *websocket_core_py = user_data;
@@ -194,17 +186,26 @@ static void s_websocket_on_connection_setup(
194186
PyGILState_STATE state = PyGILState_Ensure();
195187

196188
PyObject *websocket_binding_py = NULL;
197-
if (websocket) {
198-
websocket_binding_py = PyCapsule_New(websocket, s_websocket_capsule_name, s_websocket_capsule_destructor);
189+
if (setup->websocket) {
190+
websocket_binding_py =
191+
PyCapsule_New(setup->websocket, s_websocket_capsule_name, s_websocket_capsule_destructor);
199192
AWS_FATAL_ASSERT(websocket_binding_py && "capsule allocation failed");
200193
}
201194

195+
/* Any of the handshake_response variables could be NULL */
196+
197+
PyObject *status_code_py = NULL;
198+
if (setup->handshake_response_status != NULL) {
199+
status_code_py = PyLong_FromLong(*setup->handshake_response_status);
200+
AWS_FATAL_ASSERT(status_code_py && "status code allocation failed");
201+
}
202+
202203
PyObject *headers_py = NULL;
203-
if (num_handshake_response_headers > 0) {
204-
headers_py = PyList_New((Py_ssize_t)num_handshake_response_headers);
204+
if (setup->handshake_response_header_array != NULL) {
205+
headers_py = PyList_New((Py_ssize_t)setup->num_handshake_response_headers);
205206
AWS_FATAL_ASSERT(headers_py && "header list allocation failed");
206-
for (size_t i = 0; i < num_handshake_response_headers; ++i) {
207-
const struct aws_http_header *header_i = &handshake_response_header_array[i];
207+
for (size_t i = 0; i < setup->num_handshake_response_headers; ++i) {
208+
const struct aws_http_header *header_i = &setup->handshake_response_header_array[i];
208209
PyObject *tuple_py = PyTuple_New(2);
209210
AWS_FATAL_ASSERT(tuple_py && "header tuple allocation failed");
210211

@@ -220,14 +221,24 @@ static void s_websocket_on_connection_setup(
220221
}
221222
}
222223

224+
PyObject *body_py = NULL;
225+
if (setup->handshake_response_body != NULL) {
226+
/* AWS APIs are fine with NULL as the address of a 0-length array,
227+
* but python APIs requires that it be non-NULL */
228+
const char *ptr = setup->handshake_response_body->ptr ? (const char *)setup->handshake_response_body->ptr : "";
229+
body_py = PyBytes_FromStringAndSize(ptr, (Py_ssize_t)setup->handshake_response_body->len);
230+
AWS_FATAL_ASSERT(body_py && "response body allocation failed");
231+
}
232+
223233
PyObject *result = PyObject_CallMethod(
224234
websocket_core_py,
225235
"_on_connection_setup",
226-
"(iOiO)",
227-
error_code,
228-
websocket_binding_py ? websocket_binding_py : Py_None,
229-
handshake_response_status,
230-
headers_py ? headers_py : Py_None);
236+
"(iOOOO)",
237+
/* i */ setup->error_code,
238+
/* O */ websocket_binding_py ? websocket_binding_py : Py_None,
239+
/* O */ status_code_py ? status_code_py : Py_None,
240+
/* O */ headers_py ? headers_py : Py_None,
241+
/* O */ body_py ? body_py : Py_None);
231242

232243
if (result) {
233244
Py_DECREF(result);
@@ -240,10 +251,12 @@ static void s_websocket_on_connection_setup(
240251
}
241252

242253
Py_XDECREF(websocket_binding_py);
254+
Py_XDECREF(status_code_py);
243255
Py_XDECREF(headers_py);
256+
Py_XDECREF(body_py);
244257

245258
/* If setup failed, there will be no further callbacks, so release _WebSocketCore */
246-
if (error_code != 0) {
259+
if (setup->error_code != 0) {
247260
Py_DECREF(websocket_core_py);
248261
}
249262

test/test_websocket.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ def test_connect(self):
229229
self.assertEqual(101, setup_data.handshake_response_status)
230230
# check for response header we know should be there
231231
self.assertIn(("Upgrade", "websocket"), setup_data.handshake_response_headers)
232+
# a successful handshake response has no body
233+
self.assertIsNone(setup_data.handshake_response_body)
232234

233235
# now close the WebSocket
234236
setup_data.websocket.close()
@@ -287,6 +289,7 @@ def test_connect_failure_without_response(self):
287289
# nothing responded, so there should be no "handshake response"
288290
self.assertIsNone(setup_data.handshake_response_status)
289291
self.assertIsNone(setup_data.handshake_response_headers)
292+
self.assertIsNone(setup_data.handshake_response_body)
290293

291294
# ensure that on_connection_shutdown does NOT fire
292295
sleep(0.5)
@@ -317,6 +320,9 @@ def test_connect_failure_with_response(self):
317320
# check the HTTP response data
318321
self.assertGreaterEqual(setup_data.handshake_response_status, 400)
319322
self.assertIsNotNone(setup_data.handshake_response_headers)
323+
self.assertIsNotNone(setup_data.handshake_response_body)
324+
# check that body is a valid string
325+
self.assertGreater(len(setup_data.handshake_response_body.decode()), 0)
320326

321327
def test_exception_in_setup_callback_closes_websocket(self):
322328
with WebSocketServer(self.host, self.port) as server:

0 commit comments

Comments
 (0)