micropython-sockets-servidor-web-cliente

Sockets in MicroPython: Basic Web Server and Client

  • 5 min

A socket is an endpoint of communication that allows sending and receiving bytes over the network.

We have already connected our board to Wi-Fi. We have an IP, we have internet. So what now?

To send or receive data, we need a digital “pipe” connecting our microcontroller to another device (a cloud server, your phone, another ESP32). This pipe is called a Socket.

Sockets are the fundamental API of networks. Whether you are watching Netflix, loading this website, or sending a WhatsApp, underneath it all, sockets are sending bytes from one side to the other.

In this post, we are going to get down to the nitty-gritty to understand how they work by configuring our board in two modes:

  1. Server: Waiting for connections (e.g., to turn on an LED from the browser).
  2. Client: Initiating connections (e.g., to download the time or weather).

The socket module

MicroPython (like standard Python) uses the socket module. The logic is always:

  1. Create the socket.
  2. Connect (or wait for a connection).
  3. Send/Receive data (send/recv).
  4. Close the socket (Very important!).

MicroPython as a Web Server

This is the “Hello World” of IoT. We want to write our ESP32’s IP address into our mobile phone’s Chrome/Firefox browser and see a webpage that allows us to turn an LED on and off.

For this, our board must be “listening” on a specific port (usually 80 for web).

Server Steps

  1. Bind: Reserve an IP address and a port.
  2. Listen: Put your “ear to the ground,” waiting for calls.
  3. Accept: When someone calls, pick up. This creates a new socket exclusive to that client.

Web-Based LED Control

import network
import socket
import machine
import time

# 0. Previous configuration (Wi-Fi Connection and LED)
# Assume you have already connected to Wi-Fi using the code from the previous chapter
led = machine.Pin(2, machine.Pin.OUT)

# 1. Create the Socket
# AF_INET = IPv4, SOCK_STREAM = TCP (Reliable Protocol)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2. Bind
# '' means listen on all available network interfaces
# 80 is the standard HTTP port
try:
    s.bind(('', 80))
except OSError as e:
    # If the port was busy (soft reset), it sometimes fails.
    print("Port was busy, retrying...")

# 3. Listen
# The parameter 5 indicates how many connections we keep on hold if we are busy
s.listen(5)
print("Web Server listening on port 80...")

while True:
    try:
        # 4. Accept (Accept connection)
        # Blocks here until someone connects
        conn, addr = s.accept()
        print('Connection received from:', addr)
        
        # 5. Receive the Request
        request = conn.recv(1024) # Read up to 1024 bytes
        request_str = request.decode('utf-8')
        print("Request:", request_str)
        
        # Analyze what is being requested (Very basic logic)
        if '/on' in request_str:
            led.value(1)
            estado = "ON"
        elif '/off' in request_str:
            led.value(0)
            estado = "OFF"
        else:
            estado = "UNKNOWN"

        # 6. Send Response
        # Need to send valid HTTP headers first
        response = """HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n
        <!DOCTYPE html>
        <html>
        <head><title>ESP32 Web</title></head>
        <body>
            <h1>LED Control</h1>
            <p>Status: <strong>""" + estado + """</strong></p>
            <p><a href="/on"><button>TURN ON</button></a></p>
            <p><a href="/off"><button>TURN OFF</button></a></p>
        </body>
        </html>
        """
        conn.send(response.encode('utf-8'))
        
    except Exception as e:
        print("Connection error:", e)
        
    finally:
        # 7. Close the connection WITH THE CLIENT
        # Important to close 'conn', not 's' (the server keeps listening)
        conn.close()
Copied!

If you load this code, you will see the IP in the console. Type it into your browser, and there you have it: a control panel.

Limitations This server is blocking and single-threaded. If you try to handle many requests at once or the code gets stuck processing something, the webpage will not load. For more serious servers, we will use asyncio or libraries like Microdot later on.

MicroPython as a Client

Now the other way around. We want our board to connect to an internet server (like Google, a weather API, or your own server) to download data.

Client Steps

  1. Connect: Call a remote IP and port.
  2. Send: Send the request (e.g., “GET / HTTP/1.0”).
  3. Recv: Read the response.

Downloading Text from the Internet

We will make a manual HTTP request to see the inner workings of the protocol.

import socket

# Destination configuration
host = "www.example.com"
port = 80
path = "/"

# 1. Get the domain's IP address (DNS Lookup)
addr_info = socket.getaddrinfo(host, port)
addr = addr_info[0][-1]
print("Connecting to:", addr)

# 2. Create socket and connect
s = socket.socket()
s.connect(addr)

# 3. Send HTTP GET request
# NOTE: The \r\n\r\n line breaks are mandatory in HTTP
request = f"GET {path} HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n"
s.send(request.encode('utf-8'))

# 4. Receive response
print("--- Server Response ---")
while True:
    data = s.recv(1024) # Read in 1KB chunks
    if not data:
        break # If no data, the server closed the connection
    print(data.decode('utf-8'), end='')

# 5. Close
s.close()
Copied!

When you run it, you will see the raw HTML code of the example.com page in the console, including the HTTP/1.1 200 OK, Content-Type headers, etc.

requests: A Simpler Interface

Writing HTTP requests manually with sockets (putting in the \r\n and managing headers) is a headache and error-prone.

To act as a client in daily use, we can use the requests library, available in micropython-lib, which does all this for us. In older firmware, you might still find it under the name urequests.

import requests

response = requests.get("http://www.example.com")
print(response.text)
response.close()
Copied!

Much easier, right? But requests is built on top of the sockets we just learned about.

Using “raw” sockets is useful when we need maximum speed, custom non-HTTP protocols, or when we want to reduce memory consumption to the absolute minimum.

With this, we now know how to move information in and out of the chip. We now have the foundation to connect with real services like databases or MQTT brokers.