我在端口 12345 上运行 Golang 后端,在端口 8080 上运行 Angular 前端。它们在名为 /consultation 的页面上通过 websockets 进行通信。当我打开两个端口的防火墙并让它们通过 IP 和端口地址进行通信时,它们工作正常。但是如果它们之间有 Nginx,则失败,因为 Nginx 阻止将 cookie 从前端发送到后端。出于开发目的,我编写了后端代码,如果后端没有从前端收到 cookie,就会由于 nil map 错误而崩溃,而现在它确实收到了。我搞不清楚是什么阻止了后端接收 cookie。
Nginx 反向代理http://frontend.mydomain.me到http://本地主机:8080根据 Nginx 文档和网上其他解决方案,conf 为:
server {
listen 80;
server_name frontend.mydomain.me;
location / {
...
}
location /consultation {
# Tried with and without these.
proxy_set_header Access-Control-Allow-Headers "*";
proxy_set_header Access-Control-Allow-Methods "*";
proxy_set_header Access-Control-Allow-Credentials "true";
# Tried with and without these.
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-NginX-Proxy true;
# With or without these, http is upgraded to ws anyway.
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://localhost:8080/consultation;
}
}
正如我在上面代码中的评论中提到的那样,无论是否升级 Nginx conf 中的连接,http 仍然会升级到 ws,如响应标头中所示(显示在 Chrome 和 Firefox 的开发人员工具中的“网络”选项卡中):
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: WfOsnTuctT2GopMFe55WOC7Dwk4=
我不知道这是否表明不需要升级使用 Nginx 的连接,以及是否与问题有关。
我尝试将这些添加到 Nginx 配置中,但没有任何变化:
proxy_set_header Cookie $cookie_client;
proxy_set_header Cookie "$http_cookie;client=testvalue";
当客户端到达登陆页面时,Angular 前端会设置 cookie(不使用 HttpOnly 或 Secure):
this.cookie.set(this.cookieName, this.cookieValue)
Angular 前端代码打开与后端的套接字连接,用于http://frontend.mydomain.me/consultation:
this.socket = new WebSocket("ws://MY_IP:12345/consultation");
Golang 后端直接在 MY_IP:12345 上提供服务,无需 Nginx。以下是 /consultation 上相关页面的代码,其中包含从前端请求 cookie 的部分:
func consultation(res http.ResponseWriter, req *http.Request) {
// Upgrade connection to websocket.
conn, _ := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(res, req, nil)
// Print http request headers to console.
output, _ := httputil.DumpRequest(req, true)
fmt.Println(string(output))
// This is the code that requests for the cookie that never arrives.
var cookieName string
var cookieValue string
for _, cookie := range req.Cookies() {
cookieName = cookie.Name
cookieValue = cookie.Value
}
...
}
当我打开端口 12345 和 8080 并让前端和后端直接在它们之间通信时,cookie 已成功发送和接收,如httputil.DumpRequest()
上面的 Golang 代码转储的这些请求标头所示:
GET /consultation HTTP/1.1
Host: MY_IP:12345
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,en-GB;q=0.8
Cache-Control: no-cache
Connection: Upgrade
// Right here.
Cookie: client=f7da8732-6e15-4157-8cf7-a13b7ce2b5cf
Origin: http://MY_IP:8080
Pragma: no-cache
Sec-Websocket-Extensions: permessage-deflate; client_max_window_bits
Sec-Websocket-Key: wgMYe0jAVnutW9LEuSFFCg==
Sec-Websocket-Version: 13
Upgrade: websocket
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
但是使用 Nginx 时,可能会发送但未收到。我不确定:
GET /consultation HTTP/1.1
Host: MY_IP:12345
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,en-GB;q=0.8
Cache-Control: no-cache
Connection: Upgrade
Origin: http://frontend.mydomain.me
Pragma: no-cache
Sec-Websocket-Extensions: permessage-deflate; client_max_window_bits
Sec-Websocket-Key: fm2Bkc8ZPoTcGeAZi/n+WA==
Sec-Websocket-Version: 13
Upgrade: websocket
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
我肯定还遗漏了什么。那会是什么呢?
据我所知,我的 Nginx 代码是正确的。它基于 Nginx 文档以及其他人的解决方案。
2019 年 3 月 27 日更新:根据评论中的要求,结果如下sudo nginx -T
:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# configuration file /etc/nginx/nginx.conf:
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 768;
# multi_accept on;
}
http {
# Basic Settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_names_hash_bucket_size 128;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# SSL Settings
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
# Logging Settings
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# Gzip Settings
gzip on;
gzip_disable "msie6";
# Virtual Host Configs
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
# configuration file /etc/nginx/mime.types:
types {
# Removed for brevity.
}
# configuration file /etc/nginx/sites-enabled/subdomain1:
server {
listen 80;
server_name subdomain1.mydomain.me subdomain1.org;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/subdomain1.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/subdomain1.org/privkey.pem;
Redirect non-https traffic to https
if ($scheme != "https") {
return 301 https://$host$request_uri;
}
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/subdomain1/subdomain1;
}
location / {
include proxy_params;
proxy_pass http://unix:/home/subdomain1/subdomain1.sock;
}
}
# configuration file /etc/nginx/proxy_params:
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
## This is the back end in this question on Server fault.
## It is currently not being used since the front end connects directly to MY_IP:12345.
# configuration file /etc/nginx/sites-enabled/backend:
server {
listen 80;
server_name backend.mydomain.me;
location / {
include proxy_params;
proxy_pass http://localhost:12345/;
}
location /consultation {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'POST';
add_header 'Access-Control-Allow-Headers' 'Content-Type';
add_header 'Content-Type' 'application/json';
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'POST';
add_header 'Access-Control-Allow-Headers' 'Content-Type';
add_header 'Content-Type' 'application/json';
}
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://localhost:12345/consultation";
}
}
# configuration file /etc/nginx/proxy_params:
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
## This is the frontend for this question on Server Fault.
# configuration file /etc/nginx/sites-enabled/frontend:
server {
listen 80;
server_name frontend.mydomain.me;
location / {
include proxy_params;
proxy_pass http://localhost:8080/;
}
location /consultation {
include proxy_params;
proxy_pass http://localhost:8080/consultation;
}
}
# configuration file /etc/nginx/proxy_params:
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# configuration file /etc/nginx/proxy_params:
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Default server configuration
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
}
}
# configuration file /etc/nginx/sites-enabled/mydomain:
server {
listen 80;
server_name MY_IP mydomain.me;
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/mydomain.me/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mydomain.me/privkey.pem;
Redirect non-https traffic to https
if ($scheme != "https") {
return 301 https://$host$request_uri;
}
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/mydomain/mydomain;
}
location / {
include proxy_params;
proxy_pass http://unix:/home/mydomain/mydomain.sock;
}
}
# configuration file /etc/nginx/proxy_params:
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# configuration file /etc/nginx/sites-enabled/subdomain2:
server {
listen 80;
server_name subdomain2.mydomain.me;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/mydomain.me-0001/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mydomain.me-0001/privkey.pem;
if ($scheme != "https") {
return 301 https://$host$request_uri;
}
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/subdomain2/subdomain2;
}
location / {
include proxy_params;
proxy_pass http://unix:/home/subdomain2/subdomain2.sock;
}
}
# configuration file /etc/nginx/proxy_params:
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
答案1
事实证明,我们不能简单地在不同的域和子域之间传递 cookie。
就我而言,我尝试将其从 frontend.mydomain.me 传递到 MY_IP:12345。实际上,我还尝试将其从 frontend.mydomain.me 传递到 backend.mydomain.me。这些方法都不起作用,因为它们位于不同的域中。
这为我进一步澄清一下。
最后,我将 Nginx 配置为在 projectname.mydomain.me/frontend 上为前端提供服务,在 projectname.mydomain.me/backend 上为后端提供服务。此方法有效,因为后端和前端现在都位于同一个域 projectname.mydomain.me 上。
感谢迈克尔·汉普顿 (Michael Hampton) 在评论中为我指明正确的方向。