
Hi I have been enjoying reading reviews of asio, and I have played with it a little bit for the purpose of possibly using it for implementing various protocols, many of which are layered in various ways. It would be nice to find a systematic way to decompose the layers involved. I've been thinking about how to do this in a way similar to the ACE Streams framework, without having to resort to runtime polymorphism and type erasure, but have not found a clean way to do this. This post made me think about this differently. Christopher Kohlhoff wrote:
For example, I could easily imagine a function:
template <typename Handler> void async_http_get_content( asio::demuxer& demuxer, const std::string& url, std::vector<char>& content, Handler handler);
Internally this might parse the URL, resolve the host name, create a socket, connect, transmit request, collect response, close socket and finally invoke the application handler, all asynchronously.
I think this is very useful, so to be able to assemble this and other higher abstractions one would likely need to be able to compose in terms of more fundamental building blocks. For instance: // Asynchronously get a line of input from 'stream' template <typename BUFFER, typename DISPATCHER, typename HANDLER> void async_get_line( BUFFER& buffered_stream, DISPATCHER& dispatcher, std::vector<char>& line, Handler handler); // Asynchronously get http headers, by repeatedly using async_get_line template <typename BUFFER, typename DISPATCHER, typename HANDLER> void asych_get_http_headers( BUFFER& buffered_stream, DISPATCHER& dispatcher, std::map<std::string, std::string> >& headers, Handler handler); // Asynchronously get the http response body template <typename BUFFER, typename DISPATCHER, typename HANDLER> void asych_get_http_body( BUFFER& buffered_stream, DISPATCHER& dispatcher, std::vector<char>& body, size_t content_length, Handler handler); Now, asynch_get_line would not know how much to consume from the socket, and getting one character at a time would be inappropriate. Thus, I added another template parameter BUFFER that is a fictional concept for the purpose of this discussion that supports pushing back unconsumed data for later invocations. I see that there is an asio::buffered_stream. Maybe it can be changed to support pushing back data. It would be driven like this asio::socket_stream s; buffer<asio::socket_stream> buffer(s); std::vector<char> body; std::map<std::string, std::string> > headers; ... // Send request, then void request_sent(...) { async_get_http_headers(buffer, demuxer, headers, handle_read_headers); } void handle_read_headers(...) { size_t content_length = atoi(headers["content-length"]); asynch_get_http_body(buffer, demuxer, body, content_length, handle_read_body); } Do you think this is a fruitful way to proceed? Will this sort decomposition be unfavorable with respect to performance? Also expressing these abstract operations in general enough types allows driving a protocol stack from a different source, such as a test driver instead of a specific socket type. You have already defined concepts for many aspects of asio. Could the suggested BUFFER concept derive from the Stream concept? What would that entail for the implementation of e.g asio::buffered_stream? I'm thinking about STL and the way that iterators concepts are hierarchically derived from each other and that algorithms are expressed in terms of a certain iterator concept. Speaking of concepts. I feel a little bit lost navigating the template type hierarchies. Consistent reuse of concept names where appropriate might help. For instance, you define a concept 'Dispatcher', yet you use the name demuxer_type everywhere. Finally, I don't see any way to cancel asynchronous operations. While this may be ok for the read_some functions, things get more serious when composing larger operations. What are your thoughts around this? Has cancellation been discussed before? Thanks Mats