Description
Basic Infos
- This issue complies with the issue POLICY doc.
- I have read the documentation at readthedocs and the issue is not addressed there.
- I have tested that the issue is present in current master branch (aka latest git).
- I have searched the issue tracker for a similar issue.
- If there is a stack dump, I have decoded it.
- I have filled out all fields below.
Platform
- Hardware: [WEMOS D1 MINI]
- Core Version: [ f776454 ]
- Development Env: [Arduino IDE]
- Operating System: [Windows 10]
Settings in IDE
- Module: [Wemos D1 mini r2 & mini]
- Flash Mode: [unknown (usb)]
- Flash Size: [4MB]
- lwip Variant: [v2 Lower Memory]
- Reset Method: [ck|nodemcu]
- Flash Frequency: [40Mhz]
- CPU Frequency: [80Mhz]
- Upload Using: [SERIAL]
- Upload Speed: [921600] (serial upload only)
Problem Description
Latest version Chrome browser doesn't display the CaptivePortal webpage when Windows connects to the ESP_ap access point. Instead an empty white page is displayed. The Chrome network tab shows a net::ERR_INCOMPLETE_CHUNKED_ENCODING error. However the page is correctly displayed by Firefox, Edge and Internet Explorer.
An Iphone with latest IOS throws error "Hotspot login cannot open the page because the network connection was lost" when connecting to ESP_ap access point.
server.setContentLength(CONTENT_LENGTH_UNKNOWN); set header "Transfer-Encoding: chunked".
Data is sent in a series of chunks. The Content-Length header is omitted in this case and at the beginning of each chunk you need to add the length of the current chunk in hexadecimal format, followed by '\r\n' and then the chunk itself, followed by another '\r\n'. The terminating chunk is a regular chunk, with the exception that its length is zero.
The terminating chunk is not sent by handleRoot and handWifi function. A server.sendContent(""); before server.client().stop(); solves the issue. A terminating chunk is sent and accepted by the IOS Captive Portal handler and by the Chrome browser.
Sketch handleHttp.ino
/** Handle root or redirect to captive portal */
void handleRoot() {
if (captivePortal()) { // If captive portal redirect instead of displaying the page.
return;
}
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
server.sendHeader("Pragma", "no-cache");
server.sendHeader("Expires", "-1");
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
server.send(200, "text/html", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
server.sendContent(
"<html><head></head><body>"
"<h1>HELLO WORLD!!</h1>"
);
if (server.client().localIP() == apIP) {
server.sendContent(String("<p>You are connected through the soft AP: ") + softAP_ssid + "</p>");
} else {
server.sendContent(String("<p>You are connected through the wifi network: ") + ssid + "</p>");
}
server.sendContent(
"<p>You may want to <a href='/wifi'>config the wifi connection</a>.</p>"
"</body></html>"
);
server.sendContent("");
server.client().stop(); // Stop is needed because we sent no content length
}
/** Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */
boolean captivePortal() {
if (!isIp(server.hostHeader()) && server.hostHeader() != (String(myHostname)+".local")) {
Serial.print("Request redirected to captive portal");
server.sendHeader("Location", String("http://") + toStringIp(server.client().localIP()), true);
server.send ( 302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
server.client().stop(); // Stop is needed because we sent no content length
return true;
}
return false;
}
/** Wifi config page handler */
void handleWifi() {
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
server.sendHeader("Pragma", "no-cache");
server.sendHeader("Expires", "-1");
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
server.send(200, "text/html", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
server.sendContent(
"<html><head></head><body>"
"<h1>Wifi config</h1>"
);
if (server.client().localIP() == apIP) {
server.sendContent(String("<p>You are connected through the soft AP: ") + softAP_ssid + "</p>");
} else {
server.sendContent(String("<p>You are connected through the wifi network: ") + ssid + "</p>");
}
server.sendContent(
"\r\n<br />"
"<table><tr><th align='left'>SoftAP config</th></tr>"
);
server.sendContent(String() + "<tr><td>SSID " + String(softAP_ssid) + "</td></tr>");
server.sendContent(String() + "<tr><td>IP " + toStringIp(WiFi.softAPIP()) + "</td></tr>");
server.sendContent(
"</table>"
"\r\n<br />"
"<table><tr><th align='left'>WLAN config</th></tr>"
);
server.sendContent(String() + "<tr><td>SSID " + String(ssid) + "</td></tr>");
server.sendContent(String() + "<tr><td>IP " + toStringIp(WiFi.localIP()) + "</td></tr>");
server.sendContent(
"</table>"
"\r\n<br />"
"<table><tr><th align='left'>WLAN list (refresh if any missing)</th></tr>"
);
Serial.println("scan start");
int n = WiFi.scanNetworks();
Serial.println("scan done");
if (n > 0) {
for (int i = 0; i < n; i++) {
server.sendContent(String() + "\r\n<tr><td>SSID " + WiFi.SSID(i) + String((WiFi.encryptionType(i) == ENC_TYPE_NONE)?" ":" *") + " (" + WiFi.RSSI(i) + ")</td></tr>");
}
} else {
server.sendContent(String() + "<tr><td>No WLAN found</td></tr>");
}
server.sendContent(
"</table>"
"\r\n<br /><form method='POST' action='wifisave'><h4>Connect to network:</h4>"
"<input type='text' placeholder='network' name='n'/>"
"<br /><input type='password' placeholder='password' name='p'/>"
"<br /><input type='submit' value='Connect/Disconnect'/></form>"
"<p>You may want to <a href='/'>return to the home page</a>.</p>"
"</body></html>"
);
server.sendContent("");
server.client().stop(); // Stop is needed because we sent no content length
}
/** Handle the WLAN save form and redirect to WLAN config page again */
void handleWifiSave() {
Serial.println("wifi save");
server.arg("n").toCharArray(ssid, sizeof(ssid) - 1);
server.arg("p").toCharArray(password, sizeof(password) - 1);
server.sendHeader("Location", "wifi", true);
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
server.sendHeader("Pragma", "no-cache");
server.sendHeader("Expires", "-1");
server.send ( 302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
server.client().stop(); // Stop is needed because we sent no content length
saveCredentials();
connect = strlen(ssid) > 0; // Request WLAN connect with new credentials if there is a SSID
}
void handleNotFound() {
if (captivePortal()) { // If caprive portal redirect instead of displaying the error page.
return;
}
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += ( server.method() == HTTP_GET ) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for ( uint8_t i = 0; i < server.args(); i++ ) {
message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n";
}
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
server.sendHeader("Pragma", "no-cache");
server.sendHeader("Expires", "-1");
server.send ( 404, "text/plain", message );
}