本篇算是学习Nginx开山之作,后面还有会具体解析源码。
利用Nginx实现了一个登录、留言板、文件上传功能,功能非常简单。但是对于如何开发一个Nginx模块来说已经足够,希望能够起到抛砖引玉的效果,当前nginx版本是1.12.2
一、效果
登录界面: http://192.168.1.10/login.html 用户名和密码随意
首页: http://192.168.1.10/index.html
文件上传:http://192.168.1.10/resources.html
二、搭建
我学习Nginx的版本号是1.12.2,大家可以根据各自具体情况进行学习或者搭建服务。
2.1 增加module
在Nginx源码根目录中创建子目录webservice,将下面config文件以及.c放到其中。
ngx_addon_name=ngx_http_webservice_module
HTTP_MODULES="$HTTP_MODULES ngx_http_webservice_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_webservice_module.c"
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static char*
ngx_http_webservice_login(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t
ngx_http_webservice_login_handler(ngx_http_request_t *r);
static ngx_int_t
ngx_http_webservice_msgs_post_handler(ngx_http_request_t *r);
static char*
ngx_http_webservice_msgs_post(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t
ngx_http_webservice_messages_handler(ngx_http_request_t *r);
static char*
ngx_http_webservice_messages(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void
ngx_http_webservice_msgs_handler(ngx_http_request_t *r);
static void
ngx_http_get_param_value(ngx_log_t *log, char *body, char *key, ngx_str_t *value);
static void
ngx_http_webservice_upload_body_handler(ngx_http_request_t *r);
static ngx_int_t
ngx_http_webservice_upload_handler(ngx_http_request_t *r);
static char*
ngx_http_webservice_upload(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void
ngx_http_webservice_upload_writefile(ngx_pool_t *pool, FILE *file, const char* filename, size_t length);
static char*
ngx_http_webservice_uploadlist(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t
ngx_http_webservice_uploadlist_handler(ngx_http_request_t *r);
typedef struct {
ngx_str_t email;
ngx_str_t subject;
ngx_str_t content;
}ngx_http_webservice_messages_t;
typedef struct ngx_http_webservice_messages_chain_s ngx_http_webservice_messages_chain_t;
struct ngx_http_webservice_messages_chain_s{
ngx_http_webservice_messages_t msg;
ngx_http_webservice_messages_chain_t *next;
};
static ngx_uint_t ngx_http_webservice_messages_count = 0;
static ngx_http_webservice_messages_chain_t *messages_header = NULL;
static ngx_http_module_t ngx_http_webservice_ctx = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
static ngx_command_t ngx_http_webservice_commands[] = {
{
ngx_string("login"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_webservice_login,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
{
ngx_string("messages"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_webservice_messages,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
{
ngx_string("msgs_post"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_webservice_msgs_post,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
{
ngx_string("upload"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_webservice_upload,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
{
ngx_string("uploadlist"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_webservice_uploadlist,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command
};
ngx_module_t ngx_http_webservice_module = {
NGX_MODULE_V1,
&ngx_http_webservice_ctx,
ngx_http_webservice_commands,
NGX_HTTP_MODULE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
};
static char*
ngx_http_webservice_login(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);
clcf->handler = ngx_http_webservice_login_handler;
return NGX_CONF_OK;
}
static ngx_int_t
ngx_http_webservice_login_handler(ngx_http_request_t *r)
{
ngx_int_t rc = ngx_http_discard_request_body(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
{
return rc;
}
//校验用户名和密码
//校验通过跳转到首页
//ngx_buf_t *b;
//b = ngx_palloc(r->pool, sizeof(ngx_buf_t));
#if 0
u_char *filename = (u_char*)"/usr/local/nginx/html/index.html";
b->in_file = 1;
b->file = ngx_palloc(r->pool, sizeof(ngx_file_t));
b->file->fd = ngx_open_file(filename , NGX_FILE_RDONLY|NGX_FILE_NONBLOCK, NGX_FILE_OPEN, 0);
b->file->log = r->connection->log;
b->file->name.data = filename;
b->file->name.len = strlen((const char*)filename);
if (b->file->fd <= 0) {
return NGX_HTTP_NOT_FOUND;
}
if (ngx_file_info(filename, &b->file->info) == NGX_FILE_ERROR) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
r->headers_out.content_length_n = b->file->info.st_size;
ngx_str_t type = ngx_string("text/html");
r->headers_out.content_type = type;
b->file_pos = 0;
b->file_last = b->file->info.st_size;
//清理文件句柄
ngx_pool_cleanup_t *cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_pool_cleanup_t));
if (cln == NULL) {
return NGX_ERROR;
}
cln->handler = ngx_pool_cleanup_file;
ngx_pool_cleanup_file_t *clnf = cln->data;
clnf->fd = b->file->fd;
clnf->name = b->file->name.data;
clnf->log = r->pool->log;
#endif
r->headers_out.status = NGX_HTTP_MOVED_TEMPORARILY;
ngx_table_elt_t *location = ngx_list_push(&r->headers_out.headers);
if (location == NULL) {
return NGX_ERROR;
}
location->hash = 1;
ngx_str_set(&location->key, "Location");
ngx_str_set(&location->value, "/index.html");
r->headers_out.location = location;
r->header_only = 1;
//发送http head
return ngx_http_send_header(r);
//发送http body
//ngx_chain_t out;
//out.buf = b;
//out.next = NULL;
//return ngx_http_output_filter(r, &out);
}
static char*
ngx_http_webservice_messages(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);
clcf->handler = ngx_http_webservice_messages_handler;
return NGX_CONF_OK;
}
ngx_str_t *ngx_http_webservice_getmessages()
{
return NULL;
}
static u_char* ngx_http_webservice_messages_data(ngx_http_request_t *r)
{
ngx_uint_t pos = 0;
u_char *json = NULL;
if (messages_header == NULL) {
return (u_char*)"[]";
}
json = (u_char*)ngx_palloc(r->pool, ngx_http_webservice_messages_count * 1024);//每条记录1024字节
ngx_memset(json, 0, ngx_http_webservice_messages_count * 1024);
if (json == NULL) {
return json;
}
ngx_http_webservice_messages_chain_t *item = messages_header;
json[0] = '[';
pos++;
while (item) {
ngx_sprintf(json+pos, "{\"Email\":\"%s\",\"Subject\":\"%s\",\"Content\":\"%s\"}",
item->msg.email.data,
item->msg.subject.data,
item->msg.content.data);
pos = ngx_strlen(json);
item = item->next;
if (item != NULL) {
json[pos++] = ',';
}
}
json[pos] = ']';
return json;
}
static ngx_int_t
ngx_http_webservice_messages_handler(ngx_http_request_t *r)
{
if (r->method != NGX_HTTP_GET)
{
return NGX_HTTP_NOT_ALLOWED;
}
ngx_int_t rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK)
{
return rc;
}
u_char* body = ngx_http_webservice_messages_data(r);
ngx_int_t len = ngx_strlen(body);
ngx_str_t type = ngx_string("application/json");
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_type = type;
r->headers_out.content_length_n = len;
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, len);
if (b == NULL)
{
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_memcpy(b->pos, body, len);
b->last = b->pos + len;
b->last_buf = 1;
ngx_chain_t out;
out.buf = b;
out.next = NULL;
return ngx_http_output_filter(r, &out);
}
static char*
ngx_http_webservice_msgs_post(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);
clcf->handler = ngx_http_webservice_msgs_post_handler;
return NGX_CONF_OK;
}
static ngx_int_t
ngx_http_webservice_msgs_post_handler(ngx_http_request_t *r)
{
if (r->method != NGX_HTTP_POST)
{
return NGX_HTTP_NOT_ALLOWED;
}
//异步接收body
ngx_uint_t rc = ngx_http_read_client_request_body(r, ngx_http_webservice_msgs_handler);
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
return rc;
}
return NGX_DONE;
}
static void
ngx_http_webservice_msgs_handler(ngx_http_request_t *r)
{
u_char* data_buf = NULL;
size_t content_length = 0;
size_t body_length = 0;
size_t buf_length = 0;
ngx_chain_t *bufs = r->request_body->bufs;
ngx_buf_t *buf = NULL;
//获取body
content_length = r->headers_in.content_length_n;
data_buf = (u_char*)ngx_palloc(r->pool, content_length + 1);
while (bufs)
{
buf = bufs->buf;
bufs = bufs->next;
buf_length = buf->last - buf->pos ;
if(body_length + buf_length > content_length)
{
ngx_memcpy(data_buf + body_length, buf->pos, content_length - body_length);
body_length = content_length ;
break;
}
ngx_memcpy(data_buf + body_length, buf->pos, buf->last - buf->pos);
body_length += buf->last - buf->pos;
}
if ( body_length )
{
data_buf[body_length] = 0;
}
ngx_http_webservice_messages_chain_t *node = NULL;
node = ngx_alloc(sizeof(ngx_http_webservice_messages_chain_t),
r->pool->log);
node->next = messages_header;
messages_header = node;
ngx_http_webservice_messages_count++;
ngx_http_get_param_value(r->pool->log, (char *)data_buf, "email", &node->msg.email);
ngx_http_get_param_value(r->pool->log, (char *)data_buf, "subject", &node->msg.subject);
ngx_http_get_param_value(r->pool->log, (char *)data_buf, "message", &node->msg.content);
//由于不需要返回响应 只需要响应header即可 因此不需要设置output
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = 0;
r->header_only = 1;
//r->headers_out.content_type = type;
//必须要调用ngx_http_finalize_request
ngx_http_finalize_request(r, ngx_http_send_header(r));
return;
}
static void
ngx_http_get_param_value(ngx_log_t *log, char *body, char *key, ngx_str_t *value)
{
char *start, *end;
start = ngx_strstr(body, key);
if (start) {
start = start + ngx_strlen(key) + 1; //跳过'='
end = ngx_strchr(start, '&');
if (end) {
value->len = end - start;
} else {//表示最后一个参数
value->len = body + strlen((const char*)body) - start;
}
value->data = (u_char*)ngx_calloc(value->len+1, log);
ngx_snprintf(value->data, value->len, "%s", start);
} else {
value->len = 0;
value->data = NULL;
}
}
static char*
ngx_http_webservice_upload(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);
clcf->handler = ngx_http_webservice_upload_handler;
return NGX_CONF_OK;
}
static ngx_int_t
ngx_http_webservice_upload_handler(ngx_http_request_t *r)
{
if (r->method != NGX_HTTP_POST)
{
return NGX_HTTP_NOT_ALLOWED;
}
//异步接收body
ngx_uint_t rc = ngx_http_read_client_request_body(r, ngx_http_webservice_upload_body_handler);
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
return rc;
}
return NGX_DONE;
}
static void
ngx_http_webservice_upload_body_handler(ngx_http_request_t *r)
{
u_char* boundary = NULL;
u_char* boundary_tmp = NULL;
size_t boundary_len = 0;
ngx_table_elt_t *content_type = 0;
ngx_buf_t *buf = r->request_body->bufs->buf;
ngx_uint_t i = 0;
char *data = NULL;
char file_name[256] = {0};
char body[256] = {0};
ngx_int_t uploadsize = 0;
FILE *file = NULL;
//获取content_type
content_type = r->headers_in.content_type;
//判断是否存在boundary
boundary_tmp = (u_char*)ngx_strstr(content_type->value.data, "boundary=");
if (boundary_tmp == NULL) {
goto fail403;
}
boundary_tmp += ngx_strlen("boundary=");//指向实际value
// 第一个2 代表-- 第二个2代表最后\r\n
boundary_len = content_type->value.data + content_type->value.len - boundary + 2 + 2;
boundary = (u_char*)ngx_pcalloc(r->pool, boundary_len + 1);//结束符
ngx_snprintf(boundary, boundary_len, "--%s\r\n", boundary_tmp);
//文件上传默认保存在临时文件中,因此需要对文件进行操作
//解析fsize字段 代表上传文件大小
file = fdopen(buf->file->fd, "r");
fgets(body, sizeof(body), file);
if (ngx_strcmp(body, boundary)) {//异常
goto fail403;
}
fgets(body, sizeof(body), file);
if (ngx_strstr(body, "name=\"fsize\"") == NULL) {
goto fail403;
}
fgets(body, sizeof(body), file);
if (ngx_strcmp(body, "\r\n")) {
goto fail403;
}
fgets(body, sizeof(body), file);
uploadsize = ngx_atoi((u_char*)body, ngx_strlen(body)-2);
if (uploadsize == 0) {
goto fail403;
}
fgets(body, sizeof(body), file);
if (ngx_strcmp(body, boundary)) {//异常
goto fail403;
}
// 获取文件名称
fgets(body, sizeof(body), file);
data = ngx_strstr(body, "; filename=\"");
if (data == NULL) {
goto fail403;
}
data += ngx_strlen("; filename=\"");
while (data[i] != '\"') {
file_name[i] = data[i];
i++;
}
fgets(body, sizeof(body), file); //跳过Content-Type:
fgets(body, sizeof(body), file); //跳过\r\n
ngx_http_webservice_upload_writefile(r->pool, file, file_name, uploadsize);
fclose(file);
//由于不需要返回响应 只需要响应header即可 因此不需要设置output
ngx_str_t response = ngx_string("[{\"Status\":\"Success\"}]");
r->headers_out.content_length_n = response.len;
ngx_str_t type = ngx_string("application/json");
r->headers_out.content_type = type;
r->headers_out.status = NGX_HTTP_OK;
ngx_int_t rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
{
return ;
}
ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len);
if (b == NULL)
{
return ;
}
ngx_memcpy(b->pos, response.data, response.len);
b->last = b->pos + response.len;
b->last_buf = 1;
ngx_chain_t out;
out.buf = b;
out.next = NULL;
//必须要调用ngx_http_finalize_request
ngx_http_finalize_request(r, ngx_http_output_filter(r, &out));
return;
fail403:
r->headers_out.status = NGX_HTTP_FORBIDDEN;
r->headers_out.content_length_n = 0;
r->header_only = 1;
//必须要调用ngx_http_finalize_request
ngx_http_finalize_request(r, ngx_http_send_header(r));
return;
}
static void
ngx_http_webservice_upload_writefile(ngx_pool_t *pool, FILE *file, const char* filename, size_t length)
{
FILE *wfile = 0;
char path[256] = {0};
char *buffer = NULL;
buffer = ngx_palloc(pool, length);
ngx_memcpy(path, "/usr/local/nginx/upload/", strlen("/usr/local/nginx/upload/"));
ngx_memcpy(path + strlen(path), filename, ngx_strlen(filename));
fread(buffer, length, 1, file);
wfile = fopen(path, "w");
fwrite(buffer, length, 1, wfile);
fclose(wfile);
return;
}
static char*
ngx_http_webservice_uploadlist(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);
clcf->handler = ngx_http_webservice_uploadlist_handler;
return NGX_CONF_OK;
}
static ngx_int_t
ngx_http_webservice_uploadlist_handler(ngx_http_request_t *r)
{
ngx_buf_t *body = NULL;
ngx_chain_t out;
if (r->method != NGX_HTTP_GET)
{
return NGX_HTTP_NOT_ALLOWED;
}
ngx_int_t rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK)
{
return rc;
}
DIR *dirptr = NULL;
struct stat statbuf;
struct dirent *entry;
if((dirptr = opendir("/usr/local/nginx/upload"))==NULL)
{
printf("opendir failed!");
return 1;
}
else
{
body = ngx_create_temp_buf(r->pool, 1024);
*body->start = '[';
body->last = body->start+1;
chdir("/usr/local/nginx/upload");
while ((entry = readdir(dirptr)))
{
stat(entry->d_name, &statbuf);
if (S_ISDIR(statbuf.st_mode)) {
continue;
}
body->last = ngx_sprintf(body->last, "{\"filename\":\"%s\",\"filesize\":\"%d\"},",
entry->d_name, statbuf.st_size);
if (body->end - body->last < 256) {//可用空间小于256
ngx_buf_t *new_body = ngx_create_temp_buf(r->pool, (body->end - body->start)+512);
ngx_memcpy(new_body->start, body->start, body->last - body->pos);
new_body->last = body->last;
body = new_body;
}
}
*(body->last-1) = ']';//逗号修改为]
closedir(dirptr);
}
body->last_buf = 1;
ngx_str_t type = ngx_string("application/json");
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_type = type;
r->headers_out.content_length_n = body->last - body->pos;
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
{
return rc;
}
out.buf = body;
out.next = NULL;
return ngx_http_output_filter(r, &out);
}
2.2 修改配置文件nginx.conf
user root;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
error_log logs/error.log debug;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location /webui/upload/list {
uploadlist;
}
location /webui/upload {
upload;
}
location /webui/messages {
messages;
}
location /webui/messages/post {
msgs_post;
}
location /webui/login {
login;
}
location / {
root html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}
2.3 创建upload
由于实现了文件上传功能,因此需要创建一个目录用于保存文件,默认是/usr/local/nginx/upload,文件上传前端页面限制了只能上传jpg、gif、png、txt文件,nginx默认是最大上传1M, 代码功能实现比较简陋,如果上传是几字节,可能会出现问题。所以建议上传小于1MB的文件 。
2.4 编译
我们需要在configure阶段,增加我们自定义的模块,即:
./configure --add-module=/root/nginx/nginx-1.16.1/webservice
三、资源文件
为了方便大家学习,我将html页面以及webservice代码上传到csdn中,大家可以前去下载,积分很便宜只有1积分。欢迎大家一起探讨学习。