summaryrefslogblamecommitdiffhomepage
path: root/src/nxt_python_wsgi.c
blob: da2b4d7617bac97f5fae405508ba9894f48f1af0 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12











                                     
                     
                        




























                                                                              
                                                                            










                                                 
                                                                   












                                                                           
                                                                  

















                                                                  
                                                                  






































                                                                              
                                                          
































































































                                                                                
                                          












                                                                              

                                 




















































                                                                        

                                                                     






























































































































                                                                            
                                   



































































































                                                                               
                                             
 
                                     



                                                                              
                                                                             








































































                                                                                

                                                                        
















                                                                               

                                                                       
















                                                                               

                                                                     
















                                                                                
                                                                         

                        

                                                                         



                

                                                                         

















                                                                                

                                                                            




















                                                                               
                                                                             




















                                                                                
                                                                             























                                                                              
                                                     
 
                                    















                                         

                                                                     

































                                                                                
                                   














                                                                            

                                                      

                                                
                                             































                                                                               

                                                          
 
                                                 









                                                                               

                                                          
 
                                                 



















































































                                                                                

/*
 * Copyright (C) Valentin V. Bartenev
 * Copyright (C) NGINX, Inc.
 */


#include <Python.h>

#include <compile.h>
#include <node.h>

#include <nxt_main.h>
#include <nxt_runtime.h>
#include <nxt_application.h>


typedef struct {
    PyObject_HEAD
    //nxt_app_request_t  *request;
} nxt_py_input_t;


typedef struct {
    PyObject_HEAD
    //nxt_app_request_t  *request;
} nxt_py_error_t;


static nxt_int_t nxt_python_init(nxt_thread_t *thr);
static nxt_int_t nxt_python_run(nxt_app_request_t *r);

static PyObject *nxt_python_create_environ(nxt_thread_t *thr);
static PyObject *nxt_python_get_environ(nxt_app_request_t *r);

static PyObject *nxt_py_start_resp(PyObject *self, PyObject *args);

static void nxt_py_input_dealloc(nxt_py_input_t *self);
static PyObject *nxt_py_input_read(nxt_py_input_t *self, PyObject *args);
static PyObject *nxt_py_input_readline(nxt_py_input_t *self, PyObject *args);
static PyObject *nxt_py_input_readlines(nxt_py_input_t *self, PyObject *args);


extern nxt_int_t nxt_python_wsgi_init(nxt_thread_t *thr, nxt_runtime_t *rt);


nxt_application_module_t  nxt_python_module = {
    nxt_python_init,
    NULL,
    NULL,
    nxt_python_run,
};


static PyMethodDef nxt_py_start_resp_method[] = {
    {"nginext_start_response", nxt_py_start_resp, METH_VARARGS, ""}
};


static PyMethodDef nxt_py_input_methods[] = {
    { "read",      (PyCFunction) nxt_py_input_read,      METH_VARARGS, 0 },
    { "readline",  (PyCFunction) nxt_py_input_readline,  METH_VARARGS, 0 },
    { "readlines", (PyCFunction) nxt_py_input_readlines, METH_VARARGS, 0 },
    { NULL, NULL, 0, 0 }
};


