Skip to content
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

[Feature Request] Asynchronous HTTP Requests using asyncio #134

Open
ZeAntwan opened this issue Jun 7, 2023 · 17 comments
Open

[Feature Request] Asynchronous HTTP Requests using asyncio #134

ZeAntwan opened this issue Jun 7, 2023 · 17 comments

Comments

@ZeAntwan
Copy link

ZeAntwan commented Jun 7, 2023

Hi!
I'm looking for a way using circuit python, or even micro python to do an HTTP request asynchronously so I can keep updating a display or an led strip while I wait for some information to come back.
I'm new to CircuitPython (or MicroPython in general really) but though this would be a great addition to this library.
As mentioned on Discord, it might be some feature enhancement, or some example code using the asyncio library to handle the request correctly.

There might be some caveats (like if executing request in an order, not getting the responses in the same order), but it feels like a good first step anyway, or something people can build upon?

And I know a fork with asyncio support exists, but is very obsolete now (https://github.com/dhalbert/Adafruit_CircuitPython_Requests/tree/async) relying on old data type that I believed got deprecated as part of the circuitpython firmware update, or just general improvements, but maybe there is something there to work from?

@tekktrik
Copy link
Member

tekktrik commented Jun 7, 2023

The decision was made previously that a new library should be made for handling async requests, as opposed to adding to or modifying this library. A preliminary pull request containing some of the work that kicked this off is #118 but hasn't been updated in a while. If you'd like to help move it along, that'd be great!

@czei
Copy link

czei commented Mar 14, 2024

I need the same feature! The LED scroller I'm building freezes when the API calls take longer than 1/2 a second. You'd think that on a platform that doesn't support multithreading, there would at least be an asynchronous I/O library.

@justmobilize
Copy link
Collaborator

I wonder if the whole library needs to be async, or just the response... If it was just the response, it would be a pretty easy add...

@czei
Copy link

czei commented Mar 14, 2024

I wonder if the whole library needs to be async, or just the response... If it was just the response, it would be a pretty easy add...

You'd think. I'm new to Python, but the streams part of the asyncio library might be able to read from a socket.

https://docs.circuitpython.org/projects/asyncio/en/latest/

@dhalbert
Copy link
Contributor

You'd think. I'm new to Python, but the streams part of the asyncio library might be able to read from a socket.

I think is it true in MicroPython, and we may inherit this automically, but I don't really know.

@anecdata
Copy link
Member

anecdata commented Mar 14, 2024

There is this adafruit/circuitpython#7173 but with a comment that "needs further changes in asyncio module itself"

I did test out separating request from response quite a while back, but reading the first "H" from the response is part of the request currently in CircuitPython (but not in CPython). We need a way to know if there is data waiting, without reading?

@justmobilize
Copy link
Collaborator

I thought the H was just to determine if we got back a valid response. In theory we could also break up request() into some separate helper methods so we can easily make it async

@anecdata
Copy link
Member

anecdata commented Mar 14, 2024

right, it's just that it muddles the separation of request and response currently ...peek is what I was thinking of to know if the response is ready (ESP32SPI has socket_available, and WIZnet has _available, but I don't think there is a native equivalent currently)

@justmobilize
Copy link
Collaborator

So to me there are 3 problems:

  1. Connecting to the host. None of that code is async, and can take up to 10 seconds
  2. Sending a payload, right now it's just send this large blob. That should iterate in chunks with a yield
  3. Getting data, this again would yield between each chunk

The part that's hard is it's always dependent on network, so even if we break it all up, latency will cause issues. Since CP isn't multi-threaded

I personally would add the changes in this repo, in a way that fixing a bug fixes it in both ways

@anecdata
Copy link
Member

Yes, connection (particularly TLS) can take non-trivial time

sending in current CP requests is a bunch of small sends, but obv. not async

then there is the distinct interval between end of send to start of data available (there is a CPython socket.MSG_PEEK, but I have no idea what's involved in implementing it - but maybe non-blocking read is enough)

then highly-variable read times depending on what kind of data and all of the environment characteristics (server, network, etc.)

@justmobilize
Copy link
Collaborator

I'm happy to work on this if there is a consensus of how it would be best implemented

@czei
Copy link

czei commented Mar 15, 2024

I've never used this API, but a cursory reading indicates there's at least a chance that reading the HTTP response could be implemented:

https://docs.circuitpython.org/en/latest/shared-bindings/socketpool/index.html

The socketpool.Socket class has a method setblocking() that isn't well defined, but theoretically would cause the reads and writes to be non-blocking. You would think the recv methods would then return zero bytes read if no data was available. Better documentation would spell out exactly how each of the methods of that class would function with each blocking state, but that's no atypical for documentation.

Anyway, it looks like there's a chance to implement asynchronous HTTP calls outside of the CircuitPython http_requests library.

@anecdata
Copy link
Member

settimeout:

timeout in seconds. 0 means non-blocking. None means block indefinitely.

I think setblocking is a little redundant: True is like settimeout = None, and False is like settimeout = 0

unsuccessful non-blocking reads won't return any data, they'll raise an exception, depending on modes... EAGAIN, ETIMEOUT, etc.

@czei
Copy link

czei commented Mar 15, 2024

settimeout:

timeout in seconds. 0 means non-blocking. None means block indefinitely.

I think setblocking is a little redundant: True is like settimeout = None, and False is like settimeout = 0

unsuccessful non-blocking reads won't return any data, they'll raise an exception, depending on modes... EAGAIN, ETIMEOUT, etc.

Thanks! Where is this documented? I didn't see anything on the man pages.

@czei
Copy link

czei commented Mar 15, 2024

https://docs.circuitpython.org/en/latest/shared-bindings/socketpool/index.html#socketpool.Socket.settimeout https://docs.circuitpython.org/en/latest/shared-bindings/socketpool/index.html#socketpool.Socket.setblocking (and the exceptions... just my recollection, not definitive)

Ok, that's embarrassing: I just didn't scroll down far enough. Even so, I can't figure out what the documentation is trying to say. The codes are obviously the ANSI C standard, but there's no indication how they're being used in this context. They're listed right below the constructor for socketpool.SocketPool(), so how are we supposed to know that the I/O methods in socketpool.Socket will throw exceptions with those error numbers?

Sorry if this is obvious.

@anecdata
Copy link
Member

I'm not really sure that's documented anywhere in CircuitPython-land, other than in the core code. The idea is that CircuitPython sockets should behave like (a subset of) CPython sockets, but even the CPython docs don't make it clear.

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

No branches or pull requests

6 participants