Add REQUEST_BODY
[mistral.git] / mistral.c
bloba93160f500240871a8ba014aeea21b218b4ea8e6
1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
5 #include "php.h"
6 #include "ext/standard/info.h"
8 #include <sys/types.h>
9 #include <sys/socket.h>
10 #include <netinet/in.h>
11 #include <arpa/inet.h>
12 #include <netdb.h>
13 #include <sys/time.h>
14 #include <stdlib.h>
15 #include <stdio.h>
16 #include <unistd.h>
17 #include <string.h>
18 #include <fcntl.h>
19 #include <errno.h>
20 #include <err.h>
21 #include <stddef.h>
23 #include <ev.h>
25 #include "http11_parser.h"
26 #include "mistral.h"
29 * We did use some ideas from:
30 * https://sie.isc.org/svn/nmsg-httpk/trunk/nmsg-httpk.c
31 * http://github.com/ry/libebb/blob/master/ebb.c
34 struct client {
35 int fd;
37 ev_io ev_read;
38 ev_io ev_write;
39 ev_timer ev_timeout;
40 ev_timer ev_goodbye;
42 struct ev_loop *loop;
43 char *rbuff;
44 int rlen;
45 int done;
47 int close;
50 ev_io ev_accept;
51 double timeout = 300;
53 typedef struct _php_event_callback_t { /* {{{ */
54 zval *func;
55 zval *arg;
56 } php_event_callback_t;
57 /* }}} */
59 php_event_callback_t *callback = NULL;
61 http_parser parser;
63 void http_field_cb(void *data, const char *field, size_t flen, const char *value, size_t vlen) {
64 zval *server_vars = (zval *)data;
65 char *fld = malloc(sizeof(char) * (5 + flen + 1));
66 char *val = strndup(value, vlen);
67 size_t i;
69 memcpy(fld, "HTTP_", 5);
70 for (i = 0; i < flen; i++) {
71 if (field[i] == '-')
72 fld[5+i] = '_';
73 else
74 fld[5+i] = toupper(field[i]);
76 fld[5+flen] = '\0';
78 add_assoc_string(server_vars, (char *)fld, (char *)val, 1);
79 free(fld);
80 free(val);
83 void http_on_element(void *data, int type, const char *at, size_t length) {
84 char *line, *val;
85 zval *server_vars = (zval *)data;
87 switch( type ) {
88 case CONTENT_LENGTH:
89 line = "CONTENT_LENGTH";
90 break;
92 case CONTENT_TYPE:
93 line = "CONTENT_TYPE";
94 break;
96 case FRAGMENT:
97 line = "FRAGMENT";
98 break;
100 case HTTP_VERSION:
101 line = "HTTP_VERSION";
102 break;
104 case QUERY_STRING:
105 line = "QUERY_STRING";
106 break;
108 case REQUEST_PATH:
109 line = "REQUEST_PATH";
110 break;
112 case REQUEST_METHOD:
113 line = "REQUEST_METHOD";
114 break;
116 case REQUEST_URI:
117 line = "REQUEST_URI";
118 break;
120 case REQUEST_BODY:
121 line = "REQUEST_BODY";
122 break;
124 default:
125 line = "UNKNOWN";
128 val = strndup(at, length);
129 add_assoc_string(server_vars, (char *) line, (char *) val, 1);
130 free(val);
133 void http_on_body(void *data, const char *at, size_t length) {
134 char *line, *val;
135 zval *server_vars = (zval *)data;
137 line = "REQUEST_BODY";
139 val = strndup(at, length);
140 add_assoc_string(server_vars, (char *) line, (char *) val, 1);
141 free(val);
144 static int setnonblock(int fd)
146 int flags;
148 flags = fcntl(fd, F_GETFL);
149 if (flags < 0)
150 return flags;
151 flags |= O_NONBLOCK;
152 if (fcntl(fd, F_SETFL, flags) < 0)
153 return -1;
155 return 0;
158 static inline void _php_event_callback_free(php_event_callback_t *this_callback) /* {{{ */
160 if (!this_callback)
161 return;
163 zval_ptr_dtor(&this_callback->func);
164 if (this_callback->arg)
165 zval_ptr_dtor(&this_callback->arg);
167 efree(this_callback);
170 static void timeout_cb(struct ev_loop *loop, struct ev_timer *w, int revents) {
171 struct client *cli = (struct client *) w->data;
172 assert(w == &cli->ev_timeout);
174 ev_timer_start(loop, &cli->ev_goodbye);
177 static void goodbye_cb(struct ev_loop *loop, struct ev_timer *w, int revents) {
178 struct client *cli = (struct client *) w->data;
179 assert(w == &cli->ev_goodbye);
181 ev_io_stop(loop, &cli->ev_read);
182 ev_io_stop(loop, &cli->ev_write);
183 ev_timer_stop(loop, &cli->ev_timeout);
185 shutdown(cli->fd, SHUT_WR);
187 close(cli->fd);
188 free(cli);
191 static void write_cb(struct ev_loop *loop, struct ev_io *w, int revents)
193 struct client *cli = (struct client *) w->data;
195 if (EV_ERROR & revents) {
196 ev_timer_start(loop, &cli->ev_goodbye);
197 return;
200 if (revents & EV_WRITE) {
201 if (cli->rbuff) {
202 cli->done += write(cli->fd, cli->rbuff + cli->done, cli->rlen - cli->done);
203 if (cli->done == cli->rlen) {
204 free(cli->rbuff);
205 cli->rbuff = NULL;
206 cli->rlen = 0;
209 ev_io_stop(EV_A_ w);
212 if ((cli->rlen == 0 && cli->close == 1) || timeout == 0.0) {
213 ev_timer_start(loop, &cli->ev_goodbye);
214 } else {
215 ev_timer_again(loop, &cli->ev_timeout);
216 ev_io_start(loop, &cli->ev_read);
217 if (cli->done < cli->rlen)
218 ev_io_start(loop, &cli->ev_write);
222 static inline void* execute_php(void *_cli) {
223 int result;
224 struct client *cli = (struct client *) _cli;
225 zval retval;
226 zval *headers[1];
227 MAKE_STD_ZVAL(headers[0]);
228 array_init(headers[0]);
230 cli->done = 0;
231 cli->rbuff[cli->rlen] = '\0';
233 http_parser_init(&parser);
234 parser.http_field = &http_field_cb;
235 parser.on_element = &http_on_element;
236 parser.header_done = &http_on_element;
237 parser.data = headers[0];
238 http_parser_execute(&parser, cli->rbuff, cli->rlen, 0);
240 result = call_user_function(EG(function_table), NULL, callback->func, &retval, 1, headers TSRMLS_CC);
241 free(cli->rbuff);
242 cli->rbuff = NULL;
243 cli->rlen = 0;
245 zval_dtor(headers[0]); /* Free's the object contents */
246 FREE_ZVAL(headers[0]); /* Free's the object itself */
248 if (result == SUCCESS) {
249 if (Z_TYPE_P(&retval) == IS_STRING) {
250 cli->rlen = 256 + Z_STRLEN_P(&retval);
251 cli->rbuff = (char *) malloc(cli->rlen * sizeof(char));
252 cli->rlen = snprintf(cli->rbuff, (cli->rlen - 1), "HTTP/1.1 200 OK\r\nConnection: %s\r\nContent-Type: text/plain\r\nServer: mistral/0.1\r\nContent-Length: %u\r\n\r\n%s",
253 (timeout == 0.0 ? "close" : "keep-alive"), Z_STRLEN_P(&retval), Z_STRVAL_P(&retval));
255 } else if (Z_TYPE_P(&retval) == IS_ARRAY) {
256 HashTable *arr_hash = Z_ARRVAL_P(&retval);
257 HashPosition pointer;
258 zval **data;
259 int header_len = 11; /* HTTP/1.1 \r\n */
260 int body_len = 0;
261 int status_len = 0;
262 char *status_code = NULL;
263 char *body = NULL;
264 char *test;
266 for (zend_hash_internal_pointer_reset_ex(arr_hash, &pointer);
267 zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS;
268 zend_hash_move_forward_ex(arr_hash, &pointer)) {
269 char *key;
270 int key_len;
271 long index;
273 if (zend_hash_get_current_key_ex(arr_hash, &key, &key_len, &index, 0, &pointer) == HASH_KEY_IS_STRING) {
274 zval temp = **data;
275 zval_copy_ctor(&temp);
276 convert_to_string(&temp);
278 if (Z_STRLEN(temp) > 0) {
279 if (php_strcmp("status_code", key, key_len - 1)) {
280 status_code = strndup(Z_STRVAL(temp), Z_STRLEN(temp));
281 status_len += Z_STRLEN(temp);
282 } else if (php_strcmp("body", key, key_len - 1)) {
283 body_len += Z_STRLEN(temp);
284 } else { /* So it is a header */
285 header_len += key_len - 1;
286 header_len += 4; /* : \r\n */
287 header_len += Z_STRLEN(temp);
289 if (php_strcasecmp("Connection", key, key_len - 1) && php_strcasecmp("close", Z_STRVAL(temp), Z_STRLEN(temp))) {
290 cli->close = 1;
294 zval_dtor(&temp);
298 cli->rlen = (header_len + (status_len == 0 ? 8 : status_len + 2) + body_len);
299 cli->rbuff = (char *) malloc(cli->rlen * sizeof(char));
300 test = cli->rbuff;
302 if (status_len == 0) {
303 memcpy(test, "HTTP/1.1 200 OK\r\n", 17);
304 test += 17;
305 } else {
306 memcpy(test, "HTTP/1.1 ", 9);
307 test += 9;
309 memcpy(test, status_code, status_len);
310 test += status_len;
312 memcpy(test, "\r\n", 2);
313 test += 2;
314 free(status_code);
317 for (zend_hash_internal_pointer_reset_ex(arr_hash, &pointer);
318 zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS;
319 zend_hash_move_forward_ex(arr_hash, &pointer)) {
320 char *key;
321 int key_len;
322 long index;
324 if (zend_hash_get_current_key_ex(arr_hash, &key, &key_len, &index, 0, &pointer) == HASH_KEY_IS_STRING) {
325 zval temp = **data;
326 zval_copy_ctor(&temp);
327 convert_to_string(&temp);
329 if (Z_STRLEN(temp) > 0) {
330 if (status_len > 0 && php_strcmp("status_code", key, key_len - 1)) {
331 /* do nothing */
332 } else if (body_len > 0 && php_strcmp("body", key, key_len - 1)) {
333 body = malloc(Z_STRLEN(temp) * sizeof(char));
334 memcpy(body, Z_STRVAL(temp), Z_STRLEN(temp));
335 } else { /* So it is a header */
336 memcpy(test, key, key_len - 1);
337 test += (key_len - 1);
339 memcpy(test, ": ", 2);
340 test += 2;
342 memcpy(test, Z_STRVAL(temp), Z_STRLEN(temp));
343 test += Z_STRLEN(temp);
345 memcpy(test, "\r\n", 2);
346 test += 2;
349 zval_dtor(&temp);
353 memcpy(test, "\r\n", 2);
354 test += 2;
356 if (body) {
357 memcpy(test, body, body_len);
358 free(body);
359 test += body_len;
363 ev_io_start(cli->loop,&cli->ev_write);
366 zval_dtor(&retval);
368 if (result != SUCCESS) {
369 ev_timer_start(cli->loop, &cli->ev_goodbye);
373 static void read_cb(struct ev_loop *loop, struct ev_io *w, int revents)
375 if ((revents & EV_READ) && callback) {
376 struct client *cli = (struct client *) w->data;
377 cli->rbuff = malloc(1024);
378 if (cli->rbuff) {
379 cli->rlen = read(cli->fd,cli->rbuff,1023);
380 if (cli->rlen > 0) {
381 ev_io_stop(EV_A_ w);
382 cli->loop = loop;
383 execute_php(cli);
384 return;
385 } else {
386 free(cli->rbuff);
387 cli->rbuff = NULL;
388 cli->rlen = 0;
393 struct client *cli = (struct client *) w->data;
394 ev_io_stop(EV_A_ w);
395 ev_timer_start(loop, &cli->ev_goodbye);
398 static void accept_cb(struct ev_loop *loop, struct ev_io *w, int revents)
400 int client_fd;
401 struct client *cli;
402 struct sockaddr_in client_addr;
403 socklen_t client_len = sizeof(client_addr);
404 client_fd = accept(w->fd, (struct sockaddr *)&client_addr, &client_len);
405 if (client_fd == -1)
406 return;
408 cli = calloc(1,sizeof(struct client));
409 cli->rbuff = NULL;
410 cli->rlen = 0;
411 cli->fd = client_fd;
412 if (setnonblock(cli->fd) < 0)
413 err(1, "failed to set client socket to non-blocking");
415 ev_io_init(&cli->ev_read, read_cb, cli->fd, EV_READ);
416 cli->ev_read.data = cli;
418 ev_io_init(&cli->ev_write, write_cb, cli->fd, EV_WRITE);
419 cli->ev_write.data = cli;
421 ev_timer_init(&cli->ev_goodbye, goodbye_cb, 0., 0.);
422 cli->ev_goodbye.data = cli;
424 ev_timer_init(&cli->ev_timeout, timeout_cb, 0., (timeout == 0.0 ? 30.0 : timeout));
425 cli->ev_timeout.data = cli;
427 ev_io_start(loop,&cli->ev_read);
430 /* {{{ mistral_functions[] */
431 zend_function_entry mistral_functions[] = {
432 PHP_FE(mistral_init, NULL)
433 PHP_FE(mistral_register_callback, NULL)
434 PHP_FE(mistral_start, NULL)
435 { NULL, NULL, NULL }
437 /* }}} */
439 /* {{{ mistral_module_entry
440 * */
441 zend_module_entry mistral_module_entry = {
442 STANDARD_MODULE_HEADER,
443 "mistral",
444 mistral_functions,
445 NULL, /* Replace with NULL if there is nothing to do at php startup */
446 PHP_MSHUTDOWN(mistral),
447 NULL, /* Replace with NULL if there is nothing to do at request start */
448 NULL, /* Replace with NULL if there is nothing to do at request end */
449 PHP_MINFO(mistral),
450 PHP_MISTRAL_VERSION,
451 STANDARD_MODULE_PROPERTIES
453 /* }}} */
455 #ifdef COMPILE_DL_MISTRAL
456 ZEND_GET_MODULE(mistral)
457 #endif
459 /* {{{ PHP_MINFO_FUNCTION */
460 PHP_MINFO_FUNCTION(mistral)
462 php_info_print_table_start();
463 php_info_print_table_header(2, "Mistral support", "enabled");
464 php_info_print_table_row(2, "Version", PHP_MISTRAL_VERSION);
465 php_info_print_table_end();
467 /* }}} */
469 /* {{{ PHP_MSHUTDOWN_FUNCTION */
470 PHP_MSHUTDOWN_FUNCTION (mistral)
472 _php_event_callback_free(callback);
474 /* }}} */
476 PHP_FUNCTION(mistral_init)
478 struct addrinfo *ai = NULL;
479 struct addrinfo hints;
480 struct addrinfo *runp;
481 int listen_sock = -1;
483 char *listen_addr;
484 int listen_addr_len;
485 long listen_port;
487 struct sockaddr_storage sin;
488 struct sockaddr_in * sa4 = (struct sockaddr_in *) &sin;
489 struct sockaddr_in6 * sa6 = (struct sockaddr_in6 *) &sin;
491 int reuseaddr_on = 1;
492 int keepalive_on = 1;
494 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sld", &listen_addr, &listen_addr_len, &listen_port, &timeout) == FAILURE) {
495 RETURN_NULL();
498 if (listen_port < 1 || listen_port > 65535) {
499 RETURN_NULL();
502 bzero(&hints, sizeof (hints));
503 hints.ai_flags = AI_ADDRCONFIG || AI_NUMERICSERV;
504 hints.ai_socktype = SOCK_STREAM;
506 if (getaddrinfo(listen_addr, "", &hints, &ai) != 0) {
507 RETURN_NULL();
510 runp = ai;
512 while (runp != NULL && listen_sock < 0) {
513 listen_sock = socket(runp->ai_family, SOCK_STREAM, 0);
514 if (listen_sock < 0)
515 runp = runp->ai_next;
518 if (listen_sock < 0) {
519 err(1, "listen failed");
522 if (timeout <= 0.0) {
523 timeout = 0.0;
524 } else {
525 if (setsockopt(listen_sock, SOL_SOCKET, SO_KEEPALIVE, &keepalive_on, sizeof(keepalive_on)) == -1)
526 err(1, "setsockopt failed");
529 if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_on, sizeof(reuseaddr_on)) == -1)
530 err(1, "setsockopt failed");
532 if (runp->ai_family == AF_INET6) {
533 int null = 0;
534 setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &null, sizeof(null));
537 memset(&sin, 0, sizeof(sin));
539 sin.ss_family = runp->ai_family;
540 switch (sin.ss_family) {
541 case AF_INET6:
542 inet_pton(sin.ss_family, listen_addr, &(sa6->sin6_addr));
543 sa6->sin6_port = htons(listen_port);
544 break;
545 case AF_INET:
546 if ( strchr(listen_addr, ':') )
547 /* Invalid IPv4-string. Use the wildcard address. */
548 listen_addr = strndup("0.0.0.0", 7);
550 inet_pton(sin.ss_family, listen_addr, &(sa4->sin_addr));
551 sa4->sin_port = htons(listen_port);
554 if (ai)
555 freeaddrinfo(ai);
557 if (bind(listen_sock, (struct sockaddr *) &sin, sizeof(sin)) < 0)
558 err(1, "bind failed");
560 if (listen(listen_sock, 5) < 0)
561 err(1, "listen failed");
563 if (setnonblock(listen_sock) < 0)
564 err(1, "failed to set server socket to non-blocking");
566 ev_io_init(&ev_accept, accept_cb, listen_sock, EV_READ);
569 /* {{{ proto bool event_set(resource event, resource fd, int events, mixed callback[, mixed arg])
570 * Taken from pecl::libevent event_set
572 PHP_FUNCTION(mistral_register_callback)
574 zval *zcallback, *zarg = NULL;
575 php_event_callback_t *new_callback, *old_callback;
576 char *func_name;
578 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zcallback) != SUCCESS) {
579 return;
582 if (!zend_is_callable(zcallback, 0, &func_name TSRMLS_CC)) {
583 php_error_docref(NULL TSRMLS_CC, E_WARNING, "'%s' is not a valid callback", func_name);
584 efree(func_name);
585 RETURN_FALSE;
587 efree(func_name);
589 zval_add_ref(&zcallback);
591 new_callback = emalloc(sizeof(php_event_callback_t));
592 new_callback->func = zcallback;
593 new_callback->arg = zarg;
595 old_callback = callback;
596 callback = new_callback;
598 if (old_callback) {
599 _php_event_callback_free(old_callback);
602 RETURN_TRUE;
604 /* }}} */
606 PHP_FUNCTION(mistral_start)
608 struct ev_loop *loop = ev_default_loop(0);
610 ev_io_start(loop, &ev_accept);
611 ev_loop(loop, 0);