terom@14: Index: http.c terom@14: =================================================================== terom@14: --- http.c (revision 848) terom@14: +++ http.c (working copy) terom@14: @@ -167,8 +167,10 @@ terom@14: static void evhttp_read_header_cb(struct bufferevent *bufev, void *arg); terom@14: static void evhttp_write_cb(struct bufferevent *, void *); terom@14: static void evhttp_error_cb(struct bufferevent *bufev, short what, void *arg); terom@14: + terom@14: static int evhttp_decode_uri_internal(const char *uri, size_t length, terom@14: char *ret); terom@14: +static void _evhttp_reply_notify (struct evhttp_connection *evcon, void *arg); terom@14: terom@14: #ifndef HAVE_STRSEP terom@14: /* strsep replacement for platforms that lack it. Only works if terom@14: @@ -293,13 +295,20 @@ terom@14: terom@14: void terom@14: evhttp_write_buffer(struct evhttp_connection *evcon, terom@14: - void (*cb)(struct evhttp_connection *, void *), void *arg) terom@14: + void (*cb)(struct evhttp_connection *, void *), void *arg, terom@14: + size_t threshold) terom@14: { terom@14: event_debug(("%s: preparing to write buffer\n", __func__)); terom@14: terom@14: /* Set call back */ terom@14: evcon->cb = cb; terom@14: evcon->cb_arg = arg; terom@14: + terom@14: + if (threshold > 0) { terom@14: + bufferevent_setwatermark(evcon->bufev, EV_WRITE, threshold, 0); terom@14: + } else { terom@14: + bufferevent_setwatermark(evcon->bufev, EV_WRITE, 0, 0); terom@14: + } terom@14: terom@14: bufferevent_disable(evcon->bufev, EV_READ); terom@14: bufferevent_enable(evcon->bufev, EV_WRITE); terom@14: @@ -483,6 +492,17 @@ terom@14: switch (error) { terom@14: case EVCON_HTTP_TIMEOUT: terom@14: case EVCON_HTTP_EOF: terom@14: + /* terom@14: + * Fix a bug that caused a nasty infinite-malloc()-and-write() loop. terom@14: + * A write buffer error (e.g. EPIPE) means that we can't write to that terom@14: + * socket anymore, and evhttp_connection_fail does disable terom@14: + * EV_WRITE|EV_READ for that buffer. However, if we call req->cb, then terom@14: + * that adds a "400 Bad Request" to the buffer, and enables it for terom@14: + * EV_WRITE... which causes evhttp_connection_incoming_fail to be terom@14: + * called again with EVCON_HTTP_BUFFER_ERROR... ended up having to terom@14: + * hard-reset my machine. terom@14: + */ terom@14: + case EVCON_HTTP_BUFFER_ERROR: terom@14: /* terom@14: * these are cases in which we probably should just terom@14: * close the connection and not send a reply. this terom@14: @@ -491,7 +511,6 @@ terom@14: */ terom@14: return (-1); terom@14: case EVCON_HTTP_INVALID_HEADER: terom@14: - case EVCON_HTTP_BUFFER_ERROR: terom@14: case EVCON_HTTP_REQUEST_CANCEL: terom@14: default: /* xxx: probably should just error on default */ terom@14: /* the callback looks at the uri to determine errors */ terom@14: @@ -869,7 +888,7 @@ terom@14: /* Create the header from the store arguments */ terom@14: evhttp_make_header(evcon, req); terom@14: terom@14: - evhttp_write_buffer(evcon, evhttp_write_connectioncb, NULL); terom@14: + evhttp_write_buffer(evcon, evhttp_write_connectioncb, NULL, 0); terom@14: } terom@14: terom@14: /* Reset our connection state */ terom@14: @@ -1353,9 +1372,9 @@ terom@14: } terom@14: } terom@14: terom@14: - event_debug(("%s: bytes to read: %d (in buffer %ld)\n", terom@14: + event_debug(("%s: bytes to read: %ld (in buffer %ld)\n", terom@14: __func__, req->ntoread, terom@14: - EVBUFFER_LENGTH(req->evcon->input_buffer))); terom@14: + EVBUFFER_LENGTH(EVBUFFER_INPUT(req->evcon->bufev)))); terom@14: terom@14: return (0); terom@14: } terom@14: @@ -1688,6 +1707,10 @@ terom@14: terom@14: /* delete possible close detection events */ terom@14: evhttp_connection_stop_detectclose(evcon); terom@14: + terom@14: + /* now that the reply was sent, we don't need to abort on connection lost */ terom@14: + if (req->abort_cb) terom@14: + evhttp_connection_set_closecb(evcon, NULL, NULL); terom@14: terom@14: need_close = terom@14: (req->minor == 0 && terom@14: @@ -1753,7 +1776,7 @@ terom@14: /* Adds headers to the response */ terom@14: evhttp_make_header(evcon, req); terom@14: terom@14: - evhttp_write_buffer(evcon, evhttp_send_done, NULL); terom@14: + evhttp_write_buffer(evcon, evhttp_send_done, NULL, 0); terom@14: } terom@14: terom@14: void terom@14: @@ -1781,7 +1804,7 @@ terom@14: req->chunked = 1; terom@14: } terom@14: evhttp_make_header(req->evcon, req); terom@14: - evhttp_write_buffer(req->evcon, NULL, NULL); terom@14: + evhttp_write_buffer(req->evcon, NULL, NULL, 0); terom@14: } terom@14: terom@14: void terom@14: @@ -1796,9 +1819,32 @@ terom@14: if (req->chunked) { terom@14: evbuffer_add(output, "\r\n", 2); terom@14: } terom@14: - evhttp_write_buffer(req->evcon, NULL, NULL); terom@14: + terom@14: + evhttp_write_buffer(req->evcon, &_evhttp_reply_notify, req, req->write_threshold); terom@14: } terom@14: terom@14: +static void terom@14: +_evhttp_reply_notify (struct evhttp_connection *evcon, void *arg) terom@14: +{ terom@14: + struct evhttp_request *req = arg; terom@14: + terom@14: + if (req->write_cb) terom@14: + req->write_cb(req, req->write_cb_arg); terom@14: +} terom@14: + terom@14: +int terom@14: +evhttp_set_reply_notify(struct evhttp_request *req, size_t threshold, terom@14: + void (*write_cb)(struct evhttp_request *, void *), void *cb_arg) terom@14: +{ terom@14: + req->write_threshold = threshold; terom@14: + req->write_cb = write_cb; terom@14: + req->write_cb_arg = cb_arg; terom@14: + terom@14: + evhttp_write_buffer(req->evcon, &_evhttp_reply_notify, req, threshold); terom@14: + terom@14: + return 0; terom@14: +} terom@14: + terom@14: void terom@14: evhttp_send_reply_end(struct evhttp_request *req) terom@14: { terom@14: @@ -1807,7 +1853,7 @@ terom@14: terom@14: if (req->chunked) { terom@14: evbuffer_add(output, "0\r\n\r\n", 5); terom@14: - evhttp_write_buffer(req->evcon, evhttp_send_done, NULL); terom@14: + evhttp_write_buffer(req->evcon, evhttp_send_done, NULL, 0); terom@14: req->chunked = 0; terom@14: } else if (EVBUFFER_LENGTH(output) == 0) { terom@14: /* let the connection know that we are done with the request */ terom@14: @@ -1819,6 +1865,28 @@ terom@14: } terom@14: } terom@14: terom@14: +void _evhttp_reply_aborted(struct evhttp_connection *evcon, void *arg) terom@14: +{ terom@14: + struct evhttp_request *req = arg; terom@14: + terom@14: + /* call the callback */ terom@14: + req->abort_cb(req, req->abort_cb_arg); terom@14: + terom@14: + /* the request is now aborted, we don't want this called twice */ terom@14: + evhttp_connection_set_closecb(evcon, NULL, NULL); terom@14: +} terom@14: + terom@14: +void evhttp_set_reply_abortcb(struct evhttp_request *req, terom@14: + void (*cb)(struct evhttp_request *, void *), void *cb_arg) terom@14: +{ terom@14: + /* store the cb/arg in req */ terom@14: + req->abort_cb = cb; terom@14: + req->abort_cb_arg = cb_arg; terom@14: + terom@14: + /* add a closecb to the connection */ terom@14: + evhttp_connection_set_closecb(req->evcon, &_evhttp_reply_aborted, req); terom@14: +} terom@14: + terom@14: void terom@14: evhttp_response_code(struct evhttp_request *req, int code, const char *reason) terom@14: { terom@14: @@ -2448,6 +2516,19 @@ terom@14: * Allows for inspection of the request URI terom@14: */ terom@14: terom@14: +enum evhttp_cmd_type terom@14: +evhttp_request_get_type(struct evhttp_request *req) terom@14: +{ terom@14: + return req->type; terom@14: +} terom@14: + terom@14: +void terom@14: +evhttp_request_get_peer(struct evhttp_request *req, terom@14: + char **address, ev_uint16_t *port) terom@14: +{ terom@14: + return evhttp_connection_get_peer(req->evcon, address, port); terom@14: +} terom@14: + terom@14: const char * terom@14: evhttp_request_get_uri(struct evhttp_request *req) { terom@14: if (req->uri == NULL) terom@14: Index: http-internal.h terom@14: =================================================================== terom@14: --- http-internal.h (revision 848) terom@14: +++ http-internal.h (working copy) terom@14: @@ -141,7 +141,8 @@ terom@14: void evhttp_make_header(struct evhttp_connection *, struct evhttp_request *); terom@14: terom@14: void evhttp_write_buffer(struct evhttp_connection *, terom@14: - void (*)(struct evhttp_connection *, void *), void *); terom@14: + void (*)(struct evhttp_connection *, void *), void *, terom@14: + size_t); terom@14: terom@14: /* response sending HTML the data in the buffer */ terom@14: void evhttp_response_code(struct evhttp_request *, int, const char *); terom@14: Index: include/event2/http.h terom@14: =================================================================== terom@14: --- include/event2/http.h (revision 848) terom@14: +++ include/event2/http.h (working copy) terom@14: @@ -243,6 +243,7 @@ terom@14: */ terom@14: void evhttp_send_reply_chunk(struct evhttp_request *req, terom@14: struct evbuffer *databuf); terom@14: + terom@14: /** terom@14: Complete a chunked reply. terom@14: terom@14: @@ -250,6 +251,43 @@ terom@14: */ terom@14: void evhttp_send_reply_end(struct evhttp_request *req); terom@14: terom@14: +/** terom@14: + Used to implement flow control for sending HTTP replies. terom@14: + terom@14: + After calling evhttp_send_reply_start, and before calling terom@14: + evhttp_send_reply_end, you may call this function to register a callback terom@14: + that will be fired when the level of the HTTP connection's output buffer is terom@14: + below the given threshold (if the output buffer is empty when this function terom@14: + is called, this will happen on the next event-loop iteration). terom@14: + terom@14: + This callback behaves in an edge-triggered way, such that it will only fire terom@14: + once if the otuput buffer's level is below the threshold. It may be called terom@14: + again after the next evhttp_send_reply_chunk call. terom@14: + terom@14: + Once evhttp_send_reply_end is called (or an error occurs), the callback is terom@14: + invalidated and not used anymore. terom@14: + terom@14: + @param req a request object terom@14: + @param threshold the buffer level to watch for terom@14: + @param write_cb the callback to use terom@14: + @param cb_arg the callback argument terom@14: + terom@14: +*/ terom@14: +int evhttp_set_reply_notify(struct evhttp_request *req, size_t threshold, terom@14: + void (*write_cb)(struct evhttp_request *, void *), void *cb_arg); terom@14: + terom@14: +/** terom@14: + Register a callback to get called if the request is aborted due to some terom@14: + reason other than evhttp_send_{reply,error,reply_end}, e.g. the HTTP terom@14: + connection was lost. terom@14: + terom@14: + @param req a request object terom@14: + @param cb the callback to use terom@14: + @param cb_arg the callback argument terom@14: +*/ terom@14: +void evhttp_set_reply_abortcb(struct evhttp_request *, terom@14: + void (*)(struct evhttp_request *, void *), void *); terom@14: + terom@14: /* terom@14: * Interfaces for making requests terom@14: */ terom@14: @@ -360,6 +398,11 @@ terom@14: void evhttp_cancel_request(struct evhttp_request *req); terom@14: terom@14: terom@14: +/** Get the request type for this request */ terom@14: +enum evhttp_cmd_type evhttp_request_get_type(struct evhttp_request *req); terom@14: +/** Get the remote address and port this request came from. */ terom@14: +void evhttp_request_get_peer(struct evhttp_request *req, terom@14: + char **address, ev_uint16_t *port); terom@14: /** Returns the request URI */ terom@14: const char *evhttp_request_get_uri(struct evhttp_request *req); terom@14: /** Returns the input headers */ terom@14: Index: include/event2/http_struct.h terom@14: =================================================================== terom@14: --- include/event2/http_struct.h (revision 848) terom@14: +++ include/event2/http_struct.h (working copy) terom@14: @@ -113,6 +113,19 @@ terom@14: * the regular callback. terom@14: */ terom@14: void (*chunk_cb)(struct evhttp_request *, void *); terom@14: + terom@14: + /* terom@14: + * Request reply flow control callback terom@14: + */ terom@14: + size_t write_threshold; terom@14: + void (*write_cb)(struct evhttp_request *, void *); terom@14: + void *write_cb_arg; terom@14: + terom@14: + /* terom@14: + * Request abort callback terom@14: + */ terom@14: + void (*abort_cb)(struct evhttp_request *, void *); terom@14: + void *abort_cb_arg; terom@14: }; terom@14: terom@14: #ifdef __cplusplus terom@14: Index: buffer.c terom@14: =================================================================== terom@14: --- buffer.c (revision 848) terom@14: +++ buffer.c (working copy) terom@14: @@ -412,6 +412,9 @@ terom@14: if (size == 0 || size > buf->total_len) terom@14: return (NULL); terom@14: terom@14: +// XXX: the following will segfault if buf->total_len == 0 && size < -1 terom@14: +// XXX: the following will return a buffer of arbitrary length if size < -1 terom@14: + terom@14: /* No need to pull up anything; the first size bytes are terom@14: * already here. */ terom@14: if (chain->off >= size)