[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
Re: [Libevent-users] http server and infinite streams
On Wed, May 11, 2011 at 11:21 AM, Scott Lamb <slamb@xxxxxxxxx> wrote:
> On Wed, May 11, 2011 at 10:12 AM, Cliff Frey <cliff@xxxxxxxxxx> wrote:
>> I spent some more time thinking about this, and I have a few use cases that
>> it might be worth keeping in mind.
>>
>> It would be nice if the API made it easy to hook up a received HTTP request
>> (where we are the server) to an outgoing HTTP request (where we are the
>> client) in order to make building an HTTP proxy very simple. Not a huge
>> deal if this isn't super simple though.
>> It should be easy to hook up a zlib compressor/decompressor to the HTTP
>> library for supporting compressed transfers (with Accept-Encoding).
>
> Yay.
>
>>
>> I'm not sure who should be responsible for allocating the bufferevent
>> objects. If there was an easy way to connect two existing bufferevent
>> objects to one another (like bufferevent_pair_new, but for existing ones),
>> then it would be fine for the http library to provide the bufferevent
>> objects, but it would probably be more flexible if the caller provided the
>> bufferevent objects (but then I am unsure who should be responsible for
>> cleanup of them).
>> So, in any case, it boils down to something like
>> Option 1 (from my previous email)
>> // used instead of evhttp_set_gen_cb, cb is called as soon as the request
>> // is received, before the content of the request has been received.
>> void evhttp_set_gen_req_startcb(evhttp *, void (*cb)(struct evhttp_request
>> *, struct bufferevent *bev, void *), void *arg);
>> // this returns a bufferevent that can be used to send a reply to the client
>> struct bufferevent *evhttp_send_reply_bev(struct evhttp_request *req, int
>> code, const char *reason);
>> Option 2
>> // used instead of evhttp_set_gen_cb, cb is called as soon as the request
>> // is received, before the content of the request has been received.
>> // To received the contents of the message, a bufferevent * must be
>> returned,
>> // which the evhttp library will fill with the request data, respecting the
>> // highwater mark. Once BEV_EVENT_EOF has been received, the evhttp library
>> // will not access the bufferevent * again, and it is up to the user to free
>> // the bufferevent.
>> struct bufferevent *evhttp_set_gen_req_startcb(evhttp *, void (*cb)(struct
>> evhttp_request *, void *), void *arg);
>> // evhttp will free bev after receiving BEV_EVENT_EOF
>
> presumably BEV_EVENT_ERROR or BEV_EVENT_TIMEOUT as well
>
>> void evhttp_send_reply_bev(struct evhttp_request *req, int code, const char
>> *reason, struct bufferevent *bev);
>>
>> I think that I like option 2 better.
>> Cliff
>
> I like option 2 as well; as you say, it's very easy for proxy servers
> to use. Somehow I'd avoided reading up too much on bufferevents
> before; I just did so and see that they're perfect for this. One
> caveat is that if not all aspects of bufferevents work with evhttp,
> that should be documented.
Also, sorry if this is obvious to someone who's actually used
bufferevents, but a question: how would the error behavior work?
* If the client side fails, it probably indicates that via
BEV_EVENT_ERROR|BEV_EVENT_READING? (Presumably even if the error at
the TCP layer was on a write call.)
* If the server side fails, BEV_EVENT_ERROR|BEV_EVENT_WRITING? If the
bufferevent in question came from a client request, how do both the
client evhttp request and the user code get error notification? (Maybe
the least surprising thing would be if the user could freely call
bufferevent_setcb without worrying about the client request? It
probably should be made clear if the client request still exists when
this callback is called.)
* If the user code wants to abort a request in progress (single or
linked), how would it do so?
>
>>
>> On Tue, May 10, 2011 at 11:36 PM, Cliff Frey <cliff@xxxxxxxxxx> wrote:
>>>>
>>>> So I like the idea of getting read-by-read notification of data as it
>>>> arrives. I'm not thrilled with the idea of changing default behavior
>>>> wrt what counts as a read, though. Either a flag or another kind of
>>>> callback would make me much more comfortable about the change here.
>>>
>>> I will try and make a new version of the read-breakup change that adds a
>>> flag of some sort.
>>>>
>>>> I think that the evhttp_send_reply_chunk_with_cb behavior might be a
>>>> little screwy. Suppose that you send one chunk with a cb, then send
>>>> another chunk with cb == NULL. The second time you call
>>>> evhttp_send_reply_chunk_with_cb, it won't clear out the cb, so the
>>>> first cb will get called for the second chunk. That can't be
>>>> as-intended, right?
>>>>
>>>> I *think* that some part of this could be done by exposing the
>>>> bufferevent more, but like you I'm a little unclear on the details
>>>> here. Pausing and unpausing could (I think) be done with
>>>> bufferevent_enable and disable on the user's part; throttling could be
>>>> done with the rate-limiting functionality and the high-water/low-water
>>>> functionality; and did you know that you can have more than one
>>>> callback on an evbuffer? You can get into lots of fun with that.
>>>>
>>>> But on consideration, this code is pretty darn nice. I'm hoping other
>>>> evhttp users will have a look at it too, but on the whole I am pretty
>>>> happy about merging it into 2.1.
>>>
>>> On the whole I agree with you that my proposed API is a little bit screwy.
>>> It does get the job done in a fairly simple manner though... which is why I
>>> did it. In any case, here is an attempt at a cleaner API, but I wanted to
>>> get feedback from the list on it. This API very-much just builds on the
>>> current API (although I imagine that in the actual change to http.c, much of
>>> the current API would be implemented in terms of the new API...)
>>> Receiving HTTP requests as an HTTP server:
>>>
>>> Current interface
>>> Useful for simple servers, impossible to receive infinite/streaming
>>> requests
>>> set a maximum incoming request size (to avoid OOM)
>>> set a callback that gets a complete request:
>>> evhttp_set_gencb(evhttp *, void (*cb)(struct evhttp_request *, void
>>> *), void *arg);
>>> Proposed interface
>>> Leave the current interface for simple servers, but add another callback
>>> that supports receiving streaming requests. If this other callback is
>>> registered, then it takes precedence over the old one.
>>> This is called as soon as a request is received, before the body of the
>>> request has been received (if any)
>>>
>>> evhttp_set_gen_req_startcb(evhttp *, void (*cb)(struct evhttp_request *,
>>> struct bufferevent *bev, void *), void *arg);
>>>
>>> If the request has content-length 0, then bev is NULL. If 'cb' sets the
>>> highwater mark of bev, then the http library will attempt to respect that,
>>> and will stop reading from the underlying TCP connection if bev is full. Is
>>> is up to 'cb' to register callbacks on bev that will receive future events
>>> (including EOF for the end of the request)
>>>
>>> Sending an HTTP response as an HTTP server:
>>>
>>> Current interface: provides two decent options, does not support flow
>>> control when sending possibly-infinite streams to slow clients (i.e. very
>>> frequent stock ticker symbol updates over a persistent connection to a very
>>> very slow client)
>>> either
>>> evhttp_send_reply(struct evhttp_request *req, int code, const char
>>> *reason, struct evbuffer *databuf);
>>> or
>>> evhttp_send_reply_start(struct evhttp_request *req, int code, const char
>>> *reason);
>>> evhttp_send_reply_chunk(struct evhttp_request *req, struct evbuffer
>>> *databuf);
>>> evhttp_send_reply_chunk_with_cb(struct evhttp_request *req, struct
>>> evbuffer *databuf, void (*cb)(struct evhttp_request *, void *), void *arg);
>>> Proposed interface: add a third option that allows for flow control
>>> struct bufferevent *evhttp_send_reply_bev(struct evhttp_request *req,
>>> int code, const char *reason);
>>> It is the caller's responsibility to fill the returned bufferevent with
>>> the data to be sent to the client, and to eventually
>>> bufferevent_flush(BEV_FINISHED) on it. At this point, the caller should
>>> never access the bufferevent or evhttp_request again (i.e. the http library
>>> will free it when finished). The evhttp library would only empty this
>>> bufferevent when the underlying TCP connection's bufferevent was not too
>>> full (probably with a configurable highwater mark defining full, i.e.
>>> evhttp_connection_set_xmit_highwater or something)
>>>
>>> I believe that a similar set of additions could be made with the
>>> http-client API, but I wanted to get feedback on this potential server API
>>> first. It would likely involve incompatible changes to the evhttp_request
>>> structure, but people are not supposed to depend on that anyways.
>>> Cliff
>>
>
>
>
> --
> Scott Lamb <http://www.slamb.org/>
>
--
Scott Lamb <http://www.slamb.org/>
***********************************************************************
To unsubscribe, send an e-mail to majordomo@xxxxxxxxxxxxx with
unsubscribe libevent-users in the body.