nginx 客户端关闭连接

nginx client closes connection

我有一个完全 docker 化的应用程序:

问题是当我用 POST 请求访问我的后端端点时,响应永远不会发送到客户端。 nginx 记录了 499 代码以及此日志

 epoll_wait() reported that client prematurely closed connection, so upstream connection is closed too while sending request to upstream,

客户端就是浏览器,这一点不用怀疑。

在 firefox 中处理 1 分钟后出现错误,在 chrome 中处理 5 分钟后出现错误。据我所知,这些时间与这些浏览器的超时设置相匹配。我可以增加 firefox 的超时时间,但这不是一个可行的解决方案。

当我摆脱代理时,请求完成,客户端在大约 15 分钟内收到响应。所以我认为nginx配置有问题,但我不知道是什么。

到目前为止,我尝试增加您可以想象的所有超时,但这并没有改变任何东西。 我也尝试在 nginx 中设置 proxy_ignore_client_abort 但它对我来说没有用。事实上,nginx 和我的后端之间的连接仍然存在,请求在 15 分钟后完成(nginx 日志中的代码 200)但是 ui 没有更新,因为客户端已经终止了与 nginx 的连接。

我认为浏览器认为nginx死了,因为它没有收到任何数据,所以它关闭了TCP连接。
稍后我会尝试通过在我的网站页面之间切换来处理请求时“刺激”此 TCP 连接(因此浏览器不应关闭连接),但是如果我必须做一些奇怪的事情来获得我的后端结果,这不是一个可行的解决方案。

应该有一种方法可以处理长请求而不会遇到这些浏览器超时,但我不知道如何实现。 任何帮助将不胜感激:)

我的 nginx 配置:

user                    nginx;
pid                     /run/nginx.pid;
worker_processes        auto;
worker_rlimit_nofile    65535;

events {
    multi_accept        on;
    worker_connections  65535;
}

http {
    charset                 utf-8;
    sendfile                on;
    tcp_nopush              on;
    tcp_nodelay             on;
    server_tokens           off;
    log_not_found           off;
    types_hash_max_size     2048;
    types_hash_bucket_size  64;
    client_max_body_size    16M;

    # mime
    include                 mime.types;
    default_type            application/octet-stream;

    # logging
    log_format my_log   '$remote_addr - $remote_user [$time_local] "$request" '
                            '$status $body_bytes_sent "$http_referer" '
                            '"$http_user_agent" "$http_x_forwarded_for" ';

    access_log              /var/log/nginx/access.log my_log;
    error_log               /var/log/nginx/error.log info;

    # limits
    limit_req_log_level     warn;
    limit_req_zone          $binary_remote_addr zone=main:10m rate=10r/s;

    # SSL
    ssl_session_timeout     1d;
    ssl_session_cache       shared:SSL:10m;
    ssl_session_tickets     off;

    # Mozilla Intermediate configuration
    ssl_protocols           TLSv1.2 TLSv1.3;
    ssl_ciphers             ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

    # OCSP
    ssl_stapling            on;
    ssl_stapling_verify     on;
    resolver                1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s;
    resolver_timeout        2s;

    # Connection header for WebSocket reverse proxy
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ""      close;
    }

    map $remote_addr $proxy_forwarded_elem {

        # IPv4 addresses can be sent as_is
        ~^[0-9.]+$          "for=$remote_addr";

        # IPv6 addresses need to be bracketed and quoted
        ~^[0-9A-Fa-f:.]+$   "for\"[$remote_addr]\"";

        # Unix domain socket names cannot be represented in RFC 7239 syntax
        default             "for=unknown";
    }

    map $http_forwarded $proxy_add_forwarded {

        # If the incoming Forwarded header is syntactially valid, append to it
        "~^(,[ \t]*)*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\t \x21\x23-\x5B\x5D-\x7E\x80-\xFF]|\\[\t \x21-\x7E\x80-\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\t \x21\x23-\x5B\x5D-\x7E\x80-\xFF]|\\[\t \x21-\x7E\x80-\xFF])*\"))?)*([ \t]*,([ \t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\t \x21\x23-\x5B\x5D-\x7E\x80-\xFF]|\\[\t \x21-\x7E\x80-\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\t \x21\x23-\x5B\x5D-\x7E\x80-\xFF]|\\[\t \x21-\x7E\x80-\xFF])*\"))?)*)?)*$" "$http_forwarded, $proxy_forwarded_elem";

        # Otherwise, replace it
        default "$proxy_forwarded_elem";

    }

    # Load configs
    include /etc/nginx/conf.d/localhost.conf;
}

和localhost.conf

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name localhost;
    root /usr/share/nginx/html;

    ssl_certificate         /etc/nginx/live/localhost/cert.pem;
    ssl_certificate_key     /etc/nginx/live/localhost/key.pem;

    include /etc/nginx/conf.d/security.conf;

    include /etc/nginx/conf.d/proxy.conf;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log info;

    # nginx render files or proxy the request
    location / {
        try_files $uri @front;
    }

    location @front {
        proxy_pass http://frontend:80;
    }

    location ^~ /api/v1 {
        proxy_read_timeout 30m; # because an inference with SIMP can takes some time
        proxy_send_timeout 30m;
        proxy_connect_timeout 30m;
        proxy_pass http://backend:4000;
    }

    location = /report.html {
        root /usr/share/goaccess/html/;
    }

    location ^~ /ws {
        proxy_pass http://goaccess:7890;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_read_timeout 7d;
        proxy_connect_timeout 3600;
    }

    include /etc/nginx/conf.d/general.conf;
}

编辑: 请求是通过 Angular HttpClient 发送的,也许这个模块是 built 的一种方式,如果在短时间内没有发送响应,我会尝试中止请求对此进行调查。

好的,我想我可以回答我自己的问题。 HTTP 请求不是为长请求设计的。发出请求后,应尽快提供响应。

当你在做一个长时间的工作时,你应该使用 worker 和消息架构(或事件驱动架构)以及像 rabbitmq 或 kafka 这样的工具。您也可以使用轮询(但这不是更有效的解决方案)。

因此,在我的 POST 处理程序中,我应该做的是在数据到达时向我的代理发送一条消息,然后发出适当的响应(比如正在处理请求)。 工作人员订阅一个队列,可以接收之前发送的消息,完成工作,然后回复我的后端。然后我们可以使用 STOMP (websocket) 插件将结果路由到前端。