From f105b74dc093ffb82d30ba1ae849a422c794a132 Mon Sep 17 00:00:00 2001 From: valenok Date: Tue, 31 Aug 2010 08:01:14 +0100 Subject: [PATCH] experimental keep-alive, options defaults --- main.c | 11 ++- mongoose.c | 249 +++++++++++++++++++++++++++++---------------------- mongoose.h | 5 +- test/test.pl | 3 + 4 files changed, 157 insertions(+), 111 deletions(-) diff --git a/main.c b/main.c index 74b4346a..c8cd9f53 100644 --- a/main.c +++ b/main.c @@ -57,7 +57,7 @@ static int exit_flag; /* Program termination flag */ #if !defined(CONFIG_FILE) -#define CONFIG_FILE "mongoose.conf" +#define CONFIG_FILE "mongoose.conf" #endif /* !CONFIG_FILE */ #define MAX_OPTIONS 40 @@ -103,8 +103,9 @@ static void show_usage_and_exit(void) { fprintf(stderr, "OPTIONS:\n"); names = mg_get_valid_option_names(); - for (i = 0; names[i] != NULL; i += 2) { - fprintf(stderr, " -%s %s\n", names[i], names[i + 1]); + for (i = 0; names[i] != NULL; i += 3) { + fprintf(stderr, " -%s %s (default: \"%s\")\n", + names[i], names[i + 1], names[i + 2] == NULL ? "" : names[i + 2]); } fprintf(stderr, "See http://code.google.com/p/mongoose/wiki/MongooseManual" " for more details.\n"); @@ -164,6 +165,7 @@ static void process_command_line_arguments(char *argv[], char **options) { const char *config_file = NULL; char line[512], opt[512], val[512], path[PATH_MAX], *p; FILE *fp = NULL; + struct stat st; size_t i, line_no = 0; /* Should we use a config file ? */ @@ -175,6 +177,9 @@ static void process_command_line_arguments(char *argv[], char **options) { snprintf(path, sizeof(path), "%.*s%s", (int) (p - argv[0]) + 1, argv[0], CONFIG_FILE); } + if (stat(path, &st) == 0) { + config_file = path; + } } /* If config file was set in command line and open failed, exit */ if (config_file != NULL && (fp = fopen(config_file, "r")) == NULL) { diff --git a/mongoose.c b/mongoose.c index b10a0ece..5b6b5137 100644 --- a/mongoose.c +++ b/mongoose.c @@ -352,19 +352,31 @@ enum { CGI_INTERPRETER, CGI_ENVIRONMENT, SSI_EXTENSIONS, AUTHENTICATION_DOMAIN, URI_PROTECTION, GLOBAL_PASSWORDS_FILE, PUT_DELETE_PASSWORDS_FILE, ACCESS_LOG_FILE, ERROR_LOG_FILE, ACCESS_CONTROL_LIST, RUN_AS_USER, - EXTRA_MIME_TYPES, ENABLE_DIRECTORY_LISTING, NUM_THREADS, + EXTRA_MIME_TYPES, ENABLE_DIRECTORY_LISTING, ENABLE_KEEP_ALIVE, NUM_THREADS, NUM_OPTIONS }; -// There are two entries for each option: a short and a long version. -static const char *option_names[] = { - "r", "document_root", "p", "listening_ports", "i", "index_files", - "s", "ssl_certificate", "C", "cgi_extensions", "I", "cgi_interpreter", - "E", "cgi_environment", "S", "ssi_extensions", "R", "authentication_domain", - "P", "protect_uri", "g", "global_passwords_file", - "G", "put_delete_passwords_file", "a", "access_log_file", - "e", "error_log_file", "l", "access_control_list", "u", "run_as_user", - "m", "extra_mime_types", "d", "enable_directory_listing", "t", "num_threads", +static const char *config_options[] = { + "r", "document_root", ".", + "p", "listening_ports", "8080", + "i", "index_files", "index.html,index.htm,index.cgi", + "s", "ssl_certificate", NULL, + "C", "cgi_extensions", ".cgi,.pl,.php", + "I", "cgi_interpreter", NULL, + "E", "cgi_environment", NULL, + "S", "ssi_extensions", ".shtml,.shtm", + "R", "authentication_domain", "mydomain.com", + "P", "protect_uri", NULL, + "g", "global_passwords_file", NULL, + "G", "put_delete_passwords_file", NULL, + "a", "access_log_file", NULL, + "e", "error_log_file", NULL, + "l", "access_control_list", NULL, + "u", "run_as_user", NULL, + "m", "extra_mime_types", NULL, + "d", "enable_directory_listing", "yes", + "k", "enable_keep_alive", "no", + "t", "num_threads", "10", NULL }; @@ -402,7 +414,7 @@ struct mg_connection { }; const char **mg_get_valid_option_names(void) { - return option_names; + return config_options; } static void *call_user(struct mg_connection *conn, enum mg_event event) { @@ -412,10 +424,11 @@ static void *call_user(struct mg_connection *conn, enum mg_event event) { static int get_option_index(const char *name) { int i; - for (i = 0; option_names[i] != NULL; i += 2) { - if (strcmp(option_names[i], name) == 0 || - strcmp(option_names[i + 1], name) == 0) { - return i / 2; +#define ENTRIES_PER_OPTION 3 + for (i = 0; config_options[i] != NULL; i += ENTRIES_PER_OPTION) { + if (strcmp(config_options[i], name) == 0 || + strcmp(config_options[i + 1], name) == 0) { + return i / ENTRIES_PER_OPTION; } } return -1; @@ -448,7 +461,7 @@ static void cry(struct mg_connection *conn, const char *fmt, ...) { // same way string option can. conn->request_info.log_message = buf; if (call_user(conn, MG_EVENT_LOG) == NULL) { - fp = conn->ctx->config[ERROR_LOG_FILE] == NULL ? stderr : + fp = conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL : mg_fopen(conn->ctx->config[ERROR_LOG_FILE], "a+"); if (fp != NULL) { @@ -673,6 +686,17 @@ static int match_extension(const char *path, const char *ext_list) { } #endif // !NO_CGI +// HTTP 1.1 assumes keep alive if "Connection:" header is not set +static int should_keep_alive(const struct mg_connection *conn) { + const char *header = mg_get_header(conn, "Connection"); + return (header == NULL && !strcmp(conn->request_info.http_version, "1.1")) || + (header != NULL && !strcmp(header, "keep-alive")); +} + +static const char *suggest_connection_header(const struct mg_connection *conn) { + return should_keep_alive(conn) ? "keep-alive" : "close"; +} + static void send_http_error(struct mg_connection *conn, int status, const char *reason, const char *fmt, ...) { char buf[BUFSIZ]; @@ -700,7 +724,8 @@ static void send_http_error(struct mg_connection *conn, int status, mg_printf(conn, "HTTP/1.1 %d %s\r\n" "Content-Type: text/plain\r\n" "Content-Length: %d\r\n" - "Connection: close\r\n\r\n", status, reason, len); + "Connection: %s\r\n\r\n", status, reason, len, + suggest_connection_header(conn)); conn->num_bytes_sent += mg_printf(conn, "%s", buf); } } @@ -1266,8 +1291,8 @@ static int pull(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int len) { } int mg_read(struct mg_connection *conn, void *buf, size_t len) { - int n, buffered_data_len, nread; - const char *buffered_data; + int n, buffered_len, nread; + const char *buffered; assert(conn->content_len >= conn->consumed_content); DEBUG_TRACE(("%p %zu %lld %lld", buf, len, @@ -1283,21 +1308,21 @@ int mg_read(struct mg_connection *conn, void *buf, size_t len) { } // How many bytes of data we have buffered in the request buffer? - buffered_data = conn->buf + conn->request_len + conn->consumed_content; - buffered_data_len = conn->data_len - conn->request_len; - assert(buffered_data_len >= 0); + buffered = conn->buf + conn->request_len; + buffered_len = conn->data_len - conn->request_len; + assert(buffered_len >= 0); // Return buffered data back if we haven't done that yet. - if (conn->consumed_content < (int64_t) buffered_data_len) { - buffered_data_len -= conn->consumed_content; - if (len < (size_t) buffered_data_len) { - buffered_data_len = len; + if (conn->consumed_content < (int64_t) buffered_len) { + buffered_len -= conn->consumed_content; + if (len < (size_t) buffered_len) { + buffered_len = len; } - memcpy(buf, buffered_data, buffered_data_len); - len -= buffered_data_len; - buf = (char *) buf + buffered_data_len; - conn->consumed_content += buffered_data_len; - nread = buffered_data_len; + memcpy(buf, buffered, buffered_len); + len -= buffered_len; + buf = (char *) buf + buffered_len; + conn->consumed_content += buffered_len; + nread = buffered_len; } // We have returned all buffered data. Read new data from the remote socket. @@ -2449,11 +2474,11 @@ static void handle_file_request(struct mg_connection *conn, const char *path, "Etag: \"%s\"\r\n" "Content-Type: %.*s\r\n" "Content-Length: %" INT64_FMT "\r\n" - "Connection: close\r\n" + "Connection: %s\r\n" "Accept-Ranges: bytes\r\n" "%s\r\n", conn->request_info.status_code, msg, date, lm, etag, - mime_vec.len, mime_vec.ptr, cl, range); + mime_vec.len, mime_vec.ptr, cl, suggest_connection_header(conn), range); if (strcmp(conn->request_info.request_method, "HEAD") != 0) { send_file_data(conn, fp, cl); @@ -2487,6 +2512,11 @@ static int is_valid_http_method(const char *method) { static int parse_http_request(char *buf, struct mg_request_info *ri) { int status = 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"); @@ -2580,10 +2610,9 @@ static int is_not_modified(const struct mg_connection *conn, } static int handle_request_body(struct mg_connection *conn, FILE *fp) { - const char *expect, *data; + const char *expect, *buffered; char buf[BUFSIZ]; - int to_read, nread, data_len; - int success = 0; + int to_read, nread, buffered_len, success = 0; expect = mg_get_header(conn, "Expect"); assert(fp != NULL); @@ -2596,31 +2625,34 @@ static int handle_request_body(struct mg_connection *conn, FILE *fp) { if (expect != NULL) { (void) mg_printf(conn, "%s", "HTTP/1.1 100 Continue\r\n\r\n"); } - data = conn->buf + conn->request_len; - data_len = conn->data_len - conn->request_len; - assert(data_len >= 0); - if (conn->content_len <= (int64_t) data_len) { - success = push(fp, INVALID_SOCKET, NULL, data, - conn->content_len) == conn->content_len; - } else { - push(fp, INVALID_SOCKET, NULL, data, (int64_t) data_len); - conn->consumed_content += data_len; + buffered = conn->buf + conn->request_len; + buffered_len = conn->data_len - conn->request_len; + assert(buffered_len >= 0); + assert(conn->consumed_content == 0); - while (conn->consumed_content < conn->content_len) { - to_read = sizeof(buf); - if ((int64_t) to_read > conn->content_len - conn->consumed_content) { - to_read = (int) (conn->content_len - conn->consumed_content); - } - nread = pull(NULL, conn->client.sock, conn->ssl, buf, to_read); - if (nread <= 0 || push(fp, INVALID_SOCKET, NULL, buf, nread) != nread) { - break; - } - conn->consumed_content += nread; + if (buffered_len > 0) { + if ((int64_t) buffered_len > conn->content_len) { + buffered_len = conn->content_len; } - if (conn->consumed_content == conn->content_len) { - success = 1; + push(fp, INVALID_SOCKET, NULL, buffered, (int64_t) buffered_len); + conn->consumed_content += buffered_len; + } + + while (conn->consumed_content < conn->content_len) { + to_read = sizeof(buf); + if ((int64_t) to_read > conn->content_len - conn->consumed_content) { + to_read = (int) (conn->content_len - conn->consumed_content); } + nread = pull(NULL, conn->client.sock, conn->ssl, buf, to_read); + if (nread <= 0 || push(fp, INVALID_SOCKET, NULL, buf, nread) != nread) { + break; + } + conn->consumed_content += nread; + } + + if (conn->consumed_content == conn->content_len) { + success = 1; } // Each error code path in this function must send an error @@ -3088,8 +3120,9 @@ static void handle_ssi_file_request(struct mg_connection *conn, strerror(ERRNO)); } else { set_close_on_exec(fileno(fp)); - (void) mg_printf(conn, "%s", "HTTP/1.1 200 OK\r\n" - "Content-Type: text/html\r\nConnection: close\r\n\r\n"); + mg_printf(conn, "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html\r\nConnection: %s\r\n\r\n", + suggest_connection_header(conn)); send_ssi_file(conn, path, fp, 0); (void) fclose(fp); } @@ -3505,17 +3538,19 @@ static void close_connection(struct mg_connection *conn) { } static void discard_current_request_from_buffer(struct mg_connection *conn) { - int over_len, body_len; + char *buffered; + int buffered_len, body_len; - over_len = conn->data_len - conn->request_len; - assert(over_len >= 0); + buffered = conn->buf + conn->request_len; + buffered_len = conn->data_len - conn->request_len; + assert(buffered_len >= 0); if (conn->content_len == -1) { body_len = 0; - } else if (conn->content_len < (int64_t) over_len) { + } else if (conn->content_len < (int64_t) buffered_len) { body_len = (int) conn->content_len; } else { - body_len = over_len; + body_len = buffered_len; } conn->data_len -= conn->request_len + body_len; @@ -3524,40 +3559,45 @@ static void discard_current_request_from_buffer(struct mg_connection *conn) { static void process_new_connection(struct mg_connection *conn) { struct mg_request_info *ri = &conn->request_info; + int keep_alive_enabled; const char *cl; - reset_per_request_attributes(conn); + keep_alive_enabled = !strcmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes"); - // If next request is not pipelined, read it in - if ((conn->request_len = get_request_len(conn->buf, conn->data_len)) == 0) { - conn->request_len = read_request(NULL, conn->client.sock, conn->ssl, - conn->buf, sizeof(conn->buf), &conn->data_len); - } - assert(conn->data_len >= conn->request_len); - if (conn->request_len <= 0) { - return; // Remote end closed the connection - } + do { + reset_per_request_attributes(conn); - // Nul-terminate the request cause parse_http_request() uses sscanf - conn->buf[conn->request_len - 1] = '\0'; - if (!parse_http_request(conn->buf, ri)) { - // Do not put garbage in the access log, just send it back to the client - send_http_error(conn, 400, "Bad Request", - "Cannot parse HTTP request: [%.*s]", conn->data_len, conn->buf); - } else if (strcmp(ri->http_version, "1.0") && - strcmp(ri->http_version, "1.1")) { - // Request seems valid, but HTTP version is strange - send_http_error(conn, 505, "HTTP version not supported", ""); - log_access(conn); - } else { - // Request is valid, handle it - cl = get_header(ri, "Content-Length"); - conn->content_len = cl == NULL ? -1 : strtoll(cl, NULL, 10); - conn->birth_time = time(NULL); - handle_request(conn); - log_access(conn); - discard_current_request_from_buffer(conn); - } + // If next request is not pipelined, read it in + if ((conn->request_len = get_request_len(conn->buf, conn->data_len)) == 0) { + conn->request_len = read_request(NULL, conn->client.sock, conn->ssl, + conn->buf, sizeof(conn->buf), &conn->data_len); + } + assert(conn->data_len >= conn->request_len); + if (conn->request_len <= 0) { + return; // Remote end closed the connection + } + + // Nul-terminate the request cause parse_http_request() uses sscanf + conn->buf[conn->request_len - 1] = '\0'; + if (!parse_http_request(conn->buf, ri)) { + // Do not put garbage in the access log, just send it back to the client + send_http_error(conn, 400, "Bad Request", + "Cannot parse HTTP request: [%.*s]", conn->data_len, conn->buf); + } else if (strcmp(ri->http_version, "1.0") && + strcmp(ri->http_version, "1.1")) { + // Request seems valid, but HTTP version is strange + send_http_error(conn, 505, "HTTP version not supported", ""); + log_access(conn); + } else { + // Request is valid, handle it + cl = get_header(ri, "Content-Length"); + conn->content_len = cl == NULL ? -1 : strtoll(cl, NULL, 10); + conn->birth_time = time(NULL); + handle_request(conn); + log_access(conn); + discard_current_request_from_buffer(conn); + } + } while (keep_alive_enabled && should_keep_alive(conn)); } // Worker threads take accepted socket from the queue @@ -3788,15 +3828,6 @@ struct mg_context *mg_start(mg_callback_t user_callback, const char **options) { ctx = calloc(1, sizeof(*ctx)); ctx->user_callback = user_callback; - ctx->config[DOCUMENT_ROOT] = mg_strdup("."); - ctx->config[LISTENING_PORTS] = mg_strdup("8080"); - ctx->config[ENABLE_DIRECTORY_LISTING] = mg_strdup("yes"); - ctx->config[AUTHENTICATION_DOMAIN] = mg_strdup("mydomain.com"); - ctx->config[INDEX_FILES] = mg_strdup("index.html,index.htm,index.cgi"); - ctx->config[CGI_EXTENSIONS] = mg_strdup(".cgi,.pl,.php"); - ctx->config[SSI_EXTENSIONS] = mg_strdup(".shtml,.shtm"); - ctx->config[NUM_THREADS] = mg_strdup("10"); - while ((name = *options++) != NULL) { if ((i = get_option_index(name)) == -1) { cry(fc(ctx), "Invalid option: %s", name); @@ -3807,13 +3838,19 @@ struct mg_context *mg_start(mg_callback_t user_callback, const char **options) { free_context(ctx); return NULL; } - if (ctx->config[i] != NULL) { - free(ctx->config[i]); - } ctx->config[i] = mg_strdup(value); DEBUG_TRACE(("[%s] -> [%s]", name, value)); } + // Set default value if needed + for (i = 0; config_options[i * 3] != NULL; i++) { + if (ctx->config[i] == NULL && config_options[i * 3 + 2] != NULL) { + ctx->config[i] = mg_strdup(config_options[i * 3 + 2]); + DEBUG_TRACE(("Setting default: [%s] -> [%s]", + config_options[i * 3 + 1], config_options[i * 3 + 2])); + } + } + // NOTE(lsm): order is important here. SSL certificates must // be initialized before listening ports. UID must be set last. if (!set_ssl_option(ctx) || diff --git a/mongoose.h b/mongoose.h index 9b35c747..fc74e91a 100644 --- a/mongoose.h +++ b/mongoose.h @@ -118,8 +118,9 @@ void mg_stop(struct mg_context *); const char *mg_get_option(const struct mg_context *ctx, const char *name); -// Return array of valid configuration options. For each option, a short -// version and a long version is returned. Array is NULL terminated. +// Return array of strings that represent valid configuration options. +// For each option, a short name, long name, and default value is returned. +// Array is NULL terminated. const char **mg_get_valid_option_names(void); diff --git a/test/test.pl b/test/test.pl index 7ce1e4b1..18bed826 100644 --- a/test/test.pl +++ b/test/test.pl @@ -211,6 +211,9 @@ o("GET /$test_dir_uri/x/ HTTP/1.0\n\n", "Content-Type: text/html\r\n\r\n", 'index.cgi execution'); o("GET /ta/x/ HTTP/1.0\n\n", "SCRIPT_NAME=/ta/x/index.cgi", 'Aliases SCRIPT_NAME'); +#o("GET /hello.txt HTTP/1.1\n\n GET /hello.txt HTTP/1.0\n\n", +# 'HTTP/1.1 200.+keep-alive.+HTTP/1.1 200.+close', +# 'Request pipelining', 2); my $mime_types = { html => 'text/html',