使用upstream和subrequest访问第三方服务
本文是对陶辉《深入理解Nginx》第5章内容的梳理以及实现,代码和注释基本出自此书。
一、upstream:以向nginx服务器的请求转化为向google服务器的搜索请求为例
(一)模块框架
首先要明确的是,这里是编写一个使用upstream的模块,而不是编写upstream模块。因此,和HelloWorld类似,模块结构体ngx_http_mytest_module、模块上下文结构体ngx_http_mytest_module_ctx、数组ngx_http_mytest_command[]、方法ngx_http_mytest()和ngx_http_mytest_handler()的框架是不可少而且又十分相似的。如果忘记了它们之间的关系,请回顾原书或《深入理解Nginx》阅读与实践(一):Nginx安装配置与HelloWorld。
模块处理的请求是ngx_http_request_t结构对象r,它包含了一个ngx_http_upstream_t类型的成员upstream。当upstream非NULL时,将会根据其中设置的内容定制访问第三方服务的方式。而这个请求的处理是由upstream模块来完成的。从这里开始,要注意区分请求r中的upstream成员和Nginx提供的upstream模块不是一回事,而是由前者来指导后者的工作。前者的设定与开启(即告知upstream模块需要进行处理,通过ngx_http_upstream_init()实现)是由我们编写的的第三方模块(本文中是mytest)来完成的。
(二)upstream设置
upstream工作方式的配置可以通过填写由ngx_http_upstream_create(ngx_http_request_t *r)所传入的请求r中的ngx_http_upstream_t结构体来完成。这个函数成功返回时,即把请求r中的upstream设置为非NULL。ngx_http_upstream_t结构体主要包含了一下几个成员,由于原书对这里模块编写所需要用到的成员已做详细介绍(暂时用不到的成员在12章介绍),这里只做一个部分的概括:

typedef ngx_http_upstream_s ngx_http_upstream_t;
sturct ngx_http_upstream_s {
... ngx_chain_t request_bufs;//发给上游服务器的请求,由create_request()完成 ngx_http_upstream_conf_t conf;//超时时间等限制性参数 ngx_http_upstream_resolved_t resolved;//用于直接指定的上游服务器地址 //设定方法请见mytest模块的ngx_http_mytest_handler()方法 /* 3个必须实现的回调方法 */ ngx_int_t (*create_request)(ngx_http_request_t *r);//构造向上游服务器发送的请求内容。调用mytest时,只调用一次 ngx_int_t (*process_header)(ngx_http_request_t *r);//收到上游服务器后对包头进行处理的方法 void (*finalize_request) (ngx_http_request_t *r, ngx_int_t rc);//销毁upstream请求时调用 /* 5个可选的回调方法,本文中用不到*/ ngx_int_t (*input_filter_init)(void *data);//处理上游包体 ngx_int_t (*input_filter)(void *data,ssize_t bytes);//处理上游包体 ngx_int_t (*reinit_request)(ngx_http_request_t *r);//第一次向上游服务器建立连接失败时调用 void (*abort_request)(ngx_http_request_t *r); ngx_int_t (*rewrite_redirect)(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix); //主要用于反向代理 ...
}

可见,使用upstream功能时,除了需要按HelloWorld编写自己的模块和提供处理配置项的方法ngx_http_mytest_create_loc_conf()、ngx_http_mytest_merge_loc_conf()外,还需要填写ngx_http_upstream_t结构体并实现3个必备的回调方法。要注意的是,这些回调方法都是由模块的编写者提供、再由upstream模块来调用的。
(三)配置项获取
原书例子是将访问的URL请求/test?lumia转化成对www.google.com的搜索请求/search?q=lumia。为了简化,大部分参数采用硬编码的形式nginx.conf的添加的内容和以前一样:

location /test {
mytest;
}


原书的ngx_http_mytest_create_loc_conf()和ngx_http_mytest_merge_loc_conf()