static PyTypeObject nxt_py_input_type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "nginext._input",                   /* tp_name              */
    (int) sizeof(nxt_py_input_t),       /* tp_basicsize         */
    0,                                  /* tp_itemsize          */
    (destructor) nxt_py_input_dealloc,  /* tp_dealloc           */
    0,                                  /* tp_print             */
    0,                                  /* tp_getattr           */
    0,                                  /* tp_setattr           */
    0,                                  /* tp_compare           */
    0,                                  /* tp_repr              */
    0,                                  /* tp_as_number         */
    0,                                  /* tp_as_sequence       */
    0,                                  /* tp_as_mapping        */
    0,                                  /* tp_hash              */
    0,                                  /* tp_call              */
    0,                                  /* tp_str               */
    0,                                  /* tp_getattro          */
    0,                                  /* tp_setattro          */
    0,                                  /* tp_as_buffer         */
    Py_TPFLAGS_DEFAULT,                 /* tp_flags             */
    "nginext input object.",            /* tp_doc               */
    0,                                  /* tp_traverse          */
    0,                                  /* tp_clear             */
    0,                                  /* tp_richcompare       */
    0,                                  /* tp_weaklistoffset    */
    0,                                  /* tp_iter              */
    0,                                  /* tp_iternext          */
    nxt_py_input_methods,               /* tp_methods           */
    0,                                  /* tp_members           */
    0,                                  /* tp_getset            */
    0,                                  /* tp_base              */
    0,                                  /* tp_dict              */
    0,                                  /* tp_descr_get         */
    0,                                  /* tp_descr_set         */
    0,                                  /* tp_dictoffset        */
    0,                                  /* tp_init              */
    0,                                  /* tp_alloc             */
    0,                                  /* tp_new               */
    0,                                  /* tp_free              */
    0,                                  /* tp_is_gc             */
    0,                                  /* tp_bases             */
    0,                                  /* tp_mro - method resolution order */
    0,                                  /* tp_cache             */
    0,                                  /* tp_subclasses        */
    0,                                  /* tp_weaklist          */
    0,                                  /* tp_del               */
    0,                                  /* tp_version_tag       */
};


static char               *nxt_py_module;

static PyObject           *nxt_py_application;
static PyObject           *nxt_py_start_resp_obj;
static PyObject           *nxt_py_environ_ptyp;

static nxt_app_request_t  *nxt_app_request;


nxt_int_t
nxt_python_wsgi_init(nxt_thread_t *thr, nxt_runtime_t *rt)
{
    char    **argv;
    u_char  *p, *dir;

    PyObject  *obj, *pypath;

    argv = nxt_process_argv;

    while (*argv != NULL) {
        p = (u_char *) *argv++;

        if (nxt_strcmp(p, "--py-module") == 0) {
            if (*argv == NULL) {
                nxt_log_emerg(thr->log,
                              "no argument for option \"--py-module\"");
                return NXT_ERROR;
            }

            nxt_py_module = *argv++;

            nxt_log_error(NXT_LOG_INFO, thr->log, "python module: \"%s\"",
                          nxt_py_module);

            break;
        }
    }

    if (nxt_py_module == NULL) {
        return NXT_OK;
    }

    Py_InitializeEx(0);

    obj = NULL;
    argv = nxt_process_argv;

    while (*argv != NULL) {
        p = (u_char *) *argv++;

        if (nxt_strcmp(p, "--py-path") == 0) {
            if (*argv == NULL) {
                nxt_log_emerg(thr->log, "no argument for option \"--py-path\"");
                goto fail;
            }

            dir = (u_char *) *argv++;

            nxt_log_error(NXT_LOG_INFO, thr->log, "python path \"%s\"", dir);

            obj = PyString_FromString((char *) dir);

            if (nxt_slow_path(obj == NULL)) {
                nxt_log_alert(thr->log,
                              "Python failed create string object \"%s\"", dir);
                goto fail;
            }

            pypath = PySys_GetObject((char *) "path");

            if (nxt_slow_path(pypath == NULL)) {
                nxt_log_alert(thr->log,
                              "Python failed to get \"sys.path\" list");
                goto fail;
            }

            if (nxt_slow_path(PyList_Insert(pypath, 0, obj) != 0)) {
                nxt_log_alert(thr->log,
                      "Python failed to insert \"%s\" into \"sys.path\"", dir);
                goto fail;
            }

            Py_DECREF(obj);
            obj = NULL;

            continue;
        }
    }

    obj = PyCFunction_New(nxt_py_start_resp_method, NULL);

    if (nxt_slow_path(obj == NULL)) {
        nxt_log_alert(thr->log,
                "Python failed to initialize the \"start_response\" function");
        goto fail;
    }

    nxt_py_start_resp_obj = obj;

    obj = nxt_python_create_environ(thr);

    if (obj == NULL) {
        goto fail;
    }

    nxt_py_environ_ptyp = obj;


    obj = Py_BuildValue("[s]", "nginext");
    if (obj == NULL) {
        nxt_log_alert(thr->log,
                      "Python failed to create the \"sys.argv\" list");
        goto fail;
    }

    if (PySys_SetObject((char *) "argv", obj) != 0) {
        nxt_log_alert(thr->log, "Python failed to set the \"sys.argv\" list");
        goto fail;
    }

    Py_DECREF(obj);

    nxt_app = &nxt_python_module;

    return NXT_OK;

fail:

    Py_XDECREF(obj);
    Py_XDECREF(nxt_py_start_resp_obj);

    Py_Finalize();

    return NXT_ERROR;
}


