This is the initial commit;
[mistral.git] / mistral.c
blob3cbc00bfd1878ee5991084f575757a2c17075d85
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 <sys/time.h>
13 #include <stdlib.h>
14 #include <stdio.h>
15 #include <unistd.h>
16 #include <string.h>
17 #include <fcntl.h>
18 #include <errno.h>
19 #include <err.h>
20 #include <stddef.h>
22 #include <ev.h>
24 #include "mistral.h"
26 #define struct_client_from(cli, field) \
27 ((struct client *) (((char *) cli) - offsetof(struct client, ev_##field)))
29 /* https://sie.isc.org/svn/nmsg-httpk/trunk/nmsg-httpk.c */
31 #define SERVER_PORT 8002
32 struct client {
33 int fd;
34 zval retval;
35 ev_timer ev_timeout;
36 ev_io ev_write;
37 ev_io ev_read;
40 ev_io ev_accept;
41 double timeout = 300;
43 typedef struct _php_event_callback_t { /* {{{ */
44 zval *func;
45 zval *arg;
46 } php_event_callback_t;
47 /* }}} */
49 php_event_callback_t *callback = NULL;
51 static int setnonblock(int fd)
53 int flags;
55 flags = fcntl(fd, F_GETFL);
56 if (flags < 0)
57 return flags;
58 flags |= O_NONBLOCK;
59 if (fcntl(fd, F_SETFL, flags) < 0)
60 return -1;
62 return 0;
65 static inline void _php_event_callback_free(php_event_callback_t *this_callback) /* {{{ */
67 if (!this_callback) {
68 return;
71 zval_ptr_dtor(&this_callback->func);
72 if (this_callback->arg) {
73 zval_ptr_dtor(&this_callback->arg);
75 efree(this_callback);
78 static void write_cb(struct ev_loop *loop, struct ev_io *w, int revents)
80 struct client *cli = struct_client_from(w, write);
82 if (revents & EV_WRITE) {
83 if (Z_TYPE_P(&cli->retval) == IS_STRING) {
84 write(cli->fd, Z_STRVAL_P(&cli->retval), Z_STRLEN_P(&cli->retval));
85 ev_io_stop(EV_A_ w);
89 zval_dtor(&cli->retval);
91 if (timeout == 0.0) {
92 close(cli->fd);
93 free(cli);
94 } else {
95 ev_io_start(loop,&cli->ev_read);
100 static void read_cb(struct ev_loop *loop, struct ev_io *w, int revents)
102 struct client *cli = struct_client_from(w, read);
103 if ((revents & EV_READ) && callback) {
104 char *rbuff = emalloc(1024);
105 int r = 0;
106 zval *http_headers;
107 r = read(cli->fd,rbuff,1023);
108 rbuff[r] = '\0';
110 ev_io_stop(EV_A_ w);
112 MAKE_STD_ZVAL(http_headers);
113 ZVAL_STRINGL (http_headers, rbuff, r, 0);
115 if (call_user_function(EG(function_table), NULL, callback->func, &cli->retval, 1, &http_headers TSRMLS_CC) == SUCCESS) {
116 ev_io_init(&cli->ev_write,write_cb,cli->fd,EV_WRITE);
117 ev_io_start(loop,&cli->ev_write);
118 zval_dtor(http_headers);
119 FREE_ZVAL(http_headers);
120 return;
121 } else {
122 zval_dtor(&cli->retval);
123 FREE_ZVAL(http_headers);
125 } else {
126 ev_io_stop(EV_A_ w);
130 static void timeout_cb(struct ev_loop *loop, struct ev_timer *w, int revents) {
131 struct client *cli = struct_client_from(w, timeout);
132 ev_io_stop(EV_A_ &cli->ev_read);
133 ev_io_stop(EV_A_ &cli->ev_write);
134 close(cli->fd);
135 free(cli);
138 static void accept_cb(struct ev_loop *loop, struct ev_io *w, int revents)
140 int client_fd;
141 struct client *client;
142 struct sockaddr_in client_addr;
143 socklen_t client_len = sizeof(client_addr);
144 client_fd = accept(w->fd, (struct sockaddr *)&client_addr, &client_len);
145 if (client_fd == -1) {
146 return;
149 client = calloc(1,sizeof(*client));
150 client->fd=client_fd;
151 if (setnonblock(client->fd) < 0)
152 err(1, "failed to set client socket to non-blocking");
153 ev_io_init(&client->ev_read,read_cb,client->fd,EV_READ);
155 if (timeout > 0.0) {
156 ev_timer_init(&client->ev_timeout, timeout_cb, timeout, 0.);
157 ev_timer_start(loop, &client->ev_timeout);
160 ev_io_start(loop,&client->ev_read);
163 /* {{{ mistral_functions[] */
164 zend_function_entry mistral_functions[] = {
165 PHP_FE(mistral_init, NULL)
166 PHP_FE(mistral_register_callback, NULL)
167 PHP_FE(mistral_start, NULL)
168 { NULL, NULL, NULL }
170 /* }}} */
172 /* {{{ mistral_module_entry
173 * */
174 zend_module_entry mistral_module_entry = {
175 STANDARD_MODULE_HEADER,
176 "mistral",
177 mistral_functions,
178 NULL, /* Replace with NULL if there is nothing to do at php startup */
179 PHP_MSHUTDOWN(mistral),
180 NULL, /* Replace with NULL if there is nothing to do at request start */
181 NULL, /* Replace with NULL if there is nothing to do at request end */
182 PHP_MINFO(mistral),
183 PHP_MISTRAL_VERSION,
184 STANDARD_MODULE_PROPERTIES
186 /* }}} */
188 #ifdef COMPILE_DL_MISTRAL
189 ZEND_GET_MODULE(mistral)
190 #endif
192 /* {{{ PHP_MINFO_FUNCTION */
193 PHP_MINFO_FUNCTION(mistral)
195 php_info_print_table_start();
196 php_info_print_table_header(2, "Mistral support", "enabled");
197 php_info_print_table_row(2, "Version", PHP_MISTRAL_VERSION);
198 php_info_print_table_end();
200 /* }}} */
202 /* {{{ PHP_MSHUTDOWN_FUNCTION */
203 PHP_MSHUTDOWN_FUNCTION (mistral)
205 _php_event_callback_free(callback);
207 /* }}} */
209 PHP_FUNCTION(mistral_init)
211 int listen_addrfam = AF_INET6;
212 int listen_sock = -1;
214 char *listen_addr;
215 int listen_addr_len;
216 long listen_port;
218 struct sockaddr_storage sin;
219 struct sockaddr_in * sa4 = (struct sockaddr_in *) &sin;
220 struct sockaddr_in6 * sa6 = (struct sockaddr_in6 *) &sin;
222 int reuseaddr_on = 1;
223 int keepalive_on = 1;
225 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sld", &listen_addr, &listen_addr_len, &listen_port, &timeout) == FAILURE) {
226 RETURN_NULL();
229 if (listen_port < 1 || listen_port > 65535) {
230 RETURN_NULL();
234 listen_sock = socket(listen_addrfam, SOCK_STREAM, 0);
235 if (listen_sock < 0) {
236 listen_addrfam = (listen_addrfam == AF_INET6 ? AF_INET : AF_INET6);
237 listen_sock = socket(listen_addrfam, SOCK_STREAM, 0);
238 if (listen_sock < 0) {
239 err(1, "listen failed");
243 if (timeout <= 0.0) {
244 timeout = 0.0;
245 } else {
246 if (setsockopt(listen_sock, SOL_SOCKET, SO_KEEPALIVE, &keepalive_on, sizeof(keepalive_on)) == -1)
247 err(1, "setsockopt failed");
250 if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_on, sizeof(reuseaddr_on)) == -1)
251 err(1, "setsockopt failed");
253 if (listen_addrfam == AF_INET6) {
254 int null = 0;
255 setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &null, sizeof(null));
258 memset(&sin, 0, sizeof(sin));
260 sin.ss_family = listen_addrfam;
261 switch (sin.ss_family) {
262 case AF_INET6:
263 inet_pton(sin.ss_family, listen_addr, &(sa6->sin6_addr));
264 sa6->sin6_port = htons(listen_port);
265 break;
266 case AF_INET:
267 if ( strchr(listen_addr, ':') )
268 /* Invalid IPv4-string. Use the wildcard address. */
269 listen_addr = strdup("0.0.0.0");
271 inet_pton(sin.ss_family, listen_addr, &(sa4->sin_addr));
272 sa4->sin_port = htons(listen_port);
275 if (bind(listen_sock, (struct sockaddr *) &sin, sizeof(sin)) < 0)
276 err(1, "bind failed");
278 if (listen(listen_sock, 5) < 0)
279 err(1, "listen failed");
281 if (setnonblock(listen_sock) < 0)
282 err(1, "failed to set server socket to non-blocking");
284 ev_io_init(&ev_accept, accept_cb, listen_sock, EV_READ);
287 /* {{{ proto bool event_set(resource event, resource fd, int events, mixed callback[, mixed arg])
288 * Taken from pecl::libevent event_set
290 PHP_FUNCTION(mistral_register_callback)
292 zval *zcallback, *zarg = NULL;
293 php_event_callback_t *new_callback, *old_callback;
294 char *func_name;
296 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zcallback) != SUCCESS) {
297 return;
300 if (!zend_is_callable(zcallback, 0, &func_name TSRMLS_CC)) {
301 php_error_docref(NULL TSRMLS_CC, E_WARNING, "'%s' is not a valid callback", func_name);
302 efree(func_name);
303 RETURN_FALSE;
305 efree(func_name);
307 zval_add_ref(&zcallback);
309 new_callback = emalloc(sizeof(php_event_callback_t));
310 new_callback->func = zcallback;
311 new_callback->arg = zarg;
313 old_callback = callback;
314 callback = new_callback;
316 if (old_callback) {
317 _php_event_callback_free(old_callback);
319 RETURN_TRUE;
321 /* }}} */
323 PHP_FUNCTION(mistral_start)
325 struct ev_loop *loop = ev_default_loop (0);
327 ev_io_start(loop, &ev_accept);
328 ev_loop(loop, 0);