{"id":16131912,"url":"https://github.com/uberi/biplane","last_synced_at":"2025-08-16T15:17:25.943Z","repository":{"id":162816072,"uuid":"637305526","full_name":"Uberi/biplane","owner":"Uberi","description":"Minimal, fast, robust HTTP server library for Python/CircuitPython that uses non-blocking concurrent I/O even when asyncio isn't available!","archived":false,"fork":false,"pushed_at":"2025-02-25T04:05:07.000Z","size":34,"stargazers_count":16,"open_issues_count":2,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-09T19:48:34.458Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Uberi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-05-07T06:19:19.000Z","updated_at":"2025-03-27T15:35:16.000Z","dependencies_parsed_at":"2024-10-27T18:39:05.687Z","dependency_job_id":null,"html_url":"https://github.com/Uberi/biplane","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/Uberi/biplane","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Uberi%2Fbiplane","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Uberi%2Fbiplane/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Uberi%2Fbiplane/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Uberi%2Fbiplane/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Uberi","download_url":"https://codeload.github.com/Uberi/biplane/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Uberi%2Fbiplane/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270729221,"owners_count":24635386,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-16T02:00:11.002Z","response_time":91,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-09T22:28:15.540Z","updated_at":"2025-08-16T15:17:25.910Z","avatar_url":"https://github.com/Uberi.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"Biplane\n=======\n\nBiplane is an HTTP server library for Python/CircuitPython.\n\nCompared to common alternatives such as [Ampule](https://github.com/deckerego/ampule/), [circuitpython-native-wsgiserver](https://github.com/Neradoc/circuitpython-native-wsgiserver/), and [Adafruit_CircuitPython_HTTPServer](https://github.com/adafruit/Adafruit_CircuitPython_HTTPServer/), it has several unique features:\n\n* **Non-blocking concurrent I/O**: can process multiple requests at the same time, even when `async`/`await`/`asyncio` isn't available!\n    * While circuitpython-native-wsgiserver does non-blocking I/O as well, it performs certain operations in a blocking loop, making soft-realtime use difficult (e.g. displaying animations, driving motors).\n    * To make this work without `asyncio`, we expose the entire server as a generator, where each step of the generator is O(1).\n* **More performant**: 10ms per request on a 160MHz ESP32C3, thanks to buffered reads/writes and avoiding common issues such as bytes concatenation.\n    * Comparable to blocking I/O servers such as Ampule and Adafruit_CircuitPython_HTTPServer.\n    * Much faster than non-blocking I/O servers such as circuitpython-native-wsgiserver, which can take up to 100ms per request on a 160MHz ESP32C3 due to 1-byte recv() calls.\n* **More robust**: correctly handles binary data, overly-large paths/headers/requests, too-slow/dropped connections, etc.\n    * Strictly bounds untrusted input size during request processing using the `max_request_line_size` and `max_body_bytes` settings.\n    * Strictly bounds request processing time using the `request_timeout_seconds` setting.\n    * Correctly handles unusual cases such as binary data, dropped connections with no TCP RST, and incomplete writes from the client.\n* **Smaller**: single-file implementation with ~200 SLOC.\n    * Around the same size as Ampule, and much smaller than the other options.\n* **Few dependencies**: relies only on the `time` and `errno` libraries, both of which are built into Python/CircuitPython (as well as `wifi`, `mdns`, and `socketpool` if using the WiFi helpers).\n\nHowever, compared to those libraries, it intentionally doesn't include some features in order to keep the codebase small:\n\n* Helpers for parsing query parameters and dealing with URL encoding/decoding.\n* Helpers for building HTTP responses, such as header formatting, templating, and more.\n* Helpers for dealing with MIME types (Adafruit_CircuitPython_HTTPServer has this).\n* Support for chunked transfer encoding (Adafruit_CircuitPython_HTTPServer has this).\n* Support for serving static files (Adafruit_CircuitPython_HTTPServer has this).\n\nInstallation\n------------\n\n### Python\n\nInstall via Pip:\n\n```sh\npip install biplane\n```\n\n### CircuitPython\n\nTo install Biplane using CircUp, ensure you have set it up according to the [Adafruit CircUp guide](https://learn.adafruit.com/keep-your-circuitpython-libraries-on-devices-up-to-date-with-circup). Then:\n\n```sh\ncircup install biplane\n```\n\nFor CircuitPython devices that don't support the CIRCUITPY drive used to upload code, you can instead manually upload `biplane.py` from this folder to `lib/biplane.py` on the board using one of the following methods:\n\n* Using the Web Workflow via Bluetooth or WiFi. See the [AdaFruit Web Workflow guide](https://learn.adafruit.com/circuitpython-with-esp32-quick-start/setting-up-web-workflow) for more details.\n* Using [Thonny](https://thonny.org/), which supports uploading code to CircuitPython.\n* As a last-resort slow-but-simple option, using the CircuitPython REPL that you can access over the serial port:\n    1. Run `python3 -c 'f=open(\"biplane.py\");code=f.read();print(f\"code={repr(code)};open(\\\"lib/biplane.py\\\",\\\"w\\\").write(code) if len(code)=={len(code)} else print(\\\"CODE CORRUPTED\\\")\")'` in this folder, and copy the output of that command to the clipboard. This output is CircuitPython code that creates `lib/biplane.py` with the correct contents inside.\n    2. Paste the copied output into the CircuitPython REPL and run it. If it outputs \"CODE CORRUPTED\", that means the code changed between when you pasted it and when it arrived in CircuitPython, which means your serial terminal is sending the characters too quickly and CircuitPython can't keep up (common when using `screen` or `minicom`); to fix this, configure your terminal to wait 2ms-4ms after sending each character and try again (2ms is usually good enough). Also, make sure that you do this after freshly resetting the board.\n\nLastly, Biplane is part of the [CircuitPython Community Bundle](https://circuitpython.org/libraries), so if you have that installed, then you already have Biplane installed too.\n\nExamples\n--------\n\n### Basic example (CircuitPython)\n\nStarts a WiFi network called \"test\" (password is \"some_password\") - when connected, you can see a Hello World page at `http://app.local/` (tested on an ESP32C3):\n\n```python\nimport biplane\n\nserver = biplane.Server()\n\n@server.route(\"/\", \"GET\")\ndef main(query_parameters, headers, body):\n  return biplane.Response(\"\u003cb\u003eHello, world!\u003c/b\u003e\", content_type=\"text/html\")\n\nfor _ in server.circuitpython_start_wifi_ap(\"test\", \"some_password\", \"app\"):\n  pass\n```\n\n### Basic example (Python)\n\nStarts a server that displays a Hello World page at `http://localhost:8000`, similar to the CircuitPython example above:\n\n```python\nimport socket\nimport biplane\n\nserver = biplane.Server()\n\n@server.route(\"/\", \"POST\")\ndef main(query_parameters, headers, body):\n  return biplane.Response(\"\u003cb\u003eHello, world!\u003c/b\u003e\", content_type=\"text/html\")\n\nserver_socket = socket.socket()\nserver_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # allow the server to reuse the address immediately after it's been closed\nfor _ in server.start(server_socket, listen_on=('127.0.0.1', 8000)):\n  pass\n```\n\nThe usage is almost exactly the same, but we pass in a socket from the Python `socket` library instead of from CircuitPython's `socketpool` library.\n\n### Parallel execution (CircuitPython)\n\nBlinks an LED consistently at ~100Hz while serving HTTP requests, keeping a ~100Hz frequency regardless of how quickly HTTP requests are coming in:\n\n```python\nimport time\nimport board\nimport digitalio\nimport biplane\n\nserver = biplane.Server()\n\n@server.route(\"/\", \"GET\")\ndef main(query_parameters, headers, body):\n  return biplane.Response(\"\u003cb\u003eHello, world!\u003c/b\u003e\", content_type=\"text/html\")\n\ndef asyncio_sleep(seconds):  # minimal implementation of asyncio.sleep() as a generator\n  start_time = time.monotonic()\n  while time.monotonic() - start_time \u003c seconds:\n    yield\n\ndef blink_builtin_led():\n  with digitalio.DigitalInOut(board.LED) as led:\n    led.switch_to_output(value=False)\n    while True:\n      led.value = not led.value\n      yield from asyncio_sleep(0.01)\n\nfor _ in zip(blink_builtin_led(), server.circuitpython_start_wifi_ap(\"test\", \"some_password\", \"app\")):  # run through both generators at the same time using zip()\n  pass\n```\n\nWith other HTTP servers, blinking the LED while serving requests would either be impossible, or would become inconsistent when many HTTP requests are coming in.\n\nNote that CircuitPython's GC pauses may cause occasional longer pauses - to mitigate this, run `import gc; gc.collect()` at regular, predictable intervals, so that the GC never has to be invoked at unpredictable times.\n\n### Parallel execution with async/await (CircuitPython)\n\nMany CircuitPython implementations, especially those for boards with less RAM/flash, don't include the `asyncio` library. However, if `asyncio` is available, Biplane works well with it as well:\n\n```python\nimport asyncio\nimport board\nimport digitalio\nimport biplane\n\nserver = biplane.Server()\n\n@server.route(\"/\", \"GET\")\ndef main(query_parameters, headers, body):\n  return biplane.Response(\"\u003cb\u003eHello, world!\u003c/b\u003e\", content_type=\"text/html\")\n\nasync def run_server():\n  for _ in server.circuitpython_start_wifi_ap(\"test\", \"some_password\", \"app\")\n    await asyncio.sleep(0)  # let other tasks run\n\nasync def blink_builtin_led():\n  with digitalio.DigitalInOut(board.LED) as led:\n    led.switch_to_output(value=False)\n    while True:\n      led.value = not led.value\n      await asyncio.sleep(0.01)\n\nasyncio.run(asyncio.gather(blink_builtin_led(), run_server()))  # run both coroutines at the same time\n```\n\nEssentially, we just need to loop through the generator as usual while calling `await asyncio.sleep(0)` each iteration to let other tasks run.\n\nTo join an existing wifi network you can use `server.circuitpython_start_wifi_station(ssid, password, hostname)` instead of the call `server.circuitpython_start_wifi_ap` shown in the examples above.\n\nDevelopment\n-----------\n\nAll of the application code lives in `biplane.py`. Run tests using `python3 tests/test_basic.py`.\n\nLicense\n-------\n\nCopyright 2023 [Anthony Zhang (Uberi)](http://anthonyz.ca).\n\nThe source code is available online at [GitHub](https://github.com/Uberi/biplane).\n\nThis program is made available under the MIT license. See ``LICENSE.txt`` in the project's root directory for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fuberi%2Fbiplane","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fuberi%2Fbiplane","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fuberi%2Fbiplane/lists"}