Skip to content

httpserver simpletest fails (cp9 on Pico W) #9081

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
studiostephe opened this issue Mar 25, 2024 · 17 comments
Closed

httpserver simpletest fails (cp9 on Pico W) #9081

studiostephe opened this issue Mar 25, 2024 · 17 comments
Labels
Milestone

Comments

@studiostephe
Copy link

CircuitPython version

dafruit CircuitPython 9.0.0 on 2024-03-19; Raspberry Pi Pico W with rp2040

Code/REPL

import socketpool
import wifi

from adafruit_httpserver import Server, Request, Response

ssid = "Unintended Consequences"
password = "xxxx"

print("Connecting to", ssid)
wifi.radio.connect(ssid, password)
print("Connected to", ssid)

pool = socketpool.SocketPool(wifi.radio)
server = Server(pool, "/static", debug=True)


@server.route("/")
def base(request: Request):
    """
    Serve a default static plain text message.
    """
    return Response(request, "Hello from the CircuitPython HTTP Server!")


server.serve_forever(str(wifi.radio.ipv4_address))

Behavior

code.py output:
Connecting to Unintended Consequences
Connected to Unintended Consequences
Traceback (most recent call last):
File "code.py", line 25, in
File "adafruit_httpserver/server.py", line 185, in serve_forever
File "adafruit_httpserver/server.py", line 228, in start
File "adafruit_httpserver/server.py", line 207, in _create_server_socket
OSError: [Errno 95] EOPNOTSUPP

Description

Test code taken from first example here: https://docs.circuitpython.org/projects/httpserver/en/latest/examples.html

Additional information

No response

@anecdata
Copy link
Member

anecdata commented Mar 25, 2024

I think this is a known mismatch with the library version. Try this library branch:
https://github.com/adafruit/Adafruit_CircuitPython_HTTPServer/tree/reuseaddr

or, essentially, try:

        try:
            self._sock.setsockopt(
                self._socket_source.SOL_SOCKET, self._socket_source.SO_REUSEADDR, 1
            )
        except AttributeError:
            pass

instead of

        # TODO: Temporary backwards compatibility, remove after CircuitPython 9.0.0 release
        if implementation.version >= (9,) or implementation.name != "circuitpython":
            sock.setsockopt(socket_source.SOL_SOCKET, socket_source.SO_REUSEADDR, 1)
