nginx http模块开发实例(1) - 内容处理模块

2018-07-24 21:57:57

本例子很简单,就是简单的内容生成,处在 NGX_HTTP_CONTENT_PHASE 阶段。 

模块指令

Syntax:	my_first string;
Default: my_first "hello freecls";
Context: location

直接向客户端返回内容字符串,参数为0个或1个。

Syntax:	my_file string;
Default: my_first "test.txt";
Context: location

必须先指定 my_first 指令,my_file 用来指定要返回的文件名,可以是相对路径或者绝对路径。

源代码

文件名 ngx_http_first_module.c。

/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


static char *ngx_http_first(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char *ngx_http_setfname(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void *ngx_http_first_create_conf(ngx_conf_t *cf);
static ngx_int_t ngx_http_first_handler(ngx_http_request_t *r);

typedef struct {
	ngx_uint_t on;     //是否开启本模块
	ngx_str_t content; //返回给客户端的字符串,会被下面指令覆盖

	ngx_uint_t sendf;  //是否返回文件内容
	ngx_str_t fname;   //文件路径
} ngx_http_first_conf_t;

static ngx_command_t  ngx_http_first_commands[] = {

    { ngx_string("my_first"),
      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
	  ngx_http_first,
      0,
      0,
      NULL },

	  { ngx_string("my_file"),
	    NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
		ngx_http_setfname,
	    0,
	    0,
	    NULL },

      ngx_null_command
};


static ngx_http_module_t  ngx_http_first_module_ctx = {
    NULL,                          /* preconfiguration */
    NULL,                          /* postconfiguration */

    NULL,                          /* create main configuration */
    NULL,                          /* init main configuration */

    NULL,                          /* create server configuration */
    NULL,                          /* merge server configuration */

    ngx_http_first_create_conf,    /* create location configuration */
    NULL                           /* merge location configuration */
};


ngx_module_t  ngx_http_first_module = {
    NGX_MODULE_V1,
    &ngx_http_first_module_ctx,      /* module context */
    ngx_http_first_commands,         /* module directives */
    NGX_HTTP_MODULE,               /* module type */
    NULL,                          /* init master */
    NULL,                          /* init module */
    NULL,                          /* init process */
    NULL,                          /* init thread */
    NULL,                          /* exit thread */
    NULL,                          /* exit process */
    NULL,                          /* exit master */
    NGX_MODULE_V1_PADDING
};

//每个location都创建自己独立的 ngx_http_first_conf_t配置。
static void *
ngx_http_first_create_conf(ngx_conf_t *cf)
{
	ngx_http_first_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_first_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    return conf;
}


static char *
ngx_http_first(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
	ngx_str_t  *value;

	value = cf->args->elts;

	ngx_http_first_conf_t *my_cf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_first_module);
	my_cf->on = 1;
	my_cf->sendf = 0;
	if(cf->args->nelts > 1){
		my_cf->content.data = ngx_pcalloc(cf->pool, value[1].len);
		ngx_memcpy(my_cf->content.data, value[1].data, value[1].len);
		my_cf->content.len = value[1].len;
	}else{
		int len = strlen("hello freecls");
		my_cf->content.data = ngx_pcalloc(cf->pool, len);
		ngx_memcpy(my_cf->content.data, "hello freecls", len);
		my_cf->content.len = len;
	}

	//设置回调函数
	ngx_http_core_loc_conf_t  *clcf;

	clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
	clcf->handler = ngx_http_first_handler;

    return NGX_CONF_OK;
}

static char *
ngx_http_setfname(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{

	ngx_str_t  *value;
	value = cf->args->elts;

	ngx_http_first_conf_t *my_cf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_first_module);

	if(my_cf->on != 1){
		return NGX_CONF_OK;
	}

	my_cf->sendf = 1;
	if(cf->args->nelts > 1){
		my_cf->fname.data = ngx_pcalloc(cf->pool, value[1].len);
		ngx_memcpy(my_cf->fname.data, value[1].data, value[1].len);
		my_cf->fname.len = value[1].len;
	}else{
		int len = strlen("test.txt");
		my_cf->fname.data = ngx_pcalloc(cf->pool, len);
		ngx_memcpy(my_cf->fname.data, "test.txt", len);
		my_cf->fname.len = len;
	}

    return NGX_CONF_OK;
}

static ngx_int_t
ngx_http_first_handler(ngx_http_request_t *r)
{
	ngx_buf_t *b;
	ngx_chain_t out;

	if(!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))){
		return NGX_HTTP_NOT_ALLOWED;
	}

        //忽略请求体
	ngx_int_t rc = ngx_http_discard_request_body(r);
	if(rc != NGX_OK){
		return rc;
	}

	ngx_http_first_conf_t *my_cf = ngx_http_get_module_loc_conf(r, ngx_http_first_module);

	//发送测试内容
	if(my_cf->sendf == 0){
		r->headers_out.status = NGX_HTTP_OK;
		r->headers_out.content_length_n = my_cf->content.len;
		r->headers_out.content_type.data = (u_char *)"text/html";
		r->headers_out.content_type.len = strlen("text/html");

		//发送响应头
		rc = ngx_http_send_header(r);
		if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
		    return rc;
		}

		//构造 ngx_buf_t 准备发送响应体
		b = ngx_create_temp_buf(r->pool, my_cf->content.len);
		if (b == NULL) {
			return NGX_ERROR;
		}
		ngx_memcpy(b->pos, my_cf->content.data, my_cf->content.len);
		b->last = b->pos + my_cf->content.len;
		b->last_buf = 1; //声明时最后一块缓冲区

		//必须通过链发出去
		out.buf = b;
		out.next = NULL;

	}else{
		//发送文件内容
		ngx_str_t fname;

		//绝对路径
		if(my_cf->fname.data[0] == '/'){
			fname = my_cf->fname;
		}else{
			ngx_http_variable_value_t  *vv;
			ngx_str_t v_name = ngx_string("realpath_root");
			ngx_uint_t key = ngx_hash_key(v_name.data, v_name.len);

			//获取根目录的绝对路径
			vv = ngx_http_get_variable(r, &v_name, key);
			if(vv == NULL || vv->not_found == 1){
				return NGX_HTTP_NOT_FOUND;
			}

			//将根路径和文件名连在一起
			fname.data = ngx_pcalloc(r->pool, vv->len + my_cf->fname.len + 1);
			u_char *tmp = fname.data;
			ngx_memcpy(tmp, vv->data, vv->len);
			tmp = tmp + vv->len;
			ngx_memcpy(tmp, "/", 1);
			tmp++;
			ngx_memcpy(tmp, my_cf->fname.data, my_cf->fname.len);
			fname.len = vv->len + my_cf->fname.len + 1;
		}

		//易出bug点,如果使用 ngx_palloc,那么 ngx_buf_t 里面没有清零,那么b->temporary可能不为0
		//代表在内存里,那么也就不会读取该文件。所以必须使用 ngx_pcalloc 把内存全部置0
		b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
		b->in_file = 1;
		b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));

		//由于ngx_open_file接受的是有 '\0'结尾的字符串,所以要重新生成
		char *f_name = ngx_pcalloc(r->pool, fname.len+1);
		ngx_memcpy(f_name, fname.data, fname.len);

		b->file->fd = ngx_open_file(f_name, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);

		if (b->file->fd == NGX_INVALID_FILE) {
		    ngx_log_error(NGX_LOG_ERR, r->connection->log, ngx_errno,
		                      ngx_open_file_n " \"%s\" failed", f_name);
		    return NGX_ERROR;
		}

		b->file->log = r->connection->log;
		b->file->name = fname;

		//获取文件信息
		if(ngx_file_info(f_name, &b->file->info) == NGX_FILE_ERROR){
		    return NGX_HTTP_INTERNAL_SERVER_ERROR;
		}

		r->allow_ranges = 1;
		r->headers_out.content_length_n = b->file->info.st_size;
		r->headers_out.status = NGX_HTTP_OK;
		r->headers_out.content_type.data = (u_char *)"text/html";
		r->headers_out.content_type.len = strlen("text/html");

		//发送响应头
		rc = ngx_http_send_header(r);
		if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
			return rc;
		}

		b->file_pos = 0;
		b->file_last = b->file->info.st_size;
		b->last_buf = 1; //声明时最后一块缓冲区
		b->last_in_chain = 1;

		//注册清理文件句柄
		ngx_pool_cleanup_t * cln;
		cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_pool_cleanup_file_t));
		cln->handler = ngx_pool_cleanup_file;

		ngx_pool_cleanup_file_t *clnf = cln->data;
		clnf->fd = b->file->fd;
		clnf->name = (u_char *)f_name;
		clnf->log = r->pool->log;

		//必须通过链发出去
		out.buf = b;
		out.next = NULL;
	}

	return ngx_http_output_filter(r, &out);
}
[root@192 nginx-1.14.0]# ll /tmp/nginx_m/first/
-rw-r--r--. 1 root root  164 Jul 24 21:37 config
-rw-r--r--. 1 root root 8000 Jul 24 21:16 ngx_http_first_module.c
[root@192 nginx-1.14.0]# cat /tmp/nginx_m/first/config 
ngx_addon_name=ngx_http_first_module
HTTP_MODULES="$HTTP_MODULES ngx_http_first_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS /tmp/nginx_m/first/ngx_http_first_module.c"

上面的目录和文件自己新建,然后在 nginx 源码里编译

./configure --prefix=/usr/local/nginx --add-module=/tmp/nginx_m/first/

make

make install

配置例子

location / {
    root html;
}
#返回 hello freecls
location = /first {
    my_first;
}

#返回 /tmp/aaa.txt 文件内容
location = /first_absolute {
    my_first;
    my_file /tmp/aaa.txt;
}

#返回 /usr/local/nginx/html/aaa.txt 文件内容
location = /first_relative {
    my_first;
    my_file aaa.txt;
}


备注

1.测试环境centos7 64位,nginx版本为 1.14.0。
2..原文地址http://www.freecls.com/a/2712/d8


©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。