static ngx_int_t ngx_http_breach_header_filter(ngx_http_request_t *r)
{
	ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,"BREACH HEADER FILTER Start!.");
	
	ngx_http_breach_conf_t  *conf;

	conf = ngx_http_get_module_loc_conf (r, ngx_http_breach_filter_module);

	if ((r->headers_out.status != NGX_HTTP_OK &&
		r->headers_out.status != NGX_HTTP_FORBIDDEN &&
		r->headers_out.status != NGX_HTTP_NOT_FOUND) ||
		r->header_only ||
		r->headers_out.content_type.len == 0 ||
		(r->headers_out.content_encoding &&
		r->headers_out.content_encoding->value.len) ||
		conf->enable == 0)
	{
			return ngx_http_next_header_filter(r);
	}

	if (ngx_strncasecmp(r->headers_out.content_type.data, (u_char *)"text/html", sizeof("text/html") - 1) != 0)
	{
		ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,"BREACH HEADER FILTER: this is not text/html content!.");
		return ngx_http_next_header_filter(r);
	}


	ngx_http_clear_content_length(r);
	ngx_http_clear_accept_ranges(r);

	ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,"BREACH HEADER FILTER: end!.");
	
	return ngx_http_next_header_filter(r);
}
ngx_int_t
ngx_http_echo_send_header_if_needed(ngx_http_request_t *r,
    ngx_http_echo_ctx_t *ctx)
{
    ngx_int_t                    rc;
    ngx_http_echo_loc_conf_t    *elcf;

    if (!r->header_sent && !ctx->header_sent) {
        elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);

        r->headers_out.status = (ngx_uint_t) elcf->status;

        if (ngx_http_set_content_type(r) != NGX_OK) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        ngx_http_clear_content_length(r);
        ngx_http_clear_accept_ranges(r);

        rc = ngx_http_send_header(r);
        ctx->header_sent = 1;
        return rc;
    }

    return NGX_OK;
}
static ngx_int_t ngx_http_dynamic_etags_header_filter(ngx_http_request_t *r) {

    ngx_http_dynamic_etags_module_ctx_t       *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_dynamic_etags_module);

    if (ctx) {
        return ngx_http_next_header_filter(r);
    }

    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_dynamic_etags_module_ctx_t));
    if (ctx == NULL) {
        return NGX_ERROR;
    }

    ngx_http_set_ctx(r, ctx, ngx_http_dynamic_etags_module);

    ngx_http_clear_content_length(r);
    ngx_http_clear_accept_ranges(r);

    r->main_filter_need_in_memory = 1;
    r->filter_need_in_memory = 1;

    return NGX_OK;
}
static ngx_int_t
ngx_http_addition_header_filter(ngx_http_request_t *r)
{
    ngx_http_addition_ctx_t   *ctx;
    ngx_http_addition_conf_t  *conf;

    if (r->headers_out.status != NGX_HTTP_OK || r != r->main) {
        return ngx_http_next_header_filter(r);
    }

    conf = ngx_http_get_module_loc_conf(r, ngx_http_addition_filter_module);

    if (conf->before_body.len == 0 && conf->after_body.len == 0) {
        return ngx_http_next_header_filter(r);
    }

    if (ngx_http_test_content_type(r, &conf->types) == NULL) {
        return ngx_http_next_header_filter(r);
    }

    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_addition_ctx_t));
    if (ctx == NULL) {
        return NGX_ERROR;
    }

    ngx_http_set_ctx(r, ctx, ngx_http_addition_filter_module);

    ngx_http_clear_content_length(r);
    ngx_http_clear_accept_ranges(r);

    return ngx_http_next_header_filter(r);
}
static ngx_int_t
ngx_http_foo_header_filter(ngx_http_request_t *r)
{
    ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, "Header filter add header");
    ngx_table_elt_t  *h;

    /*
     * The filter handler adds "X-Foo: foo" header
     * to every HTTP 200 response
     */

    if (r->headers_out.status != NGX_HTTP_OK) {
        return ngx_http_next_header_filter(r);
    }

    h = ngx_list_push(&r->headers_out.headers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    h->hash = 1;
    ngx_str_set(&h->key, "X-Foo");
    ngx_str_set(&h->value, "foo");

    ngx_http_clear_content_length(r);
    ngx_http_clear_accept_ranges(r);
    ngx_http_weak_etag(r);

    return ngx_http_next_header_filter(r);
}
ngx_int_t
ngx_http_lua_send_header_if_needed(ngx_http_request_t *r,
        ngx_http_lua_ctx_t *ctx)
{
    if ( ! ctx->headers_sent ) {
        if (r->headers_out.status == 0) {
            r->headers_out.status = NGX_HTTP_OK;
        }

        if (! ctx->headers_set && ngx_http_set_content_type(r) != NGX_OK) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        if (! ctx->headers_set) {
            ngx_http_clear_content_length(r);
            ngx_http_clear_accept_ranges(r);
        }

        if (r->http_version >= NGX_HTTP_VERSION_11) {
            /* Send response headers for HTTP version <= 1.0 elsewhere */
            ctx->headers_sent = 1;
            return ngx_http_send_header(r);
        }
    }

    return NGX_OK;
}
static ngx_int_t
ngx_http_gunzip_header_filter(ngx_http_request_t *r)
{
    ngx_http_gunzip_ctx_t   *ctx;
    ngx_http_gunzip_conf_t  *conf;

    conf = ngx_http_get_module_loc_conf(r, ngx_http_gunzip_filter_module);

    /* TODO support multiple content-codings */
    /* TODO ignore content encoding? */

    if (conf->enable == NGX_HTTP_GUNZIP_OFF
        || r->headers_out.content_encoding == NULL
        || r->headers_out.content_encoding->value.len != 4
        || ngx_strncasecmp(r->headers_out.content_encoding->value.data,
                           (u_char *) "gzip", 4) != 0)
    {
        return ngx_http_next_header_filter(r);
    }

    r->gzip_vary = 1;

    if (conf->enable == NGX_HTTP_GUNZIP_ON) {
        if (!r->gzip_tested) {
            if (ngx_http_gzip_ok(r) == NGX_OK) {
                return ngx_http_next_header_filter(r);
            }

        } else if (r->gzip_ok) {
                   return ngx_http_next_header_filter(r);
        }

    } else if (conf->enable == NGX_HTTP_GUNZIP_ALWAYS
               && ngx_http_test_content_type(r, &conf->types) == NULL)
    {
               return ngx_http_next_header_filter(r);
    }

    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_gunzip_ctx_t));
    if (ctx == NULL) {
        return NGX_ERROR;
    }

    ngx_http_set_ctx(r, ctx, ngx_http_gunzip_filter_module);

    ctx->request = r;

    r->filter_need_in_memory = 1;

    r->headers_out.content_encoding->hash = 0;
    r->headers_out.content_encoding = NULL;

    ngx_http_clear_content_length(r);
    ngx_http_clear_accept_ranges(r);
    ngx_http_weak_etag(r);

    return ngx_http_next_header_filter(r);
}
static ngx_int_t
ngx_http_not_modified_header_filter(ngx_http_request_t *r)
{
    if (r->headers_out.status != NGX_HTTP_OK
        || r != r->main
        || r->disable_not_modified)
    {
        return ngx_http_next_header_filter(r);
    }

    if (r->headers_in.if_unmodified_since
        && !ngx_http_test_if_unmodified(r))
    {
        return ngx_http_filter_finalize_request(r, NULL,
                                                NGX_HTTP_PRECONDITION_FAILED);
    }

    if (r->headers_in.if_match
        && !ngx_http_test_if_match(r, r->headers_in.if_match, 0))
    {
        return ngx_http_filter_finalize_request(r, NULL,
                                                NGX_HTTP_PRECONDITION_FAILED);
    }

    if (r->headers_in.if_modified_since || r->headers_in.if_none_match) {

        if (r->headers_in.if_modified_since
            && ngx_http_test_if_modified(r))
        {
            return ngx_http_next_header_filter(r);
        }

        if (r->headers_in.if_none_match
            && !ngx_http_test_if_match(r, r->headers_in.if_none_match, 1))
        {
            return ngx_http_next_header_filter(r);
        }

        /* not modified */

        r->headers_out.status = NGX_HTTP_NOT_MODIFIED;
        r->headers_out.status_line.len = 0;
        r->headers_out.content_type.len = 0;
        ngx_http_clear_content_length(r);
        ngx_http_clear_accept_ranges(r);

        if (r->headers_out.content_encoding) {
            r->headers_out.content_encoding->hash = 0;
            r->headers_out.content_encoding = NULL;
        }

        return ngx_http_next_header_filter(r);
    }

    return ngx_http_next_header_filter(r);
}
static ngx_int_t
ngx_http_trim_header_filter(ngx_http_request_t *r)
{
    ngx_int_t                  rc;
    ngx_str_t                  flag;
    ngx_http_trim_ctx_t       *ctx;
    ngx_http_trim_loc_conf_t  *conf;

    conf = ngx_http_get_module_loc_conf(r, ngx_http_trim_filter_module);

    if (!conf->trim_enable
        || r->headers_out.status != NGX_HTTP_OK
        || (r->method & NGX_HTTP_HEAD)
        || r->headers_out.content_length_n == 0
        || (r->headers_out.content_encoding
            && r->headers_out.content_encoding->value.len)
        || ngx_http_test_content_type(r, &conf->types) == NULL)
    {
        return ngx_http_next_header_filter(r);
    }

    rc = ngx_http_arg(r, (u_char *) NGX_HTTP_TRIM_FLAG,
                      sizeof(NGX_HTTP_TRIM_FLAG) - 1, &flag);

    if(rc == NGX_OK
       && flag.len == sizeof("off") - 1
       && ngx_strncmp(flag.data, "off", sizeof("off") - 1) == 0)
    {
        return ngx_http_next_header_filter(r);
    }

    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_trim_ctx_t));
    if (ctx == NULL) {
        return NGX_ERROR;
    }

    ctx->prev = ' ';
    ctx->first_line = 1;

    ngx_http_set_ctx(r, ctx, ngx_http_trim_filter_module);

    r->main_filter_need_in_memory = 1;

    ngx_http_clear_content_length(r);
    ngx_http_clear_accept_ranges(r);

    return ngx_http_next_header_filter(r);
}
static ngx_int_t
ngx_http_not_modified_header_filter(ngx_http_request_t *r)
{
    time_t                     ims;
    ngx_http_core_loc_conf_t  *clcf;

    if (r->headers_out.status != NGX_HTTP_OK
        || r != r->main
        || r->headers_in.if_modified_since == NULL
        || r->headers_out.last_modified_time == -1)
    {
        return ngx_http_next_header_filter(r);
    }

    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

    if (clcf->if_modified_since == NGX_HTTP_IMS_OFF) {
        return ngx_http_next_header_filter(r);
    }

    ims = ngx_http_parse_time(r->headers_in.if_modified_since->value.data,
                              r->headers_in.if_modified_since->value.len);

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http ims:%d lm:%d", ims, r->headers_out.last_modified_time);

    if (ims != r->headers_out.last_modified_time) {

        if (clcf->if_modified_since == NGX_HTTP_IMS_EXACT
            || ims < r->headers_out.last_modified_time)
        {
            return ngx_http_next_header_filter(r);
        }
    }

    r->headers_out.status = NGX_HTTP_NOT_MODIFIED;
    r->headers_out.status_line.len = 0;
    r->headers_out.content_type.len = 0;
    ngx_http_clear_content_length(r);
    ngx_http_clear_accept_ranges(r);

    if (r->headers_out.content_encoding) {
        r->headers_out.content_encoding->hash = 0;
        r->headers_out.content_encoding = NULL;
    }

    return ngx_http_next_header_filter(r);
}
static ngx_int_t
ngx_http_srcache_send_not_modified(ngx_http_request_t *r)
{
    r->headers_out.status = NGX_HTTP_NOT_MODIFIED;
    r->headers_out.status_line.len = 0;
    r->headers_out.content_type.len = 0;
    ngx_http_clear_content_length(r);
    ngx_http_clear_accept_ranges(r);

    if (r->headers_out.content_encoding) {
        r->headers_out.content_encoding->hash = 0;
        r->headers_out.content_encoding = NULL;
    }

    return ngx_http_srcache_next_header_filter(r);
}
static ngx_int_t
ngx_http_echo_header_filter(ngx_http_request_t *r)
{
    ngx_http_echo_loc_conf_t    *conf;
    ngx_http_echo_ctx_t         *ctx;

    dd("We're in the header filter...");

    ctx = ngx_http_get_module_ctx(r, ngx_http_echo_module);

    /* XXX we should add option to insert contents for responses
     * of non-200 status code here... */
    /*
    if (r->headers_out.status != NGX_HTTP_OK) {
        if (ctx != NULL) {
            ctx->skip_filter = 1;
        }
        return ngx_http_echo_next_header_filter(r);
    }
    */

    conf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);
    if (conf->before_body_cmds == NULL && conf->after_body_cmds == NULL) {
        if (ctx != NULL) {
            ctx->skip_filter = 1;
        }
        return ngx_http_echo_next_header_filter(r);
    }

    if (ctx == NULL) {
        ctx = ngx_http_echo_create_ctx(r);
        if (ctx == NULL) {
            return NGX_ERROR;
        }

        ctx->headers_sent = 1;

        ngx_http_set_ctx(r, ctx, ngx_http_echo_module);
    }

    /* enable streaming here (use chunked encoding) */
    ngx_http_clear_content_length(r);
    ngx_http_clear_accept_ranges(r);

    return ngx_http_echo_next_header_filter(r);
}
static ngx_int_t
ngx_http_echo_header_filter(ngx_http_request_t *r)
{
    ngx_http_echo_loc_conf_t    *conf;
    ngx_http_echo_ctx_t         *ctx;

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "echo header filter, uri \"%V?%V\"", &r->uri, &r->args);

    ctx = ngx_http_get_module_ctx(r, ngx_http_echo_module);

    /* XXX we should add option to insert contents for responses
     * of non-200 status code here... */
    /*
    if (r->headers_out.status != NGX_HTTP_OK) {
        if (ctx != NULL) {
            ctx->skip_filter = 1;
        }
        return ngx_http_echo_next_header_filter(r);
    }
    */

    conf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);
    if (conf->before_body_cmds == NULL && conf->after_body_cmds == NULL) {
        if (ctx != NULL) {
            ctx->skip_filter = 1;
        }
        return ngx_http_echo_next_header_filter(r);
    }

    if (ctx == NULL) {
        ctx = ngx_http_echo_create_ctx(r);
        if (ctx == NULL) {
            return NGX_ERROR;
        }

        ngx_http_set_ctx(r, ctx, ngx_http_echo_module);
    }

    /* enable streaming here (use chunked encoding) */
    ngx_http_clear_content_length(r);
    ngx_http_clear_accept_ranges(r);

    return ngx_http_echo_next_header_filter(r);
}
ngx_int_t
ngx_http_echo_send_header_if_needed(ngx_http_request_t* r)
{

    if (!r->header_sent) {

        r->headers_out.status = 200;

        if (ngx_http_set_content_type(r) != NGX_OK) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        ngx_http_clear_content_length(r);
        ngx_http_clear_accept_ranges(r);

        return ngx_http_send_header(r);
    }

    return NGX_OK;
}
ngx_int_t
ngx_http_secure_token_init_body_filter(ngx_http_request_t *r, ngx_str_t* token)
{
	ngx_http_secure_token_loc_conf_t *conf;
	ngx_http_secure_token_ctx_t* ctx;
	body_processor_t* processor;

	conf = ngx_http_get_module_loc_conf(r, ngx_http_secure_token_filter_module);

	// Note: content_type_lowcase is already initialized since we called ngx_http_test_content_type
	processor = ngx_hash_find(&conf->processors_hash, r->headers_out.content_type_hash, r->headers_out.content_type_lowcase, r->headers_out.content_type_len);
	if (processor == NULL)
	{
		return NGX_DONE;
	}

	// add the token to all the URLs in the response
	ctx = ngx_pcalloc(r->pool, sizeof(*ctx));
	if (ctx == NULL)
	{
		return NGX_ERROR;
	}

	ctx->token = *token;
	ctx->process = processor->process;
	ctx->processor_context_offset = processor->processor_context_offset;
	ctx->processor_params = processor->processor_params;

	ngx_http_set_ctx(r, ctx, ngx_http_secure_token_filter_module);

	ctx->last_out = &ctx->out;

	r->filter_need_in_memory = 1;

	ngx_http_clear_content_length(r);
	ngx_http_clear_accept_ranges(r);
	ngx_http_clear_etag(r);

	return NGX_OK;
}
static ngx_int_t ngx_http_no_newlines_header_filter (ngx_http_request_t *r)
{
    ngx_http_no_newlines_ctx_t   *ctx;  /* to maintain state */
    ngx_http_no_newlines_conf_t  *conf; /* to check whether module is enabled or not */

    conf = ngx_http_get_module_loc_conf (r, ngx_http_no_newlines_module);

    /* step 1: decide whether to operate */
    if ((r->headers_out.status != NGX_HTTP_OK &&
            r->headers_out.status != NGX_HTTP_FORBIDDEN &&
            r->headers_out.status != NGX_HTTP_NOT_FOUND) ||
            r->header_only ||
            r->headers_out.content_type.len == 0 ||
            (r->headers_out.content_encoding &&
             r->headers_out.content_encoding->value.len) ||
            conf->enable == 0) {
        return ngx_http_next_header_filter(r);
    }

    if (ngx_strncasecmp(r->headers_out.content_type.data, (u_char *)"text/html", sizeof("text/html") - 1) != 0) {
        return ngx_http_next_header_filter(r);
    }

    /* step 2: operate on the header */
    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_no_newlines_ctx_t));
    if (ctx == NULL) {
        return NGX_ERROR;
    }

    ngx_http_set_ctx(r, ctx, ngx_http_no_newlines_module);

    ngx_http_clear_content_length(r);
    ngx_http_clear_accept_ranges(r);
    r->main_filter_need_in_memory = 1;

    /* step 3: call the next filter */
    return ngx_http_next_header_filter(r);
}
ngx_int_t
ngx_http_echo_send_header_if_needed(ngx_http_request_t* r,
        ngx_http_echo_ctx_t *ctx) {
    /* ngx_int_t   rc; */

    if ( ! ctx->headers_sent ) {
        r->headers_out.status = NGX_HTTP_OK;

        if (ngx_http_set_content_type(r) != NGX_OK) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        ngx_http_clear_content_length(r);
        ngx_http_clear_accept_ranges(r);

        if (r->http_version >= NGX_HTTP_VERSION_11) {
            ctx->headers_sent = 1;
            return ngx_http_send_header(r);
        }
    }

    return NGX_OK;
}
static ngx_int_t ngx_http_static_etags_header_filter(ngx_http_request_t *r) {
    char                                new_string[100];
    char                                buffer[30];
    int                                 status;
    ngx_log_t                          *log;
    u_char                             *p;
    size_t                              root;
    ngx_str_t                           path;
    ngx_http_static_etags_loc_conf_t   *loc_conf;
    struct stat                         stat_result;
    log = r->connection->log;
    
    loc_conf = ngx_http_get_module_loc_conf( r, ngx_http_static_etags_module );


    if ( 1 == loc_conf->FileETag ) {
        p = ngx_http_map_uri_to_path( r, &path, &root, 0 );
        status = stat( (char *) path.data, &stat_result );
        if ( NULL == p ) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
        if ( 0 == status) {
          ngx_memzero(&new_string, 100);
          strcat(new_string, "\"");

          if (loc_conf->etag_options->MD5) {
            int       ret;
            char      md5sum[33];
            ret = ngx_http_static_etags_md5((unsigned int) stat_result.st_size, (char *) path.data, (char *) &md5sum, (ngx_log_t *) log);
            if (ret) {
              ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "MD5ERROR Return: %s", md5sum);
              return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }
              strcat(new_string, (char *) md5sum);
          }
          if (loc_conf->etag_options->Size) {
            sprintf(buffer, "%x", (unsigned int) stat_result.st_size);
            strcat(new_string, (char *) buffer);
          }
          if (loc_conf->etag_options->MTime) {
            sprintf(buffer, "%x", (unsigned int ) stat_result.st_mtime);
            strcat(new_string, (char *) buffer);
          }
          if (loc_conf->etag_options->INode) {
            sprintf(buffer, "%x", (unsigned int ) stat_result.st_ino);
            strcat(new_string, (char *) buffer);
          }

          strcat(new_string, "\"");     
          r->headers_out.etag = ngx_list_push(&r->headers_out.headers);

          if (r->headers_out.etag == NULL) {
              return NGX_ERROR;
          }

          ngx_table_elt_t *header;

          int nelts = r->headers_in.headers.part.nelts;
          header = r->headers_in.headers.part.elts;
          int i;
          for (i = 0; i < nelts; i++) {
            if (ngx_strncmp(header[i].key.data, "If-None-Match", strlen ("If-None-Match")) == 0) {
              if(ngx_strncmp(header[i].value.data, new_string, strlen (new_string)) == 0) {
                r->headers_out.status = NGX_HTTP_NOT_MODIFIED;
                r->headers_out.status_line.len = 0;
                r->headers_out.content_type.len = 0;
                ngx_http_clear_content_length(r);
                ngx_http_clear_accept_ranges(r);

                return ngx_http_next_header_filter(r);
               }
            }
          }

          r->headers_out.etag->hash = 1;
          r->headers_out.etag->key.len = sizeof("Etag") - 1;
          r->headers_out.etag->key.data = (u_char *) "Etag";
          r->headers_out.etag->value.len = strlen( new_string );
          r->headers_out.etag->value.data = (u_char *) new_string;
        }
        else {
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Could not stat(): %s", path.data);
          return NGX_HTTP_INTERNAL_SERVER_ERROR;

        }
    }

    return ngx_http_next_header_filter(r);
}
/*
{If-None-Match和ETag , If-Modified-Since和Last-Modified
    If-Modified-Since(浏览器) = Last-Modified(服务器)
    作用:浏览器端第一次访问获得服务器的Last-Modified,第2次访问把浏览器端缓存页面的最后修改时间发送到服务器去,服务器会把这
    个时间与服务器上实际文件的最后修改时间进行对比。如果时间一致,那么返回304,客户端就直接使用本地缓存文件。如果时间不一致,就
    会返回200和新的文件内容。客户端接到之后,会丢弃旧文件,把新文件缓存起来,并显示在浏览器中.


    If-None-Match(浏览器) = ETag(服务器)
    作用: If-None-Match和ETag一起工作,工作原理是在HTTP Response中添加ETag信息。 当用户再次请求该资源时,将在HTTP Request 中加入If-None-Match
    信息(ETag的值)。如果服务器验证资源的ETag没有改变(该资源没有更新),将返回一个304状态告诉客户端使用本地缓存文件。否则将返回200状态和新的资源和Etag. 
}


{
    ETags和If-None-Match是一种常用的判断资源是否改变的方法。类似于Last-Modified和HTTP-If-Modified-Since。但是有所不同的是Last-Modified和HTTP-If-Modified-Since只判断资源的最后修改时间,而ETags和If-None-Match可以是资源任何的任何属性。
    ETags和If-None-Match的工作原理是在HTTPResponse中添加ETags信息。当客户端再次请求该资源时,将在HTTPRequest中加入If-None-Match信息(ETags的值)。如果服务器验证资源的ETags没有改变(该资源没有改变),将返回一个304状态;否则,服务器将返回200状态,并返回该资源和新的ETags。
}


{  
http响应Last-Modified和ETag

  基础知识
1) 什么是”Last-Modified”?
      在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是你请求的资源,同时有一个Last-Modified的属性标记此文件在服务期端
    最后被修改的时间,格式类似这样:Last-Modified: Fri, 12 May 2006 18:53:33 GMT
  客户端第二次请求此URL时,根据 HTTP 协议的规定,浏览器会向服务器传送 If-Modified-Since 报头,询问该时间之后文件是否有被修改过:
  If-Modified-Since: Fri, 12 May 2006 18:53:33 GMT
  如果服务器端的资源没有变化,则自动返回 HTTP 304 (Not Changed.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改
    变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。
2) 什么是”Etag”?
  HTTP 协议规格说明定义ETag为“被请求变量的实体值” (参见 ―― 章节 14.19)。 另一种说法是,ETag是一个可以与Web资源关联的记号(token)。典型的Web资源可以一个Web页,但也可能是JSON或XML文档。服务器单独负责判断记号是什么及其含义,并在HTTP响应头中将其传送到客户端,以下是服务器端返回的格式:
  ETag: "50b1c1d4f775c61:df3"
  客户端的查询更新格式是这样的:
  If-None-Match: W/"50b1c1d4f775c61:df3"
  如果ETag没改变,则返回状态304然后不返回,这也和Last-Modified一样。本人测试Etag主要在断点下载时比较有用。
  
Last-Modified和Etags如何帮助提高性能?
  聪明的开发者会把Last-Modified 和ETags请求的http报头一起使用,这样可利用客户端(例如浏览器)的缓存。因为服务器首先产生 
Last-Modified/Etag标记,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其(客户端)缓存。过程如下:
1.客户端请求一个页面(A)。
2.服务器返回页面A,并在给A加上一个Last-Modified/ETag。
3.客户端展现该页面,并将页面连同Last-Modified/ETag一起缓存。
4.客户再次请求页面A,并将上次请求时服务器返回的Last-Modified/ETag一起传递给服务器。
5.服务器检查该Last-Modified或ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304和一个空的响应体。
}
*/
static ngx_int_t
ngx_http_not_modified_header_filter(ngx_http_request_t *r)
{
    if (r->headers_out.status != NGX_HTTP_OK  //只有返回类型为OK的才进行304 not modified判断
        || r != r->main
        || r->disable_not_modified)
    {
        return ngx_http_next_header_filter(r);
    }
    

    /*
If-Unmodified-Since: 从字面上看, 意思是: 如果从某个时间点算起, 文件没有被修改.....
    1. 如果没有被修改: 则开始`继续'传送文件: 服务器返回: 200 OK
    2. 如果文件被修改: 则不传输, 服务器返回: 412 Precondition failed (预处理错误)
用途:断点续传(一般会指定Range参数). 要想断点续传, 那么文件就一定不能被修改, 否则就不是同一个文件了
*/
    if (r->headers_in.if_unmodified_since
        && !ngx_http_test_if_unmodified(r)) 
    /*
    如果从某个时间点算起, 文件被修改了,客户端请求行中带有if_unmodified_since,表示如果文件如果从某个时间段起没有被修改,
    则继续200 OK传送给客户端包体。但是现在文件却被修改了,因此返回错误
    */
    {
        return ngx_http_filter_finalize_request(r, NULL,
                                                NGX_HTTP_PRECONDITION_FAILED);
    }

    if (r->headers_in.if_match
        && !ngx_http_test_if_match(r, r->headers_in.if_match, 0))
    {
        return ngx_http_filter_finalize_request(r, NULL,
                                                NGX_HTTP_PRECONDITION_FAILED);
    }

    if (r->headers_in.if_modified_since || r->headers_in.if_none_match) {

        if (r->headers_in.if_modified_since
            && ngx_http_test_if_modified(r)) //文件有发生了修改
        {
            return ngx_http_next_header_filter(r);
        }

        if (r->headers_in.if_none_match
            && !ngx_http_test_if_match(r, r->headers_in.if_none_match, 1)) //etag不匹配,说明文件也发生了修改
        {
            return ngx_http_next_header_filter(r);
        }

        /* not modified */
        //回送304表示文件没有修改
        r->headers_out.status = NGX_HTTP_NOT_MODIFIED;
        r->headers_out.status_line.len = 0;
        r->headers_out.content_type.len = 0;
        ngx_http_clear_content_length(r);
        ngx_http_clear_accept_ranges(r);

        if (r->headers_out.content_encoding) {
            r->headers_out.content_encoding->hash = 0;
            r->headers_out.content_encoding = NULL;
        }

        return ngx_http_next_header_filter(r);
    }

    return ngx_http_next_header_filter(r);
}
static ngx_int_t
ngx_http_gunzip_header_filter(ngx_http_request_t *r)
{
    ngx_http_gunzip_ctx_t   *ctx;
    ngx_http_gunzip_conf_t  *conf;

    conf = ngx_http_get_module_loc_conf(r, ngx_http_gunzip_filter_module);

    /* TODO support multiple content-codings */
    /* TODO always gunzip - due to configuration or module request */
    /* TODO ignore content encoding? */

    if (!conf->enable
        || r->headers_out.content_encoding == NULL
        || r->headers_out.content_encoding->value.len != 4
        || ngx_strncasecmp(r->headers_out.content_encoding->value.data,
                           (u_char *) "gzip", 4) != 0)
    {
        return ngx_http_next_header_filter(r);
    }

#if (nginx_version >= 8025 || (nginx_version >= 7065 && nginx_version < 8000))

    r->gzip_vary = 1;

    if (!r->gzip_tested) {
        if (ngx_http_gzip_ok(r) == NGX_OK) {
            return ngx_http_next_header_filter(r);
        }

    } else if (!r->gzip_ok) {
        return ngx_http_next_header_filter(r);
    }

#else

    if (ngx_http_gzip_ok(r) == NGX_OK) {
        return ngx_http_next_header_filter(r);
    }

#endif

    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_gunzip_ctx_t));
    if (ctx == NULL) {
        return NGX_ERROR;
    }

    ngx_http_set_ctx(r, ctx, ngx_http_gunzip_filter_module);

    ctx->request = r;

    r->filter_need_in_memory = 1;

    r->headers_out.content_encoding->hash = 0;
    r->headers_out.content_encoding = NULL;

    ngx_http_clear_content_length(r);
    ngx_http_clear_accept_ranges(r);

    return ngx_http_next_header_filter(r);
}
static ngx_int_t
ngx_http_xss_header_filter(ngx_http_request_t *r)
{
    ngx_http_xss_ctx_t          *ctx;
    ngx_http_xss_conf_t         *conf;
    ngx_str_t                    callback;
    u_char                      *p, *src, *dst;

    if (r->headers_out.status != NGX_HTTP_OK || r != r->main) {
        dd("skipped: status not 200 or in subrequest");
        return ngx_http_next_header_filter(r);
    }

    conf = ngx_http_get_module_loc_conf(r, ngx_http_xss_filter_module);

    if ( ! conf->get_enabled || r->method != NGX_HTTP_GET) {
        dd("skipped: get_enabled disabled or the current method is not GET");
        return ngx_http_next_header_filter(r);
    }

    if (conf->callback_arg.len == 0) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                "xss: xss_get is enabled but no xss_callback_arg specified");

        return ngx_http_next_header_filter(r);
    }

    if (ngx_http_test_content_type(r, &conf->input_types) == NULL) {
        dd("skipped: content type test not passed");
        return ngx_http_next_header_filter(r);
    }

    if (ngx_http_arg(r, conf->callback_arg.data, conf->callback_arg.len,
                &callback) != NGX_OK)
    {
        dd("skipped: no callback arg found in the current request: %.*s",
                conf->callback_arg.len, conf->callback_arg.data);

        return ngx_http_next_header_filter(r);
    }

    p = ngx_palloc(r->pool, callback.len);
    if (p == NULL) {
        return NGX_ERROR;
    }

    src = callback.data; dst = p;

    ngx_unescape_uri(&dst, &src, callback.len,
            NGX_UNESCAPE_URI_COMPONENT);

    if (src != callback.data + callback.len) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                "xss: unescape uri: input data not consumed completely");

        return NGX_ERROR;
    }

    callback.data = p;
    callback.len = dst - p;

    if (ngx_http_xss_test_callback((char *) callback.data, callback.len)
            != NGX_OK)
    {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                "xss: bad callback argument: \"%V\"", &callback);

        return ngx_http_next_header_filter(r);
    }

    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_xss_ctx_t));
    if (ctx == NULL) {
        return NGX_ERROR;
    }

    /*
     * set by ngx_pcalloc():
     *
     *     ctx->callback = { 0, NULL };
     *     conf->before_body_sent = 0;
     */

    ctx->callback = callback;

    ngx_http_set_ctx(r, ctx, ngx_http_xss_filter_module);

    r->headers_out.content_type = conf->output_type;
    r->headers_out.content_type_len = conf->output_type.len;

    ngx_http_clear_content_length(r);
    ngx_http_clear_accept_ranges(r);

    return ngx_http_next_header_filter(r);
}
static ngx_int_t
ngx_http_brotli_header_filter(ngx_http_request_t *r)
{
    ngx_table_elt_t         *h, *ae;
    ngx_http_brotli_ctx_t   *ctx;
    ngx_http_brotli_conf_t  *conf;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http brotli header filter");

    conf = ngx_http_get_module_loc_conf(r, ngx_http_brotli_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);
    }

    /* Check that brotli is supported. We do not check possible q value
     * if brotli is supported it takes precendence over gzip if size >
     * brotli_min_length */
    ae = r->headers_in.accept_encoding;
    if(!ae) {
        return ngx_http_next_header_filter(r);
    }
    /* Since there is no reason for the br string to be present
     * unless brotli is accepted either as "br" or "brotli" we
     * just check for "br" */
    if (!ngx_strstrn(ae->value.data, "br", 1)) {
        return ngx_http_next_header_filter(r);
    }

    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_brotli_ctx_t));
    if (ctx == NULL) {
        return NGX_ERROR;
    }

    if (ngx_strstrn(ae->value.data, "brotli", 5)) {
        ctx->br = 0;
    } else {
        ctx->br = 1;
    }
