Permalink
Browse files

Yay WSGI app

  • Loading branch information...
1 parent a75f07f commit 4373a8e991372f5e1493d8a47958d914ea231298 @jonashaag committed Aug 17, 2010
Showing with 191 additions and 35 deletions.
  1. +3 −3 Makefile
  2. +17 −3 bjoern/bjoernmodule.c
  3. +1 −0 bjoern/bjoernmodule.h
  4. +16 −2 bjoern/common.h
  5. +9 −3 bjoern/request.h
  6. +12 −7 bjoern/server.c
  7. +130 −5 bjoern/wsgi.c
  8. +0 −1 bjoern/wsgi.h
  9. +1 −5 stuff/wsgitest_config.py
  10. +2 −6 tests/hello.py
View
@@ -32,8 +32,8 @@ small: clean
CFLAGS='-Os' make
bjoernmodule:
- $(CC) $(CPPFLAGS) $(LDFLAGS) $(objects) $(HTTP_PARSER_OBJ) -o $(BUILD_DIR)/_bjoern.so
- python -c "import _bjoern"
+ $(CC) $(CPPFLAGS) $(LDFLAGS) $(objects) $(HTTP_PARSER_OBJ) -o $(BUILD_DIR)/bjoern.so
+ python -c "import bjoern"
again: clean all
@@ -64,4 +64,4 @@ test:
callgrind:
cd _build
- valgrind --tool=callgrind python -c 'import _bjoern'
+ valgrind --tool=callgrind python -c 'import bjoern'
View
@@ -1,13 +1,27 @@
#include <Python.h>
#include "server.h"
+#include "bjoernmodule.h"
+
+static PyObject*
+run(PyObject* self, PyObject* args)
+{
+ const char* host;
+ size_t port;
+
+ if(!PyArg_ParseTuple(args, "Osi", &wsgi_app, &host, &port))
+ return NULL;
+
+ server_run(host, port);
+ Py_RETURN_NONE;
+}
static PyMethodDef Bjoern_FunctionTable[] = {
+ {"run", run, METH_VARARGS, NULL},
{NULL, NULL, 0, NULL}
};
-PyMODINIT_FUNC init_bjoern()
+PyMODINIT_FUNC initbjoern()
{
- Py_InitModule("_bjoern", Bjoern_FunctionTable);
- server_run("0.0.0.0", 8080);
+ Py_InitModule("bjoern", Bjoern_FunctionTable);
}
View
@@ -0,0 +1 @@
+PyObject* wsgi_app;
View
@@ -7,12 +7,26 @@
#include <stdbool.h>
#include <string.h>
-#define GIL_LOCK1() PyGILState_STATE _gilstate = PyGILState_Ensure()
-#define GIL_UNLOCK1() PyGILState_Release(_gilstate)
+#define GIL_LOCK(n) PyGILState_STATE _gilstate_##n = PyGILState_Ensure()
+#define GIL_UNLOCK(n) PyGILState_Release(_gilstate_##n)
#define LENGTH(array) (sizeof(array)/sizeof(array[0]))
#define ADDR_FROM_MEMBER(ptr, strct, mem) (strct*)((size_t)ptr - (size_t)(&(((strct*)NULL)->mem)));
+#define TYPECHECK2(what, check_type, print_type, errmsg_name, failure_retval) \
+ if(!check_type##_Check(what)) { \
+ PyErr_Format(\
+ PyExc_TypeError, \
+ errmsg_name " must be of type %s, not %s", \
+ print_type##_Type.tp_name, \
+ Py_TYPE(what ? what : Py_None)->tp_name \
+ ); \
+ return failure_retval; \
+ }
+#define TYPECHECK(what, type, ...) TYPECHECK2(what, type, type, __VA_ARGS__);
+
+typedef PyObject* PyKeywordFunc(PyObject* self, PyObject* args, PyObject *kwargs);
+
typedef enum {
HTTP_BAD_REQUEST = 400,
HTTP_SERVER_ERROR = 500
View
@@ -10,8 +10,10 @@ typedef enum {
REQUEST_READING,
REQUEST_PARSE_ERROR,
REQUEST_PARSE_DONE,
- REQUEST_WSGI_RESPONSE,
- REQUEST_WSGI_DONE
+ REQUEST_WSGI_GENERAL_RESPONSE,
+ REQUEST_WSGI_STRING_RESPONSE,
+ REQUEST_WSGI_FILE_RESPONSE,
+ REQUEST_WSGI_ITER_RESPONSE
} request_state;
typedef struct {
@@ -28,8 +30,12 @@ typedef struct {
ev_io ev_watcher;
bj_parser parser;
- void* response;
PyObject* headers;
+
+ void* response;
+ PyObject* response_headers;
+ PyObject* status;
+ PyObject* response_curiter;
} Request;
Request* Request_new(int client_fd);
View
@@ -20,7 +20,7 @@
}
-static int sockfd;
+static int sockfd;
typedef void ev_io_callback(struct ev_loop*, ev_io*, const int);
static ev_io_callback ev_io_on_request;
@@ -104,20 +104,25 @@ ev_io_on_read(struct ev_loop* mainloop, ev_io* watcher, const int events)
request->state = REQUEST_READING;
+ GIL_LOCK(0);
+
HANDLE_IO_ERROR(read_bytes,
/* on fatal error */ set_error(request, HTTP_SERVER_ERROR); goto out
);
- GIL_LOCK1();
Request_parse(request, read_buf, read_bytes);
- GIL_UNLOCK1();
+
switch(request->state) {
+
case REQUEST_PARSE_ERROR:
set_error(request, HTTP_BAD_REQUEST);
break;
case REQUEST_PARSE_DONE:
- if(!wsgi_call_application(request))
+ if(!wsgi_call_application(request)) {
+ if(PyErr_Occurred())
+ PyErr_Print();
set_error(request, HTTP_SERVER_ERROR);
+ }
break;
default:
assert(request->state == REQUEST_READING);
@@ -131,17 +136,17 @@ ev_io_on_read(struct ev_loop* mainloop, ev_io* watcher, const int events)
ev_io_start(mainloop, &request->ev_watcher);
again:
+ GIL_UNLOCK(0);
return;
}
static void
ev_io_on_write(struct ev_loop* mainloop, ev_io* watcher, const int events)
{
Request* request = ADDR_FROM_MEMBER(watcher, Request, ev_watcher);
- if(request->state == REQUEST_WSGI_RESPONSE) {
+ if(request->state > REQUEST_WSGI_GENERAL_RESPONSE) {
/* request->response is something that the WSGI application returned */
- wsgi_send_response(request);
- if(request->state != REQUEST_WSGI_DONE)
+ if(!wsgi_send_response(request))
return; /* come around again */
} else {
/* request->response is a C-string */
View
@@ -1,18 +1,143 @@
+#include "common.h"
#include "server.h"
+#include "bjoernmodule.h"
#include "wsgi.h"
+static PyKeywordFunc start_response;
+static bool wsgi_sendfile(Request*);
+static bool wsgi_senditer(Request*);
+
+typedef struct {
+ PyObject_HEAD
+ Request* request;
+} StartResponse;
+static PyTypeObject StartResponse_Type;
+
+
bool
wsgi_call_application(Request* request)
{
- request->response = PyString_FromString("Hello World!\n");
- request->state = REQUEST_WSGI_RESPONSE;
+ StartResponse* start_response = PyObject_NEW(StartResponse, &StartResponse_Type);
+ start_response->request = request;
+ PyObject* args = PyTuple_Pack(/* size */ 2, request->headers, start_response);
+
+ GIL_LOCK(0);
+ PyObject* retval = PyObject_CallObject(wsgi_app, args);
+ GIL_UNLOCK(0);
+
+ Py_DECREF(start_response);
+ if(retval == NULL)
+ return false;
+
+ if(PyFile_Check(retval)) {
+ request->state = REQUEST_WSGI_FILE_RESPONSE;
+ request->response = retval;
+ return true;
+ }
+
+ if(PyString_Check(retval)) {
+ request->state = REQUEST_WSGI_STRING_RESPONSE;
+ request->response = retval;
+ return true;
+ }
+
+ PyObject* iter = PyObject_GetIter(retval);
+ TYPECHECK2(iter, PyIter, PySeqIter, "wsgi application return value", false);
+
+ Py_DECREF(retval);
+ request->state = REQUEST_WSGI_ITER_RESPONSE;
+ request->response = iter;
+ /* Get the first item of the iterator, because that may execute code that
+ * invokes `start_response` (which might not have been invoked yet).
+ * Think of the following scenario:
+ *
+ * def app(environ, start_response):
+ * start_response('200 Ok', ...)
+ * yield 'Hello World'
+ *
+ * That would make `app` return an iterator (more precisely, a generator).
+ * Unfortunately, `start_response` wouldn't be called until the first item
+ * of that iterator is requested; `start_response` however has to be called
+ * _before_ the wsgi body is sent, because it passes the HTTP headers.
+ */
+ request->response_curiter = PyIter_Next(iter);
+
return true;
}
bool
wsgi_send_response(Request* request)
{
- sendall(request, PyString_AS_STRING(request->response), PyString_GET_SIZE(request->response));
- request->state = REQUEST_WSGI_DONE;
- return true;
+ switch(request->state) {
+ case REQUEST_WSGI_STRING_RESPONSE:
+ sendall(
+ request,
+ PyString_AS_STRING(request->response),
+ PyString_GET_SIZE(request->response)
+ );
+ return true;
+
+ case REQUEST_WSGI_FILE_RESPONSE:
+ return wsgi_sendfile(request);
+
+ case REQUEST_WSGI_ITER_RESPONSE:
+ return wsgi_senditer(request);
+
+ default:
+ assert(0);
+ }
}
+
+static bool
+wsgi_sendfile(Request* request)
+{
+ assert(0);
+}
+
+static bool
+wsgi_senditer(Request* request)
+{
+ if(request->response_curiter == NULL)
+ return true;
+
+ TYPECHECK(request->response_curiter, PyString, "wsgi iterable items", true);
+ sendall(
+ request,
+ PyString_AS_STRING(request->response_curiter),
+ PyString_GET_SIZE(request->response_curiter)
+ );
+
+ GIL_LOCK(0);
+ request->response_curiter = PyIter_Next(request->response);
+ GIL_UNLOCK(0);
+ return request->response_curiter != NULL;
+}
+
+
+
+static PyObject*
+start_response(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+ Request* req = ((StartResponse*)self)->request;
+
+ if(!PyArg_UnpackTuple(args,
+ /* Python function name */ "start_response",
+ /* min args */ 2, /* max args */ 2,
+ &req->status,
+ &req->response_headers
+ ))
+ return NULL;
+
+ TYPECHECK(req->status, PyString, "start_response argument 1", NULL);
+ TYPECHECK(req->response_headers, PyList, "start_response argument 2", NULL);
+
+ Py_RETURN_NONE;
+}
+
+static PyTypeObject StartResponse_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "start_response",
+ sizeof(StartResponse),
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ start_response /* __call__ */
+};
View
@@ -1,4 +1,3 @@
-#include "common.h"
#include "request.h"
bool wsgi_call_application(Request*);
View
@@ -4,8 +4,4 @@
SERVER_BOOT_DURATION = 0.1
def run_server(app, host, port):
- if bjoern.HAVE_ROUTING:
- bjoern.route('.*')(app)
- bjoern.run(host, port)
- else:
- bjoern.run(app, host, port)
+ bjoern.run(app, host, port)
View
@@ -2,10 +2,6 @@
def wsgi_app(env, start_response):
start_response('200 abc', [])
- return []
+ return ['Hello World']
-if bjoern.HAVE_ROUTING:
- bjoern.route('.*')(wsgi_app)
- bjoern.run('0.0.0.0', 8080)
-else:
- bjoern.run(wsgi_app, '0.0.0.0', 8080)
+bjoern.run(wsgi_app, '0.0.0.0', 8080)

0 comments on commit 4373a8e

Please sign in to comment.