WebServer: Allow client to send many requests on the same connection by ZakCodes · Pull Request #7414 · esp8266/Arduino (original) (raw)
Closes #7412.
Solution
Instead of telling the client to close the connection and waiting 2s for the client to do it before accepting another one, this allows the client to send another request within the 2s if there were no other clients waiting to connect to the server when the response was sent.
By default, this isn't activated. To activate it, the programmer must call ESP8266WebServer::keepAlive(true)
in the handler during the request.
As mentioned before, if there are any other clients waiting to send a request to the server when the response is sent, the server won't accept any other request from the client. This is important because the server doesn't support having multiple concurrent connections, therefore, without this, a client could stay connected to the server forever and block all the other clients.
Testing code
Arduino HTTPS server code (before patch)
Here's the code to upload on your ESP8266 before applying the patch in this pull request.
#include <Arduino.h> #include <ESP8266WebServerSecure.h> #include <ESP8266WiFi.h>
static const uint8_t PRIVATE_KEY[] = R"EOF( -----BEGIN RSA PRIVATE KEY----- MIIBPAIBAAJBAMaLGUw9UMkni86+fipZS3zoJwza4/nDJkOQeC8M31yb35fISva6 4d2K1HLMIBl4ViaNSd1RElzRJifSy2bIdcMCAwEAAQJBAIaD44Xl3QAMTQqrwWsL yLs9xodNHjwv3ZLVJLgr7oEc3yUeCv4q28AwlYDOO04OoT53GAS3m1qYv4FG7jox VaECIQD6/9e2s4WqUpOrmFdRz3AMZx5LbzMrfwYO/yEztNoLCQIhAMp/t8DxqVK9 Cu/h03x/gIal9alpv6uD3MRU4MfC6FFrAiEA9/01NQcUJl8mFaETjPoF+8saTG+W v//ljYWXWU3zLHkCIHe3RBJsjHcezg19i8Npublg+jhbDXa/8U+dAnr27tPbAiEA uysakbOSKKk6NyF7zujFEu4yhgnoqrVwYb78RunsVSM= -----END RSA PRIVATE KEY----- )EOF";
static const uint8_t CERTIFICATE[] = R"EOF( -----BEGIN CERTIFICATE----- MIICBTCCAa+gAwIBAgIUPtU58ibKekgnRomdBcIJIdYUSZ4wDQYJKoZIhvcNAQEF BQAwVzELMAkGA1UEBhMCQ0ExEjAQBgNVBAgMCVF1w4PCqWJlYzERMA8GA1UEBwwI R2F0aW5lYXUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0x OTA4MjMwMjMzNDhaFw00OTA4MTUwMjMzNDhaMFcxCzAJBgNVBAYTAkNBMRIwEAYD VQQIDAlRdcODwqliZWMxETAPBgNVBAcMCEdhdGluZWF1MSEwHwYDVQQKDBhJbnRl cm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAxosZ TD1QySeLzr5+KllLfOgnDNrj+cMmQ5B4LwzfXJvfl8hK9rrh3YrUcswgGXhWJo1J 3VESXNEmJ9LLZsh1wwIDAQABo1MwUTAdBgNVHQ4EFgQUTNUSlxtCEYth3de5ciL6 qiDdpZcwHwYDVR0jBBgwFoAUTNUSlxtCEYth3de5ciL6qiDdpZcwDwYDVR0TAQH/ BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAEqW+lzyWOT8cA+dVRwW+BkHguxR1ev6 zYHQwup2cIEwXeArBptlX0wkdjb4bGtwWM1NiqtCHCeCXyQhuPdMCLE= -----END CERTIFICATE----- )EOF";
#define WIFI_SSID "your-ssid" #define WIFI_PASSWORD "your-password"
ESP8266WebServerSecure server(443);
void setup() { Serial.begin(115200); Serial.setDebugOutput(true); Serial.println();
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
uint8_t i = 0;
while (WiFi.status() != WL_CONNECTED) {
i++;
if (i == 21) {
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.println("Could not connect to " WIFI_SSID);
}
delay(500);
}
Serial.print("Connected! IP address: ");
Serial.println(WiFi.localIP());
server.getServer().setServerKeyAndCert(PRIVATE_KEY, sizeof(PRIVATE_KEY),
CERTIFICATE, sizeof(CERTIFICATE));
server.on("/", []() {
server.send(200, "text/plain", "Hey!\r\n");
});
server.begin();
}
void loop() { server.handleClient(); }
Arduino HTTPS server code (after the patch)
Here's the code that requires the patch in this pull request to compile.
#include <Arduino.h> #include <ESP8266WebServerSecure.h> #include <ESP8266WiFi.h>
static const uint8_t PRIVATE_KEY[] = R"EOF( -----BEGIN RSA PRIVATE KEY----- MIIBPAIBAAJBAMaLGUw9UMkni86+fipZS3zoJwza4/nDJkOQeC8M31yb35fISva6 4d2K1HLMIBl4ViaNSd1RElzRJifSy2bIdcMCAwEAAQJBAIaD44Xl3QAMTQqrwWsL yLs9xodNHjwv3ZLVJLgr7oEc3yUeCv4q28AwlYDOO04OoT53GAS3m1qYv4FG7jox VaECIQD6/9e2s4WqUpOrmFdRz3AMZx5LbzMrfwYO/yEztNoLCQIhAMp/t8DxqVK9 Cu/h03x/gIal9alpv6uD3MRU4MfC6FFrAiEA9/01NQcUJl8mFaETjPoF+8saTG+W v//ljYWXWU3zLHkCIHe3RBJsjHcezg19i8Npublg+jhbDXa/8U+dAnr27tPbAiEA uysakbOSKKk6NyF7zujFEu4yhgnoqrVwYb78RunsVSM= -----END RSA PRIVATE KEY----- )EOF";
static const uint8_t CERTIFICATE[] = R"EOF( -----BEGIN CERTIFICATE----- MIICBTCCAa+gAwIBAgIUPtU58ibKekgnRomdBcIJIdYUSZ4wDQYJKoZIhvcNAQEF BQAwVzELMAkGA1UEBhMCQ0ExEjAQBgNVBAgMCVF1w4PCqWJlYzERMA8GA1UEBwwI R2F0aW5lYXUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0x OTA4MjMwMjMzNDhaFw00OTA4MTUwMjMzNDhaMFcxCzAJBgNVBAYTAkNBMRIwEAYD VQQIDAlRdcODwqliZWMxETAPBgNVBAcMCEdhdGluZWF1MSEwHwYDVQQKDBhJbnRl cm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAxosZ TD1QySeLzr5+KllLfOgnDNrj+cMmQ5B4LwzfXJvfl8hK9rrh3YrUcswgGXhWJo1J 3VESXNEmJ9LLZsh1wwIDAQABo1MwUTAdBgNVHQ4EFgQUTNUSlxtCEYth3de5ciL6 qiDdpZcwHwYDVR0jBBgwFoAUTNUSlxtCEYth3de5ciL6qiDdpZcwDwYDVR0TAQH/ BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAEqW+lzyWOT8cA+dVRwW+BkHguxR1ev6 zYHQwup2cIEwXeArBptlX0wkdjb4bGtwWM1NiqtCHCeCXyQhuPdMCLE= -----END CERTIFICATE----- )EOF";
#define WIFI_SSID "your-ssid" #define WIFI_PASSWORD "your-password"
ESP8266WebServerSecure server(443);
void setup() { Serial.begin(115200); Serial.setDebugOutput(true); Serial.println();
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
uint8_t i = 0;
while (WiFi.status() != WL_CONNECTED) {
i++;
if (i == 21) {
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.println("Could not connect to " WIFI_SSID);
}
delay(500);
}
Serial.print("Connected! IP address: ");
Serial.println(WiFi.localIP());
server.getServer().setServerKeyAndCert(PRIVATE_KEY, sizeof(PRIVATE_KEY),
CERTIFICATE, sizeof(CERTIFICATE));
server.on("/", []() {
server.keepAlive(true);
server.send(200, "text/plain", "Hey!\r\n");
});
server.begin();
}
void loop() { server.handleClient(); }
Ruby client benchmark code
require 'net/http' require 'benchmark'
DOMAIN = "<your controller's IP>" TIMES = 100
uri = URI("https://#{DOMAIN}/")
request = Net::HTTP::Get.new(uri)
http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true
Allow self signed certificates
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
Benchmark.bm(12) do |bm| request["Connection"] = "keep-alive" bm.report("keep-alive:") { http.start do |http| TIMES.times do |_| response = http.request(request) end end }
request["Connection"] = "close" bm.report("close:") { TIMES.times do |_| http.start do |http| http.get(uri.path) end end } end
Bash curl clients
while true; do curl -k https://$YOUR_IP/; done
Testing procedure and result
Before the patch
These tests are to be done on the version of this repository before applying the patch in this pull request.
Single client test
Process
- Upload the Arduino sketch for the version before the patch on your ESP8266.
- Open the serial monitor.
- Get the IP of your ESP8266.
- Edit the benchmark script to replace the
DOMAIN
variable with the IP of your ESP8266. - Execute the ruby benchmark script in a terminal.
- Get the benchmark result from the ruby script's output.
Result
user system total real
keep-alive: 0.239742 0.094293 0.334035 ( 29.232077)
close: 0.280106 0.082856 0.362962 ( 28.905040)
Multiple clients test
Process
- Upload the Arduino sketch for the version before the patch on your ESP8266.
- Open the serial monitor.
- Get the IP of your ESP8266.
- Edit the benchmark script to replace the
DOMAIN
variable with the IP of your ESP8266. - Execute
while true; do curl -k https://$YOUR_IP/; done
in a terminal with the IP of your ESP8266. - Execute the ruby benchmark script in another terminal.
- Get the benchmark result from the ruby script's output.
Result
user system total real
keep-alive: 0.255995 0.079367 0.335362 ( 57.122717)
close: 0.287268 0.053903 0.341171 ( 56.539017)
After the patch
These tests are to be done on the version of this repository after applying the patch in this pull request.
Single client test with the pre-patch sketch
Process
- Upload the Arduino sketch for the version before the patch on your ESP8266.
- Open the serial monitor.
- Get the IP of your ESP8266.
- Edit the benchmark script to replace the
DOMAIN
variable with the IP of your ESP8266. - Execute the ruby benchmark script in a terminal.
- Get the benchmark result from the ruby script's output.
Result
user system total real
keep-alive: 0.323017 0.090638 0.413655 ( 29.205106)
close: 0.274748 0.070732 0.345480 ( 28.794096)
Single client test with the post-patch sketch
Process
- Upload the Arduino sketch for the version after the patch on your ESP8266.
- Open the serial monitor.
- Get the IP of your ESP8266.
- Edit the benchmark script to replace the
DOMAIN
variable with the IP of your ESP8266. - Execute the ruby benchmark script in a terminal.
- Get the benchmark result from the ruby script's output.
Result
user system total real
keep-alive: 0.055521 0.021891 0.077412 ( 5.508462)
close: 0.273024 0.058076 0.331100 ( 28.774168)
Multiple clients test with the pre-patch sketch
Process
- Upload the Arduino for the version before the patch on your ESP8266.
- Open the serial monitor.
- Get the IP of your ESP8266.
- Edit the benchmark script to replace the
DOMAIN
variable with the IP of your ESP8266. - Execute
while true; do curl -k https://$YOUR_IP/; done
in a terminal with the IP of your ESP8266. - Execute the ruby benchmark script in another terminal.
- Get the benchmark result from the ruby script's output.
Result
user system total real
keep-alive: 0.270916 0.083906 0.354822 ( 57.148734)
close: 0.298795 0.058366 0.357161 ( 56.935414)
Multiple clients test with the post-patch sketch
Process
- Upload the Arduino for the version after the patch on your ESP8266.
- Open the serial monitor.
- Get the IP of your ESP8266.
- Edit the benchmark script to replace the
DOMAIN
variable with the IP of your ESP8266. - Execute
while true; do curl -k https://$YOUR_IP/; done
in a terminal with the IP of your ESP8266. - Execute the ruby benchmark script in another terminal.
- Get the benchmark result from the ruby script's output.
Result
user system total real
keep-alive: 0.257426 0.094239 0.351665 ( 57.534744)
close: 0.284825 0.064359 0.349184 ( 57.068869)
Conclusion
The results do not show any negative consequences to this pull request. In the worst case scenario, the performance will be the same. In the best case scenario, where a single client can make as many requests as it wants, we see a 5x performance improvement.
This was achieved with a ESP8266 at 160MHz connected to a WiFi network with excellent connection. The client is a desktop computer connected to the WiFi router via an Ethernet cable. There is very little latency in my setup. If we increased the latency, the performance improvement will increase further and there still shouldn't be any negative impact.