diff --git a/btpd/http_tr_if.c b/btpd/http_tr_if.c index 9247b5e..8e9dde2 100644 --- a/btpd/http_tr_if.c +++ b/btpd/http_tr_if.c @@ -1,6 +1,7 @@ #include "btpd.h" #include +#include #define MAX_DOWNLOAD (1 << 18) // 256kB @@ -9,14 +10,23 @@ static const char *m_tr_events[] = { "started", "stopped", "completed", "" }; struct http_tr_req { struct torrent *tp; struct http_req *req; - struct evbuffer *buf; + struct iobuf buf; + struct event rdev; + struct event wrev; + nameconn_t nc; + int sd; enum tr_event event; }; static void http_tr_free(struct http_tr_req *treq) { - evbuffer_free(treq->buf); + if (treq->sd != -1) { + btpd_ev_del(&treq->rdev); + btpd_ev_del(&treq->wrev); + close(treq->sd); + } + iobuf_free(&treq->buf); free(treq); } @@ -111,16 +121,16 @@ http_cb(struct http_req *req, struct http_response *res, void *arg) http_tr_free(treq); break; case HTTP_T_DATA: - if (treq->buf->off + res->v.data.l > MAX_DOWNLOAD) { + if (treq->buf.off + res->v.data.l > MAX_DOWNLOAD) { tr_result(treq->tp, TR_RES_FAIL, -1); http_tr_cancel(treq); break; } - if (evbuffer_add(treq->buf, res->v.data.p, res->v.data.l) != 0) + if (!iobuf_write(&treq->buf, res->v.data.p, res->v.data.l)) btpd_err("Out of memory.\n"); break; case HTTP_T_DONE: - if (parse_reply(treq->tp, treq->buf->buffer, treq->buf->off, + if (parse_reply(treq->tp, treq->buf.buf, treq->buf.off, treq->event != TR_EV_STOPPED, &interval) == 0) tr_result(treq->tp, TR_RES_OK, interval); else @@ -132,11 +142,47 @@ http_cb(struct http_req *req, struct http_response *res, void *arg) } } +static void +sd_wr_cb(int sd, short type, void *arg) +{ + struct http_tr_req *treq = arg; + if (http_write(treq->req, sd) && http_want_write(treq->req)) + btpd_ev_add(&treq->wrev, NULL); +} + +static void +sd_rd_cb(int sd, short type, void *arg) +{ + struct http_tr_req *treq = arg; + if (http_read(treq->req, sd) && http_want_read(treq->req)) + btpd_ev_add(&treq->rdev, NULL); +} + +static void +nc_cb(void *arg, int error, int sd) +{ + struct http_tr_req *treq = arg; + if (error) { + tr_result(treq->tp, TR_RES_FAIL, -1); + http_cancel(treq->req); + http_tr_free(treq); + } else { + treq->sd = sd; + event_set(&treq->wrev, sd, EV_WRITE, sd_wr_cb, treq); + event_set(&treq->rdev, sd, EV_READ, sd_rd_cb, treq); + if (http_want_read(treq->req)) + btpd_ev_add(&treq->rdev, NULL); + if (http_want_write(treq->req)) + btpd_ev_add(&treq->wrev, NULL); + } +} + struct http_tr_req * http_tr_req(struct torrent *tp, enum tr_event event, const char *aurl) { char e_hash[61], e_id[61], ip_arg[INET_ADDRSTRLEN + 4], url[512], qc; const uint8_t *peer_id = btpd_get_peer_id(); + struct http_url *http_url; qc = (strchr(aurl, '?') == NULL) ? '?' : '&'; @@ -166,16 +212,22 @@ http_tr_req(struct torrent *tp, enum tr_event event, const char *aurl) free(treq); return NULL; } - if ((treq->buf = evbuffer_new()) == NULL) + treq->buf = iobuf_init(4096); + if (treq->buf.error) btpd_err("Out of memory.\n"); treq->tp = tp; treq->event = event; + treq->sd = -1; + http_url = http_url_get(treq->req); + treq->nc = btpd_name_connect(http_url->host, http_url->port, nc_cb, treq); return treq; } void http_tr_cancel(struct http_tr_req *treq) { + if (treq->sd == -1) + btpd_name_connect_cancel(treq->nc); http_cancel(treq->req); http_tr_free(treq); } diff --git a/misc/http_client.c b/misc/http_client.c index c5d8b04..177dfb6 100644 --- a/misc/http_client.c +++ b/misc/http_client.c @@ -1,25 +1,16 @@ -#include -#include -#include -#include -#include - #include #include #include +#include #include #include #include #include -#include -#include - +#include "iobuf.h" #include "subr.h" #include "http_client.h" -#define TIMEOUT (& (struct timeval) { 45, 0 }) - struct http_url * http_url_parse(const char *url) { @@ -88,14 +79,11 @@ http_url_free(struct http_url *url) } struct http_req { - enum { - HTTP_RESOLVE, HTTP_CONNECT, HTTP_WRITE, HTTP_RECEIVE, HTTP_PARSE - } state; enum { PS_HEAD, PS_CHUNK_SIZE, PS_CHUNK_DATA, PS_CHUNK_CRLF, PS_ID_DATA } pstate; - int sd; + int parsing; int cancel; int chunked; long length; @@ -104,8 +92,8 @@ struct http_req { void *arg; struct http_url *url; - struct evbuffer *buf; - struct event ev; + struct iobuf rbuf; + struct iobuf wbuf; }; static void @@ -113,12 +101,8 @@ http_free(struct http_req *req) { if (req->url != NULL) http_url_free(req->url); - if (req->buf != NULL) - evbuffer_free(req->buf); - if (req->sd > 0) { - event_del(&req->ev); - close(req->sd); - } + iobuf_free(&req->rbuf); + iobuf_free(&req->wbuf); free(req); } @@ -235,57 +219,57 @@ again: case PS_HEAD: if (len == 0) goto error; - if ((end = evbuffer_find(req->buf, "\r\n\r\n", 4)) != NULL) + if ((end = iobuf_find(&req->rbuf, "\r\n\r\n", 4)) != NULL) dlen = 4; - else if ((end = evbuffer_find(req->buf, "\n\n", 2)) != NULL) + else if ((end = iobuf_find(&req->rbuf, "\n\n", 2)) != NULL) dlen = 2; else { - if (req->buf->off < (1 << 15)) + if (req->rbuf.off < (1 << 15)) return 1; else goto error; } - if (evbuffer_add(req->buf, "", 1) != 0) + if (!iobuf_write(&req->rbuf, "", 1)) goto error; - req->buf->off--; - if (!headers_parse(req, req->buf->buffer, end)) + req->rbuf.off--; + if (!headers_parse(req, req->rbuf.buf, end)) goto error; if (req->cancel) goto cancel; - evbuffer_drain(req->buf, end - (char *)req->buf->buffer + dlen); + iobuf_consumed(&req->rbuf, end - (char *)req->rbuf.buf + dlen); goto again; case PS_CHUNK_SIZE: assert(req->chunked); if (len == 0) goto error; - if ((end = evbuffer_find(req->buf, "\n", 1)) == NULL) { - if (req->buf->off < 20) + if ((end = iobuf_find(&req->rbuf, "\n", 1)) == NULL) { + if (req->rbuf.off < 20) return 1; else goto error; } errno = 0; - req->length = strtol(req->buf->buffer, &numend, 16); - if (req->length < 0 || numend == (char *)req->buf->buffer || errno) + req->length = strtol(req->rbuf.buf, &numend, 16); + if (req->length < 0 || numend == (char *)req->rbuf.buf || errno) goto error; if (req->length == 0) goto done; - evbuffer_drain(req->buf, end - (char *)req->buf->buffer + 1); + iobuf_consumed(&req->rbuf, end - (char *)req->rbuf.buf + 1); req->pstate = PS_CHUNK_DATA; goto again; case PS_CHUNK_DATA: if (len == 0) goto error; assert(req->length > 0); - dlen = min(req->buf->off, req->length); + dlen = min(req->rbuf.off, req->length); if (dlen > 0) { res.type = HTTP_T_DATA; res.v.data.l = dlen; - res.v.data.p = req->buf->buffer; + res.v.data.p = req->rbuf.buf; req->cb(req, &res, req->arg); if (req->cancel) goto cancel; - evbuffer_drain(req->buf, dlen); + iobuf_consumed(&req->rbuf, dlen); req->length -= dlen; if (req->length == 0) { req->pstate = PS_CHUNK_CRLF; @@ -297,15 +281,15 @@ again: if (len == 0) goto error; assert(req->length == 0); - if (req->buf->off < 2) + if (req->rbuf.off < 2) return 1; - if (req->buf->buffer[0] == '\r' && req->buf->buffer[1] == '\n') + if (req->rbuf.buf[0] == '\r' && req->rbuf.buf[1] == '\n') dlen = 2; - else if (req->buf->buffer[0] == '\n') + else if (req->rbuf.buf[0] == '\n') dlen = 1; else goto error; - evbuffer_drain(req->buf, dlen); + iobuf_consumed(&req->rbuf, dlen); req->pstate = PS_CHUNK_SIZE; goto again; case PS_ID_DATA: @@ -314,17 +298,17 @@ again: else if (len == 0) goto error; if (req->length < 0) - dlen = req->buf->off; + dlen = req->rbuf.off; else - dlen = min(req->buf->off, req->length); + dlen = min(req->rbuf.off, req->length); if (dlen > 0) { res.type = HTTP_T_DATA; - res.v.data.p = req->buf->buffer; + res.v.data.p = req->rbuf.buf; res.v.data.l = dlen; req->cb(req, &res, req->arg); if (req->cancel) goto cancel; - evbuffer_drain(req->buf, dlen); + iobuf_consumed(&req->rbuf, dlen); if (req->length > 0) { req->length -= dlen; if (req->length == 0) @@ -346,129 +330,85 @@ cancel: return 0; } -static void -http_read_cb(int sd, short type, void *arg) +struct http_url * +http_url_get(struct http_req *req) { - struct http_req *req = arg; - if (type == EV_TIMEOUT) { - http_error(req); - return; - } - int nr = evbuffer_read(req->buf, sd, 1 << 14); - if (nr < 0) { - if (nr == EAGAIN) - goto more; - else { - http_error(req); - return; - } - } - req->state = HTTP_PARSE; - if (!http_parse(req, nr)) - return; - req->state = HTTP_RECEIVE; -more: - if (event_add(&req->ev, TIMEOUT) != 0) - http_error(req); + return req->url; } -static void -http_write_cb(int sd, short type, void *arg) +int +http_want_read(struct http_req *req) { - struct http_req *req = arg; - if (type == EV_TIMEOUT) { + return 1; +} + +int +http_want_write(struct http_req *req) +{ + return req->wbuf.off > 0; +} + +int +http_read(struct http_req *req, int sd) +{ + if (!iobuf_accommodate(&req->rbuf, 4096)) { http_error(req); - return; - } - int nw = evbuffer_write(req->buf, sd); - if (nw == -1) { - if (errno == EAGAIN) - goto out; - else - goto error; + return 0; } -out: - if (req->buf->off != 0) { - if (event_add(&req->ev, TIMEOUT) != 0) - goto error; + ssize_t nr = read(sd, req->rbuf.buf + req->rbuf.off, 4096); + if (nr < 0 && errno == EAGAIN) + return 1; + else if (nr < 0) { + http_error(req); + return 0; } else { - req->state = HTTP_RECEIVE; - event_set(&req->ev, req->sd, EV_READ, http_read_cb, req); - if (event_add(&req->ev, TIMEOUT) != 0) - goto error; + req->rbuf.off += nr; + req->parsing = 1; + if (http_parse(req, nr)) { + req->parsing = 0; + return 1; + } else + return 0; } - return; -error: - http_error(req); -} - -static int -http_connect(struct http_req *req, struct in_addr inaddr) -{ - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(req->url->port); - addr.sin_addr = inaddr; - req->state = HTTP_CONNECT; - if ((req->sd = socket(PF_INET, SOCK_STREAM, 0)) == -1) - goto error; - if (set_nonblocking(req->sd) != 0) - goto error; - if ((connect(req->sd, (struct sockaddr *)&addr, sizeof(addr)) != 0 - && errno != EINPROGRESS)) - goto error; - event_set(&req->ev, req->sd, EV_WRITE, http_write_cb, req); - if (event_add(&req->ev, TIMEOUT) != 0) - goto error; - return 1; -error: - return 0; } -static void -http_dnscb(int result, char type, int count, int ttl, void *addrs, void *arg) +int +http_write(struct http_req *req, int sd) { - struct http_req *req = arg; - if (req->cancel) - http_free(req); - else if (result == DNS_ERR_NONE && type == DNS_IPv4_A && count > 0) { - int addri = rand_between(0, count - 1); - struct in_addr inaddr; - bcopy(addrs + addri * sizeof(struct in_addr), &inaddr, - sizeof(struct in_addr)); - if (!http_connect(req, inaddr)) - http_error(req); - } else + assert(req->wbuf.off > 0); + ssize_t nw = + write(sd, req->wbuf.buf, req->wbuf.off); + if (nw < 0 && errno == EAGAIN) + return 1; + else if (nw < 0) { http_error(req); + return 0; + } else { + iobuf_consumed(&req->wbuf, nw); + return 1; + } } int http_get(struct http_req **out, const char *url, const char *hdrs, http_cb_t cb, void *arg) { - struct in_addr addr; struct http_req *req = calloc(1, sizeof(*req)); if (req == NULL) return 0; - req->sd = -1; req->cb = cb; req->arg = arg; req->url = http_url_parse(url); if (req->url == NULL) goto error; - if ((req->buf = evbuffer_new()) == NULL) - goto error; - if (evbuffer_add_printf(req->buf, "GET %s HTTP/1.1\r\n" + req->rbuf = iobuf_init(4096); + req->wbuf = iobuf_init(1024); + if (!iobuf_print(&req->wbuf, "GET %s HTTP/1.1\r\n" "Host: %s:%hu\r\n" "Accept-Encoding:\r\n" "Connection: close\r\n" "%s" - "\r\n", req->url->uri, req->url->host, req->url->port, hdrs) == -1) - goto error; - if (inet_aton(req->url->host, &addr) == 1) { - if (!http_connect(req, addr)) - goto error; - } else if (evdns_resolve_ipv4(req->url->host, 0, http_dnscb, req) != 0) + "\r\n", req->url->uri, req->url->host, req->url->port, hdrs)) goto error; if (out != NULL) *out = req; @@ -481,7 +421,7 @@ error: void http_cancel(struct http_req *req) { - if (req->state == HTTP_RESOLVE || req->state == HTTP_PARSE) + if (req->parsing) req->cancel = 1; else http_free(req); diff --git a/misc/http_client.h b/misc/http_client.h index e6a11af..0adc7da 100644 --- a/misc/http_client.h +++ b/misc/http_client.h @@ -1,6 +1,15 @@ #ifndef BTPD_HTTP_CLIENT_H #define BTPD_HTTP_CLIENT_H +struct http_url { + char *host; + char *uri; + uint16_t port; +}; + +struct http_url *http_url_parse(const char *url); +void http_url_free(struct http_url *url); + struct http_response { enum { HTTP_T_ERR, HTTP_T_CODE, HTTP_T_HEADER, HTTP_T_DATA, HTTP_T_DONE @@ -19,20 +28,16 @@ struct http_response { } v; }; -struct http_url { - char *host; - char *uri; - uint16_t port; -}; - struct http_req; - typedef void (*http_cb_t)(struct http_req *, struct http_response *, void *); -struct http_url *http_url_parse(const char *url); -void http_url_free(struct http_url *url); int http_get(struct http_req **out, const char *url, const char *hdrs, http_cb_t cb, void *arg); void http_cancel(struct http_req *req); +struct http_url *http_url_get(struct http_req *req); +int http_want_read(struct http_req *req); +int http_want_write(struct http_req *req); +int http_read(struct http_req *req, int sd); +int http_write(struct http_req *req, int sd); #endif