#if (NGX_HTTP_GZIP)
    r->gzip_vary = 1;
    /* Make sure gzip does not execute */
    r->gzip_tested = 1;
    r->gzip_ok = 0;
#endif

    ngx_http_set_ctx(r, ctx, ngx_http_brotli_filter_module);

    ctx->request = r;

    h = ngx_list_push(&r->headers_out.headers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    h->hash = 1;
    ngx_str_set(&h->key, "Content-Encoding");
    if (ctx->br) {
        ngx_str_set(&h->value, "br");
    } else {
        ngx_str_set(&h->value, "brotli");
    }
    r->headers_out.content_encoding = h;

    r->main_filter_need_in_memory = 1;

    ngx_http_clear_content_length(r);
    ngx_http_clear_accept_ranges(r);
    ngx_http_weak_etag(r);

    return ngx_http_next_header_filter(r);
}
static ngx_int_t
ngx_http_send_refresh(ngx_http_request_t *r)
{
    u_char       *p, *location;
    size_t        len, size;
    uintptr_t     escape;
    ngx_int_t     rc;
    ngx_buf_t    *b;
    ngx_chain_t   out;

    len = r->headers_out.location->value.len;
    location = r->headers_out.location->value.data;

    escape = 2 * ngx_escape_uri(NULL, location, len, NGX_ESCAPE_REFRESH);

    size = sizeof(ngx_http_msie_refresh_head) - 1
           + escape + len
           + sizeof(ngx_http_msie_refresh_tail) - 1;

    r->err_status = NGX_HTTP_OK;

    r->headers_out.content_type_len = sizeof("text/html") - 1;
    ngx_str_set(&r->headers_out.content_type, "text/html");
    r->headers_out.content_type_lowcase = NULL;

    r->headers_out.location->hash = 0;
    r->headers_out.location = NULL;

    r->headers_out.content_length_n = size;

    if (r->headers_out.content_length) {
        r->headers_out.content_length->hash = 0;
        r->headers_out.content_length = NULL;
    }

    ngx_http_clear_accept_ranges(r);
    ngx_http_clear_last_modified(r);
    ngx_http_clear_etag(r);

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || r->header_only) {
        return rc;
    }

    b = ngx_create_temp_buf(r->pool, size);
    if (b == NULL) {
        return NGX_ERROR;
    }

    p = ngx_cpymem(b->pos, ngx_http_msie_refresh_head,
                   sizeof(ngx_http_msie_refresh_head) - 1);

    if (escape == 0) {
        p = ngx_cpymem(p, location, len);

    } else {
        p = (u_char *) ngx_escape_uri(p, location, len, NGX_ESCAPE_REFRESH);
    }

    b->last = ngx_cpymem(p, ngx_http_msie_refresh_tail,
                         sizeof(ngx_http_msie_refresh_tail) - 1);

    b->last_buf = 1;
    b->last_in_chain = 1;

    out.buf = b;
    out.next = NULL;

    return ngx_http_output_filter(r, &out);
}
static ngx_int_t
ngx_http_gzip_header_filter(ngx_http_request_t *r)
{
    ngx_table_elt_t       *h;
    ngx_http_gzip_ctx_t   *ctx;
    ngx_http_gzip_conf_t  *conf;
    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);
    }
    r->gzip_vary = 1;
