Klaus Demo bjoern / 47e9ed3
Merge pull request #163 from tjb1982/master #162: naïve implementation of 100-continue Jonas Haag authored 3 months ago GitHub committed 3 months ago
8 changed file(s) with 245 addition(s) and 11 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 */
179179 if(!PARSER->invalid_header) {
180180 /* Set header, or append data to header if this is not the first call */
181181 _set_or_append_header(REQUEST->headers, PARSER->field, value, len);
182 }
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
183225 return 0;
184226 }
185227
187229 on_body(http_parser* parser, const char* data, const size_t len)
188230 {
189231 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;
190240
191241 body = PyDict_GetItem(REQUEST->headers, _wsgi_input);
192242 if (body == NULL) {
1515 unsigned keep_alive : 1;
1616 unsigned response_length_unknown : 1;
1717 unsigned chunked_response : 1;
18 unsigned expect_continue : 1;
1819 } request_state;
1920
2021 typedef struct {
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());
223245 request->current_chunk = _PEP3333_Bytes_FromString(
224246 http_error_messages[HTTP_SERVER_ERROR]);
225247 }
248 } else if (request->state.expect_continue) {
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;
226255 } else {
227256 /* Wait for more data */
228257 read_state = not_yet_done;
232261 switch (read_state) {
233262 case not_yet_done:
234263 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;
235270 case done:
236271 DBG_REQ(request, "Stop read watcher, start write watcher");
237272 ev_io_stop(mainloop, &request->ev_watcher);
238 ev_io_init(&request->ev_watcher, &ev_io_on_write,
239 request->client_fd, EV_WRITE);
240 ev_io_start(mainloop, &request->ev_watcher);
273 start_writing(mainloop, request);
241274 break;
242275 case aborted:
243276 close_connection(mainloop, request);
277310 write_state = on_write_chunk(mainloop, request);
278311 }
279312
313 write_state = request->state.expect_continue
314 ? expect_continue
315 : write_state;
316
280317 switch(write_state) {
281318 case not_yet_done:
282319 break;
284321 if(request->state.keep_alive) {
285322 DBG_REQ(request, "done, keep-alive");
286323 ev_io_stop(mainloop, &request->ev_watcher);
324
287325 Request_clean(request);
288326 Request_reset(request);
289 ev_io_init(&request->ev_watcher, &ev_io_on_read,
290 request->client_fd, EV_READ);
291 ev_io_start(mainloop, &request->ev_watcher);
327
328 start_reading(mainloop, request);
292329 } else {
293330 DBG_REQ(request, "done, close");
294331 close_connection(mainloop, request);
295332 }
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);
296340 break;
297341 case aborted:
298342 /* Response was aborted due to an error. We can't do anything graceful here
5555 listen_backlog=listen_backlog)
5656 _default_instance = (sock, wsgi_app)
5757
58 return sock
59
5860
5961 def run(*args, **kwargs):
6062 """
0 requests
0 from __future__ import print_function
1 import bjoern, socket, threading, time, json, sys, requests, logging
2
3
4 def app(e, s):
5 s('200 OK', [("Content-Length", "0")])
6 return b""
7
8
9 def record_report(reports, request, resp):
10 reports.append({"request": request, "response": resp.decode("utf-8")})
11
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)
45 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
46
47 client.connect((host, port))
48 request = ("GET /{} HTTP/1.1\r\n"
49 "Expect: {}\r\n"
50 "Content-Length: {}\r\n\r\n").format(i, expectation, content_length)
51
52 if body is not None:
53 request = "{}{}".format(request, body)
54
55 logging.info("Request for test {}, iteration {}: {}".format(test_num, i, request))
56 client.send(request.encode("utf-8"))
57
58 logging.info("Request sent for test {}, iteration {}".format(test_num, i))
59 resp = client.recv(1 << 10)
60
61 logging.info("Response 1 for test {}, iteration {}".format(test_num, i))
62
63 assertion(test_num, i, request, resp, reports, client, content_length)
64
65 client.close()
66
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]))
73
74
75 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
84 host, port = "0.0.0.0", 8081
85 sock = bjoern.listen(app, host, port, reuse_port=True)
86
87 t = threading.Thread(target=bjoern.server_run, args=[sock, app])
88 t.setDaemon(True)
89 t.start()
90
91 reports = []
92 responses = []
93
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 ]
101
102 logging.basicConfig(
103 level=level,
104 format='%(name)s - %(levelname)s - %(message)s'
105 )
106
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))
123 sys.exit(1)
124