static nxt_int_t
nxt_python_init(nxt_thread_t *thr)
{
    PyObject  *module, *obj;

#if 0
    FILE          *fp;
    PyObject      *co;
    struct _node  *node;

    chdir((char *) dir);
    fp = fopen((char *) script, "r");

    if (fp == NULL) {
        nxt_log_debug(thr->log, "fopen failed");
        return NXT_ERROR;
    }


    Py_SetProgramName((char *) "python mysite/wsgi.py");
    Py_InitializeEx(0);

    node = PyParser_SimpleParseFile(fp, (char *) script, Py_file_input);

    fclose(fp);

    if (node == NULL) {
        nxt_log_debug(thr->log, "BAD node");
        return NXT_ERROR;
    }

    co = (PyObject *) PyNode_Compile(node, (char *) script);

    PyNode_Free(node);

    if (co == NULL) {
        nxt_log_debug(thr->log, "BAD co");
        return NXT_ERROR;
    }

    pModule = PyImport_ExecCodeModuleEx((char *) "_wsgi_nginext", co,
                                        (char *) script);

    Py_XDECREF(co);
#endif

    PyOS_AfterFork();

    module = PyImport_ImportModule(nxt_py_module);

    if (nxt_slow_path(module == NULL)) {
        nxt_log_emerg(thr->log, "Python failed to import module \"%s\"",
                      nxt_py_module);
        return NXT_ERROR;
    }

    obj = PyDict_GetItemString(PyModule_GetDict(module), "application");

    if (nxt_slow_path(obj == NULL)) {
        nxt_log_emerg(thr->log, "Python failed to get \"application\" "
                                "from module \"%s\"", nxt_py_module);
        goto fail;
    }

    if (nxt_slow_path(PyCallable_Check(obj) == 0)) {
        nxt_log_emerg(thr->log, "\"application\" in module \"%s\" "
                                "is not a callable object", nxt_py_module);
        goto fail;
    }

    Py_INCREF(obj);
    Py_DECREF(module);

    nxt_py_application = obj;

    return NXT_OK;

fail:

    Py_DECREF(module);

    return NXT_ERROR;
}


static nxt_int_t
nxt_python_run(nxt_app_request_t *r)
{
    u_char    *buf;
    size_t    size;
    PyObject  *result, *iterator, *item, *args, *environ;

    nxt_app_request = r;

    environ = nxt_python_get_environ(r);

    if (nxt_slow_path(environ == NULL)) {
        return NXT_ERROR;
    }

    args = PyTuple_New(2);

    if (nxt_slow_path(args == NULL)) {
        nxt_log_error(NXT_LOG_ERR, r->log,
                      "Python failed to create arguments tuple");
        return NXT_ERROR;
    }

    PyTuple_SET_ITEM(args, 0, environ);

    Py_INCREF(nxt_py_start_resp_obj);
    PyTuple_SET_ITEM(args, 1, nxt_py_start_resp_obj);

    result = PyObject_CallObject(nxt_py_application, args);

    Py_DECREF(args);

    if (nxt_slow_path(result == NULL)) {
        nxt_log_error(NXT_LOG_ERR, r->log,
                      "Python failed to call the application");
        PyErr_Print();
        return NXT_ERROR;
    }

    iterator = PyObject_GetIter(result);

    Py_DECREF(result);

    if (nxt_slow_path(iterator == NULL)) {
        nxt_log_error(NXT_LOG_ERR, r->log,
                      "the application returned not an iterable object");
        return NXT_ERROR;
    }

    while((item = PyIter_Next(iterator))) {

        if (nxt_slow_path(PyString_Check(item) == 0)) {
            nxt_log_error(NXT_LOG_ERR, r->log,
                          "the application returned not a string object");

            Py_DECREF(item);
            Py_DECREF(iterator);

            return NXT_ERROR;
        }

        size = PyString_GET_SIZE(item);
        buf = (u_char *) PyString_AS_STRING(item);

        nxt_app_write(r, buf, size);

        Py_DECREF(item);
    }

    Py_DECREF(iterator);

    if (nxt_slow_path(PyErr_Occurred() != NULL)) {
        nxt_log_error(NXT_LOG_ERR, r->log, "an application error occurred");
        PyErr_Print();
        return NXT_ERROR;
    }

    return NXT_OK;
}