#if (NGX_HTTP_DEGRADATION)
    {
        ngx_http_core_loc_conf_t  *clcf;
        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
        if (clcf->gzip_disable_degradation && ngx_http_degraded(r))
        {
            return ngx_http_next_header_filter(r);
        }
    }
#endif
    if (!r->gzip_tested)
    {
        if (ngx_http_gzip_ok(r) != NGX_OK)
        {
            return ngx_http_next_header_filter(r);
        }
    }
    else if (!r->gzip_ok)
    {
        return ngx_http_next_header_filter(r);
    }
    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_gzip_ctx_t));
    if (ctx == NULL)
    {
        return NGX_ERROR;
    }
    ngx_http_set_ctx(r, ctx, ngx_http_gzip_filter_module);
    ctx->request = r;
    ctx->buffering = (conf->postpone_gzipping != 0);
    ngx_http_gzip_filter_memory(r, ctx);
    h = ngx_list_push(&r->headers_out.headers);
    if (h == NULL)
    {
        return NGX_ERROR;
    }
    h->hash = 1;
    ngx_str_set(&h->key, "Content-Encoding");
    ngx_str_set(&h->value, "gzip");
    r->headers_out.content_encoding = h;
    r->main_filter_need_in_memory = 1;
    ngx_http_clear_content_length(r);
    ngx_http_clear_accept_ranges(r);
    ngx_http_weak_etag(r);
    return ngx_http_next_header_filter(r);
}
static ngx_int_t
ngx_http_brotli_header_filter(ngx_http_request_t *r)
{
    ngx_table_elt_t         *h;
    ngx_http_brotli_ctx_t   *ctx;
    ngx_http_brotli_conf_t  *conf;

    conf = ngx_http_get_module_loc_conf(r, ngx_http_brotli_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_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);
    }

    if (r->headers_out.content_encoding
        && r->headers_out.content_encoding->value.len)
    {
        return ngx_http_next_header_filter(r);
    }

    /* Check that brotli is supported. We do not check possible q value
     * if brotli is supported it takes precendence over gzip if size >
     * brotli_min_length */
    if (accept_br(r->headers_in.accept_encoding) != NGX_OK) {
        return ngx_http_next_header_filter(r);
    }

    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_brotli_ctx_t));
    if (ctx == NULL) {
        return NGX_ERROR;
    }

