Friday, March 1, 2024

Solving the CORS error in MicroPython

For an index to all my stories click this text

I was doing a large project when something unforeseen happened. A javascript program which was accessing a webserver on a Raspberry Pico W got a CORS error. In this story you will find the solution as this might happen to you to.

The project I was doing was made with a Raspberry Pi Pico programmed with MicroPython. The controller acted as a webserver that displayed some simple data on a webpage. So nothing special on this side.
 
The next step was a bit more complicated. I needed to fetch that data and do some calculations on them. For that I build a webpage on my PC with some Javascript code that would fetch the data and do the calculations.

And then the unforeseen happened.
Everything looked like it should but the Javascript code was not able to fetch the code.

Let me start with giving you the MicroPython code. The code is straightforward and just builds a simple webserver.

import socket
import network

# Router credentials
ssid = "MY-ROUTERS-NAME" # wifi router name
pw = "PASSWORD" # wifi router password
print("Connecting to wifi...")

# wifi connection
wifi = network.WLAN(network.STA_IF) # station mode
wifi.active(True)
wifi.connect(ssid, pw)

# wait for connection
while not wifi.isconnected():
    pass

# wifi connected
ip = str(wifi.ifconfig()[0], "\n")
print("Connected. IP: ",str(wifi.ifconfig()[0], "\n"))

def open_socket(ip):
    address = (ip, 80)
    connection = socket.socket()
    connection.bind(address)
    connection.listen(1)
    return connection

def webpage():

    html = ""
    html = html + '<!DOCTYPE html>'
    html = html + '<html>'
    html = html + 'Hi, this is the Pico'
    html = html + '<br>'
    html = html + '============='
    html = html + '</html>'
    return (html)

def serve(connection):
    while True:
        client = connection.accept()[0]
        request = client.recv(1024)
        request = str(request)
        print(request)
        html = webpage()
        client.send(html)
        client.close()
        
connection = open_socket(ip)
serve(connection)

Nothing special or complicated here.
A socket is opened and when the server gets a request the webpage is send.

To test if this functioned I just opened the URL in my webbrowser.



And there is. A simple webserver that sends a webpage that displays 2 lines of text. Of course the complete project would send far more data. But this is just for demonstrating the problem.

If you want to know more about MicroPython on the Raspberry pi pico W please consider buying one of my books on this subject:

You can buy it from Amazon by clicking the picture or this line of text.  

You can also click one of the links at the bottom of this page.

So the webserver worked.

Now let us look at the second webpage. This one was made on my PC and has Javascript code to fetch the data from the Pico's webserver.

<!DOCTYPE html>
<html>
 
<head>
    <title>
        Luc's test page
    </title>
</head>

<h1>here comes the received data</h1>
<div id="testtext"></div>

<script>

repeatrequest = setInterval(getpage, 5000);

function getpage()
{
  fetch(`http://192.168.1.82`).then(function(response) {
  return response.text().then(function(text) {
  receivedtext = text;
    });
   });
  document.getElementById("testtext").innerHTML=receivedtext;
}

</script>
 
</html>


Again nothing exciting here. A simple webpage with a fetch command that adresses the IP address of the Pico and gets the data from Pico's webpage.

It looks like this:



There was only just one small problem. The page says: here comes the received data. But nothing came.



However Thonny's shell was telling me that it received a request from a windows machine running the Firefox browser. So The Pico received a request from my Javascript code. And still the webpage on my computer did not display any information.

I checked my code, and rechecked my code and rechecked my code. But could not find any error. Then I opened the webdevelopers console in my browser and there it was.



The dreaded CORS error.

The CORS error name is an abbreviation for Cross-Origin Resource Sharing.

I herebye cite Wikipedia:

Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources on a web page to be accessed from another domain outside the domain from which the first resource was served.

Now that explains it all, doesn't it.
Well I think for most of the readers of this weblog this is all mumbo jumbo.

No worries.

Basically CORS is a safety measurement that makes sure that a computer in your local network can not access data from another computer in your local network without explicit permission. So if a hacker gets hold of a computer in your local network CORS is a way to prevent him/her getting access to other computers in the same network.
In this case it affects my computer trying to get information from my Pico.

The important part is how to get rid of it.

To get rid of the error we need to have the Pico's webserver give explicit access to it's information if another computer in our own network asks for it.
That information must be send in the header of the webpage.

We can do that by adding the following lines to our Pico webserver:

        client.send('HTTP/1.1 200 OK\n')
        client.send('Access-Control-Allow-Origin: *\n')
        client.send('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS\n')
        client.send('Access-Control-Allow-Headers: Content-Type\n')
        client.send('Content-Type: text/html\n')
        client.send('\n')

For clarity I herbye give the complete MicroPython webserver code:

import socket
import network

# Router credentials
ssid = "MY-ROUTERS-NAME" # wifi router name
pw = "PASSWORD" # wifi router password
print("Connecting to wifi...")

# wifi connection
wifi = network.WLAN(network.STA_IF) # station mode
wifi.active(True)
wifi.connect(ssid, pw)

# wait for connection
while not wifi.isconnected():
    pass

# wifi connected
ip = str(wifi.ifconfig()[0], "\n")
print("Connected. IP: ",str(wifi.ifconfig()[0], "\n"))

def open_socket(ip):
    address = (ip, 80)
    connection = socket.socket()
    connection.bind(address)
    connection.listen(1)
    return connection

def webpage():
    html = ""
    html = html + '<!DOCTYPE html>'
    html = html + '<html>'
    html = html + 'Hi, this is the Pico'
    html = html + '<br>'
    html = html + '============='
    html = html + '</html>'
    return (html)

def serve(connection):
    while True:
        client = connection.accept()[0]
        request = client.recv(1024)
        request = str(request)
        print(request)
        html = webpage()
        
        client.send('HTTP/1.1 200 OK\n')
        client.send('Access-Control-Allow-Origin: *\n')
        client.send('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS\n')
        client.send('Access-Control-Allow-Headers: Content-Type\n')
        client.send('Content-Type: text/html\n')
        client.send('\n')
        
        client.send(html)
        client.close()
        
connection = open_socket(ip)
serve(connection)


The new program lines are added before

        client.send(html)
        client.close()


so the headers are send before the webpage is send.
This gives a signal to Javascript that the webpage can safely receive data from the Pico.



And this is how the result looks.

Problem solved. 


Till next time
have fun

Luc Volders