{"id":13541952,"url":"https://github.com/belyalov/tinyweb","last_synced_at":"2025-04-06T10:13:05.898Z","repository":{"id":28108414,"uuid":"115854511","full_name":"belyalov/tinyweb","owner":"belyalov","description":"Simple and lightweight HTTP async server for micropython","archived":false,"fork":false,"pushed_at":"2024-05-14T17:04:49.000Z","size":195,"stargazers_count":263,"open_issues_count":19,"forks_count":42,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-03-30T08:12:00.851Z","etag":null,"topics":["cats","cats-effect","esp32","esp8266","http-server","iot","micropython","rest-api","restful","web-server"],"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/belyalov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","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":"2017-12-31T09:31:35.000Z","updated_at":"2025-03-28T01:18:29.000Z","dependencies_parsed_at":"2024-05-02T20:56:51.812Z","dependency_job_id":"cf02cdec-03f7-4683-b4bb-eb548f151d1b","html_url":"https://github.com/belyalov/tinyweb","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/belyalov%2Ftinyweb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/belyalov%2Ftinyweb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/belyalov%2Ftinyweb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/belyalov%2Ftinyweb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/belyalov","download_url":"https://codeload.github.com/belyalov/tinyweb/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247464222,"owners_count":20942970,"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","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":["cats","cats-effect","esp32","esp8266","http-server","iot","micropython","rest-api","restful","web-server"],"created_at":"2024-08-01T10:00:59.308Z","updated_at":"2025-04-06T10:13:05.862Z","avatar_url":"https://github.com/belyalov.png","language":"Python","readme":"## TinyWeb [![Build Status](https://travis-ci.org/belyalov/tinyweb.svg?branch=master)](https://travis-ci.org/belyalov/tinyweb)\nSimple and lightweight (thus - *tiny*) HTTP server for tiny devices like **ESP8266** / **ESP32** running [micropython](https://github.com/micropython/micropython).\nHaving simple HTTP server allows developers to create nice and modern UI for their IoT devices.\nBy itself - *tinyweb* is just simple TCP server running on top of `uasyncio` - library for micropython, therefore *tinyweb* is single threaded server.\n\n### Features\n* Fully asynchronous when using with [uasyncio](https://github.com/micropython/micropython-lib/tree/v1.0/uasyncio) library for MicroPython.\n* [Flask](http://flask.pocoo.org/) / [Flask-RESTful](https://flask-restful.readthedocs.io/en/latest/) like API.\n* *Tiny* memory usage. So you can run it on devices like **ESP8266 / ESP32** with 64K/96K of onboard RAM. BTW, there is a huge room for optimizations - so your contributions are warmly welcomed.\n* Support for static content serving from filesystem.\n* Great unittest coverage. So you can be confident about quality :)\n\n### Requirements\n\n* [logging](https://github.com/micropython/micropython-lib/tree/master/python-stdlib/logging)\n\nOn MicroPython \u003c1.13:\n\n* [uasyncio](https://github.com/micropython/micropython-lib/tree/v1.0/uasyncio) - micropython version of *async* python library.\n* [uasyncio-core](https://github.com/micropython/micropython-lib/tree/v1.0/uasyncio.core)\n\n### Quickstart\nThe easist way to try it - is using pre-compiled firmware for ESP8266 / ESP32.\nInstructions below are tested with *NodeMCU* devices. For any other devices instructions could be a bit different, so keep in mind.\n**CAUTION**: If you proceed with installation all data on your device will **lost**!\n\n#### Installation - ESP8266\n* Download latest `firmware_esp8266-version.bin` from [releases](https://github.com/belyalov/tinyweb/releases).\n* Install `esp-tool` if you haven't done already: `pip install esptool`\n* Erase flash: `esptool.py --port \u003cUART PORT\u003e --baud 256000 erase_flash`\n* Flash firmware: `esptool.py --port \u003cUART PORT\u003e --baud 256000 write_flash -fm dio 0 firmware_esp8266-v1.3.2.bin`\n\n#### Installation - ESP32\n* Download latest `firmware_esp32-version.bin` from [releases](https://github.com/belyalov/tinyweb/releases).\n* Install `esp-tool` if you haven't done already: `pip install esptool`\n* Erase flash: `esptool.py --port \u003cUART PORT\u003e --baud 256000 erase_flash`\n* Flash firmware: `esptool.py --port \u003cUART PORT\u003e --baud 256000 write_flash -fm dio 0x1000 firmware_esp32-v1.3.2.bin`\n\n#### Hello world\nLet's develop [Hello World](https://github.com/belyalov/tinyweb/blob/master/examples/hello_world.py) web app:\n```python\nimport tinyweb\n\n\n# Create web server application\napp = tinyweb.webserver()\n\n\n# Index page\n@app.route('/')\nasync def index(request, response):\n    # Start HTTP response with content-type text/html\n    await response.start_html()\n    # Send actual HTML page\n    await response.send('\u003chtml\u003e\u003cbody\u003e\u003ch1\u003eHello, world! (\u003ca href=\"/table\"\u003etable\u003c/a\u003e)\u003c/h1\u003e\u003c/html\u003e\\n')\n\n\n# Another one, more complicated page\n@app.route('/table')\nasync def table(request, response):\n    # Start HTTP response with content-type text/html\n    await response.start_html()\n    await response.send('\u003chtml\u003e\u003cbody\u003e\u003ch1\u003eSimple table\u003c/h1\u003e'\n                        '\u003ctable border=1 width=400\u003e'\n                        '\u003ctr\u003e\u003ctd\u003eName\u003c/td\u003e\u003ctd\u003eSome Value\u003c/td\u003e\u003c/tr\u003e')\n    for i in range(10):\n        await response.send('\u003ctr\u003e\u003ctd\u003eName{}\u003c/td\u003e\u003ctd\u003eValue{}\u003c/td\u003e\u003c/tr\u003e'.format(i, i))\n    await response.send('\u003c/table\u003e'\n                        '\u003c/html\u003e')\n\n\ndef run():\n    app.run(host='0.0.0.0', port=8081)\n\n```\nSimple? Let's try it!\nFlash your device with firmware, open REPL and type:\n```python\n\u003e\u003e\u003e import network\n\n# Connect to WiFi\n\u003e\u003e\u003e sta_if = network.WLAN(network.STA_IF)\n\u003e\u003e\u003e sta_if.active(True)\n\u003e\u003e\u003e sta_if.connect('\u003cssid\u003e', '\u003cpassword\u003e')\n\n# Run Hello World! :)\n\u003e\u003e\u003e import examples.hello_world as hello\n\u003e\u003e\u003e hello.run()\n```\n\nThat's it! :) Try it by open page `http://\u003cyour ip\u003e:8081`\n\nLike it? Check more [examples](https://github.com/belyalov/tinyweb/tree/master/examples) then :)\n\n### Limitations\n* HTTP protocol support - due to memory constrains only **HTTP/1.0** is supported (with exception for REST API - it uses HTTP/1.1 with `Connection: close`). Support of HTTP/1.1 may be added when `esp8266` platform will be completely deprecated.\n\n### Reference\n#### class `webserver`\nMain tinyweb app class.\n\n* `__init__(self, request_timeout=3, max_concurrency=None)` - Create instance of webserver class.\n    * `request_timeout` - Specifies timeout for client to send complete HTTP request (without HTTP body, if any), after that connection will be closed. Since `uasyncio` has very short queue (about 42 items) *Avoid* using values \u003e 5 to prevent events queue overflow.\n    * `max_concurrency` - How many connections can be processed concurrently. It is very important to limit it mostly because of memory constrain. Default value depends on platform, **3** for `esp8266`, **6** for `esp32` and **10** for others.\n    * `backlog` - Parameter to socket.listen() function. Defines size of pending to be accepted connections queue. Must be greater than `max_concurrency`.\n    * `debug` - Whether send exception info (text + backtrace) to client together with HTTP 500 or not.\n\n* `add_route(self, url, f, **kwargs)` - Map `url` into function `f`. Additional keyword arguments are supported:\n    * `methods` - List of allowed methods. Defaults to `['GET', 'POST']`\n    * `save_headers` - Due to memory constrains you most likely want to minimze memory usage by saving only headers\n    which you really need in. E.g. for POST requests it is make sense to save at least 'Content-Length' header.\n    Defaults to empty list - `[]`.\n    * `max_body_size` - Max HTTP body size (e.g. POST form data). Be careful with large forms due to memory constrains (especially with esp8266 which has 64K RAM). Defaults to `1024`.\n    * `allowed_access_control_headers` - Whenever you're using xmlHttpRequest (send JSON from browser) these headers are required to do access control. Defaults to `*`\n    * `allowed_access_control_origins` - The same idea as for header above. Defaults to `*`.\n\n* `@route` - simple and useful decorator (inspired by *Flask*). Instead of using `add_route()` directly - just decorate your function with `@route`, like this:\n    ```python\n    @app.route('/index.html')\n    async def index(req, resp):\n        await resp.send_file('static/index.simple.html')\n    ```\n* `add_resource(self, cls, url, **kwargs)` - RestAPI: Map resource class `cls` to `url`. Class `cls` is arbitrary class with with implementation of HTTP methods:\n    ```python\n    class CustomersList():\n        def get(self, data):\n            \"\"\"Return list of all customers\"\"\"\n            return {'1': {'name': 'Jack'}, '2': {'name': 'Bob'}}\n\n        def post(self, data):\n            \"\"\"Add customer\"\"\"\n            db[str(next_id)] = data\n        return {'message': 'created'}, 201\n    ```\n  `**kwargs` are optional and will be passed to handler directly.\n    **Note**: only `GET`, `POST`, `PUT` and `DELETE` methods are supported. Check [restapi full example](https://github.com/belyalov/tinyweb/blob/master/examples/rest_api.py) as well.\n\n* `@resource` - the same idea as for `route` but for resource:\n    ```python\n    # Regular version\n    @app.resource('/user/\u003cid\u003e')\n    def user(data, id):\n        return {'id': id, 'name': 'foo'}\n\n    # Generator based / different HTTP method\n    @app.resource('/user/\u003cid\u003e', method='POST')\n    async def user(data, id):\n        yield '{'\n        yield '\"id\": \"{}\",'.format(id)\n        yield '\"name\": \"test\",'\n        yield '}'\n    ```\n\n* `run(self, host=\"127.0.0.1\", port=8081, loop_forever=True, backlog=10)` - run web server. Since *tinyweb* is fully async server by default it is blocking call assuming that you've added other tasks before.\n    * `host` - host to listen on\n    * `port` - port to listen on\n    * `loop_forever` - run `async.loop_forever()`. Set to `False` if you don't want `run` to be blocking call. Be sure to call `async.loop_forever()` by yourself.\n    * `backlog` - size of pending connections queue (basically argument to `listen()` function)\n\n* `shutdown(self)` - gracefully shutdown web server. Meaning close all active connections / server socket and cancel all started coroutines. **NOTE** be sure to it in event loop or run event loop at least once, like:\n    ```python\n    async def all_shutdown():\n        await asyncio.sleep_ms(100)\n\n    try:\n        web = tinyweb.webserver()\n        web.run()\n    except KeyboardInterrupt as e:\n        print(' CTRL+C pressed - terminating...')\n        web.shutdown()\n        uasyncio.get_event_loop().run_until_complete(all_shutdown())\n    ```\n\n\n#### class `request`\nThis class contains everything about *HTTP request*. Use it to get HTTP headers / query string / etc.\n***Warning*** - to improve memory / CPU usage strings in `request` class are *binary strings*. This means that you **must** use `b` prefix when accessing items, e.g.\n\n    \u003e\u003e\u003e print(req.method)\n    b'GET'\n\nSo be sure to check twice your code which interacts with `request` class.\n\n* `method` - HTTP request method.\n* `path` - URL path.\n* `query_string` - URL path.\n* `headers` - `dict` of saved HTTP headers from request. **Only if enabled by `save_headers`.\n    ```python\n    if b'Content-Length' in self.headers:\n        print(self.headers[b'Content-Length'])\n    ```\n\n* `read_parse_form_data()` - By default (again, to save CPU/memory) *tinyweb* doesn't read form data. You have to call it manually unless you're using RESTApi. Returns `dict` of key / value pairs.\n\n#### class `response`\nUse this class to generate HTTP response. Please be noticed that `response` class is using *regular strings*, not binary strings as `request` class does.\n\n* `code` - HTTP response code. By default set to `200` which means OK, no error.\n* `version` - HTTP version. Defaults to `1.0`. Please be note - that only HTTP1.0 is internally supported by `tinyweb`. So if you changing it to `1.1` - be sure to support protocol by yourself.\n* `headers` - HTTP response headers dictionary (key / value pairs).\n* `add_header(self, key, value)` - Convenient way to add HTTP response header\n    * `key` - Header name\n    * `value` - Header value\n\n* `add_access_control_headers(self)` - Add HTTP headers required for RESTAPI (JSON query)\n\n* `redirect(self, location)` - Generate HTTP redirection (HTTP 302 Found) to `location`. This *function is coroutine*.\n\n* `start_html(self)`- Start response with HTML content type. This *function is coroutine*. This function is basically sends response line and headers. Refer to [hello world example](https://github.com/belyalov/tinyweb/blob/master/examples/hello_world.py).\n\n* `send(self, payload)` - Sends your string/bytes `payload` to client. Be sure to start your response with `start_html()` or manually. This *function is coroutine*.\n\n* `send_file(self, filename)`: Send local file as HTTP response. File type will be detected automatically unless you explicitly change it. If file doesn't exists - HTTP Error `404` will be generated.\nAdditional keyword arguments\n    * `content_type` - MIME filetype. By default - `None` which means autodetect.\n    * `content_encoding` - Specifies used compression type, e.g. `gzip`. By default - `None` which means don't add this header.\n    * `max_age` - Cache control. How long browser can keep this file on disk. Value is in `seconds`. By default - 30 days. To disable caching, set it to `0`.\n\n* `error(self, code)` - Generate HTTP error response with error `code`. This *function is coroutine*.\n","funding_links":[],"categories":["Libraries","Frameworks for Micropython"],"sub_categories":["Communications","More"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbelyalov%2Ftinyweb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbelyalov%2Ftinyweb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbelyalov%2Ftinyweb/lists"}