miguelgrinberg / microdot

The impossibly small web framework for Python and MicroPython.
MIT License
1.52k stars 119 forks source link

Microdot serving over Ethernet (Same Switch) #49

Closed AndyThirtover closed 1 year ago

AndyThirtover commented 2 years ago

Hi - I wonder if you can shed some light on an issue I've found.

I have Microdot running on a W5100S-EVB-Pico which is a RP2040 with built-in Ethernet.

Here's the stripped down start-up:

try:
    import uasyncio as asyncio
except ImportError:
    import asyncio
import network
from microdot_asyncio import Microdot, Response
from web_gen import *
#from watchd import *

"""
async def main():
    await app.start_server(host='0.0.0.0', port=80, debug=True)
    print ("This is after the Web Server has started")

event_loop = asyncio.get_event_loop()

asyncio.create_task(main())
print ("This is after the asyncio create_task of main")

#asyncio.create_task(monitor())
#print ("This is after the asyncio create_task of monitor - the watchdog thread")

#asyncio.create_task(roll(np))
#print ("This is after the asyncio create_task of roll")

event_loop.run_forever()
"""

app.run(host='0.0.0.0', port=80, debug=True)

This is avoid getting caught up with the rest of my code. If I test from a system that is on another switch to Microdot all is well. However, if I test from a device that is on the SAME switch I get these issues:

Screenshot 2022-08-11 at 17 20 31 Screenshot 2022-08-11 at 17 26 37

You'll see that the style.css file is pending - and is finally served after 6 mins.

I've noticed this sort of issue on a older technology (NetMedia Siteplayer), so I wonder if the request is arriving so fast it is not registered, and the extra switch adds a tiny delay as therefore all is good.

Regards

Andy

For reference here's the web_gen.py:

try:
    import uasyncio as asyncio
except ImportError:
    import asyncio
from microdot_asyncio import Microdot, Response
from machine import Pin
from neo_patterns import *

LED = Pin(25, Pin.OUT)

app = Microdot()

@app.route('/')
@app.route('/command')
async def hello(request):
    return Response.send_file('index.html', status_code=200)

@app.route('/docs')
async def show_docs(request):
    return Response.send_file('docs.html', status_code=200)

@app.route('/toggle')
async def toggle(request):
    LED.value(not(LED.value()))
    return Response.send_file('index.html', status_code=200)

@app.route('/json_config')
async def get_config(request):
    return Response.send_file('config.json', status_code=200)

@app.route('/button_config')
async def get_button_config(request):
    return Response.send_file('buttons.json', status_code=200)

@app.route('/config_send')
async def show_config_req(request):
    print("CONFIG DATA - REQ QUERY: {}".format(request.query_string))
    save_config(request.query_string)
    return Response.send_file('config.html', status_code=200)

@app.route('/queue/<job>')
async def get_queue(request,job):
    messages.append(job)
    return Response.redirect('/', status_code=302)

@app.route('/cmd/<job>')
async def get_cmd(request,job):
    messages.append(job)
    return Response(body="['Queued']", status_code=200)

@app.route('/scene/<job>')
async def get_scene(request,job):
    LED.value(not(LED.value()))
    scene_def = "{}:{}".format(job,request.query_string)
    messages.append(scene_def)
    return Response.redirect('/', status_code=302)

@app.route('/style.css')
async def serve_path(request):
    print('Direct style.css')
    try:
        return Response.send_file('style.css', status_code=200)
    except:
        print ("Request for {} NOT FOUND".format(path))
        return 'Request for {} NOT FOUND'.format(path)

@app.route('/<path:path>')
async def serve_path(request,path):
    print('Asked for {}'.format(path))
    try:
        return Response.send_file(path, status_code=200)
    except:
        print ("Request for {} NOT FOUND".format(path))
        return 'Request for {} NOT FOUND'.format(path)

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

The networking is all 1Gb/s switches. The local switch is a NetGear.

Regards

Andy

miguelgrinberg commented 2 years ago

I don't see why the request coming from the same switch would make a difference, and also why only the css file is affected. Any ideas on why that happens?

Also as a side note, why does the css file has its own route and all other static files have a generic endpoint?

Finally, note that the static file endpoint that you are using does not have any protections regarding returning internal files stored in your device. For example, it would not prevent someone from requesting main.py or any other file you have stored in your device.

AndyThirtover commented 2 years ago

Fair Play, I've been bashing it around all afternoon trying to work out what the difference between the project on an ESP32 and on the RP2040. I'm trying to built a replacement for the SitePlayer so I'm probably spike this on a few other platforms. I was trying to find why the style sheet was not served on the first load.

AndyThirtover commented 2 years ago

I refactored the code to serve from static, removed the debug prints and set debug=False on the app start. I also tried the nightly build of MicroPython 1.19.1 ( https://micropython.org/resources/firmware/W5100S_EVB_PICO-20220811-unstable-v1.19.1-273-g6c67fbc28.uf2 ) Sadly the issue is still present, although performance is great. I guess the Ethernet driver does not past on the second request if it is very close to the first.

Regards

Andy

miguelgrinberg commented 2 years ago

@AndyThirtover Make sure you are using the latest microdot.py from this repo. I wonder if this is related to an issue I fixed recently related to multithreading. The RP2040 can only spawn one thread, so multithreading isn't really useful from a web server perspective. The current version runs multiple requests back to back instead of in threads.

AndyThirtover commented 2 years ago

I just got delivery of the W5500-EVB-Pico and I can confirm the same issue. Perfect over two switches - very slow or failed refreshes when connected to the same switch.