熟悉Nginx,为Nginx编写插件(四) 简单MVC

看了前面三篇文章是不是觉得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}">&lt;&lt;Message{$prev}</a> &nbsp; &nbsp; <a href="?action=hoverlees&disp=msg&msg={$next}">Message{$next}&gt;&gt;</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

Join the Conversation

5 Comments

Leave a Reply to 天涯明月

Your email address will not be published. Required fields are marked *