#if (NGX_HTTP_GZIP)
    r->gzip_vary = 1;
    /* Make sure gzip does not execute */
    r->gzip_tested = 1;
    r->gzip_ok = 0;
#endif

    ngx_http_set_ctx(r, ctx, ngx_http_brotli_filter_module);

    ctx->request = r;

    h = ngx_list_push(&r->headers_out.headers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    h->hash = 1;
    ngx_str_set(&h->key, "Content-Encoding");
    ngx_str_set(&h->value, "br");
    r->headers_out.content_encoding = h;

    r->main_filter_need_in_memory = 1;

    ngx_http_clear_content_length(r);
    ngx_http_clear_accept_ranges(r);
    ngx_http_weak_etag(r);

    return ngx_http_next_header_filter(r);
}
static ngx_int_t
ngx_http_xss_header_filter(ngx_http_request_t *r)
{
    ngx_http_xss_ctx_t          *ctx;
    ngx_http_xss_loc_conf_t     *xlcf;
    ngx_str_t                    callback;
    u_char                      *p, *src, *dst;

    if (r != r->main) {
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "xss skipped in subrequests");

        return ngx_http_next_header_filter(r);
    }

    xlcf = ngx_http_get_module_loc_conf(r, ngx_http_xss_filter_module);

    if (!xlcf->get_enabled) {
        return ngx_http_next_header_filter(r);
    }

    if (r->method != NGX_HTTP_GET) {
        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "xss skipped due to the unmatched request method: %V",
                       &r->method_name);

        return ngx_http_next_header_filter(r);
    }

    if (xlcf->check_status) {

        if (r->headers_out.status != NGX_HTTP_OK
            && r->headers_out.status != NGX_HTTP_CREATED)
        {
            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "xss skipped due to unmatched response status "
                           "\"%ui\"", r->headers_out.status);

            return ngx_http_next_header_filter(r);
        }
    }

    if (xlcf->callback_arg.len == 0) {

        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "xss: xss_get is enabled but no xss_callback_arg "
                      "specified");

        return ngx_http_next_header_filter(r);
    }

    if (ngx_http_test_content_type(r, &xlcf->input_types) == NULL) {

        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "xss skipped due to unmatched Content-Type response "
                       "header");

        return ngx_http_next_header_filter(r);
    }

    if (ngx_http_arg(r, xlcf->callback_arg.data, xlcf->callback_arg.len,
                     &callback)
        != NGX_OK)
    {
        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "xss skipped: no GET argument \"%V\" specified in "
                       "the request", &xlcf->callback_arg);

        return ngx_http_next_header_filter(r);
    }

    p = ngx_palloc(r->pool, callback.len);
    if (p == NULL) {
        return NGX_ERROR;
    }

    src = callback.data; dst = p;

    ngx_unescape_uri(&dst, &src, callback.len, NGX_UNESCAPE_URI_COMPONENT);

    if (src != callback.data + callback.len) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "xss: unescape uri: input data not consumed completely");

        return NGX_ERROR;
    }

    callback.data = p;
    callback.len = dst - p;

    if (ngx_http_xss_test_callback(callback.data, callback.len) != NGX_OK) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "xss: bad callback argument: \"%V\"", &callback);

        return ngx_http_next_header_filter(r);
    }

    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_xss_ctx_t));
    if (ctx == NULL) {
        return NGX_ERROR;
    }

    /*
     * set by ngx_pcalloc():
     *
     *     ctx->callback = { 0, NULL };
     *     ctx->before_body_sent = 0;
     */

    ctx->callback = callback;

    ngx_http_set_ctx(r, ctx, ngx_http_xss_filter_module);

    r->headers_out.content_type = xlcf->output_type;
    r->headers_out.content_type_len = xlcf->output_type.len;
    r->headers_out.content_type_lowcase = NULL;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "xss output Content-Type header \"%V\"",
                   &xlcf->output_type);

    ngx_http_clear_content_length(r);
    ngx_http_clear_accept_ranges(r);

    if (xlcf->override_status
        && r->headers_out.status >= NGX_HTTP_SPECIAL_RESPONSE)
    {
        r->headers_out.status = NGX_HTTP_OK;
    }

    return ngx_http_next_header_filter(r);
}
static ngx_int_t ngx_http_dynamic_etags_body_filter(ngx_http_request_t *r, ngx_chain_t *in) {
    ngx_chain_t *chain_link;
    ngx_http_dynamic_etags_module_ctx_t       *ctx;

    ngx_int_t  rc;
    ngx_md5_t md5;
    unsigned char digest[16];
    ngx_uint_t i;

    ctx = ngx_http_get_module_ctx(r, ngx_http_dynamic_etags_module);
    if (ctx == NULL) {
        return ngx_http_next_body_filter(r, in);
    }
	
    ngx_http_dynamic_etags_loc_conf_t *loc_conf;
    loc_conf = ngx_http_get_module_loc_conf(r, ngx_http_dynamic_etags_module);
    if (1 == loc_conf->enable) {
        ngx_md5_init(&md5);
        for (chain_link = in; chain_link; chain_link = chain_link->next) {
            ngx_md5_update(&md5, chain_link->buf->pos,
                chain_link->buf->last - chain_link->buf->pos);
        }
        ngx_md5_final(digest, &md5);

        unsigned char* etag = ngx_pcalloc(r->pool, 34);
        etag[0] = etag[33] = '"';
        for ( i = 0 ; i < 16; i++ ) {
            etag[2*i+1] = hex[digest[i] >> 4];
            etag[2*i+2] = hex[digest[i] & 0xf];
        }

        if(!r->headers_out.etag) {
            r->headers_out.etag = ngx_list_push(&r->headers_out.headers);
        }

        r->headers_out.etag->hash = 1;
        r->headers_out.etag->key.len = sizeof("ETag") - 1;
        r->headers_out.etag->key.data = (u_char *) "ETag";
        r->headers_out.etag->value.len = 34;
        r->headers_out.etag->value.data = etag;

        /* look for If-None-Match in request headers */
        ngx_uint_t      found=0;
        ngx_list_part_t *part = NULL;
        ngx_table_elt_t *header = NULL;
        ngx_table_elt_t if_none_match;
        part = &r->headers_in.headers.part;
        header = part->elts;
        for ( i = 0 ; ; i++ ) {
            if ( i >= part->nelts) {
                if ( part->next == NULL ) {
                        break;
                }

                part = part->next;
                header = part->elts;
                i = 0;
            }

            if ( ngx_strcmp(header[i].key.data, "If-None-Match") == 0 ) {
                if_none_match = header[i];
                found = 1;
                break;
            }
        }

        if ( found ) {
            if ( ngx_strncmp(r->headers_out.etag->value.data, if_none_match.value.data, r->headers_out.etag->value.len) == 0 ) {

                r->headers_out.status = NGX_HTTP_NOT_MODIFIED;
                r->headers_out.status_line.len = 0;
                r->headers_out.content_type.len = 0;
                ngx_http_clear_content_length(r);
                ngx_http_clear_accept_ranges(r);
            }
        }
    }	
static ngx_int_t
ngx_http_send_special_response(ngx_http_request_t *r,
    ngx_http_core_loc_conf_t *clcf, ngx_uint_t err)
{
    u_char       *tail;
    size_t        len;
    ngx_int_t     rc;
    ngx_buf_t    *b;
    ngx_uint_t    msie_padding;
    ngx_chain_t   out[3];

    if (clcf->server_tokens) {
        len = sizeof(ngx_http_error_full_tail) - 1;
        tail = ngx_http_error_full_tail;

    } else {
        len = sizeof(ngx_http_error_tail) - 1;
        tail = ngx_http_error_tail;
    }

    msie_padding = 0;

    if (ngx_http_error_pages[err].len) {
        r->headers_out.content_length_n = ngx_http_error_pages[err].len + len;
        if (clcf->msie_padding
            && (r->headers_in.msie || r->headers_in.chrome)
            && r->http_version >= NGX_HTTP_VERSION_10
            && err >= NGX_HTTP_OFF_4XX)
        {
            r->headers_out.content_length_n +=
                                         sizeof(ngx_http_msie_padding) - 1;
            msie_padding = 1;
        }

        r->headers_out.content_type_len = sizeof("text/html") - 1;
        ngx_str_set(&r->headers_out.content_type, "text/html");
        r->headers_out.content_type_lowcase = NULL;

    } else {
        r->headers_out.content_length_n = 0;
    }

    if (r->headers_out.content_length) {
        r->headers_out.content_length->hash = 0;
        r->headers_out.content_length = NULL;
    }

    ngx_http_clear_accept_ranges(r);
    ngx_http_clear_last_modified(r);
    ngx_http_clear_etag(r);

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || r->header_only) {
        return rc;
    }

    if (ngx_http_error_pages[err].len == 0) {
        return ngx_http_send_special(r, NGX_HTTP_LAST);
    }

    b = ngx_calloc_buf(r->pool);
    if (b == NULL) {
        return NGX_ERROR;
    }

    b->memory = 1;
    b->pos = ngx_http_error_pages[err].data;
    b->last = ngx_http_error_pages[err].data + ngx_http_error_pages[err].len;

    out[0].buf = b;
    out[0].next = &out[1];

    b = ngx_calloc_buf(r->pool);
    if (b == NULL) {
        return NGX_ERROR;
    }

    b->memory = 1;

    b->pos = tail;
    b->last = tail + len;

    out[1].buf = b;
    out[1].next = NULL;

    if (msie_padding) {
        b = ngx_calloc_buf(r->pool);
        if (b == NULL) {
            return NGX_ERROR;
        }

        b->memory = 1;
        b->pos = ngx_http_msie_padding;
        b->last = ngx_http_msie_padding + sizeof(ngx_http_msie_padding) - 1;

        out[1].next = &out[2];
        out[2].buf = b;
        out[2].next = NULL;
    }

    if (r == r->main) {
        b->last_buf = 1;
    }

    b->last_in_chain = 1;

    return ngx_http_output_filter(r, &out[0]);
}
static ngx_int_t
ngx_http_zip_set_headers(ngx_http_request_t *r, ngx_http_zip_ctx_t *ctx)
{
    time_t if_range, last_modified;

    if (ngx_http_zip_add_cache_control(r) == NGX_ERROR) {
        return NGX_ERROR;
    }

    r->headers_out.content_type_len = sizeof(NGX_ZIP_MIME_TYPE) - 1;
    ngx_str_set(&r->headers_out.content_type, NGX_ZIP_MIME_TYPE);
    ngx_http_clear_content_length(r);

    if (ctx->missing_crc32) {
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                "mod_zip: Clearing Accept-Ranges header");
        ngx_http_clear_accept_ranges(r);
    }
    r->headers_out.content_length_n = ctx->archive_size;
    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
            "mod_zip: Archive will be %O bytes", ctx->archive_size);
    if (r->headers_in.range) {
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                "mod_zip: Range found");
        if (ctx->missing_crc32) {
            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                    "mod_zip: Missing checksums, ignoring Range");
            return NGX_OK;
        }
        if (r->headers_in.if_range && r->upstream) {
            if_range = ngx_http_parse_time(r->headers_in.if_range->value.data,
                    r->headers_in.if_range->value.len);
            if (if_range == NGX_ERROR) { /* treat as ETag */
                if (r->upstream->headers_in.etag) {
                    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                            "mod_zip: If-Range = %V, ETag = %V", 
                            &r->headers_in.if_range->value, &r->upstream->headers_in.etag->value);
                    if (r->upstream->headers_in.etag->value.len != r->headers_in.if_range->value.len
                            || ngx_strncmp(r->upstream->headers_in.etag->value.data,
                                r->headers_in.if_range->value.data,
                                r->headers_in.if_range->value.len)) {
                        return NGX_OK;
                    }
                } else {
                    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                            "mod_zip: No ETag from upstream");
                    return NGX_OK;
                }
            } else { /* treat as modification time */
                if (r->upstream->headers_in.last_modified) {
                    last_modified = ngx_http_parse_time(r->upstream->headers_in.last_modified->value.data,
                            r->upstream->headers_in.last_modified->value.len);
                    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                            "mod_zip: If-Range = %d, Last-Modified = %d", 
                            if_range, last_modified);
                    if (if_range != last_modified && last_modified != -1) {
                        return NGX_OK;
                    }
                } else {
                    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                            "mod_zip: No Last-Modified from upstream");
                    return NGX_OK;
                }
            }
        }
        if (ngx_http_zip_parse_range(r, &r->headers_in.range->value, ctx) 
                == NGX_ERROR) {
            r->headers_out.status = NGX_HTTP_RANGE_NOT_SATISFIABLE;
            if (ngx_http_zip_add_full_content_range(r) == NGX_ERROR) {
                return NGX_ERROR;
            }
            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                    "mod_zip: Range not satisfiable");
            ctx->ranges.nelts = 0;
            return NGX_HTTP_RANGE_NOT_SATISFIABLE;
        }
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                "mod_zip: Range is satisfiable");
        if (ctx->ranges.nelts == 1) {
            if (ngx_http_zip_add_partial_content_range(r, ctx) == NGX_ERROR) {
                return NGX_ERROR;
            }
        } else {
            if (ngx_http_zip_init_multipart_range(r, ctx) == NGX_ERROR) {
                return NGX_ERROR;
            }
        }
        r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
        r->headers_out.status_line.len = 0;
    }

    return NGX_OK;
}