comparison src/http/modules/proxy/ngx_http_proxy_cache.c @ 0:f0b350454894 NGINX_0_1_0

nginx 0.1.0 *) The first public version.
author Igor Sysoev <http://sysoev.ru>
date Mon, 04 Oct 2004 00:00:00 +0400
parents
children 46833bd150cb
comparison
equal deleted inserted replaced
-1:000000000000 0:f0b350454894
1
2 /*
3 * Copyright (C) Igor Sysoev
4 */
5
6
7 #include <ngx_config.h>
8 #include <ngx_core.h>
9 #include <ngx_http.h>
10 #include <ngx_http_proxy_handler.h>
11
12
13 static int ngx_http_proxy_process_cached_response(ngx_http_proxy_ctx_t *p,
14 int rc);
15 static int ngx_http_proxy_process_cached_header(ngx_http_proxy_ctx_t *p);
16 static void ngx_http_proxy_cache_look_complete_request(ngx_http_proxy_ctx_t *p);
17
18
19 int ngx_http_proxy_get_cached_response(ngx_http_proxy_ctx_t *p)
20 {
21 char *last;
22 ngx_http_request_t *r;
23 ngx_http_proxy_cache_t *c;
24 ngx_http_proxy_upstream_conf_t *u;
25
26 r = p->request;
27
28 if (!(c = ngx_pcalloc(r->pool, sizeof(ngx_http_proxy_cache_t)))) {
29 return NGX_HTTP_INTERNAL_SERVER_ERROR;
30 }
31
32 p->cache = c;
33
34 c->ctx.file.fd = NGX_INVALID_FILE;
35 c->ctx.file.log = r->connection->log;
36 c->ctx.path = p->lcf->cache_path;
37
38 u = p->lcf->upstream;
39
40 c->ctx.key.len = u->url.len + r->uri.len - u->location->len + r->args.len;
41 if (!(c->ctx.key.data = ngx_palloc(r->pool, c->ctx.key.len + 1))) {
42 return NGX_HTTP_INTERNAL_SERVER_ERROR;
43 }
44
45 last = ngx_cpymem(c->ctx.key.data, u->url.data, u->url.len);
46
47 last = ngx_cpymem(last, r->uri.data + u->location->len,
48 r->uri.len - u->location->len);
49
50 if (r->args.len > 0) {
51 *(last++) = '?';
52 last = ngx_cpymem(last, r->args.data, r->args.len);
53 }
54 *last = '\0';
55
56 p->header_in = ngx_create_temp_hunk(r->pool, p->lcf->header_buffer_size);
57 if (p->header_in == NULL) {
58 return NGX_HTTP_INTERNAL_SERVER_ERROR;
59 }
60 p->header_in->tag = (ngx_hunk_tag_t) &ngx_http_proxy_module;
61
62 c->ctx.buf = p->header_in;
63 c->ctx.log = r->connection->log;
64
65 return ngx_http_proxy_process_cached_response(p,
66 ngx_http_cache_get_file(r, &c->ctx));
67 }
68
69
70 static int ngx_http_proxy_process_cached_response(ngx_http_proxy_ctx_t *p,
71 int rc)
72 {
73 if (rc == NGX_OK) {
74 p->state->cache_state = NGX_HTTP_PROXY_CACHE_HIT;
75 p->header_in->pos = p->header_in->start + p->cache->ctx.header_size;
76
77 if (ngx_http_proxy_process_cached_header(p) == NGX_ERROR) {
78 return NGX_HTTP_INTERNAL_SERVER_ERROR;
79 }
80
81 p->valid_header_in = 1;
82
83 return ngx_http_proxy_send_cached_response(p);
84 }
85
86 if (rc == NGX_HTTP_CACHE_STALE) {
87 p->state->cache_state = NGX_HTTP_PROXY_CACHE_EXPR;
88
89 } else if (rc == NGX_HTTP_CACHE_AGED) {
90 p->state->cache_state = NGX_HTTP_PROXY_CACHE_AGED;
91 }
92
93 if (rc == NGX_HTTP_CACHE_STALE || rc == NGX_HTTP_CACHE_AGED) {
94 p->state->expired = ngx_time() - p->cache->ctx.expires;
95 p->header_in->pos = p->header_in->start + p->cache->ctx.header_size;
96
97 if (ngx_http_proxy_process_cached_header(p) == NGX_ERROR) {
98 return NGX_HTTP_INTERNAL_SERVER_ERROR;
99 }
100
101 p->header_in->pos = p->header_in->start + p->cache->ctx.header_size;
102 p->header_in->last = p->header_in->pos;
103
104 p->stale = 1;
105 p->valid_header_in = 1;
106
107 } else if (rc == NGX_DECLINED) {
108 p->state->cache_state = NGX_HTTP_PROXY_CACHE_MISS;
109 p->header_in->pos = p->header_in->start + p->cache->ctx.header_size;
110 p->header_in->last = p->header_in->pos;
111 }
112
113 if (p->lcf->busy_lock) {
114 p->try_busy_lock = 1;
115
116 p->header_in->pos = p->header_in->start;
117 p->header_in->last = p->header_in->start;
118
119 p->busy_lock.time = 0;
120 p->busy_lock.event = p->request->connection->read;
121 p->busy_lock.event_handler = ngx_http_proxy_busy_lock_handler;
122 p->busy_lock.md5 = p->cache->ctx.md5;
123
124 ngx_http_proxy_cache_busy_lock(p);
125 return NGX_DONE;
126 }
127
128 return ngx_http_proxy_request_upstream(p);
129 }
130
131
132 static int ngx_http_proxy_process_cached_header(ngx_http_proxy_ctx_t *p)
133 {
134 int rc, i;
135 ngx_table_elt_t *h;
136 ngx_http_request_t *r;
137 ngx_http_proxy_cache_t *c;
138
139 rc = ngx_http_proxy_parse_status_line(p);
140
141 c = p->cache;
142 r = p->request;
143
144 if (rc == NGX_AGAIN) {
145 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
146 "\"proxy_header_buffer_size\" "
147 "is too small to read header from \"%s\"",
148 c->ctx.file.name.data);
149 return NGX_ERROR;
150 }
151
152 if (rc == NGX_HTTP_PROXY_PARSE_NO_HEADER) {
153 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
154 "no valid HTTP/1.0 header in \"%s\"",
155 c->ctx.file.name.data);
156 return NGX_ERROR;
157 }
158
159 /* rc == NGX_OK */
160
161 c->status = p->status;
162 c->status_line.len = p->status_end - p->status_start;
163 c->status_line.data = ngx_palloc(r->pool, c->status_line.len + 1);
164 if (c->status_line.data == NULL) {
165 return NGX_ERROR;
166 }
167
168 /* reset for the possible parsing the upstream header */
169
170 p->status = 0;
171 p->status_count = 0;
172
173 ngx_cpystrn(c->status_line.data, p->status_start, c->status_line.len + 1);
174
175 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
176 "http cache status %d \"%s\"",
177 c->status, c->status_line.data);
178
179 /* TODO: ngx_init_table */
180 c->headers_in.headers = ngx_create_table(r->pool, 20);
181
182 for ( ;; ) {
183 rc = ngx_http_parse_header_line(r, p->header_in);
184
185 if (rc == NGX_OK) {
186
187 /* a header line has been parsed successfully */
188
189 h = ngx_http_add_header(&c->headers_in, ngx_http_proxy_headers_in);
190 if (h == NULL) {
191 return NGX_ERROR;
192 }
193
194 h->key.len = r->header_name_end - r->header_name_start;
195 h->value.len = r->header_end - r->header_start;
196
197 h->key.data = ngx_palloc(r->pool,
198 h->key.len + 1 + h->value.len + 1);
199 if (h->key.data == NULL) {
200 return NGX_ERROR;
201 }
202
203 h->value.data = h->key.data + h->key.len + 1;
204 ngx_cpystrn(h->key.data, r->header_name_start, h->key.len + 1);
205 ngx_cpystrn(h->value.data, r->header_start, h->value.len + 1);
206
207 for (i = 0; ngx_http_proxy_headers_in[i].name.len != 0; i++) {
208 if (ngx_http_proxy_headers_in[i].name.len != h->key.len) {
209 continue;
210 }
211
212 if (ngx_strcasecmp(ngx_http_proxy_headers_in[i].name.data,
213 h->key.data) == 0)
214 {
215 *((ngx_table_elt_t **) ((char *) &c->headers_in
216 + ngx_http_proxy_headers_in[i].offset)) = h;
217 break;
218 }
219 }
220
221 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
222 "http cache header: \"%s: %s\"",
223 h->key.data, h->value.data);
224
225 continue;
226
227 } else if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
228
229 /* a whole header has been parsed successfully */
230
231 ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
232 "http cache header done");
233
234 c->ctx.file_start = p->header_in->pos - p->header_in->start;
235
236 return NGX_OK;
237
238 } else if (rc == NGX_HTTP_PARSE_INVALID_HEADER) {
239
240 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
241 "invalid header in \"%s\"",
242 c->ctx.file.name.data);
243 return NGX_ERROR;
244 }
245
246 /* rc == NGX_AGAIN || rc == NGX_HTTP_PARSE_TOO_LONG_HEADER */
247
248 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
249 "\"proxy_header_buffer_size\" "
250 "is too small to read header from \"%s\"",
251 c->ctx.file.name.data);
252 return NGX_ERROR;
253 }
254 }
255
256
257 void ngx_http_proxy_cache_busy_lock(ngx_http_proxy_ctx_t *p)
258 {
259 int rc, ft_type;
260
261 rc = ngx_http_busy_lock_cachable(p->lcf->busy_lock, &p->busy_lock,
262 p->try_busy_lock);
263
264 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, p->request->connection->log, 0,
265 "http cache busy lock cachable: %d", rc);
266
267 if (rc == NGX_OK) {
268 if (p->try_busy_lock) {
269 p->busy_locked = 1;
270 p->header_in->pos = p->header_in->start + p->cache->ctx.header_size;
271 p->header_in->last = p->header_in->pos;
272
273 ngx_http_proxy_request_upstream(p);
274 return;
275 }
276
277 ngx_http_proxy_cache_look_complete_request(p);
278 return;
279 }
280
281 p->try_busy_lock = 0;
282
283 if (p->cache->ctx.file.fd != NGX_INVALID_FILE
284 && !p->cache->ctx.file.info_valid)
285 {
286 if (ngx_fd_info(p->cache->ctx.file.fd, &p->cache->ctx.file.info)
287 == NGX_FILE_ERROR)
288 {
289 ngx_log_error(NGX_LOG_CRIT, p->request->connection->log, ngx_errno,
290 ngx_fd_info_n " \"%s\" failed",
291 p->cache->ctx.file.name.data);
292 ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
293 return;
294 }
295
296 p->cache->ctx.file.info_valid = 1;
297 }
298
299 if (rc == NGX_AGAIN) {
300
301 if ((ngx_event_flags & (NGX_USE_CLEAR_EVENT|NGX_HAVE_KQUEUE_EVENT))
302 && !p->request->connection->write->active)
303 {
304 /*
305 * kqueue allows to detect when client closes prematurely
306 * connection
307 */
308
309 p->request->connection->write->event_handler =
310 ngx_http_proxy_check_broken_connection;
311
312 if (ngx_add_event(p->request->connection->write, NGX_WRITE_EVENT,
313 NGX_CLEAR_EVENT) == NGX_ERROR)
314 {
315 ngx_http_proxy_finalize_request(p,
316 NGX_HTTP_INTERNAL_SERVER_ERROR);
317 return;
318 }
319 }
320
321 return;
322 }
323
324 ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock);
325
326 if (rc == NGX_DONE) {
327 ft_type = NGX_HTTP_PROXY_FT_BUSY_LOCK;
328
329 } else {
330 /* rc == NGX_ERROR */
331 ft_type = NGX_HTTP_PROXY_FT_MAX_WAITING;
332 }
333
334 if (p->stale && (p->lcf->use_stale & ft_type)) {
335 ngx_http_proxy_finalize_request(p,
336 ngx_http_proxy_send_cached_response(p));
337 return;
338 }
339
340 p->state->status = NGX_HTTP_SERVICE_UNAVAILABLE;
341 ngx_http_proxy_finalize_request(p, NGX_HTTP_SERVICE_UNAVAILABLE);
342 }
343
344
345 static void ngx_http_proxy_cache_look_complete_request(ngx_http_proxy_ctx_t *p)
346 {
347 int rc;
348 ngx_http_cache_ctx_t *ctx;
349
350 if (!(ctx = ngx_pcalloc(p->request->pool, sizeof(ngx_http_cache_ctx_t)))) {
351 ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
352 return;
353 }
354
355 *ctx = p->cache->ctx;
356
357 rc = ngx_http_cache_open_file(ctx, ngx_file_uniq(&p->cache->ctx.file.info));
358
359 if (rc == NGX_DECLINED || rc == NGX_HTTP_CACHE_THE_SAME) {
360 p->try_busy_lock = 1;
361 p->busy_lock.time = 0;
362 ngx_http_proxy_cache_busy_lock(p);
363 return;
364 }
365
366 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, p->request->connection->log, 0,
367 "http cache old fd:%d, new fd:%d",
368 p->cache->ctx.file.fd, ctx->file.fd);
369
370 if (p->cache->ctx.file.fd != NGX_INVALID_FILE) {
371 if (ngx_close_file(p->cache->ctx.file.fd) == NGX_FILE_ERROR) {
372 ngx_log_error(NGX_LOG_ALERT, p->request->connection->log, ngx_errno,
373 ngx_close_file_n " \"%s\" failed",
374 p->cache->ctx.file.name.data);
375 }
376 }
377
378 p->cache->ctx = *ctx;
379
380 p->status = 0;
381 p->status_count = 0;
382
383 ngx_http_proxy_finalize_request(p,
384 ngx_http_proxy_process_cached_response(p, rc));
385 }
386
387
388 int ngx_http_proxy_send_cached_response(ngx_http_proxy_ctx_t *p)
389 {
390 int rc, len, i;
391 off_t rest;
392 ngx_hunk_t *h0, *h1;
393 ngx_chain_t out[2];
394 ngx_http_request_t *r;
395
396 r = p->request;
397
398 r->headers_out.status = p->cache->status;
399
400 #if 0
401 r->headers_out.content_length_n = -1;
402 r->headers_out.content_length = NULL;
403 #endif
404
405 /* copy an cached header to r->headers_out */
406
407 if (ngx_http_proxy_copy_header(p, &p->cache->headers_in) == NGX_ERROR) {
408 return NGX_HTTP_INTERNAL_SERVER_ERROR;
409 }
410
411 /* we need to allocate all before the header would be sent */
412
413 len = p->header_in->end - (p->header_in->start + p->cache->ctx.file_start);
414
415 h0 = NULL;
416 h1 = NULL;
417
418 if (len) {
419 if (!((h0 = ngx_calloc_hunk(r->pool)))) {
420 return NGX_HTTP_INTERNAL_SERVER_ERROR;
421 }
422
423 if (!((h0->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t))))) {
424 return NGX_HTTP_INTERNAL_SERVER_ERROR;
425 }
426 }
427
428 if (len < p->cache->ctx.length) {
429 if (!((h1 = ngx_calloc_hunk(r->pool)))) {
430 return NGX_HTTP_INTERNAL_SERVER_ERROR;
431 }
432
433 if (!((h1->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t))))) {
434 return NGX_HTTP_INTERNAL_SERVER_ERROR;
435 }
436 }
437
438 rc = ngx_http_send_header(r);
439
440 /* NEEDED ??? */ p->header_sent = 1;
441
442 if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
443 return rc;
444 }
445
446 rest = p->cache->ctx.length;
447
448 if (len) {
449 if (p->valid_header_in) {
450 h0->pos = p->header_in->start + p->cache->ctx.file_start;
451
452 if (len > p->cache->ctx.length) {
453 h0->last = h0->pos + p->cache->ctx.length;
454
455 } else {
456 h0->last = p->header_in->end;
457 }
458
459 h0->type = NGX_HUNK_IN_MEMORY|NGX_HUNK_TEMP;
460 }
461
462 h0->type |= NGX_HUNK_FILE;
463 h0->file_pos = p->cache->ctx.file_start;
464
465 h0->file->fd = p->cache->ctx.file.fd;
466 h0->file->log = r->connection->log;
467
468 if (len > p->cache->ctx.length) {
469 h0->file_last = h0->file_pos + p->cache->ctx.length;
470 rest = 0;
471
472 } else {
473 h0->file_last = h0->file_pos + len;
474 rest -= len;
475 }
476
477 out[0].hunk = h0;
478 out[0].next = &out[1];
479 i = 0;
480
481 } else {
482 i = -1;
483 }
484
485 if (rest) {
486 h1->file_pos = p->cache->ctx.file_start + len;
487 h1->file_last = h1->file_pos + rest;
488 h1->type = NGX_HUNK_FILE;
489
490 h1->file->fd = p->cache->ctx.file.fd;
491 h1->file->log = r->connection->log;
492
493 out[++i].hunk = h1;
494 }
495
496 out[i].next = NULL;
497 if (!r->main) {
498 out[i].hunk->type |= NGX_HUNK_LAST;
499 }
500
501 r->file.fd = p->cache->ctx.file.fd;
502
503 return ngx_http_output_filter(r, out);
504 }
505
506
507 int ngx_http_proxy_is_cachable(ngx_http_proxy_ctx_t *p)
508 {
509 time_t date, last_modified, expires, t;
510 ngx_http_proxy_headers_in_t *h;
511
512 switch (p->upstream->status) {
513 case NGX_HTTP_OK:
514 case NGX_HTTP_MOVED_PERMANENTLY:
515 case NGX_HTTP_MOVED_TEMPORARILY:
516 break;
517
518 #if 0
519 case NGX_HTTP_NOT_MODIFIED:
520 return 1;
521 #endif
522
523 default:
524 return 0;
525 }
526
527 h = &p->upstream->headers_in;
528
529 date = NGX_ERROR;
530 if (h->date) {
531 date = ngx_http_parse_time(h->date->value.data, h->date->value.len);
532 }
533 if (date == NGX_ERROR) {
534 date = ngx_time();
535 }
536 p->cache->ctx.date = date;
537
538 last_modified = NGX_ERROR;
539 if (h->last_modified) {
540 last_modified = ngx_http_parse_time(h->last_modified->value.data,
541 h->last_modified->value.len);
542 p->cache->ctx.last_modified = last_modified;
543 }
544
545 if (h->x_accel_expires) {
546 expires = ngx_atoi(h->x_accel_expires->value.data,
547 h->x_accel_expires->value.len);
548 if (expires != NGX_ERROR) {
549 p->state->reason = NGX_HTTP_PROXY_CACHE_XAE;
550 p->state->expires = expires;
551 p->cache->ctx.expires = date + expires;
552 return (expires > 0);
553 }
554 }
555
556 if (!p->lcf->ignore_expires) {
557
558 /* TODO: Cache-Control: no-cache, max-age= */
559
560 if (h->expires) {
561 expires = ngx_http_parse_time(h->expires->value.data,
562 h->expires->value.len);
563 if (expires != NGX_ERROR) {
564 p->state->reason = NGX_HTTP_PROXY_CACHE_EXP;
565 p->state->expires = expires - date;
566 p->cache->ctx.expires = expires;
567 return (date < expires);
568 }
569 }
570 }
571
572 if (p->upstream->status == NGX_HTTP_MOVED_PERMANENTLY) {
573 p->state->reason = NGX_HTTP_PROXY_CACHE_MVD;
574 p->state->expires = /* STUB: 1 hour */ 60 * 60;
575 p->cache->ctx.expires = /* STUB: 1 hour */ 60 * 60;
576 return 1;
577 }
578
579 if (p->upstream->status == NGX_HTTP_MOVED_TEMPORARILY) {
580 return 1;
581 }
582
583 if (last_modified != NGX_ERROR && p->lcf->lm_factor > 0) {
584
585 /* FIXME: time_t == int_64_t, we can use fpu */
586
587 p->state->reason = NGX_HTTP_PROXY_CACHE_LMF;
588 t = (time_t)
589 ((((int64_t) (date - last_modified)) * p->lcf->lm_factor) / 100);
590 p->state->expires = t;
591 p->cache->ctx.expires = ngx_time() + t;
592 return 1;
593 }
594
595 if (p->lcf->default_expires > 0) {
596 p->state->reason = NGX_HTTP_PROXY_CACHE_PDE;
597 p->state->expires = p->lcf->default_expires;
598 p->cache->ctx.expires = ngx_time() + p->lcf->default_expires;
599 return 1;
600 }
601
602 return 0;
603 }
604
605
606 int ngx_http_proxy_update_cache(ngx_http_proxy_ctx_t *p)
607 {
608 ngx_event_pipe_t *ep;
609
610 if (p->cache == NULL) {
611 return NGX_OK;
612 }
613
614 ep = p->upstream->event_pipe;
615
616 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, p->request->connection->log, 0,
617 "http cache update len: " OFF_T_FMT ":" OFF_T_FMT,
618 p->cache->ctx.length, ep->read_length);
619
620 if (p->cache->ctx.length == -1) {
621 /* TODO: test rc */
622 ngx_write_file(&ep->temp_file->file,
623 (char *) &ep->read_length, sizeof(off_t),
624 offsetof(ngx_http_cache_header_t, length));
625 }
626
627 return ngx_http_cache_update_file(p->request, &p->cache->ctx,
628 &ep->temp_file->file.name);
629 }