nginx入门教程 (转)
1.Nginx 状态码配置和错误文件
server { # 配置访问 /test.js 时报 403 错 location /test.js { return 403; } # 配置访问 /404 时报 404 错 location /404 { return 404; } # 配置访问 /500 时报 500 错 location /500 { return 500; } # 把指定状态码指向这个文件 uri error_page 500 502 503 504 /status.html; error_page 404 /status.html; }
server { listen 80; server_name test.me; root /Users/xiaowu/work/test.me; # 用if匹配任何以 403 开头的,会匹配到 /4034444 if ($request_uri ~* ^/403) { return 403; } # 用location匹配 /500/ /500,但不匹配 /500/1 location ~* "^/500/?$" { return 500; } # 用if匹配以 /501/ 开头的,匹配 /501/1,/501/1/2 但不匹配 /501 if ($request_uri ~* ^/501/) { return 501; } # 用location匹配 /502/ /502 /502/1 /502/1/2 location ~* "^/502(/.*)?$" { return 502; } # 用location只匹配 /503 location = /503 { return 503; } }
error_page配置小提示
注意 error_page
配置时加 =
和不加 =
的区别,加了 =
表示响应为指定的 http status code
,默认为 200,不加 =
为原错误的状态码~
# 这样可以访问错误页面时 http status 为 404 ,并且页面内容是 404.html 的内容 error_page 404 /404.html error_page 404 500 /404.html; # 这样配置访问错误页面时 http status 为 200 ,但页面内容是 404.html 的内容 error_page 404 500 = /404.html; # 这样配置访问错误页面时 http status 为 404 ,但页面内容是 404.html 的内容 error_page 404 500 =404 /404.html; # 也可以把404请求直接301到某个域上 error_page 404 =301 https://xuexb.com/404;
这样就可以根据自己需求配置错误页为指定的状态码,因为非 200 的状态码可能会被浏览器拦截。
2.主域301重定向
你的网站可能有多个域名访问,比如:www.xuexb.com
、xuexb.com
等,设置主域意思是不管用户输入哪个域名,都会 301
重定向到主域上,设置主域可以对 SEO 更友好,比如:
以xuexb.com为主域
www.xuexb.com => xuexb.com
www.xuexb.com/search/xxoo => xuexb.com/search/xxoo
www.xuexb.com/a/b/c/404.html => xuexb.com/a/b/c/404.html
配置文件核心:
server { # 设置多个域名 server_name www.xuexb.com xuexb.com; # 判断host是不是xuexb.com,如果不是则直接301重定向,permanent表示301 if ( $host != 'xuexb.com' ){ rewrite ^/(.*)$ http://xuexb.com/$1 permanent; } # 其他规则 }
3.Node.js 反向代理
服务端如果使用nodejs
运行服务,由于端口不能同时多个服务占用,而服务器中可能又是多个网站,那么可以使用 Nginx 做反向代理,比如有这些网站域名和端口:
域名 | 端口 |
---|---|
www.xxoo.com | 8001 |
www.xo.com | 8002 |
www.xo.cn | 8003 |
当然一个服务器上的网站可能还有更多,可以通过配置 Nginx 转发来代理这些端口分发,如:
server { server_name www.xxoo.com; listen 80; # 设置这个网站的根目录 root /wwwroot/www.xxoo.com/; # 由于下面配置了文件不存在则代码到 Node.js 中,那么直接访问目录(不带默认主页)的话会有问题,这里做下判断 # 如果访问目录下有 index.html 文件,则直接重写到该文件 # break 表示重写且停止,但 url 不变,而 permanent 表示301重定向,url 会更新 if ( -f $request_filename/index.html ){ rewrite (.*) $1/index.html break; } # 如果请求的文件不存在,则代理到 Node.js if ( !-f $request_filename ){ rewrite (.*) /index.js; } # 代理node服务 8001 location = /index.js { # 设置一些代理的header信息,这些信息将被透传到 Node.js 服务的header信息里 proxy_set_header Connection ""; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; # 代理服务 proxy_pass http://127.0.0.1:8001$request_uri; # 忽略其他重写 proxy_redirect off; } }
配置之后,比如你网站根目录里有 index.html
文件,访问 url
如:
访问链接 | 解析过程 | 备注 |
---|---|---|
www.xxoo.com/index.html | Nginx | 由于文件存在,直接使用 Nginx 输出 |
www.xxoo.com | Nginx | 由于判断该目录下有 index.html 文件,则自动重写到该文件,但 url 不变 |
www.xxoo.com/xx.html | Nginx -> Node.js:8001 | 由于文件不存在,使用 Nginx 代理到 Node.js 的 8001 端口 |
www.xxoo.com/xxoo/ | Nginx -> Node.js:8001 | 首先判断该目录是否存在 如果存在再判断是否有 index.html 文件一旦不成立,直接代理到 Node.js |
4.配置图片防盗链
防盗链是指当图片不是自己网站打开时返回 403 或者指定图片,是通过请求的来路判断是否是自己的站点来设置响应。
语法
valid_referers none | blocked | server_names | string
none
:表示没有来路blocked
:表示有来路server_names
:来路里包含当前域名string
:(忽略端口)- 如果是字符串:一个域名验证的规则,
*
表示通配符 - 如果是以
~
开头:正则表达式,排除https://或http://开头的字符串
- 如果是字符串:一个域名验证的规则,
以上参数可以叠加一起使用。
例子
server { # 配置所有图片 location ~* \.(gif|jpg|png|bmp)$ { # 验证可以是没有来路、或者有来路时来路匹配xuexb.com、或者匹配当前域名 valid_referers none blocked *.xuexb.com server_names; # 如果验证不通过则返回403 if ($invalid_referer) { return 403; } } }
5.配置 HTTPS
首先配置支持 HTTPS 必须让 Nginx 开启 http_ssl_module
模块,点击查看nginx编译安装参数 ,可以使用nginx -V
查看是否开启TLS SNI support enabled
。
购买/生成 SSL 证书,可以使用免费的证书,比如:Let's Encrypt,免费好用的 HTTPS 证书
# 配置 HTTPS # 配置个http的站点,用来做重定向,当然如果你不需要把 HTTP->HTTPS 可以把这个配置删了 server { listen 80; # 配置域名 server_name www.xxoo.com xxoo.com; # 添加 STS, 并让所有子域支持, 开启需慎重 add_header strict-transport-security 'max-age=31536000; includeSubDomains; preload'; # 配置让这些 HTTP 的访问全部 301 重定向到 HTTPS 的 rewrite ^(.*) https://www.xxoo.com$1 permanent; } # 配置 HTTPS server { # 配置域名 server_name www.xxoo.com xxoo.com; # https默认端口 listen 443; # 添加STS, 并让所有子域支持, 开启需慎重 add_header strict-transport-security 'max-age=31536000; includeSubDomains; preload'; # https配置 ssl on; ssl_certificate /xxoo/www.xxoo.com.crt; ssl_certificate_key /xxoo/www.xxoo.com.key; # 其他按正常配置处理即可... }
注意,这里证书的格式是 .crt
的
配置后的访问规则
输入链接 | 最终访问链接 |
---|---|
http://www.xxoo.com | https://www.xxoo.com |
http://www.xxoo.com/404/500 | https://www.xxoo.com/404/500 |
http://xxoo.com | https://www.xxoo.com |
https://www.xxoo.com | -(原链接不变) |
https://xxoo.com/500 | https://www.xxoo.com/500 |
强烈推荐使用 https://github.com/Neilpang/acme.sh
6.配置泛域名转发
有的时候,我们需要配置一些自定义的子域名,如:
xuexb.user.demo.com
a01.user.demo.com
这时候就需要域名的 DNS 解析一个泛域名 *.user.demo.com
到服务器,Nginx 可以配置如下:
子域名转发到子目录
server { listen 80; server_name ~^([\w-]+)\.user\.demo\.com$; location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; proxy_pass http://127.0.0.1:8080/$1$request_uri; } }
以上配置表示:
xuexb.user.demo.com/path?a=1
->127.0.0.1:8080/xuexb/path?a=1
a01.user.demo.com/path?a=1
->127.0.0.1:8080/a01/path?a=1
这样后端就可以根据子目录解析不同的规则,甚至 Nginx 可以再进行链接重写。
子域名配置不同的目录
server { listen 80; server_name ~^([\w-]+)\.user\.demo\.com$; root /home/user/wwwroot/user/$1; }
以上配置可以把不同的子域名分发到不同的目录中,做到路径分离的功能,如:
xuexb.user.demo.com
->/home/user/wwwroot/user/xuexb
;a01.user.demo.com
->/home/user/wwwroot/user/a01
;
7.学习使用 echo 模块
Nginx echo模块是在nginx程序上扩展了 echo
输出字符的功能,对于调试很方便,项目地址: https://github.com/openresty/echo-nginx-module 。
安装
目前支持
nginx-1.11.2
,高版本可能安装失败,我装1.11.13
失败了,更多支持nginx的版本见: https://github.com/openresty/echo-nginx-module#compatibility
去 release 下载最新的安装包,并解压.
在配置 Nginx 时用: ./configure --add-module=/你的解压路径
,并编译安装,如果是重新编译安装可参考: 重新编译安装
使用
只是研究了一些常用的
echo - 输出字符
-
语法:
echo [options] <string>...
- 默认值:
no
# 简单的hello,world!
server { location = /api/hello { echo "hello,world!"; } }
默认echo会自动换行,不需要换行可以: echo -n xxx;
;
echo_before_body,echo_after_body - 页面前、后插入内容
-
语法:
echo_before_body [options] [argument]...
- 默认值:
no
server { # 简单的输出 location = /api/hello { echo_before_body echo_before_body; echo "hello,world!"; echo_after_body echo_after_body; } # 常见于代理页面前、后插内容 location = /api/proxy_before_after { echo_before_body echo_before_body; echo_before_body echo_before_body; proxy_pass http://127.0.0.1; echo_after_body echo_after_body; echo_after_body echo_after_body; } }
echo_sleep - 请求等待
- 语法:
echo_sleep <seconds>
- 默认值:
no
server { # 简单的输出 location = /api/sleep { echo 1; # 等待1秒 echo_sleep 1; echo 2; } }
echo_location_async,echo_location - 请求指定路径
-
语法:
echo_location_async <location> [<url_args>]
- 默认值:
no
location /main { # 异步调用/sub echo_location_async /sub; echo world; } location /main2 { # 同步调用/sub echo_location_async /sub; echo world; } location /sub { echo hello; }
异步跟同步的区别是:
- 异步会并行的去请求
- 同步等待当前请求结束才会往下执行
下面这个整个时间为2s,因为新路径最大是2s:
location /main { echo_location_async /sub1; echo_location_async /sub2; echo "took $echo_timer_elapsed sec for total."; } location /sub1 { echo_sleep 2; } location /sub2 { echo_sleep 1; }
下面这个整个时间为3s,因为需要等待/sub1
完成才进入/sub2
:
location /main { echo_location /sub1; echo_location /sub2; echo "took $echo_timer_elapsed sec for total."; } location /sub1 { echo_sleep 2; } location /sub2 { echo_sleep 1; }
可以通过第二个参数传querystring
: echo_location_async /sub 'foo=Foo&bar=Bar';
echo_foreach_split - 分隔循环
- 语法:
echo_foreach_split <delimiter> <string>
location /split { echo_foreach_split ',' $arg_list; echo "item: $echo_it"; echo_end; }
上面配置当访问: /split?list=cat,dog,mouse
时会输出:
item: cat
item: dog
item: mouse
$arg_list
是对应$args.list
那么配置全echo_location_async
可以做成nginx-combo
服务了,如:
location = /api/combo { echo_foreach_split ',' $query_string; echo "/* combo: $echo_it */"; echo_location_async $echo_it; echo; echo_end; }
访问: /api/combo?/a.js,/b.js
,需要注意的是文件的路径必须以/
开始,因为匹配的location
,当然真的 nginx-combo
服务请看: nginx-http-concat
8.使用 nginx-http-concat
ginx-http-concat 是一个 Nginx 扩展模块,用来合并 HTTP 请求。
下载
访问 https://github.com/alibaba/nginx-http-concat/releases 官网下载最新的源包,如:
# 下载
wget https://github.com/alibaba/nginx-http-concat/archive/1.2.2.tar.gz
# 解压并记录解压后的目录
tar xzf 1.2.2.tar.gz
安装
使用 编译安装 ,在配置 configure 时添加参数:
# 配置
./configure 其他编译参数 --add-module=/刚才解压的目录
# 安装
make
[sudo] make install
注意: 如果是重新编译安装时不要运行 make install
,可参数: 重新编译安装
如我的配置:
./configure
--...
--add-module=/home/work/src/nginx-http-concat-1.2.2
如果有多个
--add-module
分别对应写上即可
配置
使用 location 匹配到你想要匹配的路径,对其设置参数:
server { location /static/css/ { concat on; concat_types text/css; concat_max_files 20; } location /static/js/ { concat on; concat_types application/javascript; concat_max_files 30; } }
重启服务,如: nginx -s reload
。
更新配置项点击: https://github.com/alibaba/nginx-http-concat#module-directives
使用
现在就可以通过url中的??
来合并了,比如: /static/css/??a.css,path/b.css
。
9.日志切割 - Shell
由于 Nginx 的日志(包括 access 和 error)默认都是存放在一个文件夹内,当时间长了,日志文件体积越来越大,不易管理,可以自己写个脚本来处理,其实就是定时定点去把日志移动下位置。
#!/bin/sh # 日志源目录 sourceDir='/var/log/nginx/xuexb.com/last' # 日志备份目录 backDir='/var/log/nginx/xuexb.com/back' echo "split-logs start: $(date +%Y-%m-%d %H:%M:%S)" ls $sourceDir | while read filename do mkdir -p "$backDir/$(date +%Y%m%d)/" mv "$sourceDir/$filename" "$backDir/$(date +%Y%m%d)/" echo "$sourceDir/$filename => $backDir/$(date +%Y%m%d)/$filename" done # 刷新nginx kill -USR1 `cat /var/run/nginx.pid` echo "split-logs end: $(date +%Y-%m-%d %H:%M:%S)" echo "----------------"
有几个注意的:
- 日志源目录 - 该目录存放日志文件,如 access.log,error.log,当然如果你的站点比较多,可以用域名来命名,比如: xuexb.com.access.log,xuexb.com.error.log 。
- 日志备份目录 - 会以
Ymd
命名文件夹名存放在该目录。 - 刷新 Nginx - 由于是直接移动日志文件,移动后需要刷新下 Nginx 使其再生成日志文件,当然也可以
nginx -s reload
,但reload
要比kill
的慢,而kill -USR1
是无损的。 - 定时任务 - 需要使用
root
帐户下使用crontab -e
插入一条定时任务,定时 23:55 开始分割,后面是把错误和信息输出到指定文件,方便调试,如:
55 23 * * * sh /你的目录/split-log >> /var/log/nginx/crontab.log 2>&1
10.配置浏览器缓存
使用 expires
参数。
不缓存
server { expires -1; }
输出Response Headers:
Cache-Control:no-cache
当文件没有变更时会返回 304 ,有变更时会是 200 ,如果强制命中 200 可以再添加: if_modified_since off;
忽略 Request Headers 里的 If-Modified-Since
字段。
缓存
server { expires 1d; }
1d为1天,单位如下:
ms milliseconds
s seconds
m minutes
h hours
d days
w weeks
M months,30 days
y years,365 days
如果希望最大缓存可以:
server { expires max; }
输出Response Headers:
Cache-Control:max-age=315360000
根据链接设置缓存时间
server { # 设置为1月 set $expires_time 1M; # 针对后台不缓存 if ($request_uri ~* ^/admin(\/.*)?$) { set $expires_time -1; } # 针对静态文件缓存最大 if ($request_uri ~* ^/static(\/.*)?$) { set $expires_time max; } # 设置吧 expires $expires_time; }
11.配置默认主页、目录浏览
设置默认主页
直接可以使用目录形式打开的页面称为默认主页,一般常见的有: index.html
、index.htm
、index.php
这些,要吧通过配置来完成,如:
server { root /网站根目录; # 设置默认主页,支持多个,按优先级来,空格分格 index index.html index.htm index.php; }
设置目录浏览
当一个目录内没有默认主页的文件时,直接访问目录会报 403 Forbidden
错误,而启用目录浏览功能后可以直接列出当前目录下的文件、文件夹,如:
server { root /网站根目录; # 优先使用默认主页 index index.html index.htm; # 当默认主页不存在时直接列出目录内文件树 autoindex on; }
但多 autoindex
相关可官方文档: http://nginx.org/en/docs/http/ngx_http_autoindex_module.html
注意,线上运行环境最好别开启该配置,因为这将直接暴露你的文件~
12.add_header 指令技巧
官方的介绍:
Adds the specified field to a response header provided that the response code equals 200, 201 (1.3.10), 204, 206, 301, 302, 303, 304, 307 (1.1.16, 1.0.13), or 308 (1.13.0). The value can contain variables.
There could be several add_header directives. These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.
If the always parameter is specified (1.7.5), the header field will be added regardless of the response code.
意思也就是说话在响应状态码成功时,add_header
指令才生效,并且当前《作用域》下没有 add_header
指令时,会向上层继承。
在使用过程中难免会遇到上级指令被覆盖的情况,如:
server { add_header x-name nginx; location / { root /path; } location /static/ { add_header x-name2 nginx2; } }
当匹配到 /
时,由于 location /
中没有 add_header
指令,所以会继承 server
中的 x-name
,而当匹配到 /static/
时,由于内容已经有 add_header
指令,则上层的 x-name
不会被继承,导致只会输出 x-name2
。
使用 include 语法
因为项目中的应用往往配置会有很多,我们可以把具体的功能拆分成独立的配置文件,使用 include
引用进来,如:
inc/no-cache.conf
- 无缓存inc/cache-max.conf
- 缓存最大inc/sts.conf
- STSinc/xss.conf
- XSS 安全过滤inc/php.conf
- PHP FastCGI- ...
这样就可以按需引用了,如:
location / { include inc/sts.conf; include inc/security.conf; include vhost/xiaoshuo.io/no-cache.conf; } # png,jpg 转 webp location ~* \.(jpg|png|meibanfawojiuxiangchangdianyirangquanzhongdadian)$ { include inc/sts.conf; include inc/security.conf; include vhost/xiaoshuo.io/cache-max.conf; if ($cookie_webp = '1') { rewrite ^/(.*)$ /$1.webp last; } } location ~* \.webp$ { include inc/sts.conf; include inc/security.conf; include vhost/xiaoshuo.io/cache-max.conf; try_files $uri $uri/ @webp; } location @webp { content_by_lua_file 'lua/webp.lua'; } # http-concat + max cache location /style/js/ { include inc/sts.conf; include inc/security.conf; include vhost/xiaoshuo.io/cache-max.conf; concat on; concat_types application/javascript; concat_max_files 30; default_type application/javascript; } location /style/css/ { include inc/sts.conf; include inc/security.conf; include vhost/xiaoshuo.io/cache-max.conf; concat on; concat_types text/css; concat_max_files 30; default_type text/css; } # 时效性高的接口 location ~* ^/((so\/(.+))||ajax)\.php$ { include inc/sts.conf; include inc/security.conf; include vhost/xiaoshuo.io/php.conf; add_header cache-control 'private, max-age=0, no-cache'; } # 其实所有的 php location ~* \.php$ { set $skip_cache 0; if ($request_method = POST) { set $skip_cache 1; } if ($arg_nocache = "1") { set $skip_cache 1; } fastcgi_cache_key $scheme$request_method$host$request_uri; fastcgi_cache_use_stale error timeout invalid_header updating http_500 http_503; fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; fastcgi_cache_valid 200 5m; fastcgi_cache_valid any 1m; fastcgi_cache m_xiaoshuo_php_cache; add_header x-php-cache-status $upstream_cache_status; include inc/sts.conf; include inc/security.conf; include vhost/xiaoshuo.io/cache-small.conf; include vhost/xiaoshuo.io/php.conf; }
12.proxy_pass url 反向代理的坑
说到 Nginx 就不得不说 Nginx 的反向代理是多么的好用,一个指令 proxy_pass
搞定反向代理,对于接口代理、负载均衡很是实用,但 proxy_pass
指令后面的参数很有讲究。
网上有很多什么绝对路径、相对路径的说法,其实在实际的应用中就分为两种情况:
url 只是 host
这里指不包含 $uri
,如:
http://host
- √https://host
- √http://host:port
- √https://host:port
- √http://host/
- xhttp://host:port/
- x
这时候 location
匹配的完整路径将直接透传给 url ,如:
// 访问: / 后端: / // 访问: /api/xx 后端: /api/xx // 访问: /api/xx?aa 后端: /api/xx?aa location / { proxy_pass http://node:8080; } // 访问: /api/ 后端: /api/ // 访问: /api/xx 后端: /api/xx // 访问: /api/xx?aa 后端: /api/xx?aa // 访问: /api-xx?aa 后端: location /api/ { proxy_pass http://node:8080; } // 访问: /api/ 后端: /api/ // 访问: /api/xx 后端: /api/xx // 访问: /api/xx?aa 后端: /api/xx?aa // 访问: /api-xx?aa 后端: /api-xx?aa location /api { proxy_pass http://node:8080; }
url 包含路径
注意,这里的路径哪怕只是一个 /
也是存在的,如:
http://host
- xhttps//host/
- √http://host:port
- xhttps://host:port/
- √http://host/api
- √http://host/api/
- √
当 proxy_pass url
的 url
包含路径时,匹配时会根据 location
的匹配后的链接透传给 url
,注意匹配后就是这样:
location 规则 | 访问的原始链接 | 匹配之后的路径 |
---|---|---|
location / |
/ |
|
location / |
/a |
a |
location / |
/a/b/c?d |
a/b/c?d |
location /a/ |
/a/ |
|
location /a/ |
/a/b/c?d |
b/c?d |
明白匹配之后的路径后,在 proxy_pass url
包含路径时,将会把匹配之后的路径透传给 url
,如:
// 访问: / 后端: / // 访问: /api/xx 后端: /api/xx // 访问: /api/xx?aa 后端: /api/xx?aa location / { proxy_pass http://node:8080/; } // 访问: /api/ 后端: / // 访问: /api/xx 后端: /xx // 访问: /api/xx?aa 后端: /xx?aa // 访问: /api-xx?aa 未匹配 location /api/ { proxy_pass http://node:8080/; } // 访问: /api 后端: / // 访问: /api/ 后端: // // 访问: /api/xx 后端: //xx // 访问: /api/xx?aa 后端: //xx?aa // 访问: /api-xx?aa 后端: /-xx?aa location /api { proxy_pass http://node:8080/; } // 访问: /api/ 后端: /v1 // 访问: /api/xx 后端: /v1xx // 访问: /api/xx?aa 后端: /v1xx // 访问: /api-xx?aa 未匹配 location /api/ { proxy_pass http://node:8080/v1; } // 访问: /api/ 后端: /v1/ // 访问: /api/xx 后端: /v1/xx // 访问: /api/xx?aa 后端: /v1/xx // 访问: /api-xx?aa 未匹配 location /api/ { proxy_pass http://node:8080/v1/; }
由以上规则可以看出,当 proxy_pass url
中包含路径时,结尾的 /
最好同 location
匹配规则一致。
当 proxy_pass 遇到正则
当 location
以正则形式匹配时,proxy_pass
就不能以 /
结束了,也就是不能包含路径了,比如错误的:
location ~* ^/api/ { proxy_pass http://host/; } location / { if ($uri ~* ^/api/) { proxy_pass http://host/; } }
解决办法就是把链接中的路径去掉。
重写代理链接 - url rewrite
当原始链接(浏览器访问的链接)和代理服务器链接规则不一致时,可以使用 Nginx URL Rewrite 功能去动态的重写,如:
location ~* ^/api/ { rewrite ^/api/(.*) /?path=$1 break; proxy_pass http://node:8080; }
以上请求会把匹配 /api/
的链接重写为 /?path=
的链接透传给 node:8080
服务,有意思的是当使用 rewrite
指令并且生效后,proxy_pass url
链接中的路径会被忽略,如:
// 访问: / 后端: /node/ // 访问: /api 后端: /node/api // 访问: /api/ 后端: /?path= // 访问: /api/a/b/c 后端: /?path=a/b/c location / { rewrite ^/api/(.*) /?path=$1 break; proxy_pass http://node:8080/node/; }
作者:guanbin —— 纵码万里千山
出处:https://www.cnblogs.com/guanbin-529/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。