esp8266 / Arduino

ESP8266 core for Arduino
GNU Lesser General Public License v2.1
15.99k stars 13.34k forks source link

ESP8266WebServer, serveStatic(...) not working correctly with LittleFS. #6909

Closed mike-s123 closed 4 years ago

mike-s123 commented 4 years ago

Basic Info

Platform

Settings in IDE

Problem Description

.serveStatic(), if used with LittleFS, only works for files at the root level.

If the following file structure exists...

/file1.png
/pic/file2.png

...then this works...

server.serveStatic("/file1.png", LittleFS, "/file1.png", "max-age=43200");

...but neither of these do...

server.serveStatic("/pic/file2.png", LittleFS, "/pic/file2.png", "max-age=43200");
server.serveStatic("/pic", LittleFS, "/pic", "max-age=43200");

Tested by gathering network captures using Wireshark. The one which works provides a "Cache-Control: max-age=43200" header, which is not present in the non-working ones.

earlephilhower commented 4 years ago

Can you please enable full debugging and post the logs around some of the failing requests? There is some tracing that would help pinpoint the issue cause.

mike-s123 commented 4 years ago

I've tried. My app swaps the serial port pins in order to use the hardware UART for MODBUS. So, logging is done with SoftwareSerial. I'm able to get output from the normal ESP8266WebServer debug by doing

#define DEBUG_ESP_PORT logger     // points to SoftwareSerial port
#define DEBUG_ESP_HTTP_SERVER

which then logs the stuff that ESP8266WebServer does using, e.g.

DEBUG_OUTPUT.println(authReq);

but nothing comes out from RequestHandlers... , which logs differently, e.g.

 DEBUGV("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());

...trying to figure that out, that seems to come from core/debug.h:

#ifdef DEBUG_ESP_CORE
#define DEBUGV(fmt, ...) ::printf((PGM_P)PSTR(fmt), ## __VA_ARGS__)
#endif

I did do a

#define DEBUG_ESP_CORE

and still nothing. I don't know where/how to redirect stdout through SoftwareSerial for core debugging.

earlephilhower commented 4 years ago

Sorry, I cannot reproduce this at all with my own MCVE sketch which makes the files and then serveStatics them (modified HelloServer example).

Just FYI, you need to have all the files existing when you call the serveStatic as it caches some info when it's generated. So, if the file in the subdir was missing when you called serveStatic it's not going to work properly.

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <LittleFS.h>

const char* ssid = STASSID;
const char* password = STAPSK;

ESP8266WebServer server(80);

const int led = 13;

void handleRoot() {
  digitalWrite(led, 1);
  server.send(200, "text/plain", "hello from esp8266!");
  digitalWrite(led, 0);
}

void handleNotFound() {
  digitalWrite(led, 1);
  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.send(404, "text/plain", message);
  digitalWrite(led, 0);
}

void setup(void) {
  LittleFS.format();
  LittleFS.begin();
  File f = LittleFS.open("file1.txt", "w");
  f.write("file1", 5);
  f.close();
  LittleFS.mkdir("dir2");
  f = LittleFS.open("/dir2/file2.txt", "w");
  f.write("file2", 5);
  f.close();

  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  server.on("/dir1/file", []() {
    server.send(200, "text/plain", "dir1-file1");
  });

  server.serveStatic("/file1.txt", LittleFS, "/file1.txt");
  server.serveStatic("/dir2/file2.txt", LittleFS, "/dir2/file2.txt");

  server.onNotFound(handleNotFound);

  server.begin();
  Serial.println("HTTP server started");
}

void loop(void) {
  server.handleClient();
  MDNS.update();
}
earlephilhower commented 4 years ago

I also added some cache-control headers like your original lines, and they work as well:

  server.serveStatic("/file1.txt", LittleFS, "/file1.txt", "max-age=12345");
  server.serveStatic("/dir2/file2.txt", LittleFS, "/dir2/file2.txt", "max-age=67890");
earle@server:~/Arduino/hardware/esp8266com/esp8266/tests/host$ ^C
earle@server:~/Arduino/hardware/esp8266com/esp8266/tests/host$ wget --server-response http://192.168.1.154/file1.txt
--2019-12-15 11:32:17--  http://192.168.1.154/file1.txt
Connecting to 192.168.1.154:80... connected.
HTTP request sent, awaiting response... 
  HTTP/1.1 200 OK
  Content-Type: text/plain
  Cache-Control: max-age=12345
  Content-Length: 5
  Connection: close
Length: 5 [text/plain]
Saving to: ‘file1.txt.1’

2019-12-15 11:32:17 (2.33 KB/s) - ‘file1.txt.1’ saved [5/5]

earle@server:~/Arduino/hardware/esp8266com/esp8266/tests/host$ wget --server-response http://192.168.1.154/dir2/file2.txt
--2019-12-15 11:32:25--  http://192.168.1.154/dir2/file2.txt
Connecting to 192.168.1.154:80... connected.
HTTP request sent, awaiting response... 
  HTTP/1.1 200 OK
  Content-Type: text/plain
  Cache-Control: max-age=67890
  Content-Length: 5
  Connection: close
Length: 5 [text/plain]
Saving to: ‘file2.txt.1’

2019-12-15 11:32:25 (1.99 KB/s) - ‘file2.txt.1’ saved [5/5]
mike-s123 commented 4 years ago

if the file in the subdir was missing when you called serveStatic it's not going to work properly.

That may have been what was screwing me up. Also, a lot of web searches on ESP8266 serveStatic imply that whole directories can be done (server.serveStatic("/pic", LittleFS, "/pic", "max-age=43200");, sometimes with trailing /s), but that seems not to be the case. Anyway, as long as I explicitly and fully spec each file, it's working. Sadly, the feature is not well documented.

I was sure I tried the full spec "file2" case, but I could certainly have loaded the file after the sketch started, which doesn't work, as you say.

I'm very sorry for wasting your time here, closing this.