header_access 模块设计文档

来源:互联网 发布:怎样经营淘宝 编辑:程序博客网 时间:2024/05/29 04:36
作品采用知识共享署名-相同方式共享 3.0 Unported许可协议进行许可。

  • 需求描述
  • 需求挖掘和设计
    • 数据处理流程结构
    • 配置项
    • headers 数组操作
    • hook 点位置选择
  • 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_defaultsfd_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_startstate: write    plugins_call_handle_response_filter:   mod_cache_handle_response_filterstate: resp-end    plugins_call_handle_request_done:      (NULL) -----------------------------------state: req-start    plugins_call_connection_reset:         mod_cache_cleanupstate: read    plugins_call_handle_joblist:           (NULL)state: error    plugins_call_connection_reset:state: close    [NULL]

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

plugin \ hookuri_rawuri_clean...response_startresponse_filtermod_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)    ......
原创粉丝点击