xref: /unit/src/nxt_http_static.c (revision 1854:aebe76640568)
1 
2 /*
3  * Copyright (C) NGINX, Inc.
4  */
5 
6 #include <nxt_router.h>
7 #include <nxt_http.h>
8 
9 
10 #define NXT_HTTP_STATIC_BUF_COUNT  2
11 #define NXT_HTTP_STATIC_BUF_SIZE   (128 * 1024)
12 
13 
14 static void nxt_http_static_extract_extension(nxt_str_t *path,
15     nxt_str_t *extension);
16 static void nxt_http_static_body_handler(nxt_task_t *task, void *obj,
17     void *data);
18 static void nxt_http_static_buf_completion(nxt_task_t *task, void *obj,
19     void *data);
20 
21 static nxt_int_t nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq,
22     void *data);
23 static void *nxt_http_static_mtypes_hash_alloc(void *data, size_t size);
24 static void nxt_http_static_mtypes_hash_free(void *data, void *p);
25 
26 
27 static const nxt_http_request_state_t  nxt_http_static_send_state;
28 
29 
30 nxt_http_action_t *
31 nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
32     nxt_http_action_t *action)
33 {
34     size_t              alloc, encode;
35     u_char              *p;
36     struct tm           tm;
37     nxt_buf_t           *fb;
38     nxt_int_t           ret;
39     nxt_str_t           index, extension, *mtype;
40     nxt_uint_t          level;
41     nxt_bool_t          need_body;
42     nxt_file_t          *f;
43     nxt_file_info_t     fi;
44     nxt_http_field_t    *field;
45     nxt_http_status_t   status;
46     nxt_router_conf_t   *rtcf;
47     nxt_work_handler_t  body_handler;
48 
49     if (nxt_slow_path(!nxt_str_eq(r->method, "GET", 3))) {
50 
51         if (!nxt_str_eq(r->method, "HEAD", 4)) {
52             if (action->u.share.fallback != NULL) {
53                 return action->u.share.fallback;
54             }
55 
56             nxt_http_request_error(task, r, NXT_HTTP_METHOD_NOT_ALLOWED);
57             return NULL;
58         }
59 
60         need_body = 0;
61 
62     } else {
63         need_body = 1;
64     }
65 
66     f = nxt_mp_zget(r->mem_pool, sizeof(nxt_file_t));
67     if (nxt_slow_path(f == NULL)) {
68         goto fail;
69     }
70 
71     f->fd = NXT_FILE_INVALID;
72 
73     if (r->path->start[r->path->length - 1] == '/') {
74         /* TODO: dynamic index setting. */
75         nxt_str_set(&index, "index.html");
76         nxt_str_set(&extension, ".html");
77 
78     } else {
79         nxt_str_set(&index, "");
80         nxt_str_null(&extension);
81     }
82 
83     alloc = action->name.length + r->path->length + index.length + 1;
84 
85     f->name = nxt_mp_nget(r->mem_pool, alloc);
86     if (nxt_slow_path(f->name == NULL)) {
87         goto fail;
88     }
89 
90     p = f->name;
91     p = nxt_cpymem(p, action->name.start, action->name.length);
92     p = nxt_cpymem(p, r->path->start, r->path->length);
93     p = nxt_cpymem(p, index.start, index.length);
94     *p = '\0';
95 
96     ret = nxt_file_open(task, f, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
97 
98     if (nxt_slow_path(ret != NXT_OK)) {
99         switch (f->error) {
100 
101         /*
102          * For Unix domain sockets "errno" is set to:
103          *  - ENXIO on Linux;
104          *  - EOPNOTSUPP on *BSD, MacOSX, and Solaris.
105          */
106 
107         case NXT_ENOENT:
108         case NXT_ENOTDIR:
109         case NXT_ENAMETOOLONG:
110 #if (NXT_LINUX)
111         case NXT_ENXIO:
112 #else
113         case NXT_EOPNOTSUPP:
114 #endif
115             level = NXT_LOG_ERR;
116             status = NXT_HTTP_NOT_FOUND;
117             break;
118 
119         case NXT_EACCES:
120             level = NXT_LOG_ERR;
121             status = NXT_HTTP_FORBIDDEN;
122             break;
123 
124         default:
125             level = NXT_LOG_ALERT;
126             status = NXT_HTTP_INTERNAL_SERVER_ERROR;
127             break;
128         }
129 
130         if (level == NXT_LOG_ERR && action->u.share.fallback != NULL) {
131             return action->u.share.fallback;
132         }
133 
134         if (status != NXT_HTTP_NOT_FOUND) {
135             nxt_log(task, level, "open(\"%FN\") failed %E", f->name, f->error);
136         }
137 
138         nxt_http_request_error(task, r, status);
139         return NULL;
140     }
141 
142     ret = nxt_file_info(f, &fi);
143     if (nxt_slow_path(ret != NXT_OK)) {
144         goto fail;
145     }
146 
147     if (nxt_fast_path(nxt_is_file(&fi))) {
148         r->status = NXT_HTTP_OK;
149         r->resp.content_length_n = nxt_file_size(&fi);
150 
151         field = nxt_list_zero_add(r->resp.fields);
152         if (nxt_slow_path(field == NULL)) {
153             goto fail;
154         }
155 
156         nxt_http_field_name_set(field, "Last-Modified");
157 
158         p = nxt_mp_nget(r->mem_pool, NXT_HTTP_DATE_LEN);
159         if (nxt_slow_path(p == NULL)) {
160             goto fail;
161         }
162 
163         nxt_localtime(nxt_file_mtime(&fi), &tm);
164 
165         field->value = p;
166         field->value_length = nxt_http_date(p, &tm) - p;
167 
168         field = nxt_list_zero_add(r->resp.fields);
169         if (nxt_slow_path(field == NULL)) {
170             goto fail;
171         }
172 
173         nxt_http_field_name_set(field, "ETag");
174 
175         alloc = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3;
176 
177         p = nxt_mp_nget(r->mem_pool, alloc);
178         if (nxt_slow_path(p == NULL)) {
179             goto fail;
180         }
181 
182         field->value = p;
183         field->value_length = nxt_sprintf(p, p + alloc, "\"%xT-%xO\"",
184                                           nxt_file_mtime(&fi),
185                                           nxt_file_size(&fi))
186                               - p;
187 
188         if (extension.start == NULL) {
189             nxt_http_static_extract_extension(r->path, &extension);
190         }
191 
192         rtcf = r->conf->socket_conf->router_conf;
193 
194         mtype = nxt_http_static_mtypes_hash_find(&rtcf->mtypes_hash,
195                                                  &extension);
196 
197         if (mtype != NULL) {
198             field = nxt_list_zero_add(r->resp.fields);
199             if (nxt_slow_path(field == NULL)) {
200                 goto fail;
201             }
202 
203             nxt_http_field_name_set(field, "Content-Type");
204 
205             field->value = mtype->start;
206             field->value_length = mtype->length;
207         }
208 
209         if (need_body && nxt_file_size(&fi) > 0) {
210             fb = nxt_mp_zget(r->mem_pool, NXT_BUF_FILE_SIZE);
211             if (nxt_slow_path(fb == NULL)) {
212                 goto fail;
213             }
214 
215             fb->file = f;
216             fb->file_end = nxt_file_size(&fi);
217 
218             r->out = fb;
219 
220             body_handler = &nxt_http_static_body_handler;
221 
222         } else {
223             nxt_file_close(task, f);
224             body_handler = NULL;
225         }
226 
227     } else {
228         /* Not a file. */
229 
230         nxt_file_close(task, f);
231 
232         if (nxt_slow_path(!nxt_is_dir(&fi))) {
233             if (action->u.share.fallback != NULL) {
234                 return action->u.share.fallback;
235             }
236 
237             nxt_log(task, NXT_LOG_ERR, "\"%FN\" is not a regular file",
238                     f->name);
239 
240             nxt_http_request_error(task, r, NXT_HTTP_NOT_FOUND);
241             return NULL;
242         }
243 
244         f = NULL;
245 
246         r->status = NXT_HTTP_MOVED_PERMANENTLY;
247         r->resp.content_length_n = 0;
248 
249         field = nxt_list_zero_add(r->resp.fields);
250         if (nxt_slow_path(field == NULL)) {
251             goto fail;
252         }
253 
254         nxt_http_field_name_set(field, "Location");
255 
256         encode = nxt_encode_uri(NULL, r->path->start, r->path->length);
257         alloc = r->path->length + encode * 2 + 1;
258 
259         if (r->args->length > 0) {
260             alloc += 1 + r->args->length;
261         }
262 
263         p = nxt_mp_nget(r->mem_pool, alloc);
264         if (nxt_slow_path(p == NULL)) {
265             goto fail;
266         }
267 
268         field->value = p;
269         field->value_length = alloc;
270 
271         if (encode > 0) {
272             p = (u_char *) nxt_encode_uri(p, r->path->start, r->path->length);
273 
274         } else {
275             p = nxt_cpymem(p, r->path->start, r->path->length);
276         }
277 
278         *p++ = '/';
279 
280         if (r->args->length > 0) {
281             *p++ = '?';
282             nxt_memcpy(p, r->args->start, r->args->length);
283         }
284 
285         body_handler = NULL;
286     }
287 
288     nxt_http_request_header_send(task, r, body_handler, NULL);
289 
290     r->state = &nxt_http_static_send_state;
291     return NULL;
292 
293 fail:
294 
295     nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
296 
297     if (f != NULL && f->fd != NXT_FILE_INVALID) {
298         nxt_file_close(task, f);
299     }
300 
301     return NULL;
302 }
303 
304 
305 static void
306 nxt_http_static_extract_extension(nxt_str_t *path, nxt_str_t *extension)
307 {
308     u_char  ch, *p, *end;
309 
310     end = path->start + path->length;
311     p = end;
312 
313     for ( ;; ) {
314         /* There's always '/' in the beginning of the request path. */
315 
316         p--;
317         ch = *p;
318 
319         switch (ch) {
320         case '/':
321             p++;
322             /* Fall through. */
323         case '.':
324             extension->length = end - p;
325             extension->start = p;
326             return;
327         }
328     }
329 }
330 
331 
332 static void
333 nxt_http_static_body_handler(nxt_task_t *task, void *obj, void *data)
334 {
335     size_t              alloc;
336     nxt_buf_t           *fb, *b, **next, *out;
337     nxt_off_t           rest;
338     nxt_int_t           n;
339     nxt_work_queue_t    *wq;
340     nxt_http_request_t  *r;
341 
342     r = obj;
343     fb = r->out;
344 
345     rest = fb->file_end - fb->file_pos;
346     out = NULL;
347     next = &out;
348     n = 0;
349 
350     do {
351         alloc = nxt_min(rest, NXT_HTTP_STATIC_BUF_SIZE);
352 
353         b = nxt_buf_mem_alloc(r->mem_pool, alloc, 0);
354         if (nxt_slow_path(b == NULL)) {
355             goto fail;
356         }
357 
358         b->completion_handler = nxt_http_static_buf_completion;
359         b->parent = r;
360 
361         nxt_mp_retain(r->mem_pool);
362 
363         *next = b;
364         next = &b->next;
365 
366         rest -= alloc;
367 
368     } while (rest > 0 && ++n < NXT_HTTP_STATIC_BUF_COUNT);
369 
370     wq = &task->thread->engine->fast_work_queue;
371 
372     nxt_sendbuf_drain(task, wq, out);
373     return;
374 
375 fail:
376 
377     while (out != NULL) {
378         b = out;
379         out = b->next;
380 
381         nxt_mp_free(r->mem_pool, b);
382         nxt_mp_release(r->mem_pool);
383     }
384 }
385 
386 
387 static const nxt_http_request_state_t  nxt_http_static_send_state
388     nxt_aligned(64) =
389 {
390     .error_handler = nxt_http_request_error_handler,
391 };
392 
393 
394 static void
395 nxt_http_static_buf_completion(nxt_task_t *task, void *obj, void *data)
396 {
397     ssize_t             n, size;
398     nxt_buf_t           *b, *fb, *next;
399     nxt_off_t           rest;
400     nxt_http_request_t  *r;
401 
402     b = obj;
403     r = data;
404 
405 complete_buf:
406 
407     fb = r->out;
408 
409     if (nxt_slow_path(fb == NULL || r->error)) {
410         goto clean;
411     }
412 
413     rest = fb->file_end - fb->file_pos;
414     size = nxt_buf_mem_size(&b->mem);
415 
416     size = nxt_min(rest, (nxt_off_t) size);
417 
418     n = nxt_file_read(fb->file, b->mem.start, size, fb->file_pos);
419 
420     if (n != size) {
421         if (n >= 0) {
422             nxt_log(task, NXT_LOG_ERR, "file \"%FN\" has changed "
423                     "while sending response to a client", fb->file->name);
424         }
425 
426         nxt_http_request_error_handler(task, r, r->proto.any);
427         goto clean;
428     }
429 
430     next = b->next;
431 
432     if (n == rest) {
433         nxt_file_close(task, fb->file);
434         r->out = NULL;
435 
436         b->next = nxt_http_buf_last(r);
437 
438     } else {
439         fb->file_pos += n;
440         b->next = NULL;
441     }
442 
443     b->mem.pos = b->mem.start;
444     b->mem.free = b->mem.pos + n;
445 
446     nxt_http_request_send(task, r, b);
447 
448     if (next != NULL) {
449         b = next;
450         goto complete_buf;
451     }
452 
453     return;
454 
455 clean:
456 
457     do {
458         next = b->next;
459 
460         nxt_mp_free(r->mem_pool, b);
461         nxt_mp_release(r->mem_pool);
462 
463         b = next;
464     } while (b != NULL);
465 
466     if (fb != NULL) {
467         nxt_file_close(task, fb->file);
468         r->out = NULL;
469     }
470 }
471 
472 
473 nxt_int_t
474 nxt_http_static_mtypes_init(nxt_mp_t *mp, nxt_lvlhsh_t *hash)
475 {
476     nxt_str_t   *type, extension;
477     nxt_int_t   ret;
478     nxt_uint_t  i;
479 
480     static const struct {
481         nxt_str_t   type;
482         const char  *extension;
483     } default_types[] = {
484 
485         { nxt_string("text/html"),      ".html"  },
486         { nxt_string("text/html"),      ".htm"   },
487         { nxt_string("text/css"),       ".css"   },
488 
489         { nxt_string("image/svg+xml"),  ".svg"   },
490         { nxt_string("image/webp"),     ".webp"  },
491         { nxt_string("image/png"),      ".png"   },
492         { nxt_string("image/apng"),     ".apng"  },
493         { nxt_string("image/jpeg"),     ".jpeg"  },
494         { nxt_string("image/jpeg"),     ".jpg"   },
495         { nxt_string("image/gif"),      ".gif"   },
496         { nxt_string("image/x-icon"),   ".ico"   },
497 
498         { nxt_string("image/avif"),           ".avif"  },
499         { nxt_string("image/avif-sequence"),  ".avifs" },
500 
501         { nxt_string("font/woff"),      ".woff"  },
502         { nxt_string("font/woff2"),     ".woff2" },
503         { nxt_string("font/otf"),       ".otf"   },
504         { nxt_string("font/ttf"),       ".ttf"   },
505 
506         { nxt_string("text/plain"),     ".txt"   },
507         { nxt_string("text/markdown"),  ".md"    },
508         { nxt_string("text/x-rst"),     ".rst"   },
509 
510         { nxt_string("application/javascript"),  ".js"   },
511         { nxt_string("application/json"),        ".json" },
512         { nxt_string("application/xml"),         ".xml"  },
513         { nxt_string("application/rss+xml"),     ".rss"  },
514         { nxt_string("application/atom+xml"),    ".atom" },
515         { nxt_string("application/pdf"),         ".pdf"  },
516 
517         { nxt_string("application/zip"),         ".zip"  },
518 
519         { nxt_string("audio/mpeg"),       ".mp3"  },
520         { nxt_string("audio/ogg"),        ".ogg"  },
521         { nxt_string("audio/midi"),       ".midi" },
522         { nxt_string("audio/midi"),       ".mid"  },
523         { nxt_string("audio/flac"),       ".flac" },
524         { nxt_string("audio/aac"),        ".aac"  },
525         { nxt_string("audio/wav"),        ".wav"  },
526 
527         { nxt_string("video/mpeg"),       ".mpeg" },
528         { nxt_string("video/mpeg"),       ".mpg"  },
529         { nxt_string("video/mp4"),        ".mp4"  },
530         { nxt_string("video/webm"),       ".webm" },
531         { nxt_string("video/x-msvideo"),  ".avi"  },
532 
533         { nxt_string("application/octet-stream"),  ".exe" },
534         { nxt_string("application/octet-stream"),  ".bin" },
535         { nxt_string("application/octet-stream"),  ".dll" },
536         { nxt_string("application/octet-stream"),  ".iso" },
537         { nxt_string("application/octet-stream"),  ".img" },
538         { nxt_string("application/octet-stream"),  ".msi" },
539 
540         { nxt_string("application/octet-stream"),  ".deb" },
541         { nxt_string("application/octet-stream"),  ".rpm" },
542     };
543 
544     for (i = 0; i < nxt_nitems(default_types); i++) {
545         type = (nxt_str_t *) &default_types[i].type;
546 
547         extension.start = (u_char *) default_types[i].extension;
548         extension.length = nxt_strlen(extension.start);
549 
550         ret = nxt_http_static_mtypes_hash_add(mp, hash, &extension, type);
551         if (nxt_slow_path(ret != NXT_OK)) {
552             return NXT_ERROR;
553         }
554     }
555 
556     return NXT_OK;
557 }
558 
559 
560 static const nxt_lvlhsh_proto_t  nxt_http_static_mtypes_hash_proto
561     nxt_aligned(64) =
562 {
563     NXT_LVLHSH_DEFAULT,
564     nxt_http_static_mtypes_hash_test,
565     nxt_http_static_mtypes_hash_alloc,
566     nxt_http_static_mtypes_hash_free,
567 };
568 
569 
570 typedef struct {
571     nxt_str_t  extension;
572     nxt_str_t  *type;
573 } nxt_http_static_mtype_t;
574 
575 
576 nxt_int_t
577 nxt_http_static_mtypes_hash_add(nxt_mp_t *mp, nxt_lvlhsh_t *hash,
578     nxt_str_t *extension, nxt_str_t *type)
579 {
580     nxt_lvlhsh_query_t       lhq;
581     nxt_http_static_mtype_t  *mtype;
582 
583     mtype = nxt_mp_get(mp, sizeof(nxt_http_static_mtype_t));
584     if (nxt_slow_path(mtype == NULL)) {
585         return NXT_ERROR;
586     }
587 
588     mtype->extension = *extension;
589     mtype->type = type;
590 
591     lhq.key = *extension;
592     lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length);
593     lhq.replace = 1;
594     lhq.value = mtype;
595     lhq.proto = &nxt_http_static_mtypes_hash_proto;
596     lhq.pool = mp;
597 
598     return nxt_lvlhsh_insert(hash, &lhq);
599 }
600 
601 
602 nxt_str_t *
603 nxt_http_static_mtypes_hash_find(nxt_lvlhsh_t *hash, nxt_str_t *extension)
604 {
605     nxt_lvlhsh_query_t       lhq;
606     nxt_http_static_mtype_t  *mtype;
607 
608     lhq.key = *extension;
609     lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length);
610     lhq.proto = &nxt_http_static_mtypes_hash_proto;
611 
612     if (nxt_lvlhsh_find(hash, &lhq) == NXT_OK) {
613         mtype = lhq.value;
614         return mtype->type;
615     }
616 
617     return NULL;
618 }
619 
620 
621 static nxt_int_t
622 nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
623 {
624     nxt_http_static_mtype_t  *mtype;
625 
626     mtype = data;
627 
628     return nxt_strcasestr_eq(&lhq->key, &mtype->extension) ? NXT_OK
629                                                            : NXT_DECLINED;
630 }
631 
632 
633 static void *
634 nxt_http_static_mtypes_hash_alloc(void *data, size_t size)
635 {
636     return nxt_mp_align(data, size, size);
637 }
638 
639 
640 static void
641 nxt_http_static_mtypes_hash_free(void *data, void *p)
642 {
643     nxt_mp_free(data, p);
644 }
645