ESP8266WebServer - Drop inactive connection when another is waiting to improve page load time by aWZHY0yQH81uOYvH · Pull Request #8216 · esp8266/Arduino (original) (raw)
I see. I didn't realize that calling .available()
would actually affect the state of the _server
object. First of all, just so we're on the same page, let me share the code I've been using to test this:
#define DEBUG_ESP_HTTP_SERVER #include <ESP8266WiFi.h> #include <ESP8266WebServer.h>
const char *ssid = "xxx"; const char *wifiPassword = "xxx";
ESP8266WebServer HTTPserver(80);
void setup() { Serial.begin(115200);
// Initialize WiFi Serial.print("Connecting to WiFi"); WiFi.mode(WIFI_STA); WiFi.begin(ssid, wifiPassword);
unsigned long startConnect = millis(); while(WiFi.status() != WL_CONNECTED && millis()-startConnect < 30000) { delay(1000); Serial.print("."); } if(WiFi.status() != WL_CONNECTED) { Serial.println("\nWiFi connection failed! Rebooting..."); delay(1000); ESP.restart(); }
Serial.println(" success"); Serial.print("\tConnected to "); Serial.println(ssid); Serial.print("\tIP address: "); Serial.println(WiFi.localIP());
HTTPserver.on("/", page); HTTPserver.begin(); }
void loop() { HTTPserver.handleClient(); }
void page() { HTTPserver.send(200, "text/plain", "Time: "+String(millis())); }
This Wireshark capture of the unmodified library and the double Safari connections (ESP is 192.168.1.10, computer is 192.168.1.2, the HTTP GET is sent over the 53254 connection):
And the serial monitor output with DEBUG_ESP_HTTP_SERVER
defined:
New client
http-server loop: conn=1 avail=0 status=wait-read
[repeated for 5 seconds]
http-server loop: conn=1 avail=0 status=wait-read
webserver: closing after read timeout
Drop client
New client
http-server loop: conn=1 avail=354 status=wait-read
request: GET / HTTP/1.1
method: GET url: / search: keepAlive=: 1
headerName: Host
headerValue: 192.168.1.10
headerName: Upgrade-Insecure-Requests
headerValue: 1
headerName: Accept
headerValue: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
headerName: User-Agent
headerValue: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15
headerName: Accept-Language
headerValue: en-us
headerName: Accept-Encoding
headerValue: gzip, deflate
headerName: Connection
headerValue: keep-alive
args:
args count: 0
args:
args count: 0
Request: /
Arguments:
final list of key/value pairs:
http-server loop: conn=1 avail=0 status=wait-close
[repeated for 2 seconds]
http-server loop: conn=1 avail=0 status=wait-close
Drop client
Like I tried to say in my first comment, this doesn't seem to happen every time, so these captures are of course only the ones that show the double connection issue. Here's what this becomes after my proposed modification.
New client
http-server loop: conn=1 avail=0 status=wait-read
webserver: closing since there's another connection to read from
Drop client
New client
http-server loop: conn=1 avail=0 status=wait-read
request: GET / HTTP/1.1
method: GET url: / search: keepAlive=: 1
headerName: Host
headerValue: 192.168.1.10
headerName: Upgrade-Insecure-Requests
headerValue: 1
headerName: Accept
headerValue: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
headerName: User-Agent
headerValue: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15
headerName: Accept-Language
headerValue: en-us
headerName: Accept-Encoding
headerValue: gzip, deflate
headerName: Connection
headerValue: keep-alive
args:
args count: 0
args:
args count: 0
Request: /
Arguments:
final list of key/value pairs:
http-server loop: conn=1 avail=0 status=wait-close
[repeated for 2 seconds]
http-server loop: conn=1 avail=0 status=wait-close
Drop client
From the Wireshark capture, it's clear that the act of calling _server.available()
and letting the returned client object go out of scope is causing both the 53351 and 53352 connections to be closed, just like you say. Safari then opens another single connection to re-send the GET request and then everything works. I had assumed .available()
wouldn't do anything to the server's state (which seems to be implied here/here in the docs as well unless I'm reading it wrong), but the client destructor does appear to call the TCP close code. I had assumed everything was working and the second connection wasn't closed since DEBUG_ESP_HTTP_SERVER
doesn't cause that connection close to be printed and I didn't verify it with Wireshark.
Your suggestion of using _server.hasClient()
does what I intended by dropping just the first connection:
New client
http-server loop: conn=1 avail=0 status=wait-read
webserver: closing since there's another connection to read from
Drop client
New client
http-server loop: conn=1 avail=0 status=wait-read
request: GET / HTTP/1.1
method: GET url: / search: keepAlive=: 1
headerName: Host
headerValue: 192.168.1.10
headerName: Upgrade-Insecure-Requests
headerValue: 1
headerName: Accept
headerValue: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
headerName: User-Agent
headerValue: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15
headerName: Accept-Language
headerValue: en-us
headerName: Accept-Encoding
headerValue: gzip, deflate
headerName: Connection
headerValue: keep-alive
args:
args count: 0
args:
args count: 0
Request: /
Arguments:
final list of key/value pairs:
http-server loop: conn=1 avail=0 status=wait-close
[repeated for 2 seconds]
http-server loop: conn=1 avail=0 status=wait-close
Drop client
However, I was hoping to be able to verify that there was actually something available for reading from the new connection before dropping the current one (the .available().available()
). I could imagine this might cause problems with multiple clients accessing the ESP (as mentioned by @paulocsanz whose comment seems to have disappeared) or at the very least causing lots of unnecessarily dropped and retried connections from browsers that use multiple connections to load multiple resources at once. It seems like the most ideal solution (short of handling multiple connections at once) would be to drop the connection and move on to the new one if all of the following conditions are met:
- The current connection is waiting for more data
- There is a new connection waiting with available data
- A short timeout (20ms?) has passed to hopefully allow for slow clients/network to deliver any remaining data going into the current connection
Or, equivalently, HTTP_MAX_DATA_WAIT
becomes much shorter when there's a new connection with data available. It seems like the "with data available" requirement would be a bit of a mess to implement with the way the library is currently written since you'd have to store the client object returned from .available()
regardless of whether the conditions to switch to it are met or not. Perhaps if this problem is limited to Safari or just my computer being weird, then it's not worth pursuing further.