| /** |
| * @file http/server.c HTTP Server |
| * |
| * Copyright (C) 2011 Creytiv.com |
| */ |
| |
| #include <re_types.h> |
| #include <re_mem.h> |
| #include <re_mbuf.h> |
| #include <re_sa.h> |
| #include <re_list.h> |
| #include <re_fmt.h> |
| #include <re_tmr.h> |
| #include <re_srtp.h> |
| #include <re_tcp.h> |
| #include <re_tls.h> |
| #include <re_msg.h> |
| #include <re_http.h> |
| |
| |
| enum { |
| TIMEOUT_IDLE = 600000, |
| TIMEOUT_INIT = 10000, |
| BUFSIZE_MAX = 524288, |
| }; |
| |
| struct http_sock { |
| struct list connl; |
| struct tcp_sock *ts; |
| struct tls *tls; |
| http_req_h *reqh; |
| void *arg; |
| }; |
| |
| struct http_conn { |
| struct le le; |
| struct tmr tmr; |
| struct sa peer; |
| struct http_sock *sock; |
| struct tcp_conn *tc; |
| struct tls_conn *sc; |
| struct mbuf *mb; |
| }; |
| |
| |
| static void conn_close(struct http_conn *conn); |
| |
| |
| static void sock_destructor(void *arg) |
| { |
| struct http_sock *sock = arg; |
| struct le *le; |
| |
| for (le=sock->connl.head; le;) { |
| |
| struct http_conn *conn = le->data; |
| |
| le = le->next; |
| |
| conn_close(conn); |
| mem_deref(conn); |
| } |
| |
| mem_deref(sock->tls); |
| mem_deref(sock->ts); |
| } |
| |
| |
| static void conn_destructor(void *arg) |
| { |
| struct http_conn *conn = arg; |
| |
| list_unlink(&conn->le); |
| tmr_cancel(&conn->tmr); |
| mem_deref(conn->sc); |
| mem_deref(conn->tc); |
| mem_deref(conn->mb); |
| } |
| |
| |
| static void conn_close(struct http_conn *conn) |
| { |
| list_unlink(&conn->le); |
| tmr_cancel(&conn->tmr); |
| conn->sc = mem_deref(conn->sc); |
| conn->tc = mem_deref(conn->tc); |
| conn->sock = NULL; |
| } |
| |
| |
| static void timeout_handler(void *arg) |
| { |
| struct http_conn *conn = arg; |
| |
| conn_close(conn); |
| mem_deref(conn); |
| } |
| |
| |
| static void recv_handler(struct mbuf *mb, void *arg) |
| { |
| struct http_conn *conn = arg; |
| int err = 0; |
| |
| if (conn->mb) { |
| |
| const size_t len = mbuf_get_left(mb), pos = conn->mb->pos; |
| |
| if ((mbuf_get_left(conn->mb) + len) > BUFSIZE_MAX) { |
| err = EOVERFLOW; |
| goto out; |
| } |
| |
| conn->mb->pos = conn->mb->end; |
| |
| err = mbuf_write_mem(conn->mb, mbuf_buf(mb), len); |
| if (err) |
| goto out; |
| |
| conn->mb->pos = pos; |
| } |
| else { |
| conn->mb = mem_ref(mb); |
| } |
| |
| while (conn->mb) { |
| size_t end, pos = conn->mb->pos; |
| struct http_msg *msg; |
| |
| err = http_msg_decode(&msg, conn->mb, true); |
| if (err) { |
| if (err == ENODATA) { |
| conn->mb->pos = pos; |
| err = 0; |
| break; |
| } |
| |
| goto out; |
| } |
| |
| if (mbuf_get_left(conn->mb) < msg->clen) { |
| conn->mb->pos = pos; |
| mem_deref(msg); |
| break; |
| } |
| |
| mem_deref(msg->mb); |
| msg->mb = mem_ref(msg->_mb); |
| |
| mb = conn->mb; |
| |
| end = mb->end; |
| mb->end = mb->pos + msg->clen; |
| |
| if (end > mb->end) { |
| struct mbuf *mbn = mbuf_alloc(end - mb->end); |
| if (!mbn) { |
| mem_deref(msg); |
| err = ENOMEM; |
| goto out; |
| } |
| |
| (void)mbuf_write_mem(mbn, mb->buf + mb->end, |
| end - mb->end); |
| mbn->pos = 0; |
| |
| mem_deref(conn->mb); |
| conn->mb = mbn; |
| } |
| else { |
| conn->mb = mem_deref(conn->mb); |
| } |
| |
| if (conn->sock) |
| conn->sock->reqh(conn, msg, conn->sock->arg); |
| |
| mem_deref(msg); |
| |
| if (!conn->tc) { |
| err = ENOTCONN; |
| goto out; |
| } |
| |
| tmr_start(&conn->tmr, TIMEOUT_IDLE, timeout_handler, conn); |
| } |
| |
| out: |
| if (err) { |
| conn_close(conn); |
| mem_deref(conn); |
| } |
| } |
| |
| |
| static void close_handler(int err, void *arg) |
| { |
| struct http_conn *conn = arg; |
| (void)err; |
| |
| conn_close(conn); |
| mem_deref(conn); |
| } |
| |
| |
| static void connect_handler(const struct sa *peer, void *arg) |
| { |
| struct http_sock *sock = arg; |
| struct http_conn *conn; |
| int err; |
| |
| conn = mem_zalloc(sizeof(*conn), conn_destructor); |
| if (!conn) { |
| err = ENOMEM; |
| goto out; |
| } |
| |
| list_append(&sock->connl, &conn->le, conn); |
| conn->peer = *peer; |
| conn->sock = sock; |
| |
| err = tcp_accept(&conn->tc, sock->ts, NULL, recv_handler, |
| close_handler, conn); |
| if (err) |
| goto out; |
| |
| #ifdef USE_TLS |
| if (sock->tls) { |
| err = tls_start_tcp(&conn->sc, sock->tls, conn->tc, 0); |
| if (err) |
| goto out; |
| } |
| #endif |
| |
| tmr_start(&conn->tmr, TIMEOUT_INIT, timeout_handler, conn); |
| |
| out: |
| if (err) { |
| mem_deref(conn); |
| tcp_reject(sock->ts); |
| } |
| } |
| |
| |
| /** |
| * Create an HTTP socket |
| * |
| * @param sockp Pointer to returned HTTP Socket |
| * @param laddr Network address to listen on |
| * @param reqh Request handler |
| * @param arg Handler argument |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| int http_listen(struct http_sock **sockp, const struct sa *laddr, |
| http_req_h *reqh, void *arg) |
| { |
| struct http_sock *sock; |
| int err; |
| |
| if (!sockp || !laddr || !reqh) |
| return EINVAL; |
| |
| sock = mem_zalloc(sizeof(*sock), sock_destructor); |
| if (!sock) |
| return ENOMEM; |
| |
| err = tcp_listen(&sock->ts, laddr, connect_handler, sock); |
| if (err) |
| goto out; |
| |
| sock->reqh = reqh; |
| sock->arg = arg; |
| |
| out: |
| if (err) |
| mem_deref(sock); |
| else |
| *sockp = sock; |
| |
| return err; |
| } |
| |
| |
| /** |
| * Create an HTTP secure socket |
| * |
| * @param sockp Pointer to returned HTTP Socket |
| * @param laddr Network address to listen on |
| * @param cert File path of TLS certificate |
| * @param reqh Request handler |
| * @param arg Handler argument |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| int https_listen(struct http_sock **sockp, const struct sa *laddr, |
| const char *cert, http_req_h *reqh, void *arg) |
| { |
| struct http_sock *sock; |
| int err; |
| |
| if (!sockp || !laddr || !cert || !reqh) |
| return EINVAL; |
| |
| err = http_listen(&sock, laddr, reqh, arg); |
| if (err) |
| return err; |
| |
| #ifdef USE_TLS |
| err = tls_alloc(&sock->tls, TLS_METHOD_SSLV23, cert, NULL); |
| #else |
| err = EPROTONOSUPPORT; |
| #endif |
| if (err) |
| goto out; |
| |
| out: |
| if (err) |
| mem_deref(sock); |
| else |
| *sockp = sock; |
| |
| return err; |
| } |
| |
| |
| /** |
| * Get the TCP socket of an HTTP socket |
| * |
| * @param sock HTTP socket |
| * |
| * @return TCP socket |
| */ |
| struct tcp_sock *http_sock_tcp(struct http_sock *sock) |
| { |
| return sock ? sock->ts : NULL; |
| } |
| |
| |
| /** |
| * Get the peer address of an HTTP connection |
| * |
| * @param conn HTTP connection |
| * |
| * @return Peer address |
| */ |
| const struct sa *http_conn_peer(const struct http_conn *conn) |
| { |
| return conn ? &conn->peer : NULL; |
| } |
| |
| |
| /** |
| * Get the TCP connection of an HTTP connection |
| * |
| * @param conn HTTP connection |
| * |
| * @return TCP connection |
| */ |
| struct tcp_conn *http_conn_tcp(struct http_conn *conn) |
| { |
| return conn ? conn->tc : NULL; |
| } |
| |
| |
| /** |
| * Get the TLS connection of an HTTP connection |
| * |
| * @param conn HTTP connection |
| * |
| * @return TLS connection |
| */ |
| struct tls_conn *http_conn_tls(struct http_conn *conn) |
| { |
| return conn ? conn->sc : NULL; |
| } |
| |
| |
| /** |
| * Close the HTTP connection |
| * |
| * @param conn HTTP connection |
| */ |
| void http_conn_close(struct http_conn *conn) |
| { |
| if (!conn) |
| return; |
| |
| conn->sc = mem_deref(conn->sc); |
| conn->tc = mem_deref(conn->tc); |
| } |
| |
| |
| static int http_vreply(struct http_conn *conn, uint16_t scode, |
| const char *reason, const char *fmt, va_list ap) |
| { |
| struct mbuf *mb; |
| int err; |
| |
| if (!conn || !scode || !reason) |
| return EINVAL; |
| |
| if (!conn->tc) |
| return ENOTCONN; |
| |
| mb = mbuf_alloc(8192); |
| if (!mb) |
| return ENOMEM; |
| |
| err = mbuf_printf(mb, "HTTP/1.1 %u %s\r\n", scode, reason); |
| if (fmt) |
| err |= mbuf_vprintf(mb, fmt, ap); |
| else |
| err |= mbuf_write_str(mb, "Content-Length: 0\r\n\r\n"); |
| if (err) |
| goto out; |
| |
| mb->pos = 0; |
| |
| err = tcp_send(conn->tc, mb); |
| if (err) |
| goto out; |
| |
| out: |
| mem_deref(mb); |
| |
| return err; |
| } |
| |
| |
| /** |
| * Send an HTTP response |
| * |
| * @param conn HTTP connection |
| * @param scode Response status code |
| * @param reason Response reason phrase |
| * @param fmt Formatted HTTP message |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| int http_reply(struct http_conn *conn, uint16_t scode, const char *reason, |
| const char *fmt, ...) |
| { |
| va_list ap; |
| int err; |
| |
| va_start(ap, fmt); |
| err = http_vreply(conn, scode, reason, fmt, ap); |
| va_end(ap); |
| |
| return err; |
| } |
| |
| |
| /** |
| * Send an HTTP response with content formatting |
| * |
| * @param conn HTTP connection |
| * @param scode Response status code |
| * @param reason Response reason phrase |
| * @param ctype Content type |
| * @param fmt Formatted HTTP content |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| int http_creply(struct http_conn *conn, uint16_t scode, const char *reason, |
| const char *ctype, const char *fmt, ...) |
| { |
| struct mbuf *mb; |
| va_list ap; |
| int err; |
| |
| if (!ctype || !fmt) |
| return EINVAL; |
| |
| mb = mbuf_alloc(8192); |
| if (!mb) |
| return ENOMEM; |
| |
| va_start(ap, fmt); |
| err = mbuf_vprintf(mb, fmt, ap); |
| va_end(ap); |
| if (err) |
| goto out; |
| |
| err = http_reply(conn, scode, reason, |
| "Content-Type: %s\r\n" |
| "Content-Length: %zu\r\n" |
| "\r\n" |
| "%b", |
| ctype, |
| mb->end, |
| mb->buf, mb->end); |
| if (err) |
| goto out; |
| |
| out: |
| mem_deref(mb); |
| |
| return err; |
| } |
| |
| |
| /** |
| * Send an HTTP error response |
| * |
| * @param conn HTTP connection |
| * @param scode Response status code |
| * @param reason Response reason phrase |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| int http_ereply(struct http_conn *conn, uint16_t scode, const char *reason) |
| { |
| return http_creply(conn, scode, reason, "text/html", |
| "<!DOCTYPE html>\n" |
| "<html>\n" |
| "<head><title>%u %s</title></head>\n" |
| "<body><h2>%u %s</h2></body>\n" |
| "</html>\n", |
| scode, reason, |
| scode, reason); |
| } |