Simple HTTP Server for CircuitPython

Overview

Introduction

Documentation Status Discord Build Status Code Style: Black

Simple HTTP Server for CircuitPython

Dependencies

This driver depends on:

Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading the Adafruit library and driver bundle or individual libraries can be installed using circup.

Installing from PyPI

On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally from PyPI. To install for current user:

pip3 install adafruit-circuitpython-httpserver

To install system-wide (this may be required in some cases):

sudo pip3 install adafruit-circuitpython-httpserver

To install in a virtual environment in your current project:

mkdir project-name && cd project-name
python3 -m venv .env
source .env/bin/activate
pip3 install adafruit-circuitpython-httpserver

Installing to a Connected CircuitPython Device with Circup

Make sure that you have circup installed in your Python environment. Install it with the following command if necessary:

pip3 install circup

With circup installed and your CircuitPython device connected use the following command to install:

circup install httpserver

Or the following command to update an existing version:

circup update

Contributing

Contributions are welcome! Please read our Code of Conduct before contributing to help this project stay welcoming.

Documentation

API documentation for this library can be found on Read the Docs.

For information on building library documentation, please check out this guide.

Comments
  • Case insensitive HTTPHeaders, HTTPResponse context manager and some fixes

    Case insensitive HTTPHeaders, HTTPResponse context manager and some fixes

    Added separate class for HTTP headers, that allows accessing headers using case-insensitive names.

    Also, changed some docstrings the were unclear and one that was misleading as it was inconsistent with the code.

    Changed the order of Docs References, so that the most "important" ones are on the top, with enums and dataclass-like classes below.

    Updated library version to 1.1.0 in sphinx configuration file.

    Fixes #26 Fixes #28

    opened by michalpokusa 16
  • Refactor into separate files, additional features, addition of missing typing

    Refactor into separate files, additional features, addition of missing typing

    This PR, other that separating adafruit_httpserver into multiple files #8 adds following functionality:

    • Access to additional attributes of HTTPRequest:
      • http_version extracted from Start line, e.g. "HTTP/1.1"
      • query_params extracted from path, e.g. /path?foo=bar is parsed to {"foo": "bar"}
      • headers as a dict, e.g. {'user-agent': '...', 'host': 'esp32-s2-tft.local', 'connection': 'close'}
      • body as bytes, so both text content and images can be processed #16
    • Added new parameter to HTTPResponse which allows to set it's headers #24

    Other than that, this PR also includes:

    • Refactor or addition of "enums" like HTTPMethod and MIMEType
    • ~~Removed blocking socket~~ Implemented socket_timeout property, which fixes #21
    • Changed the way server receives data, which fixes #2
    • Smaller refactor here and there
    • More typing in functions/classes etc. #6

    Despite major refactor, the usage of library stays nearly unchanged, so it won't be neccessary to rewrite existing projects from scratch, only minor changes might be required. It surely is not perfect, but I belive it is a step in the right direction.

    Closes #2 Closes #6 Closes #8 Closes #16 Closes #17 Closes #21 Closes #24

    opened by michalpokusa 10
  • CircuitPython clients can't complete requests to HTTPServer

    CircuitPython clients can't complete requests to HTTPServer

    Trying to use adafruit_requests to connect either the 5100S-based WIZnet Pico EVB running Adafruit CircuitPython 7.2.5 on 2022-04-06; Raspberry Pi Pico with rp2040 or the Adafruit Feather RP2040 with Adafruit Ethernet FeatherWing running Adafruit CircuitPython 7.2.5 on 2022-04-06; Adafruit Feather RP2040 with rp2040 to an Adafruit Feather ESP32-S2 TFT running HTTPServer on an IPv4 (this repo simpletest example; no mDNS/hostname/FQDN involved) results in one of the following exception traces in all cases:

    Either (less often):

    Traceback (most recent call last):
      File "code.py", line 211, in <module>
      File "adafruit_requests.py", line 815, in get
      File "adafruit_requests.py", line 685, in request
    OutOfRetries: Repeated socket failures
    

    Or (more often):

    Traceback (most recent call last):
      File "code.py", line 211, in <module>
      File "adafruit_requests.py", line 815, in get
      File "adafruit_requests.py", line 661, in request
      File "adafruit_requests.py", line 529, in _get_socket
      File "adafruit_wiznet5k/adafruit_wiznet5k_socket.py", line 251, in connect
      File "adafruit_wiznet5k/adafruit_wiznet5k.py", line 574, in socket_connect
    RuntimeError: Failed to establish connection.
    

    I was initially going to file this issue in WIZnet, but a sanity check of trying to connect to the HTTPServer from ESP32-S2 (e.g., Adafruit Feather ESP32-S2 TFT) also gets an exception every time, after about a minute. That surprised me, I may be doing something wrong. Maybe it's a Requests issue.

    ESP32-S2 Client Code:

    import traceback
    import wifi
    import socketpool
    import ssl
    import adafruit_requests
    from adafruit_httpserver import HTTPServer, HTTPResponse
    from secrets import secrets
    
    wifi.radio.connect(secrets['ssid'], secrets['password'])
    pool = socketpool.SocketPool(wifi.radio)
    requests = adafruit_requests.Session(pool, ssl.create_default_context())
    
    URLS = [
        "http://wifitest.adafruit.com/testwifi/index.html",
        "http://192.168.5.32",   # LAN Apache server
        "http://192.168.6.164",  # LAN ESP32-S2 with adafruit_httpserver
    ]
    
    for url in URLS:
        try:
            print(url)
            with requests.get(url) as response:
                print(response.status_code, response.reason)
        except Exception as ex:
            traceback.print_exception(ex, ex, ex.__traceback__)
    

    Output:

    code.py output:
    http://wifitest.adafruit.com/testwifi/index.html
    200 bytearray(b'OK')
    http://192.168.5.32
    200 bytearray(b'OK')
    http://192.168.6.164
    Traceback (most recent call last):
      File "code.py", line 22, in <module>
      File "adafruit_requests.py", line 720, in get
      File "adafruit_requests.py", line 661, in request
      File "adafruit_requests.py", line 512, in _get_socket
    RuntimeError: Sending request failed
    

    Both Espressif client and Espressif server are running: Adafruit CircuitPython 7.2.5 on 2022-04-06; Adafruit Feather ESP32-S2 TFT with ESP32S2

    Connecting to the HTTPServer from a browser or curl works fine.

    Connecting to local Apache server at an IPv4 from any of these clients works fine.

    opened by anecdata 10
  • Added Features.

    Added Features.

    Yes, I've seen issue #17 and others. But I'm not the one to do major rewrites from scratch, and I liked how this server was small, and could both server files and cgi-ish content. The files being served are mainly css and js and some small graphics. These also benefit from having the ability to have cache control. The forms and table based content became to large to send as a single string body so I added the ability to send data with chunked transfer encoding.

    I looked at ampule, but TBH, I had already figured this out here, and I didn't need the extra dependencies either. This is a collection of a few added capabilities. All of the additions should not break current api implementations, just extends the capabilities. Feel free to accept, make comments or just throw away.

    This also solves the issue #21 that I was having.

    opened by paul-1 8
  • Allow user to specify request buffer size.

    Allow user to specify request buffer size.

    The next limit we ran into was and 1kb buffer for reading the request. Not knowing how much memory different devices have, it seemed best to expose the buffer size rather than just increase it.

    Co-authored-by: Shae Erisson [email protected]

    opened by cthulahoops 7
  • Explicitly set accepted socket to blocking

    Explicitly set accepted socket to blocking

    The Python documentation states that otherwise, the blocking status of the newly accepted socket is implementation-defined:

    if the listening socket is in non-blocking mode, whether the socket returned by accept() is in blocking or non-blocking mode is operating system-dependent. If you want to ensure cross-platform behaviour, it is recommended you manually override this setting. https://docs.python.org/3/library/socket.html#socket-timeouts

    When the connected socket is non-blocking (as it is on picow), the http library works erratically, depending whether the request has already arrived by the time recvfrom_into call occurs.

    Closes: adafruit/circuitpython#7086

    opened by jepler 6
  • Content-Length and multibyte characters in HTTPResponse

    Content-Length and multibyte characters in HTTPResponse

    I am building a tiny web server using adafruit_httpserver.server and circuitpython 8.0.0-beta.5 on ESP32-S2. When multibyte characters (Japanese) are included in response body, the content-length header value received by a web client looks shorter, and some tailing characters in the body are missing.

    page_html = """
    <html>
      <head><title>test</title></head>
      <body>
       some text in multibyte language here..
      </body>
    </html>
    """
    
    @server.route("/test")
    def base(request):
      return HTTPResponse(content_type='text/html;charset=UTF-8',body=page_html)
    
    $ curl -v http://192.168.xx.xx/test
     :
    < HTTP/1.1 200 OK
    < Content-Length: 117
    < Content-Type: text/html;charset=UTF-8
    < Connection: close
    <
    * Excess found in a read: excess = 10, size = 117, maxdownload = 117, bytecount = 0
    (html response are shown, but the last several characters in body are missing)
    

    Looking into adafruit_httpserver/response.py, content-length is calculated as len(body), as in, response_headers.setdefault("Content-Length", content_length or len(body)) and the whole response are sent after converted into bytes. If I replace it with len(body.encode("utf-8")), the above trouble disappears, but I'm not sure this modification is right.

    response.py#L78

    opened by jun2sak 4
  • Ensure the headers are not modified in HTTPResponse

    Ensure the headers are not modified in HTTPResponse

    Fix for issue #26 which was caused by the passed in headers dict getting modified for the response. Then the calling program reused the headers in subsequent calls. Thanks to @dhalbert and @anecdata for assistance on Discord.

    opened by spovlot 4
  • Documentation: `status` in response cannot be a tuple

    Documentation: `status` in response cannot be a tuple

    Hello,

    I started using this library today and followed the documentation describing how to set the status code when responding to a request. The triple-quoted docstring for HTTPResponse.__init__ describes status as:

        :param tuple status: The HTTP status code to return, as a tuple of (int, "message").
         Common statuses are available in `HTTPStatus`.
    

    while the type hint for the constructor argument defines it as status: tuple = HTTPStatus.OK. The name tuple is recognized by the documentation generator, which creates a link to the Python.org documentation for the tuple type.

    Passing a tuple does not actually work here, and causes the response to be badly formatted. The value given to status is saved in self.status, then passed to self._send_response, which in turn calls self._send_bytes with the value produced by self._HEADERS_FORMAT.format: https://github.com/adafruit/Adafruit_CircuitPython_HTTPServer/blob/67c3ac3b76dd419a90558460b727a07b2169351f/adafruit_httpserver.py#L232-L236

    This format method is str.format, since self._HEADERS_FORMAT is just a string: https://github.com/adafruit/Adafruit_CircuitPython_HTTPServer/blob/67c3ac3b76dd419a90558460b727a07b2169351f/adafruit_httpserver.py#L179-L185

    Passing a tuple as documented – e.g. (200, 'OK') causes it to be rendered at the position of the first {} in the format string, making the response look like this:

    HTTP/1.1 (200, 'OK')
    Content-Type: text/plain
    Content-Length: 5
    Connection: close
    
    hello
    

    This is not a valid HTTP response, so when curl receives it it fails with

    * Unsupported HTTP version in response
    * Closing connection 0
    

    The docs do suggest to use the common values pre-defined as static fields in HTTPStatus, which aren't tuples but HTTPStatus instances. They are only provided for status code 200, 404, and 500, so it's likely that users would want to provide other values and try to use tuples like (403, 'Forbidden') for example. HTTPStatus instances are rendered correctly in the format string because the class specifically defines its string representation with a __str__ method: https://github.com/adafruit/Adafruit_CircuitPython_HTTPServer/blob/67c3ac3b76dd419a90558460b727a07b2169351f/adafruit_httpserver.py#L49-L50

    I'm not sure whether this should be best handled as purely a documentation change – no longer suggesting tuple – or as a code change, actually supporting tuple and rendering it correctly, maybe even raising an exception if a tuple is provided that is not made of an int and a string. I'm far from an authority on the matter, but it seems more Pythonic to me to actually support tuples rather than require an HTTPStatus object or a protocol-formatted string to directly inject into the response.

    Thanks for this library!

    opened by nicolasff 4
  • Change request handling to use split instead of regular expressions.

    Change request handling to use split instead of regular expressions.

    The regular expression fails with a stack overflow for paths of more than 135 characters. The split also appears to be much faster.

    Failing parse:

    >>> import re
    >>> _REQUEST_RE = re.compile(r"(\S+)\s+(\S+)\s")
    >>> _REQUEST_RE.match("GET /" + ("a" * 135) + " whatev")
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    RuntimeError: maximum recursion depth exceeded
    

    Co-authored-by: Shae Erisson [email protected]

    opened by cthulahoops 4
  • Adding cors headers messes up content-type and content-length

    Adding cors headers messes up content-type and content-length

    Given the following object:

    headers = {
        "Access-Control-Allow-Headers": "*",
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "GET,POST,DELETE"
    }
    

    And the following server function:

    @server.route("/api/v1/info", HTTPMethod.GET)
    def info(request):
        """Return info"""
        try:
            obj = {"version": Api.api_version, "name": Api.api_name}
            response = json.dumps(obj)
            return HTTPResponse(status=CommonHTTPStatus.OK_200, body=response, content_type=MIMEType.TYPE_JSON, headers=Api.headers)
        except:
            return HTTPResponse(status=CommonHTTPStatus.BAD_REQUEST_400, headers=Api.headers)
    

    The content-type gets turned to octet-stream and the content-length gets set to 1024.

    doing this in the response.py code in the _construct_response_bytes function works fine:

    headers.setdefault("Access-Control-Allow-Headers", "*")
    headers.setdefault("Access-Control-Allow-Origin", "*")
    headers.setdefault("Access-Control-Allow-Methods", "*")
    

    this is a temporary solution, but not very preferred. I have looked around in the code and I have no idea what causes this issue

    opened by mandar1jn 3
  • HTTP Requests from Chrome block the server

    HTTP Requests from Chrome block the server

    There seems to be a problem handling requests from Chrome. Chrome locks the http server for a minute, blocking any other requests from being handled - other than requests from the same Chrome tab. A minute after the last request from Chrome, the server will start handling other requests again.

    If you start a server, it will handle requests correctly from clients like node-red, firefox etc. (anything other than Chrome!). If you are looping with server.poll() the loop will run correctly, but will freeze for 60 seconds after the Chrome request. An example from an ESP32-S3 I am using:

    pool = socketpool.SocketPool(wifi.radio)
    server = HTTPServer(pool)
    server.request_buffer_size = 2048
    server.socket_timeout = 1
    server.start(str(wifi.radio.ipv4_address))
    
    @server.route("/")
    def base(request: HTTPRequest):
        with HTTPResponse(request, content_type=MIMEType.TYPE_TXT, chunked=False) as response:
            try:
                print("REQUEST: /")
                response.send("HELLO")
            except OSError:
                response.send("ERROR")
    
    while True:
        try:
            led.value = True
            time.sleep(0.05)
            led.value = False
            time.sleep(0.1)
            server.poll()
            
        except OSError as error:
            print(error)
            continue
    

    As soon as you hit the server with Chrome, the server responds but stops the loop and stops handling requests from other clients. If you make another request from chrome in the same tab, it will be handled, and seemingly at that time pending requests from other clients can sometimes be handled but the loop will freeze again right after.

    Making a request to an undefined path from Chrome freezes the loop also - you get the 404 in chrome and then server loop freezes - whatever code you put in your handler does not fix this problem.

    A minute after the last Chrome request, the loop restarts and the server functions normally.

    If you quit Chrome while the loop is locked, the loop restarts immediately.

    I've tried a bunch of things to fix this, including closing the connection in the handler, sending various keep-alive responses to drop the connection - none work.

    I think this needs an SDK level fix!

    opened by yousafs 10
