Refactored API, returned back to event-based handlers. Upload and Websocket API simplified

This commit is contained in:
Sergey Lyubka 2013-09-28 11:00:54 +01:00
parent 96eb48902b
commit ecbf79135f
9 changed files with 402 additions and 482 deletions

View File

@ -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]);
}

View File

@ -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);
}

View File

@ -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().

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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) {

View File

@ -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.

View File

@ -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();