python-wsgi binding is working (to some extent)
[ebb.git] / src / ebb_python.c
blobab31f900c1ef23f172fa063fa3afa2b0c6055c59
1 /* A python binding to the ebb web server
2 * Copyright (c) 2008 Ry Dahl. This software is released under the MIT
3 * License. See README file for details.
4 */
5 #include <Python.h>
6 #include "ebb.h"
7 #include <ev.h>
8 #include <assert.h>
10 #define PyDict_SetStringString(dict, str1, str2) PyDict_SetItemString(dict, str1, PyString_FromString(str2))
11 #define ASCII_UPPER(ch) ('a' <= ch && ch <= 'z' ? ch - 'a' + 'A' : ch)
13 /* Why is there a global ebb_server variable instead of a wrapping it in a
14 * class? Because you would for no conceivable reason want to run more than
15 * one ebb_server per python VM instance.
17 static ebb_server server;
18 struct ev_loop *loop;
19 static PyObject *application;
20 static PyObject *base_env;
21 static PyObject *global_http_prefix;
22 static PyObject *global_request_method;
23 static PyObject *global_request_uri;
24 static PyObject *global_fragment;
25 static PyObject *global_request_path;
26 static PyObject *global_query_string;
27 static PyObject *global_http_version;
28 static PyObject *global_request_body;
29 static PyObject *global_server_name;
30 static PyObject *global_server_port;
31 static PyObject *global_path_info;
32 static PyObject *global_content_length;
33 static PyObject *global_http_host;
35 /* A callable type called Client. __call__(status, response_headers)
36 * is the second argument to an appplcation
37 * Client.environ is the first argument.
39 typedef struct {
40 PyObject_HEAD
41 ebb_client *client;
42 } py_client;
44 static PyObject *
45 py_start_response(py_client *self, PyObject *args, PyObject *kw);
48 static PyMethodDef client_methods[] =
49 { {"start_response" , (PyCFunction)py_start_response, METH_VARARGS, NULL }
50 // , {"write" , (PyCFunction)write, METH_VARARGS, NULL }
51 , {NULL, NULL, 0, NULL}
54 static PyTypeObject py_client_t =
55 { ob_refcnt: 1
56 , tp_name: "ebb.Client"
57 , tp_doc: "a wrapper around ebb_client"
58 , tp_basicsize: sizeof(py_client)
59 , tp_flags: Py_TPFLAGS_DEFAULT
60 , tp_methods: client_methods
63 static PyObject *
64 py_start_response(py_client *self, PyObject *args, PyObject *kw)
66 PyObject *response_headers;
67 char *status_string;
68 int status;
70 if(!PyArg_ParseTuple(args, "sO", &status_string, &response_headers))
71 return NULL;
73 /* do this goofy split(' ') operation. wsgi is such a terrible api. */
74 status = 100 * (status_string[0] - '0')
75 + 10 * (status_string[1] - '0')
76 + 1 * (status_string[2] - '0');
77 assert(0 <= status && status < 1000);
79 ebb_client_write_status(self->client, status, status_string+4);
81 PyObject *iterator = PyObject_GetIter(response_headers);
82 PyObject *header_pair;
83 char *field, *value;
84 while(header_pair = PyIter_Next(iterator)) {
85 if(!PyArg_ParseTuple(header_pair, "ss", &field, &value))
86 return NULL;
87 ebb_client_write_header(self->client, field, value);
88 Py_DECREF(header_pair);
90 ebb_client_write(self->client, "\r\n", 2);
92 Py_RETURN_NONE;
95 static PyObject* env_field(struct ebb_env_item *item)
97 PyObject* f = NULL;
98 int i;
100 switch(item->type) {
101 case EBB_FIELD_VALUE_PAIR:
102 f = PyString_FromStringAndSize(NULL, PyString_GET_SIZE(global_http_prefix) + item->field_length);
103 memcpy( PyString_AS_STRING(f)
104 , PyString_AS_STRING(global_http_prefix)
105 , PyString_GET_SIZE(global_http_prefix)
107 for(i = 0; i < item->field_length; i++) {
108 char *ch = PyString_AS_STRING(f) + PyString_GET_SIZE(global_http_prefix) + i;
109 *ch = item->field[i] == '-' ? '_' : ASCII_UPPER(item->field[i]);
111 break;
112 case EBB_REQUEST_METHOD: f = global_request_method; break;
113 case EBB_REQUEST_URI: f = global_request_uri; break;
114 case EBB_FRAGMENT: f = global_fragment; break;
115 case EBB_REQUEST_PATH: f = global_request_path; break;
116 case EBB_QUERY_STRING: f = global_query_string; break;
117 case EBB_HTTP_VERSION: f = global_http_version; break;
118 case EBB_SERVER_NAME: f = global_server_name; break;
119 case EBB_SERVER_PORT: f = global_server_port; break;
120 case EBB_CONTENT_LENGTH: f = global_content_length; break;
121 default: assert(FALSE);
123 Py_INCREF(f);
124 return f;
128 static PyObject* env_value(struct ebb_env_item *item)
130 if(item->value_length > 0)
131 return PyString_FromStringAndSize(item->value, item->value_length);
132 else
133 return Py_None; // XXX need to increase ref count? :/
137 static PyObject* py_client_env(ebb_client *client)
139 PyObject *env = PyDict_Copy(base_env);
140 int i;
142 for(i=0; i < client->env_size; i++) {
143 PyDict_SetItem(env, env_field(&client->env[i])
144 , env_value(&client->env[i])
147 // PyDict_SetStringString(hash, global_path_info, rb_hash_aref(hash, global_request_path));
149 return env;
152 static py_client* py_client_new(ebb_client *client)
154 py_client *self = PyObject_New(py_client, &py_client_t);
155 if(self == NULL) return NULL;
156 self->client = client;
158 //if(0 < PyObject_SetAttrString((PyObject*)self, "environ", py_client_env(client)))
159 // return NULL;
161 return self;
164 const char *test_response = "test_response defined in ebb_python.c\r\n";
166 void request_cb(ebb_client *client, void *ignore)
169 PyObject *environ, *start_response;
171 py_client *pclient = py_client_new(client);
172 assert(pclient != NULL);
173 //environ = PyObject_GetAttrString((PyObject*)pclient, "environ");
174 environ = py_client_env(client);
175 assert(environ != NULL);
177 start_response = PyObject_GetAttrString((PyObject*)pclient, "start_response");
178 assert(start_response != NULL);
180 PyObject *arglist = Py_BuildValue("OO", environ, start_response);
181 assert(arglist != NULL);
182 assert(application != NULL);
183 PyObject *body = PyEval_CallObject(application, arglist);
184 assert(body != NULL);
186 Py_DECREF(arglist);
187 Py_DECREF(environ);
190 PyObject *iterator = PyObject_GetIter(body);
191 PyObject *body_item;
192 while (body_item = PyIter_Next(iterator)) {
193 char *body_string = PyString_AsString(body_item);
194 int body_length = PyString_Size(body_item);
195 /* Todo support streaming! */
196 ebb_client_write(pclient->client, body_string, body_length);
197 Py_DECREF(body_item);
200 ebb_client_finished(client);
202 Py_DECREF(pclient);
206 static PyObject *start_server(PyObject *self, PyObject *args)
208 PyObject *application_temp;
209 int port;
211 if(!PyArg_ParseTuple(args, "Oi", &application_temp, &port))
212 return NULL;
213 if(!PyCallable_Check(application_temp)) {
214 PyErr_SetString(PyExc_TypeError, "parameter must be callable");
215 return NULL;
217 Py_XINCREF(application_temp); /* Add a reference to new callback */
218 Py_XDECREF(application); /* Dispose of previous callback */
219 application = application_temp; /* Remember new callback */
221 loop = ev_default_loop(0);
222 ebb_server_init(&server, loop, request_cb, NULL);
223 ebb_server_listen_on_port(&server, port);
225 //ev_loop(loop, EVLOOP_ONESHOT);
226 ev_loop(loop, 0);
228 Py_XDECREF(application);
230 Py_RETURN_NONE;
233 static PyObject *stop_server(PyObject *self)
235 Py_RETURN_NONE;
239 static PyMethodDef ebb_module_methods[] =
240 { {"start_server" , (PyCFunction)start_server, METH_VARARGS, NULL }
241 , {"stop_server" , (PyCFunction)stop_server, METH_NOARGS, NULL }
242 , {NULL, NULL, 0, NULL}
245 PyMODINIT_FUNC initebb(void)
247 PyObject *m = Py_InitModule("ebb", ebb_module_methods);
249 base_env = PyDict_New();
250 PyDict_SetStringString(base_env, "SCRIPT_NAME", "");
251 PyDict_SetStringString(base_env, "SERVER_SOFTWARE", "Ebb 0.0.4");
252 PyDict_SetStringString(base_env, "SERVER_PROTOCOL", "HTTP/1.1");
253 PyDict_SetStringString(base_env, "wsgi.url_scheme", "http");
254 PyDict_SetItemString(base_env, "wsgi.multithread", Py_False);
255 PyDict_SetItemString(base_env, "wsgi.multiprocess", Py_False);
256 PyDict_SetItemString(base_env, "wsgi.run_once", Py_False);
257 //PyDict_SetItemString(base_env, "wsgi.version", (0,1));
258 //PyDict_SetItemString(base_env, "wsgi.errors", STDERR);
261 /* StartResponse */
262 py_client_t.tp_new = PyType_GenericNew;
263 if (PyType_Ready(&py_client_t) < 0) return;
264 Py_INCREF(&py_client_t);
265 PyModule_AddObject(m, "Client", (PyObject *)&py_client_t);
267 #define DEF_GLOBAL(N, val) global_##N = PyString_FromString(val)
268 DEF_GLOBAL(http_prefix, "HTTP_");
269 DEF_GLOBAL(request_method, "REQUEST_METHOD");
270 DEF_GLOBAL(request_uri, "REQUEST_URI");
271 DEF_GLOBAL(fragment, "FRAGMENT");
272 DEF_GLOBAL(request_path, "REQUEST_PATH");
273 DEF_GLOBAL(query_string, "QUERY_STRING");
274 DEF_GLOBAL(http_version, "HTTP_VERSION");
275 DEF_GLOBAL(request_body, "REQUEST_BODY");
276 DEF_GLOBAL(server_name, "SERVER_NAME");
277 DEF_GLOBAL(server_port, "SERVER_PORT");
278 DEF_GLOBAL(path_info, "PATH_INFO");
279 DEF_GLOBAL(content_length, "CONTENT_LENGTH");
280 DEF_GLOBAL(http_host, "HTTP_HOST");
282 ebb_server_init(&server, loop, request_cb, NULL);