web server serves images locally but not externally

I run the following bird house monitor code every year from April 1 to mid-June on an ESP8266. My home router "port forwards" external requests (such as from friends who want to check in) to it.

Last year, the code below successfully served all of the files (HTML, BMP, JPG, TXT) to internal requests (192.168.1.x) and to external requests (DDNSpath:externalPort).

This year, the code still successfully serves all files to internal requests but it only serves the HTML files to external requests. External requests to the BMP and JPG files just die with no error messages in the serial monitor, and the web pages that the image files are embedded in show the image alternate text, not the image.

The following have not changed since last year:

  • code
  • HTML, BMP, and JPG files
  • router
  • ISP
  • DDNS provider (Asus)
    This year, I'm using IDE version 1.8.12; I don't know what I was using last year.

Would appreciate any ideas on why this may be occurring or things to check. I do have WireShark but am a novice at running it.

#define FS_NO_GLOBALS  // <-----reqd for SPIFFS with SdFat-------

#include "FS.h" // for SPIFFS
fs::File SPIFFSfile;   // <-----fs:: added for compatibility with SdFat ------

#include <ESP8266WiFi.h>
WiFiServer server(80);
WiFiClient client;

#include <time.h>
struct tm* tmStrc;
time_t myTime;
char timeFormated[24];

#include <SdFat.h>
SdFat SD;
File myFile;

byte bTmpNow = 55; // temp
int iDCnt    = 99; // count
int cGramC   = 4567; // corrected weight
char dateStr[] = "2020/09/99";
char timeStr[] = "16:99:99";

const char* ssid = "xxxx";
const char* password = "xxx";

char str[12];

void setup()
{
  Serial.begin(115200); Serial.println(); Serial.println("Wesp06d");
  
  Serial.print("Cnctng to "); Serial.print(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500); Serial.print(".");
  }
  Serial.print(" at IP: "); server.begin(); Serial.println(WiFi.localIP());

  Serial.print("Strt SPIFFS..."); bool result = SPIFFS.begin(); Serial.print("result: "); Serial.println(result);
  Serial.println("setup done ");  Serial.println();

}