Releases(2.0.0)
Owner
Adafruit Industries
Adafruit Industries
IP-Escaner - A Python Tool to obtain information from an IP address

IP-Escaner Herramienta para obtener informacion de una direccion IP Opciones de

4 Apr 09, 2022
An curated collection of awesome resources about networking in cybersecurity

An ongoing curated collection of awesome software, libraries, frameworks, talks & videos, best practices, learning tutorials and important practical resources about networking in cybersecurity

Paul Veillard, P. Eng 7 Nov 30, 2022
A tool which is capable of scanning ports as TCP & UDP and detecting open and closed ones.

PortScanner Scan All Open Ports Of The Target IP. A tool which is capable of scanning ports as TCP & UDP and detecting open and closed ones. Clone fro

Msf Nmt 17 Nov 26, 2022
A web-based app that allows easy, simple - and if desired high-throughput - analysis of qPCR data

qpcr-Analyser A web-based GUI for the qpcr package that allows easy, simple and high-throughput analysis of qPCR data. As is described in more detail

1 Sep 13, 2022
IPE is a simple tool for analyzing IP addresses. With IPE you can find out the server region, city, country, longitude and latitude and much more in seconds.

IPE is a simple tool for analyzing IP addresses. With IPE you can find out the server region, city, country, longitude and latitude and much more in seconds.

