Hi all,
There is a fairly well-known cross-platform library in the embedded world called "mongoose" : https://mongoose.ws/
This library is able to:
It is completely asynchronous and above all, it does not rely on any intermediate network layer (like QNEthernet or Native Ethernet or even LWIP).
I just realized its port for the Teensy 4.1 and its performance is superior to anything I've tested elsewhere.
To install it, just copy the two files (.h / .c) from the library into the project folder and include the .h in your project.
While waiting for my PR to be merged, you can already try it from here : https://github.com/cesanta/mongoose/pull/1954
And there an sketch exemple that serve an LittleFS "www" directory on HTTP :
Thanks to ssilverman and PaulStoffregen for their work.
There is a fairly well-known cross-platform library in the embedded world called "mongoose" : https://mongoose.ws/
This library is able to:
- TCP client/server
- UDP client/server
- HTTP(S) client/server
- Websocket(S) client/server
- MQTT(S) client/server
It is completely asynchronous and above all, it does not rely on any intermediate network layer (like QNEthernet or Native Ethernet or even LWIP).
I just realized its port for the Teensy 4.1 and its performance is superior to anything I've tested elsewhere.
To install it, just copy the two files (.h / .c) from the library into the project folder and include the .h in your project.
While waiting for my PR to be merged, you can already try it from here : https://github.com/cesanta/mongoose/pull/1954
And there an sketch exemple that serve an LittleFS "www" directory on HTTP :
Code:
#include "driver_teensy41.h"
#include "mongoose.h"
#include "IPAddress.h"
#include <LittleFS.h>
#include <MTP_Teensy.h>
struct mg_mgr mgr; // Mongoose event manager
struct mip_if mif; // MIP network interface
LittleFS_Program _fs;
struct Entry {
const char endsWith[16];
const char mimeType[32];
};
// Table of extension->MIME strings stored in PROGMEM, needs to be global due
// to GCC section typing rules
const Entry mimeTable[] = {{".html", "text/html"},
{".htm", "text/html"},
{".css", "text/css"},
{".txt", "text/plain"},
{".js", "application/javascript"},
{".json", "application/json"},
{".png", "image/png"},
{".gif", "image/gif"},
{".jpg", "image/jpeg"},
{".ico", "image/x-icon"},
{".svg", "image/svg+xml"},
{".ttf", "application/x-font-ttf"},
{".otf", "application/x-font-opentype"},
{".woff", "application/font-woff"},
{".woff2", "application/font-woff2"},
{".eot", "application/vnd.ms-fontobject"},
{".sfnt", "application/font-sfnt"},
{".xml", "text/xml"},
{".pdf", "application/pdf"},
{".zip", "application/zip"},
{".gz", "application/x-gzip"},
{".appcache", "text/cache-manifest"},
{"", "application/octet-stream"}};
const char *mygm_http_get_content_type(const String &f) {
const char *contentType = NULL;
int s = (sizeof(mimeTable) / sizeof(mimeTable[0]));
for (int i = 0; i < s; i++)
if (f.endsWith(mimeTable[i].endsWith)) contentType = mimeTable[i].mimeType;
if (!contentType) contentType = mimeTable[s - 1].mimeType;
return contentType;
}
#define CHUNKSIZE 1350
struct FileContainerStruct {
File file;
char *chunkBuffer;
};
void mymg_http_serve_file_begin(struct mg_connection *c, mg_str *uri) {
String path = "/www" + String(uri->ptr).substring(0, uri->len);
if (path.endsWith("/")) path += "index.html";
const char *contentType = mygm_http_get_content_type(path);
FileContainerStruct *pFcs = new FileContainerStruct();
pFcs->chunkBuffer = new char[CHUNKSIZE];
if (!(pFcs->chunkBuffer)) {
delete pFcs;
mg_http_reply(c, 500, "", "Memory allocation error\n");
return;
}
pFcs->file = _fs.open(path.c_str(), FILE_READ);
Serial.printf(" -> %s : %s (%d/%d) : ", path.c_str(), pFcs->file.name(),
pFcs->file.size(), pFcs->file.available());
Serial.printf("%s\n", contentType);
if (!(pFcs->file)) {
delete pFcs;
mg_http_reply(c, 404, "", "File Not found\n");
return;
}
c->fn_data = pFcs;
mg_printf(c,
"HTTP/1.1 200 OK\r\nTransfer-Encoding: "
"chunked\r\nContent-Type: %s\r\n\r\n",
contentType);
}
void mymg_http_serve_file_chunk(struct mg_connection *c) {
if (!(c->fn_data)) return;
if (c->send.len != 0) return;
FileContainerStruct *pFcs = (FileContainerStruct *) c->fn_data;
size_t l = pFcs->file.readBytes(pFcs->chunkBuffer, CHUNKSIZE);
mg_http_write_chunk(c, pFcs->chunkBuffer, l);
if (l == 0) mymg_http_server_file_interrupt(c);
}
void mymg_http_server_file_interrupt(struct mg_connection *c) {
if (!(c->fn_data)) return;
FileContainerStruct *pFcs = (FileContainerStruct *) c->fn_data;
Serial.printf("Close : %s\n", pFcs->file.name());
pFcs->file.close();
delete pFcs->chunkBuffer;
delete pFcs;
c->fn_data = NULL;
}
static void handler(struct mg_connection *c, int ev, void *ev_data,
void *fn_data) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
switch (ev) {
case MG_EV_HTTP_MSG:
if (mg_http_match_uri(hm, "/api/hello")) {
mg_http_reply(c, 200, "", "OK : %.*s\n", hm->uri.len, hm->uri.ptr);
break;
}
// serve file
mymg_http_serve_file_begin(c, &(hm->uri));
break;
case MG_EV_POLL:
mymg_http_serve_file_chunk(c);
break;
case MG_EV_CLOSE:
case MG_EV_ERROR:
mymg_http_server_file_interrupt(c);
break;
}
}
void setup() {
Serial.begin(115200);
if (!_fs.begin(1024 * 1024 * 2)) {
Serial.print("Error starting file system.\n");
while (1)
; // Error, so don't do anything more - stay stuck here
}
MTP.addFilesystem(_fs, "LittleFS");
// Set logging function to a serial print
mg_log_set_fn([](char ch, void *) { Serial.print(ch); }, NULL);
mg_log_set(MG_LL_INFO);
mg_mgr_init(&mgr);
MG_INFO(("Starting TCP/IP stack..."));
mif.driver = &mip_driver_teensy41;
IPAddress ip(192, 168, 123, 10);
IPAddress mask(255, 255, 255, 0);
mif.enable_dhcp_client = false;
mif.enable_dhcp_server = false;
mif.ip = ip;
mif.mask = mask;
mip_init(&mgr, &mif);
// Setup HTTP listener. Respond "ok" on any HTTP request
mg_http_listen(&mgr, "http://0.0.0.0", handler, NULL);
}
void loop() {
mg_mgr_poll(&mgr, 1);
MTP.loop();
}
Thanks to ssilverman and PaulStoffregen for their work.