Nginx之旅-Nginx作为代理服务器

这个小节我们学习下Nginx的代理服务。

在没有代理的情况下 客户端是直接和服务端进行直接连接。当存在代理的时候所有的请求和想要都需要经过代理。

代理

按照应用分类可以分为正向代理和反向代理。

正向代理

正向代理

当我们在一个局域网内想上外网或者想翻墙的时候就需要一个正向代理。代理服务器会通过DNS解析域名请求服务返回给客户端。正向代理代理的是客户端,为客户端提供服务。代理请求DNS服务器。

反向代理

反向代理

反向代理一般用在针对性访问某个站点。需要客户端自己通过DNS获得域名解析。反向代理代理的对象是服务端,是为服务端提供服务的。(如根据路由分发请求等)

参考文章: 正向代理与反向代理的区别

Nginx作为代理的模块

Nginx可支持的代理协议
支持的协议

常见的Nginx作为反向代理支持协议

反向代理支持协议和模块

反向代理模块 Nginx配置模块
http、websocket、https ngx_http_proxy_module
fastcgi ngx_http_fastcgi_module
uwsgi ngx_http_uwsgi_module
grpc ngx_http_v2_module

常见的Nginx作为正向代理支持协议:

反向代理支持协议和模块

注意:不能支持HTTPS协议。

Nginx作为反向代理场景

配置语法:

1
2
3
Syntax: proxy_pass URL;
Default: --
Context: location, if in location, limit_except

URL表示如果我们请求到Nginx代理服务器进行转发到对应的链接。

URL的格式有以下三种

1
2
3
http://localhost:8000/uri/
https://192.169.1.1:8000/uri/
http://unix:/tmp/backend.socket:/uri/

最后一个进程间通信的一个socket方式。

🌰

我们编写一个在8080端口的应用,这个8080是公网无法访问到的

1
2
3
4
5
6
7
8
9
server {
listen 8080;
server_name localhost;

location / {
root /opt/app/code2;
index index.html index.htm;
}
}

在上面应用的家目录下有一个 test_proxy.html 文件

使用代理配置 将 80端口的访问代理到8080

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
listen 80;
server_name localhost;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
# 增加一个匹配规则 当访问是 以 test_proxy.html 结束 将会代理 将请求到80的请求转发到8080上
location ~ /test_proxy.html$ {
proxy_pass http://127.0.0.1:8080;
}

}

这样我们即使在主机8080端口没有开放的情况下,还可以访问到对应的服务。

Nginx作为正向代理场景

在程序服务器上配置只有指定IP能够访问

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
server_name localhost;

location / {
if ( $http_x_forwarded_for !~* "^116\.62\.103\.228") {
return 403;
}
root /opt/app/code;
index index.html index.htm;
}
}

这样如果不是指定IP是不允许访问的。

在程序允许访问的服务器(即代理服务器上做请求转发)来解决只能运行在指定服务器上访问的限制。

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
server_name localhost;

# 指定使用的 DNS 域名解析服务器
resolver 8.8.8.8;

# 将所有请求直接进行转发
location / {
proxy_pass http://$http_host$request_uri;
}
}

完成上面配置之后还需要在客户端配置代理服务器(IP+端口)。因为正向代理代理的是客户端。

我们使用代理插件或者代理工具配置程序允许访问的服务器。

反向代理支持协议和模块

整体流程就是存在一个服务只能运行某些IP进行访问,我们在对应的IP上安装NGINX服务,并开启反向代理功能转发访问受限的请求,除此之外还要再客户端配置一些本地正向代理。即实际是先访问正向代理服务器,然后由正向代理服务器进行反向代理转发,最后获取到受限制的服务。

更多关于代理模块的: 官方文档

其他配置语法–缓冲区:

1
2
3
4
Syntax:	proxy_buffering on | off;
Default:
proxy_buffering on;
Context: http, server, location

扩展:proxy_buffer_size, proxy_busy_buffers_size, proxy_buffers

开启缓冲区是指将客户端请求的数据缓存一些(尽可能的收集完客户端的请求)再发往服务端,内容存在内存中,当内存不够的时候,存在硬盘的临时文件目录下。

其他配置语法–跳转重定向:

1
2
3
4
5
6
Syntax:	proxy_redirect default;
proxy_redirect off;
proxy_redirect redirect replacement;
Default:
proxy_redirect default;
Context: http, server, location

处理301重定向请求。

其他配置语法–头信息:

1
2
3
4
5
Syntax:	proxy_set_header field value;
Default:
proxy_set_header Host $proxy_host;
proxy_set_header Connection close;
Context: http, server, location

扩展配置: proxy_set_body, proxy_hide_header

设置经过代理服务器的IP。

其他配置语法–超时:

1
2
3
4
Syntax:	proxy_connect_timeout time;
Default:
proxy_connect_timeout 60s;
Context: http, server, location

扩展:proxy_read_timeout, proxy_send_timeout

