1 /*
2 * Copyright (c) 2009-2012, FRiCKLE <info@frickle.com>
3 * Copyright (c) 2009-2012, Piotr Sikora <piotr.sikora@frickle.com>
4 * Copyrithg (c) 2002-2011, Igor Sysoev <igor@sysoev.ru>
5 * All rights reserved.
6 *
7 * This project was fully funded by c2hosting.com.
8 * Included cache_purge functionality was fully funded by yo.se.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include <ngx_config.h>
33 #include <ngx_core.h>
34 #include <ngx_http.h>
35 #include <nginx.h>
36
37 #if (NGX_HTTP_CACHE)
38
39 #define SLOWFS_PROCESS_NAME "slowfs cache process"
40
41 ngx_int_t ngx_http_slowfs_init(ngx_conf_t *);
42 ngx_int_t ngx_http_slowfs_add_variables(ngx_conf_t *);
43 void *ngx_http_slowfs_create_loc_conf(ngx_conf_t *);
44 char *ngx_http_slowfs_merge_loc_conf(ngx_conf_t *, void *, void *);
45
46 char *ngx_http_slowfs_cache_conf(ngx_conf_t *, ngx_command_t *, void *);
47 char *ngx_http_slowfs_cache_key_conf(ngx_conf_t *, ngx_command_t *,
48 void *);
49 char *ngx_http_slowfs_cache_purge_conf(ngx_conf_t *, ngx_command_t *,
50 void *);
51
52 ngx_int_t ngx_http_slowfs_handler(ngx_http_request_t *);
53 ngx_int_t ngx_http_slowfs_cache_purge_handler(ngx_http_request_t *r);
54
55 ngx_int_t ngx_http_slowfs_cache_send(ngx_http_request_t *);
56 ngx_int_t ngx_http_slowfs_static_send(ngx_http_request_t *);
57 void ngx_http_slowfs_cache_update(ngx_http_request_t *,
58 ngx_open_file_info_t *, ngx_str_t *);
59 ngx_int_t ngx_http_slowfs_cache_purge(ngx_http_request_t *,
60 ngx_http_file_cache_t *, ngx_http_complex_value_t *);
61
62 ngx_int_t ngx_http_slowfs_cache_status(ngx_http_request_t *,
63 ngx_http_variable_value_t *, uintptr_t);
64
65 typedef struct {
66 ngx_flag_t enabled;
67 ngx_shm_zone_t *cache;
68 ngx_http_complex_value_t cache_key;
69 ngx_uint_t cache_min_uses;
70 ngx_array_t *cache_valid;
71 ngx_path_t *temp_path;
72 size_t big_file_size;
73 } ngx_http_slowfs_loc_conf_t;
74
75 typedef struct {
76 ngx_uint_t cache_status;
77 } ngx_http_slowfs_ctx_t;
78
79 ngx_module_t ngx_http_slowfs_module;
80
81 static ngx_path_init_t ngx_http_slowfs_temp_path = {
82 ngx_string("/tmp"), { 1, 2, 0 }
83 };
84
85 static ngx_command_t ngx_http_slowfs_module_commands[] = {
86
87 { ngx_string("slowfs_cache"),
88 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
89 ngx_http_slowfs_cache_conf,
90 NGX_HTTP_LOC_CONF_OFFSET,
91 0,
92 NULL },
93
94 { ngx_string("slowfs_cache_key"),
95 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
96 ngx_http_slowfs_cache_key_conf,
97 NGX_HTTP_LOC_CONF_OFFSET,
98 0,
99 NULL },
100
101 { ngx_string("slowfs_cache_purge"),
102 NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
103 ngx_http_slowfs_cache_purge_conf,
104 NGX_HTTP_LOC_CONF_OFFSET,
105 0,
106 NULL },
107
108 { ngx_string("slowfs_cache_path"),
109 NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE,
110 ngx_http_file_cache_set_slot,
111 0,
112 0,
113 &ngx_http_slowfs_module },
114
115 { ngx_string("slowfs_cache_min_uses"),
116 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
117 ngx_conf_set_num_slot,
118 NGX_HTTP_LOC_CONF_OFFSET,
119 offsetof(ngx_http_slowfs_loc_conf_t, cache_min_uses),
120 NULL },
121
122 { ngx_string("slowfs_cache_valid"),
123 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
124 ngx_http_file_cache_valid_set_slot,
125 NGX_HTTP_LOC_CONF_OFFSET,
126 offsetof(ngx_http_slowfs_loc_conf_t, cache_valid),
127 NULL },
128
129 { ngx_string("slowfs_big_file_size"),
130 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
131 ngx_conf_set_size_slot,
132 NGX_HTTP_LOC_CONF_OFFSET,
133 offsetof(ngx_http_slowfs_loc_conf_t, big_file_size),
134 NULL },
135
136 { ngx_string("slowfs_temp_path"),
137 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
138 ngx_conf_set_path_slot,
139 NGX_HTTP_LOC_CONF_OFFSET,
140 offsetof(ngx_http_slowfs_loc_conf_t, temp_path),
141 NULL },
142
143 ngx_null_command
144 };
145
146 static ngx_http_variable_t ngx_http_slowfs_module_variables[] = {
147
148 { ngx_string("slowfs_cache_status"), NULL,
149 ngx_http_slowfs_cache_status, 0,
150 NGX_HTTP_VAR_NOHASH|NGX_HTTP_VAR_NOCACHEABLE, 0 },
151
152 { ngx_null_string, NULL, NULL, 0, 0, 0 }
153 };
154
155 static ngx_http_module_t ngx_http_slowfs_module_ctx = {
156 ngx_http_slowfs_add_variables, /* preconfiguration */
157 ngx_http_slowfs_init, /* postconfiguration */
158
159 NULL, /* create main configuration */
160 NULL, /* init main configuration */
161
162 NULL, /* create server configuration */
163 NULL, /* merge server configuration */
164
165 ngx_http_slowfs_create_loc_conf, /* create location configuration */
166 ngx_http_slowfs_merge_loc_conf /* merge location configuration */
167 };
168
169 ngx_module_t ngx_http_slowfs_module = {
170 NGX_MODULE_V1,
171 &ngx_http_slowfs_module_ctx, /* module context */
172 ngx_http_slowfs_module_commands, /* module directives */
173 NGX_HTTP_MODULE, /* module type */
174 NULL, /* init master */
175 NULL, /* init module */
176 NULL, /* init process */
177 NULL, /* init thread */
178 NULL, /* exit thread */
179 NULL, /* exit process */
180 NULL, /* exit master */
181 NGX_MODULE_V1_PADDING
182 };
183
184 /*
185 * source: ngx_http_static_module.c/ngx_http_static_handler
186 * Copyright (C) Igor Sysoev
187 */
188 ngx_int_t
ngx_http_slowfs_static_send(ngx_http_request_t * r)189 ngx_http_slowfs_static_send(ngx_http_request_t *r)
190 {
191 u_char *last, *location, *procname;
192 size_t root, len;
193 ngx_str_t path;
194 ngx_int_t rc;
195 ngx_uint_t level;
196 ngx_log_t *log;
197 ngx_buf_t *b;
198 ngx_chain_t out;
199 ngx_open_file_info_t of;
200 ngx_http_core_loc_conf_t *clcf;
201 /* slowfs */
202 ngx_http_slowfs_loc_conf_t *slowcf;
203 #if defined(nginx_version) && (nginx_version < 8048)
204 ngx_http_slowfs_ctx_t *slowctx;
205 ngx_uint_t old_status;
206 #endif
207 ngx_http_cache_t *c;
208 time_t valid;
209
210 log = r->connection->log;
211
212 /*
213 * ngx_http_map_uri_to_path() allocates memory for terminating '\0'
214 * so we do not need to reserve memory for '/' for possible redirect
215 */
216
217 last = ngx_http_map_uri_to_path(r, &path, &root, 0);
218 if (last == NULL) {
219 return NGX_HTTP_INTERNAL_SERVER_ERROR;
220 }
221
222 path.len = last - path.data;
223
224 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
225 "http filename: \"%s\"", path.data);
226
227 clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
228
229 ngx_memzero(&of, sizeof(ngx_open_file_info_t));
230
231 #if defined(nginx_version) && (nginx_version >= 8018)
232 of.read_ahead = clcf->read_ahead;
233 #endif
234 of.directio = clcf->directio;
235 of.valid = clcf->open_file_cache_valid;
236 of.min_uses = clcf->open_file_cache_min_uses;
237 of.errors = clcf->open_file_cache_errors;
238 of.events = clcf->open_file_cache_events;
239
240 if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
241 != NGX_OK)
242 {
243 switch (of.err) {
244
245 case 0:
246 return NGX_HTTP_INTERNAL_SERVER_ERROR;
247
248 case NGX_ENOENT:
249 case NGX_ENOTDIR:
250 case NGX_ENAMETOOLONG:
251
252 level = NGX_LOG_ERR;
253 rc = NGX_HTTP_NOT_FOUND;
254 break;
255
256 case NGX_EACCES:
257
258 level = NGX_LOG_ERR;
259 rc = NGX_HTTP_FORBIDDEN;
260 break;
261
262 default:
263
264 level = NGX_LOG_CRIT;
265 rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
266 break;
267 }
268
269 if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
270 ngx_log_error(level, log, of.err,
271 "%s \"%s\" failed", of.failed, path.data);
272 }
273
274 return rc;
275 }
276
277 r->root_tested = !r->error_page;
278
279 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", of.fd);
280
281 if (of.is_dir) {
282
283 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http dir");
284
285 r->headers_out.location = ngx_palloc(r->pool, sizeof(ngx_table_elt_t));
286 if (r->headers_out.location == NULL) {
287 return NGX_HTTP_INTERNAL_SERVER_ERROR;
288 }
289
290 len = r->uri.len + 1;
291
292 if (!clcf->alias && clcf->root_lengths == NULL && r->args.len == 0) {
293 location = path.data + clcf->root.len;
294
295 *last = '/';
296
297 } else {
298 if (r->args.len) {
299 len += r->args.len + 1;
300 }
301
302 location = ngx_pnalloc(r->pool, len);
303 if (location == NULL) {
304 return NGX_HTTP_INTERNAL_SERVER_ERROR;
305 }
306
307 last = ngx_copy(location, r->uri.data, r->uri.len);
308
309 *last = '/';
310
311 if (r->args.len) {
312 *++last = '?';
313 ngx_memcpy(++last, r->args.data, r->args.len);
314 }
315 }
316
317 /*
318 * we do not need to set the r->headers_out.location->hash and
319 * r->headers_out.location->key fields
320 */
321
322 r->headers_out.location->value.len = len;
323 r->headers_out.location->value.data = location;
324
325 return NGX_HTTP_MOVED_PERMANENTLY;
326 }
327
328 #if !(NGX_WIN32) /* the not regular files are probably Unix specific */
329
330 if (!of.is_file) {
331 ngx_log_error(NGX_LOG_CRIT, log, 0,
332 "\"%s\" is not a regular file", path.data);
333
334 return NGX_HTTP_NOT_FOUND;
335 }
336
337 #endif
338
339 log->action = "sending response to client";
340
341 r->headers_out.status = NGX_HTTP_OK;
342 r->headers_out.content_length_n = of.size;
343 r->headers_out.last_modified_time = of.mtime;
344
345 if (ngx_http_set_content_type(r) != NGX_OK) {
346 return NGX_HTTP_INTERNAL_SERVER_ERROR;
347 }
348
349 if (r != r->main && of.size == 0) {
350 return ngx_http_send_header(r);
351 }
352
353 r->allow_ranges = 1;
354
355 /* slowfs */
356 slowcf = ngx_http_get_module_loc_conf(r, ngx_http_slowfs_module);
357 valid = ngx_http_file_cache_valid(slowcf->cache_valid, 200);
358
359 /* Don't cache content that instantly expires. */
360 if (valid
361 #if defined(nginx_version) && (nginx_version < 8031)
362 /*
363 * Don't cache 0 byte files, because nginx doesn't flush response
364 * while serving them from cache and client timeouts.
365 * This has been fixed in nginx-0.8.31.
366 */
367 && of.size
368 #endif
369 ) {
370 c = r->cache;
371
372 ngx_shmtx_lock(&c->file_cache->shpool->mutex);
373 if (c->node->uses >= c->min_uses && !c->node->updating) {
374 ngx_shmtx_unlock(&c->file_cache->shpool->mutex);
375
376 if ((size_t) of.size < slowcf->big_file_size) {
377 /*
378 * Small files:
379 * - copy file to the cache in worker process,
380 * - send file to current client from the cache.
381 */
382 #if defined(nginx_version) && (nginx_version < 8048)
383 slowctx = ngx_http_get_module_ctx(r, ngx_http_slowfs_module);
384 old_status = slowctx->cache_status;
385 #endif
386
387 #if defined(nginx_version) && (nginx_version >= 1001012)
388 ngx_shmtx_lock(&c->file_cache->shpool->mutex);
389 c->node->count++;
390 ngx_shmtx_unlock(&c->file_cache->shpool->mutex);
391 #endif
392
393 ngx_http_slowfs_cache_update(r, &of, &path);
394 /* Allow cache_cleanup after cache_update. */
395 c->updated = 0;
396
397 #if defined(nginx_version) && (nginx_version < 8048)
398 if (old_status == NGX_HTTP_CACHE_EXPIRED) {
399 /*
400 * Expired cached files don't increment counter,
401 * because ngx_http_file_cache_exists isn't called.
402 */
403 ngx_shmtx_lock(&c->file_cache->shpool->mutex);
404 c->node->count++;
405 ngx_shmtx_unlock(&c->file_cache->shpool->mutex);
406 }
407 #endif
408
409 rc = ngx_http_slowfs_cache_send(r);
410
411 #if defined(nginx_version) && (nginx_version < 8048)
412 slowctx->cache_status = old_status;
413 #endif
414
415 if (rc != NGX_DECLINED) {
416 return rc;
417 }
418
419 /* continue static processing for NGX_DECLINED... */
420 } else {
421 /*
422 * Big files:
423 * - fork() new process,
424 * - copy file to the cache in new process,
425 * - send file to current client from the original source.
426 */
427 switch (fork()) {
428 case -1: /* failed */
429 ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
430 "fork() failed while spawning \"%s\"",
431 SLOWFS_PROCESS_NAME);
432 break;
433 case 0: /* child */
434 ngx_pid = ngx_getpid();
435
436 len = sizeof(SLOWFS_PROCESS_NAME) - 1 + sizeof(": to ") - 1
437 + path.len + c->file.name.len;
438
439 procname = ngx_pnalloc(r->pool, len + 1);
440 if (procname == NULL) {
441 return NGX_HTTP_INTERNAL_SERVER_ERROR;
442 }
443
444 last = ngx_snprintf(procname, len, SLOWFS_PROCESS_NAME
445 ": %V to %V", &path, &c->file.name);
446 *last = '\0';
447
448 ngx_setproctitle((char *) procname);
449
450 ngx_http_slowfs_cache_update(r, &of, &path);
451
452 exit(0);
453 break;
454 default: /* parent */
455 c->node = NULL;
456 c->updated = 1;
457 break;
458 }
459 }
460 } else {
461 ngx_shmtx_unlock(&c->file_cache->shpool->mutex);
462 }
463 }
464 /* slowfs */
465
466 /* we need to allocate all before the header would be sent */
467
468 b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
469 if (b == NULL) {
470 return NGX_HTTP_INTERNAL_SERVER_ERROR;
471 }
472
473 b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
474 if (b->file == NULL) {
475 return NGX_HTTP_INTERNAL_SERVER_ERROR;
476 }
477
478 rc = ngx_http_send_header(r);
479
480 if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
481 return rc;
482 }
483
484 b->file_pos = 0;
485 b->file_last = of.size;
486
487 b->in_file = b->file_last ? 1: 0;
488 b->last_buf = (r == r->main) ? 1: 0;
489 b->last_in_chain = 1;
490
491 b->file->fd = of.fd;
492 b->file->name = path;
493 b->file->log = log;
494 b->file->directio = of.is_directio;
495
496 out.buf = b;
497 out.next = NULL;
498
499 return ngx_http_output_filter(r, &out);
500 }
501
502 ngx_int_t
ngx_http_slowfs_cache_send(ngx_http_request_t * r)503 ngx_http_slowfs_cache_send(ngx_http_request_t *r)
504 {
505 ngx_http_slowfs_loc_conf_t *slowcf;
506 ngx_http_slowfs_ctx_t *slowctx;
507 ngx_http_cache_t *c;
508 ngx_str_t *key;
509 ngx_int_t rc;
510
511 slowcf = ngx_http_get_module_loc_conf(r, ngx_http_slowfs_module);
512 slowctx = ngx_http_get_module_ctx(r, ngx_http_slowfs_module);
513
514 c = r->cache;
515 if (c != NULL) {
516 goto skip_alloc;
517 }
518
519 c = ngx_pcalloc(r->pool, sizeof(ngx_http_cache_t));
520 if (c == NULL) {
521 return NGX_HTTP_INTERNAL_SERVER_ERROR;
522 }
523
524 rc = ngx_array_init(&c->keys, r->pool, 1, sizeof(ngx_str_t));
525 if (rc != NGX_OK) {
526 return NGX_HTTP_INTERNAL_SERVER_ERROR;
527 }
528
529 key = ngx_array_push(&c->keys);
530 if (key == NULL) {
531 return NGX_HTTP_INTERNAL_SERVER_ERROR;
532 }
533
534 rc = ngx_http_complex_value(r, &slowcf->cache_key, key);
535 if (rc != NGX_OK) {
536 return NGX_HTTP_INTERNAL_SERVER_ERROR;
537 }
538
539 slowctx = ngx_palloc(r->pool, sizeof(ngx_http_slowfs_ctx_t));
540 if (slowctx == NULL) {
541 return NGX_HTTP_INTERNAL_SERVER_ERROR;
542 }
543
544 slowctx->cache_status = NGX_HTTP_CACHE_MISS;
545
546 ngx_http_set_ctx(r, slowctx, ngx_http_slowfs_module);
547
548 r->cache = c;
549 c->body_start = ngx_pagesize;
550 c->min_uses = slowcf->cache_min_uses;
551 c->file_cache = slowcf->cache->data;
552 c->file.log = r->connection->log;
553
554 ngx_http_file_cache_create_key(r);
555
556 skip_alloc:
557 rc = ngx_http_file_cache_open(r);
558 if (rc != NGX_OK) {
559 if (rc == NGX_HTTP_CACHE_STALE) {
560 /*
561 * Revert c->node->updating = 1, we want this to be true only when
562 * ngx_slowfs_cache is in the process of copying given file.
563 */
564 ngx_shmtx_lock(&c->file_cache->shpool->mutex);
565 c->node->updating = 0;
566 c->updating = 0;
567 ngx_shmtx_unlock(&c->file_cache->shpool->mutex);
568
569 slowctx->cache_status = NGX_HTTP_CACHE_EXPIRED;
570 } else if (rc == NGX_HTTP_CACHE_UPDATING) {
571 slowctx->cache_status = NGX_HTTP_CACHE_EXPIRED;
572 }
573
574 return NGX_DECLINED;
575 }
576
577 r->connection->log->action = "sending cached response to client";
578
579 slowctx->cache_status = NGX_HTTP_CACHE_HIT;
580
581 r->headers_out.status = NGX_HTTP_OK;
582 r->headers_out.content_length_n = c->length - c->body_start;
583 r->headers_out.last_modified_time = c->last_modified;
584
585 if (ngx_http_set_content_type(r) != NGX_OK) {
586 return NGX_HTTP_INTERNAL_SERVER_ERROR;
587 }
588
589 r->allow_ranges = 1;
590
591 return ngx_http_cache_send(r);
592 }
593
594 void
ngx_http_slowfs_cache_update(ngx_http_request_t * r,ngx_open_file_info_t * of,ngx_str_t * path)595 ngx_http_slowfs_cache_update(ngx_http_request_t *r, ngx_open_file_info_t *of,
596 ngx_str_t *path)
597 {
598 ngx_http_slowfs_loc_conf_t *slowcf;
599 ngx_temp_file_t *tf;
600 ngx_http_cache_t *c;
601 ngx_int_t rc;
602 u_char *buf;
603 time_t valid, now;
604 off_t size;
605 size_t len;
606 ssize_t n;
607
608 c = r->cache;
609
610 ngx_shmtx_lock(&c->file_cache->shpool->mutex);
611
612 if (c->node->updating) {
613 /* race between concurrent processes, backoff */
614 c->node->count--;
615 ngx_shmtx_unlock(&c->file_cache->shpool->mutex);
616 return;
617 }
618
619 c->node->updating = 1;
620 c->updating = 1;
621
622 ngx_shmtx_unlock(&c->file_cache->shpool->mutex);
623
624 r->connection->log->action = "populating cache";
625
626 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
627 "http file cache copy: \"%s\" to \"%s\"",
628 path->data, c->file.name.data);
629
630 len = 8 * ngx_pagesize;
631
632 tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t));
633 if (tf == NULL) {
634 goto failed;
635 }
636
637 buf = ngx_palloc(r->pool, len);
638 if (buf == NULL) {
639 goto failed;
640 }
641
642 slowcf = ngx_http_get_module_loc_conf(r, ngx_http_slowfs_module);
643
644 valid = ngx_http_file_cache_valid(slowcf->cache_valid, 200);
645
646 now = ngx_time();
647
648 c->valid_sec = now + valid;
649 c->date = now;
650 c->last_modified = r->headers_out.last_modified_time;
651
652 /*
653 * We don't save headers, but we add empty line
654 * as a workaround for older nginx versions,
655 * so c->header_start < c->body_start.
656 */
657 c->body_start = c->header_start + 1;
658
659 ngx_http_file_cache_set_header(r, buf);
660 *(buf + c->header_start) = LF;
661
662 tf->file.fd = NGX_INVALID_FILE;
663 tf->file.log = r->connection->log;
664 tf->path = slowcf->temp_path;
665 tf->pool = r->pool;
666 tf->persistent = 1;
667
668 rc = ngx_create_temp_file(&tf->file, tf->path, tf->pool, tf->persistent,
669 tf->clean, tf->access);
670 if (rc != NGX_OK) {
671 goto failed;
672 }
673
674 n = ngx_write_fd(tf->file.fd, buf, c->body_start);
675 if ((size_t) n != c->body_start) {
676 goto failed;
677 }
678
679 size = of->size;
680
681 /*
682 * source: ngx_file.c/ngx_copy_file
683 * Copyright (C) Igor Sysoev
684 */
685 while (size > 0) {
686
687 if ((off_t) len > size) {
688 len = (size_t) size;
689 }
690
691 n = ngx_read_fd(of->fd, buf, len);
692
693 if (n == NGX_FILE_ERROR) {
694 ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
695 ngx_read_fd_n " \"%s\" failed", path->data);
696 goto failed;
697 }
698
699 if ((size_t) n != len) {
700 ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
701 ngx_read_fd_n " has read only %z of %uz bytes",
702 n, size);
703 goto failed;
704 }
705
706 n = ngx_write_fd(tf->file.fd, buf, len);
707
708 if (n == NGX_FILE_ERROR) {
709 ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
710 ngx_write_fd_n " \"%s\" failed", tf->file.name.data);
711 goto failed;
712 }
713
714 if ((size_t) n != len) {
715 ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
716 ngx_write_fd_n " has written only %z of %uz bytes",
717 n, size);
718 goto failed;
719 }
720
721 size -= n;
722 }
723
724 ngx_http_file_cache_update(r, tf);
725
726 return;
727
728 failed:
729 ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
730 "http file cache copy: \"%s\" failed", path->data);
731
732 ngx_http_file_cache_free(c, tf);
733
734 return;
735 }
736
737 ngx_int_t
ngx_http_slowfs_cache_purge(ngx_http_request_t * r,ngx_http_file_cache_t * cache,ngx_http_complex_value_t * cache_key)738 ngx_http_slowfs_cache_purge(ngx_http_request_t *r, ngx_http_file_cache_t *cache,
739 ngx_http_complex_value_t *cache_key)
740 {
741 ngx_http_cache_t *c;
742 ngx_str_t *key;
743 ngx_int_t rc;
744
745 rc = ngx_http_discard_request_body(r);
746 if (rc != NGX_OK) {
747 return NGX_ERROR;
748 }
749
750 c = ngx_pcalloc(r->pool, sizeof(ngx_http_cache_t));
751 if (c == NULL) {
752 return NGX_ERROR;
753 }
754
755 rc = ngx_array_init(&c->keys, r->pool, 1, sizeof(ngx_str_t));
756 if (rc != NGX_OK) {
757 return NGX_ERROR;
758 }
759
760 key = ngx_array_push(&c->keys);
761 if (key == NULL) {
762 return NGX_ERROR;
763 }
764
765 rc = ngx_http_complex_value(r, cache_key, key);
766 if (rc != NGX_OK) {
767 return NGX_ERROR;
768 }
769
770 r->cache = c;
771 c->body_start = ngx_pagesize;
772 c->file_cache = cache;
773 c->file.log = r->connection->log;
774
775 ngx_http_file_cache_create_key(r);
776
777 rc = ngx_http_file_cache_open(r);
778
779 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
780 "http file cache purge: %i, \"%s\"",
781 rc, c->file.name.data);
782
783 if (rc == NGX_HTTP_CACHE_UPDATING || rc == NGX_HTTP_CACHE_STALE) {
784 rc = NGX_OK;
785 }
786
787 if (rc != NGX_OK) {
788 if (rc == NGX_DECLINED) {
789 return rc;
790 } else {
791 return NGX_ERROR;
792 }
793 }
794
795 /*
796 * delete file from disk but *keep* in-memory node,
797 * because other requests might still point to it.
798 */
799
800 ngx_shmtx_lock(&cache->shpool->mutex);
801
802 if (!c->node->exists) {
803 /* race between concurrent purges, backoff */
804 ngx_shmtx_unlock(&cache->shpool->mutex);
805 return NGX_DECLINED;
806 }
807
808 #if defined(nginx_version) && (nginx_version >= 1000001)
809 cache->sh->size -= c->node->fs_size;
810 c->node->fs_size = 0;
811 #else
812 cache->sh->size -= (c->node->length + cache->bsize - 1) / cache->bsize;
813 c->node->length = 0;
814 #endif
815
816 c->node->exists = 0;
817 c->node->updating = 0;
818 c->updating = 0;
819
820 ngx_shmtx_unlock(&cache->shpool->mutex);
821
822 if (ngx_delete_file(c->file.name.data) == NGX_FILE_ERROR) {
823 /* entry in error log is enough, don't notice client */
824 ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
825 ngx_delete_file_n " \"%s\" failed", c->file.name.data);
826 }
827
828 /* file deleted from cache */
829 return NGX_OK;
830 }
831
832 ngx_int_t
ngx_http_slowfs_handler(ngx_http_request_t * r)833 ngx_http_slowfs_handler(ngx_http_request_t *r)
834 {
835 ngx_http_slowfs_loc_conf_t *slowcf;
836 ngx_int_t rc;
837
838 slowcf = ngx_http_get_module_loc_conf(r, ngx_http_slowfs_module);
839 if (!slowcf->enabled) {
840 return NGX_DECLINED;
841 }
842
843 if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
844 return NGX_HTTP_NOT_ALLOWED;
845 }
846
847 if (r->uri.data[r->uri.len - 1] == '/') {
848 return NGX_DECLINED;
849 }
850
851 #if defined(nginx_version) \
852 && ((nginx_version < 7066) \
853 || ((nginx_version >= 8000) && (nginx_version < 8038)))
854 if (r->zero_in_uri) {
855 return NGX_DECLINED;
856 }
857 #endif
858
859 rc = ngx_http_discard_request_body(r);
860 if (rc != NGX_OK) {
861 return rc;
862 }
863
864 rc = ngx_http_slowfs_cache_send(r);
865 if (rc == NGX_DECLINED) {
866 rc = ngx_http_slowfs_static_send(r);
867 }
868
869 return rc;
870 }
871
872 static char ngx_http_cache_purge_success_page_top[] =
873 "<html>" CRLF
874 "<head><title>Successful purge</title></head>" CRLF
875 "<body bgcolor=\"white\">" CRLF
876 "<center><h1>Successful purge</h1>" CRLF
877 ;
878
879 static char ngx_http_cache_purge_success_page_tail[] =
880 CRLF "</center>" CRLF
881 "<hr><center>" NGINX_VER "</center>" CRLF
882 "</body>" CRLF
883 "</html>" CRLF
884 ;
885
886 ngx_int_t
ngx_http_slowfs_cache_purge_handler(ngx_http_request_t * r)887 ngx_http_slowfs_cache_purge_handler(ngx_http_request_t *r)
888 {
889 ngx_http_slowfs_loc_conf_t *slowcf;
890 ngx_chain_t out;
891 ngx_buf_t *b;
892 ngx_str_t *key;
893 ngx_int_t rc;
894 size_t len;
895
896 slowcf = ngx_http_get_module_loc_conf(r, ngx_http_slowfs_module);
897
898 rc = ngx_http_slowfs_cache_purge(r, slowcf->cache->data,
899 &slowcf->cache_key);
900 if (rc == NGX_ERROR) {
901 return NGX_HTTP_INTERNAL_SERVER_ERROR;
902 } else if (rc == NGX_DECLINED) {
903 return NGX_HTTP_NOT_FOUND;
904 }
905
906 key = r->cache->keys.elts;
907
908 len = sizeof(ngx_http_cache_purge_success_page_top) - 1
909 + sizeof(ngx_http_cache_purge_success_page_tail) - 1
910 + sizeof("<br>Key : ") - 1 + sizeof(CRLF "<br>Path: ") - 1
911 + key[0].len + r->cache->file.name.len;
912
913 r->headers_out.content_type.len = sizeof("text/html") - 1;
914 r->headers_out.content_type.data = (u_char *) "text/html";
915 r->headers_out.status = NGX_HTTP_OK;
916 r->headers_out.content_length_n = len;
917
918 if (r->method == NGX_HTTP_HEAD) {
919 rc = ngx_http_send_header(r);
920 if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
921 return rc;
922 }
923 }
924
925 b = ngx_create_temp_buf(r->pool, len);
926 if (b == NULL) {
927 return NGX_HTTP_INTERNAL_SERVER_ERROR;
928 }
929
930 out.buf = b;
931 out.next = NULL;
932
933 b->last = ngx_cpymem(b->last, ngx_http_cache_purge_success_page_top,
934 sizeof(ngx_http_cache_purge_success_page_top) - 1);
935 b->last = ngx_cpymem(b->last, "<br>Key : ", sizeof("<br>Key : ") - 1);
936 b->last = ngx_cpymem(b->last, key[0].data, key[0].len);
937 b->last = ngx_cpymem(b->last, CRLF "<br>Path: ",
938 sizeof(CRLF "<br>Path: ") - 1);
939 b->last = ngx_cpymem(b->last, r->cache->file.name.data,
940 r->cache->file.name.len);
941 b->last = ngx_cpymem(b->last, ngx_http_cache_purge_success_page_tail,
942 sizeof(ngx_http_cache_purge_success_page_tail) - 1);
943 b->last_buf = 1;
944
945 rc = ngx_http_send_header(r);
946 if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
947 return rc;
948 }
949
950 return ngx_http_output_filter(r, &out);
951 }
952
953 /*
954 * source: ngx_http_upstream.c/ngx_http_upstream_cache_status
955 * Copyright (C) Igor Sysoev
956 */
957 ngx_int_t
ngx_http_slowfs_cache_status(ngx_http_request_t * r,ngx_http_variable_value_t * v,uintptr_t data)958 ngx_http_slowfs_cache_status(ngx_http_request_t *r,
959 ngx_http_variable_value_t *v, uintptr_t data)
960 {
961 ngx_http_slowfs_ctx_t *slowctx;
962 ngx_uint_t n;
963
964 slowctx = ngx_http_get_module_ctx(r, ngx_http_slowfs_module);
965
966 if (slowctx == NULL || slowctx->cache_status == 0) {
967 v->not_found = 1;
968 return NGX_OK;
969 }
970
971 n = slowctx->cache_status - 1;
972
973 v->valid = 1;
974 v->no_cacheable = 0;
975 v->not_found = 0;
976 v->len = ngx_http_cache_status[n].len;
977 v->data = ngx_http_cache_status[n].data;
978
979 return NGX_OK;
980 }
981
982 char *
ngx_http_slowfs_cache_conf(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)983 ngx_http_slowfs_cache_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
984 {
985 ngx_str_t *value = cf->args->elts;
986 ngx_http_slowfs_loc_conf_t *slowcf = conf;
987
988 if (slowcf->cache != NGX_CONF_UNSET_PTR && slowcf->cache != NULL) {
989 return "is either duplicate or collides with \"slowfs_cache_purge\"";
990 }
991
992 if (ngx_strcmp(value[1].data, "off") == 0) {
993 slowcf->enabled = 0;
994 slowcf->cache = NULL;
995 return NGX_CONF_OK;
996 }
997
998 slowcf->cache = ngx_shared_memory_add(cf, &value[1], 0,
999 &ngx_http_slowfs_module);
1000 if (slowcf->cache == NULL) {
1001 return NGX_CONF_ERROR;
1002 }
1003
1004 slowcf->enabled = 1;
1005
1006 return NGX_CONF_OK;
1007 }
1008
1009 char *
ngx_http_slowfs_cache_key_conf(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)1010 ngx_http_slowfs_cache_key_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
1011 {
1012 ngx_str_t *value = cf->args->elts;
1013 ngx_http_slowfs_loc_conf_t *slowcf = conf;
1014 ngx_http_compile_complex_value_t ccv;
1015
1016 if (slowcf->cache_key.value.len) {
1017 return "is either duplicate or collides with \"slowfs_cache_purge\"";
1018 }
1019
1020 ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1021
1022 ccv.cf = cf;
1023 ccv.value = &value[1];
1024 ccv.complex_value = &slowcf->cache_key;
1025
1026 if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1027 return NGX_CONF_ERROR;
1028 }
1029
1030 return NGX_CONF_OK;
1031 }
1032
1033 char *
ngx_http_slowfs_cache_purge_conf(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)1034 ngx_http_slowfs_cache_purge_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
1035 {
1036 ngx_str_t *value = cf->args->elts;
1037 ngx_http_slowfs_loc_conf_t *slowcf = conf;
1038 ngx_http_core_loc_conf_t *clcf;
1039 ngx_http_compile_complex_value_t ccv;
1040
1041 /* check for duplicates / collisions */
1042 if (slowcf->cache != NGX_CONF_UNSET_PTR && slowcf->cache != NULL) {
1043 return "is either duplicate or collides with \"slowfs_cache\"";
1044 }
1045
1046 if (slowcf->cache_key.value.len) {
1047 return "is either duplicate or collides with \"slowfs_cache\"";
1048 }
1049
1050 /* set slowfs_cache part */
1051 slowcf->cache = ngx_shared_memory_add(cf, &value[1], 0,
1052 &ngx_http_slowfs_module);
1053 if (slowcf->cache == NULL) {
1054 return NGX_CONF_ERROR;
1055 }
1056
1057 /* set slowfs_cache_key part */
1058 ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1059
1060 ccv.cf = cf;
1061 ccv.value = &value[2];
1062 ccv.complex_value = &slowcf->cache_key;
1063
1064 if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1065 return NGX_CONF_ERROR;
1066 }
1067
1068 /* set handler */
1069 clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
1070
1071 clcf->handler = ngx_http_slowfs_cache_purge_handler;
1072
1073 return NGX_CONF_OK;
1074 }
1075
1076 void *
ngx_http_slowfs_create_loc_conf(ngx_conf_t * cf)1077 ngx_http_slowfs_create_loc_conf(ngx_conf_t *cf)
1078 {
1079 ngx_http_slowfs_loc_conf_t *slowcf;
1080
1081 slowcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_slowfs_loc_conf_t));
1082 if (slowcf == NULL) {
1083 return NGX_CONF_ERROR;
1084 }
1085
1086 /*
1087 * via ngx_pcalloc():
1088 * slowcf->cache_key = NULL;
1089 * slowcf->temp_path = NULL;
1090 */
1091
1092 slowcf->enabled = NGX_CONF_UNSET;
1093 slowcf->cache = NGX_CONF_UNSET_PTR;
1094 slowcf->cache_min_uses = NGX_CONF_UNSET_UINT;
1095 slowcf->cache_valid = NGX_CONF_UNSET_PTR;
1096 slowcf->big_file_size = NGX_CONF_UNSET_SIZE;
1097
1098 return slowcf;
1099 }
1100
1101 char *
ngx_http_slowfs_merge_loc_conf(ngx_conf_t * cf,void * parent,void * child)1102 ngx_http_slowfs_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
1103 {
1104 ngx_http_slowfs_loc_conf_t *prev = parent;
1105 ngx_http_slowfs_loc_conf_t *slowcf = child;
1106
1107 ngx_conf_merge_value(slowcf->enabled, prev->enabled, 0);
1108
1109 if (slowcf->cache_key.value.data == NULL) {
1110 slowcf->cache_key = prev->cache_key;
1111 }
1112
1113 ngx_conf_merge_ptr_value(slowcf->cache, prev->cache, NULL);
1114 if (slowcf->cache && slowcf->cache->data == NULL) {
1115 ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1116 "\"slowfs_cache\" zone \"%V\" is unknown",
1117 &slowcf->cache->shm.name);
1118 return NGX_CONF_ERROR;
1119 }
1120
1121 ngx_conf_merge_uint_value(slowcf->cache_min_uses, prev->cache_min_uses, 1);
1122
1123 ngx_conf_merge_ptr_value(slowcf->cache_valid, prev->cache_valid, NULL);
1124
1125 ngx_conf_merge_size_value(slowcf->big_file_size, prev->big_file_size,
1126 131072);
1127
1128 if (ngx_conf_merge_path_value(cf, &slowcf->temp_path, prev->temp_path,
1129 &ngx_http_slowfs_temp_path) != NGX_OK)
1130 {
1131 return NGX_CONF_ERROR;
1132 }
1133
1134 return NGX_CONF_OK;
1135 }
1136
1137 ngx_int_t
ngx_http_slowfs_add_variables(ngx_conf_t * cf)1138 ngx_http_slowfs_add_variables(ngx_conf_t *cf)
1139 {
1140 ngx_http_variable_t *var, *v;
1141
1142 v = ngx_http_slowfs_module_variables;
1143
1144 var = ngx_http_add_variable(cf, &v->name, v->flags);
1145 if (var == NULL) {
1146 return NGX_ERROR;
1147 }
1148
1149 var->get_handler = v->get_handler;
1150 var->data = v->data;
1151
1152 return NGX_OK;
1153 }
1154
1155 ngx_int_t
ngx_http_slowfs_init(ngx_conf_t * cf)1156 ngx_http_slowfs_init(ngx_conf_t *cf)
1157 {
1158 ngx_http_handler_pt *h;
1159 ngx_http_core_main_conf_t *cmcf;
1160
1161 cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
1162
1163 h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
1164 if (h == NULL) {
1165 return NGX_ERROR;
1166 }
1167
1168 *h = ngx_http_slowfs_handler;
1169
1170 return NGX_OK;
1171 }
1172
1173 #else /* !NGX_HTTP_CACHE */
1174
1175 static ngx_http_module_t ngx_http_slowfs_module_ctx = {
1176 NULL, /* preconfiguration */
1177 NULL, /* postconfiguration */
1178
1179 NULL, /* create main configuration */
1180 NULL, /* init main configuration */
1181
1182 NULL, /* create server configuration */
1183 NULL, /* merge server configuration */
1184
1185 NULL, /* create location configuration */
1186 NULL, /* merge location configuration */
1187 };
1188
1189 ngx_module_t ngx_http_slowfs_module = {
1190 NGX_MODULE_V1,
1191 &ngx_http_slowfs_module_ctx, /* module context */
1192 NULL, /* module directives */
1193 NGX_HTTP_MODULE, /* module type */
1194 NULL, /* init master */
1195 NULL, /* init module */
1196 NULL, /* init process */
1197 NULL, /* init thread */
1198 NULL, /* exit thread */
1199 NULL, /* exit process */
1200 NULL, /* exit master */
1201 NGX_MODULE_V1_PADDING
1202 };
1203
1204 #endif /* NGX_HTTP_CACHE */
1205