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: secure WebSocket (wss) #34

Closed
beyonlo opened this issue Feb 3, 2022 · 60 comments
Closed

Feature request: secure WebSocket (wss) #34

beyonlo opened this issue Feb 3, 2022 · 60 comments
Assignees
Labels
enhancement New feature or request

Comments

@beyonlo
Copy link

beyonlo commented Feb 3, 2022

Hello!

Congratulations for the great project.

I would like to know if you have intention to support secure WebSocket (use SSL over WebSocket) on the Microdot.

Thank you.

@beyonlo beyonlo changed the title Feature request: secure Web socket (wss) Feature request: secure WebSocket (wss) Feb 3, 2022
@miguelgrinberg
Copy link
Owner

SSL and WebSocket are completely independent features. I have the intention to investigate adding SSL support. I wasn't considering websocket, but it's not out of the question either. My concern is that with each addition the size of the package will continue to grow and the lower-end microprocessors will not be able to run anymore, so I'll have to think about doing this in a way that doesn't affect the smaller platforms.

@miguelgrinberg miguelgrinberg added the enhancement New feature or request label Feb 3, 2022
@miguelgrinberg miguelgrinberg self-assigned this Feb 3, 2022
@beyonlo
Copy link
Author

beyonlo commented Feb 3, 2022

SSL and WebSocket are completely independent features. I have the intention to investigate adding SSL support. I wasn't considering websocket, but it's not out of the question either. My concern is that with each addition the size of the package will continue to grow and the lower-end microprocessors will not be able to run anymore, so I'll have to think about doing this in a way that doesn't affect the smaller platforms.

Well, the microdot webserver/framework do not support HTTPS (SSL over HTTP) right? I think, even lower-end microcontrollers (like as ESP32-C3) would be great a secure connection over browser to configure/operate. So, if you add SSL on HTTP, the websocket can be the same way - I think that SSL is what will use more memory, right? Well, already exists a official module uwebsocket used by REPL. Here has a official uwebsocket module example (https://github.com/micropython/micropython/blob/master/extmod/webrepl/webrepl.py) but unfortunately do not use the uasyncio, but maybe that can help to no increase the microdot so much (using already exists uwebsocket module instead create your own).
And already exists a HTTP Server official example running with SSL https://github.com/micropython/micropython/blob/master/examples/network/http_server_ssl.py but that not works with uasyncio too, unfortunately.

Well, my intention (my application) is to put in ESP32 works a secure HTTPS Server (to serve pages) + secure WebSocket Server, both running on uasyncio. I posted in MicroPython page, a time ago, a issue looking for a Simple example of uasyncio WebSocket Server (secure - with SSL) micropython/micropython#8177

I think that most work to support ssl + asyncio / websockets was done, in many PRs, like this PR shows micropython/micropython#5611

Thank you in advance!

@miguelgrinberg
Copy link
Owner

I believe uwebsocket does not work with uasyncio. But in general I agree, I'm open to add support for both SSL and WS.

@beyonlo
Copy link
Author

beyonlo commented Feb 3, 2022

I believe uwebsocket does not work with uasyncio. But in general I agree, I'm open to add support for both SSL and WS.

Hey @miguelgrinberg

That's will be great :)

I see that you have two microdot on /src/:
microdot.py for people that want to use with thread
microdot_asyncio.py to works with uasyncio/asyncio

Well, just as suggestion, maybe a option is have more options, so user can to choose what want:
microdot_https.py Secure WebServer
microdot_https_asyncio.py Secure WebServer uasyncio/asyncio
microdot_https_wss.py Secure WebServer + secure WebSocket Server
microdot_https_wss_asyncio.py Secure WebServer + secure WebSocket Server uasyncio/asyncio

Particularly I liked so much uasyncio/asyncio :)

Going forward, with this feature in microdot is possible great applications. I can have the mobile app, desktop app and web app (microdot serving) all working with ESP32 using the same protocol, the websocket. That is great not just because is the same protocol, but is bidirectional protocol. Actually, using just http request on webserver, I need to refresh every 1-5s the page to have new data from my sensors. With websockets that polling is not more necessary. That was just a example, but I think that secure WebServer + secure WebSocket Server + uasyncio/asyncio will be an amazing feature to provide great applications/products.

Thank you so much!

@miguelgrinberg
Copy link
Owner

Update: WebSocket support is coming in the next release of Microdot. Will investigate SSL support after that.

@beyonlo
Copy link
Author

beyonlo commented Aug 15, 2022

Update: WebSocket support is coming in the next release of Microdot. Will investigate SSL support after that.

@miguelgrinberg That is a amazing news!!! I'm very interested!!

Actually I started to use this project https://github.com/marcidy/micropython-uasyncio-webexample to have a HTTP Server and a Websocket Server. My intention is to have just one HTML page with an entire large web application using javaScript (Jquery), exactly as that project works. This way is good because all processing will be on Client side (browser), not in the Microcontroller. As the my application has no access to the internet, is needed to put inside HTTP Server the JQuery lib do use on that unique page, exactly as that project works as well. Look here https://github.com/marcidy/micropython-uasyncio-webexample/tree/main/www

Will be possible to works in that way (just one page.html and put JQuery lib inside HTTP Server) on the Microdot as well?

Thank you very much.

@miguelgrinberg
Copy link
Owner

Will be possible to works in that way (just one page.html and put JQuery lib inside HTTP Server) on the Microdot as well?

You can already serve static files with Microdot. I have added an example of how to do this just a few days ago: https://github.com/miguelgrinberg/microdot/tree/main/examples/static.

And for websocket it will be just a normal route. This isn't finished code, but if you want to have a look, here is a WebSocket echo example: https://github.com/miguelgrinberg/microdot/blob/websocket/examples/websocket/echo_async.py

@beyonlo
Copy link
Author

beyonlo commented Aug 15, 2022

You can already serve static files with Microdot. I have added an example of how to do this just a few days ago: https://github.com/miguelgrinberg/microdot/tree/main/examples/static.

Excellent! That will solve the use case to serve images, JQuery lib and others kind off static files! :)

And for websocket it will be just a normal route. This isn't finished code, but if you want to have a look, here is a WebSocket echo example: https://github.com/miguelgrinberg/microdot/blob/websocket/examples/websocket/echo_async.py

Great, very clean with uasyncio! Congrats!

On the next Microdot release, do you have plans do provide together to the WebSocket Server feature a HTML page (index.html) with an WebSocket Client example running on the browser? Like as a start point (ready to go)!

As soon the next version is released I will start to use Microdot! :)

@miguelgrinberg
Copy link
Owner

an WebSocket Client example running on the browser?

Here is one from another project of mine: https://github.com/miguelgrinberg/flask-sock/blob/main/examples/templates/index.html. I did not test it, but I think it should directly work with the echo example I linked above.

@beyonlo
Copy link
Author

beyonlo commented Aug 15, 2022

an WebSocket Client example running on the browser?