Paul 0 Jun 11, 2022
The AKS cluster provisioner provisions AKS clusters :-)

Overview The AKS cluster provisioner provisions AKS clusters :-) It uses the Azure CLI to configure VNet and subnets before creating the cluster itsel

Gigi Sayfan 1 Nov 10, 2021
MoreIP 一款基于Python的面向 MacOS/Linux 用户用于查询IP/域名信息的日常渗透小工具

MoreIP 一款基于Python的面向 MacOS/Linux 用户用于查询IP/域名信息的日常渗透小工具

xq17 9 Sep 21, 2022
forward several ports into a single port

port forwarding Multi-Input-Single-Output forward several ports into a single one this tool forwards packets from several ports into one single port.

Erfan Kheyrollahi Qaroğlu 3 Sep 11, 2021
Search ports in multiples hosts

Search Port ✨ Multiples Searchs ✨ Create list hosts Create list targets Start Require Python 3.10.0+. python main.py Struture Function Directory load_

Tux#3634 7 Apr 29, 2022
Python Scrcpy Client - allows you to view and control android device in realtime

Python Scrcpy Client This package allows you to view and control android device in realtime. Note: This gif is compressed and experience lower quality

LengYue 126 Jan 02, 2023
Dark Utilities - Cloudflare Uam Bypass

