--Update:
The problem is in the nodejs version. Everything works with nodejs up to version 15. I have checked versions from 10 to 15. When I update nodejs to version 16 the connection on the server closes immediately. What has changed in nodejs 16 and how can I make this work.
--Details:
I have a web application that sends a keep alive post XmlHTTPRequest to monitor changes on server. I am using xhr because I am sending a payload. Everything works in local environment. The beckend server sends messages and the client gets them. However, in production the connection gets closed immediately on the backend server when it is run with nginx on Digitalocean.
Sending request (React native expo):
this.stream = function () {
this._setReadyState(this.CONNECTING)
this.xhr = new XMLHttpRequest()
this.xhr.addEventListener('progress', this._onStreamProgress.bind(this))
this.xhr.addEventListener('load', this._onStreamLoaded.bind(this))
this.xhr.addEventListener(
'readystatechange',
this._checkStreamClosed.bind(this)
)
this.xhr.addEventListener('error', this._onStreamFailure.bind(this))
this.xhr.addEventListener('abort', this._onStreamFailure.bind(this))
this.xhr.open(this.method, this.url)
for (var header in this.headers) {
this.xhr.setRequestHeader(header, this.headers[header])
}
this.xhr.withCredentials = this.withCredentials
this.xhr.send(this.payload)
}
Backend server Nodejs Express:
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('Content-Type', 'text/event-stream')
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Connection', 'keep-alive')
res.setHeader('X-Accel-Buffering', 'no')
res.flushHeaders() // flush the headers to establish SSE
with client
res.write(
`data: ${JSON.stringify({
messageCode: 'ping',
})}\n\n`
)
res.flush()
const { pollResources } = req.body
// Give each response an id and add it to object of reponses with ids it is polling
currentId += 1
const resId = `r_${currentId}`
responseIds[resId] = { res, ids: [] }
console.log('resId', resId)
req.on('close', () => {
console.log('close', resId)
})
Nginx on Digitalocean config:
upstream keepalive {
server 127.0.0.1:5000;
keepalive 23;
keepalive_timeout 100;
keepalive_requests 100000;
}
server {
listen 80;
server_name www.example.com example.com;
return 301 https://example.com$request_uri;
}
server {
root /var/www/html;
listen 443 ssl;
server_name www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
# managed by Certb>
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
return 301 https://example.com$request_uri;
}
server {
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
server_name example.com;
location ^~ /assets/ {
gzip_static on;
expires 12h;
add_header Cache-Control public;
}
location / {
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_pass http://localhost:3000;
proxy_set_header Real_ip_header $remote_addr;
}
location /api/ {
proxy_pass http://localhost:5000/api/;
proxy_set_header Host $host;
proxy_set_header Connection '';
proxy_http_version 1.1;
#chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
proxy_set_header Real_ip_header $remote_addr;
}
location /api/document/poll {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://keepalive;
proxy_buffering off;
proxy_cache off;
}
location /sitemap.xml {
proxy_pass http://localhost:5000/sitemap.xml;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
In console on the backend I am getting two logs:
0|server | resId r_80
0|server | close r_80
My request in devtools looks like:
POST /api/document/poll HTTP/1.1
Host: example.com
Connection: keep-alive
Content-Length: 318
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"
DNT: 1
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
sec-ch-ua-platform: "Linux"
Content-Type: application/json
Accept: */*
Origin: https://example.com
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://example.com/user/61ddf21e00f725675c371809
Accept-Encoding: gzip, deflate, br
Accept-Language: en,en-US;q=0.9,sl-SI;q=0.8,sl;q=0.7,ru-UA;q=0.6,ru;q=0.5
Cookie: cookies_consent=true; language=en
The responce looks like:
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Wed, 12 Jan 2022 11:26:19 GMT
Content-Type: text/event-stream
Transfer-Encoding: chunked
Connection: keep-alive
Content-Security-Policy: default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests
X-DNS-Prefetch-Control: off
Expect-CT: max-age=0
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: no-referrer
X-XSS-Protection: 0
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: accesstoken, refreshtoken
Cache-Control: no-cache
Vary: Accept-Encoding
Content-Encoding: gzip