static PyObject *
nxt_python_create_environ(nxt_thread_t *thr)
{
    PyObject  *obj, *err, *environ;

    environ = PyDict_New();

    if (nxt_slow_path(environ == NULL)) {
        nxt_log_alert(thr->log,
                      "Python failed to create the \"environ\" dictionary");
        return NULL;
    }

    obj = Py_BuildValue("(ii)", 1, 0);

    if (nxt_slow_path(obj == NULL)) {
        nxt_log_alert(thr->log,
                  "Python failed to build the \"wsgi.version\" environ value");
        goto fail;
    }

    if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.version", obj) != 0))
    {
        nxt_log_alert(thr->log,
                    "Python failed to set the \"wsgi.version\" environ value");
        goto fail;
    }

    Py_DECREF(obj);
    obj = NULL;


    if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.multithread",
                                           Py_False)
        != 0))
    {
        nxt_log_alert(thr->log,
                "Python failed to set the \"wsgi.multithread\" environ value");
        goto fail;
    }

    if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.multiprocess",
                                           Py_True)
        != 0))
    {
        nxt_log_alert(thr->log,
               "Python failed to set the \"wsgi.multiprocess\" environ value");
        goto fail;
    }

    if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.run_once",
                                           Py_False)
        != 0))
    {
        nxt_log_alert(thr->log,
                   "Python failed to set the \"wsgi.run_once\" environ value");
        goto fail;
    }


    obj = PyString_FromString("http");

    if (nxt_slow_path(obj == NULL)) {
        nxt_log_alert(thr->log,
              "Python failed to create the \"wsgi.url_scheme\" environ value");
        goto fail;
    }

    if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.url_scheme", obj)
        != 0))
    {
        nxt_log_alert(thr->log,
                 "Python failed to set the \"wsgi.url_scheme\" environ value");
        goto fail;
    }

    Py_DECREF(obj);
    obj = NULL;


    if (nxt_slow_path(PyType_Ready(&nxt_py_input_type) != 0)) {
        nxt_log_alert(thr->log,
                 "Python failed to initialize the \"wsgi.input\" type object");
        goto fail;
    }

    obj = (PyObject *) PyObject_New(nxt_py_input_t, &nxt_py_input_type);

    if (nxt_slow_path(obj == NULL)) {
        nxt_log_alert(thr->log,
                      "Python failed to create the \"wsgi.input\" object");
        goto fail;
    }

    if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.input", obj) != 0)) {
        nxt_log_alert(thr->log,
                      "Python failed to set the \"wsgi.input\" environ value");
        goto fail;
    }

    Py_DECREF(obj);
    obj = NULL;


    err = PySys_GetObject((char *) "stderr");

    if (nxt_slow_path(err == NULL)) {
        nxt_log_alert(thr->log, "Python failed to get \"sys.stderr\" object");
        goto fail;
    }

    if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.error", err) != 0))
    {
        nxt_log_alert(thr->log,
                      "Python failed to set the \"wsgi.error\" environ value");
        goto fail;
    }


    obj = PyString_FromString("localhost");

    if (nxt_slow_path(obj == NULL)) {
        nxt_log_alert(thr->log,
                  "Python failed to create the \"SERVER_NAME\" environ value");
        goto fail;
    }

    if (nxt_slow_path(PyDict_SetItemString(environ, "SERVER_NAME", obj) != 0)) {
        nxt_log_alert(thr->log,
                      "Python failed to set the \"SERVER_NAME\" environ value");
        goto fail;
    }

    Py_DECREF(obj);


    obj = PyString_FromString("80");

    if (nxt_slow_path(obj == NULL)) {
        nxt_log_alert(thr->log,
                  "Python failed to create the \"SERVER_PORT\" environ value");
        goto fail;
    }

    if (nxt_slow_path(PyDict_SetItemString(environ, "SERVER_PORT", obj) != 0)) {
        nxt_log_alert(thr->log,
                      "Python failed to set the \"SERVER_PORT\" environ value");
        goto fail;
    }

    Py_DECREF(obj);

    return environ;

