「これはひどい」だ…orz。
my_commetd.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <alloca.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <ev.h> #define PORT 10080 #define MAX_BACKLOG 5 #define LINEBUFSIZE 1025 #define MAX_CONNS 1024 #define die(s) do { fprintf(stderr, "%s\n", (s)); exit(1); } while(0) #define die_with_err(s) do { perror((s)); exit(1); } while(0) #ifdef DEBUG #define debug(...) do { fprintf(stderr, __VA_ARGS__); } while(0) #else #define debug(...) do {} while(0) #endif struct wait_socket { struct ev_loop *loop; struct ev_io *w; FILE *fin; FILE *fout; }; struct wait_socket *wait_sockets[MAX_CONNS]; enum cmd_commet { CMD_HOOK = 0, CMD_SHOOT }; enum cmd_commet get_cmd(FILE *fin) { char buf[LINEBUFSIZE]; char *cmd; if (fgets(buf, LINEBUFSIZE, fin) == NULL) { return -1; } debug("request line: %s\n", buf); if ((cmd = strchr(buf, '?')) == NULL) { return CMD_HOOK; } cmd++; if (strncmp(cmd, "hook", 4) == 0) { return CMD_HOOK; } else if (strncmp(cmd, "shoot", 5) == 0) { return CMD_SHOOT; } else { return CMD_HOOK; } } int skip_header(FILE *fin) { char buf[LINEBUFSIZE]; int len = -1; while(1) { if (fgets(buf, LINEBUFSIZE, fin) == NULL) { break; } debug("header: %s\n", buf); if (strncasecmp(buf, "Content-Length:", 15) == 0) { len = atoi(buf + 15); } if (buf[0] == '\n' || strcmp(buf, "\r\n") == 0) { break; } } debug("content length: %d\n", len); return len; } char *read_body(FILE *fin, char *buf, int len) { if (fgets(buf, len, fin) == NULL) { return NULL; } debug("body: %s\n", buf); return buf; } void service_hook(struct ev_loop *loop, struct ev_io *w, FILE *fin, FILE *fout) { struct wait_socket *ws; debug("cmd: hook\n"); ws = malloc(sizeof(struct wait_socket)); ws->loop = loop; ws->w = w; ws->fin = fin; ws->fout = fout; wait_sockets[w->fd] = ws; } void service_shoot(FILE *fout, char *body) { int i; debug("cmd: shoot\n"); for (i = 0; i < MAX_CONNS; i++) { struct wait_socket *ws; if (wait_sockets[i] != NULL) { ws = wait_sockets[i]; ev_io_stop(ws->loop, ws->w); fprintf(ws->fout, "HTTP/1.1 200 OK\r\n"); fprintf(ws->fout, "Content-Type: text/plain\r\n\r\n"); fprintf(ws->fout, "%s", body); fclose(ws->fin); fclose(ws->fout); close(ws->w->fd); free(ws->w); free(ws); wait_sockets[i] = NULL; } } fprintf(fout, "HTTP/1.1 200 OK\r\n\r\n"); } void service_error(FILE *fout) { debug("cmd: error\n"); fprintf(fout, "HTTP/1.1 500 Server Error\r\n\r\n"); } void service(struct ev_loop *loop, struct ev_io *w, FILE *fin, FILE *fout, int *close_out) { char buf[LINEBUFSIZE]; enum cmd_commet cmd; int len = -1; char *body; cmd = get_cmd(fin); len = skip_header(fin); switch (cmd) { case CMD_HOOK: service_hook(loop, w, fin, fout); *close_out = 0; break; case CMD_SHOOT: body = alloca(len + 1); fgets(body, len + 1, fin); service_shoot(fout, body); *close_out = 1; break; default: service_error(fout); *close_out = 1; break; } } void on_readable(struct ev_loop *loop, struct ev_io *w, int revents) { FILE *fin, *fout; int close_out; if (wait_sockets[w->fd] == NULL) { fin = fdopen(w->fd, "r"); fout = fdopen(w->fd, "w"); setvbuf(fout, NULL, _IONBF, 0); } service(loop, w, fin, fout, &close_out); if (close_out) { fclose(fin); fclose(fout); close(w->fd); ev_io_stop(loop, w); free(w); } } void on_request(struct ev_loop *loop, struct ev_io *w, int revents) { int sock, flags; struct sockaddr_in addr; socklen_t len = sizeof(addr); struct ev_loop *l; ev_io *client_watcher; if ((sock = accept(w->fd, (struct sockaddr*) &addr, &len)) < 0) { if (EINTR == errno) { return; } die_with_err("accept(2)"); } debug("accepted.\n"); if ((flags = fcntl(sock, F_GETFL, 0)) < 0 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) { die_with_err("fcntl(2)"); } client_watcher = calloc(1, sizeof(ev_io)); l = w->data; ev_io_init(client_watcher, on_readable, sock, EV_READ); ev_io_start(l, client_watcher); } int main() { int sock, epfd, flags; struct sockaddr_in addr; struct ev_loop *loop; ev_io watcher; if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { die_with_err("socket(2)"); } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(PORT); if (bind(sock, (struct sockaddr*) &addr, sizeof(addr)) < 0) { die_with_err("bind(2)"); } if (listen(sock, MAX_BACKLOG) < 0) { die_with_err("listen(2)"); } if ((flags = fcntl(sock, F_GETFL, 0)) < 0 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) { die_with_err("fcntl(2)"); } memset(wait_sockets, 0, sizeof(wait_sockets)); loop = ev_default_loop(0); watcher.data = loop; ev_io_init(&watcher, on_request, sock, EV_READ); ev_io_start(loop, &watcher); ev_loop(loop, 0); close(sock); return 0; }
使い方
- http://hogehoge:10080/?hookでコネクションをフック
- http://hogehoge:10080/?shootで投げたpostデータを、ひっかけといたコネクションに返す
いろいろ
とりあえず、考えないといけないのは…
- ファイルディスクリプタの最大値がよく分からない
- struct ev_ioって任意の値を保持できなかったっけ?
- EV_Pをstruct ev_loop *loopに展開するのはまずいかも
- というかこのマクロはちょっと…
- デーモン化
- SIGPIPEとか無効にしないとまずそう
それはさておき、シングルスレッド楽だなぁ。