代理服务器到真正服务器之间的超时关系。

一般配置代理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
proxy_redirect default;

proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr; # 将真实的IP带到后端

proxy_connect_timeout 30;
proxy_send_timeout 60;
proxy_read_timeout 60;

proxy_buffer_size 32k;
# 作用:Nginx使用该大小申请read_buf,即大小指定了 upstream header 最大长度,如果响应头超过了这个长度,Nginx会报upstream sent too big header错误,然后client收到的是502。


proxy_buffering on;
# 作用:该指令开启从后端被代理服务器的响应body缓冲。
#如果proxy_buffering开启,nginx假定被代理的后端服务器会以最快速度响应,并把内容保存在由指令proxy_buffer_size 和 proxy_buffers 指定的缓冲区里边。
#如果响应body无法放在内存里边,那么部分内容会被写到磁盘上。
#如果proxy_buffering被关闭了,那么响应body会按照获取body的多少立刻同步传送到客户端。
#nginx不尝试计算被代理服务器整个响应body的大小,nginx能从服务器接受的最大数据,是由指令 proxy_buffer_size指定的。
#对于基于长轮询(long-polling)的Comet 应用来说,关闭 proxy_buffering 是重要的,不然异步响应将被缓存导致Comet无法工作。
#但是无论proxy_buffering是否开启,proxy_buffer_size都是生效的


proxy_buffers 4 128k;
# 作用:设置存储被代理服务器响应的body所占用的buffer个数和每个buffer大小。具体的意思是说,开辟4个长度为128k大小的read_buf用来存储body,当然不是连接建立初始化时就开辟4个,而是当当前buf不够存响应body时才会新申请一个,最多申请4个buf。


proxy_busy_buffers_size 256k;
# 作用:proxy_busy_buffers_size不是独立的空间,他是proxy_buffers和proxy_buffer_size的一部分。
# nginx会在没有完全读完后端响应就开始向客户端传送数据,所以它会划出一部分busy状态的buffer来专门向客户端传送数据(建议为proxy_buffers中单个缓冲区的2倍),然后它继续从后端取数据。
# proxy_busy_buffer_size参数用来设置处于busy状态的buffer有多大。
# 1)如果完整数据大小小于busy_buffer大小,当数据传输完成后,马上传给客户端;
# 2)如果完整数据大小不小于busy_buffer大小,则装满busy_buffer后,马上传给客户端;


proxy_max_temp_file_size 256k;
# 作用:设置临时文件的总大小

# 通过 proxy_temp_path 设置临时文件位置
# 语法:proxy_temp_path path [level1 level2 level3]
# 默认值:proxy_temp_path proxy_temp
# 上下文:http,server,location
#
# 作用:定义proxy的临时文件存在目录以及目录的层级。
Nginx实现websocket代理

WebSocket协议相比较于HTTP协议成功握手后可以多次进行双向通讯,直到连接被关闭。

但是WebSocket中的握手和HTTP中的握手兼容(websocket的实现在http的基础上),它使用HTTP中的Upgrade协议头(客户端向服务端发起)将连接从HTTP升级到WebSocket。这使得WebSocket程序可以更容易的使用现已存在的基础设施。

websocket协议

WebSocket工作在HTTP的80和443端口并使用前缀ws://或者wss://进行协议标注,在建立连接时使用HTTP/1.1的101状态码进行协议切换,当前标准不支持两个客户端之间不借助HTTP直接建立Websocket连接。

nginx代理websocket协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# http_upgrade 没有值 则变量connection_upgrade设置为close

upstream websocket {
server 127.0.0.1:8010; # websocket服务端口
}

