asyncio.start_server example (!)

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
User avatar
cemox
Posts: 32
Joined: Mon Oct 08, 2018 5:31 pm
Location: Turkey

asyncio.start_server example (!)

Post by cemox » Sun May 03, 2020 8:31 pm

I am trying to write a simple asyncio.start_server example, but it doesn't function properly.

Code: Select all

import uasyncio as asyncio

async def handle_echo(reader, writer):
    # while (True):
    data = await reader.read(128)
    if (data):
        message = data.decode()
        addr = writer.get_extra_info('peername')
        print("Received %r from %r" % (message, addr))
        print(type(data))

        message = ("HTTP/1.0 200 OK\r\n\r\nHello World \r\n")
        writer.write(message)
        await writer.drain()
    else:
        print("Close the client socket")
        writer.close()
        await writer.wait_closed()

loop = asyncio.get_event_loop()
coro = asyncio.start_server(handle_echo, '0.0.0.0', 80)
server = loop.run_until_complete(coro)

try:
    loop.run_forever()
except KeyboardInterrupt:
    pass

# Close the server
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
When I connect to my ESP8266 board running the above script from my phone's browser, I see the output in the terminal window (it means connection is ok) like this:
Received 'GET / HTTP/1.1\r\nHost: 192.168.4.1\r\nUpgrade-Insecure-Requests: 1\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*' from ('192.168.4.2', 50805)
<class 'bytes'>
But, the "Hello World" message does not show up on the browser. But, when I interrupt the script by Ctrl+C and soft reset the ESP8266 board, voila message appears on the browser as expected. What am I missing?

User avatar
cemox
Posts: 32
Joined: Mon Oct 08, 2018 5:31 pm
Location: Turkey

Re: asyncio.start_server example (!)

Post by cemox » Mon May 04, 2020 5:53 pm

I also noticed that the connections is somehow continues (or suspended), google chrome inspector says
CATION: request is not finished yet
Screenshot from 2020-05-04 19-14-54.png
Screenshot from 2020-05-04 19-14-54.png (83.79 KiB) Viewed 737 times

User avatar
pythoncoder
Posts: 4472
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: asyncio.start_server example (!)

Post by pythoncoder » Tue May 05, 2020 5:36 am

Alas I don't know the reason. The approach I'd use would be to study working code which uses this approach. I have a copy of Paul Sokolovsky's PicoWeb in this repo. It's an old version but has the (very minor) mods necessary to enable it to work on official firmware. I have tested it on uasyncio V2 and V3. A forum search will turn up other tested web frameworks.
Peter Hinch

User avatar
cemox
Posts: 32
Joined: Mon Oct 08, 2018 5:31 pm
Location: Turkey

Re: asyncio.start_server example (!)

Post by cemox » Tue May 05, 2020 9:07 am

Thank you Peter, I will check PicoWeb. I am glad that I did not ask a stupid question :)

User avatar
cemox
Posts: 32
Joined: Mon Oct 08, 2018 5:31 pm
Location: Turkey

Re: asyncio.start_server example (!)

Post by cemox » Tue May 05, 2020 1:40 pm

Things getting more and more interesting. The code blow works, sending http header hundred times, when I change the multiplier to, say, 10, it doesn't work. Do you think it is a bug?

Code: Select all

import uasyncio as asyncio
import utime

async def serve(reader, writer):
    t = utime.ticks_ms()
    resp = b"HTTP/1.0 200 OK\r\n\r\n" + "Ticks = {TICKS}\r\n" *100
    resp = resp.format(TICKS=t) 
    l = yield from reader.read(256)
    print(l)
    writer.write(resp)
    await writer.drain()
    writer.close()
    await writer.wait_closed()

loop = asyncio.get_event_loop()
loop.create_task(asyncio.start_server(serve, "0.0.0.0", 80))
try: 
    loop.run_forever()
except KeyboardInterrupt:
    print("closing")
    loop.close()

User avatar
pythoncoder
Posts: 4472
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: asyncio.start_server example (!)

Post by pythoncoder » Tue May 05, 2020 3:11 pm

I'm no HTML guru but there are some bits of your code I'm not keen on from a uasyncio V3 viewpoint. Here's how I'd write it:

Code: Select all

import uasyncio as asyncio
import utime