fail:

    Py_XDECREF(obj);
    Py_DECREF(environ);

    return NULL;
}


static PyObject *
nxt_python_get_environ(nxt_app_request_t *r)
{
    u_char                  *p, ch, *query;
    nxt_str_t               *str;
    nxt_uint_t              i, n;
    nxt_app_header_field_t  *fld;

    PyObject                *environ, *value;

    static const u_char prefix[5] = "HTTP_";

    static u_char key[256];

    environ = PyDict_Copy(nxt_py_environ_ptyp);

    if (nxt_slow_path(environ == NULL)) {
        nxt_log_error(NXT_LOG_ERR, r->log,
                      "Python failed to create the \"environ\" dictionary");
        return NULL;
    }

    value = PyString_FromStringAndSize((char *) r->header.version.start,
                                       r->header.version.length);

    if (nxt_slow_path(value == NULL)) {
        nxt_log_error(NXT_LOG_ERR, r->log,
              "Python failed to create the \"SERVER_PROTOCOL\" environ value");
        goto fail;
    }

    if (nxt_slow_path(PyDict_SetItemString(environ, "SERVER_PROTOCOL", value)
        != 0))
    {
        nxt_log_error(NXT_LOG_ERR, r->log,
                 "Python failed to set the \"SERVER_PROTOCOL\" environ value");
        goto fail;
    }

    Py_DECREF(value);

    value = PyString_FromStringAndSize((char *) r->header.method.start,
                                       r->header.method.length);

    if (nxt_slow_path(value == NULL)) {
        nxt_log_error(NXT_LOG_ERR, r->log,
               "Python failed to create the \"REQUEST_METHOD\" environ value");
        goto fail;
    }

    if (nxt_slow_path(PyDict_SetItemString(environ, "REQUEST_METHOD", value)
        != 0))
    {
        nxt_log_error(NXT_LOG_ERR, r->log,
                  "Python failed to set the \"REQUEST_METHOD\" environ value");
        goto fail;
    }

    Py_DECREF(value);

    value = PyString_FromStringAndSize((char *) r->header.path.start,
                                       r->header.path.length);

    if (nxt_slow_path(value == NULL)) {
        nxt_log_error(NXT_LOG_ERR, r->log,
                  "Python failed to create the \"REQUEST_URI\" environ value");
        goto fail;
    }

    if (nxt_slow_path(PyDict_SetItemString(environ, "REQUEST_URI", value)
        != 0))
    {
        nxt_log_error(NXT_LOG_ERR, r->log,
                      "Python failed to set the \"REQUEST_URI\" environ value");
        goto fail;
    }

    Py_DECREF(value);

    query = nxt_memchr(r->header.path.start, '?', r->header.path.length);

    if (query != NULL) {
        value = PyString_FromStringAndSize((char *) r->header.path.start,
                                           query - r->header.path.start);

        query++;

    } else {
        value = PyString_FromStringAndSize((char *) r->header.path.start,
                                           r->header.path.length);
    }

    if (nxt_slow_path(value == NULL)) {
        nxt_log_error(NXT_LOG_ERR, r->log,
                    "Python failed to create the \"PATH_INFO\" environ value");
        goto fail;
    }

    if (nxt_slow_path(PyDict_SetItemString(environ, "PATH_INFO", value) != 0)) {
        nxt_log_error(NXT_LOG_ERR, r->log,
                      "Python failed to set the \"PATH_INFO\" environ value");
        goto fail;
    }

    Py_DECREF(value);

    if (query != NULL) {
        value = PyString_FromStringAndSize((char *) query,
                                           r->header.path.start
                                           + r->header.path.length - query);

        if (nxt_slow_path(value == NULL)) {
            nxt_log_error(NXT_LOG_ERR, r->log,
                 "Python failed to create the \"QUERY_STRING\" environ value");
            goto fail;
        }

        if (nxt_slow_path(PyDict_SetItemString(environ, "QUERY_STRING", value)
            != 0))
        {
            nxt_log_error(NXT_LOG_ERR, r->log,
                    "Python failed to set the \"QUERY_STRING\" environ value");
            goto fail;
        }

        Py_DECREF(value);
    }

    if (r->header.content_length != NULL) {
        str = r->header.content_length;

        value = PyString_FromStringAndSize((char *) str->start, str->length);

        if (nxt_slow_path(value == NULL)) {
            nxt_log_error(NXT_LOG_ERR, r->log,
               "Python failed to create the \"CONTENT_LENGTH\" environ value");
            goto fail;
        }

        if (nxt_slow_path(PyDict_SetItemString(environ, "CONTENT_LENGTH", value)
            != 0))
        {
            nxt_log_error(NXT_LOG_ERR, r->log,
                  "Python failed to set the \"CONTENT_LENGTH\" environ value");
            goto fail;
        }

        Py_DECREF(value);
    }

    if (r->header.content_type != NULL) {
        str = r->header.content_type;

        value = PyString_FromStringAndSize((char *) str->start, str->length);

        if (nxt_slow_path(value == NULL)) {
            nxt_log_error(NXT_LOG_ERR, r->log,
               "Python failed to create the \"CONTENT_TYPE\" environ value");
            goto fail;
        }

        if (nxt_slow_path(PyDict_SetItemString(environ, "CONTENT_TYPE", value)
            != 0))
        {
            nxt_log_error(NXT_LOG_ERR, r->log,
                  "Python failed to set the \"CONTENT_TYPE\" environ value");
            goto fail;
        }

        Py_DECREF(value);
    }

    nxt_memcpy(key, prefix, sizeof(prefix));

    for (i = 0; i < r->header.fields_num; i++) {
        fld = &r->header.fields[i];
        p = key + sizeof(prefix);

        for (n = 0; n < fld->name.length; n++, p++) {

            ch = fld->name.start[n];

            if (ch >= 'a' && ch <= 'z') {
                *p = ch & ~0x20;
                continue;
            }

            if (ch == '-') {
                *p = '_';
                continue;
            }

            *p = ch;
        }

        *p = '\0';

        value = PyString_FromStringAndSize((char *) fld->value.start,
                                           fld->value.length);

        if (nxt_slow_path(PyDict_SetItemString(environ, (char *) key, value)
            != 0))
        {
            nxt_log_error(NXT_LOG_ERR, r->log,
                          "Python failed to set the \"%s\" environ value", key);
            goto fail;
        }

        Py_DECREF(value);
    }

    return environ;

fail:

    Py_XDECREF(value);
    Py_DECREF(environ);

    return NULL;
}


