看了前面三篇文章是不是觉得Nginx的插件开发很简单?多看看Nginx的源代码对我们的开发插件很有帮助的。这篇文章是计划中的最后一篇,我准备在这篇文章里去实现一个可走通的Nginx MVC模块,现在WEB开发不是流行MVC架构嘛,所以我决定实现一个简单的MVC架构来完结我的连载…(连载上瘾了..)
本文地址:http://www.hoverlees.com/blog/?p=369
MVC就是所谓的Model-View-Controller结构,目的就是把程序和美工分开的方式,而且便于维护,好的Model一般都会提高Controller的开发效率,这个很多相信很多人比我要理解得透彻啦。我所设想的架构如下图形式:
麻雀虽小五脏俱全。Model是Nginx及MVC模块,View是一系列的HTML文件,Controller是一系列的动态链接库。
首先,View和Controller一般都是放在两个目录下的,这两个目录的信息肯定不写死在模块里,那我们就把它作为模块的参数,让它们可以在Nginx的配置文件里配置Controller和View所存放的目录。
OK,这儿我们就不需要更多的配置了,然后,我们决定这个MVC的调用指令为”mvc”,它接收两个参数,一个是Controller存放目录,另一个是View存放目录,下面是设置这个指令的代码,前两章说得比较多,这里就不多说了。
static char actionPath[1024]; //全局变量,以保存配置的路径 static char templatePath[1024]; char* ngx_http_mvc_setup(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){ ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); ngx_str_t* args; args=cf->args->elts; strncpy(actionPath,(char*)args[1].data,args[1].len); actionPath[args[1].len]=0; strncpy(templatePath,(char*)args[2].data,args[2].len); templatePath[args[2].len]=0; //将两个目录的配置信息存入到全局变量。 clcf->handler = ngx_http_mvc_handler; //设置命令的处理函数,这个函数每次调用时会收到ngx_http_request_t结构,即是一个HTTP处理请求时 return NGX_CONF_OK; } static ngx_command_t ngx_http_mvc_commands[] = { { ngx_string("mvc"), NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, //配置中调用方法:mvc action_path template_path ngx_http_mvc_setup, 0, 0, NULL }, ngx_null_command };
上面指定 ngx_http_mvc_handler 为模块的处理函数,这个函数是我们的Model要发生效果的时候了,在这个函数里,我们要做的事如下流程:
- 1.创建一个Model
- 2.根据HTTP的GET参数`action`决定要调用哪个动态链接库,以及通过`disp`参数决定调用这个库的哪个函数。
- 3.调用这个库的这个函数,这个库当然是一个Controller,它的处理函数可调用Model提供的数据和函数,以执行特定功能,并可执行显示模板等操作,并将输出写到Model。
- 4.Controller执行完后,把Model里要输出的数据传递给Nginx
- 5.释放Model.
比如,一个HTTP请求:/mvc?action=hoverlees&disp=msg&… 表示调用hoverlees这个action(Linux下实际是一个动态链接库.so文件)提供的函数msg。后面的即是其它参数。
接下来要定义最核心的Model了,定义Model之前先来定义些Model要用到的东西,一个是Hash表:维护变量,值的对应关系,另一个是输出缓冲:供控制器直接写入数据,并能提供给Nginx的可自动伸长缓冲。
先实现Hash表,我这儿实现的是链式哈希表,就是可加入条目数不限制,但初始化空间太小却数据量很大的话肯定会影响索引效率的。下面还有一个散列函数没有列出来,我是根据字符串前4个字符值的和求余得到散列索引,一般来说是很散的,HOHO~
typedef struct _HOVER_HASH_ENTRY{ char* key; int type; void* data; struct _HOVER_HASH_ENTRY* next; }HOVER_HASH_ENTRY; //条目结构,维护变量和值。 typedef struct _HOVER_HASH_TABLE{ HOVER_HASH_ENTRY** items; int size; int length; }HOVER_HASH_TABLE; //哈希表结构 HOVER_HASH_TABLE* hover_hash_table_create(int size); void hover_hash_table_destroy(HOVER_HASH_TABLE* ht); int hover_hash_table_add_or_update(struct _HOVER_HASH_TABLE* this,HOVER_HASH_ENTRY* entry); int hover_hash_table_unset(struct _HOVER_HASH_TABLE* this,const char* key); int hover_hash_table_find(struct _HOVER_HASH_TABLE* this,const char* key,HOVER_HASH_ENTRY** ret);
我觉得这些函数没必要解释太多,知道哈希表数据结构的人应该都很清楚。
接下来是输出缓冲管理,它的实现是为了让Model能提供给Controller可以直接写入数据到流览器的自动伸长缓冲。
typedef struct _HOVER_OUTPUT_BUFFER{ char* data; //数据指针 int size; //当前大小 int len; //已使用大小 int incAmount;//自动伸长量 }HOVER_OUTPUT_BUFFER; HOVER_OUTPUT_BUFFER* hover_create_output_buffer(int initSize,int incAmount); int hover_output_buffer_write(HOVER_OUTPUT_BUFFER* ob,const char* data,int len); //写入数据到缓冲区 void hover_output_buffer_free(HOVER_OUTPUT_BUFFER* ob);
这个也简单,没太多需要说的。
接下来是要实现主要的Model了,Model不仅要维护请求的数据,还需要提供供Controller调用的一系列函数,例如显示模板等,真正要做一个优质的Model的话,Model需要维护很多的数据,并要提供很多的函数方便调用,我这儿的例子维护的就比较少。
#define HOVER_MVC_MODEL_GET_ARG_FIELD 1 //find httpArgs item #define HOVER_MVC_MODEL_GET_VAR_FIELD 2 //find variables item which action can set by assign function. typedef struct _HOVER_NGX_MVC_MODEL{ //public fields char* tplPath; //模块存放目录 char* actionPath; //Controller目录 ngx_http_request_t* request; //Nginx传来的request参数 //private fields HOVER_HASH_TABLE* variables; //保存可供View使用的变量 HOVER_HASH_TABLE* httpArgs; //HTTP请求参数 HOVER_OUTPUT_BUFFER* outputBuffer; //输出缓冲 void* dlHandle; //加载的Controller库 int (*disp)(struct _HOVER_NGX_MVC_MODEL* model); //Controller提供的函数,函数只接收Model指针 //model functions 可供Controller调用的函数 int (*assign) (struct _HOVER_NGX_MVC_MODEL* this,const char* name,void* value); //设置变量借模板调用,跟PHP的Smarty类似 int (*show_template) (struct _HOVER_NGX_MVC_MODEL* this,const char* templateName); //显示模板,这个示例只解析模板内的{$变量名}标签,把它转换成Model里存储的真正的变量值。真正的Model至少需要解析循环,逻辑判断等基本的标签。 int (*write) (struct _HOVER_NGX_MVC_MODEL* this,const char* data,int len); //Controller写入到浏览器的方法 int (*get) (struct _HOVER_NGX_MVC_MODEL* this,int field,const char* key,HOVER_HASH_ENTRY** ret); //获取variables,或httpArgs中的数据。 }HOVER_NGX_MVC_MODEL; //Model的内部函数,公共函数会直接附值到HOVER_NGX_MVC_MODEL中供Controller调用。 HOVER_NGX_MVC_MODEL* hoverlees_new_model(ngx_http_request_t* r); //创建Model void hoverlees_free_model(HOVER_NGX_MVC_MODEL* m); //释放Model HOVER_HASH_TABLE* hover_parse_arg_to_array(char* argstr); //把Nginx传来的HTTP参数解析到 int hover_dispatch_action(HOVER_NGX_MVC_MODEL* this,const char* action,const char* dispatch); //执行Controller,这个函数会加载action对应的动态链接库,并执行它的dispatch函数。 //公共函数的真正原型,上面的结构体声明内有说明。 int hover_mvc_assign(HOVER_NGX_MVC_MODEL* this,const char* name,void* value); int hover_mvc_show_template(HOVER_NGX_MVC_MODEL* this,const char* templateName); int hover_mvc_write_ob(HOVER_NGX_MVC_MODEL* this,const char* data,int len); int hover_mvc_get(HOVER_NGX_MVC_MODEL* this,int field,const char* key,HOVER_HASH_ENTRY** ret);
接下来是MVC处理函数 ngx_http_mvc_handler 的实现,它的流程在前面已经说过。
static ngx_int_t ngx_http_mvc_handler(ngx_http_request_t *r){ ngx_int_t rc; ngx_buf_t *b; ngx_chain_t out; HOVER_HASH_ENTRY *ret; const char* action,*disp; //create the Model.创建一个Model HOVER_NGX_MVC_MODEL* model=hoverlees_new_model(r); model->tplPath=templatePath; model->actionPath=actionPath; //get action and dispatch 取得要调用Controller的信息 if(hover_hash_table_find(model->httpArgs,"action",&ret)){//用PHP的说法,就是$action=$_GET['action'] action=ret->data; } else{ //如果没有值,就使用defaultAction,Model将加载defaultAction.so。 action="defaultAction"; } if(hover_hash_table_find(model->httpArgs,"disp",&ret)){ disp=(char*)ret->data; } else{ disp="defaultDispatch"; } b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); if (b == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } out.buf = b; out.next = NULL; //dispatch action 调用Controller hover_dispatch_action(model,action,disp); //pass the output buffer to nginx 把结果提交给Nginx输出。 b->pos = (u_char*)model->outputBuffer->data; b->last = (u_char*)model->outputBuffer->data + model->outputBuffer->len; b->memory = 1; b->last_buf = 1; r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = model->outputBuffer->len; rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return rc; } //give data to nginx ngx_int_t result=ngx_http_output_filter(r, &out); //free the model 最后释放Model hoverlees_free_model(model); return result; }
一个Model知道调用哪个Controller后,就从actionPath目录下加载对应的动态链接库,并执行相关的函数,函数里可以初始化一系列变量,也可以直接输出内容到流览器,也可以从tplPath目录下加载相应的模板,模板使用Model提供的标签,解析成动态数据后输出。一般来说,Model至少需要提供逻辑判断,变量替换,循环等基本标签(我这儿只提供了变量替换)。
下面是一个简单的Controller例子,这个Controller根据参数`msg`显示不同的信息。
//定义需要显示的消息列表 static char* data[]={"This is a simple Nginx MVC Module","You can change `msg` to show messages.",\ "Model is a nginx module called `mvc`.","View is a template file which can use tags supported by the model.(Only one tag...)",\ "Controller is a dynamic linked library which exports dispatches."}; __attribute__ ((visibility("hidden"))) int showMsg(HOVER_NGX_MVC_MODEL* model,char* message){ model->assign(model,"msg",message); model->assign(model,"url","http://www.hoverlees.com"); model->show_template(model,"msg.html"); } __attribute__ ((visibility("hidden"))) int simpleURLDecode(char* url){ //非标准的dispatch函数需要隐藏,否则也会被调用到,这直接导致segment fault. int pos=0; int pos2=0; char c; while(1){ if(url[pos]==0) break; if(url[pos]=='%'){ c=(url[pos+1]-'0')<<4+(url[pos+2]-'0'); url[pos2]=c; pos+=3; } else{ url[pos2]=url[pos]; pos++; } pos2++; } url[pos2]=0; } int defaultDispatch(HOVER_NGX_MVC_MODEL* model){//标准的dispatch函数参数均是一个model指针。 char temp[2048]; int len; len=sprintf(temp,"ActionPath:%s TemplatePath:%s",model->actionPath,model->tplPath); model->write(model,temp,len); return 1; } int msg(HOVER_NGX_MVC_MODEL* model){ char num[16]; HOVER_HASH_ENTRY* ret; if(model->get(model,HOVER_MVC_MODEL_GET_ARG_FIELD,"msg",&ret)){ int index=((char*)ret->data)[0]-'1'; if(index>4 || index<0){ index=0; } sprintf(num,"%d",index); model->assign(model,"prev",num); //向model写入变量供模板调用。 sprintf(num,"%d",index+2); model->assign(model,"next",num); showMsg(model,data[index]); } else{ showMsg(model,"Unknown `msg` parameter!"); } }
msg模板文件如下:
<html> <head> <title>{$msg}</title> </head> <body> <div style="float:right;font-size:7pt;"><a href="{$url}">{$url}</a></div> <h1>Nginx simple MVC module.</h1> <div style="margin:10px; border:#FF9999 solid 1px; background:#FFEEEE; padding:60px; text-align:center;font-size:16pt;"> {$msg} </div> <div style="padding-top:10px;text-align:center;"> <a href="?action=hoverlees&disp=msg&msg={$prev}"><<Message{$prev}</a> <a href="?action=hoverlees&disp=msg&msg={$next}">Message{$next}>></a> </div> </body> </html>
配置Nginx,加入一个location /mvc,参数是Controllers的路径和Views路径。
location /mvc{ mvc /usr/local/nginx/mvc/actions /usr/local/nginx/mvc/templates; }
然后把动态库和模块放到对应目录下,基本的MVC就完成了。
测试一下,达到了预期效果。
由于本文涉及到的具体开发代码太多,所以就只解释了头文件,有兴趣的朋友可以下载源文件。
这几篇文章涉及到的代码在这里下载,其中我新加的代码在/ext目录下。
下载源代码nginx-0.9.3
configure方式:
./configure –add-module=ext/rw –add-module=ext/mvc –add-module=ext/helloworld
必须顶你了!
要怎么才能表达我对您的敬仰有如滔滔江水, 连绵不绝呢
兄台言重了!
都很详细,清晰明了,多谢了
看了你的连载,下载了你的源代码,说声谢谢。