Dark Utilities - Cloudflare Uam Bypass

Inplex-sys 26 Dec 14, 2022
An ftp syncing python package that I use to sync pokemon saves between my hacked 3ds running ftpd and my server

Sync file pairs over ftp and apply patches to them. Useful for using ftpd to transfer ROM save files to and from your DS if you also play on an emulator. Setup a cron job to check for your DS's ftp s

17 Jan 04, 2023
Takes a file of hosts or domains and outputs the IP address of each host/domain in the file.

Takes a file of hosts or domains and outputs the IP address of each host/domain in the file. Installation $ git clone https://github.com/whoamisec75/i

whoami security 2 May 10, 2022
A Simple but Powerful cross-platform port scanning & and network automation tool.

DEDMAP is a Simple but Powerful, Clever and Flexible Cross-Platform Port Scanning tool made with ease to use and convenience in mind. Both TCP

Anurag Mondal 30 Dec 16, 2022
📨 Share files easily over your local network from the terminal! 📨

Fileshare 📨 Share files easily over your local network from the terminal! 📨 Installation #

Dopevog 11 Sep 10, 2021
ThorFI: A Novel Approach for Network Fault Injection as a Service

ThorFI: a Novel Approach for Network Fault Injection as a Service This repo includes ThorFI, a novel fault injection solution for virtual networks in

DESSERT research lab (Federico II University of Naples, Italy) 6 Dec 14, 2022
Light, simple RPC framework for Python

Agileutil是一个Python3 RPC框架。基于微服务架构,封装了rpc/http/orm/log等常用组件,提供了简洁的API,开发者可以很快上手,快速进行业务开发。

16 Nov 22, 2022
Home Assistant integration for MyEnergi devices

myenergi for Home Assistant myenergi custom component for Home Assistant This is a very early release, will add more documentations soon! This compone

Johan Isacsson 70 Dec 18, 2022
A script to automatically update the github's proxy IP in hosts file.

updateHostsGithub A script to automatically update the github's proxy IP in hosts file. Now only Mac and Linux are supported. (脚本自动更新本地hosts文件,目前仅支持Ma

2 Jul 06, 2022
A simple hosts picker for Microsoft Services

A simple Python scrip for you to select the fastest IP for Microsoft services.

Konnyaku 394 Dec 17, 2022