Giter Club home page Giter Club logo

awot's Introduction

aWOT Build Status

Arduino web server library.

Documentation

1. Getting started

2. Guide

3. API Reference

Compatibility

The aWOT web server library has been designed to work with all Arduino compatible development boards and networking options. This also means that switching the board or changing from WiFi to Ethernet will require minimal changes. The examples directory shows you how to use the library with the most popular Ethernet and WiFi libraries

However there are few subtle differences that need to be taken into account. Also unfortunately some of the WiFi and Ethernet libraries have bugs that prevent the library from working properly.

Previously the library has been tested with:

  • Adafruit Feather M0 WiFi
  • Arduino UNO + WIZnet W5100
  • Arduino Mega + WIZnet W5100
  • Arduino Due + WIZnet W5100
  • Arduino MKR WiFi 1010 (see notes)
  • Teensy 3.0 + WIZnet W5200
  • Teensy 4.1 Ethernet (see notes)
  • ESP32 + Wiznet W5500 Ethernet (see notes)
  • ESP32 + LAN8270 Ethernet
  • ESP32 WiFi
  • ESP8266 WiFi

ESP32 and ESP8266 WiFi

In both of the ESP Arduino cores the WiFiClient closes the connection automatically in the class destructor. This means that the client.stop(), does not need to be explicitly called but you will need to take extra steps if you want to keep the connection alive.

ESP32 + Wiznet W5500

The current version of the ESP32 Arduino core uses a non standard version of the Server class. Until the ESP32 core is fixed you need to manually modify the begin function in the Server.h if you want to use the Ethernet library that is shipped with the core.

Bug report: espressif/arduino-esp32#2704

Teensy 4.1 + Ethernet

The Teensy 4.1 Ethernet library currently has a bug that causes the connection to stall and reset when connections to the server are opened in fast phase. The bug has been verified but not fixed yet.

Bug report: vjmuzik/NativeEthernet#7

Arduino UNO

Because of the limited RAM and ROM Arduino UNO is on the edge of being usable for anything more complicated. If you want to use this library together with the SD card or any JSON parsing library, pay attention that you do not run out of memory.

Examples

Hello World

#include <WiFi.h>
#include <aWOT.h>

WiFiServer server(80);
Application app;

void index(Request &req, Response &res) {
  res.print("Hello World!");
}

void setup() {
  Serial.begin(115200);

  WiFi.begin("ssid", "password");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(WiFi.localIP());

  app.get("/", &index);
  server.begin();
}

void loop() {  
  WiFiClient client = server.available();

  if (client.connected()) {
    app.process(&client);
    client.stop();
  }
}

Query parameters

// HTTP GET /cats?type=lolcat
void queryParams(Request &req, Response &res) {
  char type[64];
  req.query("type", type, 64);

  res.print(type); // "lolcat"
}

void setup() {
  // other setup ...
  app.get("/cats", &queryParams);
}

Route paramaters

// HTTP GET /cats/lolcat
void routeParams(Request &req, Response &res) {
  char catId[64];
  req.route("catId", catId, 64);

  res.print(catId);
}

void setup() {
  // other setup
  app.get("/cats/:catId", &routeParams);
}

Post parameters

void postParams(Request &req, Response &res) {
  char name[10];
  char value[64];

  while (req.left()) {
    req.form(name, 10, value, 64);
    res.print(name);
    res.print(": ");
    res.println(value);
  }
}

void setup() {
  // other setup

  app.post("/form", &postParams);
}

Reading and writing headers

char userAgentBuffer[200];

// HTTP GET /headers
void headers(Request &req, Response &res) {
  char * userAgent = req.get("User-Agent"); // "Mozilla/5.0 (Macintosh; Inte ...."

  res.set("Cookie", "lolcat"); // will set Cookie header value to "lolcat"
  res.print(userAgent);
}

void setup() {
  // other setup

  // header names are handled case insensitive
  app.header("User-Agent", userAgentBuffer, 200); 
  app.get("/useragent", &headers);
}

Routers

Application app;
Router cats;

void looooong(Request &req, Response &res) {
  res.print("looooong cat is long!");
}

void ceiling(Request &req, Response &res) {
  res.print("ceiling cat is watching you debug!");
}

void nyannyan(Request &req, Response &res) {
  for (int i = 0; i < 100; i++) {
      res.print("nyan ");
  }
}

void setup() {
  // other setup

  cats.get("/long", &looooong);
  cats.get("/ceiling", &ceiling);
  cats.get("/nyan", &nyannyan);

  app.use("/cats", &cats);
}

Reducing memory usage

If you need to reduce the memory consumption add #define LOW_MEMORY_MCU before you import the library. This will reduce the size of a few internal buffers. Normally this is only used for the smallest AVR boards but it can be enabled for others if needed.

Also use the P macro to place any strings in to the program memory instead of wasting the precious RAM.

P(longString) = "Lots of text here...";
res.printP(longString);

Acknowledgements

Based on Webduino, Copyright 2009-2014 Ben Combee, Ran Talbott, Christopher Lee, Martin Lormes, Francisco M Cuenca-Acuna

Licence

MIT

awot's People

Contributors

buzzedword avatar lasselukkari avatar pablorodiz avatar per1234 avatar rngtng avatar tooblippe avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

awot's Issues

Question: how to close the response before rebooting the device

Hi, I am trying to send a complete response and close it before a reboot.
Unfortunately, the client never see the response coming. The ESP32 close the connection before flushing the full response out.

What I tried:

// res is of type aWot::Response
res.set("Connection", "close");
res.flush();
res.status(200);
res.end();
serveClient(...); // serving the client as for the examples
server.flush();
reboot();