static PyObject *
nxt_py_start_resp(PyObject *self, PyObject *args)
{
    u_char      *p, buf[4096];
    PyObject    *headers, *tuple, *string;
    nxt_str_t   str;
    nxt_uint_t  i, n;

    static const u_char resp[] = "HTTP/1.1 ";

    static const u_char default_headers[]
        = "Server: nginext/0.1\r\n"
          "Connection: close\r\n";

    n = PyTuple_GET_SIZE(args);

    if (n < 2 || n > 3) {
        return PyErr_Format(PyExc_TypeError, "invalid number of arguments");
    }

    string = PyTuple_GET_ITEM(args, 0);

    if (!PyString_Check(string)) {
        return PyErr_Format(PyExc_TypeError,
                            "the first argument is not a string");
    }

    str.length = PyString_GET_SIZE(string);
    str.start = (u_char *) PyString_AS_STRING(string);

    p = nxt_cpymem(buf, resp, sizeof(resp) - 1);
    p = nxt_cpymem(p, str.start, str.length);

    *p++ = '\r'; *p++ = '\n';

    p = nxt_cpymem(p, default_headers, sizeof(default_headers) - 1);

    headers = PyTuple_GET_ITEM(args, 1);

    if (!PyList_Check(headers)) {
        return PyErr_Format(PyExc_TypeError,
                         "the second argument is not a response headers list");
    }

    for (i = 0; i < (nxt_uint_t) PyList_GET_SIZE(headers); i++) {
        tuple = PyList_GET_ITEM(headers, i);

        if (!PyTuple_Check(tuple)) {
            return PyErr_Format(PyExc_TypeError,
                              "the response headers must be a list of tuples");
        }

        if (PyTuple_GET_SIZE(tuple) != 2) {
            return PyErr_Format(PyExc_TypeError,
                                "each header must be a tuple of two items");
        }

        string = PyTuple_GET_ITEM(tuple, 0);

        if (!PyString_Check(string)) {
            return PyErr_Format(PyExc_TypeError,
                                "all response headers names must be strings");
        }

        str.length = PyString_GET_SIZE(string);
        str.start = (u_char *) PyString_AS_STRING(string);

        p = nxt_cpymem(p, str.start, str.length);

        *p++ = ':'; *p++ = ' ';

        string = PyTuple_GET_ITEM(tuple, 1);

        if (!PyString_Check(string)) {
            return PyErr_Format(PyExc_TypeError,
                                "all response headers values must be strings");
        }

        str.length = PyString_GET_SIZE(string);
        str.start = (u_char *) PyString_AS_STRING(string);

        p = nxt_cpymem(p, str.start, str.length);

        *p++ = '\r'; *p++ = '\n';
    }

    *p++ = '\r'; *p++ = '\n';

    nxt_app_write(nxt_app_request, buf, p - buf);

    return args;
}


