lighttpd - Plugin: Overview

2023-10-10 08:18
文章标签 plugin overview lighttpd

本文主要是介绍lighttpd - Plugin: Overview,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

lighttpd - Plugin: Overview

插件是各个Web Server的重要组成部分,很多功能通过插件完成(mini_httpd这类超级小的Server当然没有什么插件,但它的目的只是实验啊,可不是取代什么Apache这类宏伟目标。说道取代Apache,貌似Nginx是目前......  算了,还是回到lighttpd吧)。

本文介绍下lighttpd的插件部分的实现。lighttpd版本1.4.31。

1. 数据结构

Lighttpd为每个插件维护一个plugin数据结构,其中包含了插件名,版本号,以及虚函数表和一个保存dlopen(Linux下)返回的非透明句柄。其定义如下,

复制代码
typedef struct {size_t version;buffer *name; /* name of the plugin */void *(* init)                       ();handler_t (* set_defaults)           (server *srv, void *p_d);handler_t (* cleanup)                (server *srv, void *p_d);/* is called ... */handler_t (* handle_trigger)         (server *srv, void *p_d);                     /* once a second */handler_t (* handle_sighup)          (server *srv, void *p_d);                     /* at a signup */handler_t (* handle_uri_raw)         (server *srv, connection *con, void *p_d);    /* after uri_raw is set */handler_t (* handle_uri_clean)       (server *srv, connection *con, void *p_d);    /* after uri is set */handler_t (* handle_docroot)         (server *srv, connection *con, void *p_d);    /* getting the document-root */handler_t (* handle_physical)        (server *srv, connection *con, void *p_d);    /* mapping url to physical path */handler_t (* handle_request_done)    (server *srv, connection *con, void *p_d);    /* at the end of a request */handler_t (* handle_connection_close)(server *srv, connection *con, void *p_d);    /* at the end of a connection */handler_t (* handle_joblist)         (server *srv, connection *con, void *p_d);    /* after all events are handled */handler_t (* handle_subrequest_start)(server *srv, connection *con, void *p_d);/* when a handler for the request* has to be found*/handler_t (* handle_subrequest)      (server *srv, connection *con, void *p_d);    /* */handler_t (* connection_reset)       (server *srv, connection *con, void *p_d);    /* */void *data;/* dlopen handle */void *lib;
} plugin;
复制代码

此外,Server的结构server,server_config里面都有一些插件相关的字段,暂时先把它们罗列在此方便一会参考。

复制代码
typedef struct {void  *ptr;size_t used;size_t size;
} buffer_plugin;typedef struct server {...buffer_plugin plugins;  //插件池void *plugin_slots;...server_config  srvconf;...
} server;typedef struct {...buffer *modules_dir;   //模块路径array *modules;        //模块名列表
     ...
} server_config;
复制代码

2. 模块加载

Plugin可以作为动态库由lighttpd主进程启动的时候“动态加载”,也可以静态编译到lighttpd中。前者灵活易于扩展,需要新增plugin时无需重新编译Web Server,只需要编译动态库,修改配置文件,并重启lighttpd(但不支持“热加载”)。当然,共享库的体积通常比静态编译要大一些(无法在多个使用者间分摊大小的情况下),加载共享库也需要一些时间。好在通常情况这两个缺点不足以使我们放弃动态库灵活,可扩展性强的优势。

了解加载函数的实现前先看几个plugin.c里的内部函数,

复制代码
static plugin *plugin_init(void);
static void plugin_free(plugin *p);static int plugins_register(server *srv, plugin *p) {plugin **ps;if (0 == srv->plugins.size) {srv->plugins.size = 4;srv->plugins.ptr  = malloc(srv->plugins.size * sizeof(*ps));srv->plugins.used = 0;} else if (srv->plugins.used == srv->plugins.size) {srv->plugins.size += 4;srv->plugins.ptr   = realloc(srv->plugins.ptr, srv->plugins.size * sizeof(*ps));}ps = srv->plugins.ptr;ps[srv->plugins.used++] = p;return 0;
}
复制代码

plugin_init分配并初始化plugin结构,初始化只是清零而已,没有其他动作。plugin_free释放plugin结构,如果是动态加载,会先释放由dlopen打开的共享库。注册函数的实现也非常直接,每次以4个plugin为单位,分配plugin池,池中有空闲就设置一个。贴出它的实现是要强调“Plugin池”的概念,类似的做法在lighttpd的许多地方都会碰到。


接下来要看加载函数了,不过plugins_loads有两个版本:静态,动态。这个是编译的时候决定的。