In a similar code using the WebServer class, I never had this kind of issues.

What am I missing?
TIA

routing use .use appends 404 error after content is served

Using hello world example:

Output in browser if app.get("", &indexCmd); is used to route

Greetings middle earth!

Output in browser if app.use(&indexCmd); is used to route

Greetings middle earth!

HTTP/1.0 404 Not Found Content-Type: text/html

** 404 Not Found**

I want to deal with the routing myself as I will serve files from SD card as well as using Ajax

Rule of thumb for required call periodicity?

I've implemented a RESTful API server. At the moment I'm running the server in a superloop. I'm calling code similar to the one from https://github.com/lasselukkari/aWOT/blob/master/examples/Ethernet/Ethernet.ino#L43-L49 periodically. I observed that dependent on the call periodicity of this logic requests are not handled properly. In my particular case (Arduino Due + my application logic) responses are delivered properly if the logic is called every 430 microseconds (approx. 2325 Hz). If the logic is called every 3-4 milliseconds (33.3-250 Hz) my client does not get a response. I'm thinking about migrating the logic into RTOS tasks. Would be great if you could provide a rule of thumb w.r.t. how regularly logic needs to be executed. That would help to assign task priorities/define task trigger periodicity.

EDIT: The requests seem to be buffered. This means one has to consider call periodicity w.r.t. application specific requirements only like e.g. accetable responsiveness for HMI related endpoints which is often around 250ms (4Hz).

static website Rewrite

Hello,
i'm using your library in my project and i like it very much, thank you for your work!
I'm building a project with an angular client flashed with your aWOT-scripts and it works very amazing.

I have a static webserver at root that serve the angular client, and a Json server listening at /api:

app.get("/api", &readAPI);

app.use(staticFiles());

I need to create a rewrite for every request that isn't /api that point to index.html of the static files, like i can do with nginx rewrites:

location / {
    try_files $uri $uri/ /index.html =404;
}

example, i want that http://myip/edit-config rewrite to http://myip/.
Can you hep please?

Thank you and sorry for my english!

Parse Multiple Query parameters of POST request

Hi LAsse,

Thanks one more time for this library!
I am having an unexpected behaviour with the parametrized function request::query to parse the data sent from a POST form, is there any exemple on this? Shorly, I can´t get the function to find any of the parameters in the url.

Here is the part of my code, with plenty of Serial.prints to figure out what´s going on:

void EthWebManager::handle_save(Request &req, Response &res)
{
String page = FPSTR(HTTP_BEGIN);
page.replace("{pageTitle}","Settings received");
page += FPSTR(SETTINGS_RECEIVED_BODY);
page += FPSTR(FOOTER);
page += FPSTR(REDIRECT_SCRIPT);
page.replace("{timeoutS}","12");
page.replace("{timeoutms}","12000");
page.replace("{target}","/");
page += FPSTR(HTML_END);
res.set("Content-Type", "text/html");
res.print(page);
Serial.println(req.readString());
for (unsigned int i = 0; i < _paramsCount; i++)
{
Serial.print("Looking for par: "); Serial.print(_params[i]->getName()); Serial.print(", old val: "); Serial.println(_params[i]->_value);
Serial.println(req.query(_params[i]->getName(), _params[i]->_value, _params[i]->getValueLength()));
Serial.print("New val: "); Serial.println(_params[i]->_value);
}
}

Here is the corresponding serial output:
SensorReleaseTime=10&Freelights=%2300ff00&Busylights=%23ff0000&PulseDuration=15&BusyPulseRange=120&Freepulserange=40&EthIP=192.168.5.240&EthGateway=192.168.5.1&EthSubnet=255.255.255.0&EthDNS=8.8.8.8&Pod1Ch1MAC=4C%3A75%3A25%3AC3%3A06%3A18&Pod1Ch2MAC=4C%3A75%3A25%3AC1%3AB1%3A08&Pod2Ch1MAC=AC%3A0B%3AFB%3A6E%3AF3%3A1C&Pod2Ch2MAC=4C%3A75%3A25%3AC1%3A61%3AF8&Pod3Ch1MAC=4C%3A75%3A25%3AC4%3A1D%3A88&Pod3Ch2MAC=4C%3A75%3A25%3AC1%3A2A%3A48&Usr=user&Pas=password&submit=
Looking for par: SensorReleaseTime, old val: 10
0
New val:
Looking for par: Freelights, old val: #00ff00
0
New val:
Looking for par: Busylights, old val: #ff0000
0
New val:
Looking for par: PulseDuration, old val: 15
0
New val:
Looking for par: BusyPulseRange, old val: 120
0
New val:
Looking for par: Freepulserange, old val: 40
0
New val:
Looking for par: EthIP, old val: 192.168.5.240
0
New val:
Looking for par: EthGateway, old val: 192.168.5.1
0
New val:
Looking for par: EthSubnet, old val: 255.255.255.0
0
New val:
Looking for par: EthDNS, old val: 8.8.8.8
0
New val:
Looking for par: Pod1Ch1MAC, old val: 4C:75:25:C3:06:18
0
New val:
Looking for par: Pod1Ch2MAC, old val: 4C:75:25:C1:B1:08
0
New val:
Looking for par: Pod2Ch1MAC, old val: AC:0B:FB:6E:F3:1C
0
New val:
Looking for par: Pod2Ch2MAC, old val: 4C:75:25:C1:61:F8
0
New val:
Looking for par: Pod3Ch1MAC, old val: 4C:75:25:C4:1D:88
0
New val:
Looking for par: Pod3Ch2MAC, old val: 4C:75:25:C1:2A:48
0
New val:
Looking for par: Usr, old val: user
0
New val:
Looking for par: Pas, old val: password
0
New val:

You can see the complete query and the names I am asking the function to look for.

Digging deeper I had a look at the declaration of your query function, I can´t really understand how it works, but looks like it is never entering the while loop. I have modified it to add a serial output:

bool Request::query(const char *name, char *buffer, int bufferLength) {
memset(buffer, 0, bufferLength);

char *position = m_query;
int nameLength = strlen(name);

while ((position = strstr(position, name))) {
Serial.println("QueryWhileLooping");
char previous = *(position - 1);

if ((previous == '\0' || previous == '&') &&
    *(position + nameLength) == '=') {
  position = position + nameLength + 1;
  while (*position && *position != '&' && --bufferLength) {
    *buffer++ = *position++;
  }

  return bufferLength > 0;
}

position++;

}
Serial.println("Query exited while loop");
return false;
}

And it seems to confirm my hypotesis....

SensorReleaseTime=10&Freelights=%2300ff00&Busylights=%23ff0000&PulseDuration=15&BusyPulseRange=120&Freepulserange=40&EthIP=192.168.5.240&EthGateway=192.168.5.1&EthSubnet=255.255.255.0&EthDNS=8.8.8.8&Pod1Ch1MAC=4C%3A75%3A25%3AC3%3A06%3A18&Pod1Ch2MAC=4C%3A75%3A25%3AC1%3AB1%3A08&Pod2Ch1MAC=AC%3A0B%3AFB%3A6E%3AF3%3A1C&Pod2Ch2MAC=4C%3A75%3A25%3AC1%3A61%3AF8&Pod3Ch1MAC=4C%3A75%3A25%3AC4%3A1D%3A88&Pod3Ch2MAC=4C%3A75%3A25%3AC1%3A2A%3A48&Usr=user&Pas=password&submit=
Looking for par: SensorReleaseTime, old val: 10
Query exited while loop
0
New val:
Looking for par: Freelights, old val: #00ff00
Query exited while loop
0
New val:
Looking for par: Busylights, old val: #ff0000
Query exited while loop
0
New val:
Looking for par: PulseDuration, old val: 15
Query exited while loop
0
New val:
Looking for par: BusyPulseRange, old val: 120
Query exited while loop
0
New val:
Looking for par: Freepulserange, old val: 40
Query exited while loop
0
New val:
Looking for par: EthIP, old val: 192.168.5.240
Query exited while loop
0
New val:
Looking for par: EthGateway, old val: 192.168.5.1
Query exited while loop
0
New val:
Looking for par: EthSubnet, old val: 255.255.255.0
Query exited while loop
0
New val:
Looking for par: EthDNS, old val: 8.8.8.8
Query exited while loop
0
New val:
Looking for par: Pod1Ch1MAC, old val: 4C:75:25:C3:06:18
Query exited while loop
0
New val:
Looking for par: Pod1Ch2MAC, old val: 4C:75:25:C1:B1:08
Query exited while loop
0
New val:
Looking for par: Pod2Ch1MAC, old val: AC:0B:FB:6E:F3:1C
Query exited while loop
0
New val:
Looking for par: Pod2Ch2MAC, old val: 4C:75:25:C1:61:F8
Query exited while loop
0
New val:
Looking for par: Pod3Ch1MAC, old val: 4C:75:25:C4:1D:88
Query exited while loop
0
New val:
Looking for par: Pod3Ch2MAC, old val: 4C:75:25:C1:2A:48
Query exited while loop
0
New val:
Looking for par: Usr, old val: user
Query exited while loop
0
New val:
Looking for par: Pas, old val: password
Query exited while loop
0
New val:
Now manually BusyPulseRange: Query exited while loop
0

Breaking changes 2.x -> 3.x

First - I really love the aWOT framework. It's been incredibly useful for creating an API-driven service on my ESP8266.

I recently updated from 2.x to 3.x and was surprised with some of the major changes that were not documented in any sort of release notes. The online documentation is accurate for 3.x, but makes no reference on changes required to upgrade from an older 2.x installation.

In the future it would be helpful if there was a migration guide for breaking changes.

Awot crash on slow network with esp8266 + ethernet

We are using ethernet on esp8266 deployed on congested network, we often see the esp8266 crashing and upon debugging it points to awot while statements of the library, so we decided adding yield on every while statements with the client.available() and it fixed the frequent crash.
We have realized the solution since slow network could make the response wait for too long and since using a while(client.available) without yield inside would potentially trigger wdt resets.

Server down when use Router object

Hi,

Making a REST API on an Arduino UNO and including new methods over time, it has come to a point that when making use of a Router object as the root element of my calls, after an undetermined number of calls, the server crashes.
These are the REST API calls:

router.get("/ip/:ip", &change_ip_arduino);
router.get("/id", &get_id_arduino);
router.get("/semaphore/:rgbId/:time", &on_semaphore);
router.get("/offsemaphore", &off_semaphore);
router.get("/beat/:rgbId/:ncodes", &beat);
router.get("/spiral/:type/:rgbId/:ncodes", &spiral);
router.get("/alarm/:time", &on_alarm);
router.get("/offalarm", &off_alarm);
router.get("/shortlongalarm/:alarmtype/:ncodes", &short_long_alarm);
router.get("/combined/:semaphoretype/:rgbId/:alarmtype/:ncodes", &combined);
router.get("/relay/:type/:relayId", &on_off_relay);
router.get("/watchdog/:time", &watchdog);
router.get("/dhcp/:type", &add_dhcp);
router.get("/dhcp", &dhcpState);
router.get("/reset", &resetIP);

app.use("/box", &router);

