cgi
Signed-off-by: Andy Green <andy.green@linaro.org>
This commit is contained in:
parent
dbfbbb41b1
commit
6a8099b071
12 changed files with 633 additions and 21 deletions
|
@ -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()
|
||||
|
|
25
changelog
25
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
|
||||
======
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,11 @@
|
|||
*/
|
||||
|
||||
#include "private-libwebsockets.h"
|
||||
#include <sys/types.h>
|
||||
#if defined(WIN32) || defined(_WIN32)
|
||||
#else
|
||||
#include <sys/wait.h>
|
||||
#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
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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 <sys/types.h>
|
||||
#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
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
16
test-server/lws-cgi-test.sh
Executable file
16
test-server/lws-cgi-test.sh
Executable file
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
Loading…
Add table
Reference in a new issue