2.1 静态加载

复制代码
int plugins_load(server *srv) {plugin *p;
#define PLUGIN_INIT(x)\p = plugin_init(); \if (x ## _plugin_init(p)) { \log_error_write(srv, __FILE__, __LINE__, "ss", #x, "plugin init failed" ); \plugin_free(p); \return -1;\}\ plugins_register(srv, p);#include "plugin-static.h"return 0;
}
复制代码

这里定义了一个宏PLUGIN_INIT,每次使用这个宏,就可以通过plugin_init分配一个新的plugin结构,并调用xxx_plugin_init()函数,最后在用plugin_register将新的plugin结构注册到server中。但问题是,只有宏定义啊,调用呢?!。据本人猜测,应该在plugin-static.h中,使用./configure的默认配置的话,代码树中并没有此文件。不过本人做了个实验,重新使用"--enable-static[=PKGS]"、"--with-PACKAGE[=ARG]"重新配置、编译了一下Lighttpd,居然还是没有plugin-static.h啊,猜测失败?谁知道的话麻烦搞告诉我下。


好在我们主要关注的是动态版本的plugins_load。

2.2 动态加载

动态加载是默认的情况。对于配置文件中使用"server.modules = (...)"所列出的每个模块进行加载。函数原型和静态版本相同,

int plugins_load(server *srv) {...for (i = 0; i < srv->srvconf.modules->used; i++) {...}

加载过程如下,

  • 使用modules_dir,"/",模块名和".so"拼装成完整的路径名。
        buffer_copy_string_buffer(srv->tmp_buf, srv->srvconf.modules_dir);buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("/"));buffer_append_string(srv->tmp_buf, modules);buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN(".so"));
  • 使用plugin_init分配并初始化一个新的plugin结构
        p = plugin_init();
  • 调用dlopen打开共享库,并将非透明的handle保存到plugin->lib
        if (NULL == (p->lib = dlopen(srv->tmp_buf->ptr, RTLD_NOW|RTLD_GLOBAL))) {...}
  • 使用模块名,“_plugin_init”组装成模块初始化函数,并用dlsym取出其地址,并调用它
复制代码
        buffer_reset(srv->tmp_buf);buffer_copy_string(srv->tmp_buf, modules);buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("_plugin_init"));...init = (int (*)(plugin *))(intptr_t)dlsym(p->lib, srv->tmp_buf->ptr);...if ((*init)(p)) {...}
复制代码
  • 调用plugins_register将模块注册到server
        plugins_register(srv, p);

3. Server和Plugin间的接口

3.1 server,plugin结构关系图

要知道plugin如何工作,先要理清下面几个数据结构的关系,server, plugin, plugin_t, server->plugin_slots, server->plugins。它们的关系如下图所示,slot中每个元素代表了一个VFT function,而每个function有一组plugin与之关联。这意味着调用VFT function的时候,要把每个plugin的对应function都调用一遍(实际上未必会都都调用,要根据某个plugin Function的返回值判断是否继续,这个稍后讨论)。

 

3.2 Plugin API: plugins_call_xxx 函数

lighttpd的Plugin模块对外(Server)的接口是plugins_call_xxx函数。这些函数则是使用PLUGIN_TO_SLOT宏(如果愿意可以称为模板,虽然C里面没有模板)来生成的。PLUGIN_TO_SLOT是一个非常重要,而又奇特的宏:1. 名字上更本看不出它是干嘛的,2. 它被多次重定义,并用来或者生成函数、或者执行代码。

plugins_call_xxx函数分成3类,其实只是原型不同而已。

复制代码
#define SERVER_FUNC(x) \static handler_t x(server *srv, void *p_d)#define CONNECTION_FUNC(x) \static handler_t x(server *srv, connection *con, void *p_d)#define INIT_FUNC(x) \static void *x()
复制代码

我们暂时忽略SERVER_FUNC,CONNECTION_FUNC和INIT_FUNC的用处。看看如何用PLUGIN_TO_SLOT生成这些API。

PLUGIN_TO_SLOT的第一次定义,以及用它生成的Plugin API:

复制代码
#define PLUGIN_TO_SLOT(x, y) \handler_t plugins_call_##y(server *srv, connection *con) {\plugin **slot;\size_t j;\if (!srv->plugin_slots) return HANDLER_GO_ON;\slot = ((plugin ***)(srv->plugin_slots))[x];\if (!slot) return HANDLER_GO_ON;\for (j = 0; j < srv->plugins.used && slot[j]; j++) { \plugin *p = slot[j];\handler_t r;\switch(r = p->y(srv, con, p->data)) {\case HANDLER_GO_ON:\break;\case HANDLER_FINISHED:\case HANDLER_COMEBACK:\case HANDLER_WAIT_FOR_EVENT:\case HANDLER_WAIT_FOR_FD:\case HANDLER_ERROR:\return r;\default:\log_error_write(srv, __FILE__, __LINE__, "sbs", #x, p->name, "unknown state");\return HANDLER_ERROR;\}\}\return HANDLER_GO_ON;\}PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_RAW, handle_uri_raw)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_REQUEST_DONE, handle_request_done)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE, handle_connection_close)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST, handle_subrequest)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST_START, handle_subrequest_start)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_JOBLIST, handle_joblist)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_DOCROOT, handle_docroot)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_PHYSICAL, handle_physical)
PLUGIN_TO_SLOT(PLUGIN_FUNC_CONNECTION_RESET, connection_reset)
复制代码

于是就有了,

handler_t plugins_call_handle_uri_clean(server *srv, connection *con) {\...
}
...

这些生成函数只是函数名不同,实现都是一样的。从server的plugin_slots中找到函数(x作为数组下标)对应的slot;然后对该slot里面的每个plugin调用此函数。当然要查看调用的返回值。根据返回值判定下一步的动作,例如,HANDLER_GO_ON的情况下会继续调用其他plugin的函数。

PLUGIN_TO_SLOT的第二次定义,以及用它生成的Plugin API:

复制代码
#undef PLUGIN_TO_SLOT#define PLUGIN_TO_SLOT(x, y) \handler_t plugins_call_##y(server *srv) {\...}
...
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_TRIGGER, handle_trigger)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SIGHUP, handle_sighup)
PLUGIN_TO_SLOT(PLUGIN_FUNC_CLEANUP, cleanup)
PLUGIN_TO_SLOT(PLUGIN_FUNC_SET_DEFAULTS, set_defaults)#undef PLUGIN_TO_SLOT
复制代码

PLUGIN_TO_SLOT的第三次定义并不是用来生成函数的。这个稍后我们会看到。

对照plugin的VFT,我们发现还有一个函数没有对应的plugins_call_xxx函数,即init。它和其他函数原型不同又只此一个,故单独定义。

handler_t plugins_call_init(server *srv) {...
}

然后是它的实现,哦!怎么又是PLUGIN_TO_SLOT?(第三次定义)

复制代码
#define PLUGIN_TO_SLOT(x, y) \if (p->y) { \plugin **slot = ((plugin ***)(srv->plugin_slots))[x]; \if (!slot) { \slot = calloc(srv->plugins.used, sizeof(*slot));\((plugin ***)(srv->plugin_slots))[x] = slot; \} \for (j = 0; j < srv->plugins.used; j++) { \if (slot[j]) continue;\slot[j] = p;\break;\}\}
复制代码

有了它,就可以初始化plugin_slots了,

复制代码
    ps = srv->plugins.ptr;...srv->plugin_slots = calloc(PLUGIN_FUNC_SIZEOF, sizeof(ps));for (i = 0; i < srv->plugins.used; i++) {...plugin *p = ps[i];PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean);...PLUGIN_TO_SLOT(PLUGIN_FUNC_SET_DEFAULTS, set_defaults);
#undef PLUGIN_TO_SLOTif (p->init) {if (NULL == (p->data = p->init())) {...}...((plugin_data *)(p->data))->id = i + 1;if (p->version != LIGHTTPD_VERSION_ID) {...}} else {p->data = NULL;}}
复制代码

具体的就不介绍了,最终的结果就是上面那张图。除了初始化slot外,plugins_call_init还需要,工作是调用每个plugin的init函数。

3.3 Plugins API的返回值

Plugin API返回值是handler_t(这名字起的。。),调用者根据它的不同返回执行不同操作。但是这些返回值的名字只能算作Plugin API 对调用这的“建议”,并非所有调用做相同的处理,有的甚至不检查这些返回值。

复制代码
typedef enum { HANDLER_UNSET,HANDLER_GO_ON,HANDLER_FINISHED,HANDLER_COMEBACK,HANDLER_WAIT_FOR_EVENT,HANDLER_ERROR,HANDLER_WAIT_FOR_FD
} handler_t;
复制代码

4. Plugins如何工作

各个plugin所实现的虚函数(callbacks)是在它所对应的plugins_call_xxx函数中被调用的,那这些函数又是在什么时候被调用呢?这个是整个plugin工作的关键。

4.1 lugins_load

