I quite like IOStream's interface, actually. If that's part of the transport layer, how do you prevent from having duplicating its behavior (read_until etc)? If there's just another separate object that would be the ITransport in twisted, I think the difference is purely one of labeling.<br>

<br><div class="gmail_quote">On Sat, Oct 13, 2012 at 8:54 PM, Ben Darnell <span dir="ltr"><<a href="mailto:ben@bendarnell.com" target="_blank">ben@bendarnell.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">

<div class="im">On Sat, Oct 13, 2012 at 10:49 AM, Laurens Van Houtven <_@lvh.cc> wrote:<br>
> Interesting. That's certainly a nice API, but that then again (read_until)<br>
> sounds like something I'd implement using dataReceived... You know,<br>
> read_until clears the buffer, logs the requested callback. data_received<br>
> adds something to the buffer, and checks if it triggered the (one of the?)<br>
> registered callbacks.<br>
<br>
</div>Right, that's how IOStream is implemented internally.  The<br>
transport/protocol split works a little differently in Tornado:<br>
IOStream is implemented something like a Protocol subclass, but we<br>
consider it a part of the transport layer.  The "protocols" are<br>
arbitrary classes that don't share any particular interface, but<br>
instead just call methods on the IOStream.<br>
<span class="HOEnZb"><font color="#888888"><br>
-Ben<br>
</font></span><div class="HOEnZb"><div class="h5"><br>
><br>
> Of course, I may just be rusted in my ways and trying to implement<br>
> everything in terms of things I know (then again, that might be just what's<br>
> needed when you're trying to make a useful general API).<br>
><br>
> I guess it's time for me to go deep-diving into Tornado :)<br>
><br>
><br>
> On Sat, Oct 13, 2012 at 7:27 PM, Ben Darnell <<a href="mailto:ben@bendarnell.com">ben@bendarnell.com</a>> wrote:<br>
>><br>
>> On Sat, Oct 13, 2012 at 10:18 AM, Laurens Van Houtven <_@lvh.cc> wrote:<br>
>> > What calls on_headers in this example? Coming from twisted, that seems<br>
>> > like<br>
>> > dataReceived's responsibility, but given your introductory paragraph<br>
>> > that's<br>
>> > not actually what goes on here?<br>
>><br>
>> The IOStream does, after send_request calls<br>
>> stream.read_until("\r\n\r\n", on_headers).  Inside IOStream, there is<br>
>> a _handle_read method that is registered with the IOLoop and fills up<br>
>> a buffer.  When the read condition is satisfied the IOStream calls<br>
>> back into application code.<br>
>><br>
>> -Ben<br>
>><br>
>> ><br>
>> ><br>
>> > On Sat, Oct 13, 2012 at 7:07 PM, Ben Darnell <<a href="mailto:ben@bendarnell.com">ben@bendarnell.com</a>> wrote:<br>
>> >><br>
>> >> On Fri, Oct 12, 2012 at 11:14 PM, Antoine Pitrou <<a href="mailto:solipsis@pitrou.net">solipsis@pitrou.net</a>><br>
>> >> wrote:<br>
>> >> > On Fri, 12 Oct 2012 15:11:54 -0700<br>
>> >> > Guido van Rossum <<a href="mailto:guido@python.org">guido@python.org</a>> wrote:<br>
>> >> >><br>
>> >> >> > 2. Method dispatch callbacks:<br>
>> >> >> ><br>
>> >> >> >     Similar to the above, the reactor or somebody has a handle on<br>
>> >> >> > your<br>
>> >> >> > object, and calls methods that you've defined when events happen<br>
>> >> >> >     e.g. IProtocol's dataReceived method<br>
>> >> >><br>
>> >> >> While I'm sure it's expedient and captures certain common patterns<br>
>> >> >> well, I like this the least of all -- calling fixed methods on an<br>
>> >> >> object sounds like a step back; it smells of the old Java way<br>
>> >> >> (before<br>
>> >> >> it had some equivalent of anonymous functions), and of asyncore,<br>
>> >> >> which<br>
>> >> >> (nearly) everybody agrees is kind of bad due to its insistence that<br>
>> >> >> you subclass its classes. (Notice how subclassing as the prevalent<br>
>> >> >> approach to structuring your code has gotten into a lot of discredit<br>
>> >> >> since 1996.)<br>
>> >> ><br>
>> >> > But how would you write a dataReceived equivalent then? Would you<br>
>> >> > have<br>
>> >> > a "task" looping on a read() call, e.g.<br>
>> >> ><br>
>> >> > @task<br>
>> >> > def my_protocol_main_loop(conn):<br>
>> >> >     while <some_condition>:<br>
>> >> >         try:<br>
>> >> >             data = yield conn.read(1024)<br>
>> >> >         except ConnectionError:<br>
>> >> >             conn.close()<br>
>> >> >             break<br>
>> >> ><br>
>> >> > I'm not sure I understand the problem with subclassing. It works fine<br>
>> >> > in Twisted. Even in Python 3 we don't shy away from subclassing, for<br>
>> >> > example the IO stack is based on subclassing RawIOBase,<br>
>> >> > BufferedIOBase,<br>
>> >> > etc.<br>
>> >><br>
>> >> Subclassing per se isn't a problem, but requiring a single<br>
>> >> dataReceived method per class can be awkward.  Many protocols are<br>
>> >> effectively state machines, and modeling each state as a function can<br>
>> >> be cleaner than a big if/switch block in dataReceived.  For example,<br>
>> >> here's a simplistic HTTP client using tornado's IOStream:<br>
>> >><br>
>> >>        from tornado import ioloop<br>
>> >>         from tornado import iostream<br>
>> >>         import socket<br>
>> >><br>
>> >>         def send_request():<br>
>> >>             stream.write("GET / HTTP/1.0\r\nHost:<br>
>> >> <a href="http://friendfeed.com" target="_blank">friendfeed.com</a>\r\n\r\n")<br>
>> >>             stream.read_until("\r\n\r\n", on_headers)<br>
>> >><br>
>> >>         def on_headers(data):<br>
>> >>             headers = {}<br>
>> >>             for line in data.split("\r\n"):<br>
>> >>                parts = line.split(":")<br>
>> >>                if len(parts) == 2:<br>
>> >>                    headers[parts[0].strip()] = parts[1].strip()<br>
>> >>             stream.read_bytes(int(headers["Content-Length"]), on_body)<br>
>> >><br>
>> >>         def on_body(data):<br>
>> >>             print data<br>
>> >>             stream.close()<br>
>> >>             ioloop.IOLoop.instance().stop()<br>
>> >><br>
>> >>         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)<br>
>> >>         stream = iostream.IOStream(s)<br>
>> >>         stream.connect(("<a href="http://friendfeed.com" target="_blank">friendfeed.com</a>", 80), send_request)<br>
>> >>         ioloop.IOLoop.instance().start()<br>
>> >><br>
>> >><br>
>> >> Classes allow and encourage broader interfaces, which are sometimes a<br>
>> >> good thing, but interact poorly with coroutines.  Both twisted and<br>
>> >> tornado use separate callbacks for incoming data and for the<br>
>> >> connection being closed, but for coroutines it's probably better to<br>
>> >> just treat a closed connection as an error on the read.  Futures (and<br>
>> >> yield from) give us a nice way to do that.<br>
>> >><br>
>> >> -Ben<br>
>> >> _______________________________________________<br>
>> >> Python-ideas mailing list<br>
>> >> <a href="mailto:Python-ideas@python.org">Python-ideas@python.org</a><br>
>> >> <a href="http://mail.python.org/mailman/listinfo/python-ideas" target="_blank">http://mail.python.org/mailman/listinfo/python-ideas</a><br>
>> ><br>
>> ><br>
>> ><br>
>> ><br>
>> > --<br>
>> > cheers<br>
>> > lvh<br>
>> ><br>
><br>
><br>
><br>
><br>
> --<br>
> cheers<br>
> lvh<br>
><br>
</div></div></blockquote></div><br><br clear="all"><br>-- <br>cheers<div>lvh</div><br>