Here is one from another project of mine: https://github.com/miguelgrinberg/flask-sock/blob/main/examples/templates/index.html. I did not test it, but I think it should directly work with the echo example I linked above.

Very good. I will test that, thanks :)

Will investigate SSL support after that.

To support SSL on HTTP Server and WebSocket Server will be amazing. I don't know if you know, but recently was created a ticket (micropython/micropython#8915) that has a relation with this feature on Microdot - maybe help.

@beyonlo
Copy link
Author

beyonlo commented Aug 16, 2022

Hi @miguelgrinberg

I tested the WebSocket branch using this WebSocket client example (https://github.com/miguelgrinberg/flask-sock/blob/main/examples/templates/index.html) and works very well!

However after I finished the test, I observed an error on the terminal. That error do not stopped the send/receive data from the browser (WebSocket Client) test, and I can't to reproduce that error anymore. I will paste here this test with the error, that maybe you can investigate if is really an error or not.

websocket_async.py example (a merge from hello_async.py and echo_async.py):

from microdot_asyncio import Microdot
from microdot_asyncio_websocket import websocket

app = Microdot()

htmldoc = '''<!doctype html>
<html>
  <head>
    <title>Flask-Sock Demo</title>
  </head>
  <body>
    <h1>Flask-Sock Demo</h1>
    <p>Type <b>close</b> to end the connection.</p>
    <div id="log"></div>
    <br>
    <form id="form">
      <label for="text">Input: </label>
      <input type="text" id="text" autofocus>
    </form>
    <script>
      const log = (text, color) => {
        document.getElementById('log').innerHTML += `<span style="color: ${color}">${text}</span><br>`;
      };

      const socket = new WebSocket('ws://' + location.host + '/echo');
      socket.addEventListener('message', ev => {
        log('<<< ' + ev.data, 'blue');
      });
      socket.addEventListener('close', ev => {
        log('<<< closed');
      });
      document.getElementById('form').onsubmit = ev => {
        ev.preventDefault();
        const textField = document.getElementById('text');
        log('>>> ' + textField.value, 'red');
        socket.send(textField.value);
        textField.value = '';
      };
    </script>
  </body>
</html>
'''

@app.route('/echo')
@websocket
async def echo(request, ws):
    c = 0
    while True:
        data = await ws.receive()
        print('Received data from client: {} - type is: {}'.format(data, type(data)))
        data = '{}_{}'.format(data, c)
        await ws.send(data)
        c += 1


@app.route('/')
async def hello(request):
    return htmldoc, 200, {'Content-Type': 'text/html'}


@app.route('/shutdown')
async def shutdown(request):
    request.app.shutdown()
    return 'The server is shutting down...'


app.run(debug=True)

Output terminal:

$ mpremote run websocket_async.py 
Starting async server on 0.0.0.0:5000...
GET / 200
Received data from client: s - type is: <class 'str'>
Received data from client: a - type is: <class 'str'>
Received data from client: asdas - type is: <class 'str'>
Received data from client: asdasd - type is: <class 'str'>
Received data from client: close - type is: <class 'str'>
Received data from client: freeee - type is: <class 'str'>
Task exception wasn't retrieved
future: <Task> coro= <generator object 'serve' at 3fcafef0>
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "microdot_asyncio.py", line 261, in serve
  File "microdot_asyncio.py", line 321, in handle_request
  File "microdot_asyncio.py", line 154, in write
  File "uasyncio/stream.py", line 1, in stream_awrite
  File "uasyncio/stream.py", line 1, in drain
OSError: [Errno 104] ECONNRESET
Received data from client: asda - type is: <class 'str'>
Received data from client: asaaa - type is: <class 'str'>
Received data from client: a - type is: <class 'str'>
Received data from client: a - type is: <class 'str'>
Received data from client: a - type is: <class 'str'>
Received data from client: a - type is: <class 'str'>
Received data from client: aasdasd - type is: <class 'str'>
Received data from client: as - type is: <class 'str'>
Received data from client: das - type is: <class 'str'>
Received data from client: d - type is: <class 'str'>
Received data from client: asd - type is: <class 'str'>
Received data from client: as - type is: <class 'str'>
Received data from client: close - type is: <class 'str'>

Output Browser:
websocket_browser

@beyonlo
Copy link
Author

beyonlo commented Aug 16, 2022

@miguelgrinberg I did tests with the example above (websocket_async.py) to check the compatibility with different browsers.

Works: Chrome, Chromium, Microsoft Edge and Opera.
Do not works: Firefox (errors below).

I have not tested others browsers, just these above.

Terminal output for Firefox test:


$ mpremote run websocket_async.py 
Starting async server on 0.0.0.0:5000...
GET / 200
Traceback (most recent call last):
  File "microdot_asyncio.py", line 353, in dispatch_request
  File "microdot_asyncio.py", line 410, in _invoke_handler
  File "microdot_asyncio_websocket.py", line 93, in wrapper
  File "microdot_asyncio_websocket.py", line 69, in websocket_upgrade
  File "microdot_asyncio_websocket.py", line 7, in handshake
  File "microdot_websocket.py", line 50, in _handshake_response
NameError: name 'abort' isn't defined
GET /favicon.ico 404
GET /echo 500

Firefox browser output:

firefox

Thank you.

@miguelgrinberg
Copy link
Owner

Thanks, this is useful feedback.

@beyonlo
Copy link
Author

beyonlo commented Aug 17, 2022

Hello @miguelgrinberg

I did some more tests.

  1. More browsers tested: Chrome for Android and Safari for Iphone (IOS). Both works.
  2. I'm using ESP32-S3 with MicroPython 1.19.1, and all tests above (in others reply) was done via WiFi, using STA mode. Now I tested the ESP32-S3 as AP mode, and sometimes (not always) show this errors:

Ps1: one kind of error here is the same found in the reply above (that as running as STA mode). Others errors are a bit different.
Ps2: one time was needed to stop the Microdot and start it again because client (browser) was not capable anymore to open the IP and Port of the AP (http://192.168.4.1:5000). I do not restarted the WiFi on the ESP32-S3 (AP mode), just restarted the Microdot and and all back to works. I understand that Microdot is transparent of network, but is something that can be done to fix that?

Received data from client: S - type is: <class 'str'>
Received data from client: D - type is: <class 'str'>
Received data from client: D - type is: <class 'str'>
Received data from client: D - type is: <class 'str'>
Traceback (most recent call last):
  File "microdot_asyncio.py", line 353, in dispatch_request
  File "microdot_asyncio.py", line 410, in _invoke_handler
  File "microdot_asyncio_websocket.py", line 95, in wrapper
  File "<stdin>", line 49, in echo
  File "microdot_asyncio_websocket.py", line 17, in receive
  File "microdot_asyncio_websocket.py", line 36, in _read_frame
  File "uasyncio/stream.py", line 1, in read
OSError: [Errno 104] ECONNRESET
Task exception wasn't retrieved
future: <Task> coro= <generator object 'serve' at 3fcc0400>
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "microdot_asyncio.py", line 261, in serve
  File "microdot_asyncio.py", line 321, in handle_request
  File "microdot_asyncio.py", line 139, in write
  File "uasyncio/stream.py", line 1, in stream_awrite
  File "uasyncio/stream.py", line 1, in drain
OSError: [Errno 104] ECONNRESET
GET / 200
GET / 200
GET /echo 200
GET /echo 200
Received data from client: Hhh - type is: <class 'str'>
Received data from client: JSON - type is: <class 'str'>
Traceback (most recent call last):
  File "microdot_asyncio.py", line 353, in dispatch_request
  File "microdot_asyncio.py", line 410, in _invoke_handler
  File "microdot_asyncio_websocket.py", line 95, in wrapper
  File "<stdin>", line 49, in echo
  File "microdot_asyncio_websocket.py", line 17, in receive
  File "microdot_asyncio_websocket.py", line 36, in _read_frame
  File "uasyncio/stream.py", line 1, in read
OSError: [Errno 104] ECONNRESET
Task exception wasn't retrieved
future: <Task> coro= <generator object 'serve' at 3fcae620>
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "microdot_asyncio.py", line 261, in serve
  File "microdot_asyncio.py", line 321, in handle_request
  File "microdot_asyncio.py", line 139, in write
  File "uasyncio/stream.py", line 1, in stream_awrite
  File "uasyncio/stream.py", line 1, in drain
OSError: [Errno 104] ECONNRESET
GET / 200
Received data from client: F - type is: <class 'str'>
Received data from client: G - type is: <class 'str'>
GET / 200
Received data from client: H - type is: <class 'str'>
Received data from client: JSON - type is: <class 'str'>
Traceback (most recent call last):
  File "microdot_asyncio.py", line 353, in dispatch_request
  File "microdot_asyncio.py", line 410, in _invoke_handler
  File "microdot_asyncio_websocket.py", line 95, in wrapper
  File "<stdin>", line 49, in echo
  File "microdot_asyncio_websocket.py", line 17, in receive
  File "microdot_asyncio_websocket.py", line 36, in _read_frame
  File "uasyncio/stream.py", line 1, in read
OSError: [Errno 104] ECONNRESET
Task exception wasn't retrieved
future: <Task> coro= <generator object 'serve' at 3fcbdbb0>
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "microdot_asyncio.py", line 261, in serve
  File "microdot_asyncio.py", line 321, in handle_request
  File "microdot_asyncio.py", line 139, in write
  File "uasyncio/stream.py", line 1, in stream_awrite
  File "uasyncio/stream.py", line 1, in drain
OSError: [Errno 104] ECONNRESET
Traceback (most recent call last):
  File "microdot_asyncio.py", line 315, in handle_request
  File "microdot_asyncio.py", line 69, in create
  File "microdot_asyncio.py", line 110, in _safe_readline
  File "uasyncio/stream.py", line 1, in readline
OSError: [Errno 104] ECONNRESET
Traceback (most recent call last):
  File "microdot_asyncio.py", line 353, in dispatch_request
  File "microdot_asyncio.py", line 410, in _invoke_handler
  File "microdot_asyncio_websocket.py", line 95, in wrapper
  File "<stdin>", line 49, in echo
  File "microdot_asyncio_websocket.py", line 17, in receive
  File "microdot_asyncio_websocket.py", line 36, in _read_frame
  File "uasyncio/stream.py", line 1, in read
OSError: [Errno 104] ECONNRESET
Task exception wasn't retrieved
future: <Task> coro= <generator object 'serve' at 3fcb4320>
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "microdot_asyncio.py", line 261, in serve
  File "microdot_asyncio.py", line 321, in handle_request
  File "microdot_asyncio.py", line 139, in write
  File "uasyncio/stream.py", line 1, in stream_awrite
  File "uasyncio/stream.py", line 1, in drain
OSError: [Errno 104] ECONNRESET
Task exception wasn't retrieved
future: <Task> coro= <generator object 'serve' at 3fcb68d0>
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "microdot_asyncio.py", line 261, in serve
  File "microdot_asyncio.py", line 321, in handle_request
  File "microdot_asyncio.py", line 139, in write
  File "uasyncio/stream.py", line 1, in stream_awrite
  File "uasyncio/stream.py", line 1, in drain
OSError: [Errno 104] ECONNRESET



@miguelgrinberg
Copy link
Owner

miguelgrinberg commented Aug 17, 2022

Looks like all the errors are caused by not handling the ECONNRESET error. I did not notice this error in my tests, it might be that they are used by the ES32 implementation, which I did not test. I'll look into handling this error properly.

@Carglglz
Copy link

Hi @beyonlo @miguelgrinberg
As a part of a ongoing development effort in #8968 to bring SSLContext to MicroPython, I've done some test with microdot and so far the _thread version works (I've done tests in UNIX and ESP32 ports), using python requests (with verify=False since I'm using self-signed certs) and curl.
Here is the diff to microdot.py:

git diff --staged src/*
diff --git a/src/microdot.py b/src/microdot.py
index 5b9e77e..48149a1 100644
--- a/src/microdot.py
+++ b/src/microdot.py
@@ -51,6 +51,8 @@ except ImportError:
     except ImportError:  # pragma: no cover
         socket = None

+import ssl
+

 def urldecode(string):
     string = string.replace('+', ' ')
@@ -847,7 +849,7 @@ class Microdot():
         """
         raise HTTPException(status_code, reason)

-    def run(self, host='0.0.0.0', port=5000, debug=False):
+    def run(self, host='0.0.0.0', port=5000, debug=False, key=None, cert=None):
         """Start the web server. This function does not normally return, as
         the server enters an endless listening loop. The :func:`shutdown`
         function provides a method for terminating the server gracefully.
@@ -882,7 +884,11 @@ class Microdot():
         self.server = socket.socket()
         ai = socket.getaddrinfo(host, port)
         addr = ai[0][-1]
-
+        ctx = None
+        if key:
+            ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+            ctx.load_cert_chain(cert,
+                                keyfile=key)
         if self.debug:  # pragma: no cover
             print('Starting {mode} server on {host}:{port}...'.format(
                 mode=concurrency_mode, host=host, port=port))
@@ -893,12 +899,17 @@ class Microdot():
         while not self.shutdown_requested:
             try:
                 sock, addr = self.server.accept()
+                if key:
+                    ssl_sock = ctx.wrap_socket(sock, server_side=True)
             except OSError as exc:  # pragma: no cover
                 if exc.errno == errno.ECONNABORTED:
                     break
                 else:
                     raise
-            create_thread(self.handle_request, sock, addr)
+            if key:
+                create_thread(self.handle_request, ssl_sock, addr, ctx)
+            else:
+                create_thread(self.handle_request, sock, addr, ctx)

     def shutdown(self):
         """Request a server shutdown. The server will then exit its request
@@ -927,7 +938,7 @@ class Microdot():
                     f = 405
         return f

-    def handle_request(self, sock, addr):
+    def handle_request(self, sock, addr, ctx):
         if not hasattr(sock, 'readline'):  # pragma: no cover
             stream = sock.makefile("rwb")
         else:
@@ -955,6 +966,8 @@ class Microdot():
             print('{method} {path} {status_code}'.format(
                 method=req.method, path=req.path,
                 status_code=res.status_code))
+        if hasattr(ctx, 'reset'):
+            ctx.reset()

     def dispatch_request(self, req):
         if req:

And the hello_tls_context.py example I use for testing:

from microdot import Microdot

app = Microdot()

htmldoc = '''<!DOCTYPE html>
<html>
    <head>
        <title>Microdot Example Page</title>
    </head>
    <body>
        <div>
            <h1>Microdot Example Page</h1>
            <p>Hello from Microdot!</p>
            <p><a href="/shutdown">Click to shutdown the server</a></p>
        </div>
    </body>
</html>
'''


@app.route('/')
def hello(request):
    return htmldoc, 200, {'Content-Type': 'text/html'}


@app.route('/shutdown')
def shutdown(request):
    request.app.shutdown()
    return 'The server is shutting down...'

with open('ec-cakey.pem', 'rb') as keyb:
    key = keyb.read()

with open('ec-cacert.pem', 'rb') as certb:
    cert = certb.read()

app.run(debug=True, key=key, cert=cert)

Sadly I don't know when or even if my implementation of SSLContext will be merged.

I will try with microdot asyncio version too, although not sure when exactly will be that.

@miguelgrinberg
Copy link
Owner

miguelgrinberg commented Aug 21, 2022

@beyonlo I have put some fixes based on your reports:

  • Firefox should work now
  • the ECONNRESET error is (hopefully) handled properly
  • an index.html file is now served in all the WebSocket examples when you connect to the root URL
  • preliminary support for websocket connections in the test client (incomplete, more work is needed still)
  • the websocket decorator was renamed to with_websocket for consistency

Let me know if things look better now.

@miguelgrinberg
Copy link
Owner

@Carglglz Thanks for doing this work! This is going to make my work a lot easier when I attempt to do this. Sounds like I might need to use the older (current) SSL implementation though, since it is unclear when or if yours is going to be merged, correct? I also want to see if it is possible to put the SSL support in a separate extension, as I'm doing with WebSocket and other features, so that it does not continue to make the main server larger.

@beyonlo
Copy link
Author

beyonlo commented Aug 22, 2022

Hello @miguelgrinberg

@beyonlo I have put some fixes based on your reports:

That's great!

  • Firefox should work now

I confirm that it works!

  • the ECONNRESET error is (hopefully) handled properly

Yes, that old errors do not show anymore, thank you. But now I did two other tests and show these errors:

1. Killing the Browser after connected.

Killing the Chrome on Android:
Ps: the error happen every time that browser is killed.

Received data from client: D
Received data from client: D
Received data from client: S
Traceback (most recent call last):
  File "microdot_asyncio.py", line 353, in dispatch_request
  File "microdot_asyncio.py", line 410, in _invoke_handler
  File "microdot_asyncio_websocket.py", line 95, in wrapper
  File "<stdin>", line 17, in echo
  File "microdot_asyncio_websocket.py", line 17, in receive
  File "microdot_asyncio_websocket.py", line 37, in _read_frame
  File "microdot_websocket.py", line 63, in _parse_frame_header
IndexError: bytes index out of range
Task exception wasn't retrieved
future: <Task> coro= <generator object 'serve' at 3fccb1c0>
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "microdot_asyncio.py", line 261, in serve
  File "microdot_asyncio.py", line 321, in handle_request
  File "microdot_asyncio.py", line 145, in write
  File "uasyncio/stream.py", line 1, in stream_awrite
  File "uasyncio/stream.py", line 1, in drain
OSError: [Errno 104] ECONNRESET
Task exception wasn't retrieved
future: <Task> coro= <generator object 'serve' at 3fcca9a0>
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "microdot_asyncio.py", line 261, in serve
  File "microdot_asyncio.py", line 321, in handle_request
  File "microdot_asyncio.py", line 145, in write
  File "uasyncio/stream.py", line 1, in stream_awrite
  File "uasyncio/stream.py", line 1, in drain
OSError: [Errno 104] ECONNRESET
Received data from client: sfsd
Received data from client: f

Killing the Chromium on Ubuntu:
Ps: the error happen just some times.

Received data from client: sd
Received data from client: a
Task exception wasn't retrieved
future: <Task> coro= <generator object 'serve' at 3fcc2450>
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "microdot_asyncio.py", line 261, in serve
  File "microdot_asyncio.py", line 321, in handle_request
  File "microdot_asyncio.py", line 145, in write
  File "uasyncio/stream.py", line 1, in stream_awrite
  File "uasyncio/stream.py", line 1, in drain
OSError: [Errno 104] ECONNRESET
Received data from client: ds
Received data from client: asdasd
Received data from client: a

Killing the Firefox on Ubuntu do not show errors

2. Happened a non-intentional lost network, and it back a few seconds after, and show this error:
Ps: I tried to force network lost connection to reproduce the problem, but not success.

Received data from client: as
Received data from client: d
Received data from client: asd
Task exception wasn't retrieved
future: <Task> coro= <generator object 'serve' at 3fcc9f20>
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "microdot_asyncio.py", line 261, in serve
  File "microdot_asyncio.py", line 321, in handle_request
  File "microdot_asyncio.py", line 145, in write
  File "uasyncio/stream.py", line 1, in stream_awrite
  File "uasyncio/stream.py", line 1, in drain
OSError: [Errno 104] ECONNRESET
Received data from client: ada
  • an index.html file is now served in all the WebSocket examples when you connect to the root URL

Excellent!

  • preliminary support for websocket connections in the test client (incomplete, more work is needed still)

I never did/used unittests, but I will to study/check how it works and start to use the test_microdot_websocket.py as well in my tests. I think need just to instance the TestMicrodotWebSocket class and call the test_websocket_echo method, right? I checked that import unittest do not is builtin by default on MicroPython, but I see that there is the unittest for MicroPython.

  • the websocket decorator was renamed to with_websocket for consistency

All right!

Additional comments:

  1. I just noticed that this new version are not showing anymore IP:PORT that Microdot are running - show nothing. In the last version was showing something like as Running at 0.0.0.0:5000.
  2. All tests above was done using the new echo_async.py on the ESP32-S3 running MicroPython 1.19.1. The ESP32-S3 was running as AP mode and my Notebook (Ubuntu) and my smartphone (Android) as STA mode, connected to ESP32-S3.
   ESP32-S3 - 192.168.4.1
   Notebook - 192.168.4.2
   Smartphone - 192.168.4.3

@miguelgrinberg
Copy link
Owner

@beyonlo I added fixes for the new errors. Thanks!

@raven703
Copy link

raven703 commented Aug 22, 2022

Confirm, that ECONNRESET has been fixed !
I`m using server side events in my ESP32 project and it was spamming in local each time browser reloads page forcing Microdot to stop serve pages.
Now everything is fine, thank you :)

@beyonlo
Copy link
Author

beyonlo commented Aug 22, 2022

@miguelgrinberg I confirm that is working without ECONNRESET errors.

Thank you!

@beyonlo
Copy link
Author

beyonlo commented Aug 23, 2022

Hi @miguelgrinberg

Could you please to provide a simple example a bit different than echo_async.py using asyncio as well? I mean, where the WebSocket Server is capable to send data to the WebSocket Client without the WebSocket Client send a request (like as the echo example), and where the WebSocket Server is capable as well to select (choose) for what WebSocket Clients to send the data? Just a example as start point.

I would like to have many WebSockets Clients (from browsers and from Desktop/Mobile), simultaneously connected do the WebSocket Server and the clients will be stayed connected. So the WebSocket Server can receive/send data from/to all them, but for some critical data (like as alarms/events), for example, the WebSocket Server need to send the data just for some specific WebSocket Clients.

Thank you in advance!

@miguelgrinberg
Copy link
Owner

miguelgrinberg commented Aug 23, 2022

@beyonlo I don't have any examples, but all you need to do is store the ws objects for all your clients in some sort of data structure (maybe a dict), so that you can access them when you need to send something to one or more clients. The route can take care of receiving data from clients, but for sending you can send from anywhere, as long as you have access to the ws object.

@beyonlo
Copy link
Author

beyonlo commented Aug 23, 2022

@beyonlo I don't have any examples, but all you need to do is store the ws objects for all your clients in some sort of data structure (maybe a dict), so that you can access them when you need to send something to one or more clients. The route can take care of receiving data from clients, but for sending you can send from anywhere, as long as you have access to the ws object.

@miguelgrinberg with this explanation now I understand how to do it - I will to try, thanks!

In my tests I found more errors. I don't know when that errors happened and I was no capable to reproduce. Note that the second error (ENOTCONN) is different from all others errors reported before.

Received data: s
Send data: s -> 9
1070296480 1070296736
Received data: Vvvg
Send data: Vvvg -> 1
1070295920 1070296048
Traceback (most recent call last):
  File "microdot_asyncio.py", line 353, in dispatch_request
  File "microdot_asyncio.py", line 410, in _invoke_handler
  File "microdot_asyncio_websocket.py", line 101, in wrapper
  File "microdot_asyncio_websocket.py", line 33, in close
  File "microdot_asyncio_websocket.py", line 28, in send
  File "uasyncio/stream.py", line 1, in stream_awrite
  File "uasyncio/stream.py", line 1, in drain
OSError: [Errno 104] ECONNRESET
Task exception wasn't retrieved
future: <Task> coro= <generator object 'serve' at 3fcaed40>
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "microdot_asyncio.py", line 261, in serve
  File "microdot_asyncio.py", line 321, in handle_request
  File "microdot_asyncio.py", line 140, in write
  File "uasyncio/stream.py", line 1, in stream_awrite
  File "uasyncio/stream.py", line 1, in drain
OSError: [Errno 128] ENOTCONN


GET / 200
GET /static/jquery-ui.min.css 200
GET /static/jquery-3.6.0.min.js 200
GET /static/jquery-ui-1.13.2.min.js 200
---------------------
<Request object at 3fcb79e0> <WebSocket object at 3fcb7aa0>
<class 'Request'> <class 'WebSocket'>
1070299616 1070299808
---------------------
1070299616 1070299808
Received data: asdsa
Send data: asdsa -> 0
1070299616 1070299808
Received data: asd

Edit:

Happened one more time:

Received data: free
Send data: free -> 76
1070301872 1070302064
Traceback (most recent call last):
  File "microdot_asyncio.py", line 353, in dispatch_request
  File "microdot_asyncio.py", line 410, in _invoke_handler
  File "microdot_asyncio_websocket.py", line 101, in wrapper
  File "microdot_asyncio_websocket.py", line 33, in close
  File "microdot_asyncio_websocket.py", line 28, in send
  File "uasyncio/stream.py", line 1, in stream_awrite
  File "uasyncio/stream.py", line 1, in drain
OSError: [Errno 104] ECONNRESET
Task exception wasn't retrieved
future: <Task> coro= <generator object 'serve' at 3fcac1d0>
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "microdot_asyncio.py", line 261, in serve
  File "microdot_asyncio.py", line 321, in handle_request
  File "microdot_asyncio.py", line 140, in write
  File "uasyncio/stream.py", line 1, in stream_awrite
  File "uasyncio/stream.py", line 1, in drain
OSError: [Errno 128] ENOTCONN

@miguelgrinberg
Copy link
Owner

@beyonlo This is useful, thanks. I think I've fixed these new errors now.

@miguelgrinberg
Copy link
Owner

@beyonlo @Carglglz TLS/SSL support is now committed to the main branch. Couple of examples here: https://github.com/miguelgrinberg/microdot/tree/main/examples/tls.

Would appreciate it if you test it and provide feedback. Note that the async support cannot currently be used with MicroPython, since uasyncio does not have the necessary support.

@Carglglz
Copy link

Carglglz commented Sep 5, 2022

@miguelgrinberg I've just tested and it works both in CPython and current MicroPython UNIX port 👍🏼 . This is what I meant, once SSLContext is implemented in MicroPython changes to microdot_ssl.py should be minimal or even non-existent (apart from code deprecation as you have already indicated)

@beyonlo
Copy link
Author

beyonlo commented Sep 5, 2022

@miguelgrinberg I tested on the ESP32-S3 with MicroPython 1.19.1 (same of all others tests) but do not works. When I try to access via browser, Microdot stop and show errors. Follow the details:

$ mpremote run hello_tls.py 
Starting sync server on 0.0.0.0:4443...
Traceback (most recent call last):
  File "<stdin>", line 36, in <module>
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
$ mpremote ls
ls :
         139 boot.py
        1493 cert.der
        2375 key.der
       37277 microdot.py
       16463 microdot_asyncio.py
        3780 microdot_asyncio_websocket.py
        2362 microdot_ssl.py
        5935 microdot_websocket.py

Chromium output:
Screenshot from 2022-09-05 13-39-52

Firefox output:
Screenshot from 2022-09-05 13-39-57

@Carglglz
Copy link

Carglglz commented Sep 5, 2022

@beyonlo This is expected as you are using self signed certificates, to make it work with your web browser you need to add the self signed certificate to your system trusted certificates. Look for adding self signed certificate to chrome or whatever browser you are using.

@beyonlo
Copy link
Author

beyonlo commented Sep 5, 2022

@Carglglz yes, you are correct. I added that exception and works - screenshot below. But the Microdot do not should to stop in that exception, I believe.

@miguelgrinberg it is already possible as well to use Secure WebSocket (wss), os just the HTTPS for now?

Screenshot from 2022-09-05 18-29-55

@Carglglz
Copy link

Carglglz commented Sep 5, 2022

@miguelgrinberg based on your feedback I've modified my implementation of SSLContext, and now the context is automatically regenerated after the handshake (so it can wrap new sockets) and also note that in CPython context.wrap_socket can wrap connected or not connected sockets, which I tried to support as well, and in UNIX port so far so good, but I'm not sure if the extra code worth it in an embedded platform.
With these modifications, tests and your hello_tls.py example are working nice.

I have to try them in ESP32 port now...

[EDIT]
Works on ESP32 port too but the CPython context.wrap_socket feature on not connected sockets its limited and unstable, I cannot get more than 4 in a row... maybe its better to enforce connect first since CPython already supports this.

@beyonlo
Copy link
Author

beyonlo commented Sep 5, 2022

@miguelgrinberg this is unfortunately the case at least in current ESP32 port, there is only enough memory to have one SSLSocket at a time. (I do have a custom ESP32 implementation where I can have up to two SSLSockets but I haven't tested doing multiple requests at a time there.)

@Carglglz Is true that will be not possible to have more than 1 Secure WebSocket using the ESP32-S3?

Actually I'm using the Microdot WebSocket Server, with an intention to have many WebSocket Clients (Browsers and Desktop applications), all them simultaneously connected and communicating with the Microdot WebSocket Server running on the ESP32-S3 - that is working.

My intention is after that HTTPS/wss works, I can just upgrade to support secure Websockets in that scenario of multi WebSockets.

I need to use the ESP32-S3 without PSRAM for special needs, but even the ESP32-S3 with just internal RAM, it has a large Free memory (~170KB):

>>> micropython.mem_info()
stack: 704 out of 15360
GC: total: 168704, used: 1584, free: 167120
 No. of 1-blocks: 27, 2-blocks: 8, max blk sz: 18, max free sz: 10432
>>> 

I checked that for each WebSocket Client connected (persistent connection) to the Microdot WebSocket Server, is allocated around ~2.2KB of RAM. I have 8 WebSockets clients connected to Microdot WebSocket Server using total of ~18KB RAM (just for that 8 persistent connections).

Sorry, but when you say that the ESP32 has only enough memory to have one secure Webocket (SSLSocket) at a time, I imagine that one Secure WebSocket connection will use more than 50/100KB. Is that realistic?

Thank you in advance!

@miguelgrinberg
Copy link
Owner

@beyonlo Correct, this should not stop Microdot. If it does, then that is an exception that I'm not catching, I'll look into that. Regarding WebSocket, I think it should work, but I have not tested wss yet.

@Carglglz I did see that you cannot wrap the main socket in MicroPython. This fake SSL context class that I built allows you to do it, but it is handled internally by delaying the actual call to wrap_socket until after a client socket is available.

@Carglglz
Copy link

Carglglz commented Sep 5, 2022

@beyonlo SSL use HEAP memory not RAM memory from MicroPython, there is some work to be able to support usage of RAM memory too so it can support multiple SSL sockets, check #8940

@miguelgrinberg yes, I've tried to do the same with mixed results in ESP32 port, for server socket it does work at expense of increasing handshake time, and for client sockets I cannot get more than 4 handshakes in a row...

[EDIT]
I've modified my tests and it seems to be working on ESP32, reusing the context instead of creating new instances does the trick in client side 👍🏼.

For server side it's still better to enforce connect first since CPython already supports this, and it saves a considerable amount of memory since it does not create additional objects. Also it's far more stable.

@beyonlo
Copy link
Author

beyonlo commented Sep 5, 2022

@beyonlo SSL use HEAP memory not RAM memory from MicroPython, there is some work to be able to support usage of RAM memory too so it can support multiple SSL sockets, check #8940

@Carglglz understood! Thank you for the explanation.

@Carglglz
Copy link

Carglglz commented Sep 8, 2022

Update: WebSocket support is coming in the next release of Microdot. Will investigate SSL support after that.

@miguelgrinberg @beyonlo I did a test with WSS and it does work 👍🏼 . It was not too difficult either.

Just modify index.html to use wss

diff --git a/index.html b/../../_local_tests/index.html
index 6e240d5..51d6b86 100644
--- a/index.html
+++ b/../../_local_tests/index.html
@@ -16,7 +16,7 @@
         document.getElementById('log').innerHTML += `<span style="color: ${color}">${text}</span><br>`;
       };

-      const socket = new WebSocket('ws://' + location.host + '/echo');
+      const socket = new WebSocket('wss://' + location.host + '/echo');
       socket.addEventListener('message', ev => {
         log('<<< ' + ev.data, 'blue');
       });

And in MicroPython a fake SSLSocket class with missing send, recv methods and readline that can accept 1 arg.

class sslsocket_class:
    def __init__(self, sock):
        self.sock = sock

    def write(self, buf):
        return self.sock.write(buf)

    def read(self, size):
        return self.sock.read(size)

    def readline(self, maxbuff=None):
        if not maxbuff:
            return self.sock.readline()
        else:
            return self.sock.readline()[:maxbuff]

    def readinto(self, *args):
        return self.sock.readinto(*args)

    def setblocking(self, flag):
        return self.sock.setblocking(flag)

    def makefile(self, *args, **kwargs):
        return self.sock.makefile(*args, **kwargs)

    def send(self, buf):
        return self.sock.write(buf)

    def recv(self, buf):
        return self.sock.read(buf)

    def __del__(self):
        self.sock.__del__()

    def getpeercert(self, *args):
        return self.sock.getpeercert(*args)

    def cipher(self):
        return self.sock.cipher()

    def close(self):
        self.sock.close()

Finally wss_echo.py

from microdot import Microdot, send_file
from microdot_websocket import with_websocket
from microdot_ssl import create_ssl_context
import sys

app = Microdot()


@app.route('/')
def index(request):
    return send_file('index.html')


@app.route('/echo')
@with_websocket
def echo(request, ws):
    while True:
        data = ws.receive()
        ws.send(data)


ext = 'der' if sys.implementation.name == 'micropython' else 'pem'
sslctx = create_ssl_context('cert.' + ext, 'key.' + ext)
app.run(port=4443, debug=True, ssl=sslctx)

@beyonlo
Copy link
Author

beyonlo commented Sep 8, 2022

@Carglglz That's great! Maybe @miguelgrinberg can to generate a ready to go wss example and put in the examples directory to tests. I can test it, like as I did with the HTTPS example, but in my case, I need that HTTPS and wss works with MicroPython uasyncio support :) I think that will be supported soon on the uasyncio!

@miguelgrinberg
Copy link
Owner

Thanks. Pushed a fix for wss and also an example.

@beyonlo
Copy link
Author

beyonlo commented Sep 9, 2022

@miguelgrinberg I tested the new echo_tls.py example on the ESP32-S3 and it works, but some errors happened until I accept the self signed certificates. Follow the details:

$ mpremote run echo_tls.py 
Starting sync server on 0.0.0.0:4443...
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30976, 'MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
GET / 200

@miguelgrinberg
Copy link
Owner

@beyonlo these errors did not stop the application, so they are not a cause for concern. Given that it is impossible for me to know all the possible error codes, and when and if it is safe to suppress the error, the option that I prefer is to log the errors and continue, which is what I'm doing here.

@beyonlo
Copy link
Author

beyonlo commented Sep 9, 2022

@miguelgrinberg I agree! To log the errors and continue is the really the best option, so if something more happen, there is a error log to analyse!

@beyonlo
Copy link
Author

beyonlo commented Sep 9, 2022

@miguelgrinberg Now that I was to stopping the test I observed that there is one more error, but is a same error happened some weeks ago and was fixed by you.

Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: [Errno 104] ECONNRESET

Complete log:

$ mpremote run echo_tls.py 
Starting sync server on 0.0.0.0:4443...
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30976, 'MBEDTLS_ERR_SSL_BAD_HS_CLIENT_HELLO')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
GET / 200
GET /echo 200
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: [Errno 104] ECONNRESET

EDIT: Just to know: will this error or any error (even already fixed by you in the past) follow the same idea, do not will be suppressed/fixed, just do not stop the Microdot and show that error on the log?

EDIT 2: My question above is important to me know if I need to report about a new/different errors or not. Like as this one, one more different error happened:

OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23


@miguelgrinberg
Copy link
Owner

@beyonlo There are no strict rules. If the error(s) prevent something from working, then they need to be investigated. If the errors are just noise, but everything works, then it really depends. I can silence errors that are clearly not a problem (such as an EPIPE, which just means that the other side closed the socket). Non-standard errors with crazy error codes I feel less inclined to mess with, as these are often the result of specific drivers or libraries, and can mean different things under different stacks.

@beyonlo
Copy link
Author

beyonlo commented Sep 12, 2022

@miguelgrinberg

Report:
1. A strange behaviour (bug too?)
2. A bug

1. Strange Behaviour:

Since last week (on my first test of hello_tls.py) I would like to report you about a different behaviour in different platforms: ESP32 and Linux.

-- Using Microdot (over MicroPython) on Linux, when I open the Linux IP, the browser quickly showed the option that indicated that it was not secure, and an option to accept anyway, and proceed with unsecure HTTPS (self-signed). This same behaviour happen in all browsers that I tested, and always errors was showed, but there is no delay.
Follow the log:

$ micropython 
MicroPython v1.19.1-299-gaf54d2ce9 on 2022-08-22; linux [GCC 11.2.0] version
Use Ctrl-D to exit, Ctrl-E for paste mode
>>> 
>>> 
>>> import hello_tls
Starting sync server on 0.0.0.0:4443...
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
GET / 200
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
GET /shutdown 200
>>> 

-- Using Microdot on the ESP32, when I open the ESP32 IP, the browser stay processing for a long time (around 10 seconds, or more) until show the option to proceed with unsecure HTTPS. The same long time delay happen as well after that option already accepted, I mean, when I try to reopen the ESP32 IP. Is a long time to open the hello page. And, after opened, when I click in Click to shutdown the server that long time delay happen again, and just after that long time, the response The server is shutting down... is showed

-- This long time to reach ESP32 happen using Chromium, Edge and Safari (IOS) browsers, always showing errors on the log.
Follow the log:

$ mpremote run hello_tls.py 
Starting sync server on 0.0.0.0:4443...
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
GET / 200
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
GET /shutdown 200

-- Some times, errors are very more intense (using chromium), and bigger delay, and ONE time was need to manually to stop the Microdot (CTRL+C) and start it again, because it was showing just the same error - not the normal errors.
Follow the log:

$ mpremote run hello_tls.py 
Starting sync server on 0.0.0.0:4443...
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
GET / 200
GET /favicon.ico 404
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23



Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23

-- Using the Firefox to reach the ESP32 there is no error messages (better than running on Linux, that still has error messages), but still have a delay, but very less, around 1-2 seconds.
Follow the log:

$ mpremote run hello_tls.py 
Starting sync server on 0.0.0.0:4443...
GET / 200
GET /favicon.ico 404
GET /shutdown 200

-- More tests was done using Microdot on ESP32 with hello_tls.py (using just Chromium), with sometimes different messages, just to report you.
Follow the log:

--

$ mpremote run echo_tls.py 
Starting sync server on 0.0.0.0:4443...
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
GET / 200
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
GET /echo 200
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: [Errno 104] ECONNRESET
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: [Errno 104] ECONNRESET
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
GET / 200
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-30592, 'MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 45, in accept
OSError: 23

--

$ mpremote run hello_tls.py 
Starting sync server on 0.0.0.0:4443...
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-29312, 'MBEDTLS_ERR_SSL_CONN_EOF')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-29312, 'MBEDTLS_ERR_SSL_CONN_EOF')
Traceback (most recent call last):
  File "microdot.py", line 914, in run
  File "microdot_ssl.py", line 46, in accept
OSError: (-29312, 'MBEDTLS_ERR_SSL_CONN_EOF')
GET / 200
GET /shutdown 200

2. The Bug:

-- While I was doing that tests above with hello_tls.py, when I tried to open the ESP32 IP, the Hello was showed and Microdot immediately stopped. This test was done with Firefox.
Follow the log:

$ mpremote run hello_tls.py 
Starting sync server on 0.0.0.0:4443...
GET / 200
Traceback (most recent call last):
  File "<stdin>", line 36, in <module>
  File "microdot.py", line 923, in run
  File "microdot.py", line 32, in create_thread
  File "microdot.py", line 966, in handle_request
  File "microdot.py", line 485, in write
OSError: -104

@miguelgrinberg
Copy link
Owner

miguelgrinberg commented Sep 12, 2022

@beyonlo So all these tests that you are doing are with a self-signed certificate that the browser does not accept? Have you tested a certificate that the browser is configured to accept/trust, which is the normal workflow? I haven't really tested self-signed certificates, they're just not very practical since all browsers reject them.

@beyonlo
Copy link
Author

beyonlo commented Sep 12, 2022

Hi @miguelgrinberg

@beyonlo So all these tests that you are doing are with a self-signed certificate

Yes, following the README.md how to create self-signed certificates - that is exactly what I need for my project, thank you!

that the browser does not accept?

I don't know if I understand this question part very well. After that browser detect that is a self-signed certificate, it show a option to Accept the Risk and Continue (screenshot below). So after that I click in the Accept the Risk and Continue button, and I do not need anymore accept the second time that I access this IP and this self-signed certificate. So, answering your question: the browser accept, but I need first click in Accept the Risk and Continue button.

Screenshot from 2022-09-12 15-30-53

Have you tested a certificate that the browser is configured to accept/trust, which is the normal workflow?

This means that is not self-signed certificate, but a certificate (mostly payed) that is generated and guaranteed by a entity, right? No, I do not have this kind of certificate! Anyway, what I need (and maybe many others) is just to use the self-signed certificates on the applications, where this applications will not be online on the internet, but mostly offline, but supporting secure connection (HTTPS and wss), even not connected to the internet, but just on local WLAN/LAN.

I haven't really tested self-signed certificates, they're just not very practical since all browsers reject them.

Sorry, I was thinking that you created the the README.md just to explain how to create a self-signed certificates to me and other people to test the TLS examples. So you do not used that for tests? Sorry, I'm a bit confuse.

@miguelgrinberg
Copy link
Owner

miguelgrinberg commented Sep 12, 2022

@beyonlo self-signed certificates have become less practical, because browsers introduce all these barriers to use them. Chrome for example does not provide an option to access the site anymore when the certificate is self-signed.

I believe this was mentioned above, you can configure your browser to accept certificates from a certificate authority that is managed by yourself. This is closer to how official certificates work. The mkcert tool makes it easy to create them, and to configure browsers to accept them. My goal is to offer support for proper certificates. Self-signed certs is less interesting to me because they have no practical use outside if testing, and even that is not easy anymore with some browsers.

The examples in the README are okay to use when you use a client that is not a browser that you can configure to ignore validation. Most clients have an option for this. Browsers unfortunately are not a good case for self-signed certs. I think I'm going to update those instructions to use mkcert instead, because it was not my intention to suggest or recommend self-signed certs for any use other than quick tests between two scripts running a client and a server.

@beyonlo
Copy link
Author

beyonlo commented Sep 13, 2022

@beyonlo self-signed certificates have become less practical, because browsers introduce all these barriers to use them. Chrome for example does not provide an option to access the site anymore when the certificate is self-signed.

Understood. I tested that on the Chrome for Android and works. But I tested just Chrome for Android, not Chrome for PC. As my smartphone has a old version of Android, maybe this version of Chrome on Mobile still works.

I believe this was mentioned above, you can configure your browser to accept certificates from a certificate authority that is managed by yourself. This is closer to how official certificates work.

So sorry, I remember that, but I was thinking that is just about Accept the Risk and Continue. Now I understand better, thank you!

The mkcert tool makes it easy to create them, and to configure browsers to accept them. My goal is to offer support for proper certificates. Self-signed certs is less interesting to me because they have no practical use outside if testing, and even that is not easy anymore with some browsers.

Perfect! I wasn't know if that is possible - like as a official certificates!

The examples in the README are okay to use when you use a client that is not a browser that you can configure to ignore validation. Most clients have an option for this. Browsers unfortunately are not a good case for self-signed certs.

All right, understood!

I think I'm going to update those instructions to use mkcert instead, because it was not my intention to suggest or recommend self-signed certs for any use other than quick tests between two scripts running a client and a server.

I think that instructions for the mkcert on the README will be great, but maybe you can to have the both options on the instructions. For example, in the future I plan to have one application that will not have browsers to connect to Microdot HTTPS and wss, just clients applications, and in this case will works using a secure connection of SSL/TLS, because as you wrote: is possible to configure the clients (that is not a browser) to ignore the validation. Maybe more people can have the same scenario where do not need browsers as clients.

@Carglglz
Copy link

-- Using the Firefox to reach the ESP32 there is no error messages (better than running on Linux, that still has error messages), but still have a delay, but very less, around 1-2 seconds.

@beyonlo This delay is expected (at least what I've tested so far), TLS handshake takes approx 1 second in ESP32

-------------------------------------------------------------- benchmark 'device': 1 tests --------------------------------------------------------------
Name (time in ms)                                            Min         Max      Mean   StdDev    Median       IQR  Outliers     OPS  Rounds  Iterations
---------------------------------------------------------------------------------------------------------------------------------------------------------
test_dev[TLS Context Client Benchmark]:[sdev@esp32]     926.4680  1,067.2600  982.6270  74.5092  931.7590  136.1090       2;0  1.0177       5           1
---------------------------------------------------------------------------------------------------------------------------------------------------------

And for the requests

ESP32

Server Software:
Server Hostname:        espdev.local
Server Port:            4443
SSL/TLS Protocol:       TLSv1.2,ECDHE-ECDSA-AES256-GCM-SHA384,256,256
Server Temp Key:        ECDH P-384 384 bits
TLS Server Name:        espdev.local

Document Path:          /
Document Length:        306 bytes

Concurrency Level:      2
Time taken for tests:   6.197 seconds
Complete requests:      4
Failed requests:        0
Total transferred:      1484 bytes
HTML transferred:       1224 bytes
Requests per second:    0.65 [#/sec] (mean)
Time per request:       3098.607 [ms] (mean)
Time per request:       1549.303 [ms] (mean, across all concurrent requests)
Transfer rate:          0.23 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:     1371 2214 716.3   2743    2870
Processing:    26   29   2.6     30      33
Waiting:       15   18   3.9     18      23
Total:       1400 2243 716.4   2776    2897

Percentage of the requests served within a certain time (ms)
  50%   2776
  66%   2776
  75%   2897
  80%   2897
  90%   2897
  95%   2897
  98%   2897
  99%   2897
 100%   2897 (longest request)

UNIX

Server Software:
Server Hostname:        localhost
Server Port:            4443
SSL/TLS Protocol:       TLSv1.2,AES256-SHA256,4096,256
TLS Server Name:        localhost

Document Path:          /
Document Length:        306 bytes

Concurrency Level:      4
Time taken for tests:   2.784 seconds
Complete requests:      100
Failed requests:        0
Total transferred:      37100 bytes
HTML transferred:       30600 bytes
Requests per second:    35.92 [#/sec] (mean)
Time per request:       111.366 [ms] (mean)
Time per request:       27.842 [ms] (mean, across all concurrent requests)
Transfer rate:          13.01 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       38  107  12.4    106     145
Processing:     0    1   0.7      1       6
Waiting:        0    0   0.2      0       1
Total:         38  108  12.3    107     146

Percentage of the requests served within a certain time (ms)
  50%    107
  66%    109
  75%    109
  80%    111
  90%    115
  95%    131
  98%    145
  99%    146
 100%    146 (longest request)

@beyonlo
Copy link
Author

beyonlo commented Sep 14, 2022

@beyonlo This delay is expected (at least what I've tested so far), TLS handshake takes approx 1 second in ESP32

@Carglglz So, if I use just HTTPS (with no wss), on the website (Microdot) running on the ESP32, will delay 1s for each click that I do right? Because as HTTP protocol is not a persistent connection, each click will be one more handshake, so will delay 1s - that is very bad. So in this case the better is always to use secure persistent WebSocket (wss) over HTTPS, so each click on the website will send/receive data over wss, that is persistent connections, and do not need to do a handshake every time. In this scenario will have just 1 handshake, when user open the website and connect to the wss. Is this thought correct?

@Carglglz
Copy link

So in this case the better is always to use secure persistent WebSocket (wss) over HTTPS, so each click on the website will send/receive data over wss, that is persistent connections, and do not need to do a handshake every time. In this scenario will have just 1 handshake, when user open the website and connect to the wss. Is this thought correct?

@beyonlo Yes, although there is a feature in TLS called session resumption (it is available in Python see rfc 5077 and SSLContext.wrap_socket), but unfortunately it hasn't been implemented yet in MicroPython and I'm not sure if it will be.

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

No branches or pull requests

4 participants