Search code examples
socketshttpmobilehttpswebserver

Website loads inconsistently on mobile only


I have a website being served from a custom webserver, and it loads and works fine when loaded from a laptop/desktop browser, but loads inconsistently on mobile browsers. (In my case I tested specifically Samsung Internet and Chrome on Android)

(The exact behaviour is: load the web page, refresh, and then after a couple of refreshes it will sometimes not be able to load a background image, or any resource on the page at all - but only on mobile browsers)

In case this was just some cached data issue, I've cleared all browser data, restarted my phone, asked friends to try on their devices etc, but I've only been able to reproduce this on mobile devices.

My web server is written using liburing, nginx as a reverse proxy, though I doubt that would be the issue

I read Can Anyone Explain These Long Network Stalled Times? and it ocurred to me that an issue could be me using multiple different HTTP requests to get resources (I've not implemented Connection: Keep-Alive), but I also get this issue on WiFi, and I get the issue even when loading a single asset (such as a background image)

Additional possibly relevant info:

  • I was initially having a similar issue on desktop as well, and I fixed it by using shutdown() before calling close() on the HTTP requests
  • I'm using the following response headers:
    • Keep-Alive: timeout=0, max=0
    • Connection: close
    • Cache-Control: no-cache
  • I'm using the following socket options:
    • SO_REUSEADDR (mainly for debug convenience)
    • SO_REUSEPORT (sockets in multiple threads bind to and listen on the same port)
    • SO_KEEPALIVE, TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT (to kill off inactive clients)
  • Oddly enough though I think this disappears for a while after restarting my phone
  • I have tried not using nginx, instead using WolfSSL for TLS, and I get the same issue

I am inclined to think that this could be an issue with what headers I'm setting in responses (or possibly some HTTPS specific detail I'm missing?), but I'm not sure And here's the actual site if anyone wants to verify the issue https://servertest.erewhon.xyz/


Solution

  • It looks to me like your server does not do a proper TLS shutdown, but is simply shutting down the underlying TCP connection. This causes your server to send a RST (packet 28) when the client is doing the proper TLS shutdown by sending the appropriate close notify TLS alert (packet 27).

    enter image description here

    This RST will result in a connection close on the client side. Depending on how fast the client has processed the incoming data this can result in abandoning still unread data in the TCP socket buffer, thus causing the problems you see.

    The difference in behavior between mobile and desktop might just be caused by the performance of the systems and maybe by the underlying TCP stack. But no matter if the desktop works fine - your web server behaves wrong.

    For details on how the connection close should happen at the HTTP level see RFC 7230 section 6.6. Note especially the following parts of this section:

    If a server performs an immediate close of a TCP connection, there is a significant risk that the client will not be able to read the last HTTP response. If the server receives additional data from the client on a fully closed connection, such as another request that was sent by the client before receiving the server's response, the server's TCP stack will send a reset packet to the client; unfortunately, the reset packet might erase the client's unacknowledged input buffers before they can be read and interpreted by the client's HTTP parser.

    To avoid the TCP reset problem, servers typically close a connection in stages. First, the server performs a half-close by closing only the write side of the read/write connection. The server then continues to read from the connection until it receives a corresponding close by the client, or until the server is reasonably certain that its own TCP stack has received the client's acknowledgement of the packet(s) containing the server's last response. Finally, the server fully closes the connection.