Nginx:访问第三方服务
参考资料<深入理解Nginx>
Nginx可以当做一个强大的反向代理服务器,其反向代理模块是基于upstream方式实现的。
upstream的使用方式
HTTP模块在处理任何一个请求时都有一个ngx_http_request_t结构对象r,而该对象又有一个ngx_http_upstream_t类型的成员upstream。
typedef struct ngx_http_request_s ngx_http_request_t
struct ngx_http_request_s {
...
ngx_http_upstream_t *upstream;
...
};
如果要启用upstream机制,那么关键就在于如何设置r->upstream成员。
1.启用upstream机制
下图列出了使用HTTP模块启用upstream机制的示意图:
下面给出我们的mytest模块的ngx_http_mytest_handler方法中启动upstream机制的大概流程
static ngx_int_t
ngx_http_mytest_handler(ngx_http_request_t *r)
{
...
//创建upstream,创建之前r->upstream==NULL;
ngx_http_upstream_create(r);
ngx_http_upstream_t *u=r->upstream;
//设置第三方服务器地址(通过设置resovled成员)
u->resolved->sockaddr=..;
u->resolved->socklen=...;
u->resolved->naddrs=;
//设置upstream的回调方法(后面有这3个方法的描述)
u->create_request=...;
u->process_header=...;
u->finalize_request=...;
//启动upstream
ngx_http_upstream_init(r);
return NGX_DONE;
}
2.upstream的回调方法
upstream有最常用的3个回调方法:create_request、process_header、finalize_request。
create_request回调方法
下图演示了create_request的回调场景。该回调方法一般用来创建发送给上游服务器的HTTP请求(通过设置r->upstream->request_buf)。
process_header回调方法
process_header负责解析上游服务器发来的基于TCP的包头。
当process_header回调方法返回NGX_OK后,upstream模块开始把上游的包体直接转发到下游客户端。
finalize_request回调方法
当请求结束后,将会回调finalize_request方法来释放资源。
需要的数据结构
在编写我们的mytest模块之前应该先了解一下该模块需要的一些数据结构
1.ngx_http_upstream_t结构体
typedef ngx_http_upstream_s ngx_http_upstream_t;
struct ngx_http_upstream_s {
...
//决定发送什么样的请求给上游服务器,在实现create_request方法时需要设置它
ngx_chain_t request_bufs;
//upstream访问时的所有限制性参数
ngx_http_upstream_conf_t conf;
//通过resolved可以直接指定上游服务器地址
ngx_http_upstream_resolved_t resolved;
/*
用于存储接收来自上游服务器的响应内容,由于它会被复用,所以具有多种意义,如:
a.在process_header方法解析上游响应的包头时,buffer中将会保存完整的相应包头
b.当buffering标志位为0时,buffer缓冲区被用于反复接收上游的包体,进而向下游转发
*/
ngx_buf_t buffer; //3个必须的回调方法,详情可以查看上面
ngx_int_t (*create_request)(ngx_http_request_t *r);
ngx_int_t (*process_header)(ngx_http_request_t *r);
void (*finalize_request) (ngx_http_request_t *r,ngx_int_t rc); unsigned buffering:1;
...
};
2.ngx_http_upstream_conf_t结构体
在我们的mytest模块中所有的请求将共享同一个ngx_http_upstream_conf_结构体。在ngx_http_mytest_create_loc_conf方法中创建跟初始化。
typedef struct{
ngx_http_upstream_conf_t upstream;
} ngx_http_mytest_conf_t;
在启动upstream前,先将ngx_http_mytest_conf_t下的upstream成员赋给r->upstream->conf成员。
3.请求上下文
因为Nginx是异步非阻塞的,导致upstream上游服务器的响应包并不是一次性就接收跟解析好的,因此需要上下文才能正确地解析upstream上游服务器的响应包。
在解析HTTP响应行时,可以使用HTTP框架提供的ngx_http_status_t结构:
typedef struct {
ngx_uint_t code;
ngx_uint_t count;
u_char *start;
u_char *end;
} ngx_http_status_t;
把ngx_http_status_t结构放到上下文中,并在process_header解析响应行时使用
typedef struct {
ngx_http_status_t status;
ngx_str_t backendServer;
} ngx_http_mytest_ctx_t;
mytest模块的完整代码
mytest模块指定的上游服务器是www.baidu.com。在nginx.conf应该这样配置
location /test {
mytest;
}
如果我们的请求是/test?lumia,该模块将会把它转化为www.baidu.com的搜索请求/s?wd=lumia,然后返回结果给客户端。
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h> //所有的请求都将共享同一个ngx_http_upstream_conf_t结构体
typedef struct{
ngx_http_upstream_conf_t upstream;
} ngx_http_mytest_conf_t; //HTTP请求的上下文
typedef struct {
ngx_http_status_t status;
ngx_str_t backendServer;
} ngx_http_mytest_ctx_t; //默认设置
static ngx_str_t ngx_http_proxy_hide_headers[] =
{
ngx_string("Date"),
ngx_string("Server"),
ngx_string("X-Pad"),
ngx_string("X-Accel-Expires"),
ngx_string("X-Accel-Redirect"),
ngx_string("X-Accel-Limit-Rate"),
ngx_string("X-Accel-Buffering"),
ngx_string("X-Accel-Charset"),
ngx_null_string
}; //部分函数的声明
static ngx_int_t
mytest_upstream_process_header(ngx_http_request_t *r);
static char *
ngx_http_mytest(ngx_conf_t *cf,ngx_command_t *cmd,void *conf);
static ngx_int_t
ngx_http_mytest_handler(ngx_http_request_t *r); //设置配置项
static ngx_command_t ngx_http_mytest_commands[]={
{
//配置项名称
ngx_string("mytest"),
//配置项类型(可出现的位置,参数的个数)
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS,
//出现了name中指定的配置项后,将会调用该方法处理配置项的参数
ngx_http_mytest,
NGX_HTTP_LOC_CONF_OFFSET,
,
NULL
},
ngx_null_command
}; //处理出现mytest配置项时处理方法(挂载handler函数)
static char *
ngx_http_mytest(ngx_conf_t *cf,ngx_command_t *cmd,void *conf)
{
//找到mytest配置项所属的配置块
ngx_http_core_loc_conf_t *clcf;
clcf=ngx_http_conf_get_module_loc_conf(cf,ngx_http_core_module);
/*
HTTP框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段时
如果请求的主机域名、URI与mytest配置项所在的配置块相匹配,
就将调用我们事先的ngx_http_mytest_handler方法处理这个请求
*/
clcf->handler=ngx_http_mytest_handler;
return NGX_CONF_OK;
} //创建并初始化mytest模块对应的结构体ngx_http_mytest_conf;
static void *ngx_http_mytest_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_mytest_conf_t *mycf;
mycf=(ngx_http_mytest_conf_t *)ngx_pcalloc(cf->pool,sizeof(ngx_http_mytest_conf_t));
if(mycf==NULL){
return NULL;
} //设置ngx_http_upstream_conf_t结构中的各成员
mycf->upstream.connect_timeout=;
mycf->upstream.read_timeout=;
mycf->upstream.send_timeout=;
mycf->upstream.store_access=; mycf->upstream.buffering=;
mycf->upstream.bufs.num=;
mycf->upstream.bufs.size=ngx_pagesize;
mycf->upstream.buffer_size=ngx_pagesize;
mycf->upstream.busy_buffers_size= * ngx_pagesize;
mycf->upstream.temp_file_write_size= * ngx_pagesize;
mycf->upstream.max_temp_file_size= * *; mycf->upstream.hide_headers=NGX_CONF_UNSET_PTR;
mycf->upstream.pass_headers=NGX_CONF_UNSET_PTR;
return mycf;
} /*
upstream模块要求hide_headers不可以为NULL。提供了ngx_http_upstream_hide_headers_hash方法来初始化该成员,
但仅可用在合并配置项的方法内,因此该方法用于初始化hide_headers成员
*/ static char *ngx_http_mytest_merge_loc_conf(ngx_conf_t *cf,void *parent,void *child)
{
ngx_http_mytest_conf_t *prev=(ngx_http_mytest_conf_t *)parent;
ngx_http_mytest_conf_t *conf=(ngx_http_mytest_conf_t *)child; ngx_hash_init_t hash;
hash.max_size=;
hash.bucket_size=;
hash.name="proxy_headers_hash";
if(ngx_http_upstream_hide_headers_hash(cf,&conf->upstream,
&prev->upstream,ngx_http_proxy_hide_headers,&hash)!=NGX_OK)
{
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
} //HTTP模块的定义
static ngx_http_module_t ngx_http_mytest_module_ctx={
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
ngx_http_mytest_create_loc_conf,
ngx_http_mytest_merge_loc_conf
}; //mytest模块的定义
ngx_module_t ngx_http_mytest_module={
NGX_MODULE_V1,
//指向ngx_http_module_t结构体
&ngx_http_mytest_module_ctx,
//用来处理nginx.conf中的配置项
ngx_http_mytest_commands,
//表示该模块的类型
NGX_HTTP_MODULE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
}; //创建发往google上游服务器的请求
static ngx_int_t
mytest_upstream_create_request(ngx_http_request_t *r)
{
//backendQueryLine为format字符串
static ngx_str_t backendQueryLine=
ngx_string("GET /s?wd=%V HTTP/1.1\r\nHost:www.baidu.com\r\nConnection:close\r\n\r\n");
ngx_int_t queryLineLen=backendQueryLine.len+r->args.len-;
ngx_buf_t *b=ngx_create_temp_buf(r->pool,queryLineLen);
//last指向请求的末尾
b->last=b->pos+queryLineLen;
ngx_snprintf(b->pos,queryLineLen,(char *)backendQueryLine.data,&r->args);
//r->upstream->request_bufs包含要发送给上游服务器的请求
r->upstream->request_bufs=ngx_alloc_chain_link(r->pool);
if(r->upstream->request_bufs==NULL)
return NGX_ERROR;
r->upstream->request_bufs->buf=b;
r->upstream->request_bufs->next=NULL; r->upstream->request_sent=;
r->upstream->header_sent=;
r->header_hash=;
return NGX_OK;
} //解析HTTP响应行
static ngx_int_t
mytest_process_status_line(ngx_http_request_t *r)
{
size_t len;
ngx_int_t rc;
ngx_http_upstream_t *u;
//取出请求的上下文
ngx_http_mytest_ctx_t *ctx=ngx_http_get_module_ctx(r,ngx_http_mytest_module);
if(ctx==NULL){
return NGX_ERROR;
} u=r->upstream;
//ngx_http_parse_status_line方法可以解析HTTP响应行
rc=ngx_http_parse_status_line(r,&u->buffer,&ctx->status);
//返回NGX_AGAIN时,表示还没有解析出完整的HTTP响应行
if(rc==NGX_AGAIN){
return rc;
}
//返回NGX_ERROR时,表示没有接收到合法的HTTP响应行
if(rc==NGX_ERROR){
ngx_log_error(NGX_LOG_ERR,r->connection->log,,
"upstream sent no valid HTTP/1.0 header");
r->http_version=NGX_HTTP_VERSION_9;
u->state->status=NGX_HTTP_OK;
return NGX_OK;
}
//解析到完整的HTTP响应行
if(u->state){
u->state->status=ctx->status.code;
}
//将解析出的信息设置到r->upstream->headers_in结构体中。
u->headers_in.status_n=ctx->status.code;
len=ctx->status.end - ctx->status.start;
u->headers_in.status_line.len=len;
u->headers_in.status_line.data=ngx_pnalloc(r->pool,len);
if(u->headers_in.status_line.data==NULL){
return NGX_ERROR;
}
ngx_memcpy(u->headers_in.status_line.data,ctx->status.start,len); //下一步将开始解析HTTP头部。设置process_header回调方法之后在收到的新字符流将由mytest_upstream_process_header解析
u->process_header=mytest_upstream_process_header;
return mytest_upstream_process_header(r);
} //解析HTTP响应头
static ngx_int_t
mytest_upstream_process_header(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_table_elt_t *h;
ngx_http_upstream_header_t *hh;
ngx_http_upstream_main_conf_t *umcf; umcf=ngx_http_get_module_main_conf(r,ngx_http_upstream_module); //循环地解析所有的HTTP头部
for(;;){
//HTTP框架提供的ngx_http_parse_header_line方法,用于解析HTTP头部
rc=ngx_http_parse_header_line(r,&r->upstream->buffer,);
//返回NGX_OK时,表示解析出一行HTTP头部
if(rc==NGX_OK){
//向headers_in.headers这个ngx_list_t链表中添加HTTP头部
h=ngx_list_push(&r->upstream->headers_in.headers);
if(h==NULL){
return NGX_ERROR;
}
//构造刚刚添加的headers链表中的HTTP头部
h->hash=r->header_hash;
h->key.len=r->header_name_end - r->header_name_start;
h->value.len=r->header_end - r->header_start;
//分配存放HTTP头部的内存空间
h->key.data=ngx_pnalloc(r->pool,
h->key.len++h->value.len++h->key.len);
if(h->key.data==NULL){
return NGX_ERROR;
}
h->value.data=h->key.data + h->key.len + ;
h->lowcase_key=h->key.data+h->key.len++h->value.len+; ngx_memcpy(h->key.data,r->header_name_start,h->key.len);
h->key.data[h->key.len]='\0';
ngx_memcpy(h->value.data,r->header_start,h->value.len);
h->value.data[h->value.len]='\0'; if(h->key.len==r->lowcase_index){
ngx_memcpy(h->lowcase_key,r->lowcase_header,h->key.len);
}else{
ngx_strlow(h->lowcase_key,h->key.data,h->key.len);
} //upstream模块会对一些HTTP头部做特殊处理
hh=ngx_hash_find(&umcf->headers_in_hash,h->hash,
h->lowcase_key,h->key.len);
if(hh&&hh->handler(r,h,hh->offset)!=NGX_OK){
return NGX_OK;
}
continue;
}
//返回NGX_HTTP_PARSE_HEADER_DONE时,表示响应中所有的HTTP头部全部解析完毕
if(rc==NGX_HTTP_PARSE_HEADER_DONE){
//根据HTTP协议规定添加两个头部
if(r->upstream->headers_in.server==NULL){
h=ngx_list_push(&r->upstream->headers_in.headers);
if(h==NULL){
return NGX_ERROR;
}
h->hash=ngx_hash(ngx_hash(ngx_hash(ngx_hash(
ngx_hash('s','e'),'r'),'v'),'e'),'r');
ngx_str_set(&h->key,"Server");
ngx_str_null(&h->value);
h->lowcase_key=(u_char *)"server";
}
if(r->upstream->headers_in.date==NULL){
h=ngx_list_push(&r->upstream->headers_in.headers);
if(h==NULL){
return NGX_ERROR;
}
h->hash=ngx_hash(ngx_hash(ngx_hash('d','a'),'t'),'e');
ngx_str_set(&h->key,"Date");
ngx_str_null(&h->value);
h->lowcase_key=(u_char *)"date";
}
return NGX_OK;
}
//如果返回NGX_AGAIN,表示还没有解析到完整的HTTP头部
if(rc==NGX_AGAIN){
return NGX_AGAIN;
}
//其他返回值都是非法的
ngx_log_error(NGX_LOG_ERR,r->connection->log,,
"upstream sent invalid header");
return NGX_HTTP_UPSTREAM_INVALID_HEADER; }
} //释放资源
static void
mytest_upstream_finalize_request(ngx_http_request_t *r,ngx_int_t rc)
{
ngx_log_error(NGX_LOG_DEBUG,r->connection->log,,
"mytest_upstream_finalize_request");
} //handler函数
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
{
//首先建立HTTP上下文结构ngx_http_mytest_ctx_t
ngx_http_mytest_ctx_t *myctx=ngx_http_get_module_ctx(r,ngx_http_mytest_module);
if(myctx==NULL){
myctx=ngx_palloc(r->pool,sizeof(ngx_http_mytest_ctx_t));
if(myctx==NULL){
return NGX_ERROR;
}
//将新建的上下文与请求关联起来
ngx_http_set_ctx(r,myctx,ngx_http_mytest_module);
}
//初始化r->upstream成员
if(ngx_http_upstream_create(r)!=NGX_OK){
ngx_log_error(NGX_LOG_ERR,r->connection->log,,
"ngx_http_upstream create() failed");
return NGX_ERROR;
}
//得到配置结构体ngx_http_mytest_conf_t
ngx_http_mytest_conf_t *mycf=(ngx_http_mytest_conf_t *)
ngx_http_get_module_loc_conf(r,ngx_http_mytest_module);
ngx_http_upstream_t *u=r->upstream;
//用配置文件中的结构体来赋给r->upstream->conf成员
u->conf=&mycf->upstream;
//决定转发包体时使用的缓冲区
u->buffering=mycf->upstream.buffering; //初始化resolved结构体,用来保存上游服务器的地址
u->resolved=(ngx_http_upstream_resolved_t *)ngx_pcalloc(r->pool,sizeof(ngx_http_upstream_resolved_t));
if(u->resolved==NULL){
ngx_log_error(NGX_LOG_ERR,r->connection->log,,
"ngx_pcalloc resolved error. %s.",strerror(errno));
return NGX_ERROR;
} //设置上游服务器地址
static struct sockaddr_in backendSockAddr;
struct hostent *pHost=gethostbyname((char *)"www.baidu.com");
if(pHost==NULL){
ngx_log_error(NGX_LOG_ERR,r->connection->log,,
"gethostbyname fail. %s",strerror(errno));
return NGX_ERROR;
} //访问上游服务器的80端口
backendSockAddr.sin_family=AF_INET;
backendSockAddr.sin_port=htons((in_port_t));
char *pDmsIP=inet_ntoa(*(struct in_addr *)(pHost->h_addr_list[]));
backendSockAddr.sin_addr.s_addr=inet_addr(pDmsIP);
myctx->backendServer.data=(u_char *)pDmsIP;
myctx->backendServer.len=strlen(pDmsIP); //将地址设置到resolved成员中
u->resolved->sockaddr=(struct sockaddr *)&backendSockAddr;
u->resolved->socklen=sizeof(struct sockaddr_in);
u->resolved->naddrs=; //设置3个必须实现的回调方法
u->create_request=mytest_upstream_create_request;
u->process_header=mytest_process_status_line;
u->finalize_request=mytest_upstream_finalize_request; //告诉HTTP框架暂时不要销毁请求
r->main->count++; //启动upstream
ngx_http_upstream_init(r);
//必须返回NGX_DONE
return NGX_DONE; }
Nginx:访问第三方服务的更多相关文章
- nginx 访问第三方服务(1)
nginx提供了两种全异步方式来与第三方服务通信,分别是upstream和subrequest. upstream:nginx为代理服务器,作消息透传.将第三方服务的内容原封不动的返回给用户. sub ...
- 《深入理解Nginx》阅读与实践(三):使用upstream和subrequest访问第三方服务
本文是对陶辉<深入理解Nginx>第5章内容的梳理以及实现,代码和注释基本出自此书. 一.upstream:以向nginx服务器的请求转化为向google服务器的搜索请求为例 (一)模块框 ...
- Nginx模块开发(4)————使用subrequest访问第三方服务
该模块可以完成如下的功能,当我们输入http://你的ip/lcw?s_sh000001时,会使用subrequest方式得到新浪服务器上的上证指数,代码如下: //start from the ve ...
- Nginx模块开发(3)————使用upstream访问第三方服务
该模块可以完成如下的功能,当我们输入http://你的ip/lcwupstream时,会使用upstream方式访问淘宝搜索,打开淘宝搜索的主页面,代码如下: //start from the ver ...
- 使用upstream和subrequest访问第三方服务
本文是对陶辉<深入理解Nginx>第5章内容的梳理以及实现,代码和注释基本出自此书. 一.upstream:以向nginx服务器的请求转化为向google服务器的搜索请求为例 (一)模块框 ...
- SAP云平台上的ABAP编程环境里如何消费第三方服务
在ABAP On-Premises环境下,使用ABAP编程消费第三方服务,相信很多ABAP顾问都已经非常熟悉了,无非就是使用CL_HTTP_CLIENT或者CL_REST_HTTP_CLIENT来发送 ...
- 基于Docker + Consul + Nginx + Consul-Template的服务负载均衡实现(转)
转:https://www.jianshu.com/p/fa41434d444a 前言 上一篇文章使用 Consul 和 Registrator 在 docker 的容器环境中搭建了服务注册和发现集群 ...
- windows+nginx+iis+redis+Task.MainForm构建分布式架构 之 (nginx+iis构建服务集群)
本次要分享的是利用windows+nginx+iis+redis+Task.MainForm组建分布式架构,由标题就能看出此内容不是一篇分享文章能说完的,所以我打算分几篇分享文章来讲解,一步一步实现分 ...
- Windows下将nginx安装为服务运行
今天看到nginx这个小服务器软件正式版更新到了1.4.2,想玩下它.这个服务器软件虽小,但功能强大,是开源软件,有着良好的性能,被很多个人.企业,甚至大型企业所使用! 由于是在Windows下,所以 ...
随机推荐
- 【IDEA】IDEA下maven项目无法提示和使用EL表达式的解决办法
今天在IDEA创建web项目之后发现无法使用EL和JSTL, 一.如果JSP中无法自动提示EL表达式,比如${pageContext.request.contextPath},可在pom.xml的&l ...
- gdb 记录临时变量
gdb ./pgm set logging file log set logging on ... set logging off gdb ./pgm | tee -a log ... file a. ...
- cocos2d学习网址
http://python.cocos2d.org/doc/programming_guide/index.html http://bbs.tairan.com/article-25-1.html h ...
- log4j2 扩展日志级别,支持将系统日志与业务处理日志拆分
项目中,有时候需要对系统中已处理的一些业务数据日志进行提取分析,通常log4j默认提供的日志级别可能不够用,这时候我们就需要对日志级别进行扩展,以满足我们的需求. 本文就简单介绍一下log4j2的日志 ...
- 牛客网 暑期ACM多校训练营(第二场)J.farm-STL(vector)+二维树状数组区间更新、单点查询 or 大暴力?
开心.jpg J.farm 先解释一下题意,题意就是一个n*m的矩形区域,每个点代表一个植物,然后不同的植物对应不同的适合的肥料k,如果植物被撒上不适合的肥料就会死掉.然后题目将每个点适合的肥料种类( ...
- spoj 375 Query on a tree (树链剖分)
Query on a tree You are given a tree (an acyclic undirected connected graph) with N nodes, and edges ...
- 将一个txt里的A和B谈话内容获取出来并分别保存到A和B的txt文件中
import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream;import java.io.Fi ...
- luogu P1446 [HNOI2008]Cards
题目链接 luogu P1446 [HNOI2008]Cards 题解 题意就是求染色方案->等价类 洗牌方式构成成了一个置换群 然而,染色数限制不能用polay定理直接求解 考虑burnsid ...
- SPCOMM控件对串口参数的设置
对于串口来说,一般大家都了解波特率,校验码,数据位之类的参数.然而在实际的数据传输中,有些参数也会影响数据的传输.现总结如下,以便大家查询.在对串口进行编程时,可用portman对串口参数进行跟踪,提 ...
- 设计模式之适配器模式(php实现)
/* github地址:https://github.com/ZQCard/design_pattern * 适配器模式:将一个类的接口转换成客户希望的另外一个接口. * 适配器模式使得原本由于接口不 ...