Gentoo solution
[mistral.git] / mistral.c
blob87b1ab4b6bc0214d3960e9ced6f34a854f2ee0d1
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 "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;
46 int close;
49 ev_io ev_accept;
50 double timeout = 300;
52 typedef struct _php_event_callback_t { /* {{{ */
53 zval *func;
54 zval *arg;
55 } php_event_callback_t;
56 /* }}} */
58 php_event_callback_t *callback = NULL;
60 http_parser parser;
62 void http_field_cb(void *data, const char *field, size_t flen, const char *value, size_t vlen) {
63 zval *server_vars = (zval *)data;
64 char *fld = malloc(5 + flen + 1);
65 char *val = strndup(value, vlen);
66 size_t i;
68 strncpy(fld, "HTTP_", 5);
69 for (i = 0; i < flen; i++) {
70 if (field[i] == '-')
71 fld[5+i] = '_';
72 else
73 fld[5+i] = toupper(field[i]);
75 fld[5+flen] = '\0';
77 add_assoc_string(server_vars, (char *)fld, (char *)val, 1);
78 free(fld);
79 free(val);
82 void http_on_element(void *data, int type, const char *at, size_t length) {
83 char *line, *val;
84 zval *server_vars = (zval *)data;
86 switch( type ) {
87 case MONGREL_CONTENT_LENGTH:
88 line = "CONTENT_LENGTH";
89 break;
91 case MONGREL_CONTENT_TYPE:
92 line = "CONTENT_TYPE";
93 break;
95 case MONGREL_FRAGMENT:
96 line = "FRAGMENT";
97 break;
99 case MONGREL_HTTP_VERSION:
100 line = "HTTP_VERSION";
101 break;
103 case MONGREL_QUERY_STRING:
104 line = "QUERY_STRING";
105 break;
107 case MONGREL_REQUEST_PATH:
108 line = "REQUEST_PATH";
109 break;
111 case MONGREL_REQUEST_METHOD:
112 line = "REQUEST_METHOD";
113 break;
115 case MONGREL_REQUEST_URI:
116 line = "REQUEST_URI";
117 break;
119 default:
120 line = "UNKNOWN";
123 val = strndup(at, length);
124 add_assoc_string(server_vars, (char *) line, (char *) val, 1);
125 free(val);
128 static int setnonblock(int fd)
130 int flags;
132 flags = fcntl(fd, F_GETFL);
133 if (flags < 0)
134 return flags;
135 flags |= O_NONBLOCK;
136 if (fcntl(fd, F_SETFL, flags) < 0)
137 return -1;
139 return 0;
142 static inline void _php_event_callback_free(php_event_callback_t *this_callback) /* {{{ */
144 if (!this_callback)
145 return;
147 zval_ptr_dtor(&this_callback->func);
148 if (this_callback->arg)
149 zval_ptr_dtor(&this_callback->arg);
151 efree(this_callback);
154 static void timeout_cb(struct ev_loop *loop, struct ev_timer *w, int revents) {
155 struct client *cli = (struct client *) w->data;
156 assert(w == &cli->ev_timeout);
158 ev_timer_start(loop, &cli->ev_goodbye);
161 static void goodbye_cb(struct ev_loop *loop, struct ev_timer *w, int revents) {
162 struct client *cli = (struct client *) w->data;
163 assert(w == &cli->ev_goodbye);
165 ev_io_stop(loop, &cli->ev_read);
166 ev_io_stop(loop, &cli->ev_write);
167 ev_timer_stop(loop, &cli->ev_timeout);
169 close(cli->fd);
170 free(cli);
173 static void write_cb(struct ev_loop *loop, struct ev_io *w, int revents)
175 struct client *cli = (struct client *) w->data;
177 if (EV_ERROR & revents) {
178 ev_timer_start(loop, &cli->ev_goodbye);
179 return;
182 if (revents & EV_WRITE) {
183 if (cli->rbuff) {
184 write(cli->fd, cli->rbuff, cli->rlen);
185 free(cli->rbuff);
187 ev_io_stop(EV_A_ w);
190 if (cli->close == 1 || timeout == 0.0) {
191 ev_timer_start(loop, &cli->ev_goodbye);
192 } else {
193 ev_timer_again(loop, &cli->ev_timeout);
194 ev_io_start(loop, &cli->ev_read);
198 static inline void* execute_php(void *_cli) {
199 int result;
200 struct client *cli = (struct client *) _cli;
201 zval retval;
202 zval *headers[1];
203 MAKE_STD_ZVAL(headers[0]);
204 array_init(headers[0]);
206 cli->rbuff[cli->rlen] = '\0';
208 http_parser_init(&parser);
209 parser.http_field = &http_field_cb;
210 parser.on_element = &http_on_element;
211 parser.data = headers[0];
212 http_parser_execute(&parser, cli->rbuff, cli->rlen, 0);
214 result = call_user_function(EG(function_table), NULL, callback->func, &retval, 1, headers TSRMLS_CC);
215 free(cli->rbuff);
216 cli->rbuff = NULL;
217 cli->rlen = 0;
219 zval_dtor(headers[0]); /* Free's the object contents */
220 FREE_ZVAL(headers[0]); /* Free's the object itself */
222 if (result == SUCCESS) {
223 if (Z_TYPE_P(&retval) == IS_STRING) {
224 cli->rlen = 256 + Z_STRLEN_P(&retval);
225 cli->rbuff = (char *) malloc(cli->rlen * sizeof(char));
226 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",
227 (timeout == 0.0 ? "close" : "keep-alive"), Z_STRLEN_P(&retval), Z_STRVAL_P(&retval));
229 } else if (Z_TYPE_P(&retval) == IS_ARRAY) {
230 HashTable *arr_hash = Z_ARRVAL_P(&retval);
231 HashPosition pointer;
232 zval **data;
233 int header_len = 11; /* HTTP/1.1 \r\n */
234 int body_len = 0;
235 int status_len = 0;
236 char *status_code = NULL;
237 char *body = NULL;
238 char *test;
240 for (zend_hash_internal_pointer_reset_ex(arr_hash, &pointer);
241 zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS;
242 zend_hash_move_forward_ex(arr_hash, &pointer)) {
243 char *key;
244 int key_len;
245 long index;
247 if (zend_hash_get_current_key_ex(arr_hash, &key, &key_len, &index, 0, &pointer) == HASH_KEY_IS_STRING) {
248 zval temp = **data;
249 zval_copy_ctor(&temp);
250 convert_to_string(&temp);
252 if (Z_STRLEN(temp) > 0) {
253 if (php_strcmp("status_code", key, key_len - 1)) {
254 status_code = strdup(Z_STRVAL(temp));
255 status_len += Z_STRLEN(temp);
256 } else if (php_strcmp("body", key, key_len - 1)) {
257 body_len += Z_STRLEN(temp);
258 } else { /* So it is a header */
259 header_len += key_len - 1;
260 header_len += 4; /* : \r\n */
261 header_len += Z_STRLEN(temp);
263 if (php_strcasecmp("Connection", key, key_len - 1) && php_strcasecmp("close", Z_STRVAL(temp), Z_STRLEN(temp))) {
264 cli->close = 1;
268 zval_dtor(&temp);
272 cli->rlen = (header_len + (status_len == 0 ? 8 : status_len + 2) + body_len);
273 cli->rbuff = (char *) malloc(cli->rlen * sizeof(char));
274 test = cli->rbuff;
276 if (status_len == 0) {
277 strncpy(test, "HTTP/1.1 200 OK\r\n", 17);
278 test += 17;
279 } else {
280 strncpy(test, "HTTP/1.1 ", 9);
281 test += 9;
282 strncpy(test, status_code, status_len);
283 test += status_len;
284 strncpy(test, "\r\n", 2);
285 test += 2;
286 free(status_code);
289 for (zend_hash_internal_pointer_reset_ex(arr_hash, &pointer);
290 zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS;
291 zend_hash_move_forward_ex(arr_hash, &pointer)) {
292 char *key;
293 int key_len;
294 long index;
296 if (zend_hash_get_current_key_ex(arr_hash, &key, &key_len, &index, 0, &pointer) == HASH_KEY_IS_STRING) {
297 zval temp = **data;
298 zval_copy_ctor(&temp);
299 convert_to_string(&temp);
301 if (Z_STRLEN(temp) > 0) {
302 if (status_len > 0 && php_strcmp("status_code", key, key_len - 1)) {
303 /* do nothing */
304 } else if (body_len > 0 && php_strcmp("body", key, key_len - 1)) {
305 body = strdup(Z_STRVAL(temp));
306 } else { /* So it is a header */
307 strncpy(test, key, key_len - 1);
308 test += key_len - 1;
309 strncpy(test, ": ", 2);
310 test += 2;
311 strncpy(test, Z_STRVAL(temp), Z_STRLEN(temp));
312 test += Z_STRLEN(temp);
313 strncpy(test, "\r\n", 2);
314 test += 2;
317 zval_dtor(&temp);
321 strncpy(test, "\r\n", 2);
322 test += 2;
324 if (body) {
325 strncpy(test, body, body_len);
326 free(body);
327 test += body_len;
331 ev_io_start(cli->loop,&cli->ev_write);
334 zval_dtor(&retval);
336 if (result != SUCCESS) {
337 ev_timer_start(cli->loop, &cli->ev_goodbye);
341 static void read_cb(struct ev_loop *loop, struct ev_io *w, int revents)
343 if ((revents & EV_READ) && callback) {
344 struct client *cli = (struct client *) w->data;
345 cli->rbuff = malloc(1024);
346 if (cli->rbuff) {
347 cli->rlen = read(cli->fd,cli->rbuff,1023);
348 if (cli->rlen > 0) {
349 ev_io_stop(EV_A_ w);
350 cli->loop = loop;
351 execute_php(cli);
352 return;
353 } else {
354 free(cli->rbuff);
355 cli->rbuff = NULL;
356 cli->rlen = 0;
361 struct client *cli = (struct client *) w->data;
362 ev_io_stop(EV_A_ w);
363 ev_timer_start(loop, &cli->ev_goodbye);
366 static void accept_cb(struct ev_loop *loop, struct ev_io *w, int revents)
368 int client_fd;
369 struct client *cli;
370 struct sockaddr_in client_addr;
371 socklen_t client_len = sizeof(client_addr);
372 client_fd = accept(w->fd, (struct sockaddr *)&client_addr, &client_len);
373 if (client_fd == -1)
374 return;
376 cli = calloc(1,sizeof(struct client));
377 cli->rbuff = NULL;
378 cli->rlen = 0;
379 cli->fd = client_fd;
380 if (setnonblock(cli->fd) < 0)
381 err(1, "failed to set client socket to non-blocking");
383 ev_io_init(&cli->ev_read, read_cb, cli->fd, EV_READ);
384 cli->ev_read.data = cli;
386 ev_io_init(&cli->ev_write, write_cb, cli->fd, EV_WRITE);
387 cli->ev_write.data = cli;
389 ev_timer_init(&cli->ev_goodbye, goodbye_cb, 0., 0.);
390 cli->ev_goodbye.data = cli;
392 ev_timer_init(&cli->ev_timeout, timeout_cb, 0., (timeout == 0.0 ? 30.0 : timeout));
393 cli->ev_timeout.data = cli;
395 ev_io_start(loop,&cli->ev_read);
398 /* {{{ mistral_functions[] */
399 zend_function_entry mistral_functions[] = {
400 PHP_FE(mistral_init, NULL)
401 PHP_FE(mistral_register_callback, NULL)
402 PHP_FE(mistral_start, NULL)
403 { NULL, NULL, NULL }
405 /* }}} */
407 /* {{{ mistral_module_entry
408 * */
409 zend_module_entry mistral_module_entry = {
410 STANDARD_MODULE_HEADER,
411 "mistral",
412 mistral_functions,
413 NULL, /* Replace with NULL if there is nothing to do at php startup */
414 PHP_MSHUTDOWN(mistral),
415 NULL, /* Replace with NULL if there is nothing to do at request start */
416 NULL, /* Replace with NULL if there is nothing to do at request end */
417 PHP_MINFO(mistral),
418 PHP_MISTRAL_VERSION,
419 STANDARD_MODULE_PROPERTIES
421 /* }}} */
423 #ifdef COMPILE_DL_MISTRAL
424 ZEND_GET_MODULE(mistral)
425 #endif
427 /* {{{ PHP_MINFO_FUNCTION */
428 PHP_MINFO_FUNCTION(mistral)
430 php_info_print_table_start();
431 php_info_print_table_header(2, "Mistral support", "enabled");
432 php_info_print_table_row(2, "Version", PHP_MISTRAL_VERSION);
433 php_info_print_table_end();
435 /* }}} */
437 /* {{{ PHP_MSHUTDOWN_FUNCTION */
438 PHP_MSHUTDOWN_FUNCTION (mistral)
440 _php_event_callback_free(callback);
442 /* }}} */
444 PHP_FUNCTION(mistral_init)
446 struct addrinfo *ai = NULL;
447 struct addrinfo hints;
448 struct addrinfo *runp;
449 int listen_sock = -1;
451 char *listen_addr;
452 int listen_addr_len;
453 long listen_port;
455 struct sockaddr_storage sin;
456 struct sockaddr_in * sa4 = (struct sockaddr_in *) &sin;
457 struct sockaddr_in6 * sa6 = (struct sockaddr_in6 *) &sin;
459 int reuseaddr_on = 1;
460 int keepalive_on = 1;
462 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sld", &listen_addr, &listen_addr_len, &listen_port, &timeout) == FAILURE) {
463 RETURN_NULL();
466 if (listen_port < 1 || listen_port > 65535) {
467 RETURN_NULL();
470 bzero(&hints, sizeof (hints));
471 hints.ai_flags = AI_ADDRCONFIG || AI_NUMERICSERV;
472 hints.ai_socktype = SOCK_STREAM;
474 if (getaddrinfo(listen_addr, "", &hints, &ai) != 0) {
475 RETURN_NULL();
478 runp = ai;
480 while (runp != NULL && listen_sock < 0) {
481 listen_sock = socket(runp->ai_family, SOCK_STREAM, 0);
482 if (listen_sock < 0)
483 runp = runp->ai_next;
486 if (listen_sock < 0) {
487 err(1, "listen failed");
490 if (timeout <= 0.0) {
491 timeout = 0.0;
492 } else {
493 if (setsockopt(listen_sock, SOL_SOCKET, SO_KEEPALIVE, &keepalive_on, sizeof(keepalive_on)) == -1)
494 err(1, "setsockopt failed");
497 if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_on, sizeof(reuseaddr_on)) == -1)
498 err(1, "setsockopt failed");
500 if (runp->ai_family == AF_INET6) {
501 int null = 0;
502 setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &null, sizeof(null));
505 memset(&sin, 0, sizeof(sin));
507 sin.ss_family = runp->ai_family;
508 switch (sin.ss_family) {
509 case AF_INET6:
510 inet_pton(sin.ss_family, listen_addr, &(sa6->sin6_addr));
511 sa6->sin6_port = htons(listen_port);
512 break;
513 case AF_INET:
514 if ( strchr(listen_addr, ':') )
515 /* Invalid IPv4-string. Use the wildcard address. */
516 listen_addr = strdup("0.0.0.0");
518 inet_pton(sin.ss_family, listen_addr, &(sa4->sin_addr));
519 sa4->sin_port = htons(listen_port);
522 if (ai)
523 freeaddrinfo(ai);
525 if (bind(listen_sock, (struct sockaddr *) &sin, sizeof(sin)) < 0)
526 err(1, "bind failed");
528 if (listen(listen_sock, 5) < 0)
529 err(1, "listen failed");
531 if (setnonblock(listen_sock) < 0)
532 err(1, "failed to set server socket to non-blocking");
534 ev_io_init(&ev_accept, accept_cb, listen_sock, EV_READ);
537 /* {{{ proto bool event_set(resource event, resource fd, int events, mixed callback[, mixed arg])
538 * Taken from pecl::libevent event_set
540 PHP_FUNCTION(mistral_register_callback)
542 zval *zcallback, *zarg = NULL;
543 php_event_callback_t *new_callback, *old_callback;
544 char *func_name;
546 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zcallback) != SUCCESS) {
547 return;
550 if (!zend_is_callable(zcallback, 0, &func_name TSRMLS_CC)) {
551 php_error_docref(NULL TSRMLS_CC, E_WARNING, "'%s' is not a valid callback", func_name);
552 efree(func_name);
553 RETURN_FALSE;
555 efree(func_name);
557 zval_add_ref(&zcallback);
559 new_callback = emalloc(sizeof(php_event_callback_t));
560 new_callback->func = zcallback;
561 new_callback->arg = zarg;
563 old_callback = callback;
564 callback = new_callback;
566 if (old_callback) {
567 _php_event_callback_free(old_callback);
570 RETURN_TRUE;
572 /* }}} */
574 PHP_FUNCTION(mistral_start)
576 struct ev_loop *loop = ev_default_loop(0);
578 ev_io_start(loop, &ev_accept);
579 ev_loop(loop, 0);