Giter Club home page Giter Club logo

micropython-nanoweb's Introduction

Nanoweb

Nanoweb is a full asynchronous web server for micropython created in order to benefit from a correct ratio between memory size and features.

It is thus able to run on an ESP8266, ESP32, Raspberry Pico, etc...

Features

  • Completely asynchronous
  • Declaration of routes via a dictionary or directly by decorator
  • Management of static files (see assets_extensions)
  • Callbacks functions when a new query or an error occurs
  • Extraction of HTML headers
  • User code dense and conci
  • Routing wildcards

Installation

You just have to copy the nanoweb.py file on the target (ESP32, Nano, etc...).

Use

See the example.py file for an advanced example where you will be able to:

  • Make a JSON response
  • Use pages protected with credentials
  • Upload file
  • Use DELETE method
  • Read POST data

And this is a simpler example:

import uasyncio
from nanoweb import Nanoweb

naw = Nanoweb()

async def api_status(request):
    """API status endpoint"""
    await request.write("HTTP/1.1 200 OK\r\n")
    await request.write("Content-Type: application/json\r\n\r\n")
    await request.write('{"status": "running"}')

# You can declare route from the Nanoweb routes dict...
naw.routes = {
    '/api/status': api_status,
}

# ... or declare route directly from the Nanoweb route decorator
@naw.route("/ping")
async def ping(request):
    await request.write("HTTP/1.1 200 OK\r\n\r\n")
    await request.write("pong")

loop = asyncio.get_event_loop()
loop.create_task(naw.run())
loop.run_forever()

Contribute

micropython-nanoweb's People

Contributors

hugokernel avatar puhitaku avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

micropython-nanoweb's Issues

Unable to get the examples working

Am able to get the webserver running without errors.
However, unable to load any pages.

running your example.py, with all example assets copied across, as well as nanoweb.py.

No error in the thonny repl, but page doesn't load, with 'connection error', see screenshot:

nanoweb-connection-refused

GET and POST methods?

How to use GET method with parameters and POST method?
Any example to implement http methods?

Connection Reset error when responding to request after reading part of request

On an ESP8266 with Micropython 1.22 and the latest NanoWeb as of this submission.

Consider this endpoint

@Server.route("/test")
async def test(request):
    if request.method != "POST":
        await request.write(
            f"HTTP/1.0 405 Method Not Allowed\r\n\r\nMethod Not Allowed"
        )
        return

    header = await request.read(4)

    if header == b"abcd":
        print("200")
        await request.write(f"HTTP/1.0 200 OK\r\n\r\nOK")
    else:
        print("415")
        await request.write(
            f"HTTP/1.0 415 Unsupported Media Type\r\n\r\nUnsupported Media Type"
        )

A file would be uploaded, but before accepting all of the contents the first four bytes would be read as the file header. When put into use in the real world this would continue reading chunks of the uploaded file until it is consumed, then responding to the request, but for now it just returns a pass/fail type of response.

When uploading 4 correct bytes it works as expected:

$ curl -X POST -d "abcd" 192.168.1.133/test
OK

When uploading four incorrect bytes, or fewer than four, it works as expected:

$ curl -X POST -d "wxyz" 192.168.1.133/test
Unsupported Media Type

$ curl -X POST -d "abc" 192.168.1.133/test
Unsupported Media Type

However, when submitting more than four bytes, leaving the stream partially read, the error occurs:

$ curl -X POST -d "abcdwxyz" 192.168.1.133/test
curl: (56) Recv failure: Connection was reset

Any value in the payload that is larger than 4 bytes will cause this error. It seems like unread data is a problem. If I read all of the bytes (eg by lazily adding a _ = await response.read(4) after the first read then sending 8 bytes) and discard them the response passes normally, but that of course defeats part of the reason I want to read the file header before continuing.

Another issue, style sheet doesn't load fully through your server

Another issue, I have bootstrap.min.css in the same folder as my html - html as below. The browser downloads the style sheet but is not able to render it, see attached screen shot - this shows the file has downloaded in 6 seconds (which itself is too long for 195K file) but if you see it has only a size of 115KB - there seems to be some data loss hence the style sheet has not been rendered.

This is the page rendered when accessed through your webserver - which is not loading the style sheet
image

This is the page when loaded directly from the hardrive (offline) see the bootstrap stylesheet file size

image

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Home1</title>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <link rel="stylesheet" href="styles.css" />
    <link rel="stylesheet" href="bootstrap.min.css"/>
   <script" src="js/bootstrap.bundle.min.js"/>	

  </head>
  <body>
    <div class="wrapper">
      <div class="logo">
        <img
          src="images/testlogo.png"
          alt=""
        />
      </div>
      <div class="text-center mt-4 name">Switch Board Login</div>
      <form class="p-3 mt-3">
        <div class="form-field d-flex align-items-center">
          <input
            type="password"
            name="password"
            id="pwd"
            placeholder="Password"
          />
        </div>
        <button class="btn mt-3">Login</button>
      </form>
      
    </div>
  </body>
</html>

Originally posted by @krishnak in #19 (comment)

Energy efficiency

I deployed plain nanoweb on a micro-controller and I'm observing it takes a lot of power while being idle, it continuously varies from 5 to 20 mA. How to make it more energy efficient?

OSError: [Errno 5] EIO Task exception wasn't retrieved

Trying to run the example on a Pi PICO W and I can't get anything to load after the authentication.
This shows in the Thonny Shell:

Task exception wasn't retrieved
future: <Task> coro= <generator object 'handle' at 20031800>
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "nanoweb.py", line 166, in handle
  File "nanoweb.py", line 37, in send_file
  File "uasyncio/stream.py", line 1, in stream_awrite
  File "uasyncio/stream.py", line 1, in write
OSError: [Errno 5] EIO

If I restart and try ip:8001/ping I get the pong response

Memory allocation error

hi,
i am trying to use you lib, but i get some strange behavior, because when i try to extend the Nanoweb class i get memory allocation error. Also when i try to import the class i get the same error.
i am using: MicroPython v1.20.0 on 2023-04-26; ESP module with ESP8266

anyone have encountered this problem?

Thanks!

Not an issue, just wanted to say a big thank you for putting this together, it's really neat.

Expending the request handler (Feature request)

Im currently working on a project where I run a wifi manager and allow the user to setup wifi through and access point. All good so far until I try to read the credentials from the POST request, I see that in the async def handle(self, reader, writer): that the only data that is handled from the reader is the method and the url.

I started working on expending that function to read the full request into a bytearray and handle more data. Would you be interested to add it in if I send a PR ? I dont mean to go super in dept and handle advanced http stuff (Im no HTTP expert anyway).

Thank you so much for creating and maintaining this project, I tried all of them out there and they are either too expensive memorywise or not up to date with the latest asyncio

cheers

Add configurable static file folder

I know you want to keep nanoweb as small as possible, but would it be possible to add a property for specifying the location of static files besides having it only look in the current folder?

Or, for example, is there a way to reroute static files so that /* reroutes to /sd/static/*?

support sending gzipped files?

I can send you a pull request because I already implemented this. Not sure if you'd be interested in it though.

I'm putting a react/tailwind app on my ESP01. Space is limited. I've found I can compress the built javascript file from 130k down to 42k with gzip. I modified nanoweb to first look for the requested file but with .gz appended then send the file with the Content-Encoding and Content-Type. Then all I need to do is upload app.js.gz rather than app.js and everything works.

Obviously this only works if you know all the browsers connecting to your device will Accept-Encoding: gzip I could check for it but I won't have the unzipped version anyway.

My changes add about 1k (41 lines) to the nanoweb file. Almost all of that is the annoying content-type code.

How to upload ?

i'm new bit, Download file is perfect, but how to upload file to ESP32 , How can I send PUT Method from example.html ?

POST method implementation issues

Hi this is more of a query than an issue with your code

In your nanoweb.py code you have


async def handle(self, reader, writer):
        items = await reader.readline()
        items = items.decode('ascii').split()
        if len(items) != 3:
            return

        request = Request()
        request.read = reader.read
        request.write = writer.awrite
        request.close = writer.aclose

When I make request from two different clients. I expect the request object to be empty whenever a new request arrives. However I am able to access the request.header of the first client when I am processing the request of the second client.

I am not new to programming or OO, only new to python. So I am making an assertion based on wisdom than my python syntax, that the Request class should be like


class Request:
    def __init__(self):
        self.url = ""
        self.method = ""
        self.headers = {}
        self.route = ""
        self.read = None
        self.write = None
        self.close = None

I am setting a Cookie in one client, however when the second client makes a request it doesn't send a cookie because it is hitting the server for the first time, however when I do a request.header - it has the cookie of the other client.

Only with the above change, the request object doesn't get shared across multiple clients.

Please feel free to share your thoughts

Possible updates for send_file() function

I made a few additions to the send_file() function in my local copy of nanoweb.py for your consideration (but feel free to close and ignore):

  1. Added content-type header
  2. Added content-length header
  3. Changed to a reusable bytearray buffer for the file read loop to be more predictable with memory usage

I also increased the read chunk size 8-fold to speed up the transfer on larger files, but that's more subjective. Maybe make that chunk size user configurable?

I've thrown some image files and large JavaScript files (React bundle) at it, and while not fast, it seems reasonably reliable for what it is. Here's the end result of what I've been using:

content_types = dict(
    html='text/html',
    css='text/css',
    js='application/javascript',
    json='application/json',
    jpg='image/jpeg',
    ico='image/x-icon',
    default='text/plain',
    )

async def send_file(request, filename):
    content_type = content_types.get(filename.split('.')[-1], content_types['default'])
    file_size = os.stat(filename)[6]

    await request.write("HTTP/1.1 200 OK\r\n")
    await request.write("Content-Type: {}\r\n".format(content_type))
    await request.write("Content-Length: {}\r\n\r\n".format(file_size))

    buf = bytearray(512)
    try:
        with open(filename, "r") as f:
            while True:
                bytes = f.readinto(buf)
                if bytes == 0:
                    break
                await request.write(buf[:bytes])
    except OSError as e:
        if e.args[0] != uerrno.ENOENT:
            raise
        raise HttpError(request, 404, "File Not Found")
    finally:
        del buf
        gc.collect()

example.py missing base64_decode() reference

I'm running this on an ESP8266 and the example.py module seems to be missing a reference to the base64_decode() function. Is there a missing import in the module or does it use a different uPy port? I defined the function myself and it seemed to be working after that:

import ubinascii

def base64_decode(val):
    return ubinascii.a2b_base64(val)

Unable to access files in subfolders

If I have a subfolder, inside the top folder as a webserver it should be able to pick things up, it doesn't. Has this been tested for scenarios other than the given one level directory structure.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.