Commit 537fb39b authored by Sergey Lyubka's avatar Sergey Lyubka

Async, non-blocking experiment

parent e1061510
#ifdef _WIN32
#undef _UNICODE
#define _MBCS
#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005+
#else
#define _XOPEN_SOURCE 600 // For flockfile() on Linux
#define __STDC_FORMAT_MACROS // <inttypes.h> wants this for C++
#define __STDC_LIMIT_MACROS // C++ wants that for INT64_MAX
#define _LARGEFILE_SOURCE // Enable fseeko() and ftello() functions
#define _FILE_OFFSET_BITS 64 // Enable 64-bit file offsets
#endif
#ifdef _MSC_VER
#pragma warning (disable : 4127) // FD_SET() emits warning, disable it
#pragma warning (disable : 4204) // missing c99 support
#endif
// Disable WIN32_LEAN_AND_MEAN. This makes windows.h always include winsock2.h
#ifdef WIN32_LEAN_AND_MEAN
#undef WIN32_LEAN_AND_MEAN
#endif
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <time.h>
#include <ctype.h>
#ifdef _WIN32
#include <windows.h>
typedef int socklen_t;
typedef int pid_t;
typedef SOCKET sock_t;
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
typedef unsigned __int64 uint64_t;
typedef __int64 int64_t;
#pragma comment(lib, "ws2_32.lib")
#define snprintf _snprintf
#define INT64_FMT "I64d"
#else
#include <inttypes.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/select.h>
#define closesocket(x) close(x)
typedef int sock_t;
#define INVALID_SOCKET ((sock_t) -1)
#define INT64_FMT PRId64
#endif
#include "mongoose.h"
struct linked_list_link { struct linked_list_link *prev, *next; };
#define LINKED_LIST_INIT(N) ((N)->next = (N)->prev = (N))
#define LINKED_LIST_DECLARE_AND_INIT(H) struct linked_list_link H = { &H, &H }
#define LINKED_LIST_ENTRY(P,T,N) ((T *)((char *)(P) - offsetof(T, N)))
#define LINKED_LIST_IS_EMPTY(N) ((N)->next == (N))
#define LINKED_LIST_FOREACH(H,N,T) \
for (N = (H)->next, T = (N)->next; N != (H); N = (T), T = (N)->next)
#define LINKED_LIST_ADD_TO_FRONT(H,N) do { ((H)->next)->prev = (N); \
(N)->next = ((H)->next); (N)->prev = (H); (H)->next = (N); } while (0)
#define LINKED_LIST_ADD_TO_TAIL(H,N) do { ((H)->prev)->next = (N); \
(N)->prev = ((H)->prev); (N)->next = (H); (H)->prev = (N); } while (0)
#define LINKED_LIST_REMOVE(N) do { ((N)->next)->prev = ((N)->prev); \
((N)->prev)->next = ((N)->next); LINKED_LIST_INIT(N); } while (0)
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
#define MAX_REQUEST_SIZE 16384
#define IOBUF_SIZE 8192
union socket_address {
struct sockaddr sa;
struct sockaddr_in sin;
#ifdef USE_IPV6
struct sockaddr_in6 sin6;
#endif
};
struct vec {
const char *ptr;
size_t len;
};
// NOTE(lsm): this enum shoulds be in sync with the config_options.
enum {
ACCESS_CONTROL_LIST, ACCESS_LOG_FILE, AUTH_DOMAIN, CGI_INTERPRETER,
CGI_PATTERN, DOCUMENT_ROOT, ENABLE_DIRECTORY_LISTING, ERROR_LOG_FILE,
EXTRA_MIME_TYPES, GLOBAL_AUTH_FILE, HIDE_FILES_PATTERN, IDLE_TIMEOUT_MS,
INDEX_FILES, LISTENING_PORT, NUM_THREADS, PUT_DELETE_AUTH_FILE, RUN_AS_USER,
SSL_CERTIFICATE, SSI_PATTERN, URL_REWRITES, NUM_OPTIONS
};
struct mg_server {
sock_t listening_sock;
union socket_address lsa; // Listening socket address
struct linked_list_link active_connections;
char *config_options[NUM_OPTIONS];
mg_event_handler_t event_handler;
void *user_data;
};
struct iobuf {
char *buf; // Buffer that holds the data
int size; // Buffer size
int len; // Number of bytes currently in a buffer
};
union endpoint {
int fd; // Opened regular local file
sock_t sock; // CGI socket
void *ssl; // SSL descriptor
};
struct mg_connection {
struct linked_list_link link; // Linkage to server->active_connections
struct mg_server *server;
sock_t client_sock; // Connected client
union socket_address csa; // Client's socket address
struct iobuf local_iobuf;
struct iobuf remote_iobuf;
union endpoint endpoint;
enum { EP_NONE, EP_FILE, EP_SOCKET, EP_SSL } endpoint_type;
enum { CONN_CLOSE = 1 } flags;
time_t expire_time;
struct mg_request_info request_info;
int request_len;
};
static const char *static_config_options[] = {
"access_control_list", NULL,
"access_log_file", NULL,
"auth_domain", "mydomain.com",
"cgi_interpreter", NULL,
"cgi_pattern", "**.cgi$|**.pl$|**.php$",
"document_root", NULL,
"enable_directory_listing", "yes",
"error_log_file", NULL,
"extra_mime_types", NULL,
"global_auth_file", NULL,
"hide_files_patterns", NULL,
"idle_timeout_ms", "30000",
"index_files","index.html,index.htm,index.cgi,index.shtml,index.php,index.lp",
"listening_port", "8080",
"num_threads", "50",
"put_delete_auth_file", NULL,
"run_as_user", NULL,
"ssl_certificate", NULL,
"ssi_pattern", "**.shtml$|**.shtm$",
"url_rewrites", NULL,
NULL
};
const char **mg_get_valid_option_names(void) {
return static_config_options;
}
static int get_option_index(const char *name) {
int i;
for (i = 0; static_config_options[i * 2] != NULL; i++) {
if (strcmp(static_config_options[i * 2], name) == 0) {
return i;
}
}
return -1;
}
const char *mg_get_option2(const struct mg_server *srv, const char *name) {
int i = get_option_index(name);
return i == -1 ? NULL : srv->config_options[i] == NULL ? "" :
srv->config_options[i];
}
static void set_close_on_exec(int fd) {
#ifdef _WIN32
(void) SetHandleInformation((HANDLE) fd, HANDLE_FLAG_INHERIT, 0);
#else
fcntl(fd, F_SETFD, FD_CLOEXEC);
#endif
}
static void set_non_blocking_mode(sock_t sock) {
#ifdef _WIN32
unsigned long on = 1;
ioctlsocket(sock, FIONBIO, &on);
#else
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
#endif
}
// A helper function for traversing a comma separated list of values.
// It returns a list pointer shifted to the next value, or NULL if the end
// of the list found.
// Value is stored in val vector. If value has form "x=y", then eq_val
// vector is initialized to point to the "y" part, and val vector length
// is adjusted to point only to "x".
static const char *next_option(const char *list, struct vec *val,
struct vec *eq_val) {
if (list == NULL || *list == '\0') {
// End of the list
list = NULL;
} else {
val->ptr = list;
if ((list = strchr(val->ptr, ',')) != NULL) {
// Comma found. Store length and shift the list ptr
val->len = list - val->ptr;
list++;
} else {
// This value is the last one
list = val->ptr + strlen(val->ptr);
val->len = list - val->ptr;
}
if (eq_val != NULL) {
// Value has form "x=y", adjust pointers and lengths
// so that val points to "x", and eq_val points to "y".
eq_val->len = 0;
eq_val->ptr = (const char *) memchr(val->ptr, '=', val->len);
if (eq_val->ptr != NULL) {
eq_val->ptr++; // Skip over '=' character
eq_val->len = val->ptr + val->len - eq_val->ptr;
val->len = (eq_val->ptr - val->ptr) - 1;
}
}
}
return list;
}
static int mg_socketpair(sock_t sp[2]) {
struct sockaddr_in sa;
sock_t sock, ret = -1;
socklen_t len = sizeof(sa);
sp[0] = sp[1] = INVALID_SOCKET;
(void) memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(0);
sa.sin_addr.s_addr = htonl(0x7f000001);
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) != INVALID_SOCKET &&
!bind(sock, (struct sockaddr *) &sa, len) &&
!listen(sock, 1) &&
!getsockname(sock, (struct sockaddr *) &sa, &len) &&
(sp[0] = socket(AF_INET, SOCK_STREAM, 6)) != -1 &&
!connect(sp[0], (struct sockaddr *) &sa, len) &&
(sp[1] = accept(sock,(struct sockaddr *) &sa, &len)) != INVALID_SOCKET) {
set_close_on_exec(sp[0]);
set_close_on_exec(sp[1]);
ret = 0;
} else {
if (sp[0] != INVALID_SOCKET) closesocket(sp[0]);
if (sp[1] != INVALID_SOCKET) closesocket(sp[1]);
sp[0] = sp[1] = INVALID_SOCKET;
}
closesocket(sock);
return ret;
}
#ifdef _WIN32
static pid_t start_process(char *cmd, char *env[], char *dir, sock_t sock) {
STARTUPINFOA si = {0};
PROCESS_INFORMATION pi = {0};
HANDLE hs = (HANDLE) sock, me = GetCurrentProcess();
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
DuplicateHandle(me, hs, me, &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
DuplicateHandle(me, hs, me, &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
closesocket(sock);
printf("Starting commad: [%s]\n", cmd);
CreateProcess(NULL, cmd, NULL, NULL, TRUE,
CREATE_NEW_PROCESS_GROUP, (void *) env, dir, &si, &pi);
CloseHandle(si.hStdOutput);
CloseHandle(si.hStdInput);
CloseHandle(pi.hThread);
return (pid_t) pi.hProcess;
}
#else
#endif
// 'sa' must be an initialized address to bind to
static sock_t open_listening_socket(union socket_address *sa) {
sock_t on = 1, sock = INVALID_SOCKET;
if ((sock = socket(sa->sa.sa_family, SOCK_STREAM, 6)) == INVALID_SOCKET ||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &on, sizeof(on)) ||
bind(sock, &sa->sa, sa->sa.sa_family == AF_INET ?
sizeof(sa->sin) : sizeof(sa->sa)) != 0 ||
listen(sock, SOMAXCONN) != 0) {
closesocket(sock);
sock = INVALID_SOCKET;
}
return sock;
}
static char *mg_strdup(const char *str) {
char *copy = malloc(strlen(str) + 1);
if (copy != NULL) {
strcpy(copy, str);
}
return copy;
}
// Valid listening port spec is: [ip_address:]port, e.g. "80", "127.0.0.1:3128"
static int parse_port_string(const char *str, union socket_address *sa) {
unsigned int a, b, c, d, port;
#if defined(USE_IPV6)
char buf[100];
#endif
// MacOS needs that. If we do not zero it, subsequent bind() will fail.
// Also, all-zeroes in the socket address means binding to all addresses
// for both IPv4 and IPv6 (INADDR_ANY and IN6ADDR_ANY_INIT).
memset(sa, 0, sizeof(*sa));
sa->sin.sin_family = AF_INET;
if (sscanf(str, "%u.%u.%u.%u:%u", &a, &b, &c, &d, &port) == 5) {
// Bind to a specific IPv4 address, e.g. 192.168.1.5:8080
sa->sin.sin_addr.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | d);
sa->sin.sin_port = htons((uint16_t) port);
#if defined(USE_IPV6)
} else if (sscanf(str, "[%49[^]]]:%d%n", buf, &port, &len) == 2 &&
inet_pton(AF_INET6, buf, &so->lsa.sin6.sin6_addr)) {
// IPv6 address, e.g. [3ffe:2a00:100:7031::1]:8080
so->lsa.sin6.sin6_family = AF_INET6;
so->lsa.sin6.sin6_port = htons((uint16_t) port);
#endif
} else if (sscanf(str, "%u", &port) == 1) {
// If only port is specified, bind to IPv4, INADDR_ANY
sa->sin.sin_port = htons((uint16_t) port);
} else {
port = 0; // Parsing failure. Make port invalid.
}
return port > 0 && port < 0xffff;
}
static int isbyte(int n) {
return n >= 0 && n <= 255;
}
static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) {
int n, a, b, c, d, slash = 32, len = 0;
if ((sscanf(spec, "%d.%d.%d.%d/%d%n", &a, &b, &c, &d, &slash, &n) == 5 ||
sscanf(spec, "%d.%d.%d.%d%n", &a, &b, &c, &d, &n) == 4) &&
isbyte(a) && isbyte(b) && isbyte(c) && isbyte(d) &&
slash >= 0 && slash < 33) {
len = n;
*net = ((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | d;
*mask = slash ? 0xffffffffU << (32 - slash) : 0;
}
return len;
}
// Verify given socket address against the ACL.
// Return -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed.
static int check_acl(const char *acl, uint32_t remote_ip) {
int allowed, flag;
uint32_t net, mask;
struct vec vec;
// If any ACL is set, deny by default
allowed = acl == NULL ? '+' : '-';
while ((acl = next_option(acl, &vec, NULL)) != NULL) {
flag = vec.ptr[0];
if ((flag != '+' && flag != '-') ||
parse_net(&vec.ptr[1], &net, &mask) == 0) {
return -1;
}
if (net == (remote_ip & mask)) {
allowed = flag;
}
}
return allowed == '+';
}
static struct mg_connection *accept_new_connection(struct mg_server *server) {
union socket_address sa;
socklen_t len = sizeof(sa);
sock_t sock = INVALID_SOCKET;
struct mg_connection *conn = NULL;
if ((sock = accept(server->listening_sock, &sa.sa, &len)) == INVALID_SOCKET) {
} else if (sock >= FD_SETSIZE) {
closesocket(sock);
} else if (!check_acl(server->config_options[ACCESS_CONTROL_LIST],
ntohl(* (uint32_t *) &sa.sin.sin_addr))) {
closesocket(sock);
} else if ((conn = calloc(1, sizeof(*conn) + MAX_REQUEST_SIZE)) == NULL) {
closesocket(sock);
} else {
// Put so socket structure into the queue
set_close_on_exec(sock);
set_non_blocking_mode(sock);
conn->client_sock = sock;
conn->csa = sa;
conn->local_iobuf.buf = (char *) conn + sizeof(*conn);
conn->local_iobuf.size = MAX_REQUEST_SIZE;
conn->remote_iobuf.buf = calloc(1, IOBUF_SIZE);
conn->remote_iobuf.size = IOBUF_SIZE;
LINKED_LIST_ADD_TO_FRONT(&server->active_connections, &conn->link);
printf("added conn %p\n", conn);
}
return conn;
}
static void close_conn(struct mg_connection *conn) {
printf("closing %p\n", conn);
LINKED_LIST_REMOVE(&conn->link);
closesocket(conn->client_sock);
free(conn->remote_iobuf.buf);
free(conn);
}
// Check whether full request is buffered. Return:
// -1 if request is malformed
// 0 if request is not yet fully buffered
// >0 actual request length, including last \r\n\r\n
static int get_request_len(const unsigned char *buf, int buf_len) {
int i;
for (i = 0; i < buf_len; i++) {
// Control characters are not allowed but >=128 are.
// Abort scan as soon as one malformed character is found.
if (!isprint(buf[i]) && buf[i] != '\r' && buf[i] != '\n' && buf[i] < 128) {
return -1;
} else if (buf[i] == '\n' && i + 1 < buf_len && buf[i + 1] == '\n') {
return i + 2;
} else if (buf[i] == '\n' && i + 2 < buf_len && buf[i + 1] == '\r' &&
buf[i + 2] == '\n') {
return i + 3;
}
}
return 0;
}
// Skip the characters until one of the delimiters characters found.
// 0-terminate resulting word. Skip the rest of the delimiters if any.
// Advance pointer to buffer to the next word. Return found 0-terminated word.
static char *skip(char **buf, const char *delimiters) {
char *p, *begin_word, *end_word, *end_delimiters;
begin_word = *buf;
end_word = begin_word + strcspn(begin_word, delimiters);
end_delimiters = end_word + strspn(end_word, delimiters);
for (p = end_word; p < end_delimiters; p++) {
*p = '\0';
}
*buf = end_delimiters;
return begin_word;
}
// Parse HTTP headers from the given buffer, advance buffer to the point
// where parsing stopped.
static void parse_http_headers(char **buf, struct mg_request_info *ri) {
int i;
for (i = 0; i < (int) ARRAY_SIZE(ri->http_headers); i++) {
ri->http_headers[i].name = skip(buf, ": ");
ri->http_headers[i].value = skip(buf, "\r\n");
if (ri->http_headers[i].name[0] == '\0')
break;
ri->num_headers = i + 1;
}
}
static int is_valid_http_method(const char *method) {
return !strcmp(method, "GET") || !strcmp(method, "POST") ||
!strcmp(method, "HEAD") || !strcmp(method, "CONNECT") ||
!strcmp(method, "PUT") || !strcmp(method, "DELETE") ||
!strcmp(method, "OPTIONS") || !strcmp(method, "PROPFIND")
|| !strcmp(method, "MKCOL");
}
// Parse HTTP request, fill in mg_request_info structure.
// This function modifies the buffer by NUL-terminating
// HTTP request components, header names and header values.
static int parse_http_message(char *buf, int len, struct mg_request_info *ri) {
int is_request, request_length = get_request_len((unsigned char *) buf, len);
if (request_length > 0) {
// Reset attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port
ri->remote_user = ri->request_method = ri->uri = ri->http_version = NULL;
ri->num_headers = 0;
buf[request_length - 1] = '\0';
// RFC says that all initial whitespaces should be ingored
while (*buf != '\0' && isspace(* (unsigned char *) buf)) {
buf++;
}
ri->request_method = skip(&buf, " ");
ri->uri = skip(&buf, " ");
ri->http_version = skip(&buf, "\r\n");
// HTTP message could be either HTTP request or HTTP response, e.g.
// "GET / HTTP/1.0 ...." or "HTTP/1.0 200 OK ..."
is_request = is_valid_http_method(ri->request_method);
if ((is_request && memcmp(ri->http_version, "HTTP/", 5) != 0) ||
(!is_request && memcmp(ri->request_method, "HTTP/", 5) != 0)) {
request_length = -1;
} else {
if (is_request) {
ri->http_version += 5;
}
parse_http_headers(&buf, ri);
}
}
return request_length;
}
static int parse_range_header(const char *header, int64_t *a, int64_t *b) {
return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
}
static void read_from_client(struct mg_connection *conn) {
struct iobuf *io = &conn->local_iobuf;
int n = recv(conn->client_sock, io->buf + io->len, io->size - io->len, 0);
printf("read %d bytes\n", n);
assert(io->len >= 0);
assert(io->len <= io->size);
if (n == 0 || (n < 0 && (errno == EINTR || errno == EAGAIN))) {
conn->flags |= CONN_CLOSE;
} else if (n > 0) {
io->len += n;
if (conn->request_len == 0) {
conn->request_len = parse_http_message(io->buf, io->len,
&conn->request_info);
}
if (conn->request_len < 0 ||
(conn->request_len == 0 || io->len >= io->size)) {
conn->flags |= CONN_CLOSE;
} else if (conn->request_len > 0) {
printf("%.*s", (int) io->len, io->buf);
}
}
}
void add_to_set(sock_t sock, fd_set *set, sock_t *max_fd) {
FD_SET(sock, set);
if (sock > *max_fd) {
*max_fd = sock;
}
}
void mg_poll_server(struct mg_server *server, unsigned int milliseconds) {
struct linked_list_link *lp, *tmp;
struct mg_connection *conn;
struct timeval tv;
fd_set read_set, write_set;
sock_t max_fd = -1;
time_t current_time = time(NULL), expire_time = current_time + 3;
FD_ZERO(&read_set);
FD_ZERO(&write_set);
add_to_set(server->listening_sock, &read_set, &max_fd);
LINKED_LIST_FOREACH(&server->active_connections, lp, tmp) {
conn = LINKED_LIST_ENTRY(lp, struct mg_connection, link);
add_to_set(conn->client_sock, &read_set, &max_fd);
}
tv.tv_sec = milliseconds / 1000;
tv.tv_usec = (milliseconds % 1000) * 1000;
if (select(max_fd + 1, &read_set, &write_set, NULL, &tv) > 0) {
if (FD_ISSET(server->listening_sock, &read_set)) {
while ((conn = accept_new_connection(server)) != NULL) {
conn->expire_time = expire_time;
}
}
LINKED_LIST_FOREACH(&server->active_connections, lp, tmp) {
conn = LINKED_LIST_ENTRY(lp, struct mg_connection, link);
if (FD_ISSET(conn->client_sock, &read_set)) {
conn->expire_time = expire_time;
read_from_client(conn);
}
}
}
// Close expired connections and those that need to be closed
LINKED_LIST_FOREACH(&server->active_connections, lp, tmp) {
conn = LINKED_LIST_ENTRY(lp, struct mg_connection, link);
if (conn->flags & CONN_CLOSE || current_time > conn->expire_time) {
close_conn(conn);
}
}
}
void mg_destroy_server(struct mg_server **server) {
struct linked_list_link *lp, *tmp;
struct mg_connection *conn;
if (server != NULL && *server != NULL) {
closesocket((*server)->listening_sock);
LINKED_LIST_FOREACH(&(*server)->active_connections, lp, tmp) {
conn = LINKED_LIST_ENTRY(lp, struct mg_connection, link);
LINKED_LIST_REMOVE(&conn->link);
free(conn);
}
free(*server);
*server = NULL;
}
}
struct mg_server *mg_create_server(const char *opts[], mg_event_handler_t func,
void *user_data) {
struct mg_server *server = calloc(1, sizeof(*server));
const char *name, *value;
char error_msg[100] = {'\0'};
int i;
#ifdef _WIN32
WSADATA data;
WSAStartup(MAKEWORD(2, 2), &data);
#endif
server->event_handler = func;
server->user_data = user_data;
LINKED_LIST_INIT(&server->active_connections);
while (opts != NULL && (name = *opts++) != NULL) {
if ((i = get_option_index(name)) == -1) {
snprintf(error_msg, sizeof(error_msg), "Invalid option: [%s]", name);
} else if ((value = *opts++) == NULL) {
snprintf(error_msg, sizeof(error_msg), "[%s] cannot be NULL", name);
} else {
if (server->config_options[i] != NULL) {
free(server->config_options[i]);
}
server->config_options[i] = mg_strdup(value);
printf("[%s] -> [%s]\n", name, value);
}
}
// Set default values
for (i = 0; static_config_options[i * 2] != NULL; i++) {
value = static_config_options[i * 2 + 1];
if (server->config_options[i] == NULL && value != NULL) {
server->config_options[i] = mg_strdup(value);
}
}
parse_port_string(server->config_options[LISTENING_PORT], &server->lsa);
server->listening_sock = open_listening_socket(&server->lsa);
set_non_blocking_mode(server->listening_sock);
if (error_msg[0] != '\0') {
mg_destroy_server(&server);
}
return server;
}
int main(int argc, char *argv[]) {
const char *options[] = {"listening_port", "8080", NULL};
struct mg_server *server = mg_create_server(options, NULL, NULL);
for (;;) {
mg_poll_server(server, 1000);
}
mg_destroy_server(&server);
#if 0
char buf[1000];
int fds[2];
struct iobuf loc, rem;
mg_socketpair(fds);
if (!start_process(argv[1], NULL, NULL, fds[1])) {
printf("start_process() failed\n");
}
init_iobuf(&rem, buf, sizeof(buf), IO_FILE, (void *) fileno(stdout));
init_iobuf(&loc, NULL, 0, IO_SOCKET, (void *) fds[0]);
while ((rem.len = read_from_iobuf(&loc, rem.buf, rem.size)) > 0) {
write_to_iobuf(&rem);
}
close_iobuf(&rem);
close_iobuf(&loc);
#endif
return 0;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment