From d0e274183210e8c1076e6b66f41ccb9b116e3a8e Mon Sep 17 00:00:00 2001 From: Sergey Lyubka <valenok@gmail.com> Date: Sat, 4 Jan 2014 11:31:55 +0000 Subject: [PATCH] Added new response creation interface --- build/test/{page.lp => page.mg.lua} | 0 docs/API.md | 38 ++++++--- examples/hello.c | 6 +- mongoose.c | 125 ++++++++++++++++++++-------- mongoose.h | 10 ++- 5 files changed, 129 insertions(+), 50 deletions(-) rename build/test/{page.lp => page.mg.lua} (100%) diff --git a/build/test/page.lp b/build/test/page.mg.lua similarity index 100% rename from build/test/page.lp rename to build/test/page.mg.lua diff --git a/docs/API.md b/docs/API.md index af60b5c4d..0eb991643 100644 --- a/docs/API.md +++ b/docs/API.md @@ -112,16 +112,34 @@ is returned. This is an interface primarily designed to push arbitrary data to websocket connections at any time. This function could be called from any thread. When -it returns, an IO thread called `func()` on each active websocket connection, -passing `param` as an extra parameter. It is allowed to call `mg_write()` or -`mg_websocket_write()` within a callback, cause these write functions are -thread-safe. - - int mg_write(struct mg_connection *, const void *buf, int len); - -Send data to the client. This function is thread-safe. This function appends -given buffer to a send queue of a given connection. It may return 0 if -there is not enough memory, in which case the data will not be sent. +it returns, an IO thread called `func()` on each active connection, +passing `param` as an extra parameter. It is allowed to call `mg_send_data()` or +`mg_websocket_write()` within a callback, cause `func` is executed in the +context of the IO thread. + + void mg_send_status(struct mg_connection *, int status_code); + void mg_send_header(struct mg_connection *, const char *name, + const char *value); + void mg_send_data(struct mg_connection *, const void *data, int data_len); + void mg_printf_data(struct mg_connection *, const char *format, ...); + +These functions are used to construct a response to the client. HTTP response +consists of three parts: + + * a status line + * zero or more HTTP headers + * response body + +Mongoose provides functions for all three parts: + * `mg_send_status()` is used to create status line. This function can be + called zero or once. If `mg_send_status()` is not called, then Mongoose + will send status 200 (success) implicitly. + * `mg_send_header()` adds HTTP header to the response. This function could + be called zero or more times. + * `mg_send_data()` and `mg_printf_data()` are used to send data to the + client. Note that Mongoose adds `Transfer-Encoding: chunked` header + implicitly, and sends data in chunks. Therefore, it is not necessary to + set `Content-Length` header. int mg_websocket_write(struct mg_connection* conn, int opcode, const char *data, size_t data_len); diff --git a/examples/hello.c b/examples/hello.c index 1f584cfef..c4fca13cf 100644 --- a/examples/hello.c +++ b/examples/hello.c @@ -4,9 +4,9 @@ // This function will be called by mongoose on every new request static int index_html(struct mg_connection *conn) { - mg_printf(conn, "HTTP/1.0 200 OK\r\n\r\n" - "Hello! Requested URI is [%s], query string is [%s]", conn->uri, - conn->query_string == NULL ? "(none)" : conn->query_string); + mg_printf_data(conn, "Hello! Requested URI is [%s], query string is [%s]", + conn->uri, + conn->query_string == NULL ? "(none)" : conn->query_string); return 0; } diff --git a/mongoose.c b/mongoose.c index 94012acf3..b0b4b3ee8 100644 --- a/mongoose.c +++ b/mongoose.c @@ -227,7 +227,8 @@ union endpoint { enum endpoint_type { EP_NONE, EP_FILE, EP_CGI, EP_USER, EP_PUT }; enum connection_flags { - CONN_CLOSE = 1, CONN_SPOOL_DONE = 2, CONN_SSL_HANDS_SHAKEN = 4 + CONN_CLOSE = 1, CONN_SPOOL_DONE = 2, CONN_SSL_HANDS_SHAKEN = 4, + CONN_HEADERS_SENT = 8 }; struct connection { @@ -445,28 +446,32 @@ static int mg_snprintf(char *buf, size_t buflen, const char *fmt, ...) { return n; } +static const char *status_code_to_str(int status_code) { + switch (status_code) { + case 200: return "OK"; + case 201: return "Created"; + case 204: return "No Content"; + case 304: return "Not Modified"; + case 400: return "Bad Request"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 409: return "Conflict"; + case 411: return "Length Required"; + case 413: return "Request Entity Too Large"; + case 415: return "Unsupported Media Type"; + case 423: return "Locked"; + case 500: return "Server Error"; + case 501: return "Not Implemented"; + default: return "Server Error"; + } +} + static void send_http_error(struct connection *conn, int code) { - const char *message; + const char *message = status_code_to_str(code); char headers[200], body[200]; int body_len, headers_len; - switch (code) { - case 201: message = "Created"; break; - case 204: message = "No Content"; break; - case 304: message = "Not Modified"; break; - case 400: message = "Bad Request"; break; - case 403: message = "Forbidden"; break; - case 404: message = "Not Found"; break; - case 405: message = "Method Not Allowed"; break; - case 409: message = "Conflict"; break; - case 411: message = "Length Required"; break; - case 413: message = "Request Entity Too Large"; break; - case 415: message = "Unsupported Media Type"; break; - case 423: message = "Locked"; break; - case 501: message = "Not Implemented"; break; - default: message = "Server Error"; break; - } - conn->mg_conn.status_code = code; body_len = mg_snprintf(body, sizeof(body), "%d %s\n", code, message); if (code >= 300 && code <= 399) { @@ -522,12 +527,25 @@ static int alloc_vprintf(char **buf, size_t size, const char *fmt, va_list ap) { return len; } -int mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap) { +static void write_chunk(struct connection *conn, const char *buf, int len) { + char chunk_size[50]; + int n = mg_snprintf(chunk_size, sizeof(chunk_size), "%X\r\n", len); + spool(&conn->remote_iobuf, chunk_size, n); + spool(&conn->remote_iobuf, buf, len); + spool(&conn->remote_iobuf, "\r\n", 2); +} + +int mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap, + int chunked) { char mem[IOBUF_SIZE], *buf = mem; int len; if ((len = alloc_vprintf(&buf, sizeof(mem), fmt, ap)) > 0) { - len = mg_write(conn, buf, (size_t) len); + if (chunked) { + write_chunk((struct connection *) conn, buf, len); + } else { + len = mg_write(conn, buf, (size_t) len); + } } if (buf != mem && buf != NULL) { free(buf); @@ -537,9 +555,12 @@ int mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap) { } int mg_printf(struct mg_connection *conn, const char *fmt, ...) { + int len; va_list ap; va_start(ap, fmt); - return mg_vprintf(conn, fmt, ap); + len = mg_vprintf(conn, fmt, ap, 0); + va_end(ap); + return len; } // A helper function for traversing a comma separated list of values. @@ -1292,6 +1313,45 @@ int mg_write(struct mg_connection *c, const void *buf, int len) { return spool(&((struct connection *) c)->remote_iobuf, buf, len); } +void mg_send_status(struct mg_connection *c, int status) { + if (c->status_code == 0) { + c->status_code = status; + mg_printf(c, "HTTP/1.1 %d %s\r\n", status, status_code_to_str(status)); + } +} + +void mg_send_header(struct mg_connection *c, const char *name, const char *v) { + if (c->status_code == 0) { + c->status_code = 200; + mg_printf(c, "HTTP/1.1 %d %s\r\n", 200, status_code_to_str(200)); + } + mg_printf(c, "%s: %s\r\n", name, v); +} + +static void terminate_headers(struct mg_connection *c) { + struct connection *conn = (struct connection *) c; + if (!(conn->flags & CONN_HEADERS_SENT)) { + mg_send_header(c, "Transfer-Encoding", "chunked"); + mg_write(c, "\r\n", 2); + conn->flags |= CONN_HEADERS_SENT; + } +} + +void mg_send_data(struct mg_connection *c, const void *data, int data_len) { + terminate_headers(c); + write_chunk((struct connection *) c, data, data_len); +} + +void mg_printf_data(struct mg_connection *c, const char *fmt, ...) { + va_list ap; + + terminate_headers(c); + + va_start(ap, fmt); + mg_vprintf(c, fmt, ap, 1); + va_end(ap); +} + #if !defined(NO_WEBSOCKET) || !defined(NO_AUTH) static int is_big_endian(void) { static const int n = 1; @@ -1852,6 +1912,9 @@ static void call_uri_handler_if_data_is_buffered(struct connection *conn) { #endif if (loc->len >= c->content_len) { conn->endpoint.uh->handler(c); + if (conn->flags & CONN_HEADERS_SENT) { + write_chunk(conn, "", 0); // Write final zero-length chunk + } close_local_endpoint(conn); } } @@ -2032,16 +2095,6 @@ static void mg_url_encode(const char *src, char *dst, size_t dst_len) { #endif // !NO_DIRECTORY_LISTING || !NO_DAV #ifndef NO_DIRECTORY_LISTING -static int mg_write_chunked(struct connection *conn, const char *buf, int len) { - char chunk_size[50]; - int n = mg_snprintf(chunk_size, sizeof(chunk_size), "%X\r\n", len); - - n = spool(&conn->remote_iobuf, chunk_size, n); - n += spool(&conn->remote_iobuf, buf, len); - n += spool(&conn->remote_iobuf, "\r\n", 2); - - return n; -} static void print_dir_entry(const struct dir_entry *de) { char size[64], mod[64], href[MAX_PATH_SIZE * 3], chunk[MAX_PATH_SIZE * 4]; @@ -2071,7 +2124,7 @@ static void print_dir_entry(const struct dir_entry *de) { "<td> %s</td><td> %s</td></tr>\n", de->conn->mg_conn.uri, href, slash, de->file_name, slash, mod, size); - mg_write_chunked((struct connection *) de->conn, chunk, n); + write_chunk((struct connection *) de->conn, chunk, n); } // Sort directory entries by size, or name, or modification time. @@ -2124,7 +2177,7 @@ static void send_directory_listing(struct connection *conn, const char *dir) { "<tr><td colspan=\"3\"><hr></td></tr>", conn->mg_conn.uri, conn->mg_conn.uri, sort_direction, sort_direction, sort_direction); - mg_write_chunked(conn, buf, strlen(buf)); + write_chunk(conn, buf, strlen(buf)); num_entries = scan_directory(conn, dir, &arr); qsort(arr, num_entries, sizeof(arr[0]), compare_dir_entries); @@ -2134,7 +2187,7 @@ static void send_directory_listing(struct connection *conn, const char *dir) { } free(arr); - mg_write_chunked(conn, "", 0); // Write final zero-length chunk + write_chunk(conn, "", 0); // Write final zero-length chunk close_local_endpoint(conn); } #endif // NO_DIRECTORY_LISTING @@ -3226,7 +3279,7 @@ static void log_access(const struct connection *conn, const char *path) { static void close_local_endpoint(struct connection *conn) { // Must be done before free() int keep_alive = should_keep_alive(&conn->mg_conn) && - conn->endpoint_type == EP_FILE; + (conn->endpoint_type == EP_FILE || conn->endpoint_type == EP_USER); switch (conn->endpoint_type) { case EP_FILE: close(conn->endpoint.fd); break; diff --git a/mongoose.h b/mongoose.h index aae1d165c..3f54a8927 100644 --- a/mongoose.h +++ b/mongoose.h @@ -74,11 +74,19 @@ void mg_iterate_over_connections(struct mg_server *, void *param); // Connection management functions -int mg_write(struct mg_connection *, const void *buf, int len); +void mg_send_status(struct mg_connection *, int status_code); +void mg_send_header(struct mg_connection *, const char *name, const char *val); +void mg_send_data(struct mg_connection *, const void *data, int data_len); +void mg_printf_data(struct mg_connection *, const char *format, ...); + int mg_websocket_write(struct mg_connection *, int opcode, const char *data, size_t data_len); + +// Deprecated in favor of mg_send_* interface +int mg_write(struct mg_connection *, const void *buf, int len); int mg_printf(struct mg_connection *conn, const char *fmt, ...); + const char *mg_get_header(const struct mg_connection *, const char *name); const char *mg_get_mime_type(const char *file_name); int mg_get_var(const struct mg_connection *conn, const char *var_name, -- GitLab