6 #include "ext/standard/info.h"
9 #include <sys/socket.h>
10 #include <netinet/in.h>
11 #include <arpa/inet.h>
25 #include "http11_parser.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
53 typedef struct _php_event_callback_t
{ /* {{{ */
56 } php_event_callback_t
;
59 php_event_callback_t
*callback
= NULL
;
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
);
69 memcpy(fld
, "HTTP_", 5);
70 for (i
= 0; i
< flen
; i
++) {
74 fld
[5+i
] = toupper(field
[i
]);
78 add_assoc_string(server_vars
, (char *)fld
, (char *)val
, 1);
83 void http_on_element(void *data
, int type
, const char *at
, size_t length
) {
85 zval
*server_vars
= (zval
*)data
;
89 line
= "CONTENT_LENGTH";
93 line
= "CONTENT_TYPE";
101 line
= "HTTP_VERSION";
105 line
= "QUERY_STRING";
109 line
= "REQUEST_PATH";
113 line
= "REQUEST_METHOD";
117 line
= "REQUEST_URI";
121 line
= "REQUEST_BODY";
128 val
= strndup(at
, length
);
129 add_assoc_string(server_vars
, (char *) line
, (char *) val
, 1);
133 void http_on_body(void *data
, const char *at
, size_t length
) {
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);
144 static int setnonblock(int fd
)
148 flags
= fcntl(fd
, F_GETFL
);
152 if (fcntl(fd
, F_SETFL
, flags
) < 0)
158 static inline void _php_event_callback_free(php_event_callback_t
*this_callback
) /* {{{ */
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
);
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
);
200 if (revents
& EV_WRITE
) {
202 cli
->done
+= write(cli
->fd
, cli
->rbuff
+ cli
->done
, cli
->rlen
- cli
->done
);
203 if (cli
->done
== cli
->rlen
) {
212 if ((cli
->rlen
== 0 && cli
->close
== 1) || timeout
== 0.0) {
213 ev_timer_start(loop
, &cli
->ev_goodbye
);
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
) {
224 struct client
*cli
= (struct client
*) _cli
;
227 MAKE_STD_ZVAL(headers
[0]);
228 array_init(headers
[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
);
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
;
259 int header_len
= 11; /* HTTP/1.1 \r\n */
262 char *status_code
= NULL
;
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
)) {
273 if (zend_hash_get_current_key_ex(arr_hash
, &key
, &key_len
, &index
, 0, &pointer
) == HASH_KEY_IS_STRING
) {
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
))) {
298 cli
->rlen
= (header_len
+ (status_len
== 0 ? 8 : status_len
+ 2) + body_len
);
299 cli
->rbuff
= (char *) malloc(cli
->rlen
* sizeof(char));
302 if (status_len
== 0) {
303 memcpy(test
, "HTTP/1.1 200 OK\r\n", 17);
306 memcpy(test
, "HTTP/1.1 ", 9);
309 memcpy(test
, status_code
, status_len
);
312 memcpy(test
, "\r\n", 2);
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
)) {
324 if (zend_hash_get_current_key_ex(arr_hash
, &key
, &key_len
, &index
, 0, &pointer
) == HASH_KEY_IS_STRING
) {
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)) {
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);
342 memcpy(test
, Z_STRVAL(temp
), Z_STRLEN(temp
));
343 test
+= Z_STRLEN(temp
);
345 memcpy(test
, "\r\n", 2);
353 memcpy(test
, "\r\n", 2);
357 memcpy(test
, body
, body_len
);
363 ev_io_start(cli
->loop
,&cli
->ev_write
);
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);
379 cli
->rlen
= read(cli
->fd
,cli
->rbuff
,1023);
393 struct client
*cli
= (struct client
*) w
->data
;
395 ev_timer_start(loop
, &cli
->ev_goodbye
);
398 static void accept_cb(struct ev_loop
*loop
, struct ev_io
*w
, int revents
)
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
);
408 cli
= calloc(1,sizeof(struct client
));
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
)
439 /* {{{ mistral_module_entry
441 zend_module_entry mistral_module_entry
= {
442 STANDARD_MODULE_HEADER
,
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 */
451 STANDARD_MODULE_PROPERTIES
455 #ifdef COMPILE_DL_MISTRAL
456 ZEND_GET_MODULE(mistral
)
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();
469 /* {{{ PHP_MSHUTDOWN_FUNCTION */
470 PHP_MSHUTDOWN_FUNCTION (mistral
)
472 _php_event_callback_free(callback
);
476 PHP_FUNCTION(mistral_init
)
478 struct addrinfo
*ai
= NULL
;
479 struct addrinfo hints
;
480 struct addrinfo
*runp
;
481 int listen_sock
= -1;
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
) {
498 if (listen_port
< 1 || listen_port
> 65535) {
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) {
512 while (runp
!= NULL
&& listen_sock
< 0) {
513 listen_sock
= socket(runp
->ai_family
, SOCK_STREAM
, 0);
515 runp
= runp
->ai_next
;
518 if (listen_sock
< 0) {
519 err(1, "listen failed");
522 if (timeout
<= 0.0) {
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
) {
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
) {
542 inet_pton(sin
.ss_family
, listen_addr
, &(sa6
->sin6_addr
));
543 sa6
->sin6_port
= htons(listen_port
);
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
);
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
;
578 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC
, "z", &zcallback
) != SUCCESS
) {
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
);
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
;
599 _php_event_callback_free(old_callback
);
606 PHP_FUNCTION(mistral_start
)
608 struct ev_loop
*loop
= ev_default_loop(0);
610 ev_io_start(loop
, &ev_accept
);