mirror of
https://github.com/cesanta/mongoose.git
synced 2024-12-27 06:51:04 +08:00
Refactored API, returned back to event-based handlers. Upload and Websocket API simplified
This commit is contained in:
parent
96eb48902b
commit
ecbf79135f
12
build/main.c
12
build/main.c
@ -271,9 +271,10 @@ static void init_server_name(void) {
|
||||
mg_version());
|
||||
}
|
||||
|
||||
static int log_message(const struct mg_connection *conn, const char *message) {
|
||||
(void) conn;
|
||||
printf("%s\n", message);
|
||||
static int event_handler(struct mg_event *event) {
|
||||
if (event->type == MG_EVENT_LOG) {
|
||||
printf("%s\n", (const char *) event->event_param);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -341,7 +342,6 @@ static void set_absolute_path(char *options[], const char *option_name,
|
||||
}
|
||||
|
||||
static void start_mongoose(int argc, char *argv[]) {
|
||||
struct mg_callbacks callbacks;
|
||||
char *options[MAX_OPTIONS];
|
||||
int i;
|
||||
|
||||
@ -385,9 +385,7 @@ static void start_mongoose(int argc, char *argv[]) {
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
// Start Mongoose
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
callbacks.log_message = &log_message;
|
||||
ctx = mg_start(&callbacks, NULL, (const char **) options);
|
||||
ctx = mg_start((const char **) options, event_handler, NULL);
|
||||
for (i = 0; options[i] != NULL; i++) {
|
||||
free(options[i]);
|
||||
}
|
||||
|
@ -326,9 +326,12 @@ static void redirect_to_ssl(struct mg_connection *conn,
|
||||
}
|
||||
}
|
||||
|
||||
static int begin_request_handler(struct mg_connection *conn) {
|
||||
const struct mg_request_info *request_info = mg_get_request_info(conn);
|
||||
int processed = 1;
|
||||
static int event_handler(struct mg_event *event) {
|
||||
struct mg_request_info *request_info = event->request_info;
|
||||
struct mg_connection *conn = event->conn;
|
||||
int result = 1;
|
||||
|
||||
if (event->type != MG_REQUEST_BEGIN) return 0;
|
||||
|
||||
if (!request_info->is_ssl) {
|
||||
redirect_to_ssl(conn, request_info);
|
||||
@ -343,9 +346,10 @@ static int begin_request_handler(struct mg_connection *conn) {
|
||||
} else {
|
||||
// No suitable handler found, mark as not processed. Mongoose will
|
||||
// try to serve the request.
|
||||
processed = 0;
|
||||
result = 0;
|
||||
}
|
||||
return processed;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static const char *options[] = {
|
||||
@ -357,7 +361,6 @@ static const char *options[] = {
|
||||
};
|
||||
|
||||
int main(void) {
|
||||
struct mg_callbacks callbacks;
|
||||
struct mg_context *ctx;
|
||||
|
||||
// Initialize random number generator. It will be used later on for
|
||||
@ -365,9 +368,7 @@ int main(void) {
|
||||
srand((unsigned) time(0));
|
||||
|
||||
// Setup and start Mongoose
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
callbacks.begin_request = begin_request_handler;
|
||||
if ((ctx = mg_start(&callbacks, NULL, options)) == NULL) {
|
||||
if ((ctx = mg_start(options, event_handler, NULL)) == NULL) {
|
||||
printf("%s\n", "Cannot start chat server, fatal exit");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
@ -3,42 +3,42 @@
|
||||
#include "mongoose.h"
|
||||
|
||||
// This function will be called by mongoose on every new request.
|
||||
static int begin_request_handler(struct mg_connection *conn) {
|
||||
const struct mg_request_info *request_info = mg_get_request_info(conn);
|
||||
char content[100];
|
||||
static int event_handler(struct mg_event *event) {
|
||||
|
||||
// Prepare the message we're going to send
|
||||
int content_length = snprintf(content, sizeof(content),
|
||||
"Hello from mongoose! Remote port: %d",
|
||||
request_info->remote_port);
|
||||
if (event->type == MG_REQUEST_BEGIN) {
|
||||
char content[100];
|
||||
|
||||
// Send HTTP reply to the client
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Content-Length: %d\r\n" // Always set Content-Length
|
||||
"\r\n"
|
||||
"%s",
|
||||
content_length, content);
|
||||
// Prepare the message we're going to send
|
||||
int content_length = snprintf(content, sizeof(content),
|
||||
"Hello from mongoose! Requested: [%s] [%s]",
|
||||
event->request_info->request_method, event->request_info->uri);
|
||||
|
||||
// Returning non-zero tells mongoose that our function has replied to
|
||||
// the client, and mongoose should not send client any more data.
|
||||
return 1;
|
||||
// Send HTTP reply to the client
|
||||
mg_printf(event->conn,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Content-Length: %d\r\n" // Always set Content-Length
|
||||
"\r\n"
|
||||
"%s",
|
||||
content_length, content);
|
||||
|
||||
// Returning non-zero tells mongoose that our function has replied to
|
||||
// the client, and mongoose should not send client any more data.
|
||||
return 1;
|
||||
}
|
||||
|
||||
// We do not handle any other event
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_context *ctx;
|
||||
struct mg_callbacks callbacks;
|
||||
|
||||
// List of options. Last element must be NULL.
|
||||
const char *options[] = {"listening_ports", "8080", NULL};
|
||||
|
||||
// Prepare callbacks structure. We have only one callback, the rest are NULL.
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
callbacks.begin_request = begin_request_handler;
|
||||
|
||||
// Start the web server.
|
||||
ctx = mg_start(&callbacks, NULL, options);
|
||||
ctx = mg_start(options, &event_handler, NULL);
|
||||
|
||||
// Wait until user hits "enter". Server is running in separate thread.
|
||||
// Navigating to http://localhost:8080 will invoke begin_request_handler().
|
||||
|
@ -10,45 +10,47 @@ static const char *html_form =
|
||||
"<input type=\"submit\" />"
|
||||
"</form></body></html>";
|
||||
|
||||
static int begin_request_handler(struct mg_connection *conn) {
|
||||
const struct mg_request_info *ri = mg_get_request_info(conn);
|
||||
static int event_handler(struct mg_event *event) {
|
||||
char post_data[1024], input1[sizeof(post_data)], input2[sizeof(post_data)];
|
||||
int post_data_len;
|
||||
|
||||
if (!strcmp(ri->uri, "/handle_post_request")) {
|
||||
// User has submitted a form, show submitted data and a variable value
|
||||
post_data_len = mg_read(conn, post_data, sizeof(post_data));
|
||||
if (event->type == MG_REQUEST_BEGIN) {
|
||||
if (!strcmp(event->request_info->uri, "/handle_post_request")) {
|
||||
// User has submitted a form, show submitted data and a variable value
|
||||
post_data_len = mg_read(event->conn, post_data, sizeof(post_data));
|
||||
|
||||
// Parse form data. input1 and input2 are guaranteed to be NUL-terminated
|
||||
mg_get_var(post_data, post_data_len, "input_1", input1, sizeof(input1));
|
||||
mg_get_var(post_data, post_data_len, "input_2", input2, sizeof(input2));
|
||||
// Parse form data. input1 and input2 are guaranteed to be NUL-terminated
|
||||
mg_get_var(post_data, post_data_len, "input_1", input1, sizeof(input1));
|
||||
mg_get_var(post_data, post_data_len, "input_2", input2, sizeof(input2));
|
||||
|
||||
// Send reply to the client, showing submitted form values.
|
||||
mg_printf(conn, "HTTP/1.0 200 OK\r\n"
|
||||
"Content-Type: text/plain\r\n\r\n"
|
||||
"Submitted data: [%.*s]\n"
|
||||
"Submitted data length: %d bytes\n"
|
||||
"input_1: [%s]\n"
|
||||
"input_2: [%s]\n",
|
||||
post_data_len, post_data, post_data_len, input1, input2);
|
||||
} else {
|
||||
// Show HTML form.
|
||||
mg_printf(conn, "HTTP/1.0 200 OK\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"Content-Type: text/html\r\n\r\n%s",
|
||||
(int) strlen(html_form), html_form);
|
||||
// Send reply to the client, showing submitted form values.
|
||||
mg_printf(event->conn, "HTTP/1.0 200 OK\r\n"
|
||||
"Content-Type: text/plain\r\n\r\n"
|
||||
"Submitted data: [%.*s]\n"
|
||||
"Submitted data length: %d bytes\n"
|
||||
"input_1: [%s]\n"
|
||||
"input_2: [%s]\n",
|
||||
post_data_len, post_data, post_data_len, input1, input2);
|
||||
} else {
|
||||
// Show HTML form.
|
||||
mg_printf(event->conn, "HTTP/1.0 200 OK\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"Content-Type: text/html\r\n\r\n%s",
|
||||
(int) strlen(html_form), html_form);
|
||||
}
|
||||
|
||||
return 1; // Mark event as processed
|
||||
}
|
||||
return 1; // Mark request as processed
|
||||
|
||||
// All other events are left not processed
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_context *ctx;
|
||||
const char *options[] = {"listening_ports", "8080", NULL};
|
||||
struct mg_callbacks callbacks;
|
||||
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
callbacks.begin_request = begin_request_handler;
|
||||
ctx = mg_start(&callbacks, NULL, options);
|
||||
ctx = mg_start(options, &event_handler, NULL);
|
||||
getchar(); // Wait until user hits "enter"
|
||||
mg_stop(ctx);
|
||||
|
||||
|
@ -3,57 +3,48 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#define strtoll strtol
|
||||
typedef __int64 int64_t;
|
||||
#else
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#endif // !_WIN32
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
static int begin_request_handler(struct mg_connection *conn) {
|
||||
if (!strcmp(mg_get_request_info(conn)->uri, "/handle_post_request")) {
|
||||
mg_printf(conn, "%s", "HTTP/1.0 200 OK\r\n\r\n");
|
||||
mg_upload(conn, "/tmp");
|
||||
} else {
|
||||
// Show HTML form. Make sure it has enctype="multipart/form-data" attr.
|
||||
static const char *html_form =
|
||||
"<html><body>Upload example."
|
||||
"<form method=\"POST\" action=\"/handle_post_request\" "
|
||||
" enctype=\"multipart/form-data\">"
|
||||
"<input type=\"file\" name=\"file\" /> <br/>"
|
||||
"<input type=\"submit\" value=\"Upload\" />"
|
||||
"</form></body></html>";
|
||||
static int event_handler(struct mg_event *event) {
|
||||
|
||||
mg_printf(conn, "HTTP/1.0 200 OK\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"Content-Type: text/html\r\n\r\n%s",
|
||||
(int) strlen(html_form), html_form);
|
||||
if (event->type == MG_REQUEST_BEGIN) {
|
||||
if (!strcmp(event->request_info->uri, "/handle_post_request")) {
|
||||
char path[200];
|
||||
FILE *fp = mg_upload(event->conn, "/tmp", path, sizeof(path));
|
||||
if (fp != NULL) {
|
||||
fclose(fp);
|
||||
mg_printf(event->conn, "HTTP/1.0 200 OK\r\n\r\nSaved: [%s]", path);
|
||||
} else {
|
||||
mg_printf(event->conn, "%s", "HTTP/1.0 200 OK\r\n\r\nNo files sent");
|
||||
}
|
||||
} else {
|
||||
// Show HTML form. Make sure it has enctype="multipart/form-data" attr.
|
||||
static const char *html_form =
|
||||
"<html><body>Upload example."
|
||||
"<form method=\"POST\" action=\"/handle_post_request\" "
|
||||
" enctype=\"multipart/form-data\">"
|
||||
"<input type=\"file\" name=\"file\" /> <br/>"
|
||||
"<input type=\"submit\" value=\"Upload\" />"
|
||||
"</form></body></html>";
|
||||
|
||||
mg_printf(event->conn, "HTTP/1.0 200 OK\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"Content-Type: text/html\r\n\r\n%s",
|
||||
(int) strlen(html_form), html_form);
|
||||
}
|
||||
|
||||
// Mark request as processed
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Mark request as processed
|
||||
// All other events left unprocessed
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void upload_handler(struct mg_connection *conn, const char *path) {
|
||||
mg_printf(conn, "Saved [%s]", path);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_context *ctx;
|
||||
const char *options[] = {"listening_ports", "8080", NULL};
|
||||
struct mg_callbacks callbacks;
|
||||
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
callbacks.begin_request = begin_request_handler;
|
||||
callbacks.upload = upload_handler;
|
||||
ctx = mg_start(&callbacks, NULL, options);
|
||||
ctx = mg_start(options, event_handler, NULL);
|
||||
getchar(); // Wait until user hits "enter"
|
||||
mg_stop(ctx);
|
||||
|
||||
|
@ -5,38 +5,52 @@
|
||||
#include <string.h>
|
||||
#include "mongoose.h"
|
||||
|
||||
static void websocket_ready_handler(struct mg_connection *conn) {
|
||||
static const char *message = "server ready";
|
||||
mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, message, strlen(message));
|
||||
}
|
||||
static int event_handler(struct mg_event *event) {
|
||||
|
||||
// Arguments:
|
||||
// flags: first byte of websocket frame, see websocket RFC,
|
||||
// http://tools.ietf.org/html/rfc6455, section 5.2
|
||||
// data, data_len: payload data. Mask, if any, is already applied.
|
||||
static int websocket_data_handler(struct mg_connection *conn, int flags,
|
||||
char *data, size_t data_len) {
|
||||
(void) flags; // Unused
|
||||
mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, data, data_len);
|
||||
if (event->type == MG_REQUEST_BEGIN) {
|
||||
const char *version_header = mg_get_header(event->conn,
|
||||
"Sec-WebSocket-Version");
|
||||
|
||||
// Returning zero means stoping websocket conversation.
|
||||
// Close the conversation if client has sent us "exit" string.
|
||||
return memcmp(data, "exit", 4);
|
||||
if (version_header != NULL) {
|
||||
// Websocket request, process it
|
||||
if (strcmp(version_header, "13") != 0) {
|
||||
mg_printf(event->conn, "%s", "HTTP/1.1 426 Upgrade Required\r\n\r\n");
|
||||
} else {
|
||||
static const char *server_ready_message = "server ready";
|
||||
char *data;
|
||||
int bits, len;
|
||||
|
||||
// Handshake, and send initial server message
|
||||
mg_websocket_handshake(event->conn);
|
||||
mg_websocket_write(event->conn, WEBSOCKET_OPCODE_TEXT,
|
||||
server_ready_message, strlen(server_ready_message));
|
||||
|
||||
while ((len = mg_websocket_read(event->conn, &bits, &data)) > 0) {
|
||||
// Echo message back to the client
|
||||
mg_websocket_write(event->conn, WEBSOCKET_OPCODE_TEXT, data, len);
|
||||
if (memcmp(data, "exit", 4) == 0) {
|
||||
mg_websocket_write(event->conn,
|
||||
WEBSOCKET_OPCODE_CONNECTION_CLOSE, "", 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_context *ctx;
|
||||
struct mg_callbacks callbacks;
|
||||
const char *options[] = {
|
||||
"listening_ports", "8080",
|
||||
"document_root", "websocket_html_root",
|
||||
NULL
|
||||
};
|
||||
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
callbacks.websocket_ready = websocket_ready_handler;
|
||||
callbacks.websocket_data = websocket_data_handler;
|
||||
ctx = mg_start(&callbacks, NULL, options);
|
||||
ctx = mg_start(options, &event_handler, NULL);
|
||||
getchar(); // Wait until user hits "enter"
|
||||
mg_stop(ctx);
|
||||
|
||||
|
323
mongoose.c
323
mongoose.c
@ -345,7 +345,7 @@ struct ssl_func {
|
||||
#define SSL_CTX_use_certificate_file (* (int (*)(SSL_CTX *, \
|
||||
const char *, int)) ssl_sw[12].ptr)
|
||||
#define SSL_CTX_set_default_passwd_cb \
|
||||
(* (void (*)(SSL_CTX *, mg_callback_t)) ssl_sw[13].ptr)
|
||||
(* (void (*)(SSL_CTX *, mg_event_handler_t)) ssl_sw[13].ptr)
|
||||
#define SSL_CTX_free (* (void (*)(SSL_CTX *)) ssl_sw[14].ptr)
|
||||
#define SSL_load_error_strings (* (void (*)(void)) ssl_sw[15].ptr)
|
||||
#define SSL_CTX_use_certificate_chain_file \
|
||||
@ -490,8 +490,7 @@ struct mg_context {
|
||||
volatile int stop_flag; // Should we stop event loop
|
||||
SSL_CTX *ssl_ctx; // SSL context
|
||||
char *config[NUM_OPTIONS]; // Mongoose configuration parameters
|
||||
struct mg_callbacks callbacks; // User-defined callback function
|
||||
mg_callback_t user_callback; // User-defined callback function
|
||||
mg_event_handler_t event_handler; // User-defined callback function
|
||||
void *user_data; // User-defined data
|
||||
|
||||
struct socket *listening_sockets;
|
||||
@ -510,6 +509,7 @@ struct mg_context {
|
||||
|
||||
struct mg_connection {
|
||||
struct mg_request_info request_info;
|
||||
struct mg_event event;
|
||||
struct mg_context *ctx;
|
||||
SSL *ssl; // SSL descriptor
|
||||
SSL_CTX *client_ssl_ctx; // SSL context for client connections
|
||||
@ -517,7 +517,7 @@ struct mg_connection {
|
||||
time_t birth_time; // Time when request was received
|
||||
int64_t num_bytes_sent; // Total bytes sent to client
|
||||
int64_t content_len; // Content-Length header value
|
||||
int64_t consumed_content; // How many bytes of content have been read
|
||||
int64_t num_bytes_read; // Bytes read from a remote socket
|
||||
char *buf; // Buffer for received data
|
||||
char *path_info; // PATH_INFO part of the URL
|
||||
int must_close; // 1 if connection must be closed
|
||||
@ -537,16 +537,25 @@ struct de {
|
||||
struct file file;
|
||||
};
|
||||
|
||||
// Return number of bytes left to read for this connection
|
||||
static int64_t left_to_read(const struct mg_connection *conn) {
|
||||
return conn->content_len + conn->request_len - conn->num_bytes_read;
|
||||
}
|
||||
|
||||
const char **mg_get_valid_option_names(void) {
|
||||
return config_options;
|
||||
}
|
||||
|
||||
static int call_user(enum mg_event ev, struct mg_connection *conn, void *p) {
|
||||
static int call_user(int type, struct mg_connection *conn, void *p) {
|
||||
if (conn != NULL && conn->ctx != NULL) {
|
||||
conn->request_info.user_data = conn->ctx->user_data;
|
||||
conn->event.user_data = conn->ctx->user_data;
|
||||
conn->event.type = type;
|
||||
conn->event.event_param = p;
|
||||
conn->event.request_info = &conn->request_info;
|
||||
conn->event.conn = conn;
|
||||
}
|
||||
return conn == NULL || conn->ctx == NULL || conn->ctx->user_callback == NULL ?
|
||||
0 : conn->ctx->user_callback(ev, conn, p);
|
||||
return conn == NULL || conn->ctx == NULL || conn->ctx->event_handler == NULL ?
|
||||
0 : conn->ctx->event_handler(&conn->event);
|
||||
}
|
||||
|
||||
static FILE *mg_fopen(const char *path, const char *mode) {
|
||||
@ -614,8 +623,7 @@ static void cry(struct mg_connection *conn, const char *fmt, ...) {
|
||||
// Do not lock when getting the callback value, here and below.
|
||||
// I suppose this is fine, since function cannot disappear in the
|
||||
// same way string option can.
|
||||
if (conn->ctx->callbacks.log_message == NULL ||
|
||||
conn->ctx->callbacks.log_message(conn, buf) == 0) {
|
||||
if (call_user(MG_EVENT_LOG, conn, buf) == 0) {
|
||||
fp = conn->ctx == NULL || conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL :
|
||||
fopen(conn->ctx->config[ERROR_LOG_FILE], "a+");
|
||||
|
||||
@ -646,7 +654,7 @@ static struct mg_connection *fc(struct mg_context *ctx) {
|
||||
static struct mg_connection fake_connection;
|
||||
fake_connection.ctx = ctx;
|
||||
// See https://github.com/cesanta/mongoose/issues/236
|
||||
fake_connection.request_info.user_data = ctx->user_data;
|
||||
fake_connection.event.user_data = ctx->user_data;
|
||||
return &fake_connection;
|
||||
}
|
||||
|
||||
@ -1497,6 +1505,7 @@ static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
|
||||
static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) {
|
||||
int nread;
|
||||
|
||||
if (len <= 0) return 0;
|
||||
if (fp != NULL) {
|
||||
// Use read() instead of fread(), because if we're reading from the CGI
|
||||
// pipe, fread() may block until IO buffer is filled up. We cannot afford
|
||||
@ -1509,6 +1518,9 @@ static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) {
|
||||
} else {
|
||||
nread = recv(conn->client.sock, buf, (size_t) len, 0);
|
||||
}
|
||||
if (nread > 0) {
|
||||
conn->num_bytes_read += nread;
|
||||
}
|
||||
|
||||
return conn->ctx->stop_flag ? -1 : nread;
|
||||
}
|
||||
@ -1524,7 +1536,6 @@ static int pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) {
|
||||
} else if (n == 0) {
|
||||
break; // No more data to read
|
||||
} else {
|
||||
conn->consumed_content += n;
|
||||
nread += n;
|
||||
len -= n;
|
||||
}
|
||||
@ -1533,46 +1544,48 @@ static int pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) {
|
||||
return nread;
|
||||
}
|
||||
|
||||
int mg_read(struct mg_connection *conn, void *buf, size_t len) {
|
||||
int n, buffered_len, nread;
|
||||
const char *body;
|
||||
int mg_read(struct mg_connection *conn, void *buf, int len) {
|
||||
int n, buffered_len, nread = 0;
|
||||
int64_t left;
|
||||
|
||||
// If Content-Length is not set, read until socket is closed
|
||||
if (conn->consumed_content == 0 && conn->content_len == 0) {
|
||||
if (conn->content_len <= 0) {
|
||||
conn->content_len = INT64_MAX;
|
||||
conn->must_close = 1;
|
||||
}
|
||||
|
||||
nread = 0;
|
||||
if (conn->consumed_content < conn->content_len) {
|
||||
// Adjust number of bytes to read.
|
||||
int64_t to_read = conn->content_len - conn->consumed_content;
|
||||
if (to_read < (int64_t) len) {
|
||||
len = (size_t) to_read;
|
||||
}
|
||||
// conn->buf body
|
||||
// |=================|==========|===============|
|
||||
// |<--request_len-->| |
|
||||
// |<-----------data_len------->| conn->buf + conn->buf_size
|
||||
|
||||
// Return buffered data
|
||||
body = conn->buf + conn->request_len + conn->consumed_content;
|
||||
buffered_len = &conn->buf[conn->data_len] - body;
|
||||
if (buffered_len > 0) {
|
||||
if (len < (size_t) buffered_len) {
|
||||
buffered_len = (int) len;
|
||||
}
|
||||
memcpy(buf, body, (size_t) buffered_len);
|
||||
len -= buffered_len;
|
||||
conn->consumed_content += buffered_len;
|
||||
nread += buffered_len;
|
||||
buf = (char *) buf + buffered_len;
|
||||
}
|
||||
// First, check for data buffered in conn->buf by read_request().
|
||||
if (len > 0 && (buffered_len = conn->data_len - conn->request_len) > 0) {
|
||||
char *body = conn->buf + conn->request_len;
|
||||
if (buffered_len > len) buffered_len = len;
|
||||
if (buffered_len > conn->content_len) buffered_len = conn->content_len;
|
||||
|
||||
// We have returned all buffered data. Read new data from the remote socket.
|
||||
n = pull_all(NULL, conn, (char *) buf, (int) len);
|
||||
memcpy(buf, body, (size_t) buffered_len);
|
||||
memmove(body, body + buffered_len,
|
||||
&conn->buf[conn->data_len] - &body[buffered_len]);
|
||||
len -= buffered_len;
|
||||
conn->data_len -= buffered_len;
|
||||
nread += buffered_len;
|
||||
}
|
||||
|
||||
// Read data from the socket.
|
||||
if (len > 0 && (left = left_to_read(conn)) > 0) {
|
||||
if (left < len) {
|
||||
len = (int) left;
|
||||
}
|
||||
n = pull_all(NULL, conn, (char *) buf + nread, (int) len);
|
||||
nread = n >= 0 ? nread + n : n;
|
||||
}
|
||||
|
||||
return nread;
|
||||
}
|
||||
|
||||
int mg_write(struct mg_connection *conn, const void *buf, size_t len) {
|
||||
int mg_write(struct mg_connection *conn, const void *buf, int len) {
|
||||
time_t now;
|
||||
int64_t n, total, allowed;
|
||||
|
||||
@ -1864,25 +1877,25 @@ static int convert_uri_to_file_name(struct mg_connection *conn, char *buf,
|
||||
// -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 char *buf, int buflen) {
|
||||
const char *s, *e;
|
||||
int len = 0;
|
||||
static int get_request_len(const char *buf, int buf_len) {
|
||||
int i;
|
||||
|
||||
for (s = buf, e = s + buflen - 1; len <= 0 && s < e; s++)
|
||||
for (i = 0; i < buf_len; i++) {
|
||||
// Control characters are not allowed but >=128 is.
|
||||
if (!isprint(* (const unsigned char *) s) && *s != '\r' &&
|
||||
*s != '\n' && * (const unsigned char *) s < 128) {
|
||||
len = -1;
|
||||
break; // [i_a] abort scan as soon as one malformed character is found;
|
||||
// don't let subsequent \r\n\r\n win us over anyhow
|
||||
} else if (s[0] == '\n' && s[1] == '\n') {
|
||||
len = (int) (s - buf) + 2;
|
||||
} else if (s[0] == '\n' && &s[1] < e &&
|
||||
s[1] == '\r' && s[2] == '\n') {
|
||||
len = (int) (s - buf) + 3;
|
||||
// Abort scan as soon as one malformed character is found;
|
||||
// don't let subsequent \r\n\r\n win us over anyhow
|
||||
if (!isprint(* (const unsigned char *) &buf[i]) && buf[i] != '\r' &&
|
||||
buf[i] != '\n' && * (const unsigned char *) &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 len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Convert month to the month number. Return -1 on error, or month number
|
||||
@ -2840,7 +2853,7 @@ static void handle_directory_request(struct mg_connection *conn,
|
||||
static void send_file_data(struct mg_connection *conn, FILE *fp,
|
||||
int64_t offset, int64_t len) {
|
||||
char buf[MG_BUF_LEN];
|
||||
int to_read, num_read, num_written;
|
||||
int num_read, num_written, to_read;
|
||||
|
||||
// If offset is beyond file boundaries, don't send anything
|
||||
if (offset > 0 && fseeko(fp, offset, SEEK_SET) != 0) {
|
||||
@ -3054,7 +3067,8 @@ static int read_request(FILE *fp, struct mg_connection *conn,
|
||||
|
||||
request_len = get_request_len(buf, *nread);
|
||||
while (conn->ctx->stop_flag == 0 &&
|
||||
*nread < bufsiz && request_len == 0 &&
|
||||
*nread < bufsiz &&
|
||||
request_len == 0 &&
|
||||
(n = pull(fp, conn, buf + *nread, bufsiz - *nread)) > 0) {
|
||||
*nread += n;
|
||||
assert(*nread <= bufsiz);
|
||||
@ -3126,7 +3140,8 @@ static int forward_body_data(struct mg_connection *conn, FILE *fp,
|
||||
SOCKET sock, SSL *ssl) {
|
||||
const char *expect, *body;
|
||||
char buf[MG_BUF_LEN];
|
||||
int to_read, nread, buffered_len, success = 0;
|
||||
int nread, buffered_len, success = 0;
|
||||
int64_t left;
|
||||
|
||||
expect = mg_get_header(conn, "Expect");
|
||||
assert(fp != NULL);
|
||||
@ -3140,33 +3155,32 @@ static int forward_body_data(struct mg_connection *conn, FILE *fp,
|
||||
(void) mg_printf(conn, "%s", "HTTP/1.1 100 Continue\r\n\r\n");
|
||||
}
|
||||
|
||||
body = conn->buf + conn->request_len + conn->consumed_content;
|
||||
buffered_len = &conn->buf[conn->data_len] - body;
|
||||
buffered_len = conn->data_len - conn->request_len;
|
||||
body = conn->buf + conn->request_len;
|
||||
assert(buffered_len >= 0);
|
||||
assert(conn->consumed_content == 0);
|
||||
|
||||
if (buffered_len > 0) {
|
||||
if ((int64_t) buffered_len > conn->content_len) {
|
||||
buffered_len = (int) conn->content_len;
|
||||
}
|
||||
push(fp, sock, ssl, body, (int64_t) buffered_len);
|
||||
conn->consumed_content += buffered_len;
|
||||
memmove((char *) body, body + buffered_len, buffered_len);
|
||||
conn->data_len -= buffered_len;
|
||||
}
|
||||
|
||||
nread = 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);
|
||||
while (conn->num_bytes_read < conn->content_len + conn->request_len) {
|
||||
left = left_to_read(conn);
|
||||
if (left > (int64_t) sizeof(buf)) {
|
||||
left = sizeof(buf);
|
||||
}
|
||||
nread = pull(NULL, conn, buf, to_read);
|
||||
nread = pull(NULL, conn, buf, left);
|
||||
if (nread <= 0 || push(fp, sock, ssl, buf, nread) != nread) {
|
||||
break;
|
||||
}
|
||||
conn->consumed_content += nread;
|
||||
}
|
||||
|
||||
if (conn->consumed_content == conn->content_len) {
|
||||
if (left_to_read(conn) == 0) {
|
||||
success = nread >= 0;
|
||||
}
|
||||
|
||||
@ -3980,7 +3994,7 @@ static void base64_encode(const unsigned char *src, int src_len, char *dst) {
|
||||
dst[j++] = '\0';
|
||||
}
|
||||
|
||||
static void send_websocket_handshake(struct mg_connection *conn) {
|
||||
void mg_websocket_handshake(struct mg_connection *conn) {
|
||||
static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
char buf[100], sha[20], b64_sha[sizeof(sha) * 2];
|
||||
SHA1_CTX sha_ctx;
|
||||
@ -3998,17 +4012,14 @@ static void send_websocket_handshake(struct mg_connection *conn) {
|
||||
"Sec-WebSocket-Accept: ", b64_sha, "\r\n\r\n");
|
||||
}
|
||||
|
||||
static void read_websocket(struct mg_connection *conn) {
|
||||
int mg_websocket_read(struct mg_connection *conn, int *bits, char **data) {
|
||||
// Pointer to the beginning of the portion of the incoming websocket message
|
||||
// queue. The original websocket upgrade request is never removed,
|
||||
// so the queue begins after it.
|
||||
unsigned char *buf = (unsigned char *) conn->buf + conn->request_len;
|
||||
int bits, n, stop = 0;
|
||||
int n, stop = 0;
|
||||
size_t i, len, mask_len, data_len, header_len, body_len;
|
||||
// data points to the place where the message is stored when passed to the
|
||||
// websocket_data callback. This is either mem on the stack,
|
||||
// or a dynamically allocated buffer if it is too large.
|
||||
char mem[4 * 1024], mask[4], *data;
|
||||
char mask[4];
|
||||
|
||||
assert(conn->content_len == 0);
|
||||
|
||||
@ -4046,28 +4057,28 @@ static void read_websocket(struct mg_connection *conn) {
|
||||
|
||||
if (header_len > 0) {
|
||||
// Allocate space to hold websocket payload
|
||||
data = mem;
|
||||
if (data_len > sizeof(mem) && (data = malloc(data_len)) == NULL) {
|
||||
if ((*data = malloc(data_len)) == NULL) {
|
||||
// Allocation failed, exit the loop and then close the connection
|
||||
// TODO: notify user about the failure
|
||||
data_len = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Save mask and bits, otherwise it may be clobbered by memmove below
|
||||
bits = buf[0];
|
||||
*bits = buf[0];
|
||||
memcpy(mask, buf + header_len - mask_len, mask_len);
|
||||
|
||||
// Read frame payload into the allocated buffer.
|
||||
assert(body_len >= header_len);
|
||||
if (data_len + header_len > body_len) {
|
||||
len = body_len - header_len;
|
||||
memcpy(data, buf + header_len, len);
|
||||
memcpy(*data, buf + header_len, len);
|
||||
// TODO: handle pull error
|
||||
pull_all(NULL, conn, data + len, data_len - len);
|
||||
pull_all(NULL, conn, *data + len, data_len - len);
|
||||
conn->data_len = conn->request_len;
|
||||
} else {
|
||||
len = data_len + header_len;
|
||||
memcpy(data, buf + header_len, data_len);
|
||||
memcpy(*data, buf + header_len, data_len);
|
||||
memmove(buf, buf + len, body_len - len);
|
||||
conn->data_len -= len;
|
||||
}
|
||||
@ -4075,21 +4086,17 @@ static void read_websocket(struct mg_connection *conn) {
|
||||
// Apply mask if necessary
|
||||
if (mask_len > 0) {
|
||||
for (i = 0; i < data_len; i++) {
|
||||
data[i] ^= mask[i % 4];
|
||||
(*data)[i] ^= mask[i % 4];
|
||||
}
|
||||
}
|
||||
|
||||
// Exit the loop if callback signalled to exit,
|
||||
// or "connection close" opcode received.
|
||||
if (((bits & 0x0f) == WEBSOCKET_OPCODE_CONNECTION_CLOSE) ||
|
||||
(conn->ctx->callbacks.websocket_data != NULL &&
|
||||
!conn->ctx->callbacks.websocket_data(conn, bits, data, data_len))) {
|
||||
if ((*bits & 0x0f) == WEBSOCKET_OPCODE_CONNECTION_CLOSE) {
|
||||
return data_len;
|
||||
stop = 1;
|
||||
}
|
||||
|
||||
if (data != mem) {
|
||||
free(data);
|
||||
}
|
||||
// Not breaking the loop, process next websocket frame.
|
||||
} else {
|
||||
// Buffering websocket request
|
||||
@ -4100,6 +4107,8 @@ static void read_websocket(struct mg_connection *conn) {
|
||||
conn->data_len += n;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mg_websocket_write(struct mg_connection* conn, int opcode,
|
||||
@ -4143,37 +4152,6 @@ int mg_websocket_write(struct mg_connection* conn, int opcode,
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void handle_websocket_request(struct mg_connection *conn) {
|
||||
const char *version = mg_get_header(conn, "Sec-WebSocket-Version");
|
||||
if (version == NULL || strcmp(version, "13") != 0) {
|
||||
send_http_error(conn, 426, "Upgrade Required", "%s", "Upgrade Required");
|
||||
} else if (conn->ctx->callbacks.websocket_connect != NULL &&
|
||||
conn->ctx->callbacks.websocket_connect(conn) != 0) {
|
||||
// Callback has returned non-zero, do not proceed with handshake
|
||||
} else {
|
||||
send_websocket_handshake(conn);
|
||||
if (conn->ctx->callbacks.websocket_ready != NULL) {
|
||||
conn->ctx->callbacks.websocket_ready(conn);
|
||||
}
|
||||
read_websocket(conn);
|
||||
}
|
||||
}
|
||||
|
||||
static int is_websocket_request(const struct mg_connection *conn) {
|
||||
const char *host, *upgrade, *connection, *version, *key;
|
||||
|
||||
host = mg_get_header(conn, "Host");
|
||||
upgrade = mg_get_header(conn, "Upgrade");
|
||||
connection = mg_get_header(conn, "Connection");
|
||||
key = mg_get_header(conn, "Sec-WebSocket-Key");
|
||||
version = mg_get_header(conn, "Sec-WebSocket-Version");
|
||||
|
||||
return host != NULL && upgrade != NULL && connection != NULL &&
|
||||
key != NULL && version != NULL &&
|
||||
mg_strcasestr(upgrade, "websocket") != NULL &&
|
||||
mg_strcasestr(connection, "Upgrade") != NULL;
|
||||
}
|
||||
#endif // !USE_WEBSOCKET
|
||||
|
||||
static int isbyte(int n) {
|
||||
@ -4231,12 +4209,12 @@ static uint32_t get_remote_ip(const struct mg_connection *conn) {
|
||||
#include "build/mod_lua.c"
|
||||
#endif // USE_LUA
|
||||
|
||||
int mg_upload(struct mg_connection *conn, const char *destination_dir) {
|
||||
FILE *mg_upload(struct mg_connection *conn, const char *destination_dir,
|
||||
char *path, int path_len) {
|
||||
const char *content_type_header, *boundary_start;
|
||||
char buf[MG_BUF_LEN], path[PATH_MAX], fname[1024], boundary[100], *s;
|
||||
char *buf, fname[1024], boundary[100], *s;
|
||||
int bl, n, i, j, headers_len, boundary_len, eof, buf_len, to_read, len = 0;
|
||||
FILE *fp;
|
||||
int bl, n, i, j, headers_len, boundary_len, eof,
|
||||
len = 0, num_uploaded_files = 0;
|
||||
|
||||
// Request looks like this:
|
||||
//
|
||||
@ -4260,15 +4238,31 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) {
|
||||
(sscanf(boundary_start, "boundary=\"%99[^\"]\"", boundary) == 0 &&
|
||||
sscanf(boundary_start, "boundary=%99s", boundary) == 0) ||
|
||||
boundary[0] == '\0') {
|
||||
return num_uploaded_files;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
boundary_len = strlen(boundary);
|
||||
bl = boundary_len + 4; // \r\n--<boundary>
|
||||
|
||||
// buf
|
||||
// conn->buf |<--------- buf_len ------>|
|
||||
// |=================|==========|===============|
|
||||
// |<--request_len-->|<--len--->| |
|
||||
// |<-----------data_len------->| conn->buf + conn->buf_size
|
||||
|
||||
buf = conn->buf + conn->request_len;
|
||||
buf_len = conn->buf_size - conn->request_len;
|
||||
len = conn->data_len - conn->request_len;
|
||||
|
||||
for (;;) {
|
||||
// Pull in headers
|
||||
assert(len >= 0 && len <= (int) sizeof(buf));
|
||||
while ((n = mg_read(conn, buf + len, sizeof(buf) - len)) > 0) {
|
||||
assert(len >= 0 && len <= buf_len);
|
||||
to_read = buf_len - len;
|
||||
if (to_read > left_to_read(conn)) {
|
||||
to_read = left_to_read(conn);
|
||||
}
|
||||
while (len < buf_len &&
|
||||
(n = pull(NULL, conn, buf + len, to_read)) > 0) {
|
||||
len += n;
|
||||
}
|
||||
if ((headers_len = get_request_len(buf, len)) <= 0) {
|
||||
@ -4297,10 +4291,12 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) {
|
||||
assert(len >= headers_len);
|
||||
memmove(buf, &buf[headers_len], len - headers_len);
|
||||
len -= headers_len;
|
||||
conn->data_len = conn->request_len + len;
|
||||
|
||||
// We open the file with exclusive lock held. This guarantee us
|
||||
// there is no other thread can save into the same file simultaneously.
|
||||
fp = NULL;
|
||||
|
||||
// Construct destination file name. Do not allow paths to have slashes.
|
||||
if ((s = strrchr(fname, '/')) == NULL &&
|
||||
(s = strrchr(fname, '\\')) == NULL) {
|
||||
@ -4308,7 +4304,7 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) {
|
||||
}
|
||||
|
||||
// Open file in binary mode. TODO: set an exclusive lock.
|
||||
snprintf(path, sizeof(path), "%s/%s", destination_dir, s);
|
||||
snprintf(path, path_len, "%s/%s", destination_dir, s);
|
||||
if ((fp = fopen(path, "wb")) == NULL) {
|
||||
break;
|
||||
}
|
||||
@ -4333,17 +4329,22 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) {
|
||||
memmove(buf, &buf[len - bl], bl);
|
||||
len = bl;
|
||||
}
|
||||
} while (!eof && (n = mg_read(conn, buf + len, sizeof(buf) - len)) > 0);
|
||||
fclose(fp);
|
||||
if (eof) {
|
||||
num_uploaded_files++;
|
||||
if (conn->ctx->callbacks.upload != NULL) {
|
||||
conn->ctx->callbacks.upload(conn, path);
|
||||
to_read = buf_len - len;
|
||||
if (to_read > left_to_read(conn)) {
|
||||
to_read = left_to_read(conn);
|
||||
}
|
||||
} while (!eof && (n = pull(NULL, conn, buf + len, to_read)) > 0);
|
||||
conn->data_len = conn->request_len + len;
|
||||
|
||||
if (eof) {
|
||||
rewind(fp);
|
||||
return fp;
|
||||
} else {
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
return num_uploaded_files;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int is_put_or_delete_request(const struct mg_connection *conn) {
|
||||
@ -4417,7 +4418,6 @@ static void handle_request(struct mg_connection *conn) {
|
||||
path[0] = '\0';
|
||||
convert_uri_to_file_name(conn, path, sizeof(path), &file);
|
||||
|
||||
DEBUG_TRACE(("%s", ri->uri));
|
||||
// Perform redirect and auth checks before calling begin_request() handler.
|
||||
// Otherwise, begin_request() would need to perform auth checks and redirects.
|
||||
if (!conn->client.is_ssl && conn->client.ssl_redir &&
|
||||
@ -4426,13 +4426,8 @@ static void handle_request(struct mg_connection *conn) {
|
||||
} else if (!is_put_or_delete_request(conn) &&
|
||||
!check_authorization(conn, path)) {
|
||||
send_authorization_request(conn);
|
||||
} else if (conn->ctx->callbacks.begin_request != NULL &&
|
||||
conn->ctx->callbacks.begin_request(conn)) {
|
||||
} else if (call_user(MG_REQUEST_BEGIN, conn, (void *) ri->uri) == 1) {
|
||||
// Do nothing, callback has served the request
|
||||
#if defined(USE_WEBSOCKET)
|
||||
} else if (is_websocket_request(conn)) {
|
||||
handle_websocket_request(conn);
|
||||
#endif
|
||||
} else if (!strcmp(ri->request_method, "OPTIONS")) {
|
||||
handle_options_request(conn);
|
||||
} else if (conn->ctx->config[DOCUMENT_ROOT] == NULL) {
|
||||
@ -4767,8 +4762,9 @@ static int set_ssl_option(struct mg_context *ctx) {
|
||||
|
||||
// If PEM file is not specified and the init_ssl callback
|
||||
// is not specified, skip SSL initialization.
|
||||
if ((pem = ctx->config[SSL_CERTIFICATE]) == NULL &&
|
||||
ctx->callbacks.init_ssl == NULL) {
|
||||
if ((pem = ctx->config[SSL_CERTIFICATE]) == NULL) {
|
||||
// MG_INIT_SSL
|
||||
// ctx->callbacks.init_ssl == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -4790,10 +4786,9 @@ static int set_ssl_option(struct mg_context *ctx) {
|
||||
|
||||
// If user callback returned non-NULL, that means that user callback has
|
||||
// set up certificate itself. In this case, skip sertificate setting.
|
||||
if ((ctx->callbacks.init_ssl == NULL ||
|
||||
!ctx->callbacks.init_ssl(ctx->ssl_ctx, ctx->user_data)) &&
|
||||
(SSL_CTX_use_certificate_file(ctx->ssl_ctx, pem, 1) == 0 ||
|
||||
SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, pem, 1) == 0)) {
|
||||
// MG_INIT_SSL
|
||||
if (SSL_CTX_use_certificate_file(ctx->ssl_ctx, pem, 1) == 0 ||
|
||||
SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, pem, 1) == 0) {
|
||||
cry(fc(ctx), "%s: cannot open %s: %s", __func__, pem, ssl_error());
|
||||
return 0;
|
||||
}
|
||||
@ -4849,7 +4844,7 @@ static int set_acl_option(struct mg_context *ctx) {
|
||||
|
||||
static void reset_per_request_attributes(struct mg_connection *conn) {
|
||||
conn->path_info = NULL;
|
||||
conn->num_bytes_sent = conn->consumed_content = 0;
|
||||
conn->num_bytes_sent = conn->num_bytes_read = 0;
|
||||
conn->status_code = -1;
|
||||
conn->must_close = conn->request_len = conn->throttle = 0;
|
||||
}
|
||||
@ -5039,9 +5034,7 @@ static void process_new_connection(struct mg_connection *conn) {
|
||||
|
||||
if (ebuf[0] == '\0') {
|
||||
handle_request(conn);
|
||||
if (conn->ctx->callbacks.end_request != NULL) {
|
||||
conn->ctx->callbacks.end_request(conn, conn->status_code);
|
||||
}
|
||||
call_user(MG_REQUEST_END, conn, (void *) conn->status_code);
|
||||
log_access(conn);
|
||||
}
|
||||
if (ri->remote_user != NULL) {
|
||||
@ -5111,12 +5104,9 @@ static void *worker_thread(void *thread_func_param) {
|
||||
conn->buf_size = MAX_REQUEST_SIZE;
|
||||
conn->buf = (char *) (conn + 1);
|
||||
conn->ctx = ctx;
|
||||
conn->request_info.user_data = ctx->user_data;
|
||||
conn->event.user_data = ctx->user_data;
|
||||
|
||||
if (ctx->callbacks.thread_start != NULL) {
|
||||
ctx->callbacks.thread_start(&conn->request_info.user_data,
|
||||
&conn->request_info.conn_data);
|
||||
}
|
||||
call_user(MG_THREAD_BEGIN, conn, NULL);
|
||||
|
||||
// Call consume_socket() even when ctx->stop_flag > 0, to let it signal
|
||||
// sq_empty condvar to wake up the master waiting in produce_socket()
|
||||
@ -5143,10 +5133,7 @@ static void *worker_thread(void *thread_func_param) {
|
||||
|
||||
close_connection(conn);
|
||||
}
|
||||
if (ctx->callbacks.thread_stop != NULL) {
|
||||
ctx->callbacks.thread_stop(&conn->request_info.user_data,
|
||||
&conn->request_info.conn_data);
|
||||
}
|
||||
call_user(MG_THREAD_END, conn, NULL);
|
||||
free(conn);
|
||||
}
|
||||
|
||||
@ -5241,9 +5228,7 @@ static void *master_thread(void *thread_func_param) {
|
||||
pthread_setschedparam(pthread_self(), SCHED_RR, &sched_param);
|
||||
#endif
|
||||
|
||||
if (ctx->callbacks.thread_start != NULL) {
|
||||
ctx->callbacks.thread_start(&ctx->user_data, NULL);
|
||||
}
|
||||
call_user(MG_THREAD_BEGIN, fc(ctx), NULL);
|
||||
|
||||
pfd = (struct pollfd *) calloc(ctx->num_listening_sockets, sizeof(pfd[0]));
|
||||
while (pfd != NULL && ctx->stop_flag == 0) {
|
||||
@ -5291,9 +5276,7 @@ static void *master_thread(void *thread_func_param) {
|
||||
#endif
|
||||
DEBUG_TRACE(("exiting"));
|
||||
|
||||
if (ctx->callbacks.thread_stop != NULL) {
|
||||
ctx->callbacks.thread_stop(&ctx->user_data, NULL);
|
||||
}
|
||||
call_user(MG_THREAD_END, fc(ctx), NULL);
|
||||
|
||||
// Signal mg_stop() that we're done.
|
||||
// WARNING: This must be the very last thing this
|
||||
@ -5340,9 +5323,9 @@ void mg_stop(struct mg_context *ctx) {
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
struct mg_context *mg_start(const struct mg_callbacks *callbacks,
|
||||
void *user_data,
|
||||
const char **options) {
|
||||
struct mg_context *mg_start(const char **options,
|
||||
mg_event_handler_t func,
|
||||
void *user_data) {
|
||||
struct mg_context *ctx;
|
||||
const char *name, *value, *default_value;
|
||||
int i;
|
||||
@ -5357,7 +5340,7 @@ struct mg_context *mg_start(const struct mg_callbacks *callbacks,
|
||||
if ((ctx = (struct mg_context *) calloc(1, sizeof(*ctx))) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
ctx->callbacks = *callbacks;
|
||||
ctx->event_handler = func;
|
||||
ctx->user_data = user_data;
|
||||
|
||||
while (options && (name = *options++) != NULL) {
|
||||
|
73
mongoose.h
73
mongoose.h
@ -25,8 +25,8 @@
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
struct mg_context; // Handle for the HTTP service itself
|
||||
struct mg_connection; // Handle for the individual connection
|
||||
struct mg_context; // Web server instance
|
||||
struct mg_connection; // HTTP request descriptor
|
||||
|
||||
|
||||
// This structure contains information about the HTTP request.
|
||||
@ -39,8 +39,6 @@ struct mg_request_info {
|
||||
long remote_ip; // Client's IP address
|
||||
int remote_port; // Client's port
|
||||
int is_ssl; // 1 if SSL-ed, 0 if not
|
||||
void *user_data; // User data pointer passed to mg_start()
|
||||
void *conn_data; // Connection-specific, per-thread user data.
|
||||
|
||||
int num_headers; // Number of HTTP headers
|
||||
struct mg_header {
|
||||
@ -49,37 +47,32 @@ struct mg_request_info {
|
||||
} http_headers[64]; // Maximum 64 headers
|
||||
};
|
||||
|
||||
enum mg_event {
|
||||
MG_REQUEST_BEGIN,
|
||||
MG_REQUEST_END,
|
||||
MG_HTTP_ERROR,
|
||||
MG_EVENT_LOG,
|
||||
MG_THREAD_BEGIN,
|
||||
MG_THREAD_END
|
||||
};
|
||||
typedef int (*mg_callback_t)(enum mg_event event,
|
||||
struct mg_connection *conn,
|
||||
void *data);
|
||||
struct mg_event {
|
||||
int type; // Event type, possible types are defined below
|
||||
#define MG_REQUEST_BEGIN 1 // event_param: NULL
|
||||
#define MG_REQUEST_END 2 // event_param: NULL
|
||||
#define MG_HTTP_ERROR 3 // event_param: int status_code
|
||||
#define MG_EVENT_LOG 4 // event_param: const char *message
|
||||
#define MG_THREAD_BEGIN 5 // event_param: NULL
|
||||
#define MG_THREAD_END 6 // event_param: NULL
|
||||
|
||||
struct mg_callbacks {
|
||||
int (*begin_request)(struct mg_connection *);
|
||||
void (*end_request)(const struct mg_connection *, int reply_status_code);
|
||||
int (*log_message)(const struct mg_connection *, const char *message);
|
||||
int (*init_ssl)(void *ssl_context, void *user_data);
|
||||
int (*websocket_connect)(const struct mg_connection *);
|
||||
void (*websocket_ready)(struct mg_connection *);
|
||||
int (*websocket_data)(struct mg_connection *, int bits,
|
||||
char *data, size_t data_len);
|
||||
void (*upload)(struct mg_connection *, const char *file_name);
|
||||
void (*thread_start)(void *user_data, void **conn_data);
|
||||
void (*thread_stop)(void *user_data, void **conn_data);
|
||||
void *user_data; // User data pointer passed to mg_start()
|
||||
void *conn_data; // Connection-specific, per-thread user data.
|
||||
void *event_param; // Event-specific parameter
|
||||
|
||||
struct mg_connection *conn;
|
||||
struct mg_request_info *request_info;
|
||||
};
|
||||
|
||||
struct mg_context *mg_start(const struct mg_callbacks *callbacks,
|
||||
void *user_data,
|
||||
const char **configuration_options);
|
||||
typedef int (*mg_event_handler_t)(struct mg_event *event);
|
||||
|
||||
struct mg_context *mg_start(const char **configuration_options,
|
||||
mg_event_handler_t func, void *user_data);
|
||||
void mg_stop(struct mg_context *);
|
||||
|
||||
void mg_websocket_handshake(struct mg_connection *);
|
||||
int mg_websocket_read(struct mg_connection *, int *bits, char **data);
|
||||
|
||||
|
||||
// Get the value of particular configuration parameter.
|
||||
// The value returned is read-only. Mongoose does not allow changing
|
||||
@ -114,17 +107,12 @@ int mg_modify_passwords_file(const char *passwords_file_name,
|
||||
const char *user,
|
||||
const char *password);
|
||||
|
||||
|
||||
// Return information associated with the request.
|
||||
struct mg_request_info *mg_get_request_info(struct mg_connection *);
|
||||
|
||||
|
||||
// Send data to the client.
|
||||
// Return:
|
||||
// 0 when the connection has been closed
|
||||
// -1 on error
|
||||
// >0 number of bytes written on success
|
||||
int mg_write(struct mg_connection *, const void *buf, size_t len);
|
||||
int mg_write(struct mg_connection *, const void *buf, int len);
|
||||
|
||||
|
||||
// Send data to a websocket client wrapped in a websocket frame.
|
||||
@ -184,7 +172,7 @@ void mg_send_file(struct mg_connection *conn, const char *path);
|
||||
// 0 connection has been closed by peer. No more data could be read.
|
||||
// < 0 read error. No more data could be read from the connection.
|
||||
// > 0 number of bytes read into the buffer.
|
||||
int mg_read(struct mg_connection *, void *buf, size_t len);
|
||||
int mg_read(struct mg_connection *, void *buf, int len);
|
||||
|
||||
|
||||
// Get the value of particular HTTP header.
|
||||
@ -258,10 +246,13 @@ struct mg_connection *mg_download(const char *host, int port, int use_ssl,
|
||||
void mg_close_connection(struct mg_connection *conn);
|
||||
|
||||
|
||||
// File upload functionality. Each uploaded file gets saved into a temporary
|
||||
// file and MG_UPLOAD event is sent.
|
||||
// Return number of uploaded files.
|
||||
int mg_upload(struct mg_connection *conn, const char *destination_dir);
|
||||
// Read multipart-form-data POST buffer, save uploaded files into
|
||||
// destination directory, and return path to the saved filed.
|
||||
// This function can be called multiple times for the same connection,
|
||||
// if more then one file is uploaded.
|
||||
// Return: path to the uploaded file, or NULL if there are no more files.
|
||||
FILE *mg_upload(struct mg_connection *conn, const char *destination_dir,
|
||||
char *path, int path_len);
|
||||
|
||||
|
||||
// Convenience function -- create detached thread.
|
||||
|
228
test/unit_test.c
228
test/unit_test.c
@ -1,24 +1,4 @@
|
||||
// Copyright (c) 2004-2013 Sergey Lyubka
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
// Unit test for the mongoose web server. Tests embedded API.
|
||||
// Unit test for the mongoose web server.
|
||||
|
||||
#define USE_WEBSOCKET
|
||||
#define USE_LUA
|
||||
@ -65,6 +45,14 @@ static void test_parse_http_message() {
|
||||
char req8[] = " HTTP/1.1 200 OK \n\n";
|
||||
char req9[] = "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n";
|
||||
|
||||
ASSERT(get_request_len("\r\n", 3) == -1);
|
||||
ASSERT(get_request_len("\r\n", 2) == 0);
|
||||
ASSERT(get_request_len("GET", 3) == 0);
|
||||
ASSERT(get_request_len("\n\n", 2) == 2);
|
||||
ASSERT(get_request_len("\n\r\n", 3) == 3);
|
||||
ASSERT(get_request_len("\xdd\xdd", 2) == 0);
|
||||
ASSERT(get_request_len("\xdd\x03", 2) == -1);
|
||||
|
||||
ASSERT(parse_http_message(req9, sizeof(req9), &ri) == sizeof(req9) - 1);
|
||||
ASSERT(ri.num_headers == 1);
|
||||
|
||||
@ -72,15 +60,15 @@ static void test_parse_http_message() {
|
||||
ASSERT(strcmp(ri.http_version, "1.1") == 0);
|
||||
ASSERT(ri.num_headers == 0);
|
||||
|
||||
ASSERT(parse_http_message(req2, sizeof(req2), &ri) == -1);
|
||||
ASSERT(parse_http_message(req3, sizeof(req3), &ri) == 0);
|
||||
ASSERT(parse_http_message(req6, sizeof(req6), &ri) == 0);
|
||||
ASSERT(parse_http_message(req7, sizeof(req7), &ri) == 0);
|
||||
ASSERT(parse_http_message(req2, sizeof(req2) - 1, &ri) == -1);
|
||||
ASSERT(parse_http_message(req3, sizeof(req3) - 1, &ri) == 0);
|
||||
ASSERT(parse_http_message(req6, sizeof(req6) - 1, &ri) == 0);
|
||||
ASSERT(parse_http_message(req7, sizeof(req7) - 1, &ri) == 0);
|
||||
ASSERT(parse_http_message("", 0, &ri) == 0);
|
||||
ASSERT(parse_http_message(req8, sizeof(req8), &ri) == sizeof(req8) - 1);
|
||||
ASSERT(parse_http_message(req8, sizeof(req8) - 1, &ri) == sizeof(req8) - 1);
|
||||
|
||||
// TODO(lsm): Fix this. Header value may span multiple lines.
|
||||
ASSERT(parse_http_message(req4, sizeof(req4), &ri) == sizeof(req4) - 1);
|
||||
ASSERT(parse_http_message(req4, sizeof(req4) - 1, &ri) == sizeof(req4) - 1);
|
||||
ASSERT(strcmp(ri.http_version, "1.1") == 0);
|
||||
ASSERT(ri.num_headers == 3);
|
||||
ASSERT(strcmp(ri.http_headers[0].name, "A") == 0);
|
||||
@ -90,7 +78,7 @@ static void test_parse_http_message() {
|
||||
ASSERT(strcmp(ri.http_headers[2].name, "baz\r\n\r") == 0);
|
||||
ASSERT(strcmp(ri.http_headers[2].value, "") == 0);
|
||||
|
||||
ASSERT(parse_http_message(req5, sizeof(req5), &ri) == sizeof(req5) - 1);
|
||||
ASSERT(parse_http_message(req5, sizeof(req5) - 1, &ri) == sizeof(req5) - 1);
|
||||
ASSERT(strcmp(ri.request_method, "GET") == 0);
|
||||
ASSERT(strcmp(ri.http_version, "1.1") == 0);
|
||||
}
|
||||
@ -201,79 +189,55 @@ static char *read_file(const char *path, int *size) {
|
||||
}
|
||||
|
||||
static const char *fetch_data = "hello world!\n";
|
||||
static const char *upload_filename = "upload_test.txt";
|
||||
static const char *upload_filename2 = "upload_test2.txt";
|
||||
static const char *upload_ok_message = "upload successful";
|
||||
|
||||
static void upload_cb(struct mg_connection *conn, const char *path) {
|
||||
const struct mg_request_info *ri = mg_get_request_info(conn);
|
||||
char *p1, *p2;
|
||||
static void test_upload(struct mg_connection *conn, const char *orig_path,
|
||||
const char *uploaded_path) {
|
||||
int len1, len2;
|
||||
char path[500], *p1, *p2;
|
||||
FILE *fp;
|
||||
|
||||
if (atoi(ri->query_string) == 1) {
|
||||
ASSERT(!strcmp(path, "./upload_test.txt"));
|
||||
ASSERT((p1 = read_file("main.c", &len1)) != NULL);
|
||||
ASSERT((p2 = read_file(path, &len2)) != NULL);
|
||||
ASSERT(len1 == len2);
|
||||
ASSERT(memcmp(p1, p2, len1) == 0);
|
||||
free(p1), free(p2);
|
||||
remove(upload_filename);
|
||||
} else if (atoi(ri->query_string) == 2) {
|
||||
if (!strcmp(path, "./upload_test.txt")) {
|
||||
ASSERT((p1 = read_file("lua_5.2.1.h", &len1)) != NULL);
|
||||
ASSERT((p2 = read_file(path, &len2)) != NULL);
|
||||
ASSERT(len1 == len2);
|
||||
ASSERT(memcmp(p1, p2, len1) == 0);
|
||||
free(p1), free(p2);
|
||||
remove(upload_filename);
|
||||
} else if (!strcmp(path, "./upload_test2.txt")) {
|
||||
ASSERT((p1 = read_file("mod_lua.c", &len1)) != NULL);
|
||||
ASSERT((p2 = read_file(path, &len2)) != NULL);
|
||||
ASSERT(len1 == len2);
|
||||
ASSERT(memcmp(p1, p2, len1) == 0);
|
||||
free(p1), free(p2);
|
||||
remove(upload_filename);
|
||||
} else {
|
||||
ASSERT(0);
|
||||
ASSERT((fp = mg_upload(conn, ".", path, sizeof(path))) != NULL);
|
||||
fclose(fp);
|
||||
ASSERT(!strcmp(path, uploaded_path));
|
||||
ASSERT((p1 = read_file(orig_path, &len1)) != NULL);
|
||||
ASSERT((p2 = read_file(path, &len2)) != NULL);
|
||||
ASSERT(len1 == len2);
|
||||
ASSERT(memcmp(p1, p2, len1) == 0);
|
||||
free(p1), free(p2);
|
||||
remove(path);
|
||||
}
|
||||
|
||||
static int event_handler(struct mg_event *event) {
|
||||
struct mg_request_info *ri = event->request_info;
|
||||
|
||||
if (event->type == MG_REQUEST_BEGIN) {
|
||||
if (!strcmp(ri->uri, "/data")) {
|
||||
mg_printf(event->conn, "HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: text/plain\r\n\r\n"
|
||||
"%s", fetch_data);
|
||||
close_connection(event->conn);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
ASSERT(0);
|
||||
}
|
||||
|
||||
mg_printf(conn, "HTTP/1.0 200 OK\r\nContent-Length: %d\r\n\r\n%s",
|
||||
(int) strlen(upload_ok_message), upload_ok_message);
|
||||
}
|
||||
if (!strcmp(ri->uri, "/upload")) {
|
||||
test_upload(event->conn, "lua_5.2.1.h", "./f1.txt");
|
||||
test_upload(event->conn, "mod_lua.c", "./f2.txt");
|
||||
ASSERT(mg_upload(event->conn, ".", NULL, 0) == NULL);
|
||||
|
||||
static int begin_request_handler_cb(struct mg_connection *conn) {
|
||||
const struct mg_request_info *ri = mg_get_request_info(conn);
|
||||
|
||||
if (!strcmp(ri->uri, "/data")) {
|
||||
mg_printf(conn, "HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: text/plain\r\n\r\n"
|
||||
"%s", fetch_data);
|
||||
close_connection(conn);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(ri->uri, "/upload")) {
|
||||
ASSERT(ri->query_string != NULL);
|
||||
ASSERT(mg_upload(conn, ".") == atoi(ri->query_string));
|
||||
mg_printf(event->conn, "HTTP/1.0 200 OK\r\n"
|
||||
"Content-Type: text/plain\r\n\r\n"
|
||||
"%s", upload_ok_message);
|
||||
close_connection(event->conn);
|
||||
return 1;
|
||||
}
|
||||
} else if (event->type == MG_EVENT_LOG) {
|
||||
printf("%s\n", (const char *) event->event_param);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int log_message_cb(const struct mg_connection *conn, const char *msg) {
|
||||
(void) conn;
|
||||
printf("%s\n", msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mg_callbacks CALLBACKS = {
|
||||
&begin_request_handler_cb, NULL, &log_message_cb, NULL, NULL, NULL, NULL,
|
||||
&upload_cb, NULL, NULL
|
||||
};
|
||||
|
||||
static const char *OPTIONS[] = {
|
||||
"document_root", ".",
|
||||
"listening_ports", LISTENING_ADDR,
|
||||
@ -283,7 +247,7 @@ static const char *OPTIONS[] = {
|
||||
};
|
||||
|
||||
static char *read_conn(struct mg_connection *conn, int *size) {
|
||||
char buf[100], *data = NULL;
|
||||
char buf[MG_BUF_LEN], *data = NULL;
|
||||
int len;
|
||||
*size = 0;
|
||||
while ((len = mg_read(conn, buf, sizeof(buf))) > 0) {
|
||||
@ -300,7 +264,7 @@ static void test_mg_download(void) {
|
||||
struct mg_connection *conn;
|
||||
struct mg_context *ctx;
|
||||
|
||||
ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL);
|
||||
ASSERT((ctx = mg_start(OPTIONS, event_handler, NULL)) != NULL);
|
||||
|
||||
ASSERT(mg_download(NULL, port, 0, ebuf, sizeof(ebuf), "%s", "") == NULL);
|
||||
ASSERT(mg_download("localhost", 0, 0, ebuf, sizeof(ebuf), "%s", "") == NULL);
|
||||
@ -362,33 +326,7 @@ static void test_mg_upload(void) {
|
||||
char ebuf[100], buf[20], *file_data, *file2_data, *post_data;
|
||||
int file_len, file2_len, post_data_len;
|
||||
|
||||
ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL);
|
||||
|
||||
// Upload one file
|
||||
ASSERT((file_data = read_file("main.c", &file_len)) != NULL);
|
||||
post_data = NULL;
|
||||
post_data_len = alloc_printf(&post_data, 0,
|
||||
"--%s\r\n"
|
||||
"Content-Disposition: form-data; "
|
||||
"name=\"file\"; "
|
||||
"filename=\"%s\"\r\n\r\n"
|
||||
"%.*s\r\n"
|
||||
"--%s--\r\n",
|
||||
boundary, upload_filename,
|
||||
file_len, file_data, boundary);
|
||||
ASSERT(post_data_len > 0);
|
||||
ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1,
|
||||
ebuf, sizeof(ebuf),
|
||||
"POST /upload?1 HTTP/1.1\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"Content-Type: multipart/form-data; "
|
||||
"boundary=%s\r\n\r\n"
|
||||
"%.*s", post_data_len, boundary,
|
||||
post_data_len, post_data)) != NULL);
|
||||
free(file_data), free(post_data);
|
||||
ASSERT(mg_read(conn, buf, sizeof(buf)) == (int) strlen(upload_ok_message));
|
||||
ASSERT(memcmp(buf, upload_ok_message, strlen(upload_ok_message)) == 0);
|
||||
mg_close_connection(conn);
|
||||
ASSERT((ctx = mg_start(OPTIONS, event_handler, NULL)) != NULL);
|
||||
|
||||
// Upload two files
|
||||
ASSERT((file_data = read_file("lua_5.2.1.h", &file_len)) != NULL);
|
||||
@ -411,21 +349,20 @@ static void test_mg_upload(void) {
|
||||
|
||||
// Final boundary
|
||||
"--%s--\r\n",
|
||||
boundary, upload_filename,
|
||||
boundary, "f1.txt",
|
||||
file_len, file_data,
|
||||
boundary, upload_filename2,
|
||||
boundary, "f2.txt",
|
||||
file2_len, file2_data,
|
||||
boundary);
|
||||
ASSERT(post_data_len > 0);
|
||||
ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1,
|
||||
ebuf, sizeof(ebuf),
|
||||
"POST /upload?2 HTTP/1.1\r\n"
|
||||
"POST /upload HTTP/1.1\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"Content-Type: multipart/form-data; "
|
||||
"boundary=%s\r\n\r\n"
|
||||
"%.*s", post_data_len, boundary,
|
||||
post_data_len, post_data)) != NULL);
|
||||
free(file_data), free(file2_data), free(post_data);
|
||||
ASSERT(mg_read(conn, buf, sizeof(buf)) == (int) strlen(upload_ok_message));
|
||||
ASSERT(memcmp(buf, upload_ok_message, strlen(upload_ok_message)) == 0);
|
||||
mg_close_connection(conn);
|
||||
@ -523,7 +460,7 @@ static void test_lua(void) {
|
||||
|
||||
conn.ctx = &ctx;
|
||||
conn.buf = http_request;
|
||||
conn.buf_size = conn.data_len = strlen(http_request);
|
||||
conn.buf_size = conn.data_len = conn.num_bytes_read = strlen(http_request);
|
||||
conn.request_len = parse_http_message(conn.buf, conn.data_len,
|
||||
&conn.request_info);
|
||||
conn.content_len = conn.data_len - conn.request_len;
|
||||
@ -595,7 +532,7 @@ static void test_request_replies(void) {
|
||||
{NULL, NULL},
|
||||
};
|
||||
|
||||
ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL);
|
||||
ASSERT((ctx = mg_start(OPTIONS, event_handler, NULL)) != NULL);
|
||||
for (i = 0; tests[i].request != NULL; i++) {
|
||||
ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
|
||||
tests[i].request)) != NULL);
|
||||
@ -604,39 +541,42 @@ static void test_request_replies(void) {
|
||||
mg_stop(ctx);
|
||||
}
|
||||
|
||||
static int api_callback(struct mg_connection *conn) {
|
||||
struct mg_request_info *ri = mg_get_request_info(conn);
|
||||
static const char *api_uri = "/?a=%20&b=&c=xx";
|
||||
static int api_cb(struct mg_event *event) {
|
||||
struct mg_request_info *ri = event->request_info;
|
||||
char post_data[100] = "";
|
||||
|
||||
ASSERT(ri->user_data == (void *) 123);
|
||||
ASSERT(ri->num_headers == 2);
|
||||
ASSERT(strcmp(mg_get_header(conn, "host"), "blah.com") == 0);
|
||||
ASSERT(mg_read(conn, post_data, sizeof(post_data)) == 3);
|
||||
ASSERT(memcmp(post_data, "b=1", 3) == 0);
|
||||
ASSERT(ri->query_string != NULL);
|
||||
ASSERT(ri->remote_ip > 0);
|
||||
ASSERT(ri->remote_port > 0);
|
||||
ASSERT(strcmp(ri->http_version, "1.0") == 0);
|
||||
if (event->type == MG_REQUEST_BEGIN) {
|
||||
ASSERT(event->user_data == (void *) 123);
|
||||
ASSERT(ri->num_headers == 2);
|
||||
ASSERT(strcmp(mg_get_header(event->conn, "host"), "blah.com") == 0);
|
||||
ASSERT(mg_read(event->conn, post_data, sizeof(post_data)) == 3);
|
||||
ASSERT(memcmp(post_data, "b=1", 3) == 0);
|
||||
ASSERT(ri->query_string != NULL);
|
||||
ASSERT(strcmp(ri->query_string, api_uri + 2) == 0);
|
||||
ASSERT(ri->remote_ip > 0);
|
||||
ASSERT(ri->remote_port > 0);
|
||||
ASSERT(strcmp(ri->http_version, "1.0") == 0);
|
||||
|
||||
mg_printf(conn, "HTTP/1.0 200 OK\r\n\r\n");
|
||||
return 1;
|
||||
mg_printf(event->conn, "HTTP/1.0 200 OK\r\n\r\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_api_calls(void) {
|
||||
char ebuf[100];
|
||||
struct mg_callbacks callbacks;
|
||||
struct mg_connection *conn;
|
||||
struct mg_context *ctx;
|
||||
static const char *request = "POST /?a=%20&b=&c=xx HTTP/1.0\r\n"
|
||||
static const char *fmt = "POST %s HTTP/1.0\r\n"
|
||||
"Host: blah.com\n" // More spaces before
|
||||
"content-length: 3\r\n" // Lower case header name
|
||||
"\r\nb=123456"; // Content size > content-length, test for mg_read()
|
||||
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
callbacks.begin_request = api_callback;
|
||||
ASSERT((ctx = mg_start(&callbacks, (void *) 123, OPTIONS)) != NULL);
|
||||
ASSERT((ctx = mg_start(OPTIONS, api_cb, (void *) 123)) != NULL);
|
||||
ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1,
|
||||
ebuf, sizeof(ebuf), "%s", request)) != NULL);
|
||||
ebuf, sizeof(ebuf), fmt, api_uri)) != NULL);
|
||||
mg_close_connection(conn);
|
||||
mg_stop(ctx);
|
||||
}
|
||||
@ -732,8 +672,8 @@ int __cdecl main(void) {
|
||||
test_base64_encode();
|
||||
test_match_prefix();
|
||||
test_remove_double_dots();
|
||||
test_should_keep_alive();
|
||||
test_parse_http_message();
|
||||
test_should_keep_alive();
|
||||
test_mg_download();
|
||||
test_mg_get_var();
|
||||
test_set_throttle();
|
||||
|
Loading…
x
Reference in New Issue
Block a user