me-no-dev / ESPAsyncWebServer

Async Web Server for ESP8266 and ESP32
3.75k stars 1.22k forks source link

Request to send HTML code #949

Closed Tech500 closed 3 years ago

Tech500 commented 3 years ago

Trying to add a link to return to Home link; inside of a text file being displayed.

request->send("text/html", "<br><br><br><h2><a href='linkAddress/BME680' >Home"</a></h2>);

What is the correct syntax?

William

stale[bot] commented 3 years ago

[STALE_SET] This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. Thank you for your contributions.

zekageri commented 3 years ago
server.on("/whatever", HTTP_GET, [](AsyncWebServerRequest *request){

  String resp = "<br><br><br><h2><a href='BME680'>Home</a></h2> ";

  AsyncWebServerResponse *response = request->beginResponse(200, "text/html", resp);
  response->addHeader("Access-Control-Allow-Origin","*");
  request->send(response);
});
Tech500 commented 3 years ago

Thank you zekageri; for your response.

stale[bot] commented 3 years ago

[STALE_CLR] This issue has been removed from the stale queue. Please ensure activity to keep it openin the future.

Tech500 commented 3 years ago

Is it possible to add a html link to a text file. Fuction reads a text file and displays to browser. Have been unable to add link after text file has been displayed.

`void notFound(AsyncWebServerRequest *request) {

digitalWrite(online, HIGH);   //turn-on online LED indicator

if (! request->url().endsWith(F(".TXT")))
{
  request->send(404);
}
else
{
  if (request->url().endsWith(F(".TXT")))
{
  //.endsWith(F(".txt")))

  // here comes some mambo-jambo to extract the filename from request->url()
  int fnsstart = request->url().lastIndexOf('/');

  fn = request->url().substring(fnsstart);

  PATH = fn;

  accessLog();

  Serial.print("File:  ");
  Serial.println(fn);

  File webFile = SPIFFS.open(fn);

  Serial.print("File size: ");

  Serial.println(webFile.size());

  if (!webFile)
  {

    Serial.println("File:  " + fn + " failed to open");
    Serial.println("\n");

  }
  else if (webFile.size() == 0)
  {

    String resp = "<br><br><br><h2><a href=http://%LINK%/Weather >Home</a> ";

    AsyncWebServerResponse *response = request->beginResponse(200, "text/html", resp);
    response->addHeader("Access-Control-Allow-Origin","*");
    request->send(response);

    webFile.close();

  }
  else
  {

    //request->send(SPIFFS, fn, String(), true);  //Download file
    request->send(SPIFFS, fn, String(), false);  //Display file

    webFile.close();

  }

  fn = "";

  end();

}

}

digitalWrite(online, LOW); //turn-off online LED indicator

}`

William

zekageri commented 3 years ago

I don't really understand what you want to do here.

In this example you opened a file and did nothing with it. You can serve a whole HTML page from directly the filesystem if you want. And inside that html file you can put whatever you like. Even links to another url on your esp.

Tech500 commented 3 years ago

zekageri your code works perfectly from setup. Is there way to provide a solution from inside "notFound(AsyncWebServerRequest *request) fuction? My attemps have all failed.

zekageri commented 3 years ago

Yes. You can serve whatever you want from any callback. I will write a code for you soon.

Tech500 commented 3 years ago

Thank you zekageri; appreciate the help!

zekageri commented 3 years ago

Here is an example for the notfound callback.

 server.onNotFound([](AsyncWebServerRequest *request){
   // for the LITTLEFS you can write your speicifc fs for example SPIFFS, next the actual file name in your system and the content type.
    AsyncWebServerResponse* response = request->beginResponse(LITTLEFS, "/NotFound.html", "text/html");
    // response->addHeader("Content-Encoding", "gzip"); // uncomment this if your file is gzipped
    request->send(response);
  });

