Skip to content

Commit 494a9bf

Browse files
committed
Add wsgi_start_response to request for a small performance gain
1 parent 34f7dc7 commit 494a9bf

3 files changed

Lines changed: 97 additions & 28 deletions

File tree

‎lib/python/mod_python/wsgi.py‎

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,32 +22,6 @@
2222

2323
def handler(req):
2424

25-
# using a closure is faster than an object-based implementation
26-
def start_response(status, headers, exc_info=None):
27-
28-
if exc_info:
29-
try:
30-
raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
31-
finally:
32-
exc_info = None
33-
34-
# avoid str2int conversion for frequently used ones
35-
if status == '200':
36-
req.status = 200
37-
elif status == '301':
38-
req.status = 301
39-
elif status == '302':
40-
req.status = 302
41-
else:
42-
req.status = int(status[:3])
43-
44-
# There is no need to req.set_content_length() or set
45-
# req.content_type because it will be in the headers anyhow.
46-
for k,v in headers:
47-
req.headers_out.add(k,v)
48-
49-
return req.write
50-
5125
options = req.get_options()
5226

5327
## Find the application callable
@@ -94,7 +68,7 @@ def start_response(status, headers, exc_info=None):
9468

9569
response = None
9670
try:
97-
response = app(env, start_response)
71+
response = app(env, req.wsgi_start_response)
9872
[req.write(token) for token in response]
9973
finally:
10074
# call close() if there is one

‎src/include/requestobject.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ extern "C" {
5959

6060
PyAPI_FUNC(PyObject *) MpRequest_FromRequest (request_rec *r);
6161

62+
#ifndef ap_is_HTTP_VALID_RESPONSE
63+
#define ap_is_HTTP_VALID_RESPONSE(x) (((x) >= 100)&&((x) < 600))
64+
#endif
65+
6266
#ifdef __cplusplus
6367
}
6468
#endif

‎src/requestobject.c‎

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ static int set_wsgi_path_info(requestobject *self)
204204
}
205205

206206
/**
207-
** request.build_wsgi_env(reqeust self)
207+
** request.build_wsgi_env(request self)
208208
**
209209
* Build a WSGI environment dictionary.
210210
*
@@ -289,6 +289,96 @@ static PyObject *req_build_wsgi_env(requestobject *self)
289289
return env;
290290
}
291291

292+
/**
293+
** request.wsgi_start_response(self, args)
294+
**
295+
* The WSGI start_response()
296+
*
297+
*/
298+
static PyObject *req_wsgi_start_response(requestobject *self, PyObject *args)
299+
{
300+
301+
char *status_line = NULL;
302+
PyObject *headers = NULL;
303+
PyObject *exc_info = NULL;
304+
int status, i;
305+
306+
if (! PyArg_ParseTuple(args, "sO|O:wsgi_start_response", &status_line, &headers, &exc_info))
307+
return NULL;
308+
309+
if (!PyList_Check(headers)) {
310+
PyErr_Format(PyExc_TypeError, "headers argument must be a list, not a '%.200s'",
311+
headers->ob_type->tp_name);
312+
return NULL;
313+
}
314+
315+
/* I don't understand what PEP3333 wants us to do with the
316+
* exception, we just re-raise it like other WSGI tools do */
317+
if (exc_info) {
318+
PyObject *exc, *value, *tb;
319+
if (PyArg_UnpackTuple(exc_info, "wsgi_start_response", 3, 3, &exc, &value, &tb)) {
320+
Py_INCREF(exc);
321+
Py_INCREF(value);
322+
Py_INCREF(tb);
323+
PyErr_Restore(exc, value, tb);
324+
}
325+
return NULL;
326+
}
327+
328+
/* add the headers */
329+
for (i=0; i < PyList_Size(headers); i++) {
330+
PyObject *key = NULL, *val = NULL;
331+
char *k, *v;
332+
PyObject *item = PyList_GetItem(headers, i);
333+
334+
if (!PyTuple_CheckExact(item)) {
335+
PyErr_Format(PyExc_TypeError, "each header must be a tuple, not a '%.200s'",
336+
item->ob_type->tp_name);
337+
return NULL;
338+
}
339+
340+
if (! PyArg_ParseTuple(item, "OO", &key, &val))
341+
return NULL;
342+
343+
if (!((PyUnicode_CheckExact(key) || PyBytes_CheckExact(key)))) {
344+
PyErr_Format(PyExc_TypeError, "header names must be strings, not '%.200s'",
345+
key->ob_type->tp_name);
346+
return NULL;
347+
}
348+
349+
if (!((PyUnicode_CheckExact(val) || PyBytes_CheckExact(val)))) {
350+
PyErr_Format(PyExc_TypeError, "header values must be strings, not '%.200s'",
351+
val->ob_type->tp_name);
352+
return NULL;
353+
}
354+
355+
MP_ANYSTR_AS_STR(k, key, 1);
356+
MP_ANYSTR_AS_STR(v, val, 1);
357+
if ((!k) || (!v)) {
358+
Py_DECREF(key); /* MP_ANYSTR_AS_STR */
359+
Py_DECREF(val); /* MP_ANYSTR_AS_STR */
360+
return NULL;
361+
}
362+
363+
apr_table_add(self->request_rec->headers_out, k, v);
364+
365+
Py_DECREF(key); /* MP_ANYSTR_AS_STR */
366+
Py_DECREF(val); /* MP_ANYSTR_AS_STR */
367+
}
368+
369+
status = atoi(status_line);
370+
if (!ap_is_HTTP_VALID_RESPONSE(status)) {
371+
PyErr_SetString(PyExc_ValueError,
372+
apr_psprintf(self->request_rec->pool,
373+
"Invalid status line: %s", status_line));
374+
return NULL;
375+
}
376+
377+
self->request_rec->status_line = apr_pstrdup(self->request_rec->pool, status_line);
378+
379+
return PyObject_GetAttrString((PyObject*)self, "write");
380+
}
381+
292382
/**
293383
** valid_phase()
294384
**
@@ -1591,6 +1681,7 @@ static PyMethodDef request_methods[] = {
15911681
{"write", (PyCFunction) req_write, METH_VARARGS},
15921682
{"get_config", (PyCFunction) req_get_config, METH_NOARGS},
15931683
{"build_wsgi_env", (PyCFunction) req_build_wsgi_env, METH_NOARGS},
1684+
{"wsgi_start_response", (PyCFunction) req_wsgi_start_response, METH_VARARGS},
15941685
{"add_cgi_vars", (PyCFunction) req_add_cgi_vars, METH_NOARGS},
15951686
{"add_common_vars", (PyCFunction) req_add_common_vars, METH_NOARGS},
15961687
{"add_handler", (PyCFunction) req_add_handler, METH_VARARGS},

0 commit comments

Comments
 (0)