/** * @file http/server.c HTTP Server * * Copyright (C) 2011 Creytiv.com */ #include #include #include #include #include #include #include #include #include #include #include #include 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; } 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", "\n" "\n" "%u %s\n" "

%u %s

\n" "\n", scode, reason, scode, reason); }