3 * Copyright (C) Igor Sysoev
7 #include <ngx_config.h>
13 ngx_http_cache_hash_t
*redirect_cache
;
14 } ngx_http_static_loc_conf_t
;
17 static ngx_int_t
ngx_http_static_handler(ngx_http_request_t
*r
);
18 static void *ngx_http_static_create_loc_conf(ngx_conf_t
*cf
);
19 static char *ngx_http_static_merge_loc_conf(ngx_conf_t
*cf
,
20 void *parent
, void *child
);
21 static ngx_int_t
ngx_http_static_init(ngx_cycle_t
*cycle
);
24 static ngx_command_t ngx_http_static_commands
[] = {
28 { ngx_string("redirect_cache"),
29 NGX_HTTP_MAIN_CONF
|NGX_HTTP_SRV_CONF
|NGX_HTTP_LOC_CONF
|NGX_CONF_TAKE3
,
30 ngx_http_set_cache_slot
,
31 NGX_HTTP_LOC_CONF_OFFSET
,
32 offsetof(ngx_http_static_loc_conf_t
, redirect_cache
),
42 ngx_http_module_t ngx_http_static_module_ctx
= {
45 NULL
, /* create main configuration */
46 NULL
, /* init main configuration */
48 NULL
, /* create server configuration */
49 NULL
, /* merge server configuration */
51 ngx_http_static_create_loc_conf
, /* create location configuration */
52 ngx_http_static_merge_loc_conf
/* merge location configuration */
56 ngx_module_t ngx_http_static_module
= {
58 &ngx_http_static_module_ctx
, /* module context */
59 ngx_http_static_commands
, /* module directives */
60 NGX_HTTP_MODULE
, /* module type */
61 ngx_http_static_init
, /* init module */
66 static ngx_int_t
ngx_http_static_handler(ngx_http_request_t
*r
)
72 ngx_str_t name
, location
;
78 ngx_http_cleanup_t
*file_cleanup
, *redirect_cleanup
;
79 ngx_http_core_loc_conf_t
*clcf
;
80 ngx_http_static_loc_conf_t
*slcf
;
82 uint32_t file_crc
, redirect_crc
;
83 ngx_http_cache_t
*file
, *redirect
;
86 if (r
->uri
.data
[r
->uri
.len
- 1] == '/') {
95 if (r
->method
!= NGX_HTTP_GET
&& r
->method
!= NGX_HTTP_HEAD
) {
96 return NGX_HTTP_NOT_ALLOWED
;
99 rc
= ngx_http_discard_body(r
);
101 if (rc
!= NGX_OK
&& rc
!= NGX_AGAIN
) {
108 * there is a valid cached open file, i.e by the index handler,
109 * and it should be already registered in r->cleanup
112 if (r
->cache
&& !r
->cache
->expired
) {
113 return ngx_http_send_cached(r
);
118 log
= r
->connection
->log
;
120 clcf
= ngx_http_get_module_loc_conf(r
, ngx_http_core_module
);
123 * make a file name, reserve 2 bytes for a trailing '/'
124 * in a possible redirect and for the last '\0'
128 name
.data
= ngx_palloc(r
->pool
, clcf
->root
.len
+ r
->uri
.len
+ 2
130 if (name
.data
== NULL
) {
131 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
134 last
= ngx_cpymem(name
.data
, clcf
->root
.data
, clcf
->root
.len
);
135 last
= ngx_cpystrn(last
, r
->uri
.data
+ clcf
->name
.len
,
136 r
->uri
.len
+ 1 - clcf
->name
.len
);
138 name
.len
= last
- name
.data
;
140 location
.data
= ngx_palloc(r
->pool
, r
->uri
.len
+ 2);
141 if (location
.data
== NULL
) {
142 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
145 last
= ngx_cpystrn(location
.data
, r
->uri
.data
, r
->uri
.len
+ 1);
149 * aliases usually have trailling "/",
150 * set it in the start of the possible redirect
153 if (*location
.data
!= '/') {
158 location
.len
= last
- location
.data
+ 1;
161 name
.data
= ngx_palloc(r
->pool
, clcf
->root
.len
+ r
->uri
.len
+ 2);
162 if (name
.data
== NULL
) {
163 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
166 location
.data
= ngx_cpymem(name
.data
, clcf
->root
.data
, clcf
->root
.len
);
167 last
= ngx_cpystrn(location
.data
, r
->uri
.data
, r
->uri
.len
+ 1);
169 name
.len
= last
- name
.data
;
170 location
.len
= last
- location
.data
+ 1;
173 ngx_log_debug1(NGX_LOG_DEBUG_HTTP
, log
, 0,
174 "http filename: \"%s\"", name
.data
);
177 /* allocate cleanups */
179 if (!(file_cleanup
= ngx_push_array(&r
->cleanup
))) {
180 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
182 file_cleanup
->valid
= 0;
184 slcf
= ngx_http_get_module_loc_conf(r
, ngx_http_static_module
);
185 if (slcf
->redirect_cache
) {
186 if (!(redirect_cleanup
= ngx_push_array(&r
->cleanup
))) {
187 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
189 redirect_cleanup
->valid
= 0;
192 redirect_cleanup
= NULL
;
197 /* look up an open files cache */
199 if (clcf
->open_files
) {
200 file
= ngx_http_cache_get(clcf
->open_files
, file_cleanup
,
203 ngx_log_debug1(NGX_LOG_DEBUG_HTTP
, log
, 0,
204 "http open file cache get: %p", file
);
206 if (file
&& !file
->expired
) {
208 return ngx_http_send_cached(r
);
216 /* look up an redirect cache */
218 if (slcf
->redirect_cache
) {
219 redirect
= ngx_http_cache_get(slcf
->redirect_cache
, redirect_cleanup
,
220 &name
, &redirect_crc
);
222 ngx_log_debug1(NGX_LOG_DEBUG_HTTP
, log
, 0,
223 "http redirect cache get: %p", redirect
);
225 if (redirect
&& !redirect
->expired
) {
228 * We do not copy a cached value so the cache entry is locked
229 * until the end of the request. In a single threaded model
230 * the redirected request should complete before other event
231 * will be processed. In a multithreaded model this locking
232 * should keep more popular redirects in cache.
235 if (!(r
->headers_out
.location
=
236 ngx_http_add_header(&r
->headers_out
, ngx_http_headers_out
)))
238 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
241 r
->headers_out
.location
->value
= redirect
->data
.value
;
243 return NGX_HTTP_MOVED_PERMANENTLY
;
256 /* TODO: redirect cache */
258 if (ngx_win32_version
< NGX_WIN_NT
) {
261 * there is no way to open a file or a directory in Win9X with
262 * one syscall because Win9X has no FILE_FLAG_BACKUP_SEMANTICS flag
263 * so we need to check its type before the opening
266 if (ngx_file_info(name
.data
, &fi
) == NGX_FILE_ERROR
) {
268 ngx_log_error(NGX_LOG_ERR
, log
, err
,
269 ngx_file_info_n
" \"%s\" failed", name
.data
);
271 if (err
== NGX_ENOENT
|| err
== NGX_ENOTDIR
) {
272 return NGX_HTTP_NOT_FOUND
;
274 } else if (err
== NGX_EACCES
) {
275 return NGX_HTTP_FORBIDDEN
;
278 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
282 if (ngx_is_dir(&fi
)) {
283 ngx_log_debug1(NGX_LOG_DEBUG_HTTP
, log
, 0,
284 "HTTP DIR: \"%s\"", name
.data
);
286 if (!(r
->headers_out
.location
=
287 ngx_http_add_header(&r
->headers_out
, ngx_http_headers_out
)))
289 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
294 r
->headers_out
.location
->value
.len
= last
- location
;
295 r
->headers_out
.location
->value
.data
= location
;
297 return NGX_HTTP_MOVED_PERMANENTLY
;
304 fd
= ngx_open_file(name
.data
, NGX_FILE_RDONLY
, NGX_FILE_OPEN
);
306 if (fd
== NGX_INVALID_FILE
) {
309 if (err
== NGX_ENOENT
|| err
== NGX_ENOTDIR
) {
311 rc
= NGX_HTTP_NOT_FOUND
;
313 } else if (err
== NGX_EACCES
) {
315 rc
= NGX_HTTP_FORBIDDEN
;
318 level
= NGX_LOG_CRIT
;
319 rc
= NGX_HTTP_INTERNAL_SERVER_ERROR
;
322 ngx_log_error(level
, log
, err
,
323 ngx_open_file_n
" \"%s\" failed", name
.data
);
328 ngx_log_debug1(NGX_LOG_DEBUG_HTTP
, log
, 0, "http static fd: %d", fd
);
330 if (ngx_fd_info(fd
, &fi
) == NGX_FILE_ERROR
) {
331 ngx_log_error(NGX_LOG_CRIT
, log
, ngx_errno
,
332 ngx_fd_info_n
" \"%s\" failed", name
.data
);
334 if (ngx_close_file(fd
) == NGX_FILE_ERROR
) {
335 ngx_log_error(NGX_LOG_ALERT
, log
, ngx_errno
,
336 ngx_close_file_n
" \"%s\" failed", name
.data
);
339 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
342 if (ngx_is_dir(&fi
)) {
344 ngx_log_debug0(NGX_LOG_DEBUG_HTTP
, log
, 0, "http dir");
346 if (ngx_close_file(fd
) == NGX_FILE_ERROR
) {
347 ngx_log_error(NGX_LOG_ALERT
, log
, ngx_errno
,
348 ngx_close_file_n
" \"%s\" failed", name
.data
);
354 r
->headers_out
.location
= ngx_list_push(&r
->headers_out
.headers
);
355 if (r
->headers_out
.location
== NULL
) {
356 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
359 r
->headers_out
.location
->value
= location
;
363 if (slcf
->redirect_cache
) {
365 if (location
.len
== redirect
->data
.value
.len
366 && ngx_memcmp(redirect
->data
.value
.data
, location
.data
,
369 redirect
->accessed
= ngx_cached_time
;
370 redirect
->updated
= ngx_cached_time
;
373 * we can unlock the cache entry because
374 * we have the local copy anyway
377 ngx_http_cache_unlock(slcf
->redirect_cache
, redirect
, log
);
378 redirect_cleanup
->valid
= 0;
380 return NGX_HTTP_MOVED_PERMANENTLY
;
385 redirect
= ngx_http_cache_alloc(slcf
->redirect_cache
, redirect
,
391 ngx_log_debug1(NGX_LOG_DEBUG_HTTP
, log
, 0,
392 "http redirect cache alloc: %p", redirect
);
395 redirect
->fd
= NGX_INVALID_FILE
;
396 redirect
->accessed
= ngx_cached_time
;
397 redirect
->last_modified
= 0;
398 redirect
->updated
= ngx_cached_time
;
399 redirect
->memory
= 1;
400 ngx_http_cache_unlock(slcf
->redirect_cache
, redirect
, log
);
401 redirect_cleanup
->valid
= 0;
408 return NGX_HTTP_MOVED_PERMANENTLY
;
411 #if !(NGX_WIN32) /* the not regular files are probably Unix specific */
413 if (!ngx_is_file(&fi
)) {
414 ngx_log_error(NGX_LOG_CRIT
, log
, ngx_errno
,
415 "\"%s\" is not a regular file", name
.data
);
417 if (ngx_close_file(fd
) == NGX_FILE_ERROR
) {
418 ngx_log_error(NGX_LOG_ALERT
, log
, ngx_errno
,
419 ngx_close_file_n
" \"%s\" failed", name
.data
);
422 return NGX_HTTP_NOT_FOUND
;
430 if (clcf
->open_files
) {
432 #if (NGX_USE_HTTP_FILE_CACHE_UNIQ)
434 if (file
&& file
->uniq
== ngx_file_uniq(&fi
)) {
435 if (ngx_close_file(fd
) == NGX_FILE_ERROR
) {
436 ngx_log_error(NGX_LOG_ALERT
, log
, ngx_errno
,
437 ngx_close_file_n
" \"%s\" failed", name
.data
);
439 file
->accessed
= ngx_cached_time
;
440 file
->updated
= ngx_cached_time
;
444 return ngx_http_send_cached(r
);
448 ngx_http_cache_unlock(clcf
->open_files
, file
, log
);
452 file
= ngx_http_cache_alloc(clcf
->open_files
, file
,
454 &name
, file_crc
, NULL
, log
);
456 file
->uniq
= ngx_file_uniq(&fi
);
461 file
= ngx_http_cache_alloc(clcf
->open_files
, file
,
463 &name
, file_crc
, NULL
, log
);
466 ngx_log_debug1(NGX_LOG_DEBUG_HTTP
, log
, 0,
467 "http open file cache alloc: %p", file
);
471 file
->data
.size
= ngx_file_size(&fi
);
472 file
->accessed
= ngx_cached_time
;
473 file
->last_modified
= ngx_file_mtime(&fi
);
474 file
->updated
= ngx_cached_time
;
478 return ngx_http_send_cached(r
);
483 log
->action
= "sending response to client";
485 file_cleanup
->data
.file
.fd
= fd
;
486 file_cleanup
->data
.file
.name
= name
.data
;
487 file_cleanup
->valid
= 1;
488 file_cleanup
->cache
= 0;
490 r
->headers_out
.status
= NGX_HTTP_OK
;
491 r
->headers_out
.content_length_n
= ngx_file_size(&fi
);
492 r
->headers_out
.last_modified_time
= ngx_file_mtime(&fi
);
494 if (r
->headers_out
.content_length_n
== 0) {
498 if (ngx_http_set_content_type(r
) != NGX_OK
) {
499 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
502 #if (NGX_SUPPRESS_WARN)
506 if (!r
->header_only
) {
507 /* we need to allocate all before the header would be sent */
509 if (!(b
= ngx_pcalloc(r
->pool
, sizeof(ngx_buf_t
)))) {
510 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
513 if (!(b
->file
= ngx_pcalloc(r
->pool
, sizeof(ngx_file_t
)))) {
514 return NGX_HTTP_INTERNAL_SERVER_ERROR
;
517 r
->filter_allow_ranges
= 1;
520 rc
= ngx_http_send_header(r
);
522 if (rc
== NGX_ERROR
|| rc
> NGX_OK
|| r
->header_only
) {
533 b
->file_last
= ngx_file_size(&fi
);
536 b
->file
->name
= name
;
542 return ngx_http_output_filter(r
, &out
);
546 static void *ngx_http_static_create_loc_conf(ngx_conf_t
*cf
)
548 ngx_http_static_loc_conf_t
*conf
;
550 if (!(conf
= ngx_palloc(cf
->pool
, sizeof(ngx_http_static_loc_conf_t
)))) {
551 return NGX_CONF_ERROR
;
554 conf
->redirect_cache
= NULL
;
560 static char *ngx_http_static_merge_loc_conf(ngx_conf_t
*cf
,
561 void *parent
, void *child
)
563 ngx_http_static_loc_conf_t
*prev
= parent
;
564 ngx_http_static_loc_conf_t
*conf
= child
;
566 if (conf
->redirect_cache
== NULL
) {
567 conf
->redirect_cache
= prev
->redirect_cache
;
574 static ngx_int_t
ngx_http_static_init(ngx_cycle_t
*cycle
)
576 ngx_http_handler_pt
*h
;
577 ngx_http_core_main_conf_t
*cmcf
;
579 cmcf
= ngx_http_cycle_get_module_main_conf(cycle
, ngx_http_core_module
);
581 h
= ngx_push_array(&cmcf
->phases
[NGX_HTTP_CONTENT_PHASE
].handlers
);
586 *h
= ngx_http_static_handler
;