async def serve(reader, writer):
    t = utime.ticks_ms()
    resp = b"HTTP/1.0 200 OK\r\n\r\n" + "Ticks = {TICKS}\r\n".format(TICKS=t) 
    l = await reader.read(256)  # yield from may not work
    print(l)
    for _ in range(100):  # More RAM friendly than sending one huge string?
        await writer.awrite(resp)
    await writer.wait_closed()  # The close() call does nothing. This closes the socket.

try: 
    asyncio.run(asyncio.start_server(serve, "0.0.0.0", 80))
except KeyboardInterrupt:
    print("closing")
finally:
    asyncio.new_event_loop()  # Clear uasyncio stored state
The above are just comments on syntax. I'm not clear whether the program logic is correct.
Peter Hinch

User avatar
cemox
Posts: 32
Joined: Mon Oct 08, 2018 5:31 pm
Location: Turkey

Re: asyncio.start_server example (!)

Post by cemox » Tue May 05, 2020 3:43 pm

Thank you for your comments Peter. Of course, I did not want to send the same sentence to the client 100 times. I noticed that it woks, if I do that.

In your code, if I change the iteration to 5 for example, it doesn't work either: Browser says: "Safari Cannot Open the Page Because the Network Connection was Lost"

Code: Select all

for _ in range(5): 
This really surprises me.

User avatar
cemox
Posts: 32
Joined: Mon Oct 08, 2018 5:31 pm
Location: Turkey

Re: asyncio.start_server example (!)

Post by cemox » Tue May 05, 2020 3:59 pm

At last it worked !

Putting a small delay right after

Code: Select all

writer.awrite(resp)
, solved the problem. The whole working code is :

Code: Select all

import uasyncio as asyncio
import utime

async def serve(reader, writer):
    t = utime.ticks_ms()
    resp = b"HTTP/1.0 200 OK\r\n\r\n" + "Ticks = {TICKS}\r\n".format(TICKS=t) 
    l = await reader.read(256)
    print(l)
    await writer.awrite(resp)
    utime.sleep(0.2)
    await writer.wait_closed()

loop = asyncio.get_event_loop()
loop.create_task(asyncio.start_server(serve, "0.0.0.0", 80))
try: 
    loop.run_forever()
except KeyboardInterrupt:
    print("closing")
    loop.close()

jomas
Posts: 50
Joined: Mon Dec 25, 2017 1:48 pm
Location: Netherlands

Re: asyncio.start_server example (!)

Post by jomas » Tue May 05, 2020 7:34 pm

cemox wrote:
Tue May 05, 2020 3:59 pm
At last it worked !

Putting a small delay right after

Code: Select all

writer.awrite(resp)
, solved the problem. The whole working code is :

Code: Select all

import uasyncio as asyncio
import utime

async def serve(reader, writer):
    t = utime.ticks_ms()
    resp = b"HTTP/1.0 200 OK\r\n\r\n" + "Ticks = {TICKS}\r\n".format(TICKS=t) 
    l = await reader.read(256)
    print(l)
    await writer.awrite(resp)
    utime.sleep(0.2)
    await writer.wait_closed()

loop = asyncio.get_event_loop()
loop.create_task(asyncio.start_server(serve, "0.0.0.0", 80))
try: 
    loop.run_forever()
except KeyboardInterrupt:
    print("closing")
    loop.close()
You really should have a look how the http protocol works.
In your code you read 256 bytes. Instead you should read lines until you hit a blank line.
Then you should send your response. But this should better be html code not just text.
After you have send the response you can close the connection

A delay should not be needed if you follow the rules and certainly you should not use 'utime.sleep' because that will block asyncio.
Also better not use 'awrite' because it is legacy code.
Last edited by jomas on Thu May 07, 2020 6:34 pm, edited 1 time in total.

User avatar
pythoncoder
Posts: 4472
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: asyncio.start_server example (!)

Post by pythoncoder » Wed May 06, 2020 4:16 am

@cemox As @jomas says, you should replace utime.sleep with

Code: Select all

await asyncio.sleep(0.2)
The only time you should use utime.sleep in asynchronous code is for extremely short delays which need a precise maximum length. I'm talking milliseconds or below.

@jomas Thanks for the info about HTTP. It's something I should learn.

I hadn't appreciated that awrite is legacy. The twists and turns in asyncio take some keeping up with. I'll update my tutorial and examples in due course.
Peter Hinch

Post Reply