另外根据作者网页上的源码,需要补充上ngx_http_proxy_hide_headers作为默认设置:

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
};

(四)3个必备的回调函数的实现
首先定义ngx_http_mytest_ctx_t结构体用于保存process_header()方法的解析状态,注意结构体的第二个成员原书没有写,需要补充上。

typedef struct {
ngx_http_status_t status;
ngx_str_t backendServer;
} ngx_http_mytest_ctx_t;

原书上3个回调函数的代码如下,详细的注释请参考原书:

mytest_upstream_create_request()构造请求


mytest_process_status_line()处理响应行,mytest_upstream_process_header()处理包头


mytest_upstream_finalize_request()释放资源

值得注意的是mytest_upstream_create_request()中计算queryLineLen中有一项-2。这是因为格式控制符"%V"是会被替换成要输出的变量的,在len成员里计算了它的长度,需要减去。这种处理不要忽略,在subrequest的mytest_post_handler()中也出现了类似的处理。
(五)修改ngx_http_mytest_handler()
完成的工作是关联HTTP上下文与请求、填写upstream配置结构体和调用ngx_http_upstream_init()启动upstream。

ngx_http_mytest_handler()

(六)测试
启动nginx,在浏览器中输入http://localhost:8080/test?lumia,可以看到返回的是http://www.google.com.hk/search?q=lumia。
(8080是作者提供的下载源码中nginx.conf设置的侦听端口号;使用hk是由于被重定向了)
(七)附注
1.URL中的问号"?"代表什么?它在参数传递时有什么用?
答:GET方法中的参数请求以问号开始。换句话说,这个"?"后面跟随的是GET方法的参数。
2.HTTP响应行、HTTP头部、HTTP包体的区分
(下面的请求和应答例子来自于维基百科)
客户端请求:
GET / HTTP/1.1
Host:www.google.com
(末尾有一个空行。第一行指定方法、资源路径、协议版本;第二行是在1.1版里必带的一个header作用指定主机)
服务器应答:
HTTP/1.1 200 OK
Content-Length: 3059
Server: GWS/2.0
Date: Sat, 11 Jan 2003 02:44:04 GMT
Content-Type: text/html
Cache-control: private
Set-Cookie: PREF=ID=73d4aef52e57bae9:TM=1042253044:LM=1042253044:S=SMCc_HRPCQiqy
X9j; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
Connection: keep-alive
...
在这个包头中,第一行就是HTTP响应行,HTTP/1.1表示支持的版本,200是HTTP状态码,表示处理成功,OK是对状态码200的一个简短描述。
根据RFC2616,可能使用“状态行”来描述会更好一些?毕竟本文中处理它的函数是mytest_process_status_line(),而且《TCP/IP详解(卷三)》也翻译为“状态行”。下面用“状态行”代替。
Status-Line The first line of a Response message is the Status-Line, consisting of the protocol version followed by a numeric status code and its associated textual phrase, with each element separated by SP characters. No CR or LF is allowed except in the final CRLF sequence.Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
除了第一行,其余部分中有一部分是HTTP响应头部,即Response Header Fields,它们提供无法放入状态行的信息。RFC2616很清楚的表明,状态行和HTTP头部是两回事:
The response-header fields allow the server to pass additional information about the response which cannot beplaced in the Status- Line. These header fields give information about the server and about further access to the resource identified by the Request-URI.response-header = Accept-Ranges ; Section 14.5
| Age ; Section 14.6
| ETag ; Section 14.19
| Location ; Section 14.30
| Proxy-Authenticate ; Section 14.33
| Retry-After ; Section 14.37
| Server ; Section 14.38
| Vary ; Section 14.44
| WWW-Authenticate ; Section 14.47
在响应头部后,可能还有实体(Entity),而它又分为实体头部(Entity Header Fields)和实体主体(Entity Body),这在RFC2616是进行区分的:
7.1 Entity Header FieldsEntity-header fields define metainformation about the entity-body or,if no body is present, about the resourceidentified by the request. Someof this metainformation is OPTIONAL; some might be REQUIRED by portions of
this specification. entity-header = Allow ; Section 14.7
| Content-Encoding ; Section 14.11
| Content-Language ; Section 14.12
| Content-Length ; Section 14.13
| Content-Location ; Section 14.14
| Content-MD5 ; Section 14.15
| Content-Range ; Section 14.16
| Content-Type ; Section 14.17
| Expires ; Section 14.21
| Last-Modified ; Section 14.29
| extension-header extension-header = message-header
阅读《深入理解Nginx》第3.6.3节可以看出,Nginx是将Response Header Fields和Entity Header Fields合称为HTTP头部一并处理的。
可见,造成理解混乱的原因可能是RFC2616进行区分的Response Header Fields和Entity Header Fields两部分被Nginx一步处理所致。
最后再看看实体主体,可以视之为HTTP传送的正文:
7.2 Entity Body The entity-body (if any) sent with an HTTP request or response is in a format and encoding defined by the entity-header fields.
这样,就把这几个名词的脉络理清楚了。
二、subrequest:以向nginx发出请求转化为向新浪股票数据发出请求为例
(一)subrequest工作流程
subrequest由HTTP框架提供,可以把原始请求分解为许多子请求。
阅读原书5.4和5.5节,可以把使用subrequest的流程概括为:
[HTTP请求需要调用mytest模块处理] -> [mytest模块创建子请求] -> [发送并等待上游服务器处理子请求的响应]
-> (可选)[postpone模块将待转发相应包体放入链表并等待发送 ]
->[执行子请求处理完毕的回调方法ngx_http_post_subrequest_pt]
->[执行父请求被重新激活后的回调方法mytest_post_handler]
这部分使用了代理模块,但在这里不做详细介绍。下面的代码中没有使用postpone。
(二)配置项设置
由于子请求需要访问新浪的服务器,并且URL为http://hq.sinajs.cn/list=s_sh000001,因此设置为

location /list {
//上游服务器地址
proxy_pass http://hq.sinajs.cn;
//不希望第三方服务对HTTP包体进行gzip压缩
proxy_set_header Accept-Encoding "";
}

同时,用户访问nginx服务器mytest模块的URI依然要进行配置

location /query {
mytest;
}

(三)请求上下文
新浪服务器返回的数据格式是这样的:
var hq_str_s_sh000001="上证指数,2070.369,-15.233,-0.73,1023439,8503131";
因此把只用来保存请求回调方法中的股票数据的请求上下文定义如下:

typedef struct {
ngx_str_t stock[6];
} ngx_http_mytest_ctx;

(四)回调方法的实现

mytest_subrequest_post_handler()子请求结束时的回调方法


mytest_post_handler()父请求的的回调方法


mytest_post_handler()用于创建子请求

mytest_post_handler()中的-6的含义与上文upstream部分mytest_upstream_create_request()中计算queryLineLen的-2类似,不再重述。
(五)测试
模块安装后并开启nginx后,输入下面的内容即可看到返回的内容(nginx.conf设置侦听端口号为8080):

http://localhost:8080/query?s_sh000001

另外,如果发现没有响应,请在当前环境(如虚拟机)中尝试直连http://hq.sinajs.cn/list=s_sh000001以保证网络连通性。
本文完整源代码请到《Nginx深入理解》作者陶辉提供的支持页面下载。
作者:五岳
出处:http://www.cnblogs.com/wuyuegb2312
对于标题未标注为“转载”的文章均为原创,其版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
使用upstream和subrequest访问第三方服务的更多相关文章
- 《深入理解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 ...
- nginx 访问第三方服务(1)
nginx提供了两种全异步方式来与第三方服务通信,分别是upstream和subrequest. upstream:nginx为代理服务器,作消息透传.将第三方服务的内容原封不动的返回给用户. sub ...
- Nginx:访问第三方服务
参考资料<深入理解Nginx> Nginx可以当做一个强大的反向代理服务器,其反向代理模块是基于upstream方式实现的. upstream的使用方式 HTTP模块在处理任何一个请求时都 ...
- SAP云平台上的ABAP编程环境里如何消费第三方服务
在ABAP On-Premises环境下,使用ABAP编程消费第三方服务,相信很多ABAP顾问都已经非常熟悉了,无非就是使用CL_HTTP_CLIENT或者CL_REST_HTTP_CLIENT来发送 ...
- Hexo+NextT第三方服务调用【4】
该系列博客列表请访问:http://www.cnblogs.com/penglei-it/category/934299.html 摘要 静态站点与动态站点有很大的不一样,它拥有一定的局 ...
- Java 后端开发常用的 10 种第三方服务
请肆无忌惮地点赞吧,微信搜索[沉默王二]关注这个在九朝古都洛阳苟且偷生的程序员.本文 GitHub github.com/itwanger 已收录,里面还有我精心为你准备的一线大厂面试题. 严格意义上 ...
- Java 10 种常用第三方服务
严格意义上说,所有软件的第三方服务都可以自己开发,不过从零到一是需要时间和金钱成本的.就像我们研发芯片,投入了巨大的成本,但仍然没有取得理想的成绩,有些事情并不是一朝一夕,投机取巧就能完成的. Jav ...
随机推荐
- js 正则练习之语法高亮
原文:js 正则练习之语法高亮 学了几天正则,差不多该总结整理写成果了,之前就想写语法高亮匹配来着,不过水平不够,看着例子都不理解.今天就分析下 次碳酸钴 和 Barret Lee 语法高亮实现. 先 ...
- Windows系统下Redis的安装
Redis是一个用的比较广泛的Key/Value的内存数据库,新浪微博.Github.StackOverflow 等大型应用中都用其作为缓存,Redis的官网为http://redis.io/. 最近 ...
- boost进程间通信经常使用开发一篇全(消息队列,共享内存,信号)
本文概要: 敏捷开发大家想必知道并且评价甚高,缩短开发周期,提高开发质量.将大project独立为不同的小app开发,整个开发过程,程序可用可測,所以提高了总体的质量.基于这样的开发模式和开发理念,进 ...
- <C++ 实现设计模式> 观察者模式
观察者模式,又称公布--订阅,mvc模式等. 通俗点讲,比方股票来说,非常多人关注一支股票,派一个人去观察股票的情况,一有变化(观察),就通知全部的预定这个消息的人. 而我们常见的mvc模式,v是指v ...
- 使用jQuery热门功能实现
非常多站点上都有返回顶部的效果,本文阐述怎样使用jquery实现返回顶部button. 首先须要在顶部加入例如以下html元素: <p id="back-to-top"> ...
- Oracle 跨库 查询 复制表数据 分布式查询
方法一: 在眼下绝大部分数据库有分布式查询的须要.以下简单的介绍怎样在oracle中配置实现跨库訪问. 比方如今有2个数据库服务器,安装了2个数据库.数据库server A和B.如今来实如今A库中訪问 ...
- 如何通过js给QQ好友发送信息
一般我们在做页面活动的时候可能会碰到点击一个按钮把一些相关的信息通过QQ发送给你的好友,这种信息推送的功能该如何实现呢!下面我来介绍下使用方法! 代码如下: <!DOCTYPE HTML> ...
- HDU2093--考试排名
考试排名 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submissi ...
- [翻译]初识SQL Server 2005 Reporting Services Part 4
原文:[翻译]初识SQL Server 2005 Reporting Services Part 4 这一篇是关于SQL Server 2005 Reporting Services四篇文章中最后一篇 ...
- Lucene.net入门学习
Lucene.net入门学习(结合盘古分词) Lucene简介 Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,即它不是一个完整的全 ...