But when the REST API calls are defined directly from the Application object, the server does not crash at any time. Why is this? Is there some kind of limitation in the Router objects?

In this way I define all the methods without Router:

app.get("/ip/:ip", &change_ip_arduino);
app.get("/id", &get_id_arduino);
app.get("/semaphore/:rgbId/:time", &on_semaphore);
app.get("/offsemaphore", &off_semaphore);
app.get("/beat/:rgbId/:ncodes", &beat);
app.get("/spiral/:type/:rgbId/:ncodes", &spiral);
app.get("/alarm/:time", &on_alarm);
app.get("/offalarm", &off_alarm);
app.get("/shortlongalarm/:alarmtype/:ncodes", &short_long_alarm);
app.get("/combined/:semaphoretype/:rgbId/:alarmtype/:ncodes", &combined);
app.get("/relay/:type/:relayId", &on_off_relay);
app.get("/watchdog/:time", &watchdog);
app.get("/dhcp/:type", &add_dhcp);
app.get("/dhcp", &dhcpState);
app.get("/reset", &resetIP);

The automatically generated App.js differs from the file in the tutorial

Hello Lasse,
I am using your Lib for the first time, everything is going great but I noticed that the automatic generation of the scripts creates a different file than in the tutorial:

automatic generated App.js

import logo from './logo.svg';
import './App.css';

 function App() {              <-------------------------------------------------------------
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

File from the tutorial

import React, { Component } from 'react';      <-------------------------------------------------------------
import logo from './logo.svg';
import './App.css';

class App extends Component {                    <-------------------------------------------------------------
        render() {                                                <-------------------------------------------------------------
	return (
		<div className="App">
		<header className="App-header">
		<img src={logo} className="App-logo" alt="logo" />
		<p>
			Edit <code>src/App.js</code> and save to reload.
		</p>
		<a
			className="App-link"
			href="https://reactjs.org"
			target="_blank"
			rel="noopener noreferrer"
		>
			Learn React
		</a>
	</header>
  </div>
  );
 } 
}
 
 export default App;

The automatically generated script caused errors, but if I use the script from the tutorial, everything works fine 👍

I am using windows 10 and the following versions:
PS J:\esp\eth-test> node -v
v14.15.3
PS J:\esp\eth-test> npm -v
6.14.9

Best regards,
Armin

net::ERR_CONNECTION_REFUSED using ethernet

Hello, thanks for your lib.
I use it with ESP32-S3 and the ethernet server fix you provided

When using WiFi, all works good,

when using ethernet,
I get net::ERR_CONNECTION_REFUSED on (random) file, for instance when the client try :

GET http://192.168.0.81/medium.gif net::ERR_CONNECTION_REFUSED

On the server side (ESP) I get no trace, that medium.gif has been requested.

Any help appreciated.
BR

Question on Functionality Using ESP32 and W5500

SETUP:
The build I have utilizes a W5500 wiznet 850io board with an ESP32-Wrover-E. I am programming currently with the latest ESP-IDF plugin for Arduino.

PLAN:
I was able to use the Wifi, Ethernet, Webserver, and SPI libraries to connect an HTML based site either with, without, or concurrently with Ethernet and WiFi by using a processor function of sorts on the client class. I am using HTTP protocols to gain user inputs/outputs from the website locally. I would like to use the file system uploader plugin for ESP32 with littleFS or FatFS to serve the webpages and to store the values in-between other serial communication I am parsing.

SITUATION:
This led me to the asynchronous library where the server.on() functions make manipulating the HTTP/HTML items easy. My issue is that when I tried to utilize the W5500 with the asynchronous library, it wouldn't open the server. I assume this is related to the issues with W5500 bypassing its internal TCP/IP engine and having to run in MAC RAW mode to function.

QUESTION:
My question is then, does the AWOT library contain functionality that would allow me to use a file system with functionality similar to the asynchronous webserver functions that I could then parse and send elsewhere over serial communication, while still being able to use either Ethernet or WiFi connection--I don't need both simultaneously?

Using ESPAsyncWebServer for better reliability

Hi, I really like your lib, good work there!
Any ideas how to add ESPAsyncWebServer? It should act like normal normal WiFiServer but you dont need to wait in loop() for new requests. Other libraries were easily ported onto that, but I am struggling with this one. Thanks!

Arduino due warning: "pgm_read_byte" redefined

In file included from src/main.cpp:2:0:
lib/aWOT/src/aWOT.h:68:0: warning: "pgm_read_byte" redefined
#define pgm_read_byte(ptr) (unsigned char)(*ptr)

In file included from /home/am/.platformio/packages/framework-arduino-sam/cores/arduino/Arduino.h:31:0,
from src/main.cpp:1:
/home/am/.platformio/packages/framework-arduino-sam/cores/arduino/avr/pgmspace.h:102:0: note: this is the location of the previous definition
#define pgm_read_byte(addr) (*(const unsigned char *)(addr))

(Extremely Niche) Feature Request: Detect Gzipped data in the output buffer

Okay, now this is a strange one and the use case is probably so niche it's not worth worrying about. However, I have permission to share the code, and this might be useful, at least as an application note or example.

Background

A neat trick I have found is to gzip data before storing it, then rely on the browser itself to decode the data on the other end. This works since, as long as the right header is set, all modern browsers handle it. Which is how I am able to serve an entire 150kiB SPA from a microcontroller without any additional storage.

How to generate an embed of any binary file:

gzip --keep <file_name>
xxd -i <file_name>.gz > embeds.h
Use a regex to adjust xxd's output to "const unsigned char <file_name>[] __attribute__((section(\".fin9\"))) = ..."

Code currently used:

bool is_gzip(const uint_farptr_t data, size_t length) {
    if (length < 2) {
        return false;
    }
    byte header[2] = {pgm_read_byte_far( data + 0), pgm_read_byte_far( data + 1)};
    if (header[0] == 0x1F && header[1] == 0x8B) {
        return true;
    }
    return false;
}

if (is_gzip_P(raw_bytes, length)) {
  res.set("Content-Encoding", "gzip");
}
...

Proposal

Adjust Response::setDefaults to add a check if the data to be sent is gzipped or not. If it is, set the appropriate header.

As I said, this is certainly something that does not have to live in the server, but it allows for some crazy things.

Can't get a response from any request sent to aWOT server

I'm just trying to write the simplest ping command.
Using nodeJS and axios to send data to the board:

setInterval(async () => {
    console.log("ping");
    let res = await axios.get('http://192.168.0.150/ping');
    console.log("RES:", res);
}, 3000);

And here is my arduino code (uno wifi rev 2)

#include <aWOT.h>

char ssid[] = "";
char pass[] = "";
int status = WL_IDLE_STATUS;

WiFiServer server(80);
Application app;

void setup() {
  Serial.begin(9600);
  connect_WiFi();
  printWifiStatus();

  app.get("/ping", &ping);
  server.begin();
}

void loop() {
  WiFiClient client = server.available();
  if (client.connected()) {
    app.process(&client);
  }
}

void ping(Request &req, Response &res) {
  Serial.println("pong");
  res.sendStatus(200);
}

void connect_WiFi() {
  // attempt to connect to Wifi network:
  while (status != WL_CONNECTED) {
    // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
    status = WiFi.begin(ssid, pass);
    // wait 5 seconds for connection:
    delay(5000);
  }
}

void printWifiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your board's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

Board connects to wifi, and for each ping sent it logs "pong" to serial out. But in nodeJS no response is ever received.
I've tried everything I can think of

res.status(200);
res.print("sent");

I've tried using res.end(); and res.flush(); in any order, I've tried setting any and all headers with no success.
I've tried sending both get and post using xhr from the browser as well. Ultimately I need this to work with a nodeJS backed but I'm open to any library that isn't axios if that's the issue...
All examples on https://awot.net make it look like it just works. What am I doing wrong?

(Low Priority) Feature Request: support AVR far pointers in `Response::writeP`

Hello,

I was recently evaluating your library to replace our custom Arduino HTTP Server implementation. Of all the libraries examined so far, yours is definitely the most polished, and certainly has better routing capabilities than the nested if statements ours uses. Please do not take my suggestions as thinking your library is "bad."

This is a very low priority issue, but may help others as well.

Background

The AVR platform uses 16 bit pointers. To get around this issue, on larger microcontrollers the memory is paged. When using PROGMEM, or __attribute__((section(".fini7"))), the resulting pointer is only valid within that page of memory! "avr/pgmspace.h" provides a handy pgm_get_far_address macro to convert the pointer to a 32 bit pointer, which solves that issue. However, it requires a different function than the usual pgm_read_byte.

Requested Code

We would be extremely appreciative if you would add an overload of Response::writeP(const uint_farptr_t data, size_t length) that uses pgm_read_byte_far.

If you are interested, I should be able to submit a pull request to resolve this issue.

Feature Request: Namespace the library (with default using)

I realized that while Request and Response are pretty specific terms, Application is certainly somewhat ambiguous, and it is entirely possible to have name collisions causing issues.

This can be a breaking change, but I have a solution to that problem. However, I do not want to make a pull request until we have handled the current ones which add additional functions.

Proposal

Wrap the entire library in namespace awot {...}, and then have at the end:

#ifndef ENABLE_AWOT_NAMESPACE
using namespace awot;
#endif

This allows someone to simply add build_flags = -D ENABLE_AWOT_NAMESPACE=True to their platformio.ini file to enable namespacing.

Request Headers

Hi,

I've been using aWOT in a new project and development is going well. Thanks for your work developing aWOT.

I would like to have access to the Host header value of HTTP requests.

I see that there is the following method to configure the aWOT application to put a header value into a buffer as a request is processed.

void header(const char* name, char* buffer, int bufferLength);

The issue I'm having with this is that as requests come in, they new request's host value clobbers the previous request's host value in the buffer. This works ok in a single threaded environment but it seems like it would be much safer if the aWOT request object would hold the header values for the particular request.

Would it be possible to add something like the following to the aWOT application instead?

void header(const char* name, int bufferLength);

This would tell the aWOT application to dynamically allocate a buffer of the specified length as part of each request object and then store the header value in buffer. The middle wear could then get the header values from the aWOT request object instead of having a separate, unconnected buffer holding the value.

Webserver with https ?

Is there a possibility to operate the awot web server based on https with WiFi and Ethernet ?

Thank you for some advice,
Armin

implementing AWOT in a class

I am getting the following error when trying to implement AWOT in a class

src/WebServerAccessPoint.cpp: In member function 'void WebServerAccessPoint::start_server()':
src/WebServerAccessPoint.cpp:42:36: error: ISO C++ forbids taking the address of an unqualified or parenthesized non-static member function to form a pointer to member function.  Say '&WebServerAccessPoint::serve_html_file' [-fpermissive]
     web_server_app.get("/", &this->serve_html_file);
                                    ^~~~~~~~~~~~~~~
src/WebServerAccessPoint.cpp:42:51: error: no matching function for call to 'awot::Application::get(const char [2], void (WebServerAccessPoint::*)(awot::Request&, awot::Response&))'
     web_server_app.get("/", &this->serve_html_file);
                                                   ^
In file included from include/WebServerAccessPoint.h:8,
                 from src/WebServerAccessPoint.cpp:2:
.pio/libdeps/esp32dev/aWOT/src/aWOT.h:300:8: note: candidate: 'void awot::Application::get(const char*, void (*)(awot::Request&, awot::Response&))'
   void get(const char* path, Router::Middleware* middleware);
        ^~~

the function has return type void, why shouldnt this work ?

aWOT and Server-Side-Events

Hello! Great job with these lib.

Please, could you explain with some example how to implement SSE with aWOT? It is possible?

Thanks in advance and bets regards

A warning...

In file included from .pio\libdeps\nodemcuv2\aWOT\src\aWOT.cpp:23:0:
.pio\libdeps\nodemcuv2\aWOT\src\aWOT.cpp: In member function 'void Response::set(const char*, const char*)':
.pio\libdeps\nodemcuv2\aWOT\src\aWOT.h:72:52: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
 #define SIZE(array) (sizeof(array) / sizeof(*array))
                                                    ^
.pio\libdeps\nodemcuv2\aWOT\src\aWOT.cpp:104:25: note: in expansion of macro 'SIZE'
   if (m_headersCount >= SIZE(m_headers)) {

I hope it can be fixed, thanks!

Member functions as Middleware*

Do somebody ever tried to build a library over this server?
Since "ESPAsyncWiFiManager" is not working over Ethernet, I am trying to build my library for a Webmanager which works over Ethernet, but I can´t figure out how to pass member functions of my EthWebManager class as middlewares.

I am trying with std::bind, here is a code snippet:

void EthWebManager::auth(Request &req, Response &res) {
char * authHeader = req.get("Authorization");

if (!str_iseq(authHeader, encoded_login1.c_str()) && !str_iseq(authHeader, encoded_login2.c_str())) {
res.set("WWW-Authenticate", "Basic realm="Secret Area"");
res.sendStatus(401);
res.end();
DEBUG_WM(F("Unauthenticated"));
}
}

void EthWebManager::handleRoot(Request &req, Response &res)
{
String page = FPSTR(WFM_HTTP_HEAD);
page.replace("{v}", "Home");
page += FPSTR(HTTP_SCRIPT);
page += FPSTR(HTTP_STYLE);
page += FPSTR(HTTP_HEAD_END);
page += F("

EthWebManager

");
page += FPSTR(HTTP_PORTAL_OPTIONS);
page += FPSTR(HTTP_END);
res.set("Content-Type", "text/html");
res.print(page);
}

void EthWebManager::startEthManager()
{
server->begin();
delay(500);
mainapp.header("Authorization", authBuffer, 200);
mainapp.use(std::bind(&EthWebManager::auth, this, std::placeholders::_1, std::placeholders::_2));
auto callable = std::bind(&EthWebManager::handleRoot, this, std::placeholders::_1, std::placeholders::_2);
mainapp.get("/", callable );
}

But I keep getting these errors:

lib\EthWebManager\src\EthWebManager.cpp: In member function 'void EthWebManager::startEthManager()':
lib\EthWebManager\src\EthWebManager.cpp:361:98: error: no matching function for call to 'awot::Application::use(std::_Bind_helper<false, void (EthWebManager::)(awot::Request&, awot::Response&), EthWebManager, const std::_Placeholder<1>&, const std::_Placeholder<2>&>::type)'
mainapp.use(std::bind(&EthWebManager::auth, this, std::placeholders::_1, std::placeholders::_2));
^
In file included from lib\EthWebManager\src\EthWebManager.h:22:0,
from lib\EthWebManager\src\EthWebManager.cpp:14:
.pio\libdeps\m5stack-core-esp32\aWOT\src/aWOT.h:322:8: note: candidate: void awot::Application::use(const char*, awot::Router*)
void use(const char* path, Router* router);
^
.pio\libdeps\m5stack-core-esp32\aWOT\src/aWOT.h:322:8: note: candidate expects 2 arguments, 1 provided
.pio\libdeps\m5stack-core-esp32\aWOT\src/aWOT.h:323:8: note: candidate: void awot::Application::use(awot::Router*)
void use(Router* router);
^
.pio\libdeps\m5stack-core-esp32\aWOT\src/aWOT.h:323:8: note: no known conversion for argument 1 from 'std::_Bind_helper<false, void (EthWebManager::)(awot::Request&, awot::Response&), EthWebManager, const std::_Placeholder<1>&, const std::_Placeholder<2>&>::type {aka std::_Bind<std::_Mem_fn<void (EthWebManager::)(awot::Request&, awot::Response&)>(EthWebManager, std::_Placeholder<1>, std::_Placeholder<2>)>}' to 'awot::Router*'
.pio\libdeps\m5stack-core-esp32\aWOT\src/aWOT.h:324:8: note: candidate: void awot::Application::use(const char*, void ()(awot::Request&, awot::Response&))
void use(const char
path, Router::Middleware* middleware);
^
.pio\libdeps\m5stack-core-esp32\aWOT\src/aWOT.h:324:8: note: candidate expects 2 arguments, 1 provided
.pio\libdeps\m5stack-core-esp32\aWOT\src/aWOT.h:325:8: note: candidate: void awot::Application::use(void ()(awot::Request&, awot::Response&))
void use(Router::Middleware
middleware);
^
.pio\libdeps\m5stack-core-esp32\aWOT\src/aWOT.h:325:8: note: no known conversion for argument 1 from 'std::_Bind_helper<false, void (EthWebManager::)(awot::Request&, awot::Response&), EthWebManager, const std::_Placeholder<1>&, const std::_Placeholder<2>&>::type {aka std::_Bind<std::_Mem_fn<void (EthWebManager::)(awot::Request&, awot::Response&)>(EthWebManager, std::_Placeholder<1>, std::_Placeholder<2>)>}' to 'void ()(awot::Request&, awot::Response&)'
lib\EthWebManager\src\EthWebManager.cpp:363:29: error: no matching function for call to 'awot::Application::get(const char [2], std::_Bind<std::_Mem_fn<void (EthWebManager::
)(awot::Request&, awot::Response&)>(EthWebManager*, std::_Placeholder<1>, std::_Placeholder<2>)>&)'
mainapp.get("/", callable ); /*
^
In file included from lib\EthWebManager\src\EthWebManager.h:22:0,
from lib\EthWebManager\src\EthWebManager.cpp:14:
.pio\libdeps\m5stack-core-esp32\aWOT\src/aWOT.h:300:8: note: candidate: void awot::Application::get(const char*, void ()(awot::Request&, awot::Response&))
void get(const char
path, Router::Middleware* middleware);
^
.pio\libdeps\m5stack-core-esp32\aWOT\src/aWOT.h:300:8: note: no known conversion for argument 2 from 'std::_Bind<std::_Mem_fn<void (EthWebManager::)(awot::Request&, awot::Response&)>(EthWebManager, std::_Placeholder<1>, std::_Placeholder<2>)>' to 'void ()(awot::Request&, awot::Response&)'
.pio\libdeps\m5stack-core-esp32\aWOT\src/aWOT.h:301:8: note: candidate: void awot::Application::get(void (
)(awot::Request&, awot::Response&))
void get(Router::Middleware* middleware);
^
.pio\libdeps\m5stack-core-esp32\aWOT\src/aWOT.h:301:8: note: candidate expects 1 argument, 2 provided
*** [.pio\build\m5stack-core-esp32\lib4c6\EthWebManager\EthWebManager.cpp.o] Error 1

error: 'class Response' has no member named 'success'

Hi, when I try to add the success response, it keep saying that class response has no member named succes.... This piece of code:
void statusCmd(Request &req, Response &res) {
res.success("application/json");
String currentStatus;
..........

Any idea?
Arduino ide for esp8266

Sketch size too High?

Hello,

I tried this library on a Arduino Mega, I used your Ethernet Example, and it increased 15% on my current sketch, is that normal?
That's too much.
Size before Library: 74%
Size after Library; 89%

Multiple query parameters

Hi,

First thanks for sharing your excellent library. I had spent quite a while looking for something like this.

However, it appears that there may be an issue with trying to pass more than one query parameter? or perhaps I am doing something wrong. (that is always very possible... )

Example code

 app.get("/c", &correctionGetRequest);
.
.
.
void correctionGetRequest(Request &req, Response &res) {
  char * correctionValue = req.query("VAL");
  char * chanNumberParam = req.query("ID"); 
  int chanNumber = atoi(chanNumberParam);
  
  res.success("text/html");
  
  res.print("chanNumber: ");
  res.print(chanNumber);	
  res.print("<br/>");
  res.print("correctionValue:");
  res.print(correctionValue);	
  res.print("<br/>");
  

It seems like when you look at the values they both equal value passed for "ID" unless you don't pass that value at all, then the other parameter works, examples :

/c?ID=1&VAL=2.63"
chanNumber: 1<br/>correctionValue:1<br/>

> curl "http://192.168.1.147/c?ID=2&VAL=2.63"
chanNumber: 2<br/>correctionValue:2<br/>

> curl "http://192.168.1.147/c?ID=3&VAL=2.63"
chanNumber: 3<br/>correctionValue:3<br/>

> curl "http://192.168.1.147/c?VAL=2.63"
chanNumber: 0<br/>correctionValue:2.63<br/>

> curl "http://192.168.1.147/c?VAL=2.63&ID=1"
chanNumber: 1<br/>correctionValue:1<br/>

> curl "http://192.168.1.147/c?VAL=2.63&ID=2"
chanNumber: 2<br/>correctionValue:2<br/>

Any suggestions?

Can't get setting value from web

I can't set the parameters to change on the web from your library, can you show me, thank you very much.

#if defined(ESP8266)
#include <ESP8266WiFi.h>
#else
#include <WiFi.h>
#include <Update.h>
#endif
#include <aWOT.h>

#define WIFI_SSID ""
#define WIFI_PASSWORD ""

WiFiServer server(80);
Application app;

char contentType[100] {};
bool shouldRestart = false;

void index(Request &req, Response &res) {
res.set("Content-Type", "text/html");

res.println("");
res.println("");
res.println("

");
res.println("");
res.println("");
res.println("");
res.println("");
res.println("<script>");
res.println(" const form = document.getElementById('form');");
res.println(" form.onsubmit = function(e) {");
res.println(" e.preventDefault();");
res.println(" var data = JSON.stringify({SSID:document.getElementById('form').value,})");
res.println(" var xmlHttp = new XMLHttpRequest();");
res.println(" xmlHttp.onreadystatechange = function() { ");
res.println(" if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {");
res.println(" console.log(xmlHttp.responseText);");
res.println(" }");
res.println(" }");
res.println(" xmlHttp.open('post', '/update', true);");
res.println(" xmlHttp.send(data);");
res.println(" }");
res.println("</script>");
res.println("");
}

void updateup(Request &req, Response &res) {
char * type = req.get("Content-Type");
Serial.println(type);
// this part, What do i have to do to get the value from web, thank you very much .

}

void setup() {
Serial.begin(115200);

WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(WiFi.localIP());

app.header("Content-Type", contentType, 100);
app.get("/", &index);
app.post("/update", &updateup);
server.begin();
}

void loop() {
WiFiClient client = server.available();

if (client.connected()) {
app.process(&client);
client.stop();
}
}
image

Feature Request / Documentation Question: Custom 404 page / Default Route

Hello,

I was recently evaluating your library to replace our custom Arduino HTTP Server implementation. Of all the libraries examined so far, yours is definitely the most polished, and certainly has better routing capabilities than the nested if statements ours uses. Please do not take my suggestions as thinking your library is "bad."

We would like to be able to serve custom fallback pages, and I can not find a method to do so. Does this ability exist? If this is not possible, it would be a much appreciated feature.

These are the three use cases we currently have, and why I believe it is important:

  • A custom 404 page.
  • A generic reply to OPTIONS requests with all the "Access-Control-Allow-..." headers as permissible as possible.
  • (Low priority) A "Method Not Allowed" page for non GET requests that do not match a page that supports them.

Proposed Solution

I believe replacing/appending return m_response.sendStatus(404); in "Application::m_process()" with a check for a custom "Router::Middleware" would easily enable this function.

Compiler error for Ehernet example

Compiler exits in this way:
cannot declare variable 'server' to be of abstract type 'EthernetServer'

Last library release installed, arduino IDE 1.8.12, ESP32 1.0.6 eith ethernet library and w5500 board.

Thanks :)

400 Bad Request

Hi,

Another question for you. On page request randomly it seems, the library returns :
400 Bad Request

If you simply refresh the browser after a short wait it works fine. There seems to be no reason to this that I can tell. It just works sometimes and not others. There is no need to reset MCU or change anything, just wait a while and the requested resource will load.

I have tried increasing RAM buffers in the header file, but that didn't really help.

Do you have any other troubleshooting steps, or ideas what might cause this?

Thanks,
Sokkou

Support for Portenta H7

The compatibility list does not include the Portenta H7. In the source code I could find some conditional logic dependent on the microcontroller. The logic distinguishes between low (LOW_MEMORY_MCU) and high memory MCUs. The Portenta H7 is a high memory MCU means this should not be a problem. The Ethernet part of the Portenta H7 should be handled by the arduino/mbed-os (corresponding PR) and arduino/ArduinoCore-mbed and the Machine Control Board part arduino-libraries/Arduino_MachineControl. Means adding #include <PortentaEthernet.h> to the example sketch should be enough to make the example work.

Nevertheless I was not able to successfully run the patched example sketch with the Portenta Machine Control Board so far. Can you think of something which needs to be patched to get the example working?

aWOT does not compile with Arduino on Ubuntu

Hi all,
the aWOT library does not compile correctly on Ubuntu, because of line 24 in aWOT.cpp:

#include <arduino.h>

I think it has to be <Arduino.h>. But this line is not necessary, because the Arduino header is also included by aWOT.h.

Without line 24 in aWOT.cpp it works very well.

I use:

  • Ubuntu 14.04
  • The latest Arduino IDE from github (version 1.6.6).
  • Latest aWOT library from here

Greets
Kalle

Use with nextJS

There's a guide for using this library with reactJS. NextJS is a wrapper around react and I was wondering if this can be used with nextJS applications.

Websocket server

Hello, i can't find info on this topic...

Is it possibile to use aWOT library as a websocket server?
If yes, can you provider an example please?

Thank you very much again.

Suggestion: Do not allocate a buffer if a closed client is passed to Process

Hello,

I was looking at pull #125 (I added a note there), and noticed a potential optimization. If myApp.process(client) is called with a closed client, the urlBuffer is allocated, then the writeBuffer is allocated, then the check if client is closed is called.
This means that the heap pointer jumps quite a bit in normal operation.

It might be better to just copy and paste this code as first thing in all the versions of Application::process(...)

if (!client) {
    return;
  }

PS: I am changing employers, so am no longer getting paid to ask for nice features / make suggestions. I just like the library and your work on it. It also means I lost access to the $300 Arduino based PLC I was using to test this code with.

Upload files from browser and store them into LittleFS/SPIFFS/SD

Hello,
I found this library very useful for working with ethernet and WiFi at the same time.
I have been able to serve files from SD (using ESP32+ SDcard reader) to the browser using one of your examples, but due to my lack of knowledge about how library works and data flow, I don't understand how to send files from browser to be stored in the SD (or SPIFFS etc..).

I think it can be done in a similar way as OTA update, but I can't get it :(
Is there any example?

Thank you in advance.
/Davide

Feature Request / Bug On Some Platforms: Reduce memory footprint by wrapping all C strings in `F(...)`

Hello,

I was recently evaluating your library to replace our custom Arduino HTTP Server implementation. Of all the libraries examined so far, yours is definitely the most polished, and certainly has better routing capabilities than the nested if statements ours uses. Please do not take my suggestions as thinking your library is "bad."

In the process of evaluating your library, I noticed that it uses 1398 bytes of RAM. I believe this is because, despite importing "Arduino.h", the library uses raw C strings for everything. Those strings are always stored in RAM, just sitting there doing nothing but taking up space.

This limits what devices the library can be run on, and/or the amount of data processing that is available when using this library.

Suggested Fix

#include <WString.h> and then use F(<HttpStatus_reasonPhrase>). An alternate approach for some code is to use this repository, with sleight modifications.

[Teensy 4.1] Requests only working if I clear the read buffer of a client before processing

Using the default Ethernet example on Teensy 4.1 with the NativeEthernet library, aWOT will only correctly respond to requests if I do:

void loop(){
  EthernetClient client = server.available();
  if (client.connected()) {
    while (client.available()) {
      client.read();
    }
    app.process(&client);
    client.stop();
  }
}

If I don't read() all available bytes first, I get an error 400.

Thoughts?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.