summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--auto/sources1
-rw-r--r--src/nxt_conf.h1
-rw-r--r--src/nxt_conf_validation.c129
-rw-r--r--src/nxt_http.h29
-rw-r--r--src/nxt_http_request.c25
-rw-r--r--src/nxt_http_route.c28
-rw-r--r--src/nxt_http_static.c599
-rw-r--r--src/nxt_router.c93
-rw-r--r--src/nxt_router.h6
-rw-r--r--src/nxt_sprintf.c4
-rw-r--r--src/nxt_string.c66
-rw-r--r--src/nxt_string.h1
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_ */