在Lighttpd server初始化阶段(main()函数)中被调用。

4.2 plugins_call_init

在Lighttpd server初始化阶段(main()函数)中被调用。调用各个Plugin虚函数的init实例。

4.3 plugins_call_set_defaults

在Lighttpd server初始化阶段(main()函数)中被调用,调用各个Plugin虚函数的实例,将server的配置读到各个Plugin私有结构中。

4.4 plugins_call_cleanup

在plugins_free中被调用,后者在server正常或者异常退出的时候被调用。

4.5 plugins_call_trigger

在worker进程的SIGALRM处理时被调用,也就是周期性每秒被调用一次。

4.6 plugins_call_sighup

在worker进程的handle_sig_hup阶段处理,SIGHUP在watcher进程收到SIGHUP后,向所有进程组中的进程转发。

4.7 plugins_call_connection_close

在connection 状态机中,的RESPONSE_END状态下,如果不是KEEP-Alive,会关闭连接,在此之前,先调用该函数。
在connection 状态机中,的ERROR状态下,如果非DIRECT模式,则调用此函数,并在稍后关闭连接。

4.8 plugins_call_connection_reset

在connection_reset()中调用,后者在

    1. 获取新的空conn (connections_get_new_connection)
    2. 释放连接(connections_free)
    3. 各种原因(正常、或异常)接关闭之后

等处多处被调用。

4.9 plugins_call_handle_uri_raw / plugins_call_handle_uri_clean

要理解调用这两个函数的时机,先要理解什么是raw和clean的uri。

复制代码
typedef struct {buffer *scheme; /* scheme without colon or slashes ( "http" or "https" ) *//* authority with optional portnumber ("site.name" or "site.name:8080" ) * NOTE: without "username:password@" */buffer *authority;/* path including leading slash ("/" or "/index.html") * - urldecoded, and sanitized  ( buffer_path_simplify() && buffer_urldecode_path() ) */buffer *path;buffer *path_raw; /* raw path, as sent from client. no urldecoding or path simplifying */buffer *query; /* querystring ( everything after "?", ie: in "/index.php?foo=1", query is "foo=1" ) */
} request_uri;
复制代码

