diff --git a/CMakeLists.txt b/CMakeLists.txt index 8efdb080..02daa610 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,6 +87,7 @@ option(LWS_IPV6 "Compile with support for ipv6" OFF) option(LWS_WITH_HTTP2 "Compile with support for http2" OFF) option(LWS_MBED3 "Platform is MBED3" OFF) option(LWS_SSL_SERVER_WITH_ECDH_CERT "Include SSL server use ECDH certificate" OFF) +option(LWS_WITH_CGI "Include CGI (spawn process with network-connected stdin/out/err) APIs" OFF) if (DEFINED YOTTA_WEBSOCKETS_VERSION_STRING) @@ -1152,6 +1153,13 @@ if (NOT LWS_WITHOUT_TESTAPPS AND NOT LWS_WITHOUT_SERVER) install(FILES ${TEST_SERVER_DATA} DESTINATION share/libwebsockets-test-server COMPONENT examples) +if (LWS_WITH_CGI) + set(CGI_TEST_SCRIPT "${PROJECT_SOURCE_DIR}/test-server/lws-cgi-test.sh") + install(FILES ${CGI_TEST_SCRIPT} + PERMISSIONS OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE OWNER_READ GROUP_READ WORLD_READ + DESTINATION share/libwebsockets-test-server + COMPONENT examples) + endif() endif() # Install the LibwebsocketsConfig.cmake and LibwebsocketsConfigVersion.cmake @@ -1205,6 +1213,7 @@ message(" LWS_WITH_HTTP2 = ${LWS_WITH_HTTP2}") message(" LWS_MBED3 = ${LWS_MBED3}") message(" LWS_SSL_SERVER_WITH_ECDH_CERT = ${LWS_SSL_SERVER_WITH_ECDH_CERT}") message(" LWS_MAX_SMP = ${LWS_MAX_SMP}") +message(" LWS_WITH_CGI = ${LWS_WITH_CGI}") message("---------------------------------------------------------------------") # These will be available to parent projects including libwebsockets using add_subdirectory() diff --git a/changelog b/changelog index 006cc20b..9a804c16 100644 --- a/changelog +++ b/changelog @@ -77,6 +77,31 @@ LWS_VISIBLE LWS_EXTERN struct lws * lws_adopt_socket_readbuf(struct lws_context *context, lws_sockfd_type accept_fd, const char *readbuf, size_t len); +3) MINOR NEWAPI CGI type "network io" subprocess execution is now possible from +a simple api. + +LWS_VISIBLE LWS_EXTERN int +lws_cgi(struct lws *wsi, char * const *exec_array, int timeout_secs); + +LWS_VISIBLE LWS_EXTERN int +lws_cgi_kill(struct lws *wsi); + +To use it, you must first set the cmake option + +$ cmake .. -DLWS_WITH_CGI=1 + +See test-server-http.c and test server path + +http://localhost:7681/cgitest + +stdin gets http body, you can test it with wget + +$ echo hello > hello.txt +$ wget http://localhost:7681/cgitest --post-file=hello.txt -O- --quiet +lwstest script +read="hello" + + v1.7.0 ====== diff --git a/lib/handshake.c b/lib/handshake.c index e48af843..6148ba54 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -156,14 +156,35 @@ http_postbody: body_chunk_len = min(wsi->u.http.content_remain,len); wsi->u.http.content_remain -= body_chunk_len; len -= body_chunk_len; +#ifdef LWS_WITH_CGI + if (wsi->cgi) { + struct lws_cgi_args args; - n = wsi->protocol->callback(wsi, - LWS_CALLBACK_HTTP_BODY, wsi->user_space, - buf, body_chunk_len); - if (n) - goto bail; + args.ch = LWS_STDIN; + args.stdwsi = &wsi->cgi->stdwsi[0]; + args.data = buf; + args.len = body_chunk_len; - buf += body_chunk_len; + /* returns how much used */ + n = user_callback_handle_rxflow( + wsi->protocol->callback, + wsi, LWS_CALLBACK_CGI_STDIN_DATA, + wsi->user_space, + (void *)&args, 0); + if (n < 0) + goto bail; + } else { +#endif + n = wsi->protocol->callback(wsi, + LWS_CALLBACK_HTTP_BODY, wsi->user_space, + buf, body_chunk_len); + if (n) + goto bail; + n = body_chunk_len; +#ifdef LWS_WITH_CGI + } +#endif + buf += n; if (wsi->u.http.content_remain) { lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, @@ -173,11 +194,16 @@ http_postbody: /* he sent all the content in time */ postbody_completion: lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); - n = wsi->protocol->callback(wsi, - LWS_CALLBACK_HTTP_BODY_COMPLETION, - wsi->user_space, NULL, 0); - if (n) - goto bail; +#ifdef LWS_WITH_CGI + if (!wsi->cgi) +#endif + { + n = wsi->protocol->callback(wsi, + LWS_CALLBACK_HTTP_BODY_COMPLETION, + wsi->user_space, NULL, 0); + if (n) + goto bail; + } goto http_complete; } diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index b4bac9f3..e10250f7 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -20,6 +20,11 @@ */ #include "private-libwebsockets.h" +#include +#if defined(WIN32) || defined(_WIN32) +#else +#include +#endif int log_level = LLL_ERR | LLL_WARN | LLL_NOTICE; static void (*lwsl_emit)(int level, const char *line) = lwsl_emit_stderr; @@ -112,6 +117,7 @@ lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs) *wsi->timeout_list_prev = wsi; } + lwsl_debug("%s: %p: %d secs\n", __func__, wsi, secs); wsi->pending_timeout_limit = now + secs; wsi->pending_timeout = reason; @@ -135,6 +141,29 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason) context = wsi->context; pt = &context->pt[(int)wsi->tsi]; +#ifdef LWS_WITH_CGI + if (wsi->mode == LWSCM_CGI) { + /* we are not a network connection, but a handler for CGI io */ + assert(wsi->master); + assert(wsi->master->cgi); + assert(wsi->master->cgi->stdwsi[(int)wsi->cgi_channel] == wsi); + /* end the binding between us and master */ + wsi->master->cgi->stdwsi[(int)wsi->cgi_channel] = NULL; + wsi->master = NULL; + wsi->socket_is_permanently_unusable = 1; + + goto just_kill_connection; + } + + if (wsi->cgi) { + /* we have a cgi going, we must kill it and close the + * related stdin/out/err wsis first + */ + wsi->cgi->being_closed = 1; + lws_cgi_kill(wsi); + } +#endif + if (wsi->mode == LWSCM_HTTP_SERVING_ACCEPTED && wsi->u.http.fd != LWS_INVALID_FILE) { lwsl_debug("closing http file\n"); @@ -1305,3 +1334,294 @@ lws_extension_callback_pm_deflate(struct lws_context *context, } #endif +LWS_VISIBLE LWS_EXTERN int +lws_is_cgi(struct lws *wsi) { +#ifdef LWS_WITH_CGI + return !!wsi->cgi; +#else + return 0; +#endif +} + +#ifdef LWS_WITH_CGI + +static struct lws * +lws_create_basic_wsi(struct lws_context *context, int tsi) +{ + struct lws *new_wsi; + + if ((unsigned int)context->pt[tsi].fds_count == + context->fd_limit_per_thread - 1) { + lwsl_err("no space for new conn\n"); + return NULL; + } + + new_wsi = lws_zalloc(sizeof(struct lws)); + if (new_wsi == NULL) { + lwsl_err("Out of memory for new connection\n"); + return NULL; + } + + new_wsi->tsi = tsi; + new_wsi->context = context; + new_wsi->pending_timeout = NO_PENDING_TIMEOUT; + new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; + + /* intialize the instance struct */ + + new_wsi->state = LWSS_CGI; + new_wsi->mode = LWSCM_CGI; + new_wsi->hdr_parsing_completed = 0; + new_wsi->position_in_fds_table = -1; + + /* + * these can only be set once the protocol is known + * we set an unestablished connection's protocol pointer + * to the start of the supported list, so it can look + * for matching ones during the handshake + */ + new_wsi->protocol = context->protocols; + new_wsi->user_space = NULL; + new_wsi->ietf_spec_revision = 0; + new_wsi->sock = LWS_SOCK_INVALID; + context->count_wsi_allocated++; + + return new_wsi; +} + +/** + * lws_cgi: spawn network-connected cgi process + * + * @wsi: connection to own the process + * @exec_array: array of "exec-name" "arg1" ... "argn" NULL + */ + +LWS_VISIBLE LWS_EXTERN int +lws_cgi(struct lws *wsi, char * const *exec_array, int timeout_secs) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + char *env_array[30], cgi_path[400]; + struct lws_cgi *cgi; + int n; + + /* + * give the master wsi a cgi struct + */ + + wsi->cgi = lws_zalloc(sizeof(*wsi->cgi)); + if (!wsi->cgi) { + lwsl_err("%s: OOM\n", __func__); + return -1; + } + + cgi = wsi->cgi; + cgi->wsi = wsi; /* set cgi's owning wsi */ + + /* create pipes for [stdin|stdout] and [stderr] */ + + for (n = 0; n < 3; n++) + if (pipe(cgi->pipe_fds[n]) == -1) + goto bail1; + + /* create cgi wsis for each stdin/out/err fd */ + + for (n = 0; n < 3; n++) { + cgi->stdwsi[n] = lws_create_basic_wsi(wsi->context, wsi->tsi); + if (!cgi->stdwsi[n]) + goto bail2; + cgi->stdwsi[n]->cgi_channel = n; + /* read side is 0, stdin we want the write side, others read */ + cgi->stdwsi[n]->sock = cgi->pipe_fds[n][!!(n == 0)]; + fcntl(cgi->pipe_fds[n][!!(n == 0)], F_SETFL, O_NONBLOCK); + } + + for (n = 0; n < 3; n++) { + cgi->stdwsi[n]->master = wsi; + if (insert_wsi_socket_into_fds(wsi->context, cgi->stdwsi[n])) + goto bail3; + } + + lws_change_pollfd(cgi->stdwsi[LWS_STDIN], LWS_POLLIN, LWS_POLLOUT); + lws_change_pollfd(cgi->stdwsi[LWS_STDOUT], LWS_POLLOUT, LWS_POLLIN); + lws_change_pollfd(cgi->stdwsi[LWS_STDERR], LWS_POLLOUT, LWS_POLLIN); + + lwsl_debug("%s: fds in %d, out %d, err %d\n", __func__, + cgi->stdwsi[LWS_STDIN]->sock, + cgi->stdwsi[LWS_STDOUT]->sock, + cgi->stdwsi[LWS_STDERR]->sock); + + lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, timeout_secs); + + /* add us to the pt list of active cgis */ + cgi->cgi_list = pt->cgi_list; + pt->cgi_list = cgi; + + /* prepare his CGI env */ + + n = 0; + if (wsi->u.hdr.ah) { + snprintf(cgi_path, sizeof(cgi_path) - 1, "PATH_INFO=%s", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_GET_URI)); + cgi_path[sizeof(cgi_path) - 1] = '\0'; + env_array[n++] = cgi_path; + if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) + env_array[n++] = "REQUEST_METHOD=POST"; + else + env_array[n++] = "REQUEST_METHOD=GET"; + } + if (lws_is_ssl(wsi)) + env_array[n++] = "HTTPS=ON"; + env_array[n++] = "SERVER_SOFTWARE=libwebsockets"; + env_array[n++] = "PATH=/bin:/usr/bin:/usrlocal/bin"; + env_array[n] = NULL; + + /* we are ready with the redirection pipes... run the thing */ +#ifdef LWS_HAVE_VFORK + cgi->pid = vfork(); +#else + cgi->pid = fork(); +#endif + if (cgi->pid < 0) { + lwsl_err("fork failed, errno %d", errno); + goto bail3; + } + + if (cgi->pid) + /* we are the parent process */ + return 0; + + /* We are the forked process, redirect and kill inherited things. + * + * Because of vfork(), we cannot do anything that changes pages in + * the parent environment. Stuff that changes kernel state for the + * process is OK. Stuff that happens after the execvpe() is OK. + */ + + for (n = 0; n < 3; n++) { + if (dup2(cgi->pipe_fds[n][!(n == 0)], n) < 0) { + lwsl_err("%s: stdin dup2 failed\n", __func__); + goto bail3; + } + close(cgi->pipe_fds[n][!(n == 0)]); + } + + execvpe(exec_array[0], &exec_array[0], &env_array[0]); + exit(1); + +bail3: + /* drop us from the pt cgi list */ + pt->cgi_list = cgi->cgi_list; + + while (--n >= 0) + remove_wsi_socket_from_fds(wsi->cgi->stdwsi[n]); +bail2: + for (n = 0; n < 3; n++) + if (wsi->cgi->stdwsi[n]) + lws_free_wsi(cgi->stdwsi[n]); + +bail1: + for (n = 0; n < 3; n++) { + if (cgi->pipe_fds[n][0]) + close(cgi->pipe_fds[n][0]); + if (cgi->pipe_fds[n][1]) + close(cgi->pipe_fds[n][1]); + } + + lws_free_set_NULL(wsi->cgi); + + lwsl_err("%s: failed\n", __func__); + + return -1; +} + +/** + * lws_cgi_kill: terminate cgi process associated with wsi + * + * @wsi: connection to own the process + */ +LWS_VISIBLE LWS_EXTERN int +lws_cgi_kill(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + struct lws_cgi **pcgi = &pt->cgi_list; + struct lws_cgi_args args; + int n, status, do_close = 0; + + if (!wsi->cgi) + return 0; + + lwsl_notice("%s: wsi %p\n", __func__, wsi); + + assert(wsi->cgi); + + if (wsi->cgi->pid > 0) { + /* kill the process */ + n = kill(wsi->cgi->pid, SIGTERM); + if (n < 0) { + lwsl_err("%s: failed\n", __func__); + return 1; + } + waitpid(wsi->cgi->pid, &status, 0); /* !!! may hang !!! */ + } + + args.stdwsi = &wsi->cgi->stdwsi[0]; + + if (wsi->cgi->pid != -1 && user_callback_handle_rxflow( + wsi->protocol->callback, + wsi, LWS_CALLBACK_CGI_TERMINATED, + wsi->user_space, + (void *)&args, 0)) { + wsi->cgi->pid = -1; + do_close = !wsi->cgi->being_closed; + } + + /* remove us from the cgi list */ + while (*pcgi) { + if (*pcgi == wsi->cgi) { + /* drop us from the pt cgi list */ + *pcgi = (*pcgi)->cgi_list; + break; + } + pcgi = &(*pcgi)->cgi_list; + } + + for (n = 0 ; n < 3; n++) { + if (wsi->cgi->pipe_fds[n][!!(n == 0)] >= 0) { + close(wsi->cgi->pipe_fds[n][!!(n == 0)]); + wsi->cgi->pipe_fds[n][!!(n == 0)] = -1; + + lws_close_free_wsi(wsi->cgi->stdwsi[n], 0); + } + } + + lws_free_set_NULL(wsi->cgi); + + if (do_close) + lws_close_free_wsi(wsi, 0); + + return 0; +} + +LWS_EXTERN int +lws_cgi_kill_terminated(struct lws_context_per_thread *pt) +{ + struct lws_cgi **pcgi = &pt->cgi_list, *cgi; + int status; + + /* check all the subprocesses on the cgi list for termination */ + while (*pcgi) { + /* get the next one because we may close current one next */ + cgi = *pcgi; + pcgi = &(*pcgi)->cgi_list; + + if (cgi->pid > 0 && + waitpid(cgi->pid, &status, WNOHANG) > 0) { + cgi->pid = 0; + lws_cgi_kill(cgi->wsi); + pcgi = &pt->cgi_list; + } + } + + return 0; +} +#endif diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 3e949392..5055c2f4 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -339,6 +339,11 @@ enum lws_callback_reasons { LWS_CALLBACK_WS_EXT_DEFAULTS = 39, + LWS_CALLBACK_CGI = 40, + LWS_CALLBACK_CGI_TERMINATED = 41, + LWS_CALLBACK_CGI_STDIN_DATA = 42, + LWS_CALLBACK_CGI_STDIN_COMPLETED = 43, + /****** add new things just above ---^ ******/ LWS_CALLBACK_USER = 1000, /* user code can use any including / above */ @@ -1535,6 +1540,7 @@ enum pending_timeout { PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND = 11, PENDING_FLUSH_STORED_SEND_BEFORE_CLOSE = 12, PENDING_TIMEOUT_SHUTDOWN_FLUSH = 13, + PENDING_TIMEOUT_CGI = 14, /****** add new things just above ---^ ******/ }; @@ -1741,6 +1747,10 @@ lws_frame_is_binary(struct lws *wsi); LWS_VISIBLE LWS_EXTERN int lws_is_ssl(struct lws *wsi); + +LWS_VISIBLE LWS_EXTERN int +lws_is_cgi(struct lws *wsi); + #ifdef LWS_SHA1_USE_OPENSSL_NAME #define lws_SHA1 SHA1 #else @@ -1813,6 +1823,27 @@ lws_get_context(const struct lws *wsi); LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_get_count_threads(struct lws_context *context); +#ifdef LWS_WITH_CGI +enum lws_enum_stdinouterr { + LWS_STDIN = 0, + LWS_STDOUT = 1, + LWS_STDERR = 2, +}; + +struct lws_cgi_args { + struct lws **stdwsi; /* get fd with lws_get_socket_fd() */ + enum lws_enum_stdinouterr ch; + unsigned char *data; /* for messages with payload */ + int len; +}; + +LWS_VISIBLE LWS_EXTERN int +lws_cgi(struct lws *wsi, char * const *exec_array, int timeout_secs); + +LWS_VISIBLE LWS_EXTERN int +lws_cgi_kill(struct lws *wsi); +#endif + /* * Wsi-associated File Operations access helpers * diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 315c32e1..1de2ca5b 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -22,6 +22,11 @@ #include "lws_config.h" #include "lws_config_private.h" + +#if defined(LWS_WITH_CGI) && defined(LWS_HAVE_VFORK) +#define _GNU_SOURCE +#endif + #ifdef LWS_HAVE_SYS_TYPES_H #include #endif @@ -353,6 +358,8 @@ enum lws_connection_states { LWSS_HTTP2_AWAIT_CLIENT_PREFACE, LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS, LWSS_HTTP2_ESTABLISHED, + + LWSS_CGI }; enum http_version { @@ -427,6 +434,7 @@ enum connection_mode { /* special internal types */ LWSCM_SERVER_LISTENER, + LWSCM_CGI, /* stdin, stdout, stderr for another cgi master wsi */ }; enum { @@ -530,6 +538,9 @@ struct lws_context_per_thread { struct lws *rx_draining_ext_list; struct lws *tx_draining_ext_list; struct lws *timeout_list; +#ifdef LWS_WITH_CGI + struct lws_cgi *cgi_list; +#endif void *http_header_data; struct allocated_headers *ah_pool; struct lws *ah_wait_list; @@ -995,6 +1006,21 @@ struct _lws_websocket_related { unsigned int tx_draining_ext:1; }; +#ifdef LWS_WITH_CGI + +/* wsi who is master of the cgi points to an lws_cgi */ + +struct lws_cgi { + struct lws_cgi *cgi_list; + struct lws *stdwsi[3]; /* points to the associated stdin/out/err wsis */ + struct lws *wsi; /* owner */ + int pipe_fds[3][2]; + int pid; + + unsigned int being_closed:1; +}; +#endif + struct lws { /* structs */ @@ -1022,6 +1048,10 @@ struct lws { /* pointers */ struct lws_context *context; +#ifdef LWS_WITH_CGI + struct lws_cgi *cgi; /* wsi being cgi master have one of these */ + struct lws *master; /* for stdin/out/err wsi to point to cgi master */ +#endif const struct lws_protocols *protocol; struct lws *timeout_list; struct lws **timeout_list_prev; @@ -1082,6 +1112,9 @@ struct lws { char pending_timeout; /* enum pending_timeout */ char pps; /* enum lws_pending_protocol_send */ char tsi; /* thread service index we belong to */ +#ifdef LWS_WITH_CGI + char cgi_channel; /* which of stdin/out/err */ +#endif }; LWS_EXTERN int log_level; @@ -1439,6 +1472,9 @@ LWS_EXTERN int lws_get_addresses(struct lws_context *context, void *ads, char *name, int name_len, char *rip, int rip_len); +LWS_EXTERN int +lws_cgi_kill_terminated(struct lws_context_per_thread *pt); + /* * custom allocator */ diff --git a/lib/server.c b/lib/server.c index 92a8eedc..4aef8bd6 100644 --- a/lib/server.c +++ b/lib/server.c @@ -609,7 +609,8 @@ upgrade_ws: return 1; } #endif - lwsl_parser("accepted v%02d connection\n", wsi->ietf_spec_revision); + lwsl_parser("accepted v%02d connection\n", + wsi->ietf_spec_revision); return 0; } /* while all chars are handled */ @@ -632,7 +633,8 @@ lws_get_idlest_tsi(struct lws_context *context) int n = 0, hit = -1; for (; n < context->count_threads; n++) { - if ((unsigned int)context->pt[n].fds_count != context->fd_limit_per_thread - 1 && + if ((unsigned int)context->pt[n].fds_count != + context->fd_limit_per_thread - 1 && (unsigned int)context->pt[n].fds_count < lowest) { lowest = context->pt[n].fds_count; hit = n; diff --git a/lib/service.c b/lib/service.c index f5dcefc4..f6040825 100644 --- a/lib/service.c +++ b/lib/service.c @@ -554,6 +554,9 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t } wsi = wsi1; } +#ifdef LWS_WITH_CGI + lws_cgi_kill_terminated(pt); +#endif #if 0 { char s[300], *p = s; @@ -588,7 +591,6 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t */ #if LWS_POSIX - /* handle session socket closed */ if ((!(pollfd->revents & pollfd->events & LWS_POLLIN)) && @@ -607,6 +609,8 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t #endif + lwsl_debug("fd=%d, revents=%d\n", pollfd->fd, pollfd->revents); + /* okay, what we came here to do... */ switch (wsi->mode) { @@ -652,7 +656,7 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t wsi->u.ws.tx_draining_ext = 0; } - if (wsi->u.ws.tx_draining_ext) { + if (wsi->u.ws.tx_draining_ext) /* we cannot deal with new RX until the TX ext * path has been drained. It's because new * rx will, eg, crap on the wsi rx buf that @@ -662,7 +666,6 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t * to avoid blocking. */ break; - } if (!(wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) /* We cannot deal with any kind of new RX @@ -795,7 +798,8 @@ drain: } while (more); if (wsi->u.hdr.ah) { - lwsl_err("%s: %p: detaching inherited used ah\n", __func__, wsi); + lwsl_err("%s: %p: detaching inherited used ah\n", + __func__, wsi); /* show we used all the pending rx up */ wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen; /* we can run the normal ah detach flow despite @@ -825,7 +829,39 @@ handle_pending: } break; +#ifdef LWS_WITH_CGI + case LWSCM_CGI: /* we exist to handle a cgi's stdin/out/err data... + * do the callback on our master wsi + */ + { + struct lws_cgi_args args; + if (wsi->cgi_channel >= LWS_STDOUT && + !(pollfd->revents & pollfd->events & LWS_POLLIN)) + break; + if (wsi->cgi_channel == LWS_STDIN && + !(pollfd->revents & pollfd->events & LWS_POLLOUT)) + break; + + if (wsi->cgi_channel == LWS_STDIN) + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_info("failed at set pollfd\n"); + return 1; + } + + args.ch = wsi->cgi_channel; + args.stdwsi = &wsi->master->cgi->stdwsi[0]; + + if (user_callback_handle_rxflow( + wsi->master->protocol->callback, + wsi->master, LWS_CALLBACK_CGI, + wsi->master->user_space, + (void *)&args, 0)) + return 1; + + break; + } +#endif default: #ifdef LWS_NO_CLIENT break; diff --git a/lws_config.h.in b/lws_config.h.in index 6ce964b6..c108eb3a 100644 --- a/lws_config.h.in +++ b/lws_config.h.in @@ -74,6 +74,9 @@ /* SSL server using ECDH certificate */ #cmakedefine LWS_SSL_SERVER_WITH_ECDH_CERT +/* CGI apis */ +#cmakedefine LWS_WITH_CGI + /* Maximum supported service threads */ #define LWS_MAX_SMP ${LWS_MAX_SMP} diff --git a/test-server/lws-cgi-test.sh b/test-server/lws-cgi-test.sh new file mode 100755 index 00000000..90c804e5 --- /dev/null +++ b/test-server/lws-cgi-test.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +echo "lwstest script stdout" +>&2 echo "lwstest script stderr" + +echo "REQUEST_METHOD=$REQUEST_METHOD" + +if [ "$REQUEST_METHOD" = "POST" ] ; then + read line + echo "read=\"$line\"" +fi + +echo "done" + +exit 0 + diff --git a/test-server/test-server-http.c b/test-server/test-server-http.c index 9cabce58..c1433436 100644 --- a/test-server/test-server-http.c +++ b/test-server/test-server-http.c @@ -129,7 +129,6 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, char buf[256]; char b64[64]; int n, m; - #ifdef EXTERNAL_POLL struct lws_pollargs *pa = (struct lws_pollargs *)in; #endif @@ -159,6 +158,43 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, goto try_to_reuse; } +#ifdef LWS_WITH_CGI + if (!strcmp(in, "/cgitest")) { + static char *cmd[] = { + "/bin/sh", + "-c", + INSTALL_DATADIR"/libwebsockets-test-server/lws-cgi-test.sh", + NULL + }; + + lwsl_notice("%s: cgitest\n", __func__); + n = lws_cgi(wsi, cmd, 5); + if (n) { + lwsl_err("%s: cgi failed\n"); + return -1; + } + p = buffer + LWS_PRE; + end = p + sizeof(buffer) - LWS_PRE; + + if (lws_add_http_header_status(wsi, 200, &p, end)) + return 1; + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_CONTENT_TYPE, + (unsigned char *)"text/plain", + 10, &p, end)) + return 1; + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION, + (unsigned char *)"close", 5, &p, end)) + return 1; + if (lws_finalize_http_header(wsi, &p, end)) + return 1; + n = lws_write(wsi, buffer + LWS_PRE, + p - (buffer + LWS_PRE), + LWS_WRITE_HTTP_HEADERS); + break; + } +#endif + /* if a legal POST URL, let it continue and accept data */ if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) return 0; @@ -225,7 +261,8 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, *p = '\0'; lwsl_info("%s\n", buffer + LWS_PRE); - n = lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), + n = lws_write(wsi, buffer + LWS_PRE, + p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS); if (n < 0) { lws_plat_file_close(wsi, pss->fd); @@ -322,7 +359,25 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, if (pss->fd == LWS_INVALID_FILE) goto try_to_reuse; - +#ifdef LWS_WITH_CGI + if (pss->reason_bf) { + lwsl_debug("%s: stdout\n", __func__); + n = read(lws_get_socket_fd(pss->args.stdwsi[LWS_STDOUT]), + buf + LWS_PRE, sizeof(buf) - LWS_PRE); + //lwsl_notice("read %d (errno %d)\n", n, errno); + if (n < 0 && errno != EAGAIN) + return -1; + if (n > 0) { + m = lws_write(wsi, (unsigned char *)buf + LWS_PRE, n, + LWS_WRITE_HTTP); + //lwsl_notice("write %d\n", m); + if (m < 0) + goto bail; + pss->reason_bf = 0; + } + break; + } +#endif /* * we can send more of whatever it is we were sending */ @@ -393,10 +448,59 @@ bail: * connection continue. */ case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: - /* if we returned non-zero from here, we kill the connection */ break; +#ifdef LWS_WITH_CGI + /* CGI IO events (POLLIN/OUT) appear here our demo user code policy is + * + * - POST data goes on subprocess stdin + * - subprocess stdout goes on http via writeable callback + * - subprocess stderr goes to the logs + */ + case LWS_CALLBACK_CGI: + pss->args = *((struct lws_cgi_args *)in); + //lwsl_notice("LWS_CALLBACK_CGI: ch %d\n", pss->args.ch); + switch (pss->args.ch) { /* which of stdin/out/err ? */ + case LWS_STDIN: + /* TBD stdin rx flow control */ + break; + case LWS_STDOUT: + pss->reason_bf |= 1 << pss->args.ch; + /* when writing to MASTER would not block */ + lws_callback_on_writable(wsi); + break; + case LWS_STDERR: + n = read(lws_get_socket_fd(pss->args.stdwsi[LWS_STDERR]), + buf, 127); + //lwsl_notice("stderr reads %d\n", n); + if (n > 0) { + if (buf[n - 1] != '\n') + buf[n++] = '\n'; + buf[n] = '\0'; + lwsl_notice("CGI-stderr: %s\n", buf); + } + break; + } + break; + + case LWS_CALLBACK_CGI_TERMINATED: + lwsl_notice("LWS_CALLBACK_CGI_TERMINATED\n"); + /* because we sent on openended http, close the connection */ + return -1; + + case LWS_CALLBACK_CGI_STDIN_DATA: /* POST body for stdin */ + lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA\n"); + pss->args = *((struct lws_cgi_args *)in); + n = write(lws_get_socket_fd(pss->args.stdwsi[LWS_STDIN]), + pss->args.data, pss->args.len); + lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: write says %d", n); + if (n < pss->args.len) + lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: sent %d only %d went", + n, pss->args.len); + return n; +#endif + /* * callbacks for managing the external poll() array appear in * protocol 0 callback diff --git a/test-server/test-server.h b/test-server/test-server.h index cb69d500..7217621e 100644 --- a/test-server/test-server.h +++ b/test-server/test-server.h @@ -67,6 +67,10 @@ extern void test_server_unlock(int care); struct per_session_data__http { lws_filefd_type fd; +#ifdef LWS_WITH_CGI + struct lws_cgi_args args; + int reason_bf; +#endif }; /*