前不久Nginx官方放出了SPDY的patch,到目前为止都还未合并进nginx源码,主要还是由于此patch还远不成熟,代码和功能都还不足够完善。个人感觉spdy patch合并进nginx源码还有些时日。本文是基于目前的patch,初窥一下nginx官方是如何在实现spdy。
#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 } #endif if (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 #endif if (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); #endif c->data = c; //FIXME // 初始化spdy,开始真正的进入spdy处理过程。 ngx_http_init_spdy(c->read); return; } } #endif c->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); }
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; #else head = 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 log return NGX_ERROR; case NGX_SPDY_RST_STREAM_HEAD: sc->handler = ngx_http_spdy_process_rst_stream; return NGX_OK; case NGX_SPDY_SETTINGS_HEAD: //TODO sc->handler = ngx_http_spdy_skip_frame; return NGX_OK; case NGX_SPDY_NOOP_HEAD: if (flags != 0 || length != 0) { //TODO log return 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: //TODO sc->handler = ngx_http_spdy_skip_frame; return NGX_OK; case NGX_SPDY_HEADERS_HEAD: //TODO log return NGX_ERROR; } // 到此说明收到的frame不是control frame,而是data frame,所以下面开始data frame的逻辑。 head = ntohl(head); // 判断control和data frame的标志位是否是0,不是0此时就是非法的,需要忽略此frame消息。 if (head >> 31) { //TODO version & type check ngx_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 handling return 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; #endif r->write_event_handler = ngx_http_core_run_phases; // 开始执行请求,主要执行所有的phases阶段,这里就开始进入了普通http请求的流程了。 // 接下去就是经过所有的handler,filter等模块流程。 ngx_http_core_run_phases(r); ngx_http_run_posted_requests(fc); }
总结: