How to create a REST server on esp8266 or esp32: CORS request, OPTION and GET – Part 4

Spread the love

Now, we have made calls from the Postman program, but doing the same from a web browser is not the same thing.

REST server on esp8266 and esp32 CORS request OPTION and GET
REST server on esp8266 and esp32 CORS request OPTION and GET

There are some security policy that we can’t ignored. The most important/annoying one is that if you want do a REST call to a server from a client, with different domain (or orign if you prefer) you enter in the tunnel of CORS.

Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell browsers to give a web application running at one origin, access to selected resources from a different origin. A web application executes a cross-origin HTTP request when it requests a resource that has a different origin (domain, protocol, or port) from its own. (Cit.)

But what mean?

If you take this html/js source

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Do get request</title>
</head>
<body>
    <div>Normal response</div>
    <div id="content"></div>
    <div>Error response</div>
    <div id="errorContent"></div>
</body>
<script>
    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            // Typical action to be performed when the document is ready:
            document.getElementById("content").innerHTML = xhttp.responseText;
        }
    };
    xhttp.onerror = function () {
        document.getElementById("errorContent").innerHTML = "Status code is " + this.status + " click F12 and check what is the problem on console";
    };

    xhttp.open("GET", "http://esp8266/settings", true);
    xhttp.send();

</script>
</html>

and you can try to execute this simple GET

    xhttp.open("GET", "http://esp8266/settings", true);

Your browser notify you in the console (F12) that you have a

Access to XMLHttpRequest at 'http://esp8266/settings' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

a CORS error, this because you try to call a REST end-point from a different origin.

Manage CORS GET

REST server on esp8266 and esp32 the tunnel of CORS
REST server on esp8266 and esp32 the tunnel of CORS

Your first try is to add the header to specify that server must accept all origin and verb, so you can add after the open and before the send this line

    xhttp.setRequestHeader('Access-Control-Allow-Headers', '*');
    xhttp.setRequestHeader('Access-Control-Allow-Origin', '*');

But now we have a new error

OPTIONS http://esp8266/settings 404 (Not Found)

Access to XMLHttpRequest at 'http://esp8266/settings' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Here you can check that the client not try to call GET request but an OPTION, and It don’t find that end point.

And the error specify that “Response to preflight request doesn't pass access control check“, and this is correlated with previous 404 on OPTION.

In the CORS call, a preflight request with the OPTIONS verb is sent, so that the server can respond whether it is acceptable to send the request with these parameters.

And now we are going to add OPTION end point to the REST server.

And we are going to add this on esp8266 sketch

void sendCrossOriginHeader(){
	server.send(204);
}

// Define routing
void restServerRouting() {
    server.on("/", HTTP_GET, []() {
    	server.send(200, F("text/html"),
            F("Welcome to the REST Web Server"));
    });
    server.on(F("/helloWorld"), HTTP_GET, getHelloWord);
    server.on(F("/settings"), HTTP_OPTIONS, sendCrossOriginHeader);
    server.on(F("/settings"), HTTP_GET, getSettings);
}

But we obtain this error again

Access to XMLHttpRequest at 'http://esp8266/settings' from origin 'http://localhost:63342' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

The problem is that the OPTION, even if, response in 204 (all 2xx are successful) It doesn’t return the correct header, so we are going to add the header on the response of the GET.

	httpRestServer.sendHeader(F("Access-Control-Allow-Origin"), F("*"));
	httpRestServer.sendHeader(F("Access-Control-Max-Age"), F("600"));
	httpRestServer.sendHeader(F("Access-Control-Allow-Methods"), F("PUT,POST,GET,OPTIONS"));
	httpRestServer.sendHeader(F("Access-Control-Allow-Headers"), F("*"));

And now finally

Normal response
{"ip":"192.168.1.129","gw":"192.168.1.1","nm":"255.255.255.0"}
Error response

Ok, we have out CORS get request.

For esp32 you must only change this includes

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

to

#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>

and chip information like so

      doc["chipRevision"] = ESP.getChipRevision();
      doc["flashChipMode"] = ESP.getFlashChipMode();
      doc["flashChipSize"] = ESP.getFlashChipSize();
      doc["flashChipSpeed"] = ESP.getFlashChipSpeed();

Here the complete sketch

/*
 *  Json parametric GET REST response with ArduinoJSON library
 *  by Mischianti Renzo <https://mischianti.org>
 *
 *  https://mischianti.org/
 *
 */

#include "Arduino.h"
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <ArduinoJson.h>

const char* ssid = "<YOUR-SSID>";
const char* password = "<YOUR-PASSWD>";

ESP8266WebServer server(80);

void setCrossOrigin(){
	server.sendHeader(F("Access-Control-Allow-Origin"), F("*"));
	server.sendHeader(F("Access-Control-Max-Age"), F("600"));
	server.sendHeader(F("Access-Control-Allow-Methods"), F("PUT,POST,GET,OPTIONS"));
	server.sendHeader(F("Access-Control-Allow-Headers"), F("*"));
};


// Serving Hello world
void getHelloWord() {
	  DynamicJsonDocument doc(512);
	  doc["name"] = "Hello world";

	  Serial.print(F("Stream..."));
	  String buf;
	  serializeJson(doc, buf);
	  server.send(200, "application/json", buf);
	  Serial.print(F("done."));
}

// Serving Hello world
void getSettings() {
	setCrossOrigin();
//
	  // Allocate a temporary JsonDocument
	  // Don't forget to change the capacity to match your requirements.
	  // Use arduinojson.org/v6/assistant to compute the capacity.
	//  StaticJsonDocument<512> doc;
	  // You can use DynamicJsonDocument as well
	  DynamicJsonDocument doc(512);

	  doc["ip"] = WiFi.localIP().toString();
	  doc["gw"] = WiFi.gatewayIP().toString();
	  doc["nm"] = WiFi.subnetMask().toString();

	  if (server.arg("signalStrength")== "true"){
		  doc["signalStrengh"] = WiFi.RSSI();
	  }

	  if (server.arg("chipInfo")== "true"){
		  doc["chipId"] = ESP.getChipId();
		  doc["flashChipId"] = ESP.getFlashChipId();
		  doc["flashChipSize"] = ESP.getFlashChipSize();
		  doc["flashChipRealSize"] = ESP.getFlashChipRealSize();
	  }
	  if (server.arg("freeHeap")== "true"){
		  doc["freeHeap"] = ESP.getFreeHeap();
	  }

	  Serial.print(F("Stream..."));
	  String buf;
	  serializeJson(doc, buf);
	  server.send(200, F("application/json"), buf);
	  Serial.print(F("done."));
}

void sendCrossOriginHeader(){
	Serial.println(F("sendCORSHeader"));
    setCrossOrigin();
	server.send(204);
}

// Define routing
void restServerRouting() {
    server.on("/", HTTP_GET, []() {
    	server.send(200, F("text/html"),
            F("Welcome to the REST Web Server"));
    });
    server.on(F("/helloWorld"), HTTP_GET, getHelloWord);
    server.on(F("/settings"), HTTP_OPTIONS, sendCrossOriginHeader);
    server.on(F("/settings"), HTTP_GET, getSettings);

}

// Manage not found URL
void handleNotFound() {
  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);
}

void setup(void) {
  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.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  // Activate mDNS this is used to be able to connect to the server
  // with local DNS hostmane esp8266.local
  if (MDNS.begin("esp8266")) {
    Serial.println("MDNS responder started");
  }

  // Set server routing
  restServerRouting();
  // Set not found response
  server.onNotFound(handleNotFound);
  // Start server
  server.begin();
  Serial.println("HTTP server started");
}

void loop(void) {
  server.handleClient();
}

Thanks

  1. REST server on esp8266 and esp32: introduction
  2. REST server on esp8266 and esp32: GET and JSON formatter
  3. REST server on esp8266 and esp32: POST, PUT, PATCH, DELETE
  4. REST server on esp8266 and esp32: CORS request, OPTION and GET
  5. REST server on esp8266 and esp32: CORS request, OPTION and POST

Spread the love

4 Responses

  1. Terry Higbee says:

    This is a FABULOUS tutorial series, but there are several problems with the code that will prevent simply cut/paste of the code presented into your IDE. The solutions are hidden in the video, but the author pans past the listings so fast that you spend hours trying to stop the video and find these little nuggets.

    There are two in particular:
    1. in the html file he provides, the test on about line 17 should be for status code 201, not 200.
    2. two keys lines appear to have been left out of ‘sendCrossOriginHeader’, namely:
    server.sendHeader(F(“access-control-allow-credentials”),F(“false”)); //left out of author’s original code
    setCrossOrigin(); //left out of author’s original v4

  2. Hi Terry,
    probably there are some differences in the code of the video.
    But in the HTML, I test 200 because, in the code of settings, I put 200, not 201.
    If you want, you can set 201 in the HTML and in the GET request, so the behavior does not change.

    The access-control-allow-credentials can be used in the auth test, and it probably remains in the video code.

    thanks for your clarification, Bye Renzo

  3. max van der Helm says:

    I couldn’t get the code to work and kept running into the CORS problem. After I add a call to setCrossOrigin() from the function “sendCrossOriginheader” the code started working.
    So I have add on row 79 of the ESP code: “setCrossOrigin();”

Leave a Reply

Your email address will not be published. Required fields are marked *