server {
listen 8020; # nginx代理端口
access_log /var/log/nginx/test_websocket.access.log main;
location / {
proxy_pass http://websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}

其中的 map映射

1
2
3
Syntax:	 map $var1 $var2 { ... }
Default: —
Context: http

参考文章:nginx map使用方法

fastcgi代理及配置语法

我们将服务端程序分为了web服务器和应用程序服务器。

web服务器是用于处理HTML文件,让客户可以通过浏览器进行访问。主流的有apache,IIS,nginx,lghttpd等。

应用服务器处理业务逻辑,比如使用python的django,flask写成的程序

通常来自客户端浏览器的请求被web服务器截获,如果是静态请求,则如nginx会自己做处理,如果是动态请求,则会抛给后端应用服务器来处理。于是如何在web服务器与应用服务器之间进行通信成了主要问题,这就引出了以下三种处理的接口:CGI,FastCGI,WSGI

fastcgi

1. CGI

通用网关接口Common Gateway Interface/CGI)描述了客户端和服务器程序之间传输数据的一种标准,可以让一个客户端,从网页浏览器向执行在网络服务器上的程序请求数据。CGI 独立于任何语言的,CGI 程序可以用任何脚本语言或者是完全独立编程语言实现,只要这个语言可以在这个系统上运行。Unix shell script, Python, Ruby, PHP,perl, Tcl, C/C++, 和 Visual Basic 都可以用来编写 CGI 程序。

最初,CGI 是在 1993 年由美国国家超级电脑应用中心(NCSA)为 NCSA HTTPd Web 服务器开发的。这个 Web 服务器使用了 UNIX shell 环境变量 来保存从 Web 服务器传递出去的参数,然后生成一个运行 CGI 的独立的进程。cgi的处理流程如下图所示:
BTycNQ.jpg

l step1. web 服务器收到客户端(浏览器)的请求Http Request,启动CGI程序,并通过环境变量标准输入传递数据

l step2. cgi进程启动解析器、加载配置(如业务相关配置)、连接其它服务器(如数据库服务器)、逻辑处理等

l step3. cgi程将处理结果通过标准输出标准错误,传递给web 服务器

l step4. web 服务器收到cgi返回的结果,构建Http Response返回给客户端,并杀死cgi进程

web服务器与cgi通过环境变量、标准输入、标准输出、标准错误互相传递数据。

总结:CGI使外部程序与Web服务器之间交互成为可能。CGI程式运行在独立的进程中,并对每个Web请求建立一个进程,这种方法非常容易实现,但效率很差,难以扩展。面对大量请求,进程的大量建立和消亡使操作系统性能大大下降。此外,由于地址空间无法共享,也限制了资源重用。

2. FastCGI

快速通用网关接口(Fast Common Gateway Interface/FastCGI)是通用网关接口(CGI)的改进,描述了客户端和服务器程序之间传输数据的一种标准。FastCGI致力于减少Web服务器CGI程式之间互动的开销,从而使服务器可以同时处理更多的Web请求。与为每个请求创建一个新的进程不同,FastCGI使用持续的进程来处理一连串的请求。这些进程由FastCGI进程管理器管理,而不是web服务器。

fastcgi

当进来一个请求时,Web 服务器把环境变量和这个页面请求通过一个unix domain socket(都位于同一物理服务器)或者一个IP Socket(FastCGI部署在其它物理服务器)传递给FastCGI进程。

fastcgi处理流程

l step1. Web 服务器启动时载入初始化FastCGI执行环境 。 例如IIS ISAPI、apache mod_fastcgi、nginx ngx_http_fastcgi_module、lighttpd mod_fastcgi

l step2. FastCGI进程管理器自身初始化,启动多个CGI解释器进程并等待来自Web 服务器的连接。启动FastCGI进程时,可以配置以ip和UNIX 域socket两种方式启动。

l step3. 当客户端请求到达Web 服务器时, Web 服务器将请求采用socket方式转发到 FastCGI主进程,FastCGI主进程选择并连接到一个CGI解释器。Web 服务器将CGI环境变量和标准输入发送到FastCGI子进程。

l step4. FastCGI子进程完成处理后将标准输出和错误信息从同一socket连接返回Web 服务器。当FastCGI子进程关闭连接时,请求便处理完成。

l step5. FastCGI子进程接着等待并处理来自Web 服务器的下一个连接。

由于 FastCGI 程序并不需要不断的产生新进程,可以大大降低服务器的压力并且产生较高的应用效率。它的速度效率最少要比CGI 技术提高 5 倍以上。它还支持分布式的部署, 即 FastCGI 程序可以在web 服务器以外的主机上执行。

总结:CGI 就是所谓的短生存期应用程序,FastCGI 就是所谓的长生存期应用程序。FastCGI像是一个常驻(long-live)型的CGI,它可以一直执行着,不会每次都要花费时间去fork一次(这是CGI最为人诟病的fork-and-execute 模式)。

Fastcgi代理配置

1
2
3
Syntax:	 fastcgi_pass  address;
Default: —
Context: location,if in location

address为后端的fastcgi server的地址

示例:

1
2
3
fastcgi_pass localhost:9000;

fastcgi_pass unix:/tmp/fastcgi.socket; # web服务器和fastcgi在同一台主机上
1
2
3
Syntax:	 fastcgi_index  name;
Default: —
Context: http,server, location

设置默认的首页文件 结合 fastcgi_param 一起设置

1
2
3
Syntax:	 fastcgi_param  parameter value [if_not_empty];
Default: —
Context: http,server, location

通过 fastcgi_param 设置变量,并将设置的变量传递到后端的FastCGI serve。

BT2BGQ.png

BT2cq0.png

Uwsgi反向代理模式

我们看下UWSGI的配置场景
BT2cq0.png

uWSGI处在Nginx和WEB应用程序之间。

为什么WEB框架要使用uwsgi?

优势:安全(对于Django需要普通用户启动),效率
BT2cq0.png

Nginx结合uwsgi能够实现动静分离 效率更高。通过Nginx能够做到动静分离。

BT2cq0.png

知识就是财富
如果您觉得文章对您有帮助, 欢迎请我喝杯水!