Here is a favicon with cache control

 server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest *request){
    AsyncWebServerResponse* response = request->beginResponse(LITTLEFS, "/AppIcon.png","image/png");
    response->addHeader("Cache-Control", "max-age=31536000");
    request->send(response);
  });

Here is a jSON file from the file system

 server.on("/newManifest.json", HTTP_GET, [](AsyncWebServerRequest *request) {
      AsyncWebServerResponse* response = request->beginResponse(LITTLEFS, "/newManifest.json", "application/json");
      response->addHeader("Cache-Control", "max-age=31536000");
      request->send(response);
  });

Here is the index.html ( the main page ) with gzipped content encoding.

 server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
      AsyncWebServerResponse* response = request->beginResponse(LITTLEFS, "/index.html", "text/html");
      response->addHeader("Content-Encoding", "gzip");
      request->send(response);
  });
Tech500 commented 3 years ago

zekageri really appreciate all the coding you wrote

This code is what trying to do in my project; on Win pb this code displays log file, then adds url link to bottom of displayed log file; which is plain text only.


<html>
<head>
<title>README</title>
</head>
<body>

<div><object data="C:\users\1234\Desktop\Notes\LOG07042020.TXT" width="100%" height="800"></object></div>

<br><h2><a href=http://10.0.0.100:8030/SdBrowse >SdBrowse</a>

</body>
</html>

Log*.TXT files are listed as url links on the SdBrowse web page; once selected the filename 
is extracted by the notFound function, then the file is displayed.

Best I can summarize; is showing what I have done so far, this from the notFound function 
to which the url link is passed, then file name is extracted.  

   String resp = "<!DOCTYPE html><html><head><title>Observations</title></head><body>";
        resp += "<div><object data=" + fn + " width='100%' height='800'></object></div>";
        resp += "<br><h2><a href=http://10.0.0.100:8030/SdBrowse >SdBrowse</a></body></html>";

        AsyncWebServerResponse *response = request->beginResponse(200, "text/html", resp);
        response->addHeader("Access-Control-Allow-Origin","*");
        request->send(response);   
zekageri commented 3 years ago

So if i understand you correctly.

You want a webpage where you can push a button and it displays a log file which is served from the esp32's filesystem?

zekageri commented 3 years ago

You could serve a page from the esp like this:

  // It will send the index.html file
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
      AsyncWebServerResponse* response = request->beginResponse(LITTLEFS, "/index.html", "text/html");
      request->send(response);
  });

INDEX.HTML file would look like this:

note: i'm using JQUERY here from a CDN

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <title>Test</title>
</head>

<body>
    <button onclick="fetchLogFile()" type="button" id="showLogFile">Show log</button>
    <div id="logFileDiv"></div>
</body>

<script>
    function fetchLogFile(){
        // FETCHING THE FILE FROM THE ESP
        $.get("/getMyLog", // <-- TheURLForLogFile
            function (data, textStatus, jqXHR) {
                $("#logFileDiv").append(data); // <-- HERE I APPEND THE DATA TO THE DIV
            },
        );
    }
</script>
</html>

And the esp would answer to the request like this:

  server.on("/getMyLog", HTTP_GET, [](AsyncWebServerRequest *request) {
      String LOGSTRING = "blablabla";
      AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", LOGSTRING );
      request->send(response);
  });

ESP could serve the whole file to the response like this:

  server.on("/getMyLog", HTTP_GET, [](AsyncWebServerRequest *request) {
      AsyncWebServerResponse* response = request->beginResponse(LITTLEFS, "/LOGFILE.txt", "text/html");
      request->send(response);
  });
Tech500 commented 3 years ago

I would like to pass variable "fn" (developed in the notFound function) instead of a hard coded file name; do not see a way to do this in your examples. Do you think it is possible?

zekageri commented 3 years ago

But why would you do that? I dont see the point. Do you need 404 response code? You can create one in an other callback.

zekageri commented 3 years ago