void loop()
{

  byte req_index              = 0;
  const byte REQ_BUF_SZ      = 20;
  char HTTP_req[REQ_BUF_SZ] = {0};
  
  char textGET[] = "GET / ";
  char textGETindex[] = "GET /index.htm";
  char textGETgraphHTM[] = "GET /graph.htm";
  char textGETlegend[] = "GET /legend.htm";
  char textGETiplist[] = "GET /iplist.htm";
  char textGETgraphBMP[] = "GET /graph.bmp";
  char textGETlegendJPG[] = "GET /legendsm.jpg";
  char textGETfavicon[] = "GET /favicon.ico";
  
  static unsigned int viewCnt = 0;

  client = server.available();

  if (client) {

    Serial.print("new clnt "); Serial.println(client.remoteIP());
    myTime = time(NULL); Serial.println(ctime(&myTime));

    fs::File iplistFile = SPIFFS.open("/iplist.txt", "a");
    iplistFile.println(ctime(&myTime)); iplistFile.println(" ");
    iplistFile.println(client.remoteIP()); iplistFile.println("
");
    iplistFile.close();

    boolean currentLineIsBlank = true;

    while (client.connected())
    {
      if (client.available())
      { // client data available to read
        char c = client.read();

        if (req_index < (REQ_BUF_SZ - 1))
        {
          HTTP_req[req_index] = c;
          req_index++;
        }
        Serial.print(c);

        if (c == '\n' && currentLineIsBlank)
        {          
          if (StrContains(HTTP_req, textGET) || StrContains(HTTP_req, textGETindex))
          {
            Serial.println(F("serving index"));

            client.println(F("HTTP/1.1 200 OK"));
            client.println(F("Content-Type: text/html"));
            client.println(F("Connnection: close"));
            client.println();

            SPIFFSfile = SPIFFS.open("/index.htm", "r");
            writeSPIFFSfile();

            client.print("<html>\r\n<u>LATEST DATA</u>
");
            client.print("Temp: "); client.print(bTmpNow); client.print(" deg F
");
            client.print("Weight: "); client.print((cGramC + 50) / 100); client.print(" gr
");
            client.print("Count: "); client.print(iDCnt); client.print("
");
            client.print("Data as of: "); client.print(dateStr); client.print(" "); client.print(timeStr);
            client.print(F(" (updated every 15 min)
"));

            client.print("
Views = "); client.print(viewCnt >> 1);
            client.print("</html>\n");

            viewCnt++; // increment count

          }
          else if (StrContains(HTTP_req, textGETgraphHTM))
          {
            Serial.println(F("serving graph page"));

            client.println(F("HTTP/1.1 200 OK"));
            client.println(F("Content-Type: text/html"));
            client.println(F("Connnection: close"));
            client.println();
            SPIFFSfile = SPIFFS.open("/graph.htm", "r");
            writeSPIFFSfile();

            client.print(F("<html>\r\n Data starts Mar 30.
Graph updated each eve."));
            client.print("</html>\n");
          }
          else if (StrContains(HTTP_req, textGETlegend))
          {
            Serial.println(F("serving legend page"));

            client.println(F("HTTP/1.1 200 OK"));
            client.println(F("Content-Type: text/html"));
            client.println(F("Connnection: close"));
            client.println();
            SPIFFSfile = SPIFFS.open("/legend.htm", "r");
            writeSPIFFSfile();
          }
          else if (StrContains(HTTP_req, textGETiplist))
          {
            Serial.println(F("serving iplist page"));

            client.println(F("HTTP/1.1 200 OK"));
            client.println(F("Content-Type: text/html"));
            client.println(F("Connnection: close"));
            client.println();
            SPIFFSfile = SPIFFS.open("/iplist.txt", "r");
            writeSPIFFSfile();
          }
          else if (StrContains(HTTP_req, textGETgraphBMP))
          {
            Serial.println("serving graph.bmp ");

            client.println(F("HTTP/1.1 200 OK"));
            client.println(F("Content-Type: image/bmp"));
            client.println();
            SPIFFSfile = SPIFFS.open("/graph.bmp", "r");
            writeSPIFFSfile();
          }
          else if (StrContains(HTTP_req, textGETlegendJPG))
          {
            Serial.println("serving legendsm.jpg ");

            client.println(F("HTTP/1.1 200 OK"));
            client.println(F("Content-Type: image/jpeg"));
            client.println();
            SPIFFSfile = SPIFFS.open("/legendsm.jpg", "r");
            writeSPIFFSfile();
          }
          else if (StrContains(HTTP_req, textGETfavicon))
          {
            client.println(F("HTTP/1.1 404 NOT FOUND"));
            client.println(F("Connnection: close"));
            client.println();
          }

          delay(1);

          StrClear(HTTP_req, REQ_BUF_SZ);
          break;  // leave while (client.connected())
        }

        if (c == '\n')
        {
          currentLineIsBlank = true;
        }
        else if (c != '\r')
        {
          currentLineIsBlank = false;
        }

      }
    }

    delay(1);
    client.stop();
    Serial.println("*********clnt stppd**********");
    Serial.println();
    
  }
}

void writeSPIFFSfile()
{
  if (SPIFFSfile)
  {
    client.write(SPIFFSfile); // https://github.com/esp8266/Arduino/issues/1853
    SPIFFSfile.close();
  }
  else
  {
    Serial.println("file !fnd");
  }
}

void StrClear(char *str, byte length)
{
  for (byte i = 0; i < length; i++)
  {
    str[i] = 0;
  }
}

byte StrContains(char *str, char *sfind)
{
  byte found = 0;
  byte index = 0;
  byte len;

  len = strlen(str);

  if (strlen(sfind) > len)
  {
    return 0;
  }
  while (index < len)
  {
    if (str[index] == sfind[found])
    {
      found++;
      if (strlen(sfind) == found)
      {
        return 1;
      }
    }
    else
    {
      found = 0;
    }
    index++;
  }

  return 0;
}

size_t readField(File* file, char* str, size_t size, char* delim) {
  char ch;
  size_t n = 0;
  while ((n + 1) < size && file->read(&ch, 1) == 1) {
    // Delete CR.
    if (ch == '\r') {
      continue; // and go back to "while"
    }
    if (strchr(delim, ch)) {
      break;
    }
    str[n++] = ch; // original had this before the if (strchr(delim,ch))...which added the delim to str
  }
  str[n] = '\0';
  return n;
}

Example html file... graph.htm This page shows the graph.bmp file.

<!DOCTYPE html>
<html>
    <head>
        <title>Graph Main</title>
        <style>
        img {
              max-width: 100%;
              height: auto;
        }
        </style>                
    </head>
    <body>
        <h1>2019 data</h1>
        <img src="graph.bmp" alt="loading...pls wait" width="480" height="320">
        <p><a href="legend.htm">Click here</a> for legend.</p>
        <p>Back to <a href="index.htm">main page</a>.</p>
  
    </body>
</html>

If every thing works on the lan, then I'd check the router port forwarding to make sure the incoming request is still being sent to the proper internal IP address.

It might not make a difference but the old html I've used in the past, referenced files/pages have a back slash like below that appends to the referenced URL.

<p>Back to <a href="/index.htm">main page</a>.</p>

zoomkat:
If every thing works on the lan, then I'd check the router port forwarding to make sure the incoming request is still being sent to the proper internal IP address.

Thanks, but the Serial.prints show the incoming external requests are coming through. And here's what is served to external requests:

Annotation 2020-03-29 082758.jpg

Whereas this is served to internal requests:

That is, just the BMP image isn't getting served to external requests. Same with the legend page, which has a JPG...the text in the HTML file comes through ok to external requests, but the JPG doesn't.

Here's an example of the Serial.print output from an external request:

GET /graph.htm HTTP/1.1
Host: myDDNS:externalPort
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: myDDNS:externalPort
Upgrade-Insecure-Requests: 1

serving graph page
clnt stppd*

new clnt a.b.c.d
Thu Jan 1 00:00:00 1970

GET /graph.bmp HTTP/1.1
Host: myDDNS:externalPort
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: image/webp,/
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: myDDNS:externalPort/graph.htm

serving graph.bmp
clnt stppd*

Here's an example of the Serial.print output from an internal request:

GET /graph.htm HTTP/1.1
Host: 192.168.1.xx
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Referer: http://192.168.1.xx/
Upgrade-Insecure-Requests: 1

serving graph page
clnt stppd*

new clnt 192.168.1.118
Thu Jan 1 00:00:00 1970

GET /graph.bmp HTTP/1.1
Host: 192.168.1.xx
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: image/webp,/
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Referer: http://192.168.1.xx/graph.htm

serving graph.bmp
clnt stppd*

They're the same, except for the IP.

Annotation 2020-03-29 082758.jpg

Followup: the above problem occurred when I used an old "git" version of the ESP8266 core. I subsequently modified the code to work with the current Boards Manager version, and now images are served to both internal and external requests. Yay. But external requests load veeeeeeeeeeeeerrrryyyy slowly, whereas internal requests are served reasonably quickly. That issue will be the subject of a separate post...