blob: 39afa39cb4cc61da530a83f0f94bdc0dcd81fdcc [file] [log] [blame]
James Kuszmaul82f6c042021-01-17 11:30:16 -08001/**
2 * @file http/client.c HTTP Client
3 *
4 * Copyright (C) 2011 Creytiv.com
5 */
6
7#include <string.h>
8#include <re_types.h>
9#include <re_mem.h>
10#include <re_mbuf.h>
11#include <re_sa.h>
12#include <re_list.h>
13#include <re_hash.h>
14#include <re_fmt.h>
15#include <re_tmr.h>
16#include <re_srtp.h>
17#include <re_tcp.h>
18#include <re_tls.h>
19#include <re_dns.h>
20#include <re_msg.h>
21#include <re_http.h>
22#include "http.h"
23
24
25enum {
26 CONN_TIMEOUT = 30000,
27 RECV_TIMEOUT = 60000,
28 IDLE_TIMEOUT = 900000,
29 BUFSIZE_MAX = 524288,
30 CONN_BSIZE = 256,
31};
32
33struct http_cli {
34 struct list reql;
35 struct hash *ht_conn;
36 struct dnsc *dnsc;
37 struct tls *tls;
38};
39
40struct conn;
41
42struct http_req {
43 struct http_chunk chunk;
44 struct sa srvv[16];
45 struct le le;
46 struct http_req **reqp;
47 struct http_cli *cli;
48 struct http_msg *msg;
49 struct dns_query *dq;
50 struct conn *conn;
51 struct mbuf *mbreq;
52 struct mbuf *mb;
53 char *host;
54 http_resp_h *resph;
55 http_data_h *datah;
56 http_conn_h *connh;
57 void *arg;
58 size_t rx_len;
59 unsigned srvc;
60 uint16_t port;
61 bool chunked;
62 bool secure;
63 bool close;
64};
65
66
67struct conn {
68 struct tmr tmr;
69 struct sa addr;
70 struct le he;
71 struct http_req *req;
72 struct tls_conn *sc;
73 struct tcp_conn *tc;
74 uint64_t usec;
75};
76
77
78static void req_close(struct http_req *req, int err,
79 const struct http_msg *msg);
80static int req_connect(struct http_req *req);
81static void timeout_handler(void *arg);
82
83
84static void cli_destructor(void *arg)
85{
86 struct http_cli *cli = arg;
87 struct le *le = cli->reql.head;
88
89 while (le) {
90 struct http_req *req = le->data;
91
92 le = le->next;
93 req_close(req, ECONNABORTED, NULL);
94 }
95
96 hash_flush(cli->ht_conn);
97 mem_deref(cli->ht_conn);
98 mem_deref(cli->dnsc);
99 mem_deref(cli->tls);
100}
101
102
103static void req_destructor(void *arg)
104{
105 struct http_req *req = arg;
106
107 list_unlink(&req->le);
108 mem_deref(req->msg);
109 mem_deref(req->dq);
110 mem_deref(req->conn);
111 mem_deref(req->mbreq);
112 mem_deref(req->mb);
113 mem_deref(req->host);
114}
115
116
117static void conn_destructor(void *arg)
118{
119 struct conn *conn = arg;
120
121 tmr_cancel(&conn->tmr);
122 hash_unlink(&conn->he);
123 mem_deref(conn->sc);
124 mem_deref(conn->tc);
125}
126
127
128static void conn_idle(struct conn *conn)
129{
130 tmr_start(&conn->tmr, IDLE_TIMEOUT, timeout_handler, conn);
131 conn->req = NULL;
132}
133
134
135static void req_close(struct http_req *req, int err,
136 const struct http_msg *msg)
137{
138 list_unlink(&req->le);
139 req->dq = mem_deref(req->dq);
140 req->datah = NULL;
141
142 if (req->conn) {
143 if (req->connh)
144 req->connh(req->conn->tc, req->conn->sc, req->arg);
145
146 if (err || req->close || req->connh)
147 mem_deref(req->conn);
148 else
149 conn_idle(req->conn);
150
151 req->conn = NULL;
152 }
153
154 req->connh = NULL;
155
156 if (req->reqp) {
157 *req->reqp = NULL;
158 req->reqp = NULL;
159 }
160
161 if (req->resph) {
162 if (msg)
163 msg->mb->pos = 0;
164
165 req->resph(err, msg, req->arg);
166 req->resph = NULL;
167 }
168
169 mem_deref(req);
170}
171
172
173static void try_next(struct conn *conn, int err)
174{
175 struct http_req *req = conn->req;
176 bool retry = conn->usec > 1;
177
178 mem_deref(conn);
179
180 if (!req)
181 return;
182
183 req->conn = NULL;
184
185 if (retry)
186 ++req->srvc;
187
188 if (req->srvc > 0 && !req->msg) {
189
190 err = req_connect(req);
191 if (!err)
192 return;
193 }
194
195 req_close(req, err, NULL);
196}
197
198
199static int write_body_buf(struct http_msg *msg, const uint8_t *buf, size_t sz)
200{
201 if ((msg->mb->pos + sz) > BUFSIZE_MAX)
202 return EOVERFLOW;
203
204 return mbuf_write_mem(msg->mb, buf, sz);
205}
206
207
208static int write_body(struct http_req *req, struct mbuf *mb)
209{
210 const size_t size = min(mbuf_get_left(mb), req->rx_len);
211 int err;
212
213 if (size == 0)
214 return 0;
215
216 if (req->datah)
217 err = req->datah(mbuf_buf(mb), size, req->msg, req->arg);
218 else
219 err = write_body_buf(req->msg, mbuf_buf(mb), size);
220
221 if (err)
222 return err;
223
224 req->rx_len -= size;
225 mb->pos += size;
226
227 return 0;
228}
229
230
231static int req_recv(struct http_req *req, struct mbuf *mb, bool *last)
232{
233 int err;
234
235 *last = false;
236
237 if (!req->chunked) {
238
239 err = write_body(req, mb);
240 if (err)
241 return err;
242
243 if (req->rx_len == 0)
244 *last = true;
245
246 return 0;
247 }
248
249 while (mbuf_get_left(mb)) {
250
251 if (req->rx_len == 0) {
252
253 err = http_chunk_decode(&req->chunk, mb, &req->rx_len);
254 if (err == ENODATA)
255 return 0;
256 else if (err)
257 return err;
258 else if (req->rx_len == 0) {
259 *last = true;
260 return 0;
261 }
262 }
263
264 err = write_body(req, mb);
265 if (err)
266 return err;
267 }
268
269 return 0;
270}
271
272
273static void timeout_handler(void *arg)
274{
275 struct conn *conn = arg;
276
277 try_next(conn, ETIMEDOUT);
278}
279
280
281static void estab_handler(void *arg)
282{
283 struct conn *conn = arg;
284 struct http_req *req = conn->req;
285 int err;
286
287 if (!req)
288 return;
289
290 err = tcp_send(conn->tc, req->mbreq);
291 if (err) {
292 try_next(conn, err);
293 return;
294 }
295
296 tmr_start(&conn->tmr, RECV_TIMEOUT, timeout_handler, conn);
297}
298
299
300static void recv_handler(struct mbuf *mb, void *arg)
301{
302 const struct http_hdr *hdr;
303 struct conn *conn = arg;
304 struct http_req *req = conn->req;
305 size_t pos;
306 bool last;
307 int err;
308
309 if (!req)
310 return;
311
312 if (req->msg) {
313 err = req_recv(req, mb, &last);
314 if (err || last)
315 goto out;
316
317 return;
318 }
319
320 if (req->mb) {
321
322 const size_t len = mbuf_get_left(mb);
323
324 if ((mbuf_get_left(req->mb) + len) > BUFSIZE_MAX) {
325 err = EOVERFLOW;
326 goto out;
327 }
328
329 pos = req->mb->pos;
330 req->mb->pos = req->mb->end;
331
332 err = mbuf_write_mem(req->mb, mbuf_buf(mb), len);
333 if (err)
334 goto out;
335
336 req->mb->pos = pos;
337 }
338 else {
339 req->mb = mem_ref(mb);
340 }
341
342 pos = req->mb->pos;
343
344 err = http_msg_decode(&req->msg, req->mb, false);
345 if (err) {
346 if (err == ENODATA) {
347 req->mb->pos = pos;
348 return;
349 }
350 goto out;
351 }
352
353 if (req->datah)
354 tmr_cancel(&conn->tmr);
355
356 hdr = http_msg_hdr(req->msg, HTTP_HDR_CONNECTION);
357 if (hdr && !pl_strcasecmp(&hdr->val, "close"))
358 req->close = true;
359
360 if (http_msg_hdr_has_value(req->msg, HTTP_HDR_TRANSFER_ENCODING,
361 "chunked"))
362 req->chunked = true;
363 else
364 req->rx_len = req->msg->clen;
365
366 err = req_recv(req, req->mb, &last);
367 if (err || last)
368 goto out;
369
370 return;
371
372 out:
373 req_close(req, err, req->msg);
374}
375
376
377static void close_handler(int err, void *arg)
378{
379 struct conn *conn = arg;
380
381 try_next(conn, err ? err : ECONNRESET);
382}
383
384
385static bool conn_cmp(struct le *le, void *arg)
386{
387 const struct conn *conn = le->data;
388 const struct http_req *req = arg;
389
390 if (!sa_cmp(&req->srvv[req->srvc], &conn->addr, SA_ALL))
391 return false;
392
393 if (req->secure != !!conn->sc)
394 return false;
395
396 return conn->req == NULL;
397}
398
399
400static int conn_connect(struct http_req *req)
401{
402 const struct sa *addr = &req->srvv[req->srvc];
403 struct conn *conn;
404 int err;
405
406 conn = list_ledata(hash_lookup(req->cli->ht_conn,
407 sa_hash(addr, SA_ALL), conn_cmp, req));
408 if (conn) {
409 err = tcp_send(conn->tc, req->mbreq);
410 if (!err) {
411 tmr_start(&conn->tmr, RECV_TIMEOUT,
412 timeout_handler, conn);
413
414 req->conn = conn;
415 conn->req = req;
416
417 ++conn->usec;
418
419 return 0;
420 }
421
422 mem_deref(conn);
423 }
424
425 conn = mem_zalloc(sizeof(*conn), conn_destructor);
426 if (!conn)
427 return ENOMEM;
428
429 hash_append(req->cli->ht_conn, sa_hash(addr, SA_ALL), &conn->he, conn);
430
431 conn->addr = *addr;
432 conn->usec = 1;
433
434 err = tcp_connect(&conn->tc, addr, estab_handler, recv_handler,
435 close_handler, conn);
436 if (err)
437 goto out;
438
439#ifdef USE_TLS
440 if (req->secure) {
441
442 err = tls_start_tcp(&conn->sc, req->cli->tls, conn->tc, 0);
443 if (err)
444 goto out;
445 }
446#endif
447
448 tmr_start(&conn->tmr, CONN_TIMEOUT, timeout_handler, conn);
449
450 req->conn = conn;
451 conn->req = req;
452
453 out:
454 if (err)
455 mem_deref(conn);
456
457 return err;
458}
459
460
461static int req_connect(struct http_req *req)
462{
463 int err = EINVAL;
464
465 while (req->srvc > 0) {
466
467 --req->srvc;
468
469 req->mb = mem_deref(req->mb);
470
471 err = conn_connect(req);
472 if (!err)
473 break;
474 }
475
476 return err;
477}
478
479
480static bool rr_handler(struct dnsrr *rr, void *arg)
481{
482 struct http_req *req = arg;
483
484 if (req->srvc >= ARRAY_SIZE(req->srvv))
485 return true;
486
487 switch (rr->type) {
488
489 case DNS_TYPE_A:
490 sa_set_in(&req->srvv[req->srvc++], rr->rdata.a.addr,
491 req->port);
492 break;
493
494 case DNS_TYPE_AAAA:
495 sa_set_in6(&req->srvv[req->srvc++], rr->rdata.aaaa.addr,
496 req->port);
497 break;
498 }
499
500 return false;
501}
502
503
504static void query_handler(int err, const struct dnshdr *hdr, struct list *ansl,
505 struct list *authl, struct list *addl, void *arg)
506{
507 struct http_req *req = arg;
508 (void)hdr;
509 (void)authl;
510 (void)addl;
511
512 dns_rrlist_apply2(ansl, req->host, DNS_TYPE_A, DNS_TYPE_AAAA,
513 DNS_CLASS_IN, true, rr_handler, req);
514 if (req->srvc == 0) {
515 err = err ? err : EDESTADDRREQ;
516 goto fail;
517 }
518
519 err = req_connect(req);
520 if (err)
521 goto fail;
522
523 return;
524
525 fail:
526 req_close(req, err, NULL);
527}
528
529
530/**
531 * Send an HTTP request
532 *
533 * @param reqp Pointer to allocated HTTP request object
534 * @param cli HTTP Client
535 * @param met Request method
536 * @param uri Request URI
537 * @param resph Response handler
538 * @param datah Content handler (optional)
539 * @param arg Handler argument
540 * @param fmt Formatted HTTP headers and body (optional)
541 *
542 * @return 0 if success, otherwise errorcode
543 */
544int http_request(struct http_req **reqp, struct http_cli *cli, const char *met,
545 const char *uri, http_resp_h *resph, http_data_h *datah,
546 void *arg, const char *fmt, ...)
547{
548 struct pl scheme, host, port, path;
549 struct http_req *req;
550 uint16_t defport;
551 bool secure;
552 va_list ap;
553 int err;
554
555 if (!cli || !met || !uri)
556 return EINVAL;
557
558 if (re_regex(uri, strlen(uri), "[a-z]+://[^:/]+[:]*[0-9]*[^]+",
559 &scheme, &host, NULL, &port, &path) || scheme.p != uri)
560 return EINVAL;
561
562 if (!pl_strcasecmp(&scheme, "http") ||
563 !pl_strcasecmp(&scheme, "ws")) {
564 secure = false;
565 defport = 80;
566 }
567#ifdef USE_TLS
568 else if (!pl_strcasecmp(&scheme, "https") ||
569 !pl_strcasecmp(&scheme, "wss")) {
570 secure = true;
571 defport = 443;
572 }
573#endif
574 else
575 return ENOTSUP;
576
577 req = mem_zalloc(sizeof(*req), req_destructor);
578 if (!req)
579 return ENOMEM;
580
581 list_append(&cli->reql, &req->le, req);
582
583 req->cli = cli;
584 req->secure = secure;
585 req->port = pl_isset(&port) ? pl_u32(&port) : defport;
586 req->resph = resph;
587 req->datah = datah;
588 req->arg = arg;
589
590 err = pl_strdup(&req->host, &host);
591 if (err)
592 goto out;
593
594 req->mbreq = mbuf_alloc(1024);
595 if (!req->mbreq) {
596 err = ENOMEM;
597 goto out;
598 }
599
600 err = mbuf_printf(req->mbreq,
601 "%s %r HTTP/1.1\r\n"
602 "Host: %r\r\n",
603 met, &path, &host);
604 if (fmt) {
605 va_start(ap, fmt);
606 err |= mbuf_vprintf(req->mbreq, fmt, ap);
607 va_end(ap);
608 }
609 else {
610 err |= mbuf_write_str(req->mbreq, "\r\n");
611 }
612 if (err)
613 goto out;
614
615 req->mbreq->pos = 0;
616
617 if (!sa_set_str(&req->srvv[0], req->host, req->port)) {
618
619 req->srvc = 1;
620
621 err = req_connect(req);
622 if (err)
623 goto out;
624 }
625 else {
626 err = dnsc_query(&req->dq, cli->dnsc, req->host,
627 DNS_TYPE_A, DNS_CLASS_IN, true,
628 query_handler, req);
629 if (err)
630 goto out;
631 }
632
633 out:
634 if (err)
635 mem_deref(req);
636 else if (reqp) {
637 req->reqp = reqp;
638 *reqp = req;
639 }
640
641 return err;
642}
643
644
645/**
646 * Set HTTP request connection handler
647 *
648 * @param req HTTP request object
649 * @param connh Connection handler
650 */
651void http_req_set_conn_handler(struct http_req *req, http_conn_h *connh)
652{
653 if (!req)
654 return;
655
656 req->connh = connh;
657}
658
659
660/**
661 * Allocate an HTTP client instance
662 *
663 * @param clip Pointer to allocated HTTP client
664 * @param dnsc DNS Client
665 *
666 * @return 0 if success, otherwise errorcode
667 */
668int http_client_alloc(struct http_cli **clip, struct dnsc *dnsc)
669{
670 struct http_cli *cli;
671 int err;
672
673 if (!clip || !dnsc)
674 return EINVAL;
675
676 cli = mem_zalloc(sizeof(*cli), cli_destructor);
677 if (!cli)
678 return ENOMEM;
679
680 err = hash_alloc(&cli->ht_conn, CONN_BSIZE);
681 if (err)
682 goto out;
683
684#ifdef USE_TLS
685 err = tls_alloc(&cli->tls, TLS_METHOD_SSLV23, NULL, NULL);
686#else
687 err = 0;
688#endif
689 if (err)
690 goto out;
691
692 cli->dnsc = mem_ref(dnsc);
693
694 out:
695 if (err)
696 mem_deref(cli);
697 else
698 *clip = cli;
699
700 return err;
701}