Klaus Demo bjoern / 191ca9e
Extensive refactoring to fix an infinte loop in sendall(). Read on. :-) sendall() was a design mistake. My assumption was that all items in the ierable returned by the WSGI application are so small that they can be sent in one write() call. Thanks to Alexander Shigin for calling my attention to this problem and making up this example script: http://tinyurl.com/howtobreakbjoern I used this opportunity to do some other refactoring, for example getting rid of the `request_state` enum (still exists but the ugly manual flag operations like `|=` and `&` are gone). Another thing I did is merging the first iterable chunk and the headers into one piece before sending them to the client - so if your response body is tiny enough only one write() call is needed for the complete response to send (before it were two, one for the headers and one for the body), which can be a performance booster. I ran the usual tests against this commit (those in tests/ and the complete WSGITest suite) and I could not see anything breaking. If you find any bugs or have any other problems with these changes anyway, please tell me! Jonas Haag 9 years ago
11 changed file(s) with 243 addition(s) and 356 deletion(s). Raw diff Collapse all Expand all
5353
5454
5555 prepare-build:
56 mkdir -p $(BUILD_DIR)
56 @mkdir -p $(BUILD_DIR)
5757
5858 clean:
59 rm -f $(BUILD_DIR)/*
59 @rm -f $(BUILD_DIR)/*
6060
6161 ab:
6262 ab -c 100 -n 10000 'http://127.0.0.1:8080/a/b/c?k=v&k2=v2#fragment'
1010
1111 bjoern aims to be *small*, *lightweight* and *very fast*.
1212
13 * less than 800 SLOC (Source Lines Of Code)
13 * less than 1000 SLOC (Source Lines Of Code)
1414 * memory footprint smaller than a megabyte
1515 * no threads, coroutines or other crap
1616 * apparently the fastest WSGI server out there
2727 typedef PyObject* PyKeywordFunc(PyObject* self, PyObject* args, PyObject *kwargs);
2828
2929 typedef enum {
30 HTTP_BAD_REQUEST = 400,
31 HTTP_LENGTH_REQUIRED = 411,
32 HTTP_SERVER_ERROR = 500
30 HTTP_BAD_REQUEST, HTTP_LENGTH_REQUIRED, HTTP_SERVER_ERROR
3331 } http_status;
3432
3533 #ifdef DEBUG
1919 if(req == NULL)
2020 return NULL;
2121
22 memset(req, 0, sizeof(Request));
23
2224 #ifdef DEBUG
2325 static unsigned long req_id = 0;
2426 req->id = req_id++;
2527 #endif
2628
2729 req->client_fd = client_fd;
28 req->state = REQUEST_FRESH;
2930 http_parser_init((http_parser*)&req->parser, HTTP_REQUEST);
3031 req->parser.parser.data = req;
31
32 req->headers = NULL;
33 req->body = NULL;
34 req->response = NULL;
35 req->status = NULL;
3632
3733 return req;
3834 }
4844 return;
4945 }
5046
51 request->state = REQUEST_ERROR | HTTP_BAD_REQUEST;
47 request->state.error = true;
48 request->state.error_code = HTTP_BAD_REQUEST;
5249 }
5350
5451 void Request_free(Request* req)
5552 {
56 if(req->state & REQUEST_RESPONSE_WSGI) {
57 Py_XDECREF(req->response);
58 }
59
60 #if 0
61 else
62 free(req->response);
63 #endif
64
53 Py_XDECREF(req->iterable);
6554 Py_XDECREF(req->body);
6655 if(req->headers)
6756 assert(req->headers->ob_refcnt >= 1);
252241 const size_t body_len) {
253242 if(!REQUEST->body) {
254243 if(!parser->content_length) {
255 REQUEST->state = REQUEST_ERROR | HTTP_LENGTH_REQUIRED;
244 REQUEST->state.error = true;
245 REQUEST->state.error_code = HTTP_LENGTH_REQUIRED;
256246 return 1;
257247 }
258248 REQUEST->body = PycStringIO->NewOutput(parser->content_length);
259249 }
260250
261251 if(PycStringIO->cwrite(REQUEST->body, body_start, body_len) < 0) {
262 REQUEST->state = REQUEST_ERROR | HTTP_SERVER_ERROR;
252 REQUEST->state.error = true;
253 REQUEST->state.error_code = HTTP_SERVER_ERROR;
263254 return 1;
264255 }
265256
298289
299290 PyDict_Update(REQUEST->headers, wsgi_base_dict);
300291
301 REQUEST->state |= REQUEST_PARSE_DONE;
292 REQUEST->state.parse_finished = true;
302293 return 0;
303294 }
304295
66
77 void _request_module_initialize(const char* host, const int port);
88
9 typedef enum {
10 REQUEST_FRESH = 1<<10,
11 REQUEST_READING = 1<<11,
12 REQUEST_ERROR = 1<<12,
13 REQUEST_PARSE_DONE = 1<<13,
14 REQUEST_START_RESPONSE_CALLED = 1<<14,
15 REQUEST_RESPONSE_STATIC = 1<<15,
16 REQUEST_RESPONSE_HEADERS_SENT = 1<<16,
17 REQUEST_RESPONSE_WSGI = 1<<17,
18 REQUEST_WSGI_STRING_RESPONSE = 1<<18,
19 REQUEST_WSGI_FILE_RESPONSE = 1<<19,
20 REQUEST_WSGI_ITER_RESPONSE = 1<<20
9 typedef struct {
10 unsigned error_code : 2;
11 unsigned error : 1;
12 unsigned parse_finished : 1;
13 unsigned start_response_called : 1;
14 unsigned headers_sent : 1;
2115 } request_state;
2216
2317 typedef struct {
4034 PyObject* headers; /* rm. */
4135 PyObject* body;
4236
43 void* response;
37 PyObject* current_chunk;
38 Py_ssize_t current_chunk_p;
39 PyObject* iterable;
4440 PyObject* status;
45 PyObject* iterable_next;
4641 } Request;
4742
4843 Request* Request_new(int client_fd);
1212 #define LISTEN_BACKLOG 1024
1313 #define READ_BUFFER_SIZE 1024*8
1414
15 #define HANDLE_IO_ERROR(n, on_fatal_err) \
16 if(n == -1 || n == 0) { \
17 if(errno == EAGAIN) \
18 goto again; \
19 \
20 print_io_error(); \
21 on_fatal_err; \
22 }
23
24
2515 static int sockfd;
2616
2717 typedef void ev_io_callback(struct ev_loop*, ev_io*, const int);
3323 static ev_io_callback ev_io_on_read;
3424 static ev_io_callback ev_io_on_write;
3525 static inline void set_error(Request*, http_status);
36 static inline void print_io_error();
3726 static inline bool set_nonblocking(int fd);
27 static bool send_chunk(Request*);
3828
3929 void
4030 server_run(const char* hostaddr, const int port)
8676 ev_io_on_request(struct ev_loop* mainloop, ev_io* watcher, const int events)
8777 {
8878 int client_fd;
89 if((client_fd = accept(watcher->fd, NULL, NULL)) < 0)
90 return print_io_error();
91
92 if(!set_nonblocking(client_fd))
93 return print_io_error();
79 if((client_fd = accept(watcher->fd, NULL, NULL)) < 0) {
80 DBG("Could not accept() client: errno %d", errno);
81 return;
82 }
83
84 if(!set_nonblocking(client_fd)) {
85 DBG("Could not set_nonnblocking() client: errno %d", errno);
86 return;
87 }
9488
9589 Request* request = Request_new(client_fd);
9690
107101
108102 DBG_REQ(request, "read %zd bytes", read_bytes);
109103
110 request->state = REQUEST_READING;
111
112104 GIL_LOCK(0);
113105
114 HANDLE_IO_ERROR(read_bytes,
115 /* on fatal error */ set_error(request, HTTP_SERVER_ERROR); goto out
116 );
106 if(read_bytes == -1) {
107 if(errno != EAGAIN && errno != EWOULDBLOCK) {
108 close(request->client_fd);
109 Request_free(request);
110 ev_io_stop(mainloop, &request->ev_watcher);
111 }
112 goto out;
113 }
117114
118115 Request_parse(request, read_buf, read_bytes);
119116
120 if(request->state & REQUEST_ERROR) {
117 if(request->state.error) {
121118 DBG_REQ(request, "Parse error");
122 set_error(request, request->state ^ REQUEST_ERROR);
123 goto out;
124 }
125
126 if(request->state & REQUEST_PARSE_DONE) {
119 set_error(request, request->state.error_code);
120 }
121 else if(request->state.parse_finished) {
127122 DBG_REQ(request, "Parse done");
128123 if(!wsgi_call_application(request)) {
129124 assert(PyErr_Occurred());
132127 }
133128 } else {
134129 DBG_REQ(request, "Waiting for more data");
135 assert(request->state == REQUEST_READING);
136 goto again;
137 }
138
139 out:
130 goto out;
131 }
132
140133 ev_io_stop(mainloop, &request->ev_watcher);
141134 ev_io_init(&request->ev_watcher, &ev_io_on_write,
142135 request->client_fd, EV_WRITE);
143136 ev_io_start(mainloop, &request->ev_watcher);
144137
145 again:
138 out:
146139 GIL_UNLOCK(0);
147140 return;
148141 }
150143 static void
151144 ev_io_on_write(struct ev_loop* mainloop, ev_io* watcher, const int events)
152145 {
146 GIL_LOCK(0);
147
153148 Request* request = ADDR_FROM_MEMBER(watcher, Request, ev_watcher);
154
155 GIL_LOCK(0);
156
157 if(request->state & REQUEST_RESPONSE_WSGI) {
158 /* request->response is something that the WSGI application returned */
159 if(!wsgi_send_response(request))
160 goto out; /* come around again */
161 if(PyErr_Occurred()) {
162 /* Internal server error. */
163 PyErr_Print();
164 set_error(request, HTTP_SERVER_ERROR);
165 }
166 }
167
168 if(request->state & REQUEST_RESPONSE_STATIC) {
169 /* request->response is a C-string */
170 sendall(request, request->response, strlen(request->response));
171 }
172
149 assert(request->current_chunk);
150
151 if(send_chunk(request))
152 goto notfinished;
153
154 if(request->iterable) {
155 PyObject* next_chunk;
156 DBG_REQ(request, "Getting next iterable chunk.");
157 next_chunk = wsgi_iterable_get_next_chunk(request);
158 if(next_chunk == NULL) {
159 if(PyErr_Occurred()) {
160 /* Internal server error. */
161 PyErr_Print();
162 set_error(request, HTTP_SERVER_ERROR);
163 goto notfinished;
164 }
165 DBG_REQ(request, "Iterable exhausted");
166 goto bye;
167 }
168 request->current_chunk = next_chunk;
169 assert(request->current_chunk_p == 0);
170 goto notfinished;
171 }
172
173 bye:
173174 DBG_REQ(request, "Done");
174175
175176 /* Everything done, bye client! */
177178 close(request->client_fd);
178179 Request_free(request);
179180
180 out:
181 notfinished:
181182 GIL_UNLOCK(0);
182183 }
183184
184 bool
185 sendall(Request* request, const char* data, size_t length)
186 {
187 ssize_t sent;
188 again:
189 while(length) {
190 sent = write(request->client_fd, data, length);
191 HANDLE_IO_ERROR(sent, /* on fatal error */ return false);
192 data += sent;
193 length -= sent;
194 }
195 return true;
185 static bool
186 send_chunk(Request* request)
187 {
188 ssize_t sent_bytes;
189
190 assert(request->current_chunk != NULL);
191 assert(request->current_chunk_p != PyString_GET_SIZE(request->current_chunk));
192
193 DBG_REQ(request, "Sending next chunk");
194 sent_bytes = write(
195 request->client_fd,
196 PyString_AS_STRING(request->current_chunk) + request->current_chunk_p,
197 PyString_GET_SIZE(request->current_chunk) - request->current_chunk_p
198 );
199
200 if(sent_bytes == -1) {
201 if(errno == EAGAIN || errno == EWOULDBLOCK) {
202 /* Try again later */
203 return 1;
204 } else {
205 /* Serious transmission failure. Hang up. */
206 fprintf(stderr, "Client %d hit errno %d\n", request->client_fd, errno);
207 Py_DECREF(request->current_chunk);
208 Py_XDECREF(request->iterable);
209 request->iterable = NULL; /* to make ev_io_on_write jump right into 'bye' */
210 return 0;
211 }
212 }
213
214 DBG_REQ(request, "Sent %zd bytes from %p", sent_bytes, request->current_chunk);
215 request->current_chunk_p += sent_bytes;
216 if(request->current_chunk_p == PyString_GET_SIZE(request->current_chunk)) {
217 DBG_REQ(request, "Done with string %p", request->current_chunk);
218 Py_DECREF(request->current_chunk);
219 request->current_chunk = NULL;
220 request->current_chunk_p = 0;
221 return 0;
222 }
223 return 1;
196224 }
197225
198226 static inline void
199227 set_error(Request* request, http_status status)
200228 {
201 request->state |= REQUEST_RESPONSE_STATIC;
202 request->state &= ~REQUEST_RESPONSE_WSGI;
203 Py_XDECREF(request->response);
229 const char* msg;
204230 switch(status) {
205231 case HTTP_SERVER_ERROR:
206 request->response = "HTTP/1.0 500 Internal Server Error\r\n\r\n" \
207 "HTTP 500 Internal Server Error :(";
232 msg = "HTTP/1.0 500 Internal Server Error\r\n\r\n" \
233 "HTTP 500 Internal Server Error :(";
208234 break;
209
210235 case HTTP_BAD_REQUEST:
211 request->response = "HTTP/1.0 400 Bad Request\r\n\r\n" \
212 "You sent a malformed request.";
236 msg = "HTTP/1.0 400 Bad Request\r\n\r\n" \
237 "You sent a malformed request.";
213238 break;
214
215239 case HTTP_LENGTH_REQUIRED:
216 request->response = "HTTP/1.0 411 Length Required\r\n";
240 msg = "HTTP/1.0 411 Length Required\r\n";
217241 break;
218
219242 default:
220243 assert(0);
221244 }
222 }
223
224 static inline void
225 print_io_error()
226 {
227 printf("IO error %d\n", errno);
245 request->current_chunk = PyString_FromString(msg);
246
247 if(request->iterable != NULL) {
248 Py_DECREF(request->iterable);
249 request->iterable = NULL;
250 }
228251 }
229252
230253 static inline bool
11
22 bool server_init(const char* hostaddr, const int port);
33 void server_run();
4 bool sendall(Request*, const char*, size_t);
33 #include "wsgi.h"
44
55 static PyKeywordFunc start_response;
6 static inline bool _check_start_response(Request*);
7 static bool wsgi_sendheaders(Request*);
8 static inline bool wsgi_sendstring(Request*);
9 static bool wsgi_sendfile(Request*);
10 static bool wsgi_senditer(Request*);
6 static Py_ssize_t wsgi_getheaders(Request*, PyObject* buf);
117
128 typedef struct {
139 PyObject_HEAD
4339 if(retval == NULL)
4440 return false;
4541
46 request->state |= REQUEST_RESPONSE_WSGI;
47
48 /* Optimize the most common case: */
49 if(PyList_Check(retval) && PyList_GET_SIZE(retval) == 1) {
50 PyObject* list = retval;
51 retval = PyList_GET_ITEM(list, 0);
52 Py_INCREF(retval);
53 Py_DECREF(list);
54 goto string_resp; /* eeeeeevil */
55 }
56
57 #if 0
58 if(PyFile_Check(retval)) {
59 request->state |= REQUEST_WSGI_FILE_RESPONSE;
60 request->response = retval;
61 goto out;
62 }
63 #endif
64
65 if(PyString_Check(retval)) {
66 string_resp:
67 request->state |= REQUEST_WSGI_STRING_RESPONSE;
68 request->response = retval;
69 goto out;
70 }
71
72 PyObject* iter = PyObject_GetIter(retval);
73 Py_DECREF(retval);
74 TYPECHECK2(iter, PyIter, PySeqIter, "wsgi application return value", false);
75
76 request->state |= REQUEST_WSGI_ITER_RESPONSE;
77 request->response = iter;
78 /* Get the first item of the iterator, because that may execute code that
42 /* The following code is somewhat magic, so worth an explanation.
43 *
44 * If the application we called was a generator, we have to call .next() on
45 * it before we do anything else because that may execute code that
7946 * invokes `start_response` (which might not have been invoked yet).
8047 * Think of the following scenario:
8148 *
8754 * Unfortunately, `start_response` wouldn't be called until the first item
8855 * of that iterator is requested; `start_response` however has to be called
8956 * _before_ the wsgi body is sent, because it passes the HTTP headers.
57 *
58 * If the application returned a list this would not be required of course,
59 * but special-handling is painful - especially in C - so here's one generic
60 * way to solve the problem:
61 *
62 * Look into the returned iterator in any case. This allows us to do other
63 * optimizations, for example if the returned value is a list with exactly
64 * one string in it, we can pick the string and throw away the list so bjoern
65 * does not have to come back again and look into the iterator a second time.
9066 */
91 request->iterable_next = PyIter_Next(iter);
92 if(PyErr_Occurred())
93 return false;
94
95 out:
96 if(!request->headers) {
67 PyObject* first_chunk = NULL;
68
69 if(PyString_Check(retval)) {
70 /* According to PEP 333 strings should be handled like any other iterable,
71 i.e. sending the response item for item. "item for item" means
72 "char for char" if you have a string. -- I'm not that stupid. */
73 first_chunk = retval;
74 }
75 else if(PyList_Check(retval) && PyList_GET_SIZE(retval) == 1) {
76 /* Optimize the most common case, a single string in a list: */
77 first_chunk = PyList_GET_ITEM(retval, 0);
78 Py_INCREF(first_chunk);
79 Py_DECREF(retval);
80 } else {
81 /* Generic iterable (list of length != 1, generator, ...) */
82 PyObject* iter = PyObject_GetIter(retval);
83 Py_DECREF(retval);
84 TYPECHECK2(iter, PyIter, PySeqIter, "wsgi application return value", false);
85 request->iterable = iter;
86 first_chunk = wsgi_iterable_get_next_chunk(request);
87 if(first_chunk == NULL && PyErr_Occurred())
88 return false;
89 }
90
91 if(request->headers == NULL) {
92 /* It is important that this check comes *after* the call to
93 wsgi_iterable_get_next_chunk(), because in case the WSGI application
94 was an iterator, there's no chance start_response could be called
95 before. See above if you don't understand what I say. */
9796 PyErr_SetString(
9897 PyExc_TypeError,
9998 "wsgi application returned before start_response was called"
10099 );
100 Py_DECREF(first_chunk);
101101 return false;
102102 }
103
104 /* Get the headers and concatenate the first body chunk to them.
105 In the first place this makes the code more simple because afterwards
106 we can throw away the first chunk PyObject; but it also is an optimization:
107 At least for small responses, the complete response could be sent with
108 one send() call (in server.c:ev_io_on_write) which is a (tiny) performance
109 booster because less kernel calls means less kernel call overhead. */
110 PyObject* buf = PyString_FromStringAndSize(NULL, 1024);
111 Py_ssize_t length = wsgi_getheaders(request, buf);
112 if(length == 0) {
113 Py_DECREF(first_chunk);
114 Py_DECREF(buf);
115 return false;
116 }
117
118 if(first_chunk == NULL) {
119 /* The iterator returned by the application was empty. No body. */
120 _PyString_Resize(&buf, length);
121 goto out;
122 }
123
124 assert(first_chunk);
125 assert(buf);
126 _PyString_Resize(&buf, length + PyString_GET_SIZE(first_chunk));
127 memcpy(PyString_AS_STRING(buf)+length, PyString_AS_STRING(first_chunk),
128 PyString_GET_SIZE(first_chunk));
129
130 Py_DECREF(first_chunk);
131
132 out:
133 request->state.headers_sent = true;
134 request->current_chunk = buf;
135 request->current_chunk_p = 0;
103136 return true;
104137 }
105138
106 bool
107 wsgi_send_response(Request* request)
108 {
109 if(!(request->state & REQUEST_RESPONSE_HEADERS_SENT)) {
110 if(wsgi_sendheaders(request))
111 return true;
112 request->state |= REQUEST_RESPONSE_HEADERS_SENT;
113 }
114
115 request_state state = request->state;
116 if(state & REQUEST_WSGI_STRING_RESPONSE) return wsgi_sendstring(request);
117 else if(state & REQUEST_WSGI_FILE_RESPONSE) return wsgi_sendfile(request);
118 else if(state & REQUEST_WSGI_ITER_RESPONSE) return wsgi_senditer(request);
119
120 assert(0);
121 return 0; /* make gcc happy */
122 }
123
124 static bool
125 wsgi_sendheaders(Request* request)
126 {
127 char buf[1024*4];
128 size_t bufpos = 0;
139 static Py_ssize_t
140 wsgi_getheaders(Request* request, PyObject* buf)
141 {
142 register char* bufp = PyString_AS_STRING(buf);
143
129144 #define buf_write(src, len) \
130145 do { \
131146 size_t n = len; \
132147 const char* s = src; \
133 while(n--) buf[bufpos++] = *s++; \
148 while(n--) *bufp++ = *s++; \
134149 } while(0)
135150
136151 buf_write("HTTP/1.0 ", strlen("HTTP/1.0 "));
137152 buf_write(PyString_AS_STRING(request->status),
138153 PyString_GET_SIZE(request->status));
139154
140 size_t n_headers = PyList_GET_SIZE(request->headers);
141 for(size_t i=0; i<n_headers; ++i) {
142 PyObject* tuple = PyList_GET_ITEM(request->headers, i);
155 PyObject *tuple, *field, *value;
156 for(Py_ssize_t i=0; i<PyList_GET_SIZE(request->headers); ++i) {
157 tuple = PyList_GET_ITEM(request->headers, i);
143158 assert(tuple);
144 TYPECHECK(tuple, PyTuple, "headers", true);
145
146 if(PyTuple_GET_SIZE(tuple) < 2) {
159 TYPECHECK(tuple, PyTuple, "headers", 0);
160
161 if(PyTuple_GET_SIZE(tuple) != 2) {
147162 PyErr_Format(
148163 PyExc_TypeError,
149164 "headers must be tuples of length 2, not %zd",
150165 PyTuple_GET_SIZE(tuple)
151166 );
152 return true;
167 return 0;
153168 }
154 PyObject* field = PyTuple_GET_ITEM(tuple, 0);
155 PyObject* value = PyTuple_GET_ITEM(tuple, 1);
156 TYPECHECK(field, PyString, "header tuple items", true);
157 TYPECHECK(value, PyString, "header tuple items", true);
169
170 field = PyTuple_GET_ITEM(tuple, 0);
171 value = PyTuple_GET_ITEM(tuple, 1);
172
173 TYPECHECK(field, PyString, "header tuple items", 0);
174 TYPECHECK(value, PyString, "header tuple items", 0);
158175
159176 buf_write("\r\n", strlen("\r\n"));
160177 buf_write(PyString_AS_STRING(field), PyString_GET_SIZE(field));
163180 }
164181 buf_write("\r\n\r\n", strlen("\r\n\r\n"));
165182
166 return !sendall(request, buf, bufpos);
167 }
168
169 static inline bool
170 wsgi_sendstring(Request* request)
171 {
172 sendall(
173 request,
174 PyString_AS_STRING(request->response),
175 PyString_GET_SIZE(request->response)
176 );
177 return true;
178 }
179
180 static bool
181 wsgi_sendfile(Request* request)
182 {
183 assert(0);
184 return 0; /* make gcc happy */
185 }
186
187 static bool
188 wsgi_senditer(Request* request)
189 {
190 #define ITER_MAXSEND 1024*4
191 PyObject* item = request->iterable_next;
192 if(!item) return true;
193
194 ssize_t sent = 0;
195 while(item && sent < ITER_MAXSEND) {
196 TYPECHECK(item, PyString, "wsgi iterable items", true);
197 if(!sendall(request, PyString_AS_STRING(item),
198 PyString_GET_SIZE(item)))
199 return true;
200 sent += PyString_GET_SIZE(item);
201 Py_DECREF(item);
202 item = PyIter_Next(request->response);
203 if(PyErr_Occurred()) {
204 /* TODO: What to do here? Parts of the response are already sent */
205 return true;
206 }
207 }
208
209 if(item) {
210 request->iterable_next = item;
211 return false;
212 } else {
213 return true;
214 }
183 return bufp - PyString_AS_STRING(buf);
184 }
185
186 inline PyObject*
187 wsgi_iterable_get_next_chunk(Request* request)
188 {
189 PyObject* next = PyIter_Next(request->iterable);
190 if(next)
191 TYPECHECK(next, PyString, "wsgi iterable items", NULL);
192 return next;
215193 }
216194
217195 static inline void
234212 {
235213 Request* request = ((StartResponse*)self)->request;
236214
237 if(request->state & REQUEST_START_RESPONSE_CALLED) {
215 if(request->state.start_response_called) {
238216 /* not the first call of start_response --
239217 throw away any previous status and headers. */
240218 Py_DECREF(request->status);
263241
264242 restore_exception_tuple(exc_info, /* incref items? */ true);
265243
266 if(request->state & REQUEST_RESPONSE_HEADERS_SENT)
244 if(request->state.headers_sent) {
267245 /* Headers already sent. According to PEP 333, we should
268246 * let the exception propagate in this case. */
269247 return NULL;
270
271 /* Headers not yet sent; handle this start_response call if 'exc_info'
272 would not be passed, but print the exception and 'sys.exc_clear()' */
248 }
249
250 /* Headers not yet sent; handle this start_response call as if 'exc_info'
251 would not have been passed, but print and clear the exception. */
273252 PyErr_Print();
274253 }
275 else if(request->state & REQUEST_START_RESPONSE_CALLED) {
254 else if(request->state.start_response_called) {
276255 PyErr_SetString(PyExc_TypeError, "'start_response' called twice without "
277256 "passing 'exc_info' the second time");
278257 return NULL;
286265
287266 request->status = status;
288267 request->headers = headers;
289 request->state |= REQUEST_START_RESPONSE_CALLED;
268 request->state.start_response_called = true;
290269
291270 Py_RETURN_NONE;
292271 }
00 #include "request.h"
11
22 bool wsgi_call_application(Request*);
3 bool wsgi_send_response(Request*);
3 PyObject* wsgi_iterable_get_next_chunk(Request*);
44 PyTypeObject StartResponse_Type;
+0
-67
tests/slow_client.py less more
0 """The sample slow + fast client.
1
2 The client can work fine if server is localhost. Otherwise time_request
3 client usually takes the same time as long_request.
4 """
5
6 import sys
7 import socket
8 import time
9 import threading
10
11 if len(sys.argv) > 2:
12 print "usage: %s [host[:port]]" % sys.argv[0]
13 sys.exit(1)
14
15 hostname = 'localhost'
16 port = 8080
17 if len(sys.argv) == 2:
18 if ':' in sys.argv[1]:
19 hostname, port = sys.argv[1].split(':')
20 port = int(port)
21 else:
22 hostname = sys.argv[1]
23 headers = '\r\nHost: %s\r\n\r\n' % hostname
24
25 def timed(func):
26 def wrapper(*args, **kwargs):
27 start = time.time()
28 try:
29 return func(*args, **kwargs)
30 finally:
31 print '%s took %.2f sec' % (repr(func), time.time() - start)
32 return wrapper
33
34 lock = threading.Lock()
35 @timed
36 def long_request():
37 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
38 s.connect((hostname, port))
39 s.send('GET /huge HTTP/1.0' + headers)
40 for i in range(1):
41 s.recv(80)
42 time.sleep(1)
43 lock.release()
44 print 'release'
45 for i in range(10):
46 s.recv(80)
47 time.sleep(1)
48 s.close()
49
50 @timed
51 def time_request():
52 lock.acquire()
53 print 'acquire'
54 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
55 s.connect((hostname, port))
56 s.send('GET /tuple HTTP/1.0' + headers)
57 s.recv(900)
58 s.close()
59
60 lock.acquire()
61 fl = threading.Thread(target=long_request)
62 tl = threading.Thread(target=time_request)
63 fl.start()
64 tl.start()
65 tl.join()
66 fl.join()
+0
-31
tests/slow_server.py less more
0 #!/usr/bin/env python
1 # -*- coding: utf-8 -*-
2
3 from time import time
4 import bjoern
5
6 def start():
7 def return_tuple(environ, start_response):
8 start_response('200 OK', [('Content-Type','text/plain')])
9 print 'tuple'
10 return ('Hello,', " it's me, ", 'Bob!')
11
12 def return_huge_answer(environ, start_response):
13 start_response('200 OK', [('Content-Type','text/plain')])
14 return ['x'*(1024*1024)]
15
16 def return_404(environ, start_response):
17 start_response('404 Not Found', (('Content-Type','text/plain'), ))
18 return "URL %s not found" % environ.get('PATH_INFO', 'UNKNOWN')
19
20 dispatch = {
21 '/tuple': return_tuple,
22 '/huge': return_huge_answer,
23 }
24
25 def choose(environ, start_response):
26 return dispatch.get(environ.get('PATH_INFO'), return_404)(environ, start_response)
27 bjoern.run(choose, '0.0.0.0', 8080)
28
29 if __name__=="__main__":
30 start()