adafruit / adafruit_circuitpython_httpserver Goto Github PK
View Code? Open in Web Editor NEWSimple HTTP Server for CircuitPython
License: MIT License
Simple HTTP Server for CircuitPython
License: MIT License
I'm happy to help out with implementing this - is anyone already working on it?
How can I add a button on a webpage that, when clicked, runs some code?
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!
Starting server on Pico W errors out on server.start(<str:ip>,<int:port>)
when running on CircuitPython 9.0.0 with 9.x httpserver library. Error was: OSError: [Errno 95] EOPNOTSUPP
. Same code runs on 8.2.10 without issue with 8.x library.
Steps to recreate:
Code adapted from Adafruit Pico W HTTP Server (https://learn.adafruit.com/pico-w-http-server-with-circuitpython/code-the-pico-w-http-server):
code.py
import os
import time
import ipaddress
import wifi
import socketpool
import busio
import board
import microcontroller
import terminalio
from digitalio import DigitalInOut, Direction
from adafruit_httpserver import Server, Request, Response, POST
# onboard LED setup
led = DigitalInOut(board.LED)
led.direction = Direction.OUTPUT
led.value = False
# connect to network
print()
print("Connecting to WiFi")
connect_text = "Connecting..."
# set static IP address
ipv4 = ipaddress.IPv4Address("192.168.1.25")
netmask = ipaddress.IPv4Address("255.255.255.0")
gateway = ipaddress.IPv4Address("192.168.1.1")
wifi.radio.set_ipv4_address(ipv4=ipv4,netmask=netmask,gateway=gateway)
# connect to your SSID
wifi.radio.connect(os.getenv('CIRCUITPY_WIFI_SSID'), os.getenv('CIRCUITPY_WIFI_PASSWORD'))
print("Connected to WiFi")
pool = socketpool.SocketPool(wifi.radio)
server = Server(pool, "/static", debug=True)
# variables for HTML
# font for HTML
font_family = "monospace"
# the HTML script
# setup as an f string
# this way, can insert string variables from code.py directly
# of note, use {{ and }} if something from html *actually* needs to be in brackets
# i.e. CSS style formatting
def webpage():
html = f"""
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html{{font-family: {font_family}; background-color: lightgrey;
display:inline-block; margin: 0px auto; text-align: center;}}
h1{{color: deeppink; width: 200; word-wrap: break-word; padding: 2vh; font-size: 35px;}}
p{{font-size: 1.5rem; width: 200; word-wrap: break-word;}}
.button{{font-family: {font_family};display: inline-block;
background-color: black; border: none;
border-radius: 4px; color: white; padding: 16px 40px;
text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}}
p.dotted {{margin: auto;
width: 75%; font-size: 25px; text-align: center;}}
</style>
</head>
<body>
<title>Pico W HTTP Server</title>
<h1>Pico W HTTP Server</h1>
<br>
<p class="dotted">This is a Pico W running an HTTP server with CircuitPython.</p>
<br>
<h1>Control the LED on the Pico W with these buttons:</h1><br>
<form accept-charset="utf-8" method="POST">
<button class="button" name="LED ON" value="ON" type="submit">LED ON</button></a></p></form>
<p><form accept-charset="utf-8" method="POST">
<button class="button" name="LED OFF" value="OFF" type="submit">LED OFF</button></a></p></form>
</body></html>
"""
return html
# route default static IP
@server.route("/")
def base(request: Request): # pylint: disable=unused-argument
# serve the HTML f string
# with content type text/html
return Response(request, f"{webpage()}", content_type='text/html')
# if a button is pressed on the site
@server.route("/", POST)
def buttonpress(request: Request):
# get the raw text
raw_text = request.raw_request.decode("utf8")
print(raw_text)
# if the led on button was pressed
if "ON" in raw_text:
# turn on the onboard LED
led.value = True
# if the led off button was pressed
if "OFF" in raw_text:
# turn the onboard LED off
led.value = False
# reload site
return Response(request, f"{webpage()}", content_type='text/html')
print("starting server..")
# startup the server
try:
server.start(str(wifi.radio.ipv4_address), 80)
print("Listening on http://%s:80" % wifi.radio.ipv4_address)
# if the server fails to begin, restart the pico w
except Exception as e:
print(f'Encountered exception.\n{e}\n')
time.sleep(5)
# print("restarting..")
# microcontroller.reset()
ping_address = ipaddress.ip_address("8.8.4.4")
clock = time.monotonic() # time.monotonic() holder for server ping
while True:
try:
# every 30 seconds, ping server & update temp reading
if (clock + 30) < time.monotonic():
if wifi.radio.ping(ping_address) is None:
print("lost connection")
else:
print("connected")
clock = time.monotonic()
# poll the server for incoming/outgoing requests
server.poll()
# pylint: disable=broad-except
except Exception as e:
print(e)
continue
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
:
Adafruit_CircuitPython_HTTPServer/adafruit_httpserver.py
Lines 232 to 236 in 67c3ac3
This format
method is str.format
, since self._HEADERS_FORMAT
is just a string:
Adafruit_CircuitPython_HTTPServer/adafruit_httpserver.py
Lines 179 to 185 in 67c3ac3
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:
Adafruit_CircuitPython_HTTPServer/adafruit_httpserver.py
Lines 49 to 50 in 67c3ac3
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!
After adding the Start/Poll method I setup some tests to see if other code could run even if there were no requests coming in. Unfortunately, that wasn't the case. Digging into it deeper I discovered that socket.accept()
method is blocking by default.
Modifying the example code with a simple print will show the issue.
while True:
try:
print("Processing before polling...")
server.poll()
print("Processing after polling...")
except OSError:
continue
The code will stop at the server.poll()
method waiting for a request to come in...
Listening on http://192.168.1.208:80
Processing before polling...
Once a request comes in the other lines will process, then it'll stop at server.poll()
again...
Processing after polling...
Processing before polling...
To fix this the socket needs to set blocking to False or it's timeout to 0 in HTTPServer.Start()
...
self._sock.bind((host, port))
self._sock.listen(5)
self._sock.setblocking(False) #do not block
There is one caveat to this. When the accept() method doesn't find any incoming connections and blocking is turned off, it raises an exception OSError [Errno 11] EAGAIN
. Therefore, server.poll()
must be the last thing in the while loop otherwise, the exception will skip any logic after the exception is raised. Which means any logic after server.poll()
will only run once a request has been received and processed successfully...
while True:
try:
print("Processing before polling...")
server.poll()
print("Processing after polling...") #This will never be called if exception is raised.
except OSError:
continue
Results in this output...
Listening on http://192.168.1.208:80
Processing before polling...
Processing before polling...
Processing before polling...
Processing before polling...
Received 485 bytes
Processing after polling...
Processing before polling...
Processing before polling...
... and so on
A possible work around for this would be to handle the specific exception OSError: [Errno 11] EAGAIN
in the server.poll()
method. But I'm not sure if catching and handling exceptions in library code is an acceptable practice.
This is likely a feature request, unless I've missed something.
In many cases I'd prefer my HTTPServer application define all possible valid URLs as server routes to python handlers, and any invalid URL request immediately returns a 404 error. No going to the file system when a route handler is not found.
In older versions of the library, I would create the server like this:
server = HTTPServer(pool, "undef")server = HTTPServer(pool)
And then use @server.route("/...")
pragmas to define all URLs.
At some point since I last updated, the interface to HTTPServer
changed so that it requires a second argument, that for root_path
. In the documentation, the example is:
server = HTTPServer(pool, "/static")
I'd prefer to prevent any attempt to access the filesystem. I know I could pass a nonexistent file/folder as root_path
, which would cause all filesystem I/O attempts to raise an error. But that would cause unnecessary file system I/O for every route handler miss, and perhaps more importantly, it is confusing to someone looking at the code. They'd wonder why the given root_path
folder didn't exist. It also causes an error message, saying which file was not found, back to the client, which might encourage someone to try manipulating URL paths in an attempt to access "private" data on the filesystem.
My first thought was to try this:
server = HTTPServer(pool, None)
However, if a client requests a URL with path that has no route handler, HTTPServer.poll()
will do an HTTPResponse.send_file()
, which eventually does root_path.endswith()
. This causes an AttributeError
to be raised because None
is not a string in this case. Would it be possible to have an option to instruct HTTPServer to never check the file system?
To do this, I'd suggest that HTTPServer.poll()
checks if root_path
is None
before calling HTTPResponse.send_file()
. If so, it would raise FileNotExistsError
to directly cause a 404 response, without going to the filesystem.
Thank you for your consideration, and for all of your hard work making this software.
Edited for typos. And clarity.
It seems that the files being released on PyPi don't include the actual library, or are not importable for some reason.
❯ python -m pip install adafruit-circuitpython-httpserver
Collecting adafruit-circuitpython-httpserver
Using cached adafruit_circuitpython_httpserver-4.0.1-py3-none-any.whl (3.8 kB)
Collecting Adafruit-Blinka (from adafruit-circuitpython-httpserver)
Downloading Adafruit_Blinka-8.19.0-py3-none-any.whl (302 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 302.3/302.3 kB 6.2 MB/s eta 0:00:00
Collecting Adafruit-PlatformDetect>=3.13.0 (from Adafruit-Blinka->adafruit-circuitpython-httpserver)
Downloading Adafruit_PlatformDetect-3.46.0-py3-none-any.whl (23 kB)
Collecting Adafruit-PureIO>=1.1.7 (from Adafruit-Blinka->adafruit-circuitpython-httpserver)
Downloading Adafruit_PureIO-1.1.11-py3-none-any.whl (10 kB)
Collecting pyftdi>=0.40.0 (from Adafruit-Blinka->adafruit-circuitpython-httpserver)
Using cached pyftdi-0.54.0-py3-none-any.whl (144 kB)
Collecting adafruit-circuitpython-typing (from Adafruit-Blinka->adafruit-circuitpython-httpserver)
Downloading adafruit_circuitpython_typing-1.9.4-py3-none-any.whl (10 kB)
Collecting pyusb!=1.2.0,>=1.0.0 (from pyftdi>=0.40.0->Adafruit-Blinka->adafruit-circuitpython-httpserver)
Using cached pyusb-1.2.1-py3-none-any.whl (58 kB)
Collecting pyserial>=3.0 (from pyftdi>=0.40.0->Adafruit-Blinka->adafruit-circuitpython-httpserver)
Using cached pyserial-3.5-py2.py3-none-any.whl (90 kB)
Collecting adafruit-circuitpython-busdevice (from adafruit-circuitpython-typing->Adafruit-Blinka->adafruit-circuitpython-httpserver)
Downloading adafruit_circuitpython_busdevice-5.2.6-py3-none-any.whl (7.5 kB)
Collecting adafruit-circuitpython-requests (from adafruit-circuitpython-typing->Adafruit-Blinka->adafruit-circuitpython-httpserver)
Downloading adafruit_circuitpython_requests-1.13.4-py3-none-any.whl (11 kB)
Collecting typing-extensions~=4.0 (from adafruit-circuitpython-typing->Adafruit-Blinka->adafruit-circuitpython-httpserver)
Using cached typing_extensions-4.6.2-py3-none-any.whl (31 kB)
Installing collected packages: pyserial, Adafruit-PlatformDetect, typing-extensions, pyusb, Adafruit-PureIO, pyftdi, adafruit-circuitpython-requests, adafruit-circuitpython-busdevice, adafruit-circuitpython-typing, Adafruit-Blinka, adafruit-circuitpython-httpserver
Successfully installed Adafruit-Blinka-8.19.0 Adafruit-PlatformDetect-3.46.0 Adafruit-PureIO-1.1.11 adafruit-circuitpython-busdevice-5.2.6 adafruit-circuitpython-httpserver-4.0.1 adafruit-circuitpython-requests-1.13.4 adafruit-circuitpython-typing-1.9.4 pyftdi-0.54.0 pyserial-3.5 pyusb-1.2.1 typing-extensions-4.6.2
CircuitPython_RGB_LED_HTTPServer on main [!+] is 📦 v0.0.0+auto.0 via 🐍 v3.10.6 (venv) took 2s
❯ python
Python 3.10.6 (main, Mar 10 2023, 10:55:28) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from adafruit_httpserver import Server
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'adafruit_httpserver'
It installs successfully, but if you try to import it there is a ModuleNotFoundError raised. My environment was Ubuntu PC. I ran into this issue while trying to build the docs locally for a library that depends on this one. Perhaps it's something specific to my environment, since the docs seem to have built successfully via actions here in github.
Hi.
When I import the adafruit_httpserver on the WIZnet W5500-EVB-Pico, I got following error.
Adafruit CircuitPython 8.2.9 on 2023-12-06; W5500-EVB-Pico with rp2040
>>> from adafruit_httpserver import Server, Request, Response, MIMEType
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "adafruit_httpserver/__init__.py", line 56, in <module>
File "adafruit_httpserver/response.py", line 20, in <module>
ImportError: no module named 'hashlib'
The error does not occur in the version I downloaded around May.
I seem to import hashlib
was added at the commit of 1e1ad58 .
How can I solve this problem?
The Metro M4 Lite running CircuitPython 9.0.0-beta2 does not contain the necessary socketpool library. Are the Airlift configurations going to be supported with HTTPServer?
forum thread for reference.
This code:
...
mdns_server = mdns.Server(wifi.radio)
mdns_server.hostname = "cont"
mdns_server.advertise_service(service_type="_http", protocol="_tcp", port=80)
...
Gives this error:
0;🐍192.168.178.89 | code.py | 8.2.0-rc.1Traceback (most recent call last):
File "code.py", line 29, in <module>
RuntimeError: Out of MDNS service slots
0;🐍192.168.178.89 | [email protected] RuntimeError | 8.2.0-rc.1
Code done running.
Is this because I need write access to my local network's DNS server?
If this worked will it allow me to set a custom hostname different from PicoW?
How do I yield data to a chunked response from a async routine?
The code reads the adc like this:
async def read_adc(pin: microcontroller.Pin):
with analogio.AnalogIn(pin) as adc:
while True:
local_value = (adc.value * 3.3) / 65536
await asyncio.sleep(0.1)
How can the local_value be yield'ed in a chunked response?
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.
I have a for example a small web page with repeated ajax requests. Page is sending one request per second and server on Pico W needs different times for processing same request. In my case it varies between 40 ms and 250 ms.
I expected the time for processing the request to be approximately in the range of 40 to 80ms.
Connection to Pico is fast and when i added some debug prints to poll() function in server.py, then i found, that the bottleneck is function - self._receive_header_bytes(conn)
def poll(self):
try:
conn, client_address = self._sock.accept()
with conn:
conn.settimeout(self._timeout)
start_msecs = supervisor.ticks_ms()
# Receiving data until empty line
header_bytes = self._receive_header_bytes(conn)
print(f"time to receive header bytes: {supervisor.ticks_ms() - start_msecs} ms")
# Return if no data received
example of printed output (it's not the same sample as shown in image)
time to receive header bytes: 157 ms
time to receive header bytes: 1 ms
time to receive header bytes: 152 ms
time to receive header bytes: 1 ms
time to receive header bytes: 129 ms
time to receive header bytes: 1 ms
time to receive header bytes: 12 ms
time to receive header bytes: 95 ms
time to receive header bytes: 210 ms
time to receive header bytes: 1 ms
time to receive header bytes: 12 ms
time to receive header bytes: 1 ms
time to receive header bytes: 46 ms
time to receive header bytes: 1 ms
time to receive header bytes: 167 ms
time to receive header bytes: 188 ms
time to receive header bytes: 199 ms
time to receive header bytes: 208 ms
example code:
import os
import time
import ipaddress
import wifi
import socketpool
import microcontroller
from adafruit_httpserver.server import HTTPServer
from adafruit_httpserver.request import HTTPRequest
from adafruit_httpserver.response import HTTPResponse
from adafruit_httpserver.methods import HTTPMethod
from adafruit_httpserver.mime_type import MIMEType
from adafruit_httpserver.status import CommonHTTPStatus
# set static IP address
ipv4 = ipaddress.IPv4Address("192.168.88.99")
netmask = ipaddress.IPv4Address("255.255.255.0")
gateway = ipaddress.IPv4Address("192.168.88.1")
wifi.radio.set_ipv4_address(ipv4=ipv4, netmask=netmask, gateway=gateway)
# connect to your SSID
wifi.radio.connect(
os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")
)
print("Connected to WiFi")
pool = socketpool.SocketPool(wifi.radio)
server = HTTPServer(pool)
@server.route("/")
def base(request: HTTPRequest): # pylint: disable=unused-argument
with HTTPResponse(request, content_type=MIMEType.TYPE_HTML) as response:
response.send("""
<html>
<header>
<script>
const ajax = function (opt, cb) {
const headers = opt.headers || {},
body = opt.body || "",
xhr = new XMLHttpRequest();
xhr.open(opt.method || (body ? "POST" : "GET"), opt.url || "/", true);
xhr.ontimeout = xhr.onabort = xhr.onerror = function (e) {
console.error("XHR error: ", e, "opt: ", opt);
};
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
cb(xhr.status, xhr.response || xhr.responseText);
}
};
headers["Content-Type"] = headers["Content-Type"] || "application/x-www-form-urlencoded";
for (const f in headers) {
xhr.setRequestHeader(f, headers[f]);
}
xhr.send(body);
};
function ajaxRequest(request, interval) {
const ax = function () {
ajax(request, function (code, response) {
if (code == 200 && response) {
console.log("response", response);
}
if (interval) {
setTimeout(ax, interval);
}
});
};
ax();
}
data = {"id":"sw1","value":true}
intervalMs = 1000
ajaxRequest({ url: "/api", method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }, intervalMs);
</script>
</header>
<body>
sapmle page with ajax request every 1 second
</body>
</html>""")
@server.route("/api", HTTPMethod.POST)
def base(request: HTTPRequest):
print("api request", request.body)
with HTTPResponse(request, content_type=MIMEType.TYPE_JSON) as response:
response.send('{"status": "ok"}')
print("starting server..")
# startup the server
try:
server.start(str(wifi.radio.ipv4_address))
print("Listening on http://%s:80" % wifi.radio.ipv4_address)
# if the server fails to begin, restart the pico w
except OSError:
time.sleep(5)
print("restarting..")
microcontroller.reset()
while True:
try:
server.poll()
except Exception as e:
print(e)
continue
Create a bare minimum example that proves the HTTPServer is running on the device, akin to a "Hello World"
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
Filing here first, though it could be a core issue and get moved.
An HTTPS server was published by a community member in early 2023 that worked on Pico W (but ran a bit slow):
https://github.com/ide/circuitpython-https-server
Then there was recently some core development to make HTTPS Server work on espressif
boards:
adafruit/circuitpython#8268
adafruit/circuitpython#8932
adafruit/circuitpython#8962
(plus further changes to require explicit SO_REUSEADDR
)
However, HTTPS Server seems to have broken on Pico W with MemoryError,
somewhere between 8.0.0 and 8.1.0 (bisect of CP versions, but using latest 4.5.5 adafruit_HTTPServer).
Also, although HTTPS Server does now run well on ESP32-S3 boards, ESP32-S2 boards (with PSRAM) also get the MemoryError
.
The MemoryError
occurs in poll
, when calling socket.accept()
:
Traceback (most recent call last):
File "code.py", line 72, in <module>
File "adafruit_httpserver/server.py", line 450, in poll
File "adafruit_httpserver/server.py", line 404, in poll
MemoryError:
Boards tested:
Adafruit CircuitPython 9.0.0-beta.2-4-gf23205c822 on 2024-02-20; Adafruit QT Py ESP32-S3 4MB Flash 2MB PSRAM with ESP32S3
Adafruit CircuitPython 9.0.0-beta.2-4-gf23205c822 on 2024-02-20; Adafruit QT Py ESP32S2 with ESP32S2
Adafruit CircuitPython 9.0.0-beta.2-4-gf23205c822 on 2024-02-20; Raspberry Pi Pico W with rp2040
This library was written in a hurry for a specific project that needed to serve large files. Most of the code was adapted from another simple HTTP server library.
It would be fine with me if this library were completely rewritten, or replaced with, say, https://github.com/deckerego/ampule, or some simple WSGI-ish server that also served files. There is nothing about the code here that is sacred. I've looked at flask and a couple of other "small" server libraries, but they don't seem small enough, or they have dependencies that are not small.
Hi,
I think I found an issue in the web server included in CircuitPython. If you post a form back to the web server that has no input fields, OR contains only chekboxes and they all are clear (which causes no form data to be posted back), the web server seems to crash.
For example this form:
<form action="/update" method="post">
<input type="checkbox" name="test" value="1">
<input type="submit" value="Update">
</form>
When the checkbox is clear, and the form is posted back, when trying to access the field value (or any of the other relevant methods like get(...)):
@server.route("/update", POST)
def update(request: Request):
x = request.form_data.fields
The web server crashes with this error:
File "adafruit_httpserver/request.py", line 321, in form_data
File "adafruit_httpserver/request.py", line 144, in __init__
File "adafruit_httpserver/request.py", line 157, in _parse_x_www_form_urlencoded
ValueError: need more than 1 values to unpack
This is using adafruit_httpserver from adafruit-circuitpython-bundle-8.x-mpy-20230829.zip on a Rasberry PI Pico on Circuit Python 8.2.4
If I add a text or hidden input in the form for example, it works:
<form action="/update" method="post">
<input type="checkbox" name="test" value="1">
<input type="hidden" name="a" value="1">
<input type="submit" value="Update">
</form>
Currently it's not possible (it seems) to set custom HTTP headers in the response.
This means using content/data from a adafruit_httpserver
device (say using XmlHttpRequest) is not possible because the CORS header Access-Control-Allow-Origin: *
cannot be set.
CircuitPython 9.0.0-beta.1 will include this PR:
This makes the semantics of socket reuse as it is in CPython. One would need to use socket.setsockopt(pool.SOL_SOCKET, pool.SO_REUSEADDR, 1)
in the same cases one would use it in CPython.
HTTPServer maintainers: Is this a breaking change for use of adafruit_httpserver
or for the examples? Thanks for looking.
As #4 points out, the request size can't grow.
Adafruit CircuitPython 8.0.3 on 2023-02-23; Raspberry Pi Pico with rp2040
I am building HTTP server with W5100S and can't get this library to work properly because of adafruit_wiznet5k_socket, i think. I cut some code to demonstrate the problem, so if you need explanation in something, i'll explain
code.py
import board
import busio
import digitalio
import time
import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket
from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K
from adafruit_httpserver.request import HTTPRequest
from adafruit_httpserver.response import HTTPResponse
from adafruit_httpserver.server import HTTPServer
from adafruit_httpserver.methods import HTTPMethod
IP_ADDRESS = (192, 168, 1, 244)
SUBNET_MASK = (255, 255, 255, 0)
GATEWAY_ADDRESS = (192, 168, 1, 1)
DNS_SERVER = (8, 8, 8, 8)
W5x00_RSTn = board.GP20
ethernetRst = digitalio.DigitalInOut(W5x00_RSTn)
ethernetRst.direction = digitalio.Direction.OUTPUT
ethernetRst.value = False
time.sleep(1)
ethernetRst.value = True
SPI0_SCK = board.GP18
SPI0_TX = board.GP19
SPI0_RX = board.GP16
SPI0_CSn = board.GP17
spi_bus = busio.SPI(SPI0_SCK, MOSI=SPI0_TX, MISO=SPI0_RX)
cs = digitalio.DigitalInOut(SPI0_CSn)
eth = WIZNET5K(spi_bus, cs, is_dhcp=False)
def routes(server):
@server.route("/", HTTPMethod.POST)
def base(request: HTTPRequest):
raw_text = request.body.decode("UTF-8")
print(raw_text)
with open('index.html', 'r') as f:
html_string = f.read()
f.close()
with HTTPResponse(request) as response:
response.send(html_string, content_type="text/html")
@server.route("/", HTTPMethod.GET)
def base(request: HTTPRequest):
with open('index.html', 'r') as f:
html_string = f.read()
f.close()
with HTTPResponse(request) as response:
response.send(html_string, content_type="text/html")
def raise_serv():
eth.ifconfig = (IP_ADDRESS, SUBNET_MASK, GATEWAY_ADDRESS, DNS_SERVER)
socket.set_interface(eth)
server_ip = eth.pretty_ip(eth.ip_address)
print(f"Listening on http://{server_ip}:80")
return HTTPServer(socket), server_ip
while True:
print("Raising server..")
server, server_ip = raise_serv()
routes(server)
server.start(str(server_ip))
print("Server raised.")
while True:
try:
server.poll()
except OSError as error:
print(error)
continue
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style type="text/css">
body {
margin: 0;
}
.wrapper {
background: #ffffff;
box-sizing: border-box;
margin: 0 auto;
max-width: 1300px;
min-height: 100vh;
}
.container {
display: grid;
grid-template-columns: 340px repeat(6, auto);
}
.el {
padding: 2px;
font-size: 17px;
}
input {
text-align: right;
}
.btn-apply {
text-align: right;
padding: 15px 2px;
}
.row-border{
border-top: 1px solid rgb(191, 252, 198);
grid-column: 1 / 8;
}
.square {
position: relative;
padding-left: 17px;
margin-left: 5px;
}
.square:after {
position:absolute;
content:"";
width:17px;
height:17px;
top:2px;
left:0;
}
.square-green {
background-color:rgb(0, 190, 0);
}
.square-red {
background-color:rgb(190, 0, 0);
}
</style>
</head>
<body>
<div class="wrapper">
<form action="" method="post">
<div class="container">
<div class="el el-1"><b></b></div>
<div class="el el-2" align="right"><b>1</b></div>
<div class="el el-3" align="right"><b>2</b></div>
<div class="el el-4" align="right"><b>3</b></div>
<div class="el el-5" align="right"><b>4</b></div>
<div class="el el-6" align="right"><b>5</b></div>
<div class="el el-7" align="right"><b>6</b></div>
<div class="row-border"></div><div class="el el-1"></div>
<div class="el el-2"><input name="name1" type="text" value="1" style="width: 95%; color:rgb(0, 150, 17)"></div>
<div class="el el-3"><input name="name2" type="text" value="2" style="width: 95%; color:rgb(0, 150, 17)"></div>
<div class="el el-4"><input name="name3" type="text" value="3" style="width: 95%; color:rgb(0, 150, 17)"></div>
<div class="el el-5"><input name="name4" type="text" value="4" style="width: 95%; color:rgb(0, 150, 17)"></div>
<div class="el el-6"><input name="name5" type="text" value="5" style="width: 95%; color:rgb(0, 150, 17)"></div>
<div class="el el-7"><input name="name6" type="text" value="6" style="width: 95%; color:rgb(0, 150, 17)"></div>
<div class="row-border"></div>
<div class="el el-1"></div>
<div class="el el-2" align="right">1<span class="square square-$COLOR1"></span></div>
<div class="el el-3" align="right">1<span class="square square-$COLOR2"></span></div>
<div class="el el-4" align="right">1<span class="square square-$COLOR3"></span></div>
<div class="el el-5" align="right">1<span class="square square-$COLOR4"></span></div>
<div class="el el-6" align="right">1<span class="square square-$COLOR5"></span></div>
<div class="el el-7" align="right">1<span class="square square-$COLOR6"></span></div>
<div class="row-border"></div>
<div class="el el-1"></div>
<div class="el el-2"></div>
<div class="el el-3"></div>
<div class="el el-4"></div>
<div class="el el-5"></div>
<div class="el el-6"></div>
<div class="el el-7"></div>
<div class="row-border"></div>
<div class="el el-1" style="padding-left: 10px;"></div>
<div class="el el-2" align="right"><input type="radio" name="log_level1" value="1" id="" $CHECKEDTRUE1></div>
<div class="el el-3" align="right"><input type="radio" name="log_level2" value="1" id="" $CHECKEDTRUE2></div>
<div class="el el-4" align="right"><input type="radio" name="log_level3" value="1" id="" $CHECKEDTRUE3></div>
<div class="el el-5" align="right"><input type="radio" name="log_level4" value="1" id="" $CHECKEDTRUE4></div>
<div class="el el-6" align="right"><input type="radio" name="log_level5" value="1" id="" $CHECKEDTRUE5></div>
<div class="el el-7" align="right"><input type="radio" name="log_level6" value="1" id="" $CHECKEDTRUE6></div>
<div class="row-border"></div>
<div class="el el-1" style="padding-left: 10px;"></div>
<div class="el el-2" align="right"><input type="radio" name="log_level1" value="0" id="" $CHECKEDFALSE1></div>
<div class="el el-3" align="right"><input type="radio" name="log_level2" value="0" id="" $CHECKEDFALSE2></div>
<div class="el el-4" align="right"><input type="radio" name="log_level3" value="0" id="" $CHECKEDFALSE3></div>
<div class="el el-5" align="right"><input type="radio" name="log_level4" value="0" id="" $CHECKEDFALSE4></div>
<div class="el el-6" align="right"><input type="radio" name="log_level5" value="0" id="" $CHECKEDFALSE5></div>
<div class="el el-7" align="right"><input type="radio" name="log_level6" value="0" id="" $CHECKEDFALSE6></div>
<div class="row-border"></div>
<div class="el el-1"></div>
<div class="el el-2"></div>
<div class="el el-3"></div>
<div class="el el-4"></div>
<div class="el el-5"></div>
<div class="el el-6"></div>
<div class="el el-7"></div>
<div class="row-border"></div>
<div class="el el-1" style="padding-left: 10px;"></div>
<div class="el el-2"><input name="impulse_time1" type="text" style="width: 95%;" value="1"></div>
<div class="el el-3"><input name="impulse_time2" type="text" style="width: 95%;" value="1"></div>
<div class="el el-4"><input name="impulse_time3" type="text" style="width: 95%;" value="1"></div>
<div class="el el-5"><input name="impulse_time4" type="text" style="width: 95%;" value="1"></div>
<div class="el el-6"><input name="impulse_time5" type="text" style="width: 95%;" value="1"></div>
<div class="el el-7"><input name="impulse_time6" type="text" style="width: 95%;" value="1"></div>
<div class="row-border"></div>
<div class="el el-1" style="padding-left: 10px;"></div>
<div class="el el-2"><button name="impulse_line" value="1" style="width: 100%;">1</button></div>
<div class="el el-3"><button name="impulse_line" value="2" style="width: 100%;">2</button></div>
<div class="el el-4"><button name="impulse_line" value="3" style="width: 100%;">3</button></div>
<div class="el el-5"><button name="impulse_line" value="4" style="width: 100%;">4</button></div>
<div class="el el-6"><button name="impulse_line" value="5" style="width: 100%;">5</button></div>
<div class="el el-7"><button name="impulse_line" value="6" style="width: 100%;">6</button></div>
<div class="row-border"></div>
</div>
<div class="btn-apply">
<button type="submit" name="apply_changes" value="1">a</button>
</div>
</form>
</div>
</body>
</html>
After first request, server stops responding after some time, giving "ERR_CONNECTION_REFUSED" in browsers. Also, multiple POST requests(2 fast clicks on button) are doing the same, but faster. I've done some tests with different systems, that gave me no results. The only thing, that helped me is "Incognito" mode. Other versions of CircuitPython don't help.
When I interrupt programm with CTRL+C it gives.
File "/lib/adafruit_httpserver/response.py", line 167, in send
File "/lib/adafruit_httpserver/response.py", line 242, in _send_bytes
File "/lib/adafruit_wiznet5k/adafruit_wiznet5k_socket.py", line 422, in send
File "/lib/adafruit_wiznet5k/adafruit_wiznet5k.py", line 1034, in socket_write
File "/lib/adafruit_wiznet5k/adafruit_wiznet5k.py", line 614, in write
Hello folks (from germany),
danhalbert from adafruits forum guided me here, that's why I'm kindly asking here the original question.
RPI PICO W should:
Following the example https://github.com/adafruit/Adafruit_Ci ... asyncio.py me I hardly tried to spin up the http-server on wifi.radio.ipv4_address_ap rather than on wifi.radio.ipv4_address.
Don't get me wrong: clear decorated routes (@server.route...) are best practice. That's why I welcome that pattern.
This led to no success. Indeed this is what's going on:
What's wrong here? Any suggestions are appreciated.
import time
import board
import digitalio
import wifi
import socketpool
from adafruit_httpserver import Server, REQUEST_HANDLED_RESPONSE_SENT, Request, Response
from asyncio import create_task, gather, run, sleep as async_sleep
led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT
ap_ssid = "myAP"
ap_password = "12345678"
wifi.radio.start_ap(ssid=ap_ssid, password=ap_password)
print("Access point created with SSID: {}, password: {}".format(ap_ssid, ap_password))
print("My IP address is", wifi.radio.ipv4_address_ap)
pool = socketpool.SocketPool(wifi.radio)
server = Server(pool, "/static", debug=False)
server.start(str(wifi.radio.ipv4_address_ap))
@server.route("/")
def base(request: Request):
return Response(request, "Hello from the CircuitPython Access Point")
async def handle_http_requests():
while True:
pool_result = server.poll()
if pool_result == REQUEST_HANDLED_RESPONSE_SENT:
#
pass
await async_sleep(0)
async def do_something_useful():
global i
while True:
led.value = 1
await async_sleep(1)
led.value = 0
await async_sleep(1)
async def main():
await gather(
create_task(handle_http_requests()),
create_task(do_something_useful()),
)
run(main())
P.S.:
A use case came up on Discord (sending jpeg from memory, not a file) for sending bytes rather than a string.
Currently send
and send_chunk
expect strings, but perhaps with either a parameter or type checking, either could be handled.
Workaround is to use _send_bytes()
.
Addendum: bytearray, bytes, or memoryview; anything sockets support
tried circuitpython 8 beta 1/2/3 but the HTTPServer is not usable on pico w.
minimal examples like all three from this very lib: https://github.com/adafruit/Adafruit_CircuitPython_HTTPServer/tree/main/examples
are not repsonding to simple browser or curl requests
ERR_CONNECTION_REFUSED (like about every 50. request does in fact get a response)
then i read this, and he is absolutly right: #14 (comment)
replacing the .mpy to source .py and changing L:334 to
self._sock.setblocking(True)
will make the HTTPServer respond every time
not sure if this is related to this issue: #9 which does belong to this MR: bcabd1d which originally does change this very line to the opposite that makes the lib work for me and @justbuchanan.
sadly this is not a real solution, since it is now waiting/blocking to a incoming request and no other magic can happen while waiting :-(
I presume this has already been considered. If so, I'd be great to document why it has been implemented yes (technical limitations, memory,..) that way if others decide to implement it, can use this information to make better decisions.
Thanks for your awesome work.
Here the example in the MDNS section of the docs is the cpu_information example not the mdns example that is in the repo.
https://docs.circuitpython.org/projects/httpserver/en/latest/examples.html
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.
The default hostname is PicoW.
How do I set it to something different?
I am using circuit python 8.2
If I do this:
...
from adafruit_httpserver import Server, Request, FileResponse, MIMETypes, GET, JSONResponse
wifi.radio.hostname = str("xx")
...
server.serve_forever(str(wifi.radio.hostname)) # line 67
...
The REPL says:
>>> Wi-Fi: No IP | Done | 8.2.0-rc.1
Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.
code.py output:
0;🐍192.168.178.89 | code.py | 8.2.0-rc.1socket_resolve_host() returned -2
Traceback (most recent call last):
File "adafruit_httpserver/server.py", line 138, in _verify_can_start
gaierror: (-2, 'Name or service not known')
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "code.py", line 67, in <module>
File "adafruit_httpserver/server.py", line 151, in serve_forever
File "adafruit_httpserver/server.py", line 170, in start
File "adafruit_httpserver/server.py", line 140, in _verify_can_start
RuntimeError: Cannot start server on xx:80
0;🐍192.168.178.89 | 140@adafruit_httpserver/server.py RuntimeError | 8.2.0-rc.1
Code done running.
0;🐍192.168.178.89 | REPL | 8.2.0-rc.1
Adafruit CircuitPython 8.2.0-rc.1 on 2023-06-27; Raspberry Pi Pico W with rp2040
Needless to say removing this and rebooting ...
0;🐍Wi-Fi: Authentication failure | REPL | 8.2.0-rc.1
Adafruit CircuitPython 8.2.0-rc.1 on 2023-06-27; Raspberry Pi Pico W with rp2040
>>>
>>> Wi-Fi: No IP | Done | 8.2.0-rc.1
Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.
code.py output:
0;🐍192.168.178.89 | code.py | 8.2.0-rc.1Started development server on http://PicoW:80
If I do the GET request too quickly on a PICO W it gets stuck in an infinite loop.
I took inspiration from this issue on how to debug: #41
When I add print(bytes_sent, bytes_to_send)
to the response.py _send_bytes method I can see it gets stuck in an infinite loop.
Hard to capture in text as the board locks up and so does Mu serial connection I took a video and I've attached an image from the point where the issue happens.
code.py at the moment is:
import socketpool
import wifi
import os
from adafruit_httpserver.mime_type import MIMEType
from adafruit_httpserver.request import HTTPRequest
from adafruit_httpserver.response import HTTPResponse
from adafruit_httpserver.server import HTTPServer
if not wifi.radio.ipv4_address:
ssid = os.getenv('WIFI_SSID')
print(f"Connecting to Wifi: {ssid}")
wifi.radio.connect(ssid, os.getenv('WIFI_PASSWORD'))
print(f"Connected: IP address is {wifi.radio.ipv4_address}")
pool = socketpool.SocketPool(wifi.radio)
server = HTTPServer(pool)
server.socket_timeout = 0.25
count = 0
poll_count = 0
@server.route("/")
def base(request: HTTPRequest):
print(request.path, request.connection)
"""
Serve the default index.html file.
"""
with HTTPResponse(request, content_type=MIMEType.TYPE_HTML) as response:
response.send_file("index.html")
@server.route("/MUTE")
def mute(request: HTTPRequest):
print(request.path, request.connection)
"""
Serve the default index.html file.
"""
try:
global count
count = count + 1
print(f"{count} sending")
# device.send(0xE2)
with HTTPResponse(request, content_type=MIMEType.TYPE_HTML) as response:
response.send_file("index.html")
print(f"{count} sent")
except Exception as e:
print(e)
print(f"Listening on http://{wifi.radio.ipv4_address}:80")
# Start the server.
server.start(str(wifi.radio.ipv4_address))
while True:
try:
poll_count = poll_count + 1
if(poll_count % 10_000 == 0):
print(f"Polling {poll_count}")
server.poll()
if(poll_count % 10_000 == 0):
print(f"Poll Done {poll_count}")
except Exception as error:
print(error)
raise error
and index.html is:
<!DOCTYPE html>
<html lang="en">
<head>
<title>REMOTE</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
</head>
<body class="bg-dark">
<div class="container-sm bg-dark rounded text-center">
<br>
<div class="row">
<div class="col-1">
<div class="text-light">
<a class="btn brn-dark btn-lg text-light btn-outline-primary" href="/MUTE" role="button"> 🔈 </a>
</div>
</div>
</div>
</div>
</body>
</html>
Adafruit CircuitPython 8.2.9 on M5stack Timer Camera X.
I am building simple HTTP server.
I ran examples/httpserver_simpletest_auto.py( https://docs.circuitpython.org/projects/httpserver/en/latest/examples.html#id4 ) and got the following error.
AttributeError : 'SocketPool' object has no attribure 'SOL_SOCET' .
I saw adafruitt_server/server.py line216 .
self.host, self.port = host, port
host is "192.168.10.116". port is 80.
Let me know if there is anything else to do.
HTTPServer stops working on CircuitPython version >= 9.0
Around line 207 in server.py it is assumed that sock.setsockopt(socket_source.SOL_SOCKET, socket_source.SO_REUSEADDR, 1)
would work as of CircuitPython version 9.0. Unfortunately it doesn't and raises an OSError: [Errno 95] EOPNOTSUPP
.
Perhaps better in the circuitpython implementations to just try to set SO_REUSEADDR and catch + ignore the error.
Using Circuitpython 8.0.0-b3 with socket reuse patch, on a picoW.
Firefox: Everything works fine.
Chrome: Socket/connection is left in a blocking state. It appears that chrome is reopening a connection in anticipation of the next request.
This is the call that is blocking, when there are no bytes to receive.
In lib
? At the same level as code.py
? Doesn't matter?
in examples/httpserver_handler_serves_file.py
line 13
change /static to /www
Thanks
Example from release notes 4.2.0:
Read the [docs](http://circuitpython.readthedocs.io/projects/HTTPServer/en/latest/) for info on how to use it.
That URL leads to 404.
I guess it should be https://docs.circuitpython.org/projects/httpserver/en/latest/api.html ?
import socketpool
> ImportError: no module named 'socketpool'
(circuitpython) PS D:\> circup install socketpool
Found device at D:\, running CircuitPython 7.3.2.
Searching for dependencies for: ['socketpool']
WARNING:
socketpool is not a known CircuitPython library.
The library could benefit from a simple extract of the classes into their own .py files. This will provide for easier maintenance and future expansion of the library. It should be possible to do this in a way that doesn't break existing code.
Something like this...
Hi,
The method request.form_data.get seems to return the string "None" if the requested field doesn't exist. Is should probably return the NoneType None?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.