Hi mod_python-dev,
I have created a patch for mod_python that fixes up the following issues:
MODPYTHON-212
req.read() with no arguments doesn't return all data where input
filter inserts extra data in input stream
MODPYTHON-222
Support for chunked transfer encoding on request content
It does this by:
(1) Making read() and readline() ignore the content length in the case
where no size argument is specified, and instead call
ap_get_client_block() in a loop until it returns zero. The result buffer
is dynamically expanded as necessary with _PyString_Resize().
(2) Adding a request.set_read_policy() call to specify whether
REQUEST_NO_BODY, REQUEST_CHUNKED_ERROR or REQUEST_CHUNKED_DECHUNK is
passed to ap_setup_client_block() on the first read()/readline() call.
The default is REQUEST_CHUNKED_DECHUNK, but could be set to
REQUEST_CHUNKED_ERROR for compatibility with the current behaviour.
This patch is against the mod_python-3.3.1 release. Please find it
attached as "modpython-212-222-fix.patch". I have tested it manually and
run test.py on it as well.
--- Possible Objections ---
Aside from content-length-related objections that are obviously
addressed by the above, I also noted the following objection to enabling
the use of REQUEST_CHUNKED_DECHUNK on the mailing list:
Graham Dumpleton, 2007-02-23,
http://www.webservertalk.com/archive309-2007-2-1820928.html
> [...] when using REQUEST_CHUNKED_DECHUNK it is important
> that the read buffer be large enough to handle any chunk being read. Ie.,
> from Apache source code:
>
> [...]
> * In order to use the last two options, the caller MUST provide a buffer
> * large enough to hold a chunk-size line, including any extensions.
>
> If chunked is used the mod_python source can't know what the buffer size
> it would need to use is as that is going to be dependent on the
higher level
> application and what it is doing.
If you look carefully here, for example,
http://mail-archives.apache.org/mod_mbox/httpd-cvs/199611.mbox/
[EMAIL PROTECTED]
you can see old get_client_block() code that reads the chunk header line
into the client-provided buffer before applying chunk decoding, hence
the requirement that "the caller MUST provide a buffer large enough to
hold a chunk-size line, including any extensions". However, the current
ap_get_client_block() has completely replaced this with some APR "bucket
brigade" magic. It seems to me that they just forgot to remove this comment.
--- Substitute Patch ---
If this patch is not satisfactory, I would suggest at least the changes
in the attached patch "requestobject.c.fix1.patch". This corrects the
following problems in req_read():
(1) The first ap_get_client_block() call will currently overwrite any
data retrieved by the preceding "If anything left in the readline
buffer" code.
(2) The return value of the first call to ap_get_client_block() is not
checked for errors (though it does look like it will try again in the
loop below, where error checking is in fact done).
An additional problem NOT corrected by "requestobject.c.fix1.patch" (but
corrected by the main patch):
(3) It seems that req_readline() allocates a buffer the size of the
whole response just to return one line if the size argument is unspecified.
--- Mailing List Problems ---
By the way, it looks like the mod_python web page needs to be updated
regarding the address of the -dev email list. I had to go through the
following redirects to actually find the list:
"<[EMAIL PROTECTED]>: This mailing list has moved to
python-dev at quetz.apache.org."
"<[EMAIL PROTECTED]>: This mailing list has moved to
mod_python-dev at quetz.apache.org."
Regards,
Alex
--- requestobject.c.orig 2006-12-03 15:06:37.000000000 +1030
+++ requestobject.c.fix1 2008-07-10 22:38:09.015625000 +0930
@@ -847,10 +847,11 @@
static PyObject * req_read(requestobject *self, PyObject *args)
{
- int rc, bytes_read, chunk_len;
+ int rc;
+ long bytes_read, chunk_len;
char *buffer;
PyObject *result;
- int copied = 0;
+ long copied = 0;
long len = -1;
if (! PyArg_ParseTuple(args, "|l", &len))
@@ -907,13 +908,9 @@
if (copied == len)
return result; /* we're done! */
- /* read it in */
- Py_BEGIN_ALLOW_THREADS
- chunk_len = ap_get_client_block(self->request_rec, buffer, len);
- Py_END_ALLOW_THREADS
- bytes_read = chunk_len;
-
- /* if this is a "short read", try reading more */
+ /* read it in, looping for short reads */
+ bytes_read = copied;
+ chunk_len = 1;
while ((bytes_read < len) && (chunk_len != 0)) {
Py_BEGIN_ALLOW_THREADS
chunk_len = ap_get_client_block(self->request_rec,
diff -ur mod_python-3.3.1/lib/python/mod_python/apache.py
mod_python-3.3.1.p/lib/python/mod_python/apache.py
--- mod_python-3.3.1/lib/python/mod_python/apache.py 2006-10-27
10:24:12.000000000 +0930
+++ mod_python-3.3.1.p/lib/python/mod_python/apache.py 2008-07-10
22:54:19.000000000 +0930
@@ -1153,10 +1153,10 @@
INCLUDES_MAGIC_TYPE3 = "text/x-server-parsed-html3"
DIR_MAGIC_TYPE = "httpd/unix-directory"
-# for req.read_body
-REQUEST_NO_BODY = 0
-REQUEST_CHUNKED_ERROR = 1
-REQUEST_CHUNKED_DECHUNK = 2
+# for req.read_body and req.set_read_policy()
+REQUEST_NO_BODY = _apache.REQUEST_NO_BODY
+REQUEST_CHUNKED_ERROR = _apache.REQUEST_CHUNKED_ERROR
+REQUEST_CHUNKED_DECHUNK = _apache.REQUEST_CHUNKED_DECHUNK
# for req.connection.keepalive
AP_CONN_UNKNOWN = _apache.AP_CONN_UNKNOWN
diff -ur mod_python-3.3.1/src/_apachemodule.c
mod_python-3.3.1.p/src/_apachemodule.c
--- mod_python-3.3.1/src/_apachemodule.c 2006-11-23 10:38:36.000000000
+1030
+++ mod_python-3.3.1.p/src/_apachemodule.c 2008-07-10 22:43:43.000000000
+0930
@@ -780,6 +780,16 @@
PyDict_SetItemString(d, "table", (PyObject *)&MpTable_Type);
+ o = PyInt_FromLong(REQUEST_NO_BODY);
+ PyDict_SetItemString(d, "REQUEST_NO_BODY", o);
+ Py_DECREF(o);
+ o = PyInt_FromLong(REQUEST_CHUNKED_ERROR);
+ PyDict_SetItemString(d, "REQUEST_CHUNKED_ERROR", o);
+ Py_DECREF(o);
+ o = PyInt_FromLong(REQUEST_CHUNKED_DECHUNK);
+ PyDict_SetItemString(d, "REQUEST_CHUNKED_DECHUNK", o);
+ Py_DECREF(o);
+
o = PyInt_FromLong(AP_CONN_UNKNOWN);
PyDict_SetItemString(d, "AP_CONN_UNKNOWN", o);
Py_DECREF(o);
diff -ur mod_python-3.3.1/src/include/mod_python.h
mod_python-3.3.1.p/src/include/mod_python.h
--- mod_python-3.3.1/src/include/mod_python.h 2006-10-24 13:11:54.000000000
+0930
+++ mod_python-3.3.1.p/src/include/mod_python.h 2008-07-11 00:28:59.000000000
+0930
@@ -21,7 +21,7 @@
*
* mod_python.h
*
- * $Id: mod_python.h 467228 2006-10-24 03:41:54Z grahamd $
+ * $Id: mod_python.h 231054 2005-08-09 15:37:04Z jgallacher $
*
* See accompanying documentation and source code comments
* for details.
diff -ur mod_python-3.3.1/src/include/requestobject.h
mod_python-3.3.1.p/src/include/requestobject.h
--- mod_python-3.3.1/src/include/requestobject.h 2006-10-08
19:28:35.000000000 +0930
+++ mod_python-3.3.1.p/src/include/requestobject.h 2008-07-10
22:56:38.000000000 +0930
@@ -49,6 +49,7 @@
int rbuff_len; /* read buffer size */
int rbuff_pos; /* position into the buffer */
PyObject * session;
+ int read_policy;
} requestobject;
diff -ur mod_python-3.3.1/src/requestobject.c
mod_python-3.3.1.p/src/requestobject.c
--- mod_python-3.3.1/src/requestobject.c 2006-12-03 15:06:37.000000000
+1030
+++ mod_python-3.3.1.p/src/requestobject.c 2008-07-11 01:01:40.000000000
+0930
@@ -24,6 +24,8 @@
#include "mod_python.h"
+#define ALLOCATED_RBUFF_SIZE HUGE_STRING_LEN
+
/* mod_ssl.h is not safe for inclusion in 2.0, so duplicate the
* optional function declarations. */
APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup,
@@ -76,6 +78,7 @@
result->rbuff = NULL;
result->rbuff_pos = 0;
result->rbuff_len = 0;
+ result->read_policy = REQUEST_CHUNKED_DECHUNK;
/* we make sure that the object dictionary is there
* before registering the object with the GC
@@ -837,83 +840,83 @@
return PyInt_FromLong(ap_meets_conditions(self->request_rec));
}
-
/**
- ** request.read(request self, int bytes)
+ ** request.set_read_policy(request self, int read_policy)
**
- * Reads stuff like POST requests from the client
- * (based on the old net_read)
+ * Set the read_policy value to pass to ap_setup_client_block() on the
+ * first read()/readline().
+ *
+ * Valid values include apache.REQUEST_NO_BODY,
+ * apache.REQUEST_CHUNKED_ERROR, and apache.REQUEST_CHUNKED_DECHUNK.
+ *
+ * The default value is apache.REQUEST_CHUNKED_DECHUNK.
*/
-static PyObject * req_read(requestobject *self, PyObject *args)
+static PyObject * req_set_read_policy(requestobject *self, PyObject *args)
{
- int rc, bytes_read, chunk_len;
- char *buffer;
- PyObject *result;
- int copied = 0;
- long len = -1;
-
- if (! PyArg_ParseTuple(args, "|l", &len))
+ if (! PyArg_ParseTuple(args, "i", &self->read_policy))
return NULL;
- if (len == 0) {
- return PyString_FromString("");
- }
+ Py_INCREF(Py_None);
+ return Py_None;
+}
- /* is this the first read? */
- if (! self->request_rec->read_length) {
+/*
+ * called by req_read() and req_readline().
+ * returns false if result should be returned or true if the caller can
continue.
+ */
+static int req_read_setup(requestobject *self, PyObject **result)
+{
+ int rc;
- /* then do some initial setting up */
- rc = ap_setup_client_block(self->request_rec, REQUEST_CHUNKED_ERROR);
- if(rc != OK) {
- PyObject *val = PyInt_FromLong(rc);
- if (val == NULL)
- return NULL;
- PyErr_SetObject(get_ServerReturn(), val);
- Py_DECREF(val);
- return NULL;
- }
+ /* do initial setting up */
+ rc = ap_setup_client_block(self->request_rec, self->read_policy);
- if (! ap_should_client_block(self->request_rec)) {
- /* client has nothing to send */
- return PyString_FromString("");
- }
+ if (rc != OK) {
+ *result = NULL;
+ PyObject *val = PyInt_FromLong(rc);
+ if (val == NULL)
+ return 0;
+ PyErr_SetObject(get_ServerReturn(), val);
+ Py_DECREF(val);
+ return 0;
}
- if (len < 0)
- /* XXX ok to use request_rec->remaining? */
- len = self->request_rec->remaining +
- (self->rbuff_len - self->rbuff_pos);
+ if (! ap_should_client_block(self->request_rec)) {
+ /* client has nothing to send */
+ *result = PyString_FromString("");
+ return 0;
+ }
+
+ return 1;
+}
+
+/* called by req_read() to implement the len >= 0 case */
+static PyObject * req_read_some(requestobject *self, long len)
+{
+ long bytes_read = 0;
+ long chunk_len;
+ char *buffer;
+ PyObject *result;
/* PYTHON 2.5: 'PyString_FromStringAndSize' uses Py_ssize_t for input
parameters */
result = PyString_FromStringAndSize(NULL, len);
-
- /* possibly no more memory */
- if (result == NULL)
+ if (result == NULL) /* possibly no more memory */
return NULL;
-
buffer = PyString_AS_STRING((PyStringObject *) result);
/* if anything left in the readline buffer */
- while ((self->rbuff_pos < self->rbuff_len) && (copied < len))
- buffer[copied++] = self->rbuff[self->rbuff_pos++];
+ while ((self->rbuff_pos < self->rbuff_len) && (bytes_read < len))
+ buffer[bytes_read++] = self->rbuff[self->rbuff_pos++];
- /* Free rbuff if we're done with it */
+ /* free rbuff if we're done with it */
if (self->rbuff_pos >= self->rbuff_len && self->rbuff != NULL) {
free(self->rbuff);
self->rbuff = NULL;
}
- if (copied == len)
- return result; /* we're done! */
-
- /* read it in */
- Py_BEGIN_ALLOW_THREADS
- chunk_len = ap_get_client_block(self->request_rec, buffer, len);
- Py_END_ALLOW_THREADS
- bytes_read = chunk_len;
-
- /* if this is a "short read", try reading more */
+ /* read it in, looping for short reads */
+ chunk_len = 1;
while ((bytes_read < len) && (chunk_len != 0)) {
Py_BEGIN_ALLOW_THREADS
chunk_len = ap_get_client_block(self->request_rec,
@@ -937,20 +940,84 @@
return result;
}
+/* called by req_read() to implement the len < 0 case */
+static PyObject * req_read_all(requestobject *self)
+{
+ long bytes_read = 0;
+ long chunk_len;
+ char *buffer;
+ long buffer_size = 4096;
+ PyObject *result;
+
+ /* PYTHON 2.5: 'PyString_FromStringAndSize' uses Py_ssize_t for input
parameters */
+ result = PyString_FromStringAndSize(NULL, buffer_size);
+ if (result == NULL) /* possibly no more memory */
+ return NULL;
+ buffer = PyString_AS_STRING((PyStringObject *) result);
+
+ /* if anything left in the readline buffer */
+ while (self->rbuff_pos < self->rbuff_len) {
+
+ /* resize buffer if full */
+ if (bytes_read == buffer_size) {
+ buffer_size *= 2;
+ if (_PyString_Resize(&result, buffer_size))
+ return NULL;
+ buffer = PyString_AS_STRING((PyStringObject *) result);
+ }
+
+ /* copy byte */
+ buffer[bytes_read++] = self->rbuff[self->rbuff_pos++];
+ }
+
+ /* free rbuff if we're done with it */
+ if (self->rbuff_pos >= self->rbuff_len && self->rbuff != NULL) {
+ free(self->rbuff);
+ self->rbuff = NULL;
+ }
+
+ /* read it in, looping for short reads */
+ do {
+ /* resize buffer if full */
+ if (bytes_read == buffer_size) {
+ buffer_size *= 2;
+ if (_PyString_Resize(&result, buffer_size))
+ return NULL;
+ buffer = PyString_AS_STRING((PyStringObject *) result);
+ }
+
+ Py_BEGIN_ALLOW_THREADS
+ chunk_len = ap_get_client_block(self->request_rec,
+ buffer+bytes_read,
buffer_size-bytes_read);
+ Py_END_ALLOW_THREADS
+ if (chunk_len == -1) {
+ PyErr_SetObject(PyExc_IOError,
+ PyString_FromString("Client read error
(Timeout?)"));
+ return NULL;
+ }
+ else
+ bytes_read += chunk_len;
+ }
+ while (chunk_len != 0);
+
+ /* resize buffer to actual data size */
+ /* PYTHON 2.5: '_PyString_Resize' uses Py_ssize_t for input parameters */
+ if (_PyString_Resize(&result, bytes_read))
+ return NULL;
+
+ return result;
+}
+
/**
- ** request.readline(request self, int maxbytes)
+ ** request.read(request self, int bytes)
**
* Reads stuff like POST requests from the client
- * (based on the old net_read) until EOL
+ * (based on the old net_read)
*/
-static PyObject * req_readline(requestobject *self, PyObject *args)
+static PyObject * req_read(requestobject *self, PyObject *args)
{
-
- int rc, chunk_len, bytes_read;
- char *buffer;
PyObject *result;
- int copied = 0;
long len = -1;
if (! PyArg_ParseTuple(args, "|l", &len))
@@ -960,122 +1027,113 @@
return PyString_FromString("");
}
- /* is this the first read? */
+ /* do first-read setup if necessary */
if (! self->request_rec->read_length) {
+ if (! req_read_setup(self, &result))
+ return result;
+ }
- /* then do some initial setting up */
- rc = ap_setup_client_block(self->request_rec, REQUEST_CHUNKED_ERROR);
+ if (len < 0)
+ return req_read_all(self);
+ else
+ return req_read_some(self, len);
+}
- if (rc != OK) {
- PyObject *val = PyInt_FromLong(rc);
- if (val == NULL)
- return NULL;
- PyErr_SetObject(get_ServerReturn(), val);
- Py_DECREF(val);
- return NULL;
- }
+/**
+ ** request.readline(request self, int maxbytes)
+ **
+ * Reads stuff like POST requests from the client
+ * (based on the old net_read) until EOL
+ */
- if (! ap_should_client_block(self->request_rec)) {
- /* client has nothing to send */
- return PyString_FromString("");
- }
+static PyObject * req_readline(requestobject *self, PyObject *args)
+{
+ PyObject *result;
+ char *buffer;
+ long buffer_size;
+ long copied = 0;
+ long len = -1;
+
+ if (! PyArg_ParseTuple(args, "|l", &len))
+ return NULL;
+
+ if (len == 0) {
+ return PyString_FromString("");
}
- if (len < 0)
- len = self->request_rec->remaining +
- (self->rbuff_len - self->rbuff_pos);
+ /* do first-read setup if necessary */
+ if (! self->request_rec->read_length) {
+ if (! req_read_setup(self, &result))
+ return result;
+ }
/* create the result buffer */
/* PYTHON 2.5: 'PyString_FromStringAndSize' uses Py_ssize_t for input
parameters */
- result = PyString_FromStringAndSize(NULL, len);
-
- /* possibly no more memory */
- if (result == NULL)
+ buffer_size = (len >= 0? len : 4096);
+ result = PyString_FromStringAndSize(NULL, buffer_size);
+ if (result == NULL) /* possibly no more memory */
return NULL;
-
buffer = PyString_AS_STRING((PyStringObject *) result);
- /* is there anything left in the rbuff from previous reads? */
- if (self->rbuff_pos < self->rbuff_len) {
-
- /* if yes, process that first */
- while (self->rbuff_pos < self->rbuff_len) {
+ /* process anything left in the rbuff from previous reads */
+ while (self->rbuff_pos < self->rbuff_len) {
- buffer[copied++] = self->rbuff[self->rbuff_pos];
- if ((self->rbuff[self->rbuff_pos++] == '\n') ||
- (copied == len)) {
+ /* resize result buffer if full */
+ if (copied == buffer_size) {
+ buffer_size *= 2;
+ if (_PyString_Resize(&result, buffer_size))
+ return NULL;
+ buffer = PyString_AS_STRING((PyStringObject *) result);
+ }
- /* our work is done */
+ /* copy byte */
+ buffer[copied++] = self->rbuff[self->rbuff_pos];
+ if ((self->rbuff[self->rbuff_pos++] == '\n') ||
+ (copied == len)) { /* note len may be < 0 */
- /* resize if necessary */
- if (copied < len)
- /* PYTHON 2.5: '_PyString_Resize' uses Py_ssize_t for
input parameters */
- if(_PyString_Resize(&result, copied))
- return NULL;
-
- /* fix for MODPYTHON-181 leak */
- if (self->rbuff_pos >= self->rbuff_len && self->rbuff != NULL)
{
- free(self->rbuff);
- self->rbuff = NULL;
- }
+ /* our work is done */
- return result;
- }
- }
- }
+ /* resize result buffer if necessary */
+ if (copied < buffer_size)
+ /* PYTHON 2.5: '_PyString_Resize' uses Py_ssize_t for input
parameters */
+ if(_PyString_Resize(&result, copied))
+ return NULL;
- /* Free old rbuff as the old contents have been copied over and
- we are about to allocate a new rbuff. Perhaps this could be reused
- somehow? */
- if (self->rbuff_pos >= self->rbuff_len && self->rbuff != NULL)
- {
- free(self->rbuff);
- self->rbuff = NULL;
+ /* fix for MODPYTHON-181 leak */
+ if (self->rbuff_pos >= self->rbuff_len && self->rbuff != NULL) {
+ free(self->rbuff);
+ self->rbuff = NULL;
+ }
+
+ return result;
+ }
}
- /* if got this far, the buffer should be empty, we need to read more */
+ /* if got this far, the rbuff should be empty, we need to read more */
- /* create a read buffer
+ /* create a read buffer if not already present
- The buffer len will be at least HUGE_STRING_LEN in size,
+ The buffer len will always be exactly HUGE_STRING_LEN in size,
to avoid memory fragmention
*/
- self->rbuff_len = len > HUGE_STRING_LEN ? len : HUGE_STRING_LEN;
- self->rbuff_pos = 0;
- self->rbuff = malloc(self->rbuff_len);
- if (! self->rbuff)
- return PyErr_NoMemory();
-
- /* read it in */
- Py_BEGIN_ALLOW_THREADS
- chunk_len = ap_get_client_block(self->request_rec, self->rbuff,
- self->rbuff_len);
- Py_END_ALLOW_THREADS;
-
- /* ap_get_client_block could return -1 on error */
- if (chunk_len == -1) {
-
- /* Free rbuff since returning NULL here should end the request */
- free(self->rbuff);
- self->rbuff = NULL;
-
- PyErr_SetObject(PyExc_IOError,
- PyString_FromString("Client read error (Timeout?)"));
- return NULL;
+ if (! self->rbuff) {
+ self->rbuff = malloc(ALLOCATED_RBUFF_SIZE);
+ if (! self->rbuff)
+ return PyErr_NoMemory();
}
-
- bytes_read = chunk_len;
- /* if this is a "short read", try reading more */
- while ((chunk_len != 0 ) && (bytes_read + copied < len)) {
+ for (;;) {
+ /* read some data into rbuff */
Py_BEGIN_ALLOW_THREADS
- chunk_len = ap_get_client_block(self->request_rec,
- self->rbuff + bytes_read,
- self->rbuff_len - bytes_read);
+ self->rbuff_pos = 0;
+ self->rbuff_len = (int)ap_get_client_block(self->request_rec,
+ self->rbuff,
+ ALLOCATED_RBUFF_SIZE);
Py_END_ALLOW_THREADS
- if (chunk_len == -1) {
+ /* check read result */
+ if (self->rbuff_len == -1) {
/* Free rbuff since returning NULL here should end the request */
free(self->rbuff);
@@ -1085,31 +1143,48 @@
PyString_FromString("Client read error
(Timeout?)"));
return NULL;
}
- else
- bytes_read += chunk_len;
- }
- self->rbuff_len = bytes_read;
- self->rbuff_pos = 0;
- /* now copy the remaining bytes */
- while (self->rbuff_pos < self->rbuff_len) {
-
- buffer[copied++] = self->rbuff[self->rbuff_pos];
- if ((self->rbuff[self->rbuff_pos++] == '\n') ||
- (copied == len))
- /* our work is done */
+ /* end of stream? */
+ if (self->rbuff_len == 0)
break;
+
+ /* copy into result buffer up to \n or specified length */
+ while (self->rbuff_pos < self->rbuff_len) {
+
+ /* resize buffer if full */
+ if (copied == buffer_size) {
+ buffer_size *= 2;
+ if (_PyString_Resize(&result, buffer_size))
+ return NULL;
+ buffer = PyString_AS_STRING((PyStringObject *) result);
+ }
+
+ /* copy byte */
+ buffer[copied++] = self->rbuff[self->rbuff_pos];
+ if ((self->rbuff[self->rbuff_pos++] == '\n') ||
+ (copied == len)) { /* len may be < 0 meaning this test
will always pass */
+
+ /* leave rbuff_pos and rbuff_len describing the remaining data
*/
+
+ /* our work is done */
+ goto break_outer_loop;
+ }
+ }
+
+ /* reset */
+ self->rbuff_pos = 0;
}
+break_outer_loop:
/* Free rbuff if we're done with it */
- if (self->rbuff_pos >= self->rbuff_len && self->rbuff != NULL)
+ if (self->rbuff_pos >= self->rbuff_len)
{
free(self->rbuff);
self->rbuff = NULL;
}
- /* resize if necessary */
- if (copied < len)
+ /* resize result buffer if necessary */
+ if (copied < buffer_size)
/* PYTHON 2.5: '_PyString_Resize' uses Py_ssize_t for input parameters
*/
if(_PyString_Resize(&result, copied))
return NULL;
@@ -1472,6 +1547,7 @@
{"is_https", (PyCFunction) req_is_https,
METH_NOARGS},
{"log_error", (PyCFunction) req_log_error,
METH_VARARGS},
{"meets_conditions", (PyCFunction) req_meets_conditions,
METH_NOARGS},
+ {"set_read_policy", (PyCFunction) req_set_read_policy,
METH_VARARGS},
{"read", (PyCFunction) req_read,
METH_VARARGS},
{"readline", (PyCFunction) req_readline,
METH_VARARGS},
{"readlines", (PyCFunction) req_readlines,
METH_VARARGS},
Only in mod_python-3.3.1.p/test: modules