This is an attempt to execute the function in a seperate thread.
[mistral.git] / mistral.c
blobb20b9a32a226f37d8e4926af75185050fb583b5e
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>
22 #include <pthread.h>
24 #include <ev.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 static int setnonblock(int fd)
62 int flags;
64 flags = fcntl(fd, F_GETFL);
65 if (flags < 0)
66 return flags;
67 flags |= O_NONBLOCK;
68 if (fcntl(fd, F_SETFL, flags) < 0)
69 return -1;
71 return 0;
74 static inline void _php_event_callback_free(php_event_callback_t *this_callback) /* {{{ */
76 if (!this_callback) {
77 return;
80 zval_ptr_dtor(&this_callback->func);
81 if (this_callback->arg) {
82 zval_ptr_dtor(&this_callback->arg);
84 efree(this_callback);
87 static void timeout_cb(struct ev_loop *loop, struct ev_timer *w, int revents) {
88 struct client *cli = (struct client *) w->data;
89 assert(w == &cli->ev_timeout);
91 ev_timer_start(loop, &cli->ev_goodbye);
94 static void goodbye_cb(struct ev_loop *loop, struct ev_timer *w, int revents) {
95 struct client *cli = (struct client *) w->data;
96 assert(w == &cli->ev_goodbye);
98 ev_io_stop(loop, &cli->ev_read);
99 ev_io_stop(loop, &cli->ev_write);
100 ev_timer_stop(loop, &cli->ev_timeout);
102 close(cli->fd);
105 static void write_cb(struct ev_loop *loop, struct ev_io *w, int revents)
107 struct client *cli = (struct client *) w->data;
109 if(EV_ERROR & revents) {
110 ev_timer_start(loop, &cli->ev_goodbye);
111 return;
114 if (revents & EV_WRITE) {
115 if (cli->rbuff) {
116 write(cli->fd, cli->rbuff, cli->rlen);
117 free(cli->rbuff);
119 ev_io_stop(EV_A_ w);
122 if (cli->close == 1 || timeout == 0.0) {
123 ev_timer_start(loop, &cli->ev_goodbye);
124 } else {
125 ev_timer_again(loop, &cli->ev_timeout);
126 ev_io_start(loop, &cli->ev_read);
130 /* Gracefully stolen concept from ebb_pthread */
131 void* execute_php(void *_cli) {
132 int result;
133 struct client *cli = (struct client *) _cli;
134 zval retval;
136 zval *http_headers[1];
138 cli->rbuff[cli->rlen] = '\0';
140 MAKE_STD_ZVAL(http_headers[0]);
141 ZVAL_STRINGL (http_headers[0], cli->rbuff, (cli->rlen + 1), 0);
143 result = call_user_function(EG(function_table), NULL, callback->func, &retval, 1, http_headers TSRMLS_CC);
144 free(cli->rbuff);
145 cli->rbuff = NULL;
146 cli->rlen = 0;
147 FREE_ZVAL(http_headers[0]); /* Free's the object itself */
150 if (result == SUCCESS) {
151 if (Z_TYPE_P(&retval) == IS_STRING) {
152 cli->rlen = 256 + Z_STRLEN_P(&retval);
153 cli->rbuff = (char *) malloc(cli->rlen * sizeof(char));
154 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",
155 (timeout == 0.0 ? "close" : "keep-alive"), Z_STRLEN_P(&retval), Z_STRVAL_P(&retval));
157 } else if (Z_TYPE_P(&retval) == IS_ARRAY) {
158 HashTable *arr_hash = Z_ARRVAL_P(&retval);
159 HashPosition pointer;
160 zval **data;
161 int header_len = 11; /* HTTP/1.1 \r\n */
162 int body_len = 0;
163 int status_len = 0;
164 char *status_code = NULL;
165 char *body = NULL;
166 char *test;
168 for (zend_hash_internal_pointer_reset_ex(arr_hash, &pointer);
169 zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS;
170 zend_hash_move_forward_ex(arr_hash, &pointer)) {
171 char *key;
172 int key_len;
173 long index;
175 if (zend_hash_get_current_key_ex(arr_hash, &key, &key_len, &index, 0, &pointer) == HASH_KEY_IS_STRING) {
176 zval temp = **data;
177 zval_copy_ctor(&temp);
178 convert_to_string(&temp);
180 if (Z_STRLEN(temp) > 0) {
181 if (php_strcmp("status_code", key, key_len - 1)) {
182 status_code = strdup(Z_STRVAL(temp));
183 status_len += Z_STRLEN(temp);
184 } else if (php_strcmp("body", key, key_len - 1)) {
185 body_len += Z_STRLEN(temp);
186 } else { /* So it is a header */
187 header_len += key_len - 1;
188 header_len += 4; /* : \r\n */
189 header_len += Z_STRLEN(temp);
191 if (php_strcasecmp("Connection", key, key_len - 1) && php_strcasecmp("close", Z_STRVAL(temp), Z_STRLEN(temp))) {
192 cli->close = 1;
196 zval_dtor(&temp);
200 cli->rlen = (header_len + (status_len == 0 ? 8 : status_len + 2) + body_len);
201 cli->rbuff = (char *) malloc(cli->rlen * sizeof(char));
202 test = cli->rbuff;
204 if (status_len == 0) {
205 strncpy(test, "HTTP/1.1 200 OK\r\n", 17);
206 test += 17;
207 } else {
208 strncpy(test, "HTTP/1.1 ", 9);
209 test += 9;
210 strncpy(test, status_code, status_len);
211 test += status_len;
212 strncpy(test, "\r\n", 2);
213 test += 2;
214 free(status_code);
217 for (zend_hash_internal_pointer_reset_ex(arr_hash, &pointer);
218 zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS;
219 zend_hash_move_forward_ex(arr_hash, &pointer)) {
220 char *key;
221 int key_len;
222 long index;
224 if (zend_hash_get_current_key_ex(arr_hash, &key, &key_len, &index, 0, &pointer) == HASH_KEY_IS_STRING) {
225 zval temp = **data;
226 zval_copy_ctor(&temp);
227 convert_to_string(&temp);
229 if (Z_STRLEN(temp) > 0) {
230 if (status_len > 0 && php_strcmp("status_code", key, key_len - 1)) {
231 /* do nothing */
232 } else if (body_len > 0 && php_strcmp("body", key, key_len - 1)) {
233 body = strdup(Z_STRVAL(temp));
234 } else { /* So it is a header */
235 strncpy(test, key, key_len - 1);
236 test += key_len - 1;
237 strncpy(test, ": ", 2);
238 test += 2;
239 strncpy(test, Z_STRVAL(temp), Z_STRLEN(temp));
240 test += Z_STRLEN(temp);
241 strncpy(test, "\r\n", 2);
242 test += 2;
248 strncpy(test, "\r\n", 2);
249 test += 2;
251 if (body) {
252 strncpy(test, body, body_len);
253 free(body);
254 test += body_len;
258 ev_io_start(cli->loop,&cli->ev_write);
261 zval_dtor(&retval);
263 if (result != SUCCESS) {
264 ev_timer_start(cli->loop, &cli->ev_goodbye);
268 static void read_cb(struct ev_loop *loop, struct ev_io *w, int revents)
270 if ((revents & EV_READ) && callback) {
271 struct client *cli = (struct client *) w->data;
272 cli->rbuff = malloc(1024);
273 if (cli->rbuff) {
274 cli->rlen = read(cli->fd,cli->rbuff,1023);
275 if (cli->rlen > 0) {
276 pthread_t thread;
277 ev_io_stop(EV_A_ w);
278 cli->loop = loop;
279 pthread_create(&thread, NULL, execute_php, cli);
280 pthread_detach(thread);
281 return;
282 } else {
283 free(cli->rbuff);
284 cli->rbuff = NULL;
285 cli->rlen = 0;
290 struct client *cli = (struct client *) w->data;
291 ev_io_stop(EV_A_ w);
292 ev_timer_start(loop, &cli->ev_goodbye);
295 static void accept_cb(struct ev_loop *loop, struct ev_io *w, int revents)
297 int client_fd;
298 struct client *cli;
299 struct sockaddr_in client_addr;
300 socklen_t client_len = sizeof(client_addr);
301 client_fd = accept(w->fd, (struct sockaddr *)&client_addr, &client_len);
302 if (client_fd == -1)
303 return;
305 cli = calloc(1,sizeof(struct client));
306 cli->rbuff = NULL;
307 cli->rlen = 0;
308 cli->fd = client_fd;
309 if (setnonblock(cli->fd) < 0)
310 err(1, "failed to set client socket to non-blocking");
312 ev_io_init(&cli->ev_read, read_cb, cli->fd, EV_READ);
313 cli->ev_read.data = cli;
315 ev_io_init(&cli->ev_write, write_cb, cli->fd, EV_WRITE);
316 cli->ev_write.data = cli;
318 ev_timer_init(&cli->ev_goodbye, goodbye_cb, 0., 0.);
319 cli->ev_goodbye.data = cli;
321 ev_timer_init(&cli->ev_timeout, timeout_cb, 0., (timeout == 0.0 ? 30.0 : timeout));
322 cli->ev_timeout.data = cli;
324 ev_io_start(loop,&cli->ev_read);
327 /* {{{ mistral_functions[] */
328 zend_function_entry mistral_functions[] = {
329 PHP_FE(mistral_init, NULL)
330 PHP_FE(mistral_register_callback, NULL)
331 PHP_FE(mistral_start, NULL)
332 { NULL, NULL, NULL }
334 /* }}} */
336 /* {{{ mistral_module_entry
337 * */
338 zend_module_entry mistral_module_entry = {
339 STANDARD_MODULE_HEADER,
340 "mistral",
341 mistral_functions,
342 NULL, /* Replace with NULL if there is nothing to do at php startup */
343 PHP_MSHUTDOWN(mistral),
344 NULL, /* Replace with NULL if there is nothing to do at request start */
345 NULL, /* Replace with NULL if there is nothing to do at request end */
346 PHP_MINFO(mistral),
347 PHP_MISTRAL_VERSION,
348 STANDARD_MODULE_PROPERTIES
350 /* }}} */
352 #ifdef COMPILE_DL_MISTRAL
353 ZEND_GET_MODULE(mistral)
354 #endif
356 /* {{{ PHP_MINFO_FUNCTION */
357 PHP_MINFO_FUNCTION(mistral)
359 php_info_print_table_start();
360 php_info_print_table_header(2, "Mistral support", "enabled");
361 php_info_print_table_row(2, "Version", PHP_MISTRAL_VERSION);
362 php_info_print_table_end();
364 /* }}} */
366 /* {{{ PHP_MSHUTDOWN_FUNCTION */
367 PHP_MSHUTDOWN_FUNCTION (mistral)
369 _php_event_callback_free(callback);
371 /* }}} */
373 PHP_FUNCTION(mistral_init)
375 struct addrinfo *ai = NULL;
376 struct addrinfo hints;
377 struct addrinfo *runp;
378 int listen_sock = -1;
380 char *listen_addr;
381 int listen_addr_len;
382 long listen_port;
384 struct sockaddr_storage sin;
385 struct sockaddr_in * sa4 = (struct sockaddr_in *) &sin;
386 struct sockaddr_in6 * sa6 = (struct sockaddr_in6 *) &sin;
388 int reuseaddr_on = 1;
389 int keepalive_on = 1;
391 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sld", &listen_addr, &listen_addr_len, &listen_port, &timeout) == FAILURE) {
392 RETURN_NULL();
395 if (listen_port < 1 || listen_port > 65535) {
396 RETURN_NULL();
399 bzero(&hints, sizeof (hints));
400 hints.ai_flags = AI_ADDRCONFIG || AI_NUMERICSERV;
401 hints.ai_socktype = SOCK_STREAM;
403 if (getaddrinfo (listen_addr, "", &hints, &ai) != 0) {
404 RETURN_NULL();
407 runp = ai;
409 while (runp != NULL && listen_sock < 0) {
410 listen_sock = socket(runp->ai_family, SOCK_STREAM, 0);
411 if (listen_sock < 0)
412 runp = runp->ai_next;
415 if (listen_sock < 0) {
416 err(1, "listen failed");
419 if (timeout <= 0.0) {
420 timeout = 0.0;
421 } else {
422 if (setsockopt(listen_sock, SOL_SOCKET, SO_KEEPALIVE, &keepalive_on, sizeof(keepalive_on)) == -1)
423 err(1, "setsockopt failed");
426 if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_on, sizeof(reuseaddr_on)) == -1)
427 err(1, "setsockopt failed");
429 if (runp->ai_family == AF_INET6) {
430 int null = 0;
431 setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &null, sizeof(null));
434 memset(&sin, 0, sizeof(sin));
436 sin.ss_family = runp->ai_family;
437 switch (sin.ss_family) {
438 case AF_INET6:
439 inet_pton(sin.ss_family, listen_addr, &(sa6->sin6_addr));
440 sa6->sin6_port = htons(listen_port);
441 break;
442 case AF_INET:
443 if ( strchr(listen_addr, ':') )
444 /* Invalid IPv4-string. Use the wildcard address. */
445 listen_addr = strdup("0.0.0.0");
447 inet_pton(sin.ss_family, listen_addr, &(sa4->sin_addr));
448 sa4->sin_port = htons(listen_port);
451 if (ai)
452 freeaddrinfo (ai);
454 if (bind(listen_sock, (struct sockaddr *) &sin, sizeof(sin)) < 0)
455 err(1, "bind failed");
457 if (listen(listen_sock, 5) < 0)
458 err(1, "listen failed");
460 if (setnonblock(listen_sock) < 0)
461 err(1, "failed to set server socket to non-blocking");
463 ev_io_init(&ev_accept, accept_cb, listen_sock, EV_READ);
466 /* {{{ proto bool event_set(resource event, resource fd, int events, mixed callback[, mixed arg])
467 * Taken from pecl::libevent event_set
469 PHP_FUNCTION(mistral_register_callback)
471 zval *zcallback;
472 php_event_callback_t *new_callback, *old_callback;
473 zval *cb;
474 char *func_name;
476 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zcallback) != SUCCESS) {
477 return;
480 if (!zend_is_callable(zcallback, 0, &func_name TSRMLS_CC)) {
481 php_error_docref(NULL TSRMLS_CC, E_WARNING, "'%s' is not a valid callback", func_name);
482 efree(func_name);
483 RETURN_FALSE;
485 efree(func_name);
487 MAKE_STD_ZVAL(cb);
488 *(cb) = *zcallback;
489 zval_copy_ctor(cb);
491 new_callback = emalloc(sizeof(php_event_callback_t));
492 new_callback->func = cb;
493 new_callback->arg = NULL;
495 old_callback = callback;
496 callback = new_callback;
498 if (old_callback) {
499 _php_event_callback_free(old_callback);
501 RETURN_TRUE;
503 /* }}} */
505 PHP_FUNCTION(mistral_start)
507 struct ev_loop *loop = ev_default_loop (0);
509 ev_io_start(loop, &ev_accept);
510 ev_loop(loop, 0);