How to configure nginx to keep alive xhr with ssl?

Viewed 449

--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
0 Answers
Related