Nginx的正向代理与反向代理详解
正向代理和反向代理的概念
代理服务(Proxy),通常也称为正向代理服务。
如果把局域网外Internet想象成一个巨大的资源库,那么资源就分布到了Internet的各个点上,局域网内的客户端要访问这个库里的资源就必须统一通过代理服务器才能对各个站点进行访问。
局域网内的机器借助代理服务访问局域网外的网站, 这主要是为了增加局域网内部网络的安全性,使得网外的威胁因素不容易影响到网内,这里代理服务器起到了一部分防火墙的功能。同时,利用代理服务器也可以对局域网外的访问进行必要的监控和管理。正向代理服务器不支持外部对内部网络的访问请求。
与正向代理服务相反,如果局域网向Internet提供资源,让Internet上的其他用户可以访问局域网内的资源,也可以设置一个代理服务器,它提供的服务就叫做反向代理(Reverse Proxy)服务。可以看到,反向代理服务与代理服务在功能逻辑上刚好是相反的。
正向代理服务器与反向代理服务器的概念很简单,归纳起来就是,正向代理服务器用来让局域网客户机接入外网以访问外网资源,反向代理服务器用来让外网的客户端接入局域网中的站点以访问站点中的资源。理解这两个概念的关键是要明白我们当前的角色和目的是什么,在正向代理服务器中,我们的角色是客户端,目的是要访问外网的资源;在反向代理服务器中,我们的角色是站点,目的是把站点的资源发布出去让其他客户端能够访问。
知道了这两个概念,就可以学习如何让Nginx服务器来提供代理和反向代理服务器了。
Nginx的正向代理服务
Nginx服务器正向代理服务的配置的3个指令
在实际应用中,使用Nginx服务器代理服务功能的情况相对少一些,Nginx代理服务本向也相对简单,涉及的主要指令不多。这些指令原则上可以出现在Nginx配置文件的http块、server块或者location块中,但一般是在搭建的Nginx服务器中单独配置一个server块用来设置代理服务。
1.resolver指令
该指令用于指定DNS服务器的IP地址。DNS服务器的主要工作是进行域名的解析,将域名映射为对应的IP地址。该指令的语法结构为:
resolver address ... [valid=time];
address,DNS服务器的IP地址。如果不指定商品号,默认使用53端口。
time,设置数据包在网络中的有效时间。出现该指令的主要原因是,在访问站点时,有很多情况使得数据包在一定时间内不能被传递到目的地,但是又不能让该数据包无期限地存在,于是就需要设定一段时间,当数据包在这段时间内没有到达目的地,就会被丢弃,然后发送都会接收到一个消息,并决定是否要重发数据包。
使用该指令的一个例子如下:
resolver 127.0.0.1 [::1}:5353 valid=30s
在实际应用中,一般不需要设置这么复杂,只要将DNS服务器的IP地址设置给该指令即可。
从Nginx1.1.7版本开始,该指令支持设置多个IP地址,从Nginx1.3.1开发版本和Nginx1.2.2稳定版本开始,该指令支持设置IPV6地址。
2.resolver_timeount指令
resolver_timeount time;
该指令用于设置DNS服务器域名解析超时时间,语法结果为:
3.proxy_pass指令
该指令用于设置代理服务器的协议和地址,它不仅仅用于Nginx服务器的代理服务器,更主要的是应用于反向代理服务,我们马上就会谈及。该指令的语法结构为:
proxy_pass URL;
其中,URL即为设置的代理服务器协议和地址。
在代理服务配置中,该指令的设置相对固定,因此在这里就不介绍其他细节了,具体内容在学习Nginx服务器的反向代理服务时再重点阐述。在代理服务配置中,该指令配置为:
proxy_pass http://$http_host$request_uri;
其中,代理服务器协议设置为HTTP,$http_host和$request_uri两个变量是Nginx配置支持的用于自动获取主机和URI的变量。配置代理服务时,一般不要改变该指令的配置。
正向代理服务使用示例
..
server
{
resolver 8.8.8.8;
listen 82;
location /
{
proxy_pass http://$http_host$request_uri;
}
}
实现的片段很简单,设置DNS服务器地址为8.8.8.8,使用默认的53号端口作为DNS服务器的服务端口,代理服务的监听端口设置为82端口,Nginx服务器接收到的所有请求都由第5行的location块进行过滤处理。
Nginx服务器代理服务使用的场合不多,从上一节的配置指令来看,功能也相对简单。在使用过程中,有一些需要注意的事项在这里说明一下。
首先,我们在上面提到过,设置Nginx服务器的代理服务器,一般是配置到一个server块中,注意,在该server块中,不要出现 server_name指令,即不要设置虚拟主机的名称或IP。而resolver指令是必需的,如果没有该指令,Nginx服务器无法处理接收到的域名。
其次,Nginx服务器的代理服务器不支持正向代理HTTPS站点。
Nginx的反向代理服务
Nginx服务器的反向代理服务是其最常用的重要功能之一,在实际工作中应用广泛,涉及的配置指令也比较多,各类指令完成的功能也不尽相同。下面按照功能分类向大家介绍配置该服务需要掌握的指令。由反向代理服务又可以衍生出多种与此相关的Nginx服务器的重要功能,随后将逐步梳理这些功能,并提供配置实例供大家参考。
Nginx服务器提供的反向代理服务也是比较高效的。它能够同时接收的客户端连接由worker_processes指令和worker_connections指令决定,计算方法为:worker_processes * worker_connections / 4.
配置Nginx服务器反向代理用到的指令如果没有特别说明,原则上可以出现在Nginx配置文件的http块、server块或者location块中,但同正向代理服务的设置一样,一般是在搭建的Nginx服务器中单独配置一个server块来设置反射代理服务。这些指令主要由ngx_http_proxy_module模块进行解析和处理。该模块是Nginx服务器的标准HTTP模块。
反向代理的基本设置的27个指令
学习Nginx服务器的反向代理 服务,要涉及与后端代理服务器相关的配置,是客户端提供正常Web服务的基础,大家应该熟练掌握,尤其是proxy_pass指令,在实际应用过程中需要注意一些配置细节,需要小心使用。
1.proxy_pass指令
该指令用来设置被代理服务器的地址,可以是主机名称、IP地址加端口号等形式。其语法结构为:
proxy_pass URL;
其中,URL为要设置的被代理服务器的地址,包含传输协议、主机名称或IP地址加商品号、URI等要素。传输协议通常是“http”或者“https”。指令同时还接受以“unix”开始的UNIX-domain套接字路径。例如:
proxy_pass http://www.myweb.name/uri;
proxy_pass http://localhost/uri;
proxy_pass http://unix:/tmp/backend.socket:/uri/;
如果被代理服务器是一组服务器的话,可以使用upstream指令配置后端服务器组。例如:
#多个服务器
...
upstream proxy_svrs #配置后端服务器
{
server http://192.168.1.1:8001/uri/;
server http://192.168.1.2:8001/uri/;
server http://192.168.1.3:8001/uri/;
} server
{
...
listen 80;
server_name www.myweb.name;
location /
{
proxy_pass proxy_svrs; #使用服务器组名称
}
}
这里首先需要提醒大家proxy_pass指令在使用服务器组名称时应该注意一个细节。在上例中,在组内的各个服务器中都指明了传输协议“http://”,而在proxy_pass指令中就不需要指明了。如果 现在将upstream指令的配置改为:
#不指明http
...
upstream proxy_svrs #配置后端服务器
{
server 192.168.1.1:8001/uri/;
server 192.168.1.2:8001/uri/;
server 192.168.1.3:8001/uri/;
}
我们就需要在proxy_pass指令中指明传输协议“http://”;
proxy_pass http://proxy_svrs;
在使用该指令的过程中还需要注意,URL中是否包含有URI,Nginx服务器的处理方式是不同的。如果URL中不包含URI,Nginx服务器不会改变原地址的URI;但是如果包含了URI,Nginx服务器将会使用新的URI替代原来的URI。我们举例来说明。
请看下面的Nginx配置片段:
..
server
{
...
server_name www.myweb.name;
resolver 8.8.8.8;
listen 82;
location /server/
{
...
proxy_pass http://192.168.1.1;
}
}
如果客户端使用“http://www.myweb.name/server ”发起请求,该请求被配置中显示的location块进行处理,由于proxy_pass指令变量不含有URI,所以转向的地址为“http:///192.168.1.1/server ”;我们再来看下面的Nginx片段:
..
server
{
...
server_name www.myweb.name;
resolver 8.8.8.8;
listen 82;
location /server/
{
...
proxy_pass http://192.168.1.1/loc;
}
}
在该配置实例中,proxy_pass指令的URI包含了URI“/loc”;如果客户端仍然使用“http://www.myweb.name/server ”发起请求,Nginx服务器将会把地址转向“http://192.168.1.1/loc/ ”;
通过上面的实例,我们可以总结 出,在使用proxy_pass指令时,如果不想改变原地址中的URI,就不要在URL变量中配置URI。
明白了上面这两个例子的用法,我们来解释大家经常讨论的一个问题,就是proxy_pass指令的URL变量末尾是否加斜杠“/”的问题。
请看这两个配置示例:
#配置1 proxy_pass http://192.168.1.1;
#配置2 proxy_pass http://192.168.1.1/;
配置1和配置2的区别在于,配置2中的proxy_pass指令的URL变量末尾添加了斜杠“/”,这意味着配置2中的proxy_pass指令的URL变量包含了URI“/”,而配置1中的proxy_pass指令的URL变量不包含URI。理解了这一点,我们就可以解释下面的实例和现象了。大家注意各例子之间的对比。
实例1:
..
server
{
...
listen 80;
server_name www.myweb.name; #注意location的uri变量
location /
{
...
#配置1 proxy_pass http://192.168.1.1;
#配置2 proxy_pass http://192.168.1.1/;
}
}
在该配置中,location块使用“/”作为uri变量的值来匹配不包含URI的请求URL。由于请求URL中不包含URL,因此配置1和配置2的效果是一样的。比如客户端的请求URL为“http://www.myweb.name/index.htm”,其将会被实例1中的location块匹配成功并进行处理。不管使用配置1不是配置2,转向的URL都为:“http://192.168.1.1/index.htm”。
实例2:
..
server
{
...
listen 80;
server_name www.myweb.name; #注意location的uri变量
location /server/
{
...
#配置1 proxy_pass http://192.168.1.1;
#配置2 proxy_pass http://192.168.1.1/;
}
}
在该配置中,location块使用“/server/”作为uri变量的值来匹配包含的URI“/server/”的请求URL。这时,使用配置1和配置2的转向结果就不相同了。使用配置1和配置2的转向效果就不相同了。使用配置1时候,proxy_pass指令中的URL变量不包含URI,Nginx服务器将不改变原地址的URI,使用配置2的时候,proxy_pass指令中的URL变量包含URI“/”,Nginx服务器会将原地址的URI替换为"/"。
比如客户端的请求URI为“http://www.myweb.name/server/index.htm”将会被实例2的location块匹配成功并进行处理。使用配置1的时候,转向的URL为“http://192.168.1.1/server/index.htm”,原地址的URI“、server/”示被改变;使用配置2时,转向的URL为“http://192.168.1.1/index.htm”,可以看到原地址的URI“/server/”被替换为“/”。
大家在应用过程中,一定要注意到该指令在配置上的细节问题,分清楚URL和URI的区别与联系,并能够正确使用它们配置出符合需求的Nginx服务器。
2.proxy_hide_header指令
该指令用于设置Nginx服务器在发送HTTP响应时,隐藏一些头域信息。其语法结构为:
proxy_hide_header field;
其中,field为需要隐藏的头域。该指令可以在http块、server块或者location块中进行配置。
3.proxy_pass_header指令
默认情况下,Nginx服务器在发送响应报文时,报文头中不包含“Date”、“Server”、“X-Accel”等来自被代理服务器的头域信息。该指令可以设置这些头域信息以被发送,其语法结构为:
proxy_pass_header field;
4.proxy_pass_request_body指令
该指令用于配置是否将客户端请求的请求体发送给代理服务器,其语法结构为:
proxy_pass_request_body on | off;
默认开启(on),开头可以在http块、server块或者location块中进行配置。
5.proxy_pass_request_headers指令
该指令用于配置是否将客户端请求的请求头发送给代理服务器,其语法结构为:
proxy_pass_request_headers on | off;
默认开启(on),开头可以在http块、server块或者location块中进行配置。
6.proxy_set_header指令
该指令可以理发Nginx服务器接收到的客户端请求的请求头信息,然后将新的请求头发送给被代理的服务器,其语法结构为:
proxy_set_header field value;
field,要更新的信息所在的区域;value,更改的值,支持使用文本、变量或者变量的组合。
默认情况下,该指令的设置为:
proxy_set_header Host $proxy_host;
proxy_set_header Connection close;
请看一些设置实例:
proxy_set_header Host $http_host; #将目前Host头域的值填充成客户端的地址
proxy_set_header Host $$host; #将当前location块的server_name指令填充到Host头域
proxy_set_header Host $$host:$proxy_port; #listener指令值一起填充到Host头域.
7.proxy_set_body指令
指该指令可以更改Nginx服务器接收到的客户端请求的请求信息,然后将新的请求体发送给被代理的服务器。其语法结构为:
proxy_set_body_value;
其中,value为更改的信息,支持使用文本、变量或者变量的组合。
8.proxy_bind指令
官方文档中对该指令的解释是,强制将与代理主机的连接绑定到指定的IP地址。通俗来讲就是,在配置多个基于名称或者基于IP地址。通俗来讲就是,在配置了多个基于名称或者基于IP主机的情况下,如果我们希望代理连接由指定的主机处理,就可以使用该指令进行配置,其语法结构为:
proxy_bind adress;
其中,adress为指定主机的IP地址。
9.proxy_connect_timeout指令
该指令配置Nginx服务器与后端被代理服务器尝试建立连接的超时时间,其语法结构为:
proxy_connect_timeout time;
其中,time为设置的超时时间,默认60s。
10.proxy_read_timeout指令
该指令配置Nginx服务器向后端被代理服务器(组)发出的read请求后,等待响应的超时时间,其语法结构为:
proxy_read_timeout time;
其中,time为设置的超时时间,默认60s。
11.proxy_send_timeout指令
该指令配置Nginx服务器向后端被代理服务器(组)发出的write请求后,等待响应的超时时间,其语法结构为:
proxy_write_timeount time
其中,time为设置的超时时间,默认60s。
12.proxy_http_version指令
该指令设置用于Nginx服务器提供代理服务的HTTP协议版本,其语法结构为:
proxy_http_version 1.0 | 1.1;
默认版本为1.0版本,1.1版本支持upstream服务器组设置的keepalive指令。
13.proxy_method指令
该指令用于设置Nginx服务器请求被代理服务器时使用的请求方法,一般为POST或者GET。设置了该指令,客户端的请求方法将被忽略。其语法结构为:
proxy_method method;
其中,method的值可以设置为POST或者GET,注意不加引号。
14.proxy_ignore_client_abort指令
该指令用过设置在客户端中断网络请求时,Nginx服务器是否中断对被代理服务器的请求,其语法结构为:
proxy_ignore_client_abort on | off
默认设置为off,当客户端中断网络请求时,Nginx服务器中断对被代理服务器的请求。
15.proxy_ignore_header指令
该指令用于设置一些HTTP响应头的头域,Nginx服务器接收到被代理服务器的响应数据后,不会处理被设置的头域。其语法结构为:
proxy_ignore_header field ...;
其中,field为要设置的HTTP响应头的头域,例如“X-Accel-Redirect”、“X-Accel-Expires”、“Cache-Control”、“Expires”或“Set-Cookie”等。
16.proxy_redirect指令
该指令用于修改被代理服务器返回的响应头中的Location头域和“Refresh”头域,与proxy_pass指令配合使用。比如,Nginx服务器通过proxy_pass指令将客户端的请求地址重写为被代理服务器的地址,那么Nginx服务器返回客户端的响应头中“Location”头域显示的地址就应该和客户端发起请求的地址相对应,而不是代理服务器直接返回的地址信息,否则就会出问题。该指令解决了这个问题,可以把代理服务器返回的地址信息更改为需要的地址信息。其语法结构为:
proxy_redirect redirect replacement
proxy_redirect default;
proxy_redirect off;
redirect,匹配“Location”头域值的字符串,支持变量的使用和正则表达式。
replacement,用于替换redirect变量内容的字符串,支持变量的使用。
该指令的用法我们通过几个配置实例来解释。
对于第1个结构,假设被代理服务器返回的响应头中的“Location”头域为:
Location: http://localhost:8081/proxy/some/uri
该指令设置为:
proxy_redirect http://localhost:8081/proxy/ http://myweb/fronted/;
Nginx服务器会将“Location”头域信息更改为:
Location:http://myweb/frontend//some/uri;
这样,客户端收到的响应信息头部中的“Location”头域就被更改了。
结构 2使用default,代表使用location块的uri变量作为replacement,并使用proxy_pass变量作为redirect。请看下面两段配置,它们的配置效果是等同的。
#配置1
location /server/
{
proxy_pass http://proxyserver/source/;
proxy_redirect default; } #配置2
location /server/
{
proxy_pass http//proxyserver/source/;
proxy_redirect http://proxyserver/source/ /server/; }
使用结构3可以将当前作用域下所有的proxy_redirect指令全部设置为无效。
17.proxy_intercept_errors指令
该指令用于配置一个状态是否开启还是关闭。在开启状态时,如果被代理的服务器返回的HTTP状态码为400或者大于400,则Nginx服务器使用自己定义的错误页(使用error_page指令);如果是关闭状态,Nginx服务器直接将被代理服务器返回的HTTP状态返回给客户端。其请求结构为
proxy_intercept_errors on | off
18.proxy_headers_hash_max_size指令
该指令用于配置HTTP报文头哈希表的容量,其语法结构为:
proxy_headers_hash_max_size size;
其中,size为HTTP报文头哈希表的容量上限,默认为512个字符,即:
proxy_headers_hash_max_size 512;
Nginx服务器为了能够快速检索HTTP报文头中的各项信息,比如服务器名称、MIME类型、请求头名等,使用哈希表存储这些信息。Nginx服务器在申请存放HTTP报文头的空间时,通常以固定大小为单位申请,该大小由proxy_headers_hash_bucket_size指令配置。
在Nignx配置中,不仅能够配置整个哈希表的大小上限,对大部分内容项,也可以配置其大小上限,比如server_names_hash_max_size指令和server_names_hash_bucket_size指令用来设置服务器名称的字符数长度。
19.proxy_headers_hash_bucket_size指令
该指令用于设置Nginx服务器申请存放HTTP报文头的哈希表容量的单位大小。该指令的具体作用在上面proxy_headers_bucket_size指令的使用中已经说明。其语法结构为:
proxy_headers_hash_bucket_size size;
20.proxy_next_upstream指令
在配置Nginx服务器反向代理功能时,如果使用upstream指令配置了一组服务器作为代理 服务器,服务器组中各服务器的访问规则遵循upstream指令配置的轮询规则 ,同时可以使用该指令配置在发生哪些异常情况时,将请求顺次交由下一个组内服务器处理。该指令的语法结构为:
proxy_next_upstream status ...;
其中,status为设置的服务器返回状态,可以是一个或者多个。这些状态包括:
error,建立连接、向被代理服务器发送请求或者读取响应头时服务器发生连接错误。
timeout,建立连接、向被代理服务器发送请求或者读取响应头时服务器发生连接超时。
invalid_header,被代理的服务器返回的响应头为空或者无效。
http_500|http_502|http_503|http_504|http_404,被代理的服务器返回500、502、503、504或者404状态代码。
off,无法将请求发送给被代理的服务器。
注意
与被代理的服务器进行数据传输的过程中发送错误的请求,不包含在该指令支持的状态之内。
21.proxy_ssl_session_reuse指令
该指令用于配置是否使用基于SSL安全协议的会话连接(“https://”)被代理的服务器,其语法结构为:
proxy_ssl_session_reuse on | off
Nginx的正向代理与反向代理详解的更多相关文章
- Nginx详解(正向代理、反向代理、负载均衡原理)
Nginx配置详解 nginx概述 nginx是一款自由的.开源的.高性能的HTTP服务器和反向代理服务器:同时也是一个IMAP.POP3.SMTP代理服务器:nginx可以作为一个HTTP服务器进行 ...
- Centos8 Docker+Nginx部署Asp.Net Core Nginx正向代理与反向代理 负载均衡实现无状态更新
首先了解Nginx 相关介绍(正向代理和反向代理区别) 所谓代理就是一个代表.一个渠道: 此时就涉及到两个角色,一个是被代理角色,一个是目标角色,被代理角色通过这个代理访问目标角色完成一些任务的过程称 ...
- nginx正向代理,反向代理,透明代理(总结)
1正向代理 正向代理,也就是传说中的代理,他的工作原理就像一个跳板, 简单的说, 我是一个用户,我访问不了某网站,但是我能访问一个代理服务器 这个代理服务器呢,他能访问那个我不能访问的网站 于是我先连 ...
- 正向代理与反向代理的区别【Nginx读书笔记】(zz)
正向代理与反向代理的区别[Nginx读书笔记] 正向代理的概念 正向代理,也就是传说中的代理,他的工作原理就像一个跳板,简单的说,我是一个用户,我访问不了某网站,但是我能访问一个代理服务器 ...
- Nginx的upstream反向代理、负载均衡详解
这篇文章的前提是已经配置好了NGINX,而且tomcat已经配置好了,而且能能够访问了. 说反向代理之前,我们先看看正向代理,正向代理也是大家最常接触的到的代理模式. 正向代理最大的特点是客户端非常明 ...
- 正向代理与反向代理区别图解 (nginx)
1. 背景 经常听到代理,比如通常我们要上国外的网站时,需要买vpn作为跳板机器进行访问. 但是在公司里面也听到了nginx支持反向代理. 那什么是正向代理,什么是反向代理?在网上看了写内容,说一下自 ...
- nginx:正向代理和反向代理
一.正向代理 原理:正向代理是一个位于客户端和目标服务器之间的代理服务器(中间服务器).为了从目标服务器取得内容,客户端向代理服务器发送一个请求,并且指定目标服务器,之后代理向目标服务器转交并且将获得 ...
- Nginx正向代理和反向代理
关于代理 说到代理,首先我们要明确一个概念,所谓代理就是一个代表.一个渠道: 此时就设计到两个角色,一个是被代理角色,一个是目标角色,被代理角色通过这个代理访问目标角色完成一些任务的过程称为代理操作过 ...
- [转帖]nginx学习,看这一篇就够了:下载、安装。使用:正向代理、反向代理、负载均衡。常用命令和配置文件
nginx学习,看这一篇就够了:下载.安装.使用:正向代理.反向代理.负载均衡.常用命令和配置文件 2019-10-09 15:53:47 冯insist 阅读数 7285 文章标签: nginx学习 ...
随机推荐
- Gedit —— 推荐于NOI系列考试(NOIlinux)的轻量编程环境
由于Vim,Emacs上手艰难,Guide又特别难用,Anjuta还闪退 故推荐一款轻量化的编程环境:Gedit(文本编辑器) 配置方法: 1:在桌面上新建main.cpp,打开方式选择使用gedit ...
- PHP环境在7以上的项目报错A non-numeric value encountered
报错如下图: 解决办法: 在相对应的报错控制器层加入一行代码,需加载控制器上方,代码如下: ini_set("error_reporting","E_ALL & ...
- 问题:win7下配置好服务器就是不能查询数据库。(已解决)
我用C写访问mysql的CGI程序,可以执行. 但是,当我写好网页再去访问这个CGI,出现下面的错误 我的环境是:IIS作为服务器,MYSQL数据库,VC++6.0写CGI. 跟踪了一下,发现只要我调 ...
- SQL语句基本
基础 创建数据库 CREATE DATABASE database-name 1 删除数据库 drop database dbname 1 备份sql server 创建 备份数据的 device U ...
- js-day06-jQuery事件和DOM操作-练习题
jQuery事件绑定 js中绑定事件,三种方式: 方式1: 直接在元素上,增加onXxx事件属性. <button onclick="alert(1);">点我< ...
- http://www.layui.com/doc/modules/laydate.html实时通信\日期、==插件
8520**ali chengyouli http://www.layui.com/doc/modules/laydate.html实时通信\日期.==插件
- Java中CAS原理详解
在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁 锁机制存在以下问题: (1)在多线程竞争下,加锁.释放锁会导致比较多的上下文切换和调度延时,引起性能问题. (2 ...
- [Swift]LeetCode299. 猜数字游戏 | Bulls and Cows
You are playing the following Bulls and Cows game with your friend: You write down a number and ask ...
- [Swift]LeetCode933. 最近的请求次数 | Number of Recent Calls
Write a class RecentCounter to count recent requests. It has only one method: ping(int t), where t r ...
- [Swift]LeetCode987. 二叉树的垂序遍历 | Vertical Order Traversal of a Binary Tree
Given a binary tree, return the vertical order traversal of its nodes values. For each node at posit ...