Server从request URL中提取原始的uri部分,包括scheme,authority(即hostname),path,query(‘?’之后,'#'之前),以及fragment(‘#’之后的部分)。要注意的是path_raw和path的区别,path_raw是指尚未经过decoding、simplifying的“原始的path”,例如,包含"%20"之类的转移,"../"之类妄想逃离chroot范围的字段尚未清除的情况。

看看http_response_prepare是如何处理URL的,

复制代码
     handler_t http_response_prepare(server *srv, connection *con) {...if (con->conf.is_ssl) {buffer_copy_string_len(con->uri.scheme, CONST_STR_LEN("https"));} else {buffer_copy_string_len(con->uri.scheme, CONST_STR_LEN("http"));}  buffer_copy_string_buffer(con->uri.authority, con->request.http_host);buffer_to_lower(con->uri.authority);.../** their might be a fragment which has to be cut away */if (NULL != (qstr = strchr(con->request.uri->ptr, '#'))) {con->request.uri->used = qstr - con->request.uri->ptr;con->request.uri->ptr[con->request.uri->used++] = '\0';}/** extract query string from request.uri */if (NULL != (qstr = strchr(con->request.uri->ptr, '?'))) {buffer_copy_string    (con->uri.query, qstr + 1);buffer_copy_string_len(con->uri.path_raw, con->request.uri->ptr, qstr - con->request.uri->ptr);} else {buffer_reset     (con->uri.query);buffer_copy_string_buffer(con->uri.path_raw, con->request.uri);}...}
复制代码

这一步部分只是完成“提取”工作,path_row, query等还都是“原始”的,此时调用便是调用plugins_call_handle_uri_raw的时机。

然后继续对uri进程处理decode url-encoding,以及simplifying,处理完之后uri.path被设置。

            buffer_copy_string_buffer(srv->tmp_buf, con->uri.path_raw);buffer_urldecode_path(srv->tmp_buf);buffer_path_simplify(con->uri.path, srv->tmp_buf);

之后就可以调用plugins_call_handle_uri_clean了。

4.10 plugins_call_docroot

还是http_response_prepare中,话说处理完uri,提取并转码了path而得到con->uri.path之后。需要吧逻辑地址转换为物理地址。先记录下doc_root,和rel_path到physical结构中。之后,调用plugins_call_docroot。而此plugin可能会设置con->server_name,如果没有设置,使用默认值,

        buffer_copy_string_buffer(con->physical.doc_root, con->conf.document_root);buffer_copy_string_buffer(con->physical.rel_path, con->uri.path);

4.11 plugins_call_request_done

connection 状态机RESPONSE_END的时候调用。另一个地方是ERROR状态下,如果http_status已经决定的话。

4.12 plugins_call_joblist

worker进程的主循环的中,会对每个处于joblist的con调用,state_machine,以及plugins_call_joblist。此外network_server_handle_fdevent中,每accept一个新的conn,会调用state_machine以及plugins_call_joblist。

4.13 plugins_call_subrequest_start

当http_response_prepare已经吧physical路径设置完后,会做一些检查,文件是否存在、path是不是目录,如果是目录,需要重定向到index文件。而整个检查基于cache系统,即先从cache中查看。如果没有再查看实际的文件。

然后就会调用plugins_call_subrequest_start,简而言之,在设置、检查完physical path后调用。

4.14 plugins_call_subrequest

在http_response_prepare最后调用,如果之前没有因有些原因出去的话。

作者: beacer
出处: http://www.cnblogs.com/beacer/
本文为原创,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
内部代码来自开源软件,对代码的使用请参考相应软件的许可证。

这篇关于lighttpd - Plugin: Overview的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/179200

相关文章

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

RabbitMQ练习(AMQP 0-9-1 Overview)

1、What is AMQP 0-9-1 AMQP 0-9-1(高级消息队列协议)是一种网络协议,它允许遵从该协议的客户端(Publisher或者Consumer)应用程序与遵从该协议的消息中间件代理(Broker,如RabbitMQ)进行通信。 AMQP 0-9-1模型的核心概念包括消息发布者(producers/publisher)、消息(messages)、交换机(exchanges)、

Jenkins 通过 Version Number Plugin 自动生成和管理构建的版本号

步骤 1:安装 Version Number Plugin 登录 Jenkins 的管理界面。进入 “Manage Jenkins” -> “Manage Plugins”。在 “Available” 选项卡中搜索 “Version Number Plugin”。选中并安装插件,完成后可能需要重启 Jenkins。 步骤 2:配置版本号生成 打开项目配置页面。在下方找到 “Build Env

Exception in plugin Android ButterKnife zelezny

所在页面的布局文件命名id有问题,不能有两个下划线,,如tv__name

【异常】java.sql.SQLException: Unable to load authentication plugin ‘caching_sha2_password‘.

异常现象 执行mysql数据库操作的时候,出现以下异常信息: java.sql.SQLException: Unable to load authentication plugin 'caching_sha2_password'.at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:868) ~[mysql-connector-

Android studio Plugin is too old 问题

Plugin is too old, please update to a more recent version, or set ANDROID_DAILY_OVERRIDE environment variable to ........ 将默认的  classpath 'com.android.tools.build:gradle:2.2.0-beta2' 修改成 clas

CMake Overview

Reference CMake Tutorial CMakeList.txt 对于每个project,需要在目录里有一个CMakeList.txt文件。 Command 这个文件由一系列的命令组成,每个命令的形式为: command(args...) command是命令的名字,不区分大小写;args是命令的参数,而各个参数以空格分割。——如果参数中包括空格,则用双引号括起来。

vue-skeleton-webpack-plugin類庫使用記錄

vue-skeleton-webpack-plugin vue-cli3 未实现开发阶段注入骨架屏样式 若在vue.config.js 开启 css.extract = true 开发阶段无法实现样式热加载   在路由層面和首頁層面都加入骨架屏 構建的時候會報錯 [Vue warn]: Property or method "_ssrNode" is not defined on the

tensorrt plugin

自定义plugin 流程 首先明确要开发的算子,最好是 CUDA 实现;继承 IPluginV2DynamicExt / IPluginV2IOExt类实现一个Plugin 类,在这里调用前面实现的算子;继承 IPluginCreator 类实现一个 PluginCreator 类,用于创建插件实例,然后注册该 Creator 类;编译插件项目,生成动态链接库;在构造 engine 之前,

Jenkins Environment Injector Plugin 插件详解

引言 在做自动化测试的过程中,我们需要经常发送测试报告给相关研发、产品和上级,但是Jenkins邮件模板不支持Javascritpt脚本来动态生成数据,只支持静态的HTML代码,那么我们就没有办法了吗?非也,我们可以通过环境变量注入的方式读取外部的数据进行引用从而实现报告数据的真实性和实时性,下面将详细介绍Jenkins环境变量注入插件Environment Injector Plugin的使用