In my example, your log file content will be appended to the web page's div. Just like you did. But without the link element. If you want to wrap every line in your txt file into a link element you can do that on the client side via javascript. That way the esp doesnt have to handle that as well.

Tech500 commented 3 years ago

I serve a web page that lists all LOG files. Once a file is selected it becomes a notFound request and the filename is extracted; which is the fn named variable. Since we can not have a GET request for each LOG filename; this is the reason it needs to be a variable.

Current code displays LOG file well; just with no way to reurn directly to the servered web page that lists LOG files, other than using the back arrow in the browser.

Project web site

zekageri commented 3 years ago

You can download the files when the user clicks one of the links.

For example:

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <!-- Note iam using JQUERY -->
    <title>Test</title>
</head>

<body>

<label for="logs">Select a log file</label>

<select name="logOptions" id="logs">
  <option value="log1.txt">log1</option> <!-- value is the name of the file -->
  <option value="log2.txt">log2</option>
  <option value="log3.txt">log3</option>
  <option value="log4.txt">log4</option>
</select>

    <div id="logFileDiv"></div>
</body>

<script>

$(document).ready(function () {

    $("#logs").on("change", function (e) {
        var logFileName = $(this).val();
        downloadLogFile(logFileName);
    });

});

// Note there are no error checks in this example.
function downloadLogFile(fileName){
    $.ajax({
        url:`/downLog?param0=/${fileName}`,
           success: function (data){
               $("#logFileDiv").empty();
               $("#logFileDiv").html(data);
           }
    });
}

</script>
</html>

ESP SIDE:

server.on("/downLog", HTTP_GET, [](AsyncWebServerRequest *request){
   AsyncWebParameter* p = request->getParam(0);
   AsyncWebServerResponse *response;
   char fileBuff[32];
   p->value().toCharArray(fileBuff,32);
   if( LITTLEFS.exists(fileBuff) ){
     response = request->beginResponse(LITTLEFS, fileBuff, "text/plain", true);
     request->send(response);
   }else{
     response = request->beginResponse(204, "text/plain", "No such file");
     request->send(response);
   }
});

That way you serve a relative small html page and fetch the contents of the log files when the user wants to see it. And you don't have to show them an other page, they can see it right there.

Tech500 commented 3 years ago

Does jQuery need to be installed on Win pc to use your code? Could not get code to work.

zekageri commented 3 years ago

Jquery is a frontend javascript library. In this case, it will work if you have an internet connection. At least the esp. But i assume you have since i can see the esp's web interface so the problem is elsewhere. What part is not working?

zekageri commented 3 years ago

But why would you want to use the notfound handler for this?

You can create an other callback just like i said and download the logs as you wish. If the problem is that the log file names are hardcoded, you can fetch all the files in the filesystem when a user loads a page and put all the file names inside the select element in my example.

Tech500 commented 3 years ago

"Lots of "moving" parts," I do not list all files; only LOG*.TXT files with the SdBrowse request. Requested url is not found; goes to the notfound handler, "/filename" is extracted from url Then sent using "request->send(SPIFFS, fn, String(), false); //Display file."

I would like to avoid adding additional "serverAsync.on("/something", HTTP_GET." Plus; I want to pass variable to String processor6 for "uncfn," which is "/" taken off the fn variable Need for

<object in HTML6 (index6.h).data=http:\ipaddress:8030\LOG06242021.TXT width="100%" height="700">

Would like to replace "request->send(SPIFFS, fn, String(), false);" with something similar to "request->send_P(200, PSTR("text/html"), HTML6, processor6);"

Tech500 commented 3 years ago

zekageri, Took another look at your example code; works 100%, thank you.

Jquery example

How would I format:

"Lat: 39.76091 , Long: -85.99419 Elevation: 843 Feet Indianapolis, IN , Sun , 07/04/2021 , 00:00:00 EDT Sun , 07/04/2021 , 00:00:00 EDT , Temperature: 79.3 F. , Heatindex: 78.9 F. , Humidity: 41.7 % , Dewpoint: 54.1 F. , Barometer: 29.968 inHg. , 0.000 Diff. inHg , Day 0.00 , Hour 0.00 , Five Minute 0.00 Sun , 07/04/2021 , 00:15:00 EDT , Temperature: 79.3 F. , Heatindex: 78.9 F. , Humidity: 42.0 % , Dewpoint: 54.2 F. , Barometer: 29.969 inHg. , 0.000 Diff. inHg , Day 0.00 , Hour 0.00 , Five Minute 0.00"

--to have it on a single line with no wrapping?

zekageri commented 3 years ago

You can do this on Javascript client side. Before you append the content of the file you could replace or remove all the line breaks first like this:

someText = someText.replace(/(\r\n|\n|\r)

And your text should be in someText variable without Line breaks.

Example from my previous example JS:

function downloadLogFile(fileName){
    $.ajax({
        url:`/downLog?param0=/${fileName}`,
           success: function (data){
               // replacing all kinds of line breaks with regex. 
               $("#logFileDiv").html( data.replace(/(\r\n|\n|\r) );

              // Or here is an alternative, it is replacing with nothing '' or you can replace it with a blank whitespace ' ';
              // $("#logFileDiv").html( data.replace(/(?:\r\n|\r|\n)/g, '') ); 
           }
    });
}
Tech500 commented 3 years ago

Sara Santos @ rntlab.com found a better solution:

To solve that issue you can add the following styling to the paragraph that displays the data:

white-space: pre-wrap;

Sara found this suggestion here: Solution

Now; log files have header, text area and hyperlink!

zekageri commented 3 years ago

You wanted the text in a single line without wrapping and you does not said that you wanted it formatted. In this case you better create the txt file already formatted when logging at esp side. Or you could easily log this information in a JSON file at esp side and client side you can do anything with that JSON. ^^

check out arduinojson library

Tech500 commented 3 years ago

zekageri Thank you for all your coding examples and patience and time spent responding.

Here is code I am using to add header, text area, and hyperlink:


"HTML6":

const char HTML6[] PROGMEM = R"====(
<!DOCTYPE HTML>
<html>

<head>
    <title>Show</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.main
{
  type: text/txt;
  font-size:18px;
  width: auto;
  height: auto;
  margin: 30px;
}
</style>
</head>

<body>

    <h2>Observations
    <!-- <a href="/Show"><button>Show Log File</button></a> -->
    <br>
    <br>
    <div class="main" style="white-space: pre-wrap;" type="text/txt" id=file-content></div>
    <!-- <p style="white-space: pre-wrap;"id="file-content"></p>  -->
    <br>  
    <a href=http://%LINK%/SdBrowse >SdBrowse</a></h2><br>    

</body>
<script>
  window.addEventListener('load', getFile);
  function getFile(){
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() 
    {
      if (this.readyState == 4 && this.status == 200) 
      {
        console.log(this.responseText);
        document.getElementById("file-content").innerHTML=this.responseText;
      }
    };
  xhr.open("GET", "/get-file", true);
  xhr.send();
}
</script>
</html>
)====";

----------
Request "/Show " //Loads HTML6

serverAsync.on("/Show", HTTP_GET, [](AsyncWebServerRequest * request) {

PATH = "/Show";
accessLog();

if (! flag == 1)
{

  request->send_P(200, PSTR("text/html"), HTML6, processor6);

}
end();

});


Request to get text file for HTML6:

 serverAsync.on("/get-file", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send(SPIFFS, fn, "text/txt");
  });

---------------------

Request to get redirect from "notFound" function:

serverAsync.on("/redirect/internal", HTTP_GET, [](AsyncWebServerRequest *request){ request->redirect("/Show"); //recevies HTML request to redirect });

All that is left is a bit "fine tuning."

Check out project web site

ESP32 Project "RainGauge" code

William