Nginx 最强大的地方是在于其 HTTP 请求的反向代理,也即常说的七层反向代理。在这一层代理中,通过 Nginx 框架提供的相关配置,我们能在该层将发送过来的 http 协议转换成各种其他的协议比如 fastcgi 协议、uwsgi 协议、grpc、http(高版本协议)、websocket 协议等。这样使用 Nginx 框架,我们可以支持多种应用服务(java web、python web 等)的反向代理
Nginx 从 1.9.0 开始,新增加了一个 stream 模块,用来实现四层协议( TCP 或UDP 协议)的转发、代理或者负载均衡。这层比较简单,只是单纯将 TCP 或 UDP层的流量转发到上游服务器中
Nginx的四层反向代理
前面我们刚开始使用 Nginx 时只是用了 http 指令块,因为是针对 http 请求进行处理。这进行的是四层反向代理,转发 TCP 或者 UDP 协议的报文。针对该层的处理,Nginx 是使用了 stream 模块进行处理,对应的是 stream 指令块,它和 http 指令块非类似,用法几乎一致。stream 指令块里面可以包含 server 指令块,server 指令块里面也可以包含 listen 指令块指定监听的端口、还可以包含 proxy_pass指令,对该端口监听的 tcp 或者 udp 报文进行转发。总之,和 http 指令块大部分用法一致
...
stream {
...
server {
listen 1234;
# 转发四层流量
proxy_pass 192.168.232.132:1234;
}
…
}
...
nginx-1.11.2 版开始, ngx_stream_core_module,也同 http 模块一样支持变量,部分支持变量如下,这和 http 模块也是类似的,甚至连变量名都非常相似
$binary_remote_addr 二进制格式的客户端地址
$baytes_received 从客户端接收的字节数
$bytes_sent 发往客户端的字节数
$hostname 连接域名
$msec 毫秒精度的当前时间
$nginx_version nginx版本
$pid worker进程号
$protocol 通信协议(udp 、 tcp)
$remote_addr 客户端ip
$remote_port 客户端端口
$server_addr 接受链接的服务器ip,计算此变量需要一次系统调用。所以避免系统调用,在listen指令中指定地址并且使用bind参数。
$server_port 请求到达服务器的端口号
$server_name 服务器名称
$session_time 毫秒精度的回话时间(版本1.11.4开始)
$status 会话状态(版本1.11.4开始),可以是以下几个值(200成功 ; 400不能正常解析客户端数据 ; 403 禁止访问 ; 500 服务器内部错误 ; 502网关错误 , 比如上游服务器无法链接 ; 503 服务不可用,比如由于限制链接等措施导致 )
$time_iso8601 ISO 8601时间格式
$time_local 普通日志格式时间戳
Nginx七层反向代理
http 协议的反向代理
nginx七层反向代理处理的是http请求,对应的http协议,如果只是转发http请求, 可以简单使用proxy_pass指令 , 与四层方向代理类似
# 在转发 http请求时, url必须以http或者https开头
Syntax: proxy_pass url;
Default: -
Context: location , if in location , limit_except
在使用proxy_pass指令对http 或者 https 协议进行反向代理时, 需要注意以下问题:
在url不屑道rui 时,会将对应的url 直接转发到上游服务器
在url 携带 uri 时 , 会将location 参数中匹配上的那一段替换为该url
示例配置
...
http {
server {
listen 8000;
location /test {
proxy_pass http://ip:port/xyz;
}
}
server {
listen 9000;
location /test {
proxy_pass http://ip:port;
}
}
}
...
在代理 http 请求 http://主机 ip:8000/test/abc 时,由于 proxy_pass 指令后面的 URL 带了 /xyz 这样的路径,所以转发的请求是 http:// ip:port/xyz/abc,其中匹配的 /test 已经被替换掉了。而对于第二个 http 请求 http://主机ip:9000/test/abc 时,由于 proxy_pass 指令后面直接是上游服务器地址,没有带URI,所以转发的请求为 http://ip:port/test/abc,其中匹配到的/test 并没有被替换
实验
四层反向代理示例
在nginx配置中加入如下内容
…
stream {
server {
listen 8080;
return '8080 server get ip:$remote_addr!\n';
}
server {
listen 8081;
# 注意 , 只有写iP和 port ,不要加上“http:”等这类前缀,这是四层协议
proxy_pass 127.0.0.1:8080;
}
}
…
Curl 一下
[root@localhost data]# curl 192.168.232.13:8080
8080 server get ip:192.168.232.14!
curl: (56) Recv failure: Connection reset by peer
[root@localhost data]# curl 192.168.232.13:8081
8080 server get ip:127.0.0.1!
telnet一下
[root@localhost data]# telnet 192.168.232.13 8080
Trying 192.168.232.13...
Connected to 192.168.232.13.
Escape character is '^]'.
8080 server get ip:192.168.232.14!
Connection closed by foreign host.
[root@localhost data]# telnet 192.168.232.13 8081
Trying 192.168.232.13...
Connected to 192.168.232.13.
Escape character is '^]'.
8080 server get ip:127.0.0.1!
Connection closed by foreign host.
结论: 访问 30 端口时,nginx 帮我们转发 tcp 层流量到 3000 端口,最后
返回了相关响应字符串。
七层反向代理示例
nginx.conf 中加入如下
user root;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8000;
server_name localhost;
return 200 '$uri\n';
}
server {
listen 8001;
location /test {
proxy_pass http://127.0.0.1:8000;
}
}
server {
listen 8002;
location /test {
proxy_pass http://127.0.0.1:8000/xyz;
}
}
}
启动 nginx 后,通过请求服务器的 8000 端口,我们可以知道请求的 uri 值,然后测试经过两种类型的 proxy_pass 配置后最后的 uri 的值。
[root@localhost data]# curl 192.168.232.13:8000
/
[root@localhost data]# curl 192.168.232.13:8000/abc
/abc
[root@localhost data]# curl 192.168.232.13:8001/test
/test
[root@localhost data]# curl 192.168.232.13:8001/test/abc
/test/abc
[root@localhost data]# curl 192.168.232.13:8002/test
/xyz
[root@localhost data]# curl 192.168.232.13:8002/test/abc
/xyz/abc
Websocket 的反向代理
Websocket 是目前比较成熟的技术了, websocket 协议为创建客户端和服务器端需要实时双向通讯的webapp提供了一个选择, 服务器可以向浏览器推送相关消息,这样在前段实现的某个页面中我么可以及时看到服务器的状态变化,而不是实时刷新去获取后台信息 。 目前大部分浏览器都支持websocket协议,例如chrome firefox iE safari opera等 ,数量越来越多。 更多的服务器框架现在也支持websocket 。 在js 、Java、Python中提供websocket开发库,这也使得websocket 协议的广泛应用于web服务的开发中,当然作为浏览器和后台服务的中间代理nginx也一定支持websocket , 这样才能更好的完成代理的功能, 在nginx中通过 ngx_http_proxy_module模块实现websocket反向代理功能。
简单配置如下:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection “upgrade”
上述配置表示将转发的协议提升至 1.1, 同时在转发的 http 请求的头部中加上如下配置
Upgrade : websocket
Connection : upgrade
这两个字段表示请求服务器升级协议为websocket,上游服务器处理完请求后,相应如下报文
HTTP/1.1 101 Switching Protocoli
Upgrade : websocket
Connection: upgrade
这个响应是告诉客户端已成功切换协议,升级为 Websocket 协议。握手成功之后,服务器端和客户端便角色对等,就像普通的 Socket 一样,能够双向通信。不再进行 HTTP 的交互,而是开始 WebSocket 的数据帧协议实现数据交换。默认情况下,连接将会在无数据传输 60 秒后关闭,proxy_read_timeout 参数可以延长这个时间。源站通过定期发送 ping 帧以保持连接并确认连接是否还在使用。
通过以上简简单单的三行配置,我们就能在 Nginx 中轻松实现 Websocket 的反向代理,这也说明了 Nginx
wsgi 的反向代理
首先,理清楚几个概念:
WSGI:全称是 Web Server Gateway Interface,WSGI 只是一种规范,描述 web server如何与 web application 通信的规范。要实现 WSGI 协议,必须同时实现 web server和 web application,当前运行在 WSGI 协议之上的 web 框架有 Flask, Django,这也是目前最流行的 python web 框架。
uwsgi:与 WSGI 一样是一种通信协议,是 uWSGI 服务器的独占协议,用于定义传输信息的类型(type of information),每一个 uwsgi packet 前 4byte 为传输信息类型的描述。
uWSGI:是一个 web 服务器,实现了 WSGI 协议、uwsgi 协议、http 协议等
WSGI 协议其实是定义了一种 server 与 application 解耦的规范,即可以有多个实现 WSGI server 的服务器,也可以有多个实现 WSGI application 的框架,那么就可以选择任意的 server 和 application 组合实现自己的 web 应用。Django,Flask 框架都有自己实现的简单的 WSGI server,一般用于服务器调试,生产环境下直接使用 WSGI server。
Nginx 中将 http 协议的报文转换成 uwsgi 协议的报文,只需要使用 uwsgi_pass指令即可。和 proxy_pass 指令类似,前者转发为 uwsgi 协议的报文,后者代理转发 http 协议的报文。其余用法一致。
Syntax: uwsgi_pass [protocol://]address;
Default: —
Context: location, if in location
官方示例
uwsgi_pass localhost:9000;
uwsgi_pass uwsgi://localhost:9000;
uwsgi_pass suwsgi://[2001:db8::1]:9090;
示例
...
http {
...
server {
listen 9000;
location / {
# 包含uwsgi 请求描述文件
include uwsgi_parame;
# 配置请求传递, socket地址
uwsgi_pass 127.0.0.1:9000;
}
}
}
...