Я использую серверную часть Golang на порту 12345 и интерфейсную часть Angular на порту 8080. Они общаются через веб-сокеты на странице с именем / consult. Когда я открываю брандмауэр для обоих портов и заставляю их обмениваться данными через свои IP-адреса и адреса портов, они работают нормально. Но с Nginx между ними это не удается, потому что Nginx предотвращает отправку cookie из внешнего интерфейса в серверный. В целях разработки я закодировал серверную часть так, чтобы она вызывала панику и прерывание из-за ошибки карты nil, если она не получает cookie от передней части, что теперь и происходит. Я не могу понять, что мешает получению 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)
Угловой код переднего конца, который открывает соединение сокета с серверной частью для http://frontend.mydomain.me/consultation:
this.socket = new WebSocket("ws://MY_IP:12345/consultation");
Серверная часть Golang обслуживается напрямую по адресу MY_IP: 12345, без Nginx. Вот код соответствующей страницы по адресу / консультация, он содержит бит, запрашивающий 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, а также на решениях других людей.
Обновление 27 марта 2019 года: как просили в комментариях, вот какие результаты 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;
Оказывается, мы не можем просто передавать файлы cookie между разными доменами и поддоменами.
В моем случае я попытался передать его из frontend.mydomain.me в MY_IP: 12345. На самом деле я также пытался передать его с frontend.mydomain.me на backend.mydomain.me. Ничего из этого не сработало, потому что они находятся в разных доменах.
это прояснил это немного больше для меня.
В конце концов, я настроил Nginx для обслуживания внешнего интерфейса на projectname.mydomain.me/frontend и backend на projectname.mydomain.me/backend. Это сработало, потому что серверная и внешняя части теперь находятся в одном домене, projectname.mydomain.me.
Спасибо Майклу Хэмптону за то, что он указал мне правильное направление в комментариях.