header_access 模块设计文档

作品 采用 知识共享署名-相同方式共享 3.0 Unported许可协议 进行许可。

  • version: for lighttpd 1.4.26

需求描述

mod_header_access 模块用来删除 lighttpd 处理过程中获取和产生的 request 和 response 的头信息中指定的内容,例如:

Cache-Control: no-cache

通过修改 headers 信息,从而可以影响后续模块如 mod_cache/mod_proxy 的处理行为。

request 指客户端(浏览器)发送至 lighttpd 的请求,response 指从后端 backend 返回给 lighttpd 的应答——例如 mod_proxy 从后端 apache 返回的响应,不包括对 lighttpd 返回给客户端的响应 headers 的处理。

对 headers 的删除操作应该包括对值的检查,例如,Cache-Control 符合 no-cache 和 max-age=0 时应该删除,而其他的值则不执行。需要能够对这些值进行配置。

要求模块在删除时进行检查,确保对一下关键的 header,如 Host 不会进行删除操作导致不能正常提供服务。

需求挖掘和设计

数据处理流程结构

通过对需求的分析,可以得到如下的关系图:

图中 m1 即是我们需要添加的模块:mod_header_access,其 hook 点设置的目的,就是为了在删除某些头信息之后,影响后面的模块的 hook 点从处理功能。例如,对 Cache-Control 为 no-cache 的情况,m1 为 mod_header_access,m2 为 mod_cache,则 m1 在 hook 1 位置删除该头信息后就会改变 m2 在 hook 1 位置的处理方式。

关于 hook 点位置选择,需要参考 lighttpd 的状态机并考虑对其他模块的影响。请跳到 hook 点位置选择

配置项

对于值的检查和配置,相应的配置文件 lighttpd.conf 选项如下:

header_access.del-request-header = ("Cache-Control" => ("no-cache", "max-age=0"))
header_access.del-response-header = ("Cache-Control" => ("no-cache", "max-age=0"))
header_access.debug = "enable"

这需要在模块的初始化配置点 handle_set_defaults() 中进行相应的解析,并在这个 plugin 特定的 plugin_config 配置中组织一个二维数组(array)。

headers 数组操作

关键的数据结构为 con->request.headers 和 con->response.headers,这是两个 array * 结构的数组。

(完整的数据结构关系图后续补充,目前时间实在是太过于紧张...)

自然,对 con->request.headers 和 con->response.headers 需要进行删除数组元素的操作。但 array.c 中并未提供相关的 array_remove_element() 函数,所以我们需要增加一个。算法设计如下:

首先看看操作前的数组结构示例:

这里 data 在插入时只进行 append 操作,为了保证排序,作者使用了一个 sorted 数组,每个元素的值就是 data 中对应元素的下标,在插入时即进行排序,保证 sorted 的顺序,也就保证了 array 的顺序。

删除为 key 的元素时(图中的 data_unset y),找到该元素在 data 和 sorted 中对应的位置 idx 和 pos(array.c 中已提供 array_get_index 函数),并找到 data 末尾元素(data[i_tail], i_tail = array->used - 1)在 sorted 中的位置 p_tail。然后,分别将 data[idx] 和 data[i_tail] 的内容交换,将 sorted[pos] 和 sorted[p_tail] 的内容交换。交换后的结构如下:

最后,将 sorted pos 后的元素全部执行一次 memmove() 操作,因为 sorted 中只包含指针,所以移动的开销应该是可以接受的。

另外,原来有一个 array_pop() 函数,应该是有 BUG 的,因为其在进行 a->used-- 后并没有相应修改 a->sorted 数组!

hook 点位置选择

在 mod_header_access 中编写的函数需要在 mod_header_access_plugin_init() 阶段挂载到对应 plugin 的 hook 回调函数上,但具体应该使用哪个 hook 点,需要对 lighttpd 的状态机和相关模块如 mod_cache 和 mod_proxy 的关系进行梳理。

lighttpd 状态机的变化如下图所示:

在每个状态,有 0 到多个 hook 点,关系如下表所示(以 mod_cache 为例):

STATE
    hook_points                            mod_cache
=======================================================================
network_init():
    plugins_call_init:                     mod_cache_init
    plugins_call_set_defaults:             mod_cache_set_defaults
fd_event_poll():
    plugins_call_handle_trigger:           (NULL)
    plugins_call_connection_reset:         mod_cache_cleanup
-----------------------------------
state: connect
    plugins_call_handle_joblist:           (NULL)
state: req-start
    [NULL]
state: read
    [NULL]
state: req-end
    [NULL]
state: handle-req
    plugins_call_handle_uri_raw:           (NULL)
    plugins_call_handle_uri_clean:         mod_cache_uri_handler
    plugins_call_handle_docroot:           mod_cache_docroot_handler
    plugins_call_handle_physical:          (NULL)
    plugins_call_handle_subrequest:        mod_cache_handle_memory_storage
    plugins_call_handle_subrequest_start:  (NULL)
state: resp-start
    plugins_call_handle_response_start:    mod_cache_handle_response_start
state: write
    plugins_call_handle_response_filter:   mod_cache_handle_response_filter
state: resp-end
    plugins_call_handle_request_done:      (NULL) 
-----------------------------------
state: req-start
    plugins_call_connection_reset:         mod_cache_cleanup
state: read
    plugins_call_handle_joblist:           (NULL)
state: error
    plugins_call_connection_reset:
state: close
    [NULL]

将几个关键的模块合并在一起考虑,可以得到如下关系表:

plugin \ hookuri_rawuri_clean...response_startresponse_filter
mod_setenv  ...  
mod_header_access?DEL_REQ_HEADER...DEL_RESP_HEADER?
mod_cache ..affected.......affected.. 
mod_proxy ..affected.......affected.. 

因为 delele header 的目的是为了改变 mod_cache/mod_proxy 的处理方式,所以配置的模块的加载顺序也应该如上所示,将 mod_header_access 放在 mod_cache/mod_proxy 之前。否则,mod_header_access 的 hook 就必须提前,例如,放到 handle_uri_raw 中,但这样一来,mod_setenv 在 handle_uri_clean 部分又可能会设置增加一些头信息,导致 delete 失效。

mod_cache/mod_proxy 对 request 的处理都在 handle_uri_clean,所以 mod_header_access 放在 handle_uri_clean 处最为合理,可以防止中间其他模块加入的头信息的影响(如 mod_setenv ——后续可以考虑增加设置选项是否覆盖其他模块)。

对 response 处理,放在 handle_response_start 点。因为根据对 lighttpd 状态机的分析,在这个 hook 点的所有模块的回调函数都结束后,会调用 http_response_write_header() 函数将 con->response.headers 数组的内容写到一个缓冲区 con->output_queue,此后的 hook 点都不会再修改这个缓冲区,而最终写到网络 socket 中的内容,都从这个缓冲区来;同时,我们也不需要修改发送给客户端浏览器的头信息,所以不需要再在后面的 hook 点做处理。调用关系如下:

state:resp-start
    connections.c:connection_state_matchine
      connections.c:connection_handle_write_prepare
        plugin.c:plugins_call_handle_response_start
          mod_header_access->handle_response_start (**)
          mod_cache->handle_response_start
          mod_proxy->handle_response_start
        response.c:http_response_write_header  /* "con->response.headers" written to "con->output_queue") */
        plugin.c:plugins_call_handle_response_filter
          ...
state:write
    connections.c:connection_handle_write
        network.c:network_write_chunkqueue(con->output_queue)
    ......
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值