Klaus Demo bjoern / 29cb0d3
Fix #26 and #27 and some cleanup (back at 990 lines :-) Jonas Haag 9 years ago
5 changed file(s) with 88 addition(s) and 104 deletion(s). Raw diff Collapse all Expand all
11 TODO/BUGS
22 =================
33
4 * ssize_t -> Py_ssize_t
45 * There's a memory leak after ~ 20,000 requests (two `make ab` runs)
56 * wsgi_getheaders: get rid of buf_write (but memcpy is bad for that small strings)
7 * wsgi_iterable_get_next_chunk: refactor
68 * sendfile
79 * body -> tempfile for large requests
44 (c >= 'A' && c <= 'F') ? (c - 'A' + 10) : NOHEX)
55 #define NOHEX -1
66
7 size_t unquote_url_inplace(char* url, size_t len) {
7 size_t unquote_url_inplace(char* url, size_t len)
8 {
89 for(char *p=url, *end=url+len; url != end; ++url, ++p) {
910 if(*url == '%') {
1011 if(url >= end-2) {
2425 return len;
2526 }
2627
27 /* Case insensitive string comparison */
28 bool string_iequal(const char* a, const size_t len, const char* b)
28 void _initialize_static_strings()
2929 {
30 if(len != strlen(b))
31 return false;
32 for(size_t i=0; i<len; ++i)
33 if(a[i] != b[i] && a[i] - ('a'-'A') != b[i])
34 return false;
35 return true;
36 }
37
38 void _initialize_static_strings() {
3930 #define _(name) _##name = PyString_FromString(#name)
4031 _(REMOTE_ADDR); _(PATH_INFO); _(QUERY_STRING);
41 _(HTTP_FRAGMENT); _(REQUEST_METHOD); _(SERVER_PROTOCOL); _(GET);
42 _(close); _(0); _(Connection);
43 _Content_Length = PyString_FromString("Content-Length");
44 _Content_Type = PyString_FromString("Content-Type");
32 _(REQUEST_METHOD); _(SERVER_PROTOCOL); _(GET);
33 _(HTTP_CONTENT_LENGTH); _(CONTENT_LENGTH); _(HTTP_CONTENT_TYPE); _(CONTENT_TYPE);
4534 _HTTP_1_1 = PyString_FromString("HTTP/1.1");
4635 _HTTP_1_0 = PyString_FromString("HTTP/1.0");
4736 _wsgi_input = PyString_FromString("wsgi.input");
66 #include <stdbool.h>
77 #include <string.h>
88
9 enum http_status { HTTP_BAD_REQUEST = 1, HTTP_LENGTH_REQUIRED, HTTP_SERVER_ERROR };
10
11 size_t unquote_url_inplace(char* url, size_t len);
912 void _initialize_static_strings();
10 size_t unquote_url_inplace(char* url, size_t len);
11 bool string_iequal(const char* a, const size_t len, const char* b);
1213
13 typedef enum {
14 HTTP_BAD_REQUEST = 1, HTTP_LENGTH_REQUIRED, HTTP_SERVER_ERROR
15 } http_status;
16
17 PyObject *_REMOTE_ADDR, *_PATH_INFO, *_QUERY_STRING,
18 *_HTTP_FRAGMENT, *_REQUEST_METHOD, *_SERVER_PROTOCOL,
19 *_GET, *_HTTP_1_1, *_HTTP_1_0, *_Content_Length, *_Content_Type,
20 *_Connection, *_wsgi_input, *_close, *_0, *_empty_string;
14 PyObject *_REMOTE_ADDR, *_PATH_INFO, *_QUERY_STRING, *_REQUEST_METHOD, *_GET,
15 *_HTTP_CONTENT_LENGTH, *_CONTENT_LENGTH, *_HTTP_CONTENT_TYPE, *_CONTENT_TYPE,
16 *_SERVER_PROTOCOL, *_HTTP_1_1, *_HTTP_1_0, *_wsgi_input, *_empty_string;
2117
2218 #ifdef DEBUG
2319 #define DBG_REQ(request, ...) \
11 #include <cStringIO.h>
22 #include "request.h"
33
4 static inline void PyDict_ReplaceKey(PyObject* dict, PyObject* k1, PyObject* k2);
45 static PyObject* wsgi_http_header(Request*, const char*, const size_t);
56 static http_parser_settings parser_settings;
67 static PyObject* wsgi_base_dict = NULL;
126127 return 0;
127128 }
128129
129 static int on_fragment(http_parser* parser,
130 const char* fragm_start,
131 const size_t fragm_len) {
132 _set_header_free_value(
133 _HTTP_FRAGMENT,
134 PyString_FromStringAndSize(fragm_start, fragm_len)
135 );
136 return 0;
137 }
138
139130 static int on_header_field(http_parser* parser,
140131 const char* field_start,
141132 const size_t field_len) {
204195
205196 static int on_message_complete(http_parser* parser)
206197 {
198 /* HTTP_CONTENT_{LENGTH,TYPE} -> CONTENT_{LENGTH,TYPE} */
199 PyDict_ReplaceKey(REQUEST->headers, _HTTP_CONTENT_LENGTH, _CONTENT_LENGTH);
200 PyDict_ReplaceKey(REQUEST->headers, _HTTP_CONTENT_TYPE, _CONTENT_TYPE);
201
207202 /* SERVER_PROTOCOL (REQUEST_PROTOCOL) */
208203 _set_header(_SERVER_PROTOCOL, parser->http_minor == 1 ? _HTTP_1_1 : _HTTP_1_0);
204
209205 /* REQUEST_METHOD */
210206 if(parser->method == HTTP_GET) {
211207 /* I love useless micro-optimizations. */
214210 _set_header_free_value(_REQUEST_METHOD,
215211 PyString_FromString(http_method_str(parser->method)));
216212 }
213
217214 /* REMOTE_ADDR */
218215 _set_header(_REMOTE_ADDR, REQUEST->client_addr);
216
219217 /* wsgi.input */
220218 _set_header_free_value(_wsgi_input,
221219 PycStringIO->NewInput(
232230 static PyObject*
233231 wsgi_http_header(Request* request, const char* data, size_t len)
234232 {
235 /* Do not rename Content-Length and Content-Type */
236 if(string_iequal(data, len, "Content-Length")) {
237 Py_INCREF(_Content_Length);
238 return _Content_Length;
239 }
240 if(string_iequal(data, len, "Content-Type")) {
241 Py_INCREF(_Content_Type);
242 return _Content_Type;
243 }
244
245233 PyObject* obj = PyString_FromStringAndSize(/* empty string */ NULL,
246234 len+strlen("HTTP_"));
247235 char* dest = PyString_AS_STRING(obj);
264252 return obj;
265253 }
266254
255 static inline void
256 PyDict_ReplaceKey(PyObject* dict, PyObject* old_key, PyObject* new_key)
257 {
258 PyObject* value = PyDict_GetItem(dict, old_key);
259 if(value) {
260 Py_INCREF(value);
261 PyDict_DelItem(dict, old_key);
262 PyDict_SetItem(dict, new_key, value);
263 Py_DECREF(value);
264 }
265 }
266
267267
268268 static http_parser_settings
269269 parser_settings = {
271271 .on_path = on_path,
272272 .on_query_string = on_query_string,
273273 .on_url = NULL,
274 .on_fragment = on_fragment,
274 .on_fragment = NULL,
275275 .on_header_field = on_header_field,
276276 .on_header_value = on_header_value,
277277 .on_headers_complete = on_headers_complete,
11 #include "bjoernmodule.h"
22 #include "wsgi.h"
33
4 #define ASSERT_ISINSTANCE(what, type, ...) ASSERT_ISINSTANCE_IMPL(what, type, type, __VA_ARGS__);
5 #define ASSERT_ISINSTANCE_IMPL(what, check_type, print_type, errmsg_name, failure_retval) \
6 if(!what || !check_type##_Check(what)) { \
7 assert(Py_TYPE(what ? what : Py_None)->tp_name); \
8 PyErr_Format(\
9 PyExc_TypeError, \
10 errmsg_name " must be of type %s, not %s", \
11 print_type##_Type.tp_name, \
12 Py_TYPE(what ? what : Py_None)->tp_name \
13 ); \
14 return failure_retval; \
15 }
4 #define TYPE_ERROR_INNER(what, expected, ...) \
5 PyErr_Format(PyExc_TypeError, what " must be " expected " " __VA_ARGS__)
6 #define TYPE_ERROR(what, expected, got) \
7 TYPE_ERROR_INNER(what, expected, "(got '%.200s' object instead)", Py_TYPE(got)->tp_name)
168
179 static PyObject* (start_response)(PyObject* self, PyObject* args, PyObject *kwargs);
1810 static Py_ssize_t wsgi_getheaders(Request*, PyObject* buf);
2315 PyObject_HEAD
2416 Request* request;
2517 } StartResponse;
26
2718
2819 bool
2920 wsgi_call_application(Request* request)
10293 /* Generic iterable (list of length != 1, generator, ...) */
10394 PyObject* iter = PyObject_GetIter(retval);
10495 Py_DECREF(retval);
105 ASSERT_ISINSTANCE_IMPL(iter, PyIter, PySeqIter,
106 "wsgi application return value", false);
96 if(iter == NULL)
97 return false;
10798 request->iterable = iter;
10899 first_chunk = wsgi_iterable_get_next_chunk(request);
109100 if(first_chunk == NULL && PyErr_Occurred())
116107 * was an iterator, there's no chance start_response could be called
117108 * before. See above if you don't understand what I say. */
118109 PyErr_SetString(
119 PyExc_TypeError,
110 PyExc_RuntimeError,
120111 "wsgi application returned before start_response was called"
121112 );
122113 Py_DECREF(first_chunk);
123114 return false;
124115 }
125
126 if(!inspect_headers(request))
127 return NULL;
128116
129117 if(should_keep_alive(request)) {
130118 request->state.chunked_response = request->state.response_length_unknown;
176164 static inline bool
177165 inspect_headers(Request* request)
178166 {
179 for(Py_ssize_t i=0; i<PyList_GET_SIZE(request->headers); ++i) {
180 PyObject* tuple = PyList_GET_ITEM(request->headers, i);
181 assert(tuple);
182 ASSERT_ISINSTANCE(tuple, PyTuple, "headers", false);
183
184 if(PyTuple_GET_SIZE(tuple) != 2) {
185 PyErr_Format(
186 PyExc_TypeError,
187 "headers must be tuples of length 2, not %zd",
188 PyTuple_GET_SIZE(tuple)
189 );
190 return false;
191 }
167 Py_ssize_t i;
168 PyObject* tuple;
169
170 for(i=0; i<PyList_GET_SIZE(request->headers); ++i) {
171 tuple = PyList_GET_ITEM(request->headers, i);
172
173 if(!PyTuple_Check(tuple) || PyTuple_GET_SIZE(tuple) != 2)
174 goto err;
192175
193176 PyObject* field = PyTuple_GET_ITEM(tuple, 0);
194177 PyObject* value = PyTuple_GET_ITEM(tuple, 1);
195178
196 ASSERT_ISINSTANCE(field, PyString, "header tuple items", false);
197 ASSERT_ISINSTANCE(value, PyString, "header tuple items", false);
198
199 if(string_iequal(PyString_AS_STRING(field), PyString_GET_SIZE(field),
200 "Content-Length")) {
179 if(!PyString_Check(field) || !PyString_Check(value))
180 goto err;
181
182 if(!strncasecmp(PyString_AS_STRING(field), "Content-Length", PyString_GET_SIZE(field)))
201183 request->state.response_length_unknown = false;
202 }
203184 }
204185 return true;
186
187 err:
188 TYPE_ERROR_INNER("start_response argument 2", "a list of 2-tuples",
189 "(found invalid '%.200s' object at position %d)", Py_TYPE(tuple)->tp_name, i);
190 return false;
205191 }
206192
207193 static Py_ssize_t
246232 next = PyIter_Next(request->iterable);
247233 if(next == NULL)
248234 return NULL;
249 ASSERT_ISINSTANCE(next, PyString, "wsgi iterable items", NULL);
235 if(!PyString_Check(next)) {
236 TYPE_ERROR("wsgi iterable items", "strings", next);
237 Py_DECREF(next);
238 return NULL;
239 }
250240 if(PyString_GET_SIZE(next))
251241 return next;
242 Py_DECREF(next);
252243 }
253244 }
254245
275266 if(request->state.start_response_called) {
276267 /* not the first call of start_response --
277268 * throw away any previous status and headers. */
278 Py_DECREF(request->status);
279 Py_DECREF(request->headers);
280 request->status = NULL;
281 request->headers = NULL;
269 Py_CLEAR(request->status);
270 Py_CLEAR(request->headers);
271 request->state.response_length_unknown = false;
282272 }
283273
284274 PyObject* status = NULL;
288278 return NULL;
289279
290280 if(exc_info) {
291 ASSERT_ISINSTANCE(exc_info, PyTuple, "start_response argument 3", NULL);
292 if(PyTuple_GET_SIZE(exc_info) != 3) {
293 PyErr_Format(
294 PyExc_TypeError,
295 "start_response argument 3 must be a tuple of length 3, "
296 "not of length %zd",
297 PyTuple_GET_SIZE(exc_info)
298 );
281 if(!PyTuple_Check(exc_info) || PyTuple_GET_SIZE(exc_info) != 3) {
282 TYPE_ERROR("start_response argument 3", "a 3-tuple", exc_info);
299283 return NULL;
300284 }
301285
317301 return NULL;
318302 }
319303
320 ASSERT_ISINSTANCE(status, PyString, "start_response argument 1", NULL);
321 ASSERT_ISINSTANCE(headers, PyList, "start_response argument 2", NULL);
322
323 Py_INCREF(status);
324 Py_INCREF(headers);
304 if(!PyString_Check(status)) {
305 TYPE_ERROR("start_response argument 1", "a 'status reason' string", status);
306 return NULL;
307 }
308 if(!PyList_Check(headers)) {
309 TYPE_ERROR("start response argument 2", "a list of 2-tuples", headers);
310 return NULL;
311 }
312
313 request->headers = headers;
314
315 if(!inspect_headers(request)) {
316 request->headers = NULL;
317 return NULL;
318 }
325319
326320 request->status = status;
327 request->headers = headers;
321
322 Py_INCREF(request->status);
323 Py_INCREF(request->headers);
324
328325 request->state.start_response_called = true;
329326
330327 Py_RETURN_NONE;