diff --git a/include/re_http.h b/include/re_http.h index 6fa96da..853fa3e 100644 --- a/include/re_http.h +++ b/include/re_http.h @@ -110,6 +110,20 @@ bool http_msg_xhdr_has_value(const struct http_msg *msg, const char *name, int http_msg_print(struct re_printf *pf, const struct http_msg *msg); +/* Client */ +struct http_cli; +struct http_req; +struct dnsc; + +typedef void (http_resp_h)(int err, const struct http_msg *msg, void *arg); +typedef void (http_data_h)(struct mbuf *mb, void *arg); + +int http_client_alloc(struct http_cli **clip, struct dnsc *dnsc); +int http_request(struct http_req **reqp, struct http_cli *cli, const char *met, + const char *uri, http_resp_h *resph, http_data_h *datah, + void *arg, const char *fmt, ...); + + /* Server */ struct http_sock; struct http_conn; diff --git a/src/http/client.c b/src/http/client.c new file mode 100644 index 0000000..05dff16 --- /dev/null +++ b/src/http/client.c @@ -0,0 +1,457 @@ +/** + * @file http/client.c HTTP Client + * + * Copyright (C) 2011 Creytiv.com + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +enum { + CONN_TIMEOUT = 30000, + RECV_TIMEOUT = 30000, + BUFSIZE_MAX = 524288, +}; + +struct http_cli { + struct dnsc *dnsc; + struct tls *tls; +}; + +struct http_req { + struct sa srvv[16]; + struct tmr tmr; + struct http_req **reqp; + struct dns_query *dq; + struct tls_conn *sc; + struct tcp_conn *tc; + struct mbuf *mbreq; + struct mbuf *mb; + struct tls *tls; + char *host; + http_resp_h *resph; + http_data_h *datah; + void *arg; + unsigned srvc; + uint16_t port; + bool secure; + bool data; +}; + + +static int req_connect(struct http_req *req); + + +static void cli_destructor(void *arg) +{ + struct http_cli *cli = arg; + + mem_deref(cli->dnsc); + mem_deref(cli->tls); +} + + +static void req_destructor(void *arg) +{ + struct http_req *req = arg; + + tmr_cancel(&req->tmr); + mem_deref(req->dq); + mem_deref(req->sc); + mem_deref(req->tc); + mem_deref(req->mbreq); + mem_deref(req->mb); + mem_deref(req->tls); + mem_deref(req->host); +} + + +static void req_close(struct http_req *req, int err, + const struct http_msg *msg) +{ + tmr_cancel(&req->tmr); + req->dq = mem_deref(req->dq); + req->sc = mem_deref(req->sc); + req->tc = mem_deref(req->tc); + req->datah = NULL; + + if (req->reqp) { + *req->reqp = NULL; + req->reqp = NULL; + } + + if (req->resph) { + req->resph(err, msg, req->arg); + req->resph = NULL; + } +} + + +static void timeout_handler(void *arg) +{ + struct http_req *req = arg; + int err = ETIMEDOUT; + + if (req->srvc > 0) { + + err = req_connect(req); + if (!err) + return; + } + + req_close(req, err, NULL); + mem_deref(req); +} + + +static void estab_handler(void *arg) +{ + struct http_req *req = arg; + int err; + + err = tcp_send(req->tc, req->mbreq); + if (err) { + req_close(req, err, NULL); + mem_deref(req); + return; + } + + tmr_start(&req->tmr, RECV_TIMEOUT, timeout_handler, req); +} + + +static void recv_handler(struct mbuf *mb, void *arg) +{ + struct http_msg *msg = NULL; + struct http_req *req = arg; + size_t pos; + int err; + + if (req->data) { + if (req->datah) + req->datah(mb, req->arg); + return; + } + + if (req->mb) { + + const size_t len = mbuf_get_left(mb); + + if ((mbuf_get_left(req->mb) + len) > BUFSIZE_MAX) { + err = EOVERFLOW; + goto out; + } + + pos = req->mb->pos; + req->mb->pos = req->mb->end; + + err = mbuf_write_mem(req->mb, mbuf_buf(mb), len); + if (err) + goto out; + + req->mb->pos = pos; + } + else { + req->mb = mem_ref(mb); + } + + pos = req->mb->pos; + + err = http_msg_decode(&msg, req->mb, false); + if (err) { + if (err == ENODATA) { + req->mb->pos = pos; + return; + } + goto out; + } + + if (req->datah) { + tmr_cancel(&req->tmr); + req->data = true; + if (req->resph) + req->resph(0, msg, req->arg); + mem_deref(msg); + return; + } + + if (mbuf_get_left(req->mb) < msg->clen) { + req->mb->pos = pos; + mem_deref(msg); + return; + } + + req->mb->end = req->mb->pos + msg->clen; + + out: + req_close(req, err, msg); + mem_deref(req); + mem_deref(msg); +} + + +static void close_handler(int err, void *arg) +{ + struct http_req *req = arg; + + if (req->srvc > 0 && !req->data) { + + err = req_connect(req); + if (!err) + return; + } + + req_close(req, err ? err : ECONNRESET, NULL); + mem_deref(req); +} + + +static int req_connect(struct http_req *req) +{ + int err = EINVAL; + + while (req->srvc > 0) { + + --req->srvc; + + tmr_cancel(&req->tmr); + req->sc = mem_deref(req->sc); + req->tc = mem_deref(req->tc); + req->mb = mem_deref(req->mb); + + err = tcp_connect(&req->tc, &req->srvv[req->srvc], + estab_handler, recv_handler, close_handler, + req); + if (err) + continue; + +#ifdef USE_TLS + if (req->secure) { + + err = tls_start_tcp(&req->sc, req->tls, req->tc, 0); + if (err) { + req->tc = mem_deref(req->tc); + continue; + } + } +#endif + tmr_start(&req->tmr, CONN_TIMEOUT, timeout_handler, req); + break; + } + + return err; +} + + +static bool rr_handler(struct dnsrr *rr, void *arg) +{ + struct http_req *req = arg; + + if (req->srvc >= ARRAY_SIZE(req->srvv)) + return true; + + switch (rr->type) { + + case DNS_TYPE_A: + sa_set_in(&req->srvv[req->srvc++], rr->rdata.a.addr, + req->port); + break; + + case DNS_TYPE_AAAA: + sa_set_in6(&req->srvv[req->srvc++], rr->rdata.aaaa.addr, + req->port); + break; + } + + return false; +} + + +static void query_handler(int err, const struct dnshdr *hdr, struct list *ansl, + struct list *authl, struct list *addl, void *arg) +{ + struct http_req *req = arg; + (void)hdr; + (void)authl; + (void)addl; + + dns_rrlist_apply2(ansl, req->host, DNS_TYPE_A, DNS_TYPE_AAAA, + DNS_CLASS_IN, true, rr_handler, req); + if (req->srvc == 0) { + err = err ? err : EDESTADDRREQ; + goto fail; + } + + err = req_connect(req); + if (err) + goto fail; + + return; + + fail: + req_close(req, err, NULL); + mem_deref(req); +} + + +/** + * Send an HTTP request + * + * @param reqp Pointer to allocated HTTP request object + * @param cli HTTP Client + * @param met Request method + * @param uri Request URI + * @param resph Response handler + * @param datah Content handler (optional) + * @param arg Handler argument + * @param fmt Formatted HTTP headers and body (optional) + * + * @return 0 if success, otherwise errorcode + */ +int http_request(struct http_req **reqp, struct http_cli *cli, const char *met, + const char *uri, http_resp_h *resph, http_data_h *datah, + void *arg, const char *fmt, ...) +{ + struct pl scheme, host, port, path; + struct http_req *req; + uint16_t defport; + bool secure; + va_list ap; + int err; + + if (!reqp || !cli || !met || !uri) + return EINVAL; + + if (re_regex(uri, strlen(uri), "[a-z]+://[^:/]+[:]*[0-9]*[^]+", + &scheme, &host, NULL, &port, &path) || scheme.p != uri) + return EINVAL; + + if (!pl_strcasecmp(&scheme, "http")) { + secure = false; + defport = 80; + } +#ifdef USE_TLS + else if (!pl_strcasecmp(&scheme, "https")) { + secure = true; + defport = 443; + } +#endif + else + return ENOTSUP; + + req = mem_zalloc(sizeof(*req), req_destructor); + if (!req) + return ENOMEM; + + req->tls = mem_ref(cli->tls); + req->secure = secure; + req->port = pl_isset(&port) ? pl_u32(&port) : defport; + req->resph = resph; + req->datah = datah; + req->arg = arg; + + err = pl_strdup(&req->host, &host); + if (err) + goto out; + + req->mbreq = mbuf_alloc(1024); + if (!req->mbreq) { + err = ENOMEM; + goto out; + } + + err = mbuf_printf(req->mbreq, + "%s %r HTTP/1.1\r\n" + "Host: %r\r\n", + met, &path, &host); + if (fmt) { + va_start(ap, fmt); + err |= mbuf_vprintf(req->mbreq, fmt, ap); + va_end(ap); + } + else { + err |= mbuf_write_str(req->mbreq, "\r\n"); + } + if (err) + goto out; + + req->mbreq->pos = 0; + + if (!sa_set_str(&req->srvv[0], req->host, req->port)) { + + req->srvc = 1; + + err = req_connect(req); + if (err) + goto out; + } + else { + err = dnsc_query(&req->dq, cli->dnsc, req->host, + DNS_TYPE_A, DNS_CLASS_IN, true, + query_handler, req); + if (err) + goto out; + } + + out: + if (err) + mem_deref(req); + else { + req->reqp = reqp; + *reqp = req; + } + + return err; +} + + +/** + * Allocate an HTTP client instance + * + * @param clip Pointer to allocated HTTP client + * @param dnsc DNS Client + * + * @return 0 if success, otherwise errorcode + */ +int http_client_alloc(struct http_cli **clip, struct dnsc *dnsc) +{ + struct http_cli *cli; + int err; + + if (!clip || !dnsc) + return EINVAL; + + cli = mem_zalloc(sizeof(*cli), cli_destructor); + if (!cli) + return ENOMEM; + +#ifdef USE_TLS + err = tls_alloc(&cli->tls, TLS_METHOD_SSLV23, NULL, NULL); +#else + err = 0; +#endif + if (err) + goto out; + + cli->dnsc = mem_ref(dnsc); + + out: + if (err) + mem_deref(cli); + else + *clip = cli; + + return err; +} diff --git a/src/http/mod.mk b/src/http/mod.mk index 8cfb6c9..a194109 100644 --- a/src/http/mod.mk +++ b/src/http/mod.mk @@ -5,5 +5,6 @@ # SRCS += http/auth.c +SRCS += http/client.c SRCS += http/msg.c SRCS += http/server.c