nginx服务器屏蔽上游错误码
平时的开发工作中,有时会遇到脚本权限不对导致403,文件被删除导致404,甚至后端业务异常导致5xx等情况,其实我们可以在服务器加上判断,检测当后端服务出现异常的时候前端返回一个指定的静态文件(也可以是一个动态资源)。
这样可以为一些关键业务(html或者动态资源,js等)配置此功能,当后端关键业务出现错误时,也会把一个指定的正确资源返回给用户。
想法自然是在nginx向客户端输出响应头时捕获信息,查看是不是异常,异常则跳转到指定文件
看了下nginx的第三方模块都没有实现这个功能,那还是自己来造轮子吧,最直接的开发方法是用openresty或者nginx+lua,nginx-lua提供了header_filter_by_lua挂载入口,可惜尝试下来却不行,仔细查看官方文档才发现这个入口里面禁用了一些关键IO操作的api。
Uses Lua code specified in <lua-script-str> to define an output header filter. Note that the following API functions are currently disabled within this context: Output API functions (e.g., ngx.say and ngx.send_headers) Control API functions (e.g., ngx.exit and ngx.exec) Subrequest API functions (e.g., ngx.location.capture and ngx.location.capture_multi) Cosocket API functions (e.g., ngx.socket.tcp and ngx.req.socket). Here is an example of overriding a response header (or adding one if absent) in
只能做nginx模块开发来实现了。折腾了3个晚上,艰苦的调试过程就不提了,直接贴代码吧。
nginx配置文件如下:
location ~ \.php($|/) { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; up_error_code 403 404 500 503 504 505; up_error_go /ok.html; } location / { root html; index index.html index.htm; up_error_code 502; up_error_go /ok.html; }
其中:
up_error_code是需要屏蔽的后段错误码类型,支持多个;
up_error_go是需要跳转到的资源位置
(502错误需要在location /里面才能捕获到,这个非常奇怪,原因还不清楚,所以除了要在proxy的location里面设置其他错误过滤外也要在根目录下过滤502错误才行。)
代码(ngx_http_upstream_error_go_module.c)实现较为简单,具体如下:
/* * Copyright (C) Ciaos */ #include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> typedef struct { ngx_array_t *error_codes; ngx_str_t file_path; } ngx_http_upstream_error_go_conf_t; static void *ngx_http_upstream_error_go_create_conf(ngx_conf_t *cf); static char *ngx_http_upstream_error_go_merge_conf(ngx_conf_t *cf, void *parent,void *child); static char *ngx_http_upstream_error_go_set_code(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_upstream_error_go_set_file_path(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_upstream_error_go_init(ngx_conf_t *cf); static ngx_command_t ngx_http_upstream_error_go_commands[] = { { ngx_string("up_error_code"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, ngx_http_upstream_error_go_set_code, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("up_error_go"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_upstream_error_go_set_file_path, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_upstream_error_go_module_ctx = { NULL, /* preconfiguration */ ngx_http_upstream_error_go_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_upstream_error_go_create_conf, /* create location configration */ ngx_http_upstream_error_go_merge_conf /* merge location configration */ }; ngx_module_t ngx_http_upstream_error_go_module = { NGX_MODULE_V1, &ngx_http_upstream_error_go_module_ctx, /* module context */ ngx_http_upstream_error_go_commands, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; static ngx_int_t ngx_http_upstream_error_go_header_filter(ngx_http_request_t *r) { ngx_uint_t e; ngx_uint_t *ecode; ngx_http_upstream_error_go_conf_t *clcf; clcf = ngx_http_get_module_loc_conf(r, ngx_http_upstream_error_go_module); if(clcf == NULL) { return NGX_ERROR; } if(clcf->error_codes){ ecode = clcf->error_codes->elts; for(e = 0; e < clcf->error_codes->nelts; e ++){ if(r->headers_out.status == *(ecode+e)){ r->err_status = 0; ngx_str_set(&r->headers_out.status_line, "200 OK"); ngx_http_internal_redirect(r, &clcf->file_path, &r->args); return NGX_ERROR; } } } return ngx_http_next_header_filter(r); } static ngx_int_t ngx_http_upstream_error_go_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { return ngx_http_next_body_filter(r, in); } static char * ngx_http_upstream_error_go_set_code(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_upstream_error_go_conf_t *clcf = conf; ngx_str_t *value; ngx_uint_t i; ngx_uint_t *ecode; if(clcf->error_codes == NGX_CONF_UNSET_PTR) { clcf->error_codes = ngx_array_create(cf->pool, 4, sizeof(ngx_uint_t)); if(clcf->error_codes == NULL) { return NGX_CONF_ERROR; } } value = cf->args->elts; for(i=1; i< cf->args->nelts; i++){ ecode = ngx_array_push(clcf->error_codes); if(ecode == NULL) { return NGX_CONF_ERROR; } *ecode = ngx_atoi(value[i].data, value[i].len); } return NGX_CONF_OK; } static char * ngx_http_upstream_error_go_set_file_path(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_upstream_error_go_conf_t *clcf = conf; ngx_str_t *value; value = cf->args->elts; clcf->file_path = value[1]; return NGX_CONF_OK; } static void * ngx_http_upstream_error_go_create_conf(ngx_conf_t *cf) { ngx_http_upstream_error_go_conf_t *clcf; clcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_error_go_conf_t)); if (clcf == NULL) { return NULL; } clcf->error_codes = NGX_CONF_UNSET_PTR; return clcf; } static char * ngx_http_upstream_error_go_merge_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_upstream_error_go_conf_t *prev = parent; ngx_http_upstream_error_go_conf_t *clcf = child; ngx_conf_merge_str_value(clcf->file_path, prev->file_path, ""); ngx_conf_merge_ptr_value(clcf->error_codes, prev->error_codes, NULL); return NGX_CONF_OK; } static ngx_int_t ngx_http_upstream_error_go_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_upstream_error_go_header_filter; ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_upstream_error_go_body_filter; return NGX_OK; }
配置如下(config):
ngx_addon_name=ngx_http_upstream_error_go_module HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_upstream_error_go_module" NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_upstream_error_go_module.c"
将源代码和配置文件放到某个目录下,编译nginx(版本1.7.4测试通过)带上此模块即可,测试工具可用Test::Nginx(perl -t t/test.t或prove -r t),测试代码如下
use Test::Nginx::Socket; repeat_each(1); plan tests => 1 * repeat_each() * blocks(); our $config = <<"_EOC_"; location /foo { fastcgi_pass 127.0.0.1; up_error_code 403 404 502 500 503 504 505; up_error_go /index.html; } _EOC_ run_tests(); __DATA__ === TEST 1: upstream 403 --- http_config eval " server{ listen 127.0.0.1:80; location /foo { return 403; } } " --- config eval: $::config --- request GET /foo --- error_code: 200 === TEST 2: upstream 404 --- http_config eval " server{ listen 127.0.0.1:80; location /foo { return 404; } } " --- config eval: $::config --- request GET /foo --- error_code: 200 === TEST 3: upstream 502 --- http_config --- config eval: $::config --- request GET /foo --- error_code: 200