Klaus Demo bjoern / b4059d4
support for 417 Expectation Error Tom Brennan a month ago
6 changed file(s) with 207 addition(s) and 63 deletion(s). Raw diff Collapse all Expand all
1313
1414 typedef struct { char* data; size_t len; } string;
1515
16 enum http_status { HTTP_BAD_REQUEST = 1, HTTP_LENGTH_REQUIRED, HTTP_SERVER_ERROR };
16 enum http_status {
17 HTTP_BAD_REQUEST = 1,
18 HTTP_LENGTH_REQUIRED,
19 HTTP_EXPECTATION_FAILED,
20 HTTP_SERVER_ERROR
21 };
1722
1823 size_t unquote_url_inplace(char* url, size_t len);
1924 void _init_common(void);
1717 #define _PEP3333_String_FromFormat(...) PyUnicode_FromFormat(__VA_ARGS__)
1818 #define _PEP3333_String_GET_SIZE(u) PyUnicode_GET_LENGTH(u)
1919 #define _PEP3333_String_Concat(u1, u2) PyUnicode_Concat(u1, u2)
20 #define _PEP3333_String_CompareWithASCIIString(o, c_str) PyUnicode_CompareWithASCIIString(o, c_str)
2021
2122 #else
2223
5253
5354 return ret;
5455 }
56
57 static int _PEP3333_String_CompareWithASCIIString(PyObject *o, const char *c_str)
58 {
59 return memcmp(_PEP3333_Bytes_AS_DATA(o), c_str, _PEP3333_Bytes_GET_SIZE(o));
60 }
5561 #endif
5662
5763 #endif /* _PY2PY3_H */
144144 /* Ignore invalid header */
145145 if(PARSER->invalid_header) {
146146 return 0;
147 }
148
149 /* If `Expect` is encountered, set request->state to notify that the client
150 * expects an "HTTP/1.1 100 Continue" response before it will send the body */
151 if (!strncmp(field, HTTP_EXPECT, len)) {
152 if (parser->http_major > 0 && parser->http_minor > 0) {
153 REQUEST->state.expect_continue = true;
154 }
155147 }
156148
157149 char field_processed[len];
187179 if(!PARSER->invalid_header) {
188180 /* Set header, or append data to header if this is not the first call */
189181 _set_or_append_header(REQUEST->headers, PARSER->field, value, len);
190 }
182
183 /*
184 ** HTTP/1.1 describes the header `Expect: 100-continue`, which provides
185 ** a mechanism for a client to request permission from a server to send
186 ** the rest of the request (i.e., the body). This restriction is entirely
187 ** client-side in that, regardless of this header existing, the client
188 ** can opt to send the body immediately (i.e., contradicting the header).
189 ** In that case, the header MAY be ignored (according to the RFC). It's
190 ** important to note that this mechanism is not intended to mitigate DoS
191 ** attacks, etc. It's an optional client-side courtesy only.
192 **
193 ** Some popular clients (e.g., cURL) do in fact wait for an indefinite
194 ** time period before sending the rest of the request, and so it is
195 ** appropriate to respond with `HTTP/1.1 100 Continue` in that case.
196 **
197 ** In the future, it may be a reasonable improvement to provide a callback
198 ** API to provide a mechanism for calling code (i.e., Python) to determine
199 ** whether continuation is appropriate based on the existing headers, but
200 ** currently the behavior implemented here is to respond with 100-continue
201 ** automatically as soon as the Expect header is detected, or
202 ** `417 Expectation Failed` if the header contains anything other than
203 ** "100-continue".
204 **
205 ** see https://tools.ietf.org/html/rfc2616#page-48 for specifics.
206 */
207 if (REQUEST->state.expect_continue || REQUEST->state.error_code)
208 return 0;
209
210 if (parser->http_major > 0 && parser->http_minor > 0) {
211 bool field_is_http_expect = !_PEP3333_String_CompareWithASCIIString(
212 PARSER->field, "HTTP_EXPECT"
213 );
214
215 if (field_is_http_expect) {
216 if (!strncmp(value, "100-continue", len)) {
217 REQUEST->state.expect_continue = true;
218 } else {
219 REQUEST->state.error_code = HTTP_EXPECTATION_FAILED;
220 }
221 }
222 }
223 }
224
191225 return 0;
192226 }
193227
195229 on_body(http_parser* parser, const char* data, const size_t len)
196230 {
197231 PyObject *body;
232
233 /*
234 ** If you're reading the body, you're not waiting for it, so
235 ** no need to respond 100-continue.
236 ** See RFC 2616 https://tools.ietf.org/html/rfc2616#page-49 for details.
237 ** See also comment above in `on_header_value`.
238 */
239 REQUEST->state.expect_continue = false;
198240
199241 body = PyDict_GetItem(REQUEST->headers, _wsgi_input);
200242 if (body == NULL) {
4949 #define REQUEST_FROM_WATCHER(watcher) \
5050 (Request*)((size_t)watcher - (size_t)(&(((Request*)NULL)->ev_watcher)));
5151
52 #define HTTP_EXPECT "Expect"
53 #define CONTINUE_RESPONSE "HTTP/1.1 100 Continue\r\n\r\n"
54
5552 Request* Request_new(ServerInfo*, int client_fd, const char* client_addr);
5653 void Request_parse(Request*, const char*, const size_t);
5754 void Request_reset(Request*);
2828 NULL, /* Error codes start at 1 because 0 means "no error" */
2929 "HTTP/1.1 400 Bad Request\r\n\r\n",
3030 "HTTP/1.1 406 Length Required\r\n\r\n",
31 "HTTP/1.1 417 Expectation Failed\r\n\r\n",
3132 "HTTP/1.1 500 Internal Server Error\r\n\r\n"
3233 };
34
35 static const char *CONTINUE = "HTTP/1.1 100 Continue\r\n\r\n";
3336
3437 enum _rw_state {
3538 not_yet_done = 1,
3639 done,
3740 aborted,
41 expect_continue,
3842 };
3943 typedef enum _rw_state read_state;
4044 typedef enum _rw_state write_state;
168172
169173 ev_io_init(&request->ev_watcher, &ev_io_on_read,
170174 client_fd, EV_READ);
175 ev_io_start(mainloop, &request->ev_watcher);
176 }
177
178
179 static void
180 start_reading(struct ev_loop *mainloop, Request *request)
181 {
182 ev_io_init(&request->ev_watcher, &ev_io_on_read,
183 request->client_fd, EV_READ);
184 ev_io_start(mainloop, &request->ev_watcher);
185 }
186
187 static void
188 start_writing(struct ev_loop *mainloop, Request *request)
189 {
190 ev_io_init(&request->ev_watcher, &ev_io_on_write,
191 request->client_fd, EV_WRITE);
171192 ev_io_start(mainloop, &request->ev_watcher);
172193 }
173194
210231 http_error_messages[request->state.error_code]);
211232 assert(request->iterator == NULL);
212233 } else if(request->state.parse_finished) {
213 /* HTTP parse successful */
234 /* HTTP parse successful, meaning we have the entire
235 * request (the header _and_ the body). */
214236 read_state = done;
215 bool wsgi_ok = wsgi_call_application(request);
216 if (!wsgi_ok) {
237
238 if (!wsgi_call_application(request)) {
217239 /* Response is "HTTP 500 Internal Server Error" */
218240 DBG_REQ(request, "WSGI app error");
219241 assert(PyErr_Occurred());
224246 http_error_messages[HTTP_SERVER_ERROR]);
225247 }
226248 } else if (request->state.expect_continue) {
227 read_state = done;
228 request->current_chunk = _PEP3333_Bytes_FromString(
229 CONTINUE_RESPONSE);
230 request->state.keep_alive = true;
249 /*
250 ** Handle "Expect: 100-continue" header.
251 ** See https://tools.ietf.org/html/rfc2616#page-48 and `on_header_value`
252 ** in request.c for more details.
253 */
254 read_state = expect_continue;
231255 } else {
232256 /* Wait for more data */
233257 read_state = not_yet_done;
237261 switch (read_state) {
238262 case not_yet_done:
239263 break;
264 case expect_continue:
265 DBG_REQ(request, "pause read, write 100-continue");
266 ev_io_stop(mainloop, &request->ev_watcher);
267 request->current_chunk = _PEP3333_Bytes_FromString(CONTINUE);
268 start_writing(mainloop, request);
269 break;
240270 case done:
241271 DBG_REQ(request, "Stop read watcher, start write watcher");
242272 ev_io_stop(mainloop, &request->ev_watcher);
243 ev_io_init(&request->ev_watcher, &ev_io_on_write,
244 request->client_fd, EV_WRITE);
245 ev_io_start(mainloop, &request->ev_watcher);
273 start_writing(mainloop, request);
246274 break;
247275 case aborted:
248276 close_connection(mainloop, request);
282310 write_state = on_write_chunk(mainloop, request);
283311 }
284312
313 write_state = request->state.expect_continue
314 ? expect_continue
315 : write_state;
316
285317 switch(write_state) {
286318 case not_yet_done:
287319 break;
290322 DBG_REQ(request, "done, keep-alive");
291323 ev_io_stop(mainloop, &request->ev_watcher);
292324
293 if (!request->state.expect_continue) {
294 /* There will be more to receive/send after sending 100-Continue, so
295 * don't clobber the request until after the "real" response. */
296 Request_clean(request);
297 Request_reset(request);
298 }
299
300 request->state.expect_continue = false;
301
302 ev_io_init(&request->ev_watcher, &ev_io_on_read,
303 request->client_fd, EV_READ);
304 ev_io_start(mainloop, &request->ev_watcher);
325 Request_clean(request);
326 Request_reset(request);
327
328 start_reading(mainloop, request);
305329 } else {
306330 DBG_REQ(request, "done, close");
307331 close_connection(mainloop, request);
308332 }
333 break;
334 case expect_continue:
335 DBG_REQ(request, "expect continue");
336 ev_io_stop(mainloop, &request->ev_watcher);
337
338 request->state.expect_continue = false;
339 start_reading(mainloop, request);
309340 break;
310341 case aborted:
311342 /* Response was aborted due to an error. We can't do anything graceful here
00 from __future__ import print_function
1 import bjoern, socket, threading, time, json, sys, requests
2
3
4 responses = 0
1 import bjoern, socket, threading, time, json, sys, requests, logging
52
63
74 def app(e, s):
8 s('200 OK', [("Content-Length", "14")])
9 return b"hello response"
5 s('200 OK', [("Content-Length", "0")])
6 return b""
107
118
12 def make_request(host, port, reports, i):
13 global responses
9 def record_report(reports, request, resp):
10 reports.append({"request": request, "response": resp.decode("utf-8")})
1411
15 content_length = 1 << i
12
13 def expect_100_continue(test_num, i, request, resp, reports, client, content_length):
14 if resp == b"HTTP/1.1 100 Continue\r\n\r\n":
15 body = "".join("x" for x in range(0, content_length))
16
17 logging.debug("Request body for test {}, iteration {}: {}".format(test_num, i, body))
18
19 client.send(body.encode("utf-8"))
20
21 logging.info("Request body sent for test {}, iteration {}".format(test_num, i))
22
23 resp = client.recv(1 << 10)
24
25 logging.info("Response 2 for test, {}, iteration {}".format(test_num, i))
26 else:
27 record_report(reports, request, resp)
28
29
30 def expect_417_expectation_failed(test_num, i, request, resp, reports, *args):
31 if resp != b"HTTP/1.1 417 Expectation Failed\r\n\r\n":
32 record_report(reports, request, resp)
33
34
35 def expect_200_ok(test_num, i, request, resp, reports, *args):
36 if resp != b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: Keep-Alive\r\n\r\n":
37 record_report(reports, request, resp)
38
39
40 def make_request(host, port, reports, responses, test_num, i,
41 expectation="100-continue", assertion=expect_100_continue,
42 body=None):
43
44 content_length = 1 << i if not body else len(body)
1645 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1746
1847 client.connect((host, port))
1948 request = ("GET /{} HTTP/1.1\r\n"
20 "Accept: */*\r\n"
21 "Expect: 100-continue\r\n"
22 "Content-Length: {}\r\n\r\n").format(i, content_length)
49 "Expect: {}\r\n"
50 "Content-Length: {}\r\n\r\n").format(i, expectation, content_length)
2351
52 if body is not None:
53 request = "{}{}".format(request, body)
54
55 logging.info("Request for test {}, iteration {}: {}".format(test_num, i, request))
2456 client.send(request.encode("utf-8"))
25 print("Request for iteration {}".format(i))
57
58 logging.info("Request sent for test {}, iteration {}".format(test_num, i))
2659 resp = client.recv(1 << 10)
2760
28 if resp == b"HTTP/1.1 100 Continue\r\n\r\n":
29 client.send("".join("x" for x in range(0, content_length)).encode("utf-8"))
30 resp = client.recv(1 << 10)
31 reports.append({"request": request, "response": resp.decode("utf-8")})
61 logging.info("Response 1 for test {}, iteration {}".format(test_num, i))
62
63 assertion(test_num, i, request, resp, reports, client, content_length)
3264
3365 client.close()
3466
35 print("Response for iteration {}".format(i))
36 responses = responses + 1
67 logging.info("Response recv'ed for test {}, iteration {}".format(test_num, i))
68 responses.append(resp)
69
70
71 def usage():
72 print("usage: python {} $iterations $log_level".format(sys.argv[0]))
3773
3874
3975 if __name__ == "__main__":
76
77 if len(sys.argv) > 1 and sys.argv[1] in {"-h", "--help"}:
78 usage()
79 sys.exit(0)
80
81 iterations = int(sys.argv[1]) if len(sys.argv) > 1 else 1
82 level = getattr(logging, sys.argv[2].upper()) if len(sys.argv) > 2 else logging.WARNING
83
4084 host, port = "0.0.0.0", 8081
4185 sock = bjoern.listen(app, host, port, reuse_port=True)
4286
4589 t.start()
4690
4791 reports = []
48 iterations = int(sys.argv[1]) if len(sys.argv) > 1 else 1
92 responses = []
4993
50 for i in range(0, iterations):
51 t = threading.Thread(target=make_request, args=[host, port, reports, i + 1])
52 t.setDaemon(True)
53 t.start()
94 tests = [
95 {"expectation": "100-continue", "assertion": expect_100_continue},
96 {"expectation": "100-continue", "assertion": expect_100_continue, "body": ""},
97 {"expectation": "badness", "assertion": expect_417_expectation_failed},
98 {"expectation": "100-continue", "assertion": expect_200_ok, "body": "test"},
99 {"expectation": "badness", "assertion": expect_417_expectation_failed, "body": "test"}
100 ]
54101
55 while responses < iterations:
56 time.sleep(1)
102 logging.basicConfig(
103 level=level,
104 format='%(name)s - %(levelname)s - %(message)s'
105 )
57106
58 if len(reports) < iterations:
59 print(json.dumps(reports), file=sys.stderr)
107 for idx, test in enumerate(tests):
108 request_count = iterations * (idx + 1)
109
110 for i in range(0, iterations):
111 t = threading.Thread(
112 target=make_request,
113 args=[host, port, reports, responses, idx + 1, i + 1],
114 kwargs=test
115 )
116 t.start()
117
118 while len(responses) < request_count:
119 time.sleep(1)
120
121 if reports:
122 logging.error(json.dumps(reports, indent=4))
60123 sys.exit(1)
61124