static void
nxt_py_input_dealloc(nxt_py_input_t *self)
{
    PyObject_Del(self);
}


static PyObject *
nxt_py_input_read(nxt_py_input_t *self, PyObject *args)
{
    u_char      *buf;
    PyObject    *body, *obj;
    Py_ssize_t  size;
    nxt_uint_t  n;

    nxt_app_request_t *r = nxt_app_request;

    size = r->body_rest;

    n = PyTuple_GET_SIZE(args);

    if (n > 0) {
        if (n != 1) {
            return PyErr_Format(PyExc_TypeError, "invalid number of arguments");
        }

        obj = PyTuple_GET_ITEM(args, 0);

        size = PyNumber_AsSsize_t(obj, PyExc_OverflowError);

        if (nxt_slow_path(size < 0)) {
            if (size == -1 && PyErr_Occurred()) {
                return NULL;
            }

            return PyErr_Format(PyExc_ValueError,
                                "the read body size cannot be zero or less");
        }

        if (size == 0 || size > r->body_rest) {
            size = r->body_rest;
        }
    }

    body = PyString_FromStringAndSize(NULL, size);

    if (nxt_slow_path(body == NULL)) {
        return NULL;
    }

    buf = (u_char *) PyString_AS_STRING(body);

    if (nxt_app_http_read_body(r, buf, size) != NXT_OK) {
        return PyErr_Format(PyExc_IOError, "failed to read body");
    }

    return body;
}


static PyObject *
nxt_py_input_readline(nxt_py_input_t *self, PyObject *args)
{
    return PyString_FromString("");
}


static PyObject *
nxt_py_input_readlines(nxt_py_input_t *self, PyObject *args)
{
    return PyList_New(0);
}