leevis.com
leevis.com copied to clipboard
nginx支持websocket
概述
http协议不具备服务端主动给客户端发消息,WebSocket的出现,使得浏览器具备了实时双向通信的能力。 WebSocket复用了HTTP的握手通道,而nginx作为http协议代理也需要处理这种变更。
请求协议升级:
GET / HTTP/1.1
Host: www.test.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: xxxxxxx
Connection: Upgrade:表示要升级协议Upgrade: websocket:表示要升级到websocket协议。Sec-WebSocket-Version: 13:表示websocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。Sec-WebSocket-Key: xxxxxxx:与后面服务端响应首部的Sec-WebSocket-Accept是配套的,提供基本的防护,比如恶意的连接,或者无意的连接。
响应协议升级
HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: yyyyyyy
101 Switching Protocols表示协议切换。到此完成协议升级,后续的数据交互都按照新的协议来。
nginx 支持websocket的配置
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close; # 如果需要支持回源长连接,则close需要改为keep-alive
}
server {
...
location /chat/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
代码分析
在ngx_http_proxy_process_header回调解析resp的header时候,如果状态码是101,且有请求头有upgrade,则赋u->upgrade = 1;
static ngx_int_t
ngx_http_proxy_process_header(ngx_http_request_t *r) {
...
if (u->headers_in.status_n == NGX_HTTP_SWITCHING_PROTOCOLS) {
u->keepalive = 0;
if (r->headers_in.upgrade) {
u->upgrade = 1;
}
}
...
}
static void
ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u)
{
...
if (u->upgrade) {
#if (NGX_HTTP_CACHE)
if (r->cache) {
ngx_http_file_cache_free(r->cache, u->pipe->temp_file);
}
#endif
// 协议升级
ngx_http_upstream_upgrade(r, u);
return;
}
...
}
static void
ngx_http_upstream_upgrade(ngx_http_request_t *r, ngx_http_upstream_t *u)
{
ngx_connection_t *c;
ngx_http_core_loc_conf_t *clcf;
c = r->connection;
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
/* TODO: prevent upgrade if not requested or not possible */
if (r != r->main) {
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"connection upgrade in subrequest");
ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
return;
}
r->keepalive = 0;
c->log->action = "proxying upgraded connection";
u->read_event_handler = ngx_http_upstream_upgraded_read_upstream;
u->write_event_handler = ngx_http_upstream_upgraded_write_upstream;
r->read_event_handler = ngx_http_upstream_upgraded_read_downstream;
r->write_event_handler = ngx_http_upstream_upgraded_write_downstream;
if (clcf->tcp_nodelay) {
if (ngx_tcp_nodelay(c) != NGX_OK) {
ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
return;
}
if (ngx_tcp_nodelay(u->peer.connection) != NGX_OK) {
ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
return;
}
}
if (ngx_http_send_special(r, NGX_HTTP_FLUSH) == NGX_ERROR) {
ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
return;
}
if (u->peer.connection->read->ready
|| u->buffer.pos != u->buffer.last)
{
ngx_post_event(c->read, &ngx_posted_events);
ngx_http_upstream_process_upgraded(r, 1, 1);
return;
}
ngx_http_upstream_process_upgraded(r, 0, 1);
}