From 08a8d1510d5f73d91112ead9e6ac075fb7d2bac0 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 19 Sep 2019 02:47:09 +0300 Subject: Basic support for serving static files. --- src/nxt_http_static.c | 599 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 599 insertions(+) create mode 100644 src/nxt_http_static.c (limited to 'src/nxt_http_static.c') 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 +#include + + +#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); +} -- cgit