diff --git a/docs/README.md b/docs/README.md index 99e6f773..2abc79a1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -648,11 +648,18 @@ Serve static file. ### mg\_http\_reply() ```c -void mg_http_reply(struct mg_connection *c, int code, const char *fmt, ...); +void mg_http_reply(struct mg_connection *c, int status_code, const char *headers, + const char *body_fmt, ...); ``` -Send simple HTTP response using `printf()` semantic. -`code` is HTTP response code, `fmt` is format for the HTTP body. +Send simple HTTP response using `printf()` semantic. This function formats +response body according to a `body_fmt`, and automatically appends a correct +`Content-Length` header. Extra headers could be passed via `headers` +parameter. + +- `status_code` - an HTTP response code +- `headers` - extra headers, default NULL. If not NULL, must end with `\r\n` +- `fmt` - a format string for the HTTP body, in a printf semantics ### mg\_http\_header() diff --git a/examples/esp32/main/main.c b/examples/esp32/main/main.c index 1538d53a..277841c1 100644 --- a/examples/esp32/main/main.c +++ b/examples/esp32/main/main.c @@ -10,7 +10,7 @@ // Event handler for an accepted connection static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { if (ev == MG_EV_HTTP_MSG) { - mg_http_reply(c, 200, "Hello from ESP32!"); + mg_http_reply(c, 200, "", "Hello from ESP32!"); } } diff --git a/examples/http-restful-server/main.c b/examples/http-restful-server/main.c index 9f098d01..7ad21982 100644 --- a/examples/http-restful-server/main.c +++ b/examples/http-restful-server/main.c @@ -14,9 +14,9 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { if (ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; if (mg_http_match_uri(hm, "/api/f1")) { - mg_http_reply(c, 200, "{\"result\": %d}\n", 123); // Serve REST response + mg_http_reply(c, 200, "", "{\"result\": %d}\n", 123); // Serve REST } else if (mg_http_match_uri(hm, "/api/f2/*")) { - mg_http_reply(c, 200, "{\"result\": \"%.*s\"}\n", (int) hm->uri.len, + mg_http_reply(c, 200, "", "{\"result\": \"%.*s\"}\n", (int) hm->uri.len, hm->uri.ptr); } else { mg_http_serve_dir(c, ev_data, s_web_directory); // Serve static files diff --git a/examples/websocket-server/main.c b/examples/websocket-server/main.c index 94adfc73..f9b76751 100644 --- a/examples/websocket-server/main.c +++ b/examples/websocket-server/main.c @@ -24,7 +24,7 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { mg_ws_upgrade(c, hm); } else if (mg_http_match_uri(hm, "/rest")) { // Serve REST response - mg_http_reply(c, 200, "{\"result\": %d}\n", 123); + mg_http_reply(c, 200, "", "{\"result\": %d}\n", 123); } else { // Serve static files mg_http_serve_dir(c, ev_data, s_web_directory); diff --git a/mongoose.c b/mongoose.c index 73672160..2973b5dd 100644 --- a/mongoose.c +++ b/mongoose.c @@ -586,17 +586,16 @@ void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) { mg_send(c, "\r\n", 2); } -void mg_http_reply(struct mg_connection *c, int code, const char *fmt, ...) { +void mg_http_reply(struct mg_connection *c, int code, const char *headers, + const char *fmt, ...) { char mem[100], *buf = mem; va_list ap; int len; va_start(ap, fmt); len = mg_vasprintf(&buf, sizeof(mem), fmt, ap); va_end(ap); - mg_printf(c, - "HTTP/1.1 %d OK\r\nContent-Type: text/plain\r\n" - "Content-Length: %d\r\n\r\n", - code, len); + mg_printf(c, "HTTP/1.1 %d OK\r\n%sContent-Length: %d\r\n\r\n", code, + headers == NULL ? "" : headers, len); mg_send(c, buf, len); if (buf != mem) free(buf); } @@ -623,7 +622,7 @@ int mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, mg_http_get_var(&hm->query, "offset", offset, sizeof(offset)); mg_http_get_var(&hm->query, "name", name, sizeof(name)); if (name[0] == '\0') { - mg_http_reply(c, 400, "%s", "name required"); + mg_http_reply(c, 400, "", "%s", "name required"); return -1; } else { FILE *fp; @@ -632,12 +631,12 @@ int mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, LOG(LL_DEBUG, ("%p %d bytes @ %d [%s]", c->fd, (int) hm->body.len, (int) oft, name)); if ((fp = fopen(path, oft == 0 ? "wb" : "a")) == NULL) { - mg_http_reply(c, 400, "fopen(%s): %d", name, errno); + mg_http_reply(c, 400, "", "fopen(%s): %d", name, errno); return -2; } else { fwrite(hm->body.ptr, 1, hm->body.len, fp); fclose(fp); - mg_http_reply(c, 200, ""); + mg_http_reply(c, 200, "", ""); return hm->body.len; } } @@ -730,7 +729,7 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, char etag[64]; if (fp == NULL || stat(path, &st) != 0 || mg_http_etag(etag, sizeof(etag), &st) != etag) { - mg_http_reply(c, 404, "%s", "Not found\n"); + mg_http_reply(c, 404, "", "%s", "Not found\n"); } else if (inm != NULL && mg_vcasecmp(inm, etag) == 0) { mg_printf(c, "HTTP/1.1 304 Not Modified\r\nContent-Length: 0\r\n\r\n"); } else { @@ -950,7 +949,7 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm, MG_VERSION); mg_http_write_chunk(c, "", 0); } else { - mg_http_reply(c, 400, "Cannot open dir"); + mg_http_reply(c, 400, "", "Cannot open dir"); LOG(LL_DEBUG, ("%p opendir(%s) -> %d", c->fd, dir, errno)); } } @@ -963,7 +962,7 @@ void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, if (realpath(dir, root) == NULL) LOG(LL_DEBUG, ("realpath(%s): %d", dir, errno)); if (!mg_is_dir(root)) { - mg_http_reply(c, 400, "Bad web root [%s]\n", root); + mg_http_reply(c, 400, "", "Bad web root [%s]\n", root); } else { // NOTE(lsm): Xilinx snprintf does not 0-terminate the detination for // the %.*s specifier, if the length is zero. Make sure hm->uri.len > 0 @@ -979,7 +978,7 @@ void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, is_index = true; } if (strlen(real) < strlen(root) || memcmp(real, root, strlen(root)) != 0) { - mg_http_reply(c, 404, "Not found %.*s\n", hm->uri.len, hm->uri.ptr); + mg_http_reply(c, 404, "", "Not found %.*s\n", hm->uri.len, hm->uri.ptr); } else { FILE *fp = fopen(real, "r"); #if MG_ENABLE_HTTP_DEBUG_ENDPOINT @@ -989,7 +988,7 @@ void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, #if MG_ENABLE_DIRECTORY_LISTING listdir(c, hm, real); #else - mg_http_reply(c, 403, "%s", "Directory listing not supported"); + mg_http_reply(c, 403, "", "%s", "Directory listing not supported"); #endif } else { mg_http_serve_file(c, hm, real, guess_content_type(real)); diff --git a/mongoose.h b/mongoose.h index c8cb9720..3116c809 100644 --- a/mongoose.h +++ b/mongoose.h @@ -676,7 +676,8 @@ void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm, const char *path); void mg_http_serve_file(struct mg_connection *, struct mg_http_message *, const char *, const char *mime); -void mg_http_reply(struct mg_connection *c, int code, const char *fmt, ...); +void mg_http_reply(struct mg_connection *, int status_code, const char *headers, + const char *body_fmt, ...); struct mg_str *mg_http_get_header(struct mg_http_message *, const char *name); void mg_http_event_handler(struct mg_connection *c, int ev); int mg_http_get_var(const struct mg_str *, const char *name, char *, int); diff --git a/src/http.c b/src/http.c index 3659b649..4c10b4d0 100644 --- a/src/http.c +++ b/src/http.c @@ -219,17 +219,16 @@ void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) { mg_send(c, "\r\n", 2); } -void mg_http_reply(struct mg_connection *c, int code, const char *fmt, ...) { +void mg_http_reply(struct mg_connection *c, int code, const char *headers, + const char *fmt, ...) { char mem[100], *buf = mem; va_list ap; int len; va_start(ap, fmt); len = mg_vasprintf(&buf, sizeof(mem), fmt, ap); va_end(ap); - mg_printf(c, - "HTTP/1.1 %d OK\r\nContent-Type: text/plain\r\n" - "Content-Length: %d\r\n\r\n", - code, len); + mg_printf(c, "HTTP/1.1 %d OK\r\n%sContent-Length: %d\r\n\r\n", code, + headers == NULL ? "" : headers, len); mg_send(c, buf, len); if (buf != mem) free(buf); } @@ -256,7 +255,7 @@ int mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, mg_http_get_var(&hm->query, "offset", offset, sizeof(offset)); mg_http_get_var(&hm->query, "name", name, sizeof(name)); if (name[0] == '\0') { - mg_http_reply(c, 400, "%s", "name required"); + mg_http_reply(c, 400, "", "%s", "name required"); return -1; } else { FILE *fp; @@ -265,12 +264,12 @@ int mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, LOG(LL_DEBUG, ("%p %d bytes @ %d [%s]", c->fd, (int) hm->body.len, (int) oft, name)); if ((fp = fopen(path, oft == 0 ? "wb" : "a")) == NULL) { - mg_http_reply(c, 400, "fopen(%s): %d", name, errno); + mg_http_reply(c, 400, "", "fopen(%s): %d", name, errno); return -2; } else { fwrite(hm->body.ptr, 1, hm->body.len, fp); fclose(fp); - mg_http_reply(c, 200, ""); + mg_http_reply(c, 200, "", ""); return hm->body.len; } } @@ -363,7 +362,7 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, char etag[64]; if (fp == NULL || stat(path, &st) != 0 || mg_http_etag(etag, sizeof(etag), &st) != etag) { - mg_http_reply(c, 404, "%s", "Not found\n"); + mg_http_reply(c, 404, "", "%s", "Not found\n"); } else if (inm != NULL && mg_vcasecmp(inm, etag) == 0) { mg_printf(c, "HTTP/1.1 304 Not Modified\r\nContent-Length: 0\r\n\r\n"); } else { @@ -583,7 +582,7 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm, MG_VERSION); mg_http_write_chunk(c, "", 0); } else { - mg_http_reply(c, 400, "Cannot open dir"); + mg_http_reply(c, 400, "", "Cannot open dir"); LOG(LL_DEBUG, ("%p opendir(%s) -> %d", c->fd, dir, errno)); } } @@ -596,7 +595,7 @@ void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, if (realpath(dir, root) == NULL) LOG(LL_DEBUG, ("realpath(%s): %d", dir, errno)); if (!mg_is_dir(root)) { - mg_http_reply(c, 400, "Bad web root [%s]\n", root); + mg_http_reply(c, 400, "", "Bad web root [%s]\n", root); } else { // NOTE(lsm): Xilinx snprintf does not 0-terminate the detination for // the %.*s specifier, if the length is zero. Make sure hm->uri.len > 0 @@ -612,7 +611,7 @@ void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, is_index = true; } if (strlen(real) < strlen(root) || memcmp(real, root, strlen(root)) != 0) { - mg_http_reply(c, 404, "Not found %.*s\n", hm->uri.len, hm->uri.ptr); + mg_http_reply(c, 404, "", "Not found %.*s\n", hm->uri.len, hm->uri.ptr); } else { FILE *fp = fopen(real, "r"); #if MG_ENABLE_HTTP_DEBUG_ENDPOINT @@ -622,7 +621,7 @@ void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, #if MG_ENABLE_DIRECTORY_LISTING listdir(c, hm, real); #else - mg_http_reply(c, 403, "%s", "Directory listing not supported"); + mg_http_reply(c, 403, "", "%s", "Directory listing not supported"); #endif } else { mg_http_serve_file(c, hm, real, guess_content_type(real)); diff --git a/src/http.h b/src/http.h index 225e57b4..0496dc77 100644 --- a/src/http.h +++ b/src/http.h @@ -32,7 +32,8 @@ void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm, const char *path); void mg_http_serve_file(struct mg_connection *, struct mg_http_message *, const char *, const char *mime); -void mg_http_reply(struct mg_connection *c, int code, const char *fmt, ...); +void mg_http_reply(struct mg_connection *, int status_code, const char *headers, + const char *body_fmt, ...); struct mg_str *mg_http_get_header(struct mg_http_message *, const char *name); void mg_http_event_handler(struct mg_connection *c, int ev); int mg_http_get_var(const struct mg_str *, const char *name, char *, int); diff --git a/test/unit_test.c b/test/unit_test.c index 20e4c274..f4b56777 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -323,19 +323,19 @@ static void eh1(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { ("[%.*s %.*s] message len %d", (int) hm->method.len, hm->method.ptr, (int) hm->uri.len, hm->uri.ptr, (int) hm->message.len)); if (mg_http_match_uri(hm, "/foo/*")) { - mg_http_reply(c, 200, "uri: %.*s", hm->uri.len - 5, hm->uri.ptr + 5); + mg_http_reply(c, 200, "", "uri: %.*s", hm->uri.len - 5, hm->uri.ptr + 5); } else if (mg_http_match_uri(hm, "/ws")) { mg_ws_upgrade(c, hm); } else if (mg_http_match_uri(hm, "/body")) { - mg_http_reply(c, 200, "%.*s", (int) hm->body.len, hm->body.ptr); + mg_http_reply(c, 200, "", "%.*s", (int) hm->body.len, hm->body.ptr); } else if (mg_http_match_uri(hm, "/bar")) { - mg_http_reply(c, 404, "not found"); + mg_http_reply(c, 404, "", "not found"); } else if (mg_http_match_uri(hm, "/badroot")) { mg_http_serve_dir(c, hm, "/BAAADDD!"); } else if (mg_http_match_uri(hm, "/creds")) { char user[100], pass[100]; mg_http_creds(hm, user, sizeof(user), pass, sizeof(pass)); - mg_http_reply(c, 200, "[%s]:[%s]", user, pass); + mg_http_reply(c, 200, "", "[%s]:[%s]", user, pass); } else if (mg_http_match_uri(hm, "/upload")) { mg_http_upload(c, hm, "."); } else if (mg_http_match_uri(hm, "/test/")) { @@ -470,10 +470,10 @@ static void test_http_server(void) { ASSERT(cmpbody(buf, "kuku") == 0); ASSERT(fetch(&mgr, buf, url, "GET /badroot HTTP/1.0\r\n\n") == 400); - // LOG(LL_INFO, ("--> [%s]", buf)); #if MG_ARCH == MG_ARCH_WIN32 ASSERT(cmpbody(buf, "Bad web root [Z:\\BAAADDD!]\n") == 0); #else + LOG(LL_INFO, ("--> [%s]", buf)); ASSERT(cmpbody(buf, "Bad web root [/BAAADDD!]\n") == 0); #endif