error_page指令解释

nginx指令error_page的作用是当发生错误的时候能够显示一个预定义的uri,比如:

error_page 502 503     /50x.html;

这样实际上产生了一个内部跳转(internal redirect),当访问出现502、503的时候就能返回50x.html中的内容。

同时我们也可以自己定义这种情况下的返回状态吗,比如:

error_page 502 503 =200 /50x.html;

这样用户访问产生502 、503的时候给用户的返回状态是200,内容是50x.html。

当error_page后面跟的不是一个静态的内容的话,比如是由proxyed server或者FastCGI/uwsgi/SCGI server处理的话,server返回的状态(200, 302, 401 或者 404)也能返回给用户。

error_page 404 = /404.php;

也可以设置一个named location,然后在里边做对应的处理。

500 502 503 504 @jump_to_error;
location @jump_to_error {    
    ...
}

同时也能够通过使客户端进行302、301等重定向的方式处理错误页面,默认状态码为302。

error_page 403      http://example.com/forbidden.html;
error_page 404 =301 http://example.com/notfound.html;

同时error_page在一次请求中只能响应一次,对应的nginx有另外一个配置可以控制这个选项:recursive_error_pages
默认为false,作用是控制error_page能否在一次请求中触发多次。

使用实例

使用error_page进行web站点降级服务

下面介绍一种我们这边经常使用的利用error_page来进行降级的一个方案。
对于基于nginx的web站点来说,如果用户访问量过大可能好导致nginx返回50x错误,这样用户就会看到错误页面,非常不友好,如果是一个偏静态的站点,当出现这种错误的时候如果将静态化的页面返回给用户也是一种在极端情况下比较合适的降级方案。
我们定期的去curl我们的页面,生成静态化的html页面,然后当发生错误的时候使用nginx lua去获取静态页面返回给用户。

示例配置如下:

server {
    ...
    location @jump_to_error {
        lua_code_cache on;
        content_by_lua_file /project_home/lua/error.lua;
    }
    error_page 500 502 503 504 @jump_to_error;
}

使用error_page时候的gzip问题

我们观察到当经过error_page处理之后,返回给用户的状态码是对应的5xx,这时页面是不会被gzip的,而将对应配置改成:

error_page 500 502 503 504 =200 @jump_to_error;

这样页面就会被gzip,但是由于这样在access log中会看到对应返回码是200而不是本来的5xx,不利于我们查找错误,在这样错误的情况下定位问题更加重要,而没有gzip不是有非常大的影响,所以最后我们衡量没有采用这种方式,保持原来方式不变。
最后去调研了下为什么nginx会这样处理,看到nginx的源码中有相应的处理,代码如下:
nginx/src/http/modules/ngx_http_gzip_filter_module.c

static ngx_int_t
ngx_http_gzip_header_filter(ngx_http_request_t *r) 
{
    ... 
    conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
    if (!conf->enable
        || (r->headers_out.status != NGX_HTTP_OK
        && r->headers_out.status != NGX_HTTP_FORBIDDEN
        && r->headers_out.status != NGX_HTTP_NOT_FOUND)
        || (r->headers_out.content_encoding
        && r->headers_out.content_encoding->value.len)
        || (r->headers_out.content_length_n != -1
        && r->headers_out.content_length_n < conf->min_length)
        || ngx_http_test_content_type(r, &conf->types) == NULL
        || r->header_only)
    {   
        return ngx_http_next_header_filter(r);
    }   
    ... 
}

可以看到代码里面直接判断了一下返回的header里面的status是不是200、403或者404,如果不是的话就跳过gzip filter这个模块。

为什么nginx要这么处理呢,去挖了一下,在nginx的mailing list看到有人提问,nginx开发组给出了一些解答,举了一些栗子:比如当由于服务器原因导致内存分配异常导致返回给用户500的时候再分配内存进行gzip显然不合适,感兴趣同学可以去详细看下