diff options
-rw-r--r-- | auto/sources | 1 | ||||
-rw-r--r-- | src/nxt_conf.h | 1 | ||||
-rw-r--r-- | src/nxt_conf_validation.c | 129 | ||||
-rw-r--r-- | src/nxt_http.h | 29 | ||||
-rw-r--r-- | src/nxt_http_request.c | 25 | ||||
-rw-r--r-- | src/nxt_http_route.c | 28 | ||||
-rw-r--r-- | src/nxt_http_static.c | 599 | ||||
-rw-r--r-- | src/nxt_router.c | 93 | ||||
-rw-r--r-- | src/nxt_router.h | 6 | ||||
-rw-r--r-- | src/nxt_sprintf.c | 4 | ||||
-rw-r--r-- | src/nxt_string.c | 66 | ||||
-rw-r--r-- | src/nxt_string.h | 1 |
12 files changed, 953 insertions, 29 deletions
diff --git a/auto/sources b/auto/sources index 858eaa8c..c4b3808b 100644 --- a/auto/sources +++ b/auto/sources @@ -84,6 +84,7 @@ NXT_LIB_SRCS=" \ src/nxt_http_response.c \ src/nxt_http_error.c \ src/nxt_http_route.c \ + src/nxt_http_static.c \ src/nxt_application.c \ src/nxt_external.c \ src/nxt_port_hash.c \ diff --git a/src/nxt_conf.h b/src/nxt_conf.h index 2435b0e2..725a6c95 100644 --- a/src/nxt_conf.h +++ b/src/nxt_conf.h @@ -71,6 +71,7 @@ typedef struct { nxt_conf_value_t *conf; nxt_mp_t *pool; nxt_str_t error; + void *ctx; } nxt_conf_validation_t; diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 078ddd17..c934b10b 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -8,6 +8,7 @@ #include <nxt_conf.h> #include <nxt_cert.h> #include <nxt_router.h> +#include <nxt_http.h> typedef enum { @@ -45,6 +46,12 @@ static nxt_int_t nxt_conf_vldt_type(nxt_conf_validation_t *vldt, static nxt_int_t nxt_conf_vldt_error(nxt_conf_validation_t *vldt, const char *fmt, ...); +static nxt_int_t nxt_conf_vldt_mtypes(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_mtypes_type(nxt_conf_validation_t *vldt, + nxt_str_t *name, nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_mtypes_extension(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_listener(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value); #if (NXT_TLS) @@ -130,6 +137,16 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_websocket_members[] = { }; +static nxt_conf_vldt_object_t nxt_conf_vldt_static_members[] = { + { nxt_string("mime_types"), + NXT_CONF_VLDT_OBJECT, + &nxt_conf_vldt_mtypes, + NULL }, + + NXT_CONF_VLDT_END +}; + + static nxt_conf_vldt_object_t nxt_conf_vldt_http_members[] = { { nxt_string("header_read_timeout"), NXT_CONF_VLDT_INTEGER, @@ -161,6 +178,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_http_members[] = { &nxt_conf_vldt_object, (void *) &nxt_conf_vldt_websocket_members }, + { nxt_string("static"), + NXT_CONF_VLDT_OBJECT, + &nxt_conf_vldt_object, + (void *) &nxt_conf_vldt_static_members }, + NXT_CONF_VLDT_END }; @@ -289,6 +311,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_action_members[] = { &nxt_conf_vldt_pass, NULL }, + { nxt_string("share"), + NXT_CONF_VLDT_STRING, + NULL, + NULL }, + NXT_CONF_VLDT_END }; @@ -735,6 +762,108 @@ nxt_conf_vldt_error(nxt_conf_validation_t *vldt, const char *fmt, ...) } +typedef struct { + nxt_mp_t *pool; + nxt_str_t *type; + nxt_lvlhsh_t hash; +} nxt_conf_vldt_mtypes_ctx_t; + + +static nxt_int_t +nxt_conf_vldt_mtypes(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, + void *data) +{ + nxt_int_t ret; + nxt_conf_vldt_mtypes_ctx_t ctx; + + ctx.pool = nxt_mp_create(1024, 128, 256, 32); + if (nxt_slow_path(ctx.pool == NULL)) { + return NXT_ERROR; + } + + nxt_lvlhsh_init(&ctx.hash); + + vldt->ctx = &ctx; + + ret = nxt_conf_vldt_object_iterator(vldt, value, + &nxt_conf_vldt_mtypes_type); + + vldt->ctx = NULL; + + nxt_mp_destroy(ctx.pool); + + return ret; +} + + +static nxt_int_t +nxt_conf_vldt_mtypes_type(nxt_conf_validation_t *vldt, nxt_str_t *name, + nxt_conf_value_t *value) +{ + nxt_int_t ret; + nxt_conf_vldt_mtypes_ctx_t *ctx; + + ret = nxt_conf_vldt_type(vldt, name, value, + NXT_CONF_VLDT_STRING|NXT_CONF_VLDT_ARRAY); + if (ret != NXT_OK) { + return ret; + } + + ctx = vldt->ctx; + + ctx->type = nxt_mp_get(ctx->pool, sizeof(nxt_str_t)); + if (nxt_slow_path(ctx->type == NULL)) { + return NXT_ERROR; + } + + *ctx->type = *name; + + if (nxt_conf_type(value) == NXT_CONF_ARRAY) { + return nxt_conf_vldt_array_iterator(vldt, value, + &nxt_conf_vldt_mtypes_extension); + } + + /* NXT_CONF_STRING */ + + return nxt_conf_vldt_mtypes_extension(vldt, value); +} + + +static nxt_int_t +nxt_conf_vldt_mtypes_extension(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value) +{ + nxt_str_t ext, *dup_type; + nxt_conf_vldt_mtypes_ctx_t *ctx; + + ctx = vldt->ctx; + + if (nxt_conf_type(value) != NXT_CONF_STRING) { + return nxt_conf_vldt_error(vldt, "The \"%V\" MIME type array must " + "contain only strings.", ctx->type); + } + + nxt_conf_get_string(value, &ext); + + if (ext.length == 0) { + return nxt_conf_vldt_error(vldt, "An empty file extension for " + "the \"%V\" MIME type.", ctx->type); + } + + dup_type = nxt_http_static_mtypes_hash_find(&ctx->hash, &ext); + + if (dup_type != NULL) { + return nxt_conf_vldt_error(vldt, "The \"%V\" file extension has been " + "declared for \"%V\" and \"%V\" " + "MIME types at the same time.", + &ext, dup_type, ctx->type); + } + + return nxt_http_static_mtypes_hash_add(ctx->pool, &ctx->hash, + &ext, ctx->type); +} + + static nxt_int_t nxt_conf_vldt_listener(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value) diff --git a/src/nxt_http.h b/src/nxt_http.h index 2ca3f536..560b7310 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -24,7 +24,9 @@ typedef enum { NXT_HTTP_NOT_MODIFIED = 304, NXT_HTTP_BAD_REQUEST = 400, + NXT_HTTP_FORBIDDEN = 403, NXT_HTTP_NOT_FOUND = 404, + NXT_HTTP_METHOD_NOT_ALLOWED = 405, NXT_HTTP_REQUEST_TIMEOUT = 408, NXT_HTTP_LENGTH_REQUIRED = 411, NXT_HTTP_PAYLOAD_TOO_LARGE = 413, @@ -187,6 +189,25 @@ typedef struct { } nxt_http_proto_table_t; +#define NXT_HTTP_DATE_LEN nxt_length("Wed, 31 Dec 1986 16:40:00 GMT") + +nxt_inline u_char * +nxt_http_date(u_char *buf, struct tm *tm) +{ + static const char *week[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", + "Sat" }; + + static const char *month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + + return nxt_sprintf(buf, buf + NXT_HTTP_DATE_LEN, + "%s, %02d %s %4d %02d:%02d:%02d GMT", + week[tm->tm_wday], tm->tm_mday, + month[tm->tm_mon], tm->tm_year + 1900, + tm->tm_hour, tm->tm_min, tm->tm_sec); +} + + nxt_int_t nxt_http_init(nxt_task_t *task, nxt_runtime_t *rt); nxt_int_t nxt_h1p_init(nxt_task_t *task, nxt_runtime_t *rt); nxt_int_t nxt_http_response_hash_init(nxt_task_t *task, nxt_runtime_t *rt); @@ -225,6 +246,14 @@ nxt_http_pass_t *nxt_http_pass_application(nxt_task_t *task, void nxt_http_routes_cleanup(nxt_task_t *task, nxt_http_routes_t *routes); void nxt_http_pass_cleanup(nxt_task_t *task, nxt_http_pass_t *pass); +nxt_http_pass_t *nxt_http_static_handler(nxt_task_t *task, + nxt_http_request_t *r, nxt_http_pass_t *pass); +nxt_int_t nxt_http_static_mtypes_init(nxt_mp_t *mp, nxt_lvlhsh_t *hash); +nxt_int_t nxt_http_static_mtypes_hash_add(nxt_mp_t *mp, nxt_lvlhsh_t *hash, + nxt_str_t *extension, nxt_str_t *type); +nxt_str_t *nxt_http_static_mtypes_hash_find(nxt_lvlhsh_t *hash, + nxt_str_t *extension); + nxt_http_pass_t *nxt_http_request_application(nxt_task_t *task, nxt_http_request_t *r, nxt_http_pass_t *pass); diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index 8c38eeb2..a18a02e7 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -17,8 +17,8 @@ static void nxt_http_request_mem_buf_completion(nxt_task_t *task, void *obj, void *data); static void nxt_http_request_done(nxt_task_t *task, void *obj, void *data); -static u_char *nxt_http_date(u_char *buf, nxt_realtime_t *now, struct tm *tm, - size_t size, const char *format); +static u_char *nxt_http_date_cache_handler(u_char *buf, nxt_realtime_t *now, + struct tm *tm, size_t size, const char *format); static const nxt_http_request_state_t nxt_http_request_init_state; @@ -27,9 +27,9 @@ static const nxt_http_request_state_t nxt_http_request_body_state; nxt_time_string_t nxt_http_date_cache = { (nxt_atomic_uint_t) -1, - nxt_http_date, - "%s, %02d %s %4d %02d:%02d:%02d GMT", - nxt_length("Wed, 31 Dec 1986 16:40:00 GMT"), + nxt_http_date_cache_handler, + NULL, + NXT_HTTP_DATE_LEN, NXT_THREAD_TIME_GMT, NXT_THREAD_TIME_SEC, }; @@ -578,17 +578,8 @@ nxt_http_request_close_handler(nxt_task_t *task, void *obj, void *data) static u_char * -nxt_http_date(u_char *buf, nxt_realtime_t *now, struct tm *tm, size_t size, - const char *format) +nxt_http_date_cache_handler(u_char *buf, nxt_realtime_t *now, struct tm *tm, + size_t size, const char *format) { - static const char *week[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", - "Sat" }; - - static const char *month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - - return nxt_sprintf(buf, buf + size, format, - week[tm->tm_wday], tm->tm_mday, - month[tm->tm_mon], tm->tm_year + 1900, - tm->tm_hour, tm->tm_min, tm->tm_sec); + return nxt_http_date(buf, tm); } diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index 0b665573..c3c11faa 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -376,15 +376,9 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_http_route_match_conf_t mtcf; static nxt_str_t pass_path = nxt_string("/action/pass"); + static nxt_str_t share_path = nxt_string("/action/share"); static nxt_str_t match_path = nxt_string("/match"); - pass_conf = nxt_conf_get_path(cv, &pass_path); - if (nxt_slow_path(pass_conf == NULL)) { - return NULL; - } - - nxt_conf_get_string(pass_conf, &pass); - match_conf = nxt_conf_get_path(cv, &match_path); n = (match_conf != NULL) ? nxt_conf_object_members_count(match_conf) : 0; @@ -401,6 +395,19 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, match->pass.handler = NULL; match->items = n; + pass_conf = nxt_conf_get_path(cv, &pass_path); + + if (pass_conf == NULL) { + pass_conf = nxt_conf_get_path(cv, &share_path); + if (nxt_slow_path(pass_conf == NULL)) { + return NULL; + } + + match->pass.handler = nxt_http_static_handler; + } + + nxt_conf_get_string(pass_conf, &pass); + string = nxt_str_dup(mp, &match->pass.name, &pass); if (nxt_slow_path(string == NULL)) { return NULL; @@ -870,13 +877,18 @@ static void nxt_http_route_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_http_route_t *route) { + nxt_http_pass_t *pass; nxt_http_route_match_t **match, **end; match = &route->match[0]; end = match + route->items; while (match < end) { - nxt_http_pass_resolve(task, tmcf, &(*match)->pass); + pass = &(*match)->pass; + + if (pass->handler == NULL) { + nxt_http_pass_resolve(task, tmcf, &(*match)->pass); + } match++; } diff --git a/src/nxt_http_static.c b/src/nxt_http_static.c new file mode 100644 index 00000000..44b85389 --- /dev/null +++ b/src/nxt_http_static.c @@ -0,0 +1,599 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include <nxt_router.h> +#include <nxt_http.h> + + +#define NXT_HTTP_STATIC_BUF_COUNT 2 +#define NXT_HTTP_STATIC_BUF_SIZE (128 * 1024) + + +static void nxt_http_static_extract_extension(nxt_str_t *path, + nxt_str_t *extension); +static void nxt_http_static_body_handler(nxt_task_t *task, void *obj, + void *data); +static void nxt_http_static_buf_completion(nxt_task_t *task, void *obj, + void *data); + +static nxt_int_t nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq, + void *data); +static void *nxt_http_static_mtypes_hash_alloc(void *data, size_t size); +static void nxt_http_static_mtypes_hash_free(void *data, void *p); + + +static const nxt_http_request_state_t nxt_http_static_send_state; + + +nxt_http_pass_t * +nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, + nxt_http_pass_t *pass) +{ + size_t alloc, encode; + u_char *p; + struct tm tm; + nxt_buf_t *fb; + nxt_int_t ret; + nxt_str_t index, extension, *mtype; + nxt_uint_t level; + nxt_bool_t need_body; + nxt_file_t *f; + nxt_file_info_t fi; + nxt_http_field_t *field; + nxt_http_status_t status; + nxt_router_conf_t *rtcf; + nxt_work_handler_t body_handler; + + if (nxt_slow_path(!nxt_str_eq(r->method, "GET", 3))) { + + if (!nxt_str_eq(r->method, "HEAD", 4)) { + nxt_http_request_error(task, r, NXT_HTTP_METHOD_NOT_ALLOWED); + return NULL; + } + + need_body = 0; + + } else { + need_body = 1; + } + + f = nxt_mp_zget(r->mem_pool, sizeof(nxt_file_t)); + if (nxt_slow_path(f == NULL)) { + goto fail; + } + + f->fd = NXT_FILE_INVALID; + + if (r->path->start[r->path->length - 1] == '/') { + /* TODO: dynamic index setting. */ + nxt_str_set(&index, "index.html"); + nxt_str_set(&extension, ".html"); + + } else { + nxt_str_null(&index); + nxt_str_null(&extension); + } + + alloc = pass->name.length + r->path->length + index.length + 1; + + f->name = nxt_mp_nget(r->mem_pool, alloc); + if (nxt_slow_path(f->name == NULL)) { + goto fail; + } + + p = f->name; + p = nxt_cpymem(p, pass->name.start, pass->name.length); + p = nxt_cpymem(p, r->path->start, r->path->length); + p = nxt_cpymem(p, index.start, index.length); + *p = '\0'; + + ret = nxt_file_open(task, f, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0); + + if (nxt_slow_path(ret != NXT_OK)) { + switch (f->error) { + + case NXT_ENOENT: + case NXT_ENOTDIR: + case NXT_ENAMETOOLONG: + level = NXT_LOG_ERR; + status = NXT_HTTP_NOT_FOUND; + break; + + case NXT_EACCES: + level = NXT_LOG_ERR; + status = NXT_HTTP_FORBIDDEN; + break; + + default: + level = NXT_LOG_ALERT; + status = NXT_HTTP_INTERNAL_SERVER_ERROR; + break; + } + + if (status != NXT_HTTP_NOT_FOUND) { + nxt_log(task, level, "open(\"%FN\") failed %E", f->name, f->error); + } + + nxt_http_request_error(task, r, status); + return NULL; + } + + ret = nxt_file_info(f, &fi); + if (nxt_slow_path(ret != NXT_OK)) { + goto fail; + } + + if (nxt_fast_path(nxt_is_file(&fi))) { + r->status = NXT_HTTP_OK; + r->resp.content_length_n = nxt_file_size(&fi); + + field = nxt_list_zero_add(r->resp.fields); + if (nxt_slow_path(field == NULL)) { + goto fail; + } + + nxt_http_field_name_set(field, "Last-Modified"); + + p = nxt_mp_nget(r->mem_pool, NXT_HTTP_DATE_LEN); + if (nxt_slow_path(p == NULL)) { + goto fail; + } + + nxt_localtime(nxt_file_mtime(&fi), &tm); + + field->value = p; + field->value_length = nxt_http_date(p, &tm) - p; + + field = nxt_list_zero_add(r->resp.fields); + if (nxt_slow_path(field == NULL)) { + goto fail; + } + + nxt_http_field_name_set(field, "ETag"); + + alloc = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3; + + p = nxt_mp_nget(r->mem_pool, alloc); + if (nxt_slow_path(p == NULL)) { + goto fail; + } + + field->value = p; + field->value_length = nxt_sprintf(p, p + alloc, "\"%xT-%xO\"", + nxt_file_mtime(&fi), + nxt_file_size(&fi)) + - p; + + if (extension.start == NULL) { + nxt_http_static_extract_extension(r->path, &extension); + } + + rtcf = r->conf->socket_conf->router_conf; + + mtype = nxt_http_static_mtypes_hash_find(&rtcf->mtypes_hash, + &extension); + + if (mtype != NULL) { + field = nxt_list_zero_add(r->resp.fields); + if (nxt_slow_path(field == NULL)) { + goto fail; + } + + nxt_http_field_name_set(field, "Content-Type"); + + field->value = mtype->start; + field->value_length = mtype->length; + } + + if (need_body && nxt_file_size(&fi) > 0) { + fb = nxt_mp_zget(r->mem_pool, NXT_BUF_FILE_SIZE); + if (nxt_slow_path(fb == NULL)) { + goto fail; + } + + fb->file = f; + fb->file_end = nxt_file_size(&fi); + + r->out = fb; + + body_handler = &nxt_http_static_body_handler; + + } else { + nxt_file_close(task, f); + body_handler = NULL; + } + + } else { + /* Not a file. */ + + nxt_file_close(task, f); + f = NULL; + + if (nxt_slow_path(!nxt_is_dir(&fi))) { + nxt_log(task, NXT_LOG_ERR, "\"%FN\" is not a regular file", + f->name); + nxt_http_request_error(task, r, NXT_HTTP_NOT_FOUND); + return NULL; + } + + r->status = NXT_HTTP_MOVED_PERMANENTLY; + r->resp.content_length_n = 0; + + field = nxt_list_zero_add(r->resp.fields); + if (nxt_slow_path(field == NULL)) { + goto fail; + } + + nxt_http_field_name_set(field, "Location"); + + encode = nxt_encode_uri(NULL, r->path->start, r->path->length); + alloc = r->path->length + encode * 2 + 1; + + if (r->args->length > 0) { + alloc += 1 + r->args->length; + } + + p = nxt_mp_nget(r->mem_pool, alloc); + if (nxt_slow_path(p == NULL)) { + goto fail; + } + + field->value = p; + field->value_length = alloc; + + if (encode > 0) { + p = (u_char *) nxt_encode_uri(p, r->path->start, r->path->length); + + } else { + p = nxt_cpymem(p, r->path->start, r->path->length); + } + + *p++ = '/'; + + if (r->args->length > 0) { + *p++ = '?'; + nxt_memcpy(p, r->args->start, r->args->length); + } + + body_handler = NULL; + } + + nxt_http_request_header_send(task, r, body_handler); + + r->state = &nxt_http_static_send_state; + return NULL; + +fail: + + nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); + + if (f != NULL && f->fd != NXT_FILE_INVALID) { + nxt_file_close(task, f); + } + + return NULL; +} + + +static void +nxt_http_static_extract_extension(nxt_str_t *path, nxt_str_t *extension) +{ + u_char ch, *p, *end; + + end = path->start + path->length; + p = end; + + for ( ;; ) { + /* There's always '/' in the beginning of the request path. */ + + p--; + ch = *p; + + switch (ch) { + case '/': + p++; + /* Fall through. */ + case '.': + extension->length = end - p; + extension->start = p; + return; + } + } +} + + +static void +nxt_http_static_body_handler(nxt_task_t *task, void *obj, void *data) +{ + size_t alloc; + nxt_buf_t *fb, *b, **next, *out; + nxt_off_t rest; + nxt_int_t n; + nxt_work_queue_t *wq; + nxt_http_request_t *r; + + r = obj; + fb = r->out; + + rest = fb->file_end - fb->file_pos; + out = NULL; + next = &out; + n = 0; + + do { + alloc = nxt_min(rest, NXT_HTTP_STATIC_BUF_SIZE); + + b = nxt_buf_mem_alloc(r->mem_pool, alloc, 0); + if (nxt_slow_path(b == NULL)) { + goto fail; + } + + b->completion_handler = nxt_http_static_buf_completion; + b->parent = r; + + nxt_mp_retain(r->mem_pool); + + *next = b; + next = &b->next; + + rest -= alloc; + + } while (rest > 0 && ++n < NXT_HTTP_STATIC_BUF_COUNT); + + wq = &task->thread->engine->fast_work_queue; + + nxt_sendbuf_drain(task, wq, out); + return; + +fail: + + while (out != NULL) { + b = out; + out = b->next; + + nxt_mp_free(r->mem_pool, b); + nxt_mp_release(r->mem_pool); + } +} + + +static const nxt_http_request_state_t nxt_http_static_send_state + nxt_aligned(64) = +{ + .error_handler = nxt_http_request_error_handler, +}; + + +static void +nxt_http_static_buf_completion(nxt_task_t *task, void *obj, void *data) +{ + ssize_t n, size; + nxt_buf_t *b, *fb; + nxt_off_t rest; + nxt_http_request_t *r; + + b = obj; + r = data; + fb = r->out; + + if (nxt_slow_path(fb == NULL || r->error)) { + goto clean; + } + + rest = fb->file_end - fb->file_pos; + size = nxt_buf_mem_size(&b->mem); + + size = nxt_min(rest, (nxt_off_t) size); + + n = nxt_file_read(fb->file, b->mem.start, size, fb->file_pos); + + if (n != size) { + if (n >= 0) { + nxt_log(task, NXT_LOG_ERR, "file \"%FN\" has changed " + "while sending response to a client", fb->file->name); + } + + nxt_http_request_error_handler(task, r, r->proto.any); + goto clean; + } + + if (n == rest) { + nxt_file_close(task, fb->file); + r->out = NULL; + + b->next = nxt_http_buf_last(r); + + } else { + fb->file_pos += n; + b->next = NULL; + } + + b->mem.pos = b->mem.start; + b->mem.free = b->mem.pos + n; + + nxt_http_request_send(task, r, b); + return; + +clean: + + nxt_mp_free(r->mem_pool, b); + nxt_mp_release(r->mem_pool); + + if (fb != NULL) { + nxt_file_close(task, fb->file); + r->out = NULL; + } +} + + +nxt_int_t +nxt_http_static_mtypes_init(nxt_mp_t *mp, nxt_lvlhsh_t *hash) +{ + nxt_str_t *type, extension; + nxt_int_t ret; + nxt_uint_t i; + + static const struct { + nxt_str_t type; + const char *extension; + } default_types[] = { + + { nxt_string("text/html"), ".html" }, + { nxt_string("text/html"), ".htm" }, + { nxt_string("text/css"), ".css" }, + + { nxt_string("image/svg+xml"), ".svg" }, + { nxt_string("image/svg+xml"), ".svg" }, + { nxt_string("image/webp"), ".webp" }, + { nxt_string("image/png"), ".png" }, + { nxt_string("image/jpeg"), ".jpeg" }, + { nxt_string("image/jpeg"), ".jpg" }, + { nxt_string("image/gif"), ".gif" }, + { nxt_string("image/x-icon"), ".ico" }, + + { nxt_string("font/woff"), ".woff" }, + { nxt_string("font/woff2"), ".woff2" }, + { nxt_string("font/otf"), ".otf" }, + { nxt_string("font/ttf"), ".ttf" }, + + { nxt_string("text/plain"), ".txt" }, + { nxt_string("text/markdown"), ".md" }, + { nxt_string("text/x-rst"), ".rst" }, + + { nxt_string("application/javascript"), ".js" }, + { nxt_string("application/json"), ".json" }, + { nxt_string("application/xml"), ".xml" }, + { nxt_string("application/rss+xml"), ".rss" }, + { nxt_string("application/atom+xml"), ".atom" }, + { nxt_string("application/pdf"), ".pdf" }, + + { nxt_string("application/zip"), ".zip" }, + + { nxt_string("audio/mpeg"), ".mp3" }, + { nxt_string("audio/ogg"), ".ogg" }, + { nxt_string("audio/midi"), ".midi" }, + { nxt_string("audio/midi"), ".mid" }, + { nxt_string("audio/flac"), ".flac" }, + { nxt_string("audio/aac"), ".aac" }, + { nxt_string("audio/wav"), ".wav" }, + + { nxt_string("video/mpeg"), ".mpeg" }, + { nxt_string("video/mpeg"), ".mpg" }, + { nxt_string("video/mp4"), ".mp4" }, + { nxt_string("video/webm"), ".webm" }, + { nxt_string("video/x-msvideo"), ".avi" }, + + { nxt_string("application/octet-stream"), ".exe" }, + { nxt_string("application/octet-stream"), ".bin" }, + { nxt_string("application/octet-stream"), ".dll" }, + { nxt_string("application/octet-stream"), ".iso" }, + { nxt_string("application/octet-stream"), ".img" }, + { nxt_string("application/octet-stream"), ".msi" }, + + { nxt_string("application/octet-stream"), ".deb" }, + { nxt_string("application/octet-stream"), ".rpm" }, + }; + + for (i = 0; i < nxt_nitems(default_types); i++) { + type = (nxt_str_t *) &default_types[i].type; + + extension.start = (u_char *) default_types[i].extension; + extension.length = nxt_strlen(extension.start); + + ret = nxt_http_static_mtypes_hash_add(mp, hash, &extension, type); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + } + + return NXT_OK; +} + + +static const nxt_lvlhsh_proto_t nxt_http_static_mtypes_hash_proto + nxt_aligned(64) = +{ + NXT_LVLHSH_DEFAULT, + nxt_http_static_mtypes_hash_test, + nxt_http_static_mtypes_hash_alloc, + nxt_http_static_mtypes_hash_free, +}; + + +typedef struct { + nxt_str_t extension; + nxt_str_t *type; +} nxt_http_static_mtype_t; + + +nxt_int_t +nxt_http_static_mtypes_hash_add(nxt_mp_t *mp, nxt_lvlhsh_t *hash, + nxt_str_t *extension, nxt_str_t *type) +{ + nxt_lvlhsh_query_t lhq; + nxt_http_static_mtype_t *mtype; + + mtype = nxt_mp_get(mp, sizeof(nxt_http_static_mtype_t)); + if (nxt_slow_path(mtype == NULL)) { + return NXT_ERROR; + } + + mtype->extension = *extension; + mtype->type = type; + + lhq.key = *extension; + lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length); + lhq.replace = 1; + lhq.value = mtype; + lhq.proto = &nxt_http_static_mtypes_hash_proto; + lhq.pool = mp; + + return nxt_lvlhsh_insert(hash, &lhq); +} + + +nxt_str_t * +nxt_http_static_mtypes_hash_find(nxt_lvlhsh_t *hash, nxt_str_t *extension) +{ + nxt_lvlhsh_query_t lhq; + nxt_http_static_mtype_t *mtype; + + lhq.key = *extension; + lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length); + lhq.proto = &nxt_http_static_mtypes_hash_proto; + + if (nxt_lvlhsh_find(hash, &lhq) == NXT_OK) { + mtype = lhq.value; + return mtype->type; + } + + return NULL; +} + + +static nxt_int_t +nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq, void *data) +{ + nxt_http_static_mtype_t *mtype; + + mtype = data; + + return nxt_strcasestr_eq(&lhq->key, &mtype->extension) ? NXT_OK + : NXT_DECLINED; +} + + +static void * +nxt_http_static_mtypes_hash_alloc(void *data, size_t size) +{ + return nxt_mp_align(data, size, size); +} + + +static void +nxt_http_static_mtypes_hash_free(void *data, void *p) +{ + nxt_mp_free(data, p); +} diff --git a/src/nxt_router.c b/src/nxt_router.c index 89ea5fe6..28781600 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -122,6 +122,8 @@ static void nxt_router_conf_send(nxt_task_t *task, static nxt_int_t nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, u_char *start, u_char *end); +static nxt_int_t nxt_router_conf_process_static(nxt_task_t *task, + nxt_router_conf_t *rtcf, nxt_conf_value_t *conf); static nxt_app_t *nxt_router_app_find(nxt_queue_t *queue, nxt_str_t *name); static void nxt_router_listen_socket_rpc_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_socket_conf_t *skcf); @@ -1399,7 +1401,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *conf, *http, *value, *websocket; nxt_conf_value_t *applications, *application; nxt_conf_value_t *listeners, *listener; - nxt_conf_value_t *routes_conf; + nxt_conf_value_t *routes_conf, *static_conf; nxt_socket_conf_t *skcf; nxt_http_routes_t *routes; nxt_event_engine_t *engine; @@ -1419,6 +1421,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, #if (NXT_TLS) static nxt_str_t certificate_path = nxt_string("/tls/certificate"); #endif + static nxt_str_t static_path = nxt_string("/settings/http/static"); static nxt_str_t websocket_path = nxt_string("/settings/http/websocket"); conf = nxt_conf_json_parse(tmcf->mem_pool, start, end, NULL); @@ -1440,6 +1443,13 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, tmcf->router_conf->threads = nxt_ncpu; } + static_conf = nxt_conf_get_path(conf, &static_path); + + ret = nxt_router_conf_process_static(task, tmcf->router_conf, static_conf); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + router = tmcf->router_conf->router; applications = nxt_conf_get_path(conf, &applications_path); @@ -1788,6 +1798,87 @@ fail: } +static nxt_int_t +nxt_router_conf_process_static(nxt_task_t *task, nxt_router_conf_t *rtcf, + nxt_conf_value_t *conf) +{ + uint32_t next, i; + nxt_mp_t *mp; + nxt_str_t *type, extension, str; + nxt_int_t ret; + nxt_uint_t exts; + nxt_conf_value_t *mtypes_conf, *ext_conf, *value; + + static nxt_str_t mtypes_path = nxt_string("/mime_types"); + + mp = rtcf->mem_pool; + + ret = nxt_http_static_mtypes_init(mp, &rtcf->mtypes_hash); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + + if (conf == NULL) { + return NXT_OK; + } + + mtypes_conf = nxt_conf_get_path(conf, &mtypes_path); + + if (mtypes_conf != NULL) { + next = 0; + + for ( ;; ) { + ext_conf = nxt_conf_next_object_member(mtypes_conf, &str, &next); + + if (ext_conf == NULL) { + break; + } + + type = nxt_str_dup(mp, NULL, &str); + if (nxt_slow_path(type == NULL)) { + return NXT_ERROR; + } + + if (nxt_conf_type(ext_conf) == NXT_CONF_STRING) { + nxt_conf_get_string(ext_conf, &str); + + if (nxt_slow_path(nxt_str_dup(mp, &extension, &str) == NULL)) { + return NXT_ERROR; + } + + ret = nxt_http_static_mtypes_hash_add(mp, &rtcf->mtypes_hash, + &extension, type); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + + continue; + } + + exts = nxt_conf_array_elements_count(ext_conf); + + for (i = 0; i < exts; i++) { + value = nxt_conf_get_array_element(ext_conf, i); + + nxt_conf_get_string(value, &str); + + if (nxt_slow_path(nxt_str_dup(mp, &extension, &str) == NULL)) { + return NXT_ERROR; + } + + ret = nxt_http_static_mtypes_hash_add(mp, &rtcf->mtypes_hash, + &extension, type); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + } + } + } + + return NXT_OK; +} + + static nxt_app_t * nxt_router_app_find(nxt_queue_t *queue, nxt_str_t *name) { diff --git a/src/nxt_router.h b/src/nxt_router.h index b55a4de3..ec18ff48 100644 --- a/src/nxt_router.h +++ b/src/nxt_router.h @@ -38,9 +38,13 @@ typedef struct { typedef struct { uint32_t count; uint32_t threads; + + nxt_mp_t *mem_pool; + nxt_router_t *router; nxt_http_routes_t *routes; - nxt_mp_t *mem_pool; + + nxt_lvlhsh_t mtypes_hash; nxt_router_access_log_t *access_log; } nxt_router_conf_t; diff --git a/src/nxt_sprintf.c b/src/nxt_sprintf.c index 240f47ef..9557b327 100644 --- a/src/nxt_sprintf.c +++ b/src/nxt_sprintf.c @@ -12,8 +12,8 @@ /* * Supported formats: * - * %[0][width][x][X]O nxt_off_t - * %[0][width]T nxt_time_t + * %[0][width][x|X]O nxt_off_t + * %[0][width][x|X]T nxt_time_t * %[0][width][u][x|X]z ssize_t/size_t * %[0][width][u][x|X]d int/u_int * %[0][width][u][x|X]l long diff --git a/src/nxt_string.c b/src/nxt_string.c index 4d3b3954..b89e9555 100644 --- a/src/nxt_string.c +++ b/src/nxt_string.c @@ -507,3 +507,69 @@ nxt_decode_uri(u_char *dst, u_char *src, size_t length) return dst; } + + +uintptr_t +nxt_encode_uri(u_char *dst, u_char *src, size_t length) +{ + u_char *end; + nxt_uint_t n; + + static const u_char hex[16] = "0123456789ABCDEF"; + + /* " ", "#", "%", "?", %00-%1F, %7F-%FF */ + + static const uint32_t escape[] = { + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + + /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ + 0x80000029, /* 1000 0000 0000 0000 0000 0000 0010 1001 */ + + /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + + /* ~}| {zyx wvut srqp onml kjih gfed cba` */ + 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ + + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + }; + + end = src + length; + + if (dst == NULL) { + + /* Find the number of the characters to be escaped. */ + + n = 0; + + while (src < end) { + + if (escape[*src >> 5] & (1U << (*src & 0x1f))) { + n++; + } + + src++; + } + + return (uintptr_t) n; + } + + while (src < end) { + + if (escape[*src >> 5] & (1U << (*src & 0x1f))) { + *dst++ = '%'; + *dst++ = hex[*src >> 4]; + *dst++ = hex[*src & 0xf]; + + } else { + *dst++ = *src; + } + + src++; + } + + return (uintptr_t) dst; +} diff --git a/src/nxt_string.h b/src/nxt_string.h index 56d7316b..8d7b3b73 100644 --- a/src/nxt_string.h +++ b/src/nxt_string.h @@ -169,6 +169,7 @@ NXT_EXPORT nxt_bool_t nxt_strvers_match(u_char *version, u_char *prefix, size_t length); NXT_EXPORT u_char *nxt_decode_uri(u_char *dst, u_char *src, size_t length); +NXT_EXPORT uintptr_t nxt_encode_uri(u_char *dst, u_char *src, size_t length); #endif /* _NXT_STRING_H_INCLUDED_ */ |