```in `server.py`

@studiostephe
Copy link
Author

Either way, I get the same error when I get to "sock.setsockopt":

Traceback (most recent call last):
File "code.py", line 25, in
File "/lib/adafruit_httpserver/server.py", line 186, in serve_forever
File "/lib/adafruit_httpserver/server.py", line 227, in start
File "/lib/adafruit_httpserver/server.py", line 205, in _set_socket_level_to_reuse_address
OSError: [Errno 95] EOPNOTSUPP

@DJDevon3
Copy link

Same, tried the library you posted and getting the same error.

Adafruit CircuitPython 9.0.0 on 2024-03-19; Raspberry Pi Pico W with rp2040
Board ID:raspberry_pi_pico_w
Connecting to WiFi...
✅ Wifi!
Traceback (most recent call last):
  File "code.py", line 28, in <module>
  File "/lib/adafruit_httpserver/server.py", line 186, in serve_forever
  File "/lib/adafruit_httpserver/server.py", line 227, in start
  File "/lib/adafruit_httpserver/server.py", line 205, in _set_socket_level_to_reuse_address
OSError: [Errno 95] EOPNOTSUPP

@DJDevon3
Copy link

DJDevon3 commented Mar 25, 2024

Confirmed server.py is using

def _set_socket_level_to_reuse_address(self) -> None:
        """
        On systems that have SO_REUSEADDR, prevents "Address already in use"
        error when restarting the server.
        """
        try:
            self._sock.setsockopt(
                self._socket_source.SOL_SOCKET, self._socket_source.SO_REUSEADDR, 1
            )
        except AttributeError:
            pass

Tried using Connection Manager just to see if the socket management might change something.

import os

import adafruit_connection_manager
import wifi

from adafruit_httpserver import Server, Request, Response

# Get WiFi details, ensure these are setup in settings.toml
ssid = os.getenv("CIRCUITPY_WIFI_SSID")
password = os.getenv("CIRCUITPY_WIFI_PASSWORD")

print("Connecting to WiFi...")
wifi.radio.connect(ssid, password)
print("✅ Wifi!")

# Initalize Wifi, Socket Pool, Request Session
pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio)
server = Server(pool, "/static", debug=True)


@server.route("/")
def base(request: Request):
    """Serve a default static plain text message"""
    return Response(request, "Hello from the CircuitPython HTTP Server!")
server.serve_forever(str(wifi.radio.ipv4_address))

Still same error.

Connecting to WiFi...
✅ Wifi!
Traceback (most recent call last):
  File "code.py", line 28, in <module>
  File "/lib/adafruit_httpserver/server.py", line 186, in serve_forever
  File "/lib/adafruit_httpserver/server.py", line 227, in start
  File "/lib/adafruit_httpserver/server.py", line 205, in _set_socket_level_to_reuse_address
OSError: [Errno 95] EOPNOTSUPP

@studiostephe
Copy link
Author

studiostephe commented Mar 25, 2024

Interesting update: Since my use case is actually making an AP, I tried this code.py:

import socketpool
import wifi
from adafruit_httpserver import Server, Request, Response, SSEResponse, GET

wifi.radio.start_ap('MyAP')

pool = socketpool.SocketPool(wifi.radio)
server = Server(pool,debug=True)
print(wifi.radio.ap_active)

@server.route("/")
def base(request: Request):
    """
    Serve a default static plain text message.
    """
    return Response(request, "Hello from the CircuitPython HTTP Server!")

server.serve_forever(str(wifi.radio.ipv4_address_ap))

and I modified server.py thusly (starting at line 198):

    def _set_socket_level_to_reuse_address(self) -> None:
        """
        On systems that have SO_REUSEADDR, prevents "Address already in use"
        error when restarting the server.
        """
        try:
            self._sock.setsockopt(
                self._socket_source.SOL_SOCKET, self._socket_source.SO_REUSEADDR, 1
            )
        except Exception as e:
            print("oh nooooo:", e)

and got a successful result despite still having the error:
code.py output:
True
oh nooooo: [Errno 95] EOPNOTSUPP
Started development server on http://192.168.4.1:80

And I can even get a successful response in my browser:
192.168.4.16 -- "GET /" 382 -- "200 OK" 125 -- 236ms

EDIT: that modification to server.py works when joining a network too:
code.py output:
Connecting to Unintended Consequences
Connected to Unintended Consequences
oh nooooo: [Errno 95] EOPNOTSUPP
Started development server on http://10.0.1.53:80
10.0.1.33 -- "GET /" 380 -- "200 OK" 125 -- 126ms

@DJDevon3
Copy link

DJDevon3 commented Mar 25, 2024

I'm getting the same result for that too. Interesting error message lol. Yes I'm seeing the same for joining now too even though I'm using connection manager. :)

I guess the question is why the AttributeError is preventing it from running but a general ExceptionError allows it to somewhat function.

Something a bit weird. I changed password = os.getenv("PASSWORD") which doesn't exist in my settings.toml and it still connected. It's keeping the info in a buffer somewhere or it's not actually connecting to wifi...

Connecting to WiFi...
✅ Wifi!
Started development server on http://xxx.xxx.xxx:80
xxx.xxx.xxx.xxx -- "GET /" 347 -- "200 OK" 125 -- 240ms
xxx.xxx.xxx.xxx -- "GET /favicon.ico" 300 -- "404 Not Found" 131 -- 217ms

Copy/Paste the url of the development server and in my browser I see:
Hello from the CircuitPython HTTP Server!

Pretty sure if I change os.getenv to a non-existent variable it shouldn't work but here we are. Only thing I changed in server.py was line 207 from except AttributeError: to except Exception:

@studiostephe
Copy link
Author

studiostephe commented Mar 25, 2024

I think the way its written in the library, the except only runs if the type of error is AttributeError. Since the type of error actually being thrown is OSError, the error is just raised instead of handled.

As to knowing the actual cause of the error, I'm way out of my depth. I just wanted to display a lil' sensor reading, and I thought this would be faster than hooking up a display 💀

re: non-existent variable- Try power cycling the pico, the wifi hardware might still be connected to your network from a previous run

@DJDevon3
Copy link

DJDevon3 commented Mar 25, 2024

That makes sense, all of it. Thanks.

Yeah it eventually devolves into an e address reuse error.

Traceback (most recent call last):
  File "code.py", line 28, in <module>
  File "/lib/adafruit_httpserver/server.py", line 186, in serve_forever
  File "/lib/adafruit_httpserver/server.py", line 229, in start
OSError: [Errno 112] EADDRINUSE

There's a rabbit hole here best left to someone who like these kinds of rabbit holes.

@anecdata
Copy link
Member

anecdata commented Mar 25, 2024

This should get sorted out in the library. If I understand it correctly, if the .setsockopt call can't be run (by removing it, or catching any exceptions), the server should still work, but there may be edge cases where the server won't be able to restart properly.

@studiostephe
Copy link
Author

studiostephe commented Mar 25, 2024

Out of curiosity when did you see the [Errno 112] EADDRINUSE? I can hit the server a bunch of times successfully:

10.0.1.33 -- "GET /" 380 -- "200 OK" 125 -- 63ms
10.0.1.33 -- "GET /" 380 -- "200 OK" 125 -- 190ms
10.0.1.33 -- "GET /" 380 -- "200 OK" 125 -- 95ms
10.0.1.33 -- "GET /" 380 -- "200 OK" 125 -- 44ms
10.0.1.33 -- "GET /" 380 -- "200 OK" 125 -- 144ms
and lots more...

also thanks for the help so far everybody :)

@DJDevon3
Copy link

When I hit file save in Mu. Apparently once you get the server up and running you shouldn't attempt to save code.py again? If I leave the server running yes I can refresh the browser as many times as I want without issue.

@anecdata
Copy link
Member

anecdata commented Mar 25, 2024

Looks like this is more than (or not at all) a library issue... I don't think SO_REUSEADDR is implemented in the raspberrypi port

int common_hal_socketpool_socket_setsockopt(socketpool_socket_obj_t *self, int level, int optname, const void *value, size_t optlen) {

Probably the testing so far has been with espressif

@dhalbert
Copy link
Collaborator

#9084 is meant to fix this. Could you someone test the build artifact? Thanks.
https://github.com/adafruit/circuitpython/actions/runs/8421732172?pr=9084

@dhalbert dhalbert added this to the 9.0.x milestone Mar 25, 2024
@DJDevon3
Copy link

DJDevon3 commented Mar 26, 2024

Linked Pi Pico W Artifact works with 9.x bundle library. No need to change AttributeError to Exception anymore. Can soft-reload without issue. Simpletests work. Good fix. 👍

Also perhaps worth noting. I got it working with Connection Manager. This brings up the question do we want to update the adafruit_httpserver simpletest examples with Connection Manager?

import os

import adafruit_connection_manager
import wifi

from adafruit_httpserver import Server, Request, Response

# Get WiFi details, ensure these are setup in settings.toml
ssid = os.getenv("CIRCUITPY_WIFI_SSID")
password = os.getenv("CIRCUITPY_WIFI_PASSWORD")

print("Connecting to WiFi...")
wifi.radio.connect(ssid, password)
print("✅ Wifi!")

# Initalize Wifi, Socket Pool, Request Session
pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio)
server = Server(pool, "/static", debug=True)


@server.route("/")
def base(request: Request):
    """Serve a default static plain text message"""
    return Response(request, "Hello from the CircuitPython HTTP Server!")


server.serve_forever(str(wifi.radio.ipv4_address))

@anecdata
Copy link
Member

anecdata commented Mar 26, 2024

Closing, fixed by #9084

Dan commented in another thread that adafruit_httpserver does not need to use ConnectionManager (though I am a little murky on what's best if the project has a mix of HTTPServer + ConnectionManager-based network clients)

@dhalbert
Copy link
Collaborator

Dan commented in another thread that adafruit_httpsrver does not need to use ConnectionManager.

I meant that the actual adafruit_httpserver library does not need ConnectionManager internally. But it's fine and useful to use ConnectionManager in a test program. ConnectionManager makes it easy to get the socketpool and the ssl context.

If you want to open a new PR to use ConnectionManager in a simpletest or other example, I think that's a fine idea.

@anecdata
Copy link
Member

anecdata commented Mar 26, 2024

Thanks for clarifying, Dan (I inferred something not implied)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants