前不久Nginx官方放出了SPDY的patch,到目前为止都还未合并进nginx源码,主要还是由于此patch还远不成熟,代码和功能都还不足够完善。个人感觉spdy patch合并进nginx源码还有些时日。本文是基于目前的patch,初窥一下nginx官方是如何在实现spdy。

上图是nginx处理一个请求的大致流程,这里只是绘制了简单的模型,实际过程还是相当的复杂的。图中红色部分即是SPDY patch的主要内容。
针对普通的http请求,nginx是采用了一个状态机来流式的解析http协议,(所谓流式解析就是“收到一个字节就解析一个字节“,不必一定要收齐多少字节才开始解析),最终将请求的所有数据存储到一个request对象中,在接下来的handler模块中,就可以处理这个request对象了。一个请求的处理工作完成后,就开始经过一层一层的filter模块将响应给发出去。
面对一个spdy请求,nginx总体处理流程和http请求一样,主要不同的地方就是请求的解析器变了,但spdy的解析结果也是完全的将数据存储到普通http使用的request对象中,并没有引入一个新的spdy request对象,这就有点像是将spdy请求透明的转化为http请求。也只有将spdy请求转化为http请求,目前的所有handler、filter模块才能够正常的工作。由于经过handler、filter模块后的响应是一个http的响应,所以必须引入一个spdy filter模块将http响应的内容组装成一个spdy的帧消息来应答客户端。
下面再继续看看SPDY解析处理过程的细节,spdy filter模块做的事情比较简单,本文就暂且忽略了。
看过nginx源码的读者应该都知道客户端发送一个请求给nginx时,nginx首先将被调用的一个函数是ngx_http_init_request,在此函数中重点关注如下代码段:
#if (NGX_HTTP_SSL){ngx_http_ssl_srv_conf_t  *sscf;sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module);if (sscf->enable || addr_conf->ssl) {if (c->ssl == NULL) {c->log->action = "SSL handshaking";if (addr_conf->ssl && sscf->ssl.ctx == NULL) {ngx_log_error(NGX_LOG_ERR, c->log, 0,"no \"ssl_certificate\" is defined ""in server listening on SSL port");ngx_http_close_connection(c);return;}#if (NGX_HTTP_SPDY)if (addr_conf->spdy) {r->spdy_stream = (void *) 1; //FIXME}
#endifif (ngx_ssl_create_connection(&sscf->ssl, c, 0) // NGX_SSL_BUFFER) FIXME!= NGX_OK){ngx_http_close_connection(c);return;}rev->handler = ngx_http_ssl_handshake;}r->main_filter_need_in_memory = 1;}}#endif

由于spdy是强制ssl的,所以必须走这段逻辑,然后进入ngx_http_ssl_handshake过程完成ssl的握手认证。这些过程也只是普通的https请求需要经历步奏罢了,那么在整个ssl握手完成后,nginx是如何判断此请求时https还是spdy呢?看下面一段代码:

static void
ngx_http_ssl_handshake_handler(ngx_connection_t *c)
{ngx_http_request_t  *r;r = c->data;#if (NGX_HTTP_SPDY)r->spdy_stream = NULL; //FIXME
#endifif (c->ssl->handshaked) {/** The majority of browsers do not send the "close notify" alert.* Among them are MSIE, old Mozilla, Netscape 4, Konqueror,* and Links.  And what is more, MSIE ignores the server's alert.** Opera and recent Mozilla send the alert.*/c->ssl->no_wait_shutdown = 1;#if (NGX_HTTP_SPDY){unsigned       len;const u_char  *data;// 这个地方就是使用TLS-NPN扩展获取spdy协议的判断,也是spdy请求的处理入口。
// NPN在spdy介绍中已经提到,是google特地为spdy的部署而开发的一个tls扩展协议。SSL_get0_next_proto_negotiated(c->ssl->connection, &data, &len);if (len == sizeof("spdy/2") - 1&& ngx_memcmp(data, "spdy/2", sizeof("spdy/2") - 1) == 0){#if (NGX_STAT_STUB)(void) ngx_atomic_fetch_add(ngx_stat_reading, -1);(void) ngx_atomic_fetch_add(ngx_stat_requests, -1);
#endifc->data = c; //FIXME// 初始化spdy,开始真正的进入spdy处理过程。ngx_http_init_spdy(c->read);return;}}
#endifc->log->action = "reading client request line";c->read->handler = ngx_http_process_request_line;/* STUB: epoll edge */ c->write->handler = ngx_http_empty_handler;ngx_http_process_request_line(c->read);return;}ngx_http_close_request(r, NGX_HTTP_BAD_REQUEST);return;
}

此函数式ssl握手完成后,开始进入真正的请求数据处理的入口函数。经过 SSL_get0_next_proto_negotiated识别为spdy后,就开始真正的进入spdy处理过程了。省略性的看看ngx_http_init_spdy最后的几行关键代码。

void
ngx_http_init_spdy(ngx_event_t *rev)
{//。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。一开始基本都在初始化zlib相关,为请求和响应的压缩和解压做好准备。//// 这里挂载了事件回调函数,接下来将重点关注读事件上挂载的回调,这个过程将完成spdy请求的解析。rev->handler = ngx_http_spdy_read_handler;c->write->handler = ngx_http_spdy_write_handler;ngx_http_spdy_read_handler(rev);
}

在spdy请求解析过程中,读事件rev上始终挂载着ngx_http_spdy_read_handler这个回调函数,它的作用当然就是读事件发生的时候,从socket上读出数据,然后把数据交给spdy解析器。 在ngx_http_spdy_read_handler函数中有如下一代码段:

        do {
// sc 是一个ngx_http_spdy_connection_t结构,抽象的是一个tcp连接。这里调用的handler
// 就是此刻挂载的spdy解析器。不同的frame消息这里将会挂载不同的解析器。rc = sc->handler(sc, &p, n);n = end - p;if (rc == NGX_AGAIN) {ngx_memcpy(sc->buffer, p, NGX_SPDY_STATE_BUFFER_SIZE);break;}if (rc == NGX_ERROR) {ngx_log_error(NGX_LOG_WARN, c->log, 0, "SPDY ERROR");ngx_http_spdy_finalize_connection(sc,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}} while (n);

本文提到的spdy解析器,就是处理spdy不同frame的函数,不了解frame的可以看看我写的Google spdy介绍。frame处理的入口是ngx_http_spdy_process_frame:

static ngx_int_t
ngx_http_spdy_process_frame(ngx_http_spdy_connection_t *sc, u_char **pos,size_t size)
{u_char                  *p, flags;size_t                   length;uint32_t                 head;ngx_http_spdy_stream_t  *stream;// frame的8个字节头部信息没有读完整,返回去继续等待数据。这就不是流式解析了,因为此处一定要满足8字节后才开始解析过程。if (size < 8) {return NGX_AGAIN;}p = *pos;// 将头4字节转化为一个整数再进行比较判断,因为头4字节决定了frame的类型。
#if (NGX_HAVE_NONALIGNED)head = *(uint32_t *) p;
#elsehead = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
#endif// 取第5字节的flags域和最后3个字节的长度域。flags = p[4];length = ngx_spdy_frame_parse_len(p + 5);sc->length = length;sc->flags = flags;ngx_log_debug3(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,"spdy process frame head:%ui f:%ui l:%ui",head, flags, length);*pos += 8;// 这里就是根据frame的类型,挂载不同的frame解析器。switch中列举的case全部是control frame。这里有如此之多的TODO,就可以看出spdy patch的代码还不完善。switch (head) {case NGX_SPDY_SYN_STREAM_HEAD:sc->handler = ngx_http_spdy_process_syn_stream;return NGX_OK;case NGX_SPDY_SYN_REPLY_HEAD://TODO logreturn NGX_ERROR;case NGX_SPDY_RST_STREAM_HEAD:sc->handler = ngx_http_spdy_process_rst_stream;return NGX_OK;case NGX_SPDY_SETTINGS_HEAD://TODOsc->handler = ngx_http_spdy_skip_frame;return NGX_OK;case NGX_SPDY_NOOP_HEAD:if (flags != 0 || length != 0) {//TODO logreturn NGX_ERROR;}return NGX_OK;case NGX_SPDY_PING_HEAD:sc->handler = ngx_http_spdy_process_ping;return NGX_OK;case NGX_SPDY_GOAWAY_HEAD://TODOsc->handler = ngx_http_spdy_skip_frame;return NGX_OK;case NGX_SPDY_HEADERS_HEAD://TODO logreturn NGX_ERROR;}// 到此说明收到的frame不是control frame,而是data frame,所以下面开始data frame的逻辑。head = ntohl(head);// 判断control和data frame的标志位是否是0,不是0此时就是非法的,需要忽略此frame消息。if (head >> 31) {//TODO version & type checkngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,"spdy unknown frame %ui", head);sc->handler = ngx_http_spdy_skip_frame;return NGX_OK;}// 根据头中的sid判断此data frame属于哪个stream。 stream = ngx_http_spdy_get_stream_by_id(sc, head);if (stream == NULL || stream->request->discard_body) {sc->handler = ngx_http_spdy_skip_frame;return NGX_OK;}if (stream->half_closed) {//TODO log && error handlingreturn NGX_ERROR;}// 挂载data frame解析器,开始着手解析data frame。sc->stream = stream;sc->handler = ngx_http_spdy_process_data_frame;return ngx_http_spdy_process_data_frame(sc, pos, size - 8); //FIXME
}

到此处就完成frame的判断,这是所有frame的入口之处,接下去就是执行一个frame解析器解析具体的frame了。SYN_STREAM control frame正是创建一个stream,开始发起一个请求,下面就看看这个frame的处理过程,其他的frame就不在本文分析了。

static ngx_int_t
ngx_http_spdy_process_syn_stream(ngx_http_spdy_connection_t *sc, u_char **pos,size_t size)
{u_char                    *p;ngx_uint_t                 sid, prio, index;ngx_http_cleanup_t        *cln;ngx_http_request_t        *r;ngx_http_spdy_stream_t    *stream;ngx_http_spdy_srv_conf_t  *sscf;// 这里又一个需要收齐数据才能处理的地方,这里的10个字节就是Stream-ID, Associated-To-Stream-ID以及优先级等。if (size < 10) {return NGX_AGAIN;}p = *pos;sc->length -= 10;*pos += 10;// 解析stream id和stream的优先级。sid = ngx_spdy_frame_parse_sid(p);prio = p[5] >> 2;ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->connection->log, 0,"spdy SYN_STREAM frame sid:%ui prio:%ui", sid, prio);sscf = ngx_http_get_module_srv_conf(sc->default_request,ngx_http_spdy_module);// sscf->concurrent_streams是配置文件里指定的每个连接上允许的最大并发stream个数。if (sc->processing == sscf->concurrent_streams) {ngx_http_spdy_send_rst_stream(sc, sid, NGX_SPDY_REFUSED_STREAM);sc->handler = ngx_http_spdy_skip_headers;return NGX_OK;}// 创建好普通的http request对象。r = ngx_http_spdy_create_request(sc);if (r == NULL) {return NGX_ERROR;}// 创建一个新的stream。stream = ngx_pcalloc(r->pool, sizeof(ngx_http_spdy_stream_t));if (stream == NULL) {return NGX_ERROR;}r->spdy_stream = stream;stream->id = sid;stream->request = r;stream->connection = sc;stream->priority = prio;// 注意此处,flags域设置了FIN_FLAG就表示此stream不会收到data frame了,也就是一个普通http get操作,没有body数据。所以此处将stream设置为half close状态。stream->half_closed = sc->flags & NGX_SPDY_FLAG_FIN;。。。。。。。。。。。。。。。。。。。。。。。。sc->stream = stream;// 解析来开始解析headers了。sc->handler = ngx_http_spdy_process_headers;return NGX_OK;
}

现在开始解析headers,具体的解析过程这里不做分析,有兴趣的读者可以对照spdy协议draft文档去分析代码。我们就看看ngx_http_spdy_process_headers最后几行代码:

static ngx_int_t
ngx_http_spdy_process_headers(ngx_http_spdy_connection_t *sc, u_char **pos,size_t size)
{int                         z;ngx_buf_t                  *buf;ngx_int_t                   rc;ngx_uint_t                  last;ngx_table_elt_t            *h;ngx_connection_t           *c;ngx_http_request_t         *r;。。。。。。。。。。。。。。。。。。。。。。。。。。。。。sc->processing++;// 这里将要开始执行这个请求了,下面详细看看其过程。ngx_http_spdy_run_request(r);// 这个frame处理完成后,就重置spdy解析器为frame判断入口回调函数,准备处理下一个frame。sc->handler = ngx_http_spdy_process_frame;return NGX_DONE;
}static void
ngx_http_spdy_run_request(ngx_http_request_t *r)
{ngx_uint_t                  i;ngx_list_part_t            *part;ngx_table_elt_t            *h;ngx_connection_t           *fc;ngx_http_header_t          *hh;ngx_http_core_main_conf_t  *cmcf;// 根据spdy的请求数据去构造普通http的请求行,这就是前面提到的spdy到http的转化过程。if (ngx_http_spdy_construct_request_line(r) != NGX_OK) {ngx_http_spdy_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);return;}。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;// 处理header,也是在转化spdy到http。if (ngx_http_process_request_header(r) != NGX_OK) {return;}if (r->plain_http) {ngx_log_error(NGX_LOG_INFO, fc->log, 0,"client sent plain HTTP request to HTTPS port");ngx_http_spdy_finalize_request(r, NGX_HTTP_TO_HTTPS);return;}#if (NGX_STAT_STUB)(void) ngx_atomic_fetch_add(ngx_stat_reading, -1);r->stat_reading = 0;(void) ngx_atomic_fetch_add(ngx_stat_writing, 1);r->stat_writing = 1;
#endifr->write_event_handler = ngx_http_core_run_phases;// 开始执行请求,主要执行所有的phases阶段,这里就开始进入了普通http请求的流程了。
// 接下去就是经过所有的handler,filter等模块流程。ngx_http_core_run_phases(r);ngx_http_run_posted_requests(fc);
}

总结:

spdy patch的实现还是非常的清晰,主要目标就两个:1、是解析spdy frame,2、是转化为了http request。从代码可以看出此patch仅仅是一个初步的实现,还有很多的地方没有完善,后面nginx官网在实现上指不定还有大的调整也不奇怪。当然,spdy的server push等复杂又强大功能在此patch中根本还没有一点实现。

Nginx SPDY patch实现相关推荐

  1. Nginx容器动态流量管理方案-nginx-upsync-module+nginx_upstream_check_module初体验

    缘起 最近一直在研究日志收集系统的框架,之前在线游戏的数据发送都是由游戏服务器发送的,我来之前一直是rsync传输,也还算稳定.但现在上了单机游戏,只能由手机客户端直接发送,dau比较高,最近很火的& ...

  2. Nginx服务器---正向代理

    Nginx服务器-----正向代理 文章目录 Nginx服务器-----正向代理 前言 一.nginx是什么? 二.代理服务 1.什么是代理呢?下面一张图了解一下 2.正向代理 2.1什么是正向代理呢 ...

  3. nginx正向代理的配置和使用

    nginx正向代理的配置和使用 nginx正向代理的配置和使用 nginx正向代理的配置和使用 安装包准备 下载nginx安装包 下载正向代理模块的包 版本与模块对照表 部署nginx服务 上传ngi ...

  4. nginx安装第三方模块nginx_upstream_check_module

    一.nginx后端健康检查 nginx自带健康检查的缺陷: Nginx只有当有访问时后,才发起对后端节点探测. 如果本次请求中,节点正好出现故障,Nginx依然将请求转交给故障的节点,然后再转交给健康 ...

  5. gzip 与 deflate :gzip算法原理深入分析

    gzip 与 deflate :gzip算法原理深入分析 gzip 与 deflate :gzip算法原理深入分析 - gzip 与 deflate :gzip算法原理深入分析 [复制链接]     ...

  6. 安全测试--WEB,微信端渗透测试报告

    测试流程: 1.先用wvs扫 2.暴力破解 3.稍微看下请求包有什么不妥的地方没有,比如明文传输,cookie放太多个人信息,等等 4.一边看wvs结果可以一边找sql注入点,比如查询的地方 5.一边 ...

  7. nginx--正向代理、反向代理及负载均衡(图解+配置)

    目录 学习背景 一.代理服务 二.正向代理 1.1 什么是正向代理? 1.2 如何实现nginx正向代理? 1.3 客户端访问(验证正向代理) 二.反向代理 2.1 什么是反向代理? 2.2 ngin ...

  8. nginx lua 安装spdy

    关于spdy 摘自 http://zh.wikipedia.org/wiki/SPDY SPDY SPDY是Google开发的基于传输控制协议(TCP)的应用层协议 .Google最早是在Chromi ...

  9. nginx patch补丁方式添加 nginx_upstream_check_module 模块,并测试健康检查

    原创博客地址:陈帅同学-nginx patch补丁方式添加 nginx_upstream_check_module 模块,并测试健康检查 我的测试环境 contos:6.7nginx:1.63 che ...

最新文章

  1. 传惠普下周公布webOS平台最终命运
  2. AutoMapper的介绍与使用(二)
  3. Struts2之拦截器篇
  4. linq to sql 详
  5. 死在路上的创业者们:如何跨越市场推广之坎?
  6. 面向对象学习(1):认识软件系统的复杂性
  7. redhat linux查看进程cpu,Redhat Linux 查看cpu、memory 等命令
  8. python网络编程第三版网盘_Python网络编程(socketserver、TFTP云盘、HTTPServer服务器模型)...
  9. Matlab的两种除法:左除(\)和右除(/)
  10. php表格显示成绩,学生成绩表格展示
  11. 区块链组适应不断变化的监管环境
  12. ex is not shell_linux下环境变量详解
  13. (三)沟通管理风险管理采购管理@相关方管理
  14. 三十六计珍藏版(上)
  15. Ubuntu18.04系统安装及深度学习框架搭建
  16. 竞价推广效果不好,是哪些方面影响的呢?
  17. linux7.4离线内核升级,CentOS 7.4升级Linux内核
  18. IMS/SIP - GCF IMS相关认证测试如何通过看case procedure来得知哪些field/IE必须带?
  19. HTML网页设计-梅西专属
  20. 日历日程安排表格calendar

热门文章

  1. hook函数教程(一)什么是钩子
  2. 你玩的是互联网还是寂寞
  3. 连锁百货企业数据分析系统建设方案
  4. LCL 滤波器的特性分析
  5. 论文:lda2vev:Mixing Dirichlet Topic Models and Word Embeddings to Make lda2vec
  6. Linux重要命令-sar
  7. 给0-2 岁孩子的书单
  8. 按键精灵定位坐标循环_LinkTrack UWB定位正式支持ROS机器人操作系统,驱动开源,自由定制消息格式...
  9. Android中Launcher实例
  10. 8月收到的最新更新附近的人交友系统源码,自动打招呼,自动发视频通话+自动聊天多功能机器人交友源码