diff --git a/lib/monkey/include/monkey/mk_api.h b/lib/monkey/include/monkey/mk_api.h index 6458bded55f..de0cb011958 100644 --- a/lib/monkey/include/monkey/mk_api.h +++ b/lib/monkey/include/monkey/mk_api.h @@ -45,7 +45,7 @@ pthread_key_t MK_EXPORT _mkp_data; #ifdef TRACE #define PLUGIN_TRACE(...) \ - mk_api->trace("", \ + mk_api->trace("plug", \ MK_TRACE_PLUGIN, \ __FUNCTION__, \ __FILE__, \ diff --git a/lib/monkey/include/monkey/mk_http.h b/lib/monkey/include/monkey/mk_http.h index 5a409ac624f..a60f9ec1ba4 100644 --- a/lib/monkey/include/monkey/mk_http.h +++ b/lib/monkey/include/monkey/mk_http.h @@ -120,6 +120,7 @@ struct mk_http_session int close_now; /* Close the session ASAP */ struct mk_channel *channel; + struct mk_sched_conn *conn; unsigned int body_size; unsigned int body_length; @@ -198,6 +199,7 @@ void mk_http_request_init(struct mk_http_session *session, struct mk_http_request *request); struct mk_http_header *mk_http_header_get(int name, struct mk_http_request *req, const char *key, unsigned int len); +int mk_http_request_end(struct mk_http_session *cs); #define mk_http_session_get(conn) \ (struct mk_http_session *) \ diff --git a/lib/monkey/include/monkey/mk_http_internal.h b/lib/monkey/include/monkey/mk_http_internal.h index 70e0cb96a91..89f59569cc7 100644 --- a/lib/monkey/include/monkey/mk_http_internal.h +++ b/lib/monkey/include/monkey/mk_http_internal.h @@ -22,6 +22,8 @@ #include +#define MK_HEADER_ETAG_SIZE 32 + struct response_headers { int status; @@ -55,6 +57,9 @@ struct response_headers mk_ptr_t content_encoding; char *location; + int etag_len; + char etag_buf[MK_HEADER_ETAG_SIZE]; + /* * This field allow plugins to add their own response * headers @@ -140,6 +145,13 @@ struct mk_http_request */ int stage30_blocked; + /* + * If the connection is being managed by a plugin (e.g: CGI), associate the + * plugin reference to the stage30_handler field. This is useful to handle + * protocol exception and notify the handlers about it. + */ + void *stage30_handler; + /* Static file information */ struct file_info file_info; diff --git a/lib/monkey/include/monkey/mk_plugin.h b/lib/monkey/include/monkey/mk_plugin.h index ece8fbd3d3e..52316caf0f2 100644 --- a/lib/monkey/include/monkey/mk_plugin.h +++ b/lib/monkey/include/monkey/mk_plugin.h @@ -107,7 +107,7 @@ struct plugin_api void (*_error) (int, const char *, ...) PRINTF_WARNINGS(2,3); /* HTTP request function */ - int (*http_session_end) (struct mk_http_session *session); + int (*http_request_end) (struct mk_http_session *cs, int close); int (*http_request_error) (int, struct mk_http_session *, struct mk_http_request *); /* memory functions */ @@ -243,20 +243,7 @@ struct mk_plugin_event struct mk_plugin *handler; /* plugin owner/handler */ }; -struct mk_plugin_stage { - int (*stage10) (int); - int (*stage20) (struct mk_http_session *, struct mk_http_request *); - int (*stage30) (struct mk_plugin *, struct mk_http_session *, - struct mk_http_request *); - int (*stage40) (struct mk_http_session *, struct mk_http_request *); - int (*stage50) (int); - - /* Just a reference to the parent plugin */ - struct mk_plugin *plugin; - - /* Only used when doing direct mapping from config->stageN_handler; */ - struct mk_list _head; -}; +struct mk_plugin_stage; /* Info: used to register a plugin */ struct mk_plugin { @@ -291,6 +278,23 @@ struct mk_plugin { int load_type; }; +struct mk_plugin_stage { + int (*stage10) (int); + int (*stage20) (struct mk_http_session *, struct mk_http_request *); + int (*stage30) (struct mk_plugin *, struct mk_http_session *, + struct mk_http_request *); + int (*stage30_hangup) (struct mk_plugin *, struct mk_http_session *, + struct mk_http_request *); + int (*stage40) (struct mk_http_session *, struct mk_http_request *); + int (*stage50) (int); + + /* Just a reference to the parent plugin */ + struct mk_plugin *plugin; + + /* Only used when doing direct mapping from config->stageN_handler; */ + struct mk_list _head; +}; + void mk_plugin_api_init(); void mk_plugin_load_all(); @@ -321,7 +325,7 @@ int mk_plugin_event_socket_change_mode(int socket, int mode, unsigned int behavi struct mk_plugin *mk_plugin_load(int type, const char *shortname, void *data); void *mk_plugin_load_symbol(void *handler, const char *symbol); -int mk_plugin_http_session_end(struct mk_http_session *cs); +int mk_plugin_http_request_end(struct mk_http_session *cs, int close); /* Register functions */ struct plugin *mk_plugin_register(struct plugin *p); diff --git a/lib/monkey/include/monkey/mk_plugin_stage.h b/lib/monkey/include/monkey/mk_plugin_stage.h index b8d3f4bc123..22c621189c6 100644 --- a/lib/monkey/include/monkey/mk_plugin_stage.h +++ b/lib/monkey/include/monkey/mk_plugin_stage.h @@ -60,7 +60,8 @@ static inline int mk_plugin_stage_run_20(struct mk_http_session *cs, } static inline int mk_plugin_stage_run_30(struct mk_http_session *cs, - struct mk_http_request *sr) + struct mk_http_request *sr, + struct mk_plugin **handler) { int ret; struct mk_list *head; @@ -77,6 +78,7 @@ static inline int mk_plugin_stage_run_30(struct mk_http_session *cs, return ret; case MK_PLUGIN_RET_CLOSE_CONX: case MK_PLUGIN_RET_CONTINUE: + *handler = stage->plugin; return ret; default: mk_err("Plugin returns invalid value %i", ret); diff --git a/lib/monkey/man/monkey.1 b/lib/monkey/man/monkey.1 index 996ae1c50ca..cabda22b9da 100644 --- a/lib/monkey/man/monkey.1 +++ b/lib/monkey/man/monkey.1 @@ -1,30 +1,108 @@ -.TH Monkey 1 "Feb 5, 2011" +.TH Monkey 1 "Jul 01, 2015" .\" Please update the above date whenever this man page is modified. .SH NAME monkey \- Monkey HTTP Server .SH SYNOPSIS .B monkey [options] .SH DESCRIPTION -\fBmonkey\fP is a very small, fast and scalable web server for Linux. -This manual page only lists the command line arguments. For further and more detailed information regarding the program configuration see the Monkey manual, found at http://monkey-project.com/documentation . +\fBmonkey\fP is fast and scalable web server for Linux, OSX and FreeBSD. +This manual page only lists the command line arguments available. For further and +more detailed information regarding the program configuration see the Monkey manual, +found at http://monkey-project.com/documentation . .SH OPTIONS This command will launch the Monkey webserver. It accepts the following options: .TP 8 -.B \-h, --help -Prints a brief help message and Perform a sanity check on the configuration file. The server will not run. +.B \-c, --configdir=DIR +Allows to specify an optional configuration directory where the required +configuration files by Monkey are located. It's expected that under this directory +exists the files: monkey.conf, monkey.mime, plugins.load and the plugins/ directory. .TP 8 -.B \-v, --version -Prints Monkey's version and exit + +.B \-s, --serverconf=FILE +Overrides the default path of the 'monkey.conf' configuration file. .TP 8 -.B \-D, --daemon + +.B \-d, --daemon Launches the server as a daemon (background process). The default behaviour is to stay attached to the controlling terminal. .TP 8 -.B \-c, --configdir directory -Specifies the configuration files directory + +.B \-I, --pid-file=FILE +Overrides the default path for the 'pid' file. +.TP 8 + +.B \-p, --port=PORT +Specify a single listener TCP port, this option overrides the content of the +configuration file. +.TP 8 + +.B \-o, --one-shot=DIR +The one-shot mode and directory path associated, makes to serve that directory +content as the document root of the default virtual host. +.TP 8 + +.B \-t, --transport=TRANSPORT +Specify a transport layer plugin. This option overrides the configuration file +and allows just one plugin, either 'liana' for plain sockets or 'tls' for SSL/TLS +support (just if 'tls' plugin was compiled and available). +.TP 8 + +.B \-w, --workers=N +Specify the number of worker threads that Monkey will spawn upon start. Each worker +thread is capable to handle several connections, is suggested to spawn a worker per +CPU core available. By default Monkey set this value to zero (--workers=0) which +let's Monkey core to detect the number of CPU core available and spawn one worker +per core. +.TP 8 + +.B \-m, --mimes-conf-file=FILE +Specify a configuration file that holds the mime types available. +.TP 8 + +.B \-l, --plugins-load-conf-file=FILE +Specify a configuration file that holds the list of dynamic plugins to load. +.TP 8 + +.B \-S, --sites-conf-dir=DIR +Specify the directory path where the files that defines the virtual hosts are +available. +.TP 8 + +.B \-P, --plugins-conf-dir=DIR +Specify the directory path where the plugins can locate their configuration files. +.TP 8 + +.B \-b, --balancing-mode +This option enable the OLD balancing mode when receiving connections. It means that +for every TCP connection that arrives, the Monkey Scheduler will assign the +connection to the least-busy worker thread. + +The default and new mechanism is based on the shared TCP sockets implementation, +which let the OS Kernel to decide to which worker thread assign the new connection. +.TP 8 + +.B \-T, --allow-shared-sockets +When using shared TCP sockets (no --balancing-mode), multiple instances of Monkey +can be started. Monkey will detect if the TCP port is in use by another process, +despite the address can be shared, Monkey will notify this and abort. This option +let Monkey know that despites the address is shared and in-use, continue anyways. +.TP 8 + +.B \-b, --build +Print build information and exit. It prints to the standard output the configure +options, default paths, built-in plugins and others. +.TP 8 + +.B \-v, --version +Prints Monkey's version and exit. +.TP 8 + +.B \-h, --help +Prints this help. +.TP 8 .SH SIGNALS The following signals are supported by Monkey: @@ -43,11 +121,7 @@ The following signals are supported by Monkey: .SS Bug reports In general, send bug reports to the bug report mailing list at . You are requested to use a descriptive subject for your email message, perhaps parts of the error message. -.SH "SEE ALSO" -Monkey can be run either with this command or -\&\fIbanana\fR\|(1). - .SH AUTHOR -Eduardo Silva and the rest of the Monkey Project team. +Eduardo Silva and the rest of the Monkey Project team. .PP This manpage is maintained by the Monkey HTTP Daemon Project team. diff --git a/lib/monkey/mk_core/mk_event_epoll.c b/lib/monkey/mk_core/mk_event_epoll.c index fdfc1223518..f8cb4119412 100644 --- a/lib/monkey/mk_core/mk_event_epoll.c +++ b/lib/monkey/mk_core/mk_event_epoll.c @@ -128,7 +128,9 @@ static inline int _mk_event_del(struct mk_event_ctx *ctx, struct mk_event *event MK_TRACE("[FD %i] Epoll, remove from QUEUE_FD=%i, ret=%i", event->fd, ctx->efd, ret); if (ret < 0) { - mk_libc_error("epoll_ctl"); +#ifdef TRACE + mk_libc_warn("epoll_ctl"); +#endif } return ret; diff --git a/lib/monkey/mk_server/mk_header.c b/lib/monkey/mk_server/mk_header.c index 393a3c44f75..cc1aa42cb4e 100644 --- a/lib/monkey/mk_server/mk_header.c +++ b/lib/monkey/mk_server/mk_header.c @@ -284,6 +284,11 @@ int mk_header_prepare(struct mk_http_session *cs, } } + /* E-Tag */ + if (sh->etag_len > 0) { + mk_iov_add(iov, sh->etag_buf, sh->etag_len, MK_FALSE); + } + /* Content-Encoding */ if (sh->content_encoding.len > 0) { mk_iov_add(iov, mk_header_content_encoding.data, diff --git a/lib/monkey/mk_server/mk_http.c b/lib/monkey/mk_server/mk_http.c index 542ddce7cfe..7251cdf47fd 100644 --- a/lib/monkey/mk_server/mk_http.c +++ b/lib/monkey/mk_server/mk_http.c @@ -275,6 +275,10 @@ int mk_http_handler_read(struct mk_sched_conn *conn, struct mk_http_session *cs) int total_bytes = 0; char *tmp = 0; +#ifdef TRACE + int socket = conn->event.fd; +#endif + MK_TRACE("MAX REQUEST SIZE: %i", mk_config->max_request_size); try_pending: @@ -299,11 +303,11 @@ int mk_http_handler_read(struct mk_sched_conn *conn, struct mk_http_session *cs) cs->body_size = new_size; memcpy(cs->body, cs->body_fixed, cs->body_length); MK_TRACE("[FD %i] New size: %i, length: %i", - cs->socket, new_size, cs->body_length); + socket, new_size, cs->body_length); } else { MK_TRACE("[FD %i] Realloc from %i to %i", - cs->socket, cs->body_size, new_size); + socket, cs->body_size, new_size); tmp = mk_mem_realloc(cs->body, new_size + 1); if (tmp) { cs->body = tmp; @@ -321,40 +325,28 @@ int mk_http_handler_read(struct mk_sched_conn *conn, struct mk_http_session *cs) bytes = mk_sched_conn_read(conn, cs->body + cs->body_length, max_read); MK_TRACE("[FD %i] read %i", socket, bytes); - - if (bytes < 0) { - mk_http_session_remove(cs); + if (bytes <= 0) { return -1; } - if (bytes == 0) { - return -1; - } - - if (bytes > 0) { - if (bytes > max_read) { - MK_TRACE("[FD %i] Buffer still have data: %i", - cs->socket, bytes - max_read); - - cs->body_length += max_read; - cs->body[cs->body_length] = '\0'; - total_bytes += max_read; - - goto try_pending; - } - else { - cs->body_length += bytes; - cs->body[cs->body_length] = '\0'; + if (bytes > max_read) { + MK_TRACE("[FD %i] Buffer still have data: %i", + socket, bytes - max_read); + cs->body_length += max_read; + cs->body[cs->body_length] = '\0'; + total_bytes += max_read; - total_bytes += bytes; - } + goto try_pending; + } + else { + cs->body_length += bytes; + cs->body[cs->body_length] = '\0'; - MK_TRACE("[FD %i] Retry total bytes: %i", - cs->socket, total_bytes); - return total_bytes; + total_bytes += bytes; } - return bytes; + MK_TRACE("[FD %i] Retry total bytes: %i", socket, total_bytes); + return total_bytes; } /* Build error page */ @@ -700,6 +692,7 @@ int mk_http_init(struct mk_http_session *cs, struct mk_http_request *sr) { int ret; struct mimetype *mime; + struct mk_plugin *handler = NULL; MK_TRACE("[FD %i] HTTP Protocol Init, session %p", cs->socket, sr); @@ -760,7 +753,7 @@ int mk_http_init(struct mk_http_session *cs, struct mk_http_request *sr) * check if some plugin would like to handle it */ MK_TRACE("No file, look for handler plugin"); - ret = mk_plugin_stage_run_30(cs, sr); + ret = mk_plugin_stage_run_30(cs, sr, &handler); if (ret == MK_PLUGIN_RET_CLOSE_CONX) { if (sr->headers.status > 0) { return mk_http_error(sr->headers.status, cs, sr); @@ -838,10 +831,11 @@ int mk_http_init(struct mk_http_session *cs, struct mk_http_request *sr) /* Plugin Stage 30: look for handlers for this request */ if (sr->stage30_blocked == MK_FALSE) { - ret = mk_plugin_stage_run_30(cs, sr); + ret = mk_plugin_stage_run_30(cs, sr, &handler); MK_TRACE("[FD %i] STAGE_30 returned %i", cs->socket, ret); switch (ret) { case MK_PLUGIN_RET_CONTINUE: + sr->stage30_handler = handler; return MK_PLUGIN_RET_CONTINUE; case MK_PLUGIN_RET_CLOSE_CONX: if (sr->headers.status > 0) { @@ -911,7 +905,13 @@ int mk_http_init(struct mk_http_session *cs, struct mk_http_request *sr) return mk_http_error(MK_CLIENT_NOT_FOUND, cs, sr); } + /* Configure some headers */ sr->headers.last_modified = sr->file_info.last_modification; + sr->headers.etag_len = snprintf(sr->headers.etag_buf, + MK_HEADER_ETAG_SIZE, + "ETag: \"%x-%zx\"\r\n", + (unsigned int) sr->file_info.last_modification, + sr->file_info.size); if (sr->if_modified_since.data && sr->method == MK_METHOD_GET) { time_t date_client; /* Date sent by client */ @@ -1060,24 +1060,15 @@ static inline void mk_http_request_ka_next(struct mk_http_session *cs) mk_http_parser_init(&cs->parser); } -static inline int mk_http_request_end(struct mk_sched_conn *conn, - struct mk_sched_worker *sched) +int mk_http_request_end(struct mk_http_session *cs) { - struct mk_http_session *cs; struct mk_http_request *sr; - (void) sched; - - cs = mk_http_session_get(conn); - if (!cs) { - MK_TRACE("[FD %i] Not found", conn->event.fd); - return -1; - } /* Check if we have some enqueued pipeline requests */ if (cs->pipelined == MK_TRUE) { sr = mk_list_entry_first(&cs->request_list, struct mk_http_request, _head); MK_TRACE("[FD %i] Pipeline finishing %p", - conn->event.fd, sr); + cs->conn->event.fd, sr); /* Remove node and release resources */ mk_list_del(&sr->_head); @@ -1085,8 +1076,9 @@ static inline int mk_http_request_end(struct mk_sched_conn *conn, if (mk_list_is_empty(&cs->request_list) != 0) { #ifdef TRACE - sr = mk_list_entry_first(&cs->request_list, struct mk_http_request, _head); - MK_TRACE("[FD %i] Pipeline next is %p", conn->event.fd, sr); + sr = mk_list_entry_first(&cs->request_list, + struct mk_http_request, _head); + MK_TRACE("[FD %i] Pipeline next is %p", cs->conn->event.fd, sr); #endif return 0; } @@ -1099,14 +1091,14 @@ static inline int mk_http_request_end(struct mk_sched_conn *conn, * close it. */ if (cs->close_now == MK_TRUE) { - MK_TRACE("[FD %i] No KeepAlive mode, remove", conn->event.fd); + MK_TRACE("[FD %i] No KeepAlive mode, remove", cs->conn->event.fd); mk_http_session_remove(cs); return -1; } else { mk_http_request_free_list(cs); mk_http_request_ka_next(cs); - mk_sched_conn_timeout_add(conn, sched); + mk_sched_conn_timeout_add(cs->conn, mk_sched_get_thread_conf()); return 0; } @@ -1266,12 +1258,26 @@ int mk_http_error(int http_status, struct mk_http_session *cs, */ void mk_http_session_remove(struct mk_http_session *cs) { - MK_TRACE("[FD %i] HTTP Session remove", cs->socket); + struct mk_list *tmp; + struct mk_list *head; + struct mk_plugin *handler; + struct mk_http_request *sr; + MK_TRACE("[FD %i] HTTP Session remove", cs->socket); if (cs->_sched_init == MK_FALSE) { return; } + /* On session remove, make sure to cleanup any handler */ + mk_list_foreach_safe(head, tmp, &cs->request_list) { + sr = mk_list_entry(head, struct mk_http_request, _head); + if (sr->stage30_handler) { + MK_TRACE("Hangup stage30 handler"); + handler = sr->stage30_handler; + handler->stage->stage30_hangup(handler, cs, sr); + } + } + if (cs->body != cs->body_fixed) { mk_mem_free(cs->body); } @@ -1315,6 +1321,9 @@ int mk_http_session_init(struct mk_http_session *cs, struct mk_sched_conn *conn) /* Map the channel, just for protocol-handler internal stuff */ cs->channel = &conn->channel; + /* Map the connection instance, required to handle exceptions */ + cs->conn = conn; + /* creation time in unix time */ cs->init_time = conn->arrive_time; @@ -1367,7 +1376,6 @@ void mk_http_request_free_list(struct mk_http_session *cs) /* sr = last node */ MK_TRACE("[FD %i] Free struct client_session", cs->socket); - mk_list_foreach_safe(sr_head, temp, &cs->request_list) { sr_node = mk_list_entry(sr_head, struct mk_http_request, _head); mk_list_del(sr_head); @@ -1427,6 +1435,10 @@ int mk_http_sched_read(struct mk_sched_conn *conn, struct mk_http_session *cs; struct mk_http_request *sr; +#ifdef TRACE + int socket = conn->event.fd; +#endif + cs = mk_http_session_get(conn); if (cs->_sched_init == MK_FALSE) { /* Create session for the client */ @@ -1474,6 +1486,7 @@ int mk_http_sched_read(struct mk_sched_conn *conn, return ret; } +/* The scheduler got a connection close event from the remote client */ int mk_http_sched_close(struct mk_sched_conn *conn, struct mk_sched_worker *sched, int type) @@ -1486,16 +1499,21 @@ int mk_http_sched_close(struct mk_sched_conn *conn, #else (void) type; #endif + + /* Release resources of the requests and session */ cs = mk_http_session_get(conn); mk_http_session_remove(cs); - return 0; } int mk_http_sched_done(struct mk_sched_conn *conn, struct mk_sched_worker *worker) { - return mk_http_request_end(conn, worker); + (void) worker; + struct mk_http_session *cs; + + cs = mk_http_session_get(conn); + return mk_http_request_end(cs); } struct mk_sched_handler mk_http_handler = { diff --git a/lib/monkey/mk_server/mk_plugin.c b/lib/monkey/mk_server/mk_plugin.c index 3f3e70db0e9..f62d1e4a6e1 100644 --- a/lib/monkey/mk_server/mk_plugin.c +++ b/lib/monkey/mk_server/mk_plugin.c @@ -222,7 +222,7 @@ void mk_plugin_api_init() api->_error = mk_print; /* HTTP callbacks */ - api->http_session_end = mk_plugin_http_session_end; + api->http_request_end = mk_plugin_http_request_end; // api->http_request_error = mk_http_error; /* Memory callbacks */ @@ -544,7 +544,7 @@ void mk_plugin_preworker_calls() } } -int mk_plugin_http_session_end(struct mk_http_session *cs) +int mk_plugin_http_request_end(struct mk_http_session *cs, int close) { int ret; int con; @@ -552,20 +552,25 @@ int mk_plugin_http_session_end(struct mk_http_session *cs) MK_TRACE("[FD %i] PLUGIN HTTP REQUEST END", cs->socket); - if (!mk_list_is_empty(&cs->request_list)) { - mk_err("[FD %i] Tried to end non-existing request.", cs->socket); + if (mk_list_is_empty(&cs->request_list) != 0) { + MK_TRACE("[FD %i] Tried to end non-existing request.", cs->socket); + cs->status = MK_REQUEST_STATUS_INCOMPLETE; return -1; } sr = mk_list_entry_last(&cs->request_list, struct mk_http_request, _head); mk_plugin_stage_run_40(cs, sr); - ret = 0; /* FIXME */ - //ret = mk_http_request_end(NULL, NULL); - MK_TRACE(" ret = %i", ret); + if (close == MK_TRUE) { + cs->close_now = MK_TRUE; + } + /* Let's check if we should ask to finalize the connection or not */ + ret = mk_http_request_end(cs); + MK_TRACE("[FD %i] HTTP session end = %i", cs->socket, ret); if (ret < 0) { - con = mk_sched_event_close(NULL, NULL, MK_EP_SOCKET_CLOSED); + con = mk_sched_event_close(cs->conn, mk_sched_get_thread_conf(), + MK_EP_SOCKET_CLOSED); if (con != 0) { return con; } diff --git a/lib/monkey/mk_server/mk_scheduler.c b/lib/monkey/mk_server/mk_scheduler.c index b5c018652b2..621b8fe875e 100644 --- a/lib/monkey/mk_server/mk_scheduler.c +++ b/lib/monkey/mk_server/mk_scheduler.c @@ -446,7 +446,6 @@ int mk_sched_remove_client(struct mk_sched_conn *conn, MK_TRACE("[FD %i] Scheduler remove", event->fd); mk_event_del(sched->loop, event); - /* Invoke plugins in stage 50 */ mk_plugin_stage_run_50(event->fd); @@ -456,7 +455,7 @@ int mk_sched_remove_client(struct mk_sched_conn *conn, rb_erase(&conn->_rb_head, &sched->rb_queue); mk_sched_conn_timeout_del(conn); - /* Close - network layer */ + /* Close at network layer level */ conn->net->close(event->fd); /* Release and return */ @@ -546,7 +545,7 @@ int mk_sched_event_read(struct mk_sched_conn *conn, struct mk_sched_worker *sched) { int ret = 0; - size_t count; + size_t count = 0; size_t total = 0; struct mk_event *event; diff --git a/lib/monkey/mk_server/mk_server.c b/lib/monkey/mk_server/mk_server.c index e1502928379..384f061e3d9 100644 --- a/lib/monkey/mk_server/mk_server.c +++ b/lib/monkey/mk_server/mk_server.c @@ -387,23 +387,25 @@ void mk_server_worker_loop() conn = (struct mk_sched_conn *) event; if (event->mask & MK_EVENT_WRITE) { - MK_TRACE("[FD %i] EPoll Event WRITE", event->fd); + MK_TRACE("[FD %i] Event WRITE", event->fd); ret = mk_sched_event_write(conn, sched); //printf("event write ret=%i\n", ret); } if (event->mask & MK_EVENT_READ) { + MK_TRACE("[FD %i] Event READ", event->fd); ret = mk_sched_event_read(conn, sched); } - if (event->mask & MK_EVENT_CLOSE) { + if (event->mask & MK_EVENT_CLOSE && ret != -1) { + MK_TRACE("[FD %i] Event CLOSE", event->fd); ret = -1; } if (ret < 0) { - MK_TRACE("[FD %i] Epoll Event FORCE CLOSE | ret = %i", + MK_TRACE("[FD %i] Event FORCE CLOSE | ret = %i", event->fd, ret); mk_sched_event_close(conn, sched, MK_EP_SOCKET_CLOSED); } diff --git a/lib/monkey/mk_server/mk_stream.c b/lib/monkey/mk_server/mk_stream.c index a527ce247e7..7b1b37e1c7d 100644 --- a/lib/monkey/mk_server/mk_stream.c +++ b/lib/monkey/mk_server/mk_stream.c @@ -105,7 +105,7 @@ static inline void mk_copybuf_consume(struct mk_stream *stream, size_t bytes) int mk_channel_flush(struct mk_channel *channel) { int ret = 0; - size_t count; + size_t count = 0; size_t total = 0; do { diff --git a/lib/monkey/plugins/cgi/cgi.c b/lib/monkey/plugins/cgi/cgi.c index 9e2055885c7..312fdb4a72c 100644 --- a/lib/monkey/plugins/cgi/cgi.c +++ b/lib/monkey/plugins/cgi/cgi.c @@ -24,6 +24,43 @@ #include #include +void cgi_finish(struct cgi_request *r) +{ +#ifdef TRACE + if (r->active == MK_TRUE) { + if (mk_list_is_empty(&r->cs->request_list) != 0 ) { + PLUGIN_TRACE("CGI Finish / request_list is empty"); + } + PLUGIN_TRACE("CGI Finish / session_fd=%i child_fd=%i child_pid=%l", + r->cs->socket, r->fd, r->child); + } +#endif + + /* + * Unregister & close the CGI child process pipe reader fd from the + * thread event loop, otherwise we may get unexpected notifications. + */ + mk_api->ev_del(mk_api->sched_loop(), (struct mk_event *) r); + close(r->fd); + if (r->chunked && r->active) { + channel_write(r->sr->session, "0\r\n\r\n", 5); + } + + /* Try to kill any child process */ + if (r->child > 0) { + kill(r->child, SIGKILL); + r->child = 0; + } + + /* Invalidte our socket handler */ + requests_by_socket[r->socket] = NULL; + + if (r->active) { + mk_api->http_request_end(r->cs, r->hangup); + } + cgi_req_del(r); +} + int swrite(const int fd, const void *buf, const size_t count) { ssize_t pos = count, ret = 0; @@ -262,11 +299,23 @@ static int do_cgi(const char *const __restrict__ file, if (!r) { return 403; } + r->child = pid; + + /* + * Hang up?: by default Monkey assumes the CGI scripts generate + * content dynamically (no Content-Length header), so for such HTTP/1.0 + * clients we should close the connection as KeepAlive is not supported + * by specification, only on HTTP/1.1 where the Chunked Transfer encoding + * exists. + */ + if (r->sr->protocol >= MK_HTTP_PROTOCOL_11) { + r->hangup = MK_FALSE; + } + /* Set transfer encoding */ if (r->sr->protocol >= MK_HTTP_PROTOCOL_11 && (r->sr->headers.status < MK_REDIR_MULTIPLE || - r->sr->headers.status > MK_REDIR_USE_PROXY)) - { + r->sr->headers.status > MK_REDIR_USE_PROXY)) { r->sr->headers.transfer_encoding = MK_HEADER_TE_TYPE_CHUNKED; r->chunked = 1; } @@ -436,13 +485,20 @@ static void cgi_read_config(const char * const path) int mk_cgi_plugin_init(struct plugin_api **api, char *confdir) { - mk_api = *api; + struct rlimit lim; + mk_api = *api; mk_list_init(&cgi_global_matches); + + /* Read configuration files */ cgi_read_config(confdir); pthread_key_create(&cgi_request_list, NULL); - struct rlimit lim; + /* + * We try to perform some quick lookup over the list of CGI + * instances. We do this with a fixed length array, if you use CGI + * you don't care too much about performance anyways. + */ getrlimit(RLIMIT_NOFILE, &lim); requests_by_socket = mk_api->mem_alloc_z(sizeof(struct cgi_request *) * lim.rlim_cur); @@ -521,7 +577,7 @@ int mk_cgi_stage30(struct mk_plugin *plugin, run_cgi: /* start running the CGI */ if (cgi_req_get(cs->socket)) { - printf("Error, someone tried to retry\n"); + PLUGIN_TRACE("Error, someone tried to retry\n"); return MK_PLUGIN_RET_CONTINUE; } @@ -537,6 +593,32 @@ int mk_cgi_stage30(struct mk_plugin *plugin, return MK_PLUGIN_RET_CONTINUE; } +/* + * Invoked everytime a remote client drop the active connection, this + * callback is triggered by the Monkey Scheduler + */ +int mk_cgi_stage30_hangup(struct mk_plugin *plugin, + struct mk_http_session *cs, + struct mk_http_request *sr) +{ + struct cgi_request *r; + (void) plugin; + + r = requests_by_socket[cs->socket]; + if (!r) { + return -1; + } + + cgi_finish(r); + + /* + * FIXME: do we need this here ?, at some point we may need + * to invalidate the handler + */ + sr->stage30_handler = NULL; + return 0; +} + void mk_cgi_worker_init() { struct mk_list *list = mk_api->mem_alloc_z(sizeof(struct mk_list)); @@ -547,7 +629,8 @@ void mk_cgi_worker_init() struct mk_plugin_stage mk_plugin_stage_cgi = { - .stage30 = &mk_cgi_stage30 + .stage30 = &mk_cgi_stage30, + .stage30_hangup = &mk_cgi_stage30_hangup }; struct mk_plugin mk_plugin_cgi = { diff --git a/lib/monkey/plugins/cgi/cgi.h b/lib/monkey/plugins/cgi/cgi.h index 4796694b988..263058e6550 100644 --- a/lib/monkey/plugins/cgi/cgi.h +++ b/lib/monkey/plugins/cgi/cgi.h @@ -74,9 +74,11 @@ struct cgi_request { unsigned int in_len; - int fd; /* From the CGI app */ - int socket; - + int fd; /* Pipe the CGI proc */ + int socket; /* Client connection */ + int hangup; /* Should close connection when done ? */ + int active; /* Active session ? */ + pid_t child; /* child process ID */ unsigned char status_done; unsigned char all_headers_done; unsigned char chunked; @@ -87,6 +89,8 @@ pthread_key_t cgi_request_list; extern struct cgi_request **requests_by_socket; +void cgi_finish(struct cgi_request *r); + int swrite(const int fd, const void *buf, const size_t count); int channel_write(struct mk_http_session *session, void *buf, size_t count); diff --git a/lib/monkey/plugins/cgi/event.c b/lib/monkey/plugins/cgi/event.c index 8c0eab3fb14..59cf3f6f1ad 100644 --- a/lib/monkey/plugins/cgi/event.c +++ b/lib/monkey/plugins/cgi/event.c @@ -2,7 +2,8 @@ /* Monkey HTTP Server * ================== - * Copyright (C) 2012, Lauri Kasanen + * Copyright 2001-2015 Monkey Software LLC + * Copyright (C) 2012-2013, Lauri Kasanen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,18 +20,19 @@ #include "cgi.h" -/* Get the earliest break between headers and content. - - The reason for this function is that some CGI apps - use LFLF and some use CRLFCRLF. +/* + * The reason for this function is that some CGI apps + * + * use LFLF and some use CRLFCRLF. + * + * If that app then sends content that has the other break + * in the beginning, monkey can accidentally send part of the + * content as headers. + */ - If that app then sends content that has the other break - in the beginning, monkey can accidentally send part of the - content as headers. -*/ static char *getearliestbreak(const char buf[], const unsigned bufsize, - unsigned char * const advance) { - + unsigned char * const advance) + { char * const crend = memmem(buf, bufsize, MK_IOV_CRLFCRLF, sizeof(MK_IOV_CRLFCRLF) - 1); char * const lfend = memmem(buf, bufsize, MK_IOV_LFLF, @@ -55,22 +57,6 @@ static char *getearliestbreak(const char buf[], const unsigned bufsize, return crend; } -static void cgi_done(struct cgi_request *r) -{ - mk_api->ev_del(mk_api->sched_loop(), (struct mk_event *) r); - if (r->chunked) { - channel_write(r->sr->session, "0\r\n\r\n", 5); - } - - /* XXX Fixme: this needs to be atomic */ - requests_by_socket[r->socket] = NULL; - - /* Note: Must make sure we ignore the close event caused by this line */ - mk_api->http_session_end(r->cs); - //mk_api->socket_close(r->fd); - cgi_req_del(r); -} - int process_cgi_data(struct cgi_request *r) { int ret; @@ -121,8 +107,7 @@ int process_cgi_data(struct cgi_request *r) /* Write the rest of the headers without chunking */ end = getearliestbreak(outptr, r->in_len, &advance); if (!end) { - channel_write(r->cs, outptr, r->in_len); - r->in_len = 0; + /* Let's return until we have the headers break */ return MK_PLUGIN_RET_EVENT_OWNED; } end += advance; @@ -149,7 +134,6 @@ int process_cgi_data(struct cgi_request *r) if (ret < 0) { return MK_PLUGIN_RET_EVENT_CLOSE; } - r->in_len = 0; if (r->chunked) { channel_write(r->sr->session, MK_CRLF, 2); @@ -162,14 +146,27 @@ int cb_cgi_read(void *data) int n; struct cgi_request *r = data; + if ((BUFLEN - r->in_len) < 1) { + PLUGIN_TRACE("CLOSE BY SIZE"); + cgi_finish(r); + return -1; + } + /* Read data from the CGI process */ - n = read(r->fd, r->in_buf, BUFLEN); - PLUGIN_TRACE("CGI returned %lu bytes", n); + if (r->active == MK_FALSE) { + cgi_finish(r); + return -1; + } + + n = read(r->fd, r->in_buf + r->in_len, BUFLEN - r->in_len); + PLUGIN_TRACE("CGI returned %d bytes (parent CS_FD=%i)", n, + r->cs->socket); if (n <= 0) { - cgi_done(r); + /* It most of cases this means the child process finished */ + cgi_finish(r); return MK_PLUGIN_RET_EVENT_CLOSE; } - r->in_len = n; + r->in_len += n; process_cgi_data(r); return 0; diff --git a/lib/monkey/plugins/cgi/request.c b/lib/monkey/plugins/cgi/request.c index c74a8af3583..87a963cdd99 100644 --- a/lib/monkey/plugins/cgi/request.c +++ b/lib/monkey/plugins/cgi/request.c @@ -24,29 +24,43 @@ struct cgi_request *cgi_req_create(int fd, int socket, struct mk_http_request *sr, struct mk_http_session *cs) { - struct cgi_request *newcgi = mk_api->mem_alloc_z(sizeof(struct cgi_request)); - if (!newcgi) return NULL; + struct cgi_request *cgi; - newcgi->fd = fd; - newcgi->socket = socket; - newcgi->sr = sr; - newcgi->cs = cs; + cgi = mk_api->mem_alloc_z(sizeof(struct cgi_request)); + if (!cgi) { + return NULL; + } - return newcgi; + cgi->fd = fd; + cgi->socket = socket; + cgi->sr = sr; + cgi->cs = cs; + cgi->hangup = MK_TRUE; + cgi->active = MK_TRUE; + + return cgi; } void cgi_req_add(struct cgi_request *r) { - struct mk_list *list = pthread_getspecific(cgi_request_list); + struct mk_list *list; + + list = pthread_getspecific(cgi_request_list); mk_list_add(&r->_head, list); } int cgi_req_del(struct cgi_request *r) { - if (!r) return 1; + PLUGIN_TRACE("[R] child_fd=%i child_pid=%lu\n", + r->fd, r->child); - mk_list_del(&r->_head); - mk_api->mem_free(r); + if (r->active == MK_TRUE) { + mk_list_del(&r->_head); + r->active = MK_FALSE; + } + else { + mk_mem_free(r); + } return 0; }