diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..ef7ef75 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 2.8) + +project(libuhttp C) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/Modules/") + +#set(CMAKE_VERBOSE_MAKEFILE ON) +#add_definitions(--DUH_DEBUG) + +add_definitions(-O -Wall -Werror --std=gnu99 -D_GNU_SOURCE) + +include_directories(${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}/src) + +add_subdirectory(src) +add_subdirectory(example) diff --git a/README.md b/README.md index bd45011..bdfdf6a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # libuhttp -a very tiny and fast HTTP library based on libev for Embedded Linux + +![](https://img.shields.io/badge/license-GPLV3-brightgreen.svg?style=plastic "License") + +A very tiny and fast HTTP library based on [libev](http://software.schmorp.de/pkg/libev.html) and +[http-parser](https://github.com/nodejs/http-parser) for Embedded Linux. diff --git a/cmake/Modules/FindHttpParser.cmake b/cmake/Modules/FindHttpParser.cmake new file mode 100755 index 0000000..4c285bf --- /dev/null +++ b/cmake/Modules/FindHttpParser.cmake @@ -0,0 +1,38 @@ +# - Try to find http-parser +# Once done this will define +# HTTPPARSER_FOUND - System has http-parser +# HTTPPARSER_INCLUDE_DIRS - The http-parser include directories +# HTTPPARSER_LIBRARIES - The libraries needed to use http-parser + +find_path(HTTPPARSER_INCLUDE_DIR + NAMES http_parser.h +) +find_library(HTTPPARSER_LIBRARY + NAMES http_parser +) + +if(HTTPPARSER_INCLUDE_DIR) + file(STRINGS "${HTTPPARSER_INCLUDE_DIR}/http_parser.h" + HTTP_PARSER_VERSION_MAJOR REGEX "^#define[ \t]+HTTP_PARSER_VERSION_MAJOR[ \t]+[0-9]+") + file(STRINGS "${HTTPPARSER_INCLUDE_DIR}/http_parser.h" + HTTP_PARSER_VERSION_MINOR REGEX "^#define[ \t]+HTTP_PARSER_VERSION_MINOR[ \t]+[0-9]+") + string(REGEX REPLACE "[^0-9]+" "" HTTP_PARSER_VERSION_MAJOR "${HTTP_PARSER_VERSION_MAJOR}") + string(REGEX REPLACE "[^0-9]+" "" HTTP_PARSER_VERSION_MINOR "${HTTP_PARSER_VERSION_MINOR}") + set(HTTP_PARSER_VERSION "${HTTP_PARSER_VERSION_MAJOR}.${HTTP_PARSER_VERSION_MINOR}") + unset(HTTP_PARSER_VERSION_MINOR) + unset(HTTP_PARSER_VERSION_MAJOR) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set HTTPPARSER_FOUND to TRUE +# if all listed variables are TRUE and the requested version matches. +find_package_handle_standard_args(HttpParser REQUIRED_VARS + HTTPPARSER_LIBRARY HTTPPARSER_INCLUDE_DIR + VERSION_VAR HTTP_PARSER_VERSION) + +if(HTTPPARSER_FOUND) + set(HTTPPARSER_LIBRARIES ${HTTPPARSER_LIBRARY}) + set(HTTPPARSER_INCLUDE_DIRS ${HTTPPARSER_INCLUDE_DIR}) +endif() + +mark_as_advanced(HTTPPARSER_INCLUDE_DIR HTTPPARSER_LIBRARY) \ No newline at end of file diff --git a/cmake/Modules/FindLibev.cmake b/cmake/Modules/FindLibev.cmake new file mode 100755 index 0000000..1fbefa8 --- /dev/null +++ b/cmake/Modules/FindLibev.cmake @@ -0,0 +1,38 @@ +# - Try to find libev +# Once done this will define +# LIBEV_FOUND - System has libev +# LIBEV_INCLUDE_DIRS - The libev include directories +# LIBEV_LIBRARIES - The libraries needed to use libev + +find_path(LIBEV_INCLUDE_DIR + NAMES ev.h +) +find_library(LIBEV_LIBRARY + NAMES ev +) + +if(LIBEV_INCLUDE_DIR) + file(STRINGS "${LIBEV_INCLUDE_DIR}/ev.h" + LIBEV_VERSION_MAJOR REGEX "^#define[ \t]+EV_VERSION_MAJOR[ \t]+[0-9]+") + file(STRINGS "${LIBEV_INCLUDE_DIR}/ev.h" + LIBEV_VERSION_MINOR REGEX "^#define[ \t]+EV_VERSION_MINOR[ \t]+[0-9]+") + string(REGEX REPLACE "[^0-9]+" "" LIBEV_VERSION_MAJOR "${LIBEV_VERSION_MAJOR}") + string(REGEX REPLACE "[^0-9]+" "" LIBEV_VERSION_MINOR "${LIBEV_VERSION_MINOR}") + set(LIBEV_VERSION "${LIBEV_VERSION_MAJOR}.${LIBEV_VERSION_MINOR}") + unset(LIBEV_VERSION_MINOR) + unset(LIBEV_VERSION_MAJOR) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBEV_FOUND to TRUE +# if all listed variables are TRUE and the requested version matches. +find_package_handle_standard_args(Libev REQUIRED_VARS + LIBEV_LIBRARY LIBEV_INCLUDE_DIR + VERSION_VAR LIBEV_VERSION) + +if(LIBEV_FOUND) + set(LIBEV_LIBRARIES ${LIBEV_LIBRARY}) + set(LIBEV_INCLUDE_DIRS ${LIBEV_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBEV_INCLUDE_DIR LIBEV_LIBRARY) \ No newline at end of file diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100755 index 0000000..ae751a0 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(helloworld helloworld.c) +target_link_libraries(helloworld uhttp) diff --git a/example/helloworld.c b/example/helloworld.c new file mode 100755 index 0000000..c474621 --- /dev/null +++ b/example/helloworld.c @@ -0,0 +1,64 @@ +#include +#include +#include "uhttp.h" + +static void signal_cb(struct ev_loop *loop, ev_signal *w, int revents) +{ + printf("Got signal: %d\n", w->signum); + ev_break(loop, EVBREAK_ALL); +} + +void route_test(struct uh_connection *con) +{ + struct uh_value *url = uh_get_url(con); + struct uh_value *header_host = uh_get_header(con, "Host"); + struct uh_value *header_ua = uh_get_header(con, "User-Agent"); + + uh_send_head(con, 200, -1, NULL); + uh_printf_chunk(con, "

Hello World

"); + uh_printf_chunk(con, "

Libuhttp v%s

", uh_version()); + uh_printf_chunk(con, "

Url: %.*s

", (int)url->len, url->at); + + if (header_host) + uh_printf_chunk(con, "

Host: %.*s

", (int)header_host->len, header_host->at); + + if (header_ua) + uh_printf_chunk(con, "

User-Agent: %.*s

", (int)header_ua->len, header_ua->at); + + uh_send_chunk(con, NULL, 0); +} + +int main(int argc, char **argv) +{ + struct ev_loop *loop = EV_DEFAULT; + ev_signal *sig_watcher = NULL; + struct uh_server *srv; + + uh_log_info("libuhttp version: %s\n", uh_version()); + + sig_watcher = calloc(1, sizeof(ev_signal)); + if (!sig_watcher) + return -1; + + ev_signal_init(sig_watcher, signal_cb, SIGINT); + ev_signal_start(loop, sig_watcher); + + srv = uh_server_new(loop, "0.0.0.0", 8000); + if (!srv) { + uh_log_err("uh_server_new failed\n"); + return -1; + } + + uh_register_route(srv, "/test", route_test); + + uh_log_info("Listen on 8000...\n"); + + ev_run(loop, 0); + + free(sig_watcher); + uh_server_free(srv); + + return 0; +} + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100755 index 0000000..0332f2b --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,30 @@ +# The version number. +set(UHTTP_VERSION_MAJOR 0) +set(UHTTP_VERSION_MINOR 1) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +# Check the necessary Libraries +find_package(Libev REQUIRED) +find_package(HttpParser REQUIRED) + +include_directories(${LIBEV_INCLUDE_DIRS} ${HTTPPARSER_INCLUDE_DIRS}) +set(EXTRA_LIBS ${LIBEV_LIBRARIES} ${HTTPPARSER_LIBRARIES}) +set(SOURCE_FILES uhttp.c uhttp_log.c uhttp_buf.c) + +add_library(uhttp SHARED ${SOURCE_FILES}) +set_target_properties(uhttp PROPERTIES VERSION ${UHTTP_VERSION_MAJOR}.${UHTTP_VERSION_MINOR}) +target_link_libraries(uhttp ${EXTRA_LIBS}) + +# configure a header file to pass some of the CMake settings to the source code +configure_file(uhttp_config.h.in ${CMAKE_CURRENT_BINARY_DIR}/uhttp_config.h) + +install( + FILES uhttp.h ${CMAKE_CURRENT_BINARY_DIR}/uhttp_config.h uhttp_log.h uhttp_buf.h + DESTINATION include +) + +install( + TARGETS uhttp LIBRARY + DESTINATION lib +) diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000..ab52acf --- /dev/null +++ b/src/list.h @@ -0,0 +1,208 @@ +/*- + * Copyright (c) 2011 Felix Fietkau + * Copyright (c) 2010 Isilon Systems, Inc. + * Copyright (c) 2010 iX Systems, Inc. + * Copyright (c) 2010 Panasas, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _LINUX_LIST_H_ +#define _LINUX_LIST_H_ + +#include +#include + +#define prefetch(x) + +#ifndef container_of +#define container_of(ptr, type, member) \ + ({ \ + const typeof(((type *) NULL)->member) *__mptr = (ptr); \ + (type *) ((char *) __mptr - offsetof(type, member)); \ + }) +#endif + +struct list_head { + struct list_head *next; + struct list_head *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } +#undef LIST_HEAD +#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) + +static inline void +INIT_LIST_HEAD(struct list_head *list) +{ + list->next = list->prev = list; +} + +static inline bool +list_empty(const struct list_head *head) +{ + return (head->next == head); +} + +static inline bool +list_is_first(const struct list_head *list, + const struct list_head *head) +{ + return list->prev == head; +} + +static inline bool +list_is_last(const struct list_head *list, + const struct list_head *head) +{ + return list->next == head; +} + +static inline void +_list_del(struct list_head *entry) +{ + entry->next->prev = entry->prev; + entry->prev->next = entry->next; +} + +static inline void +list_del(struct list_head *entry) +{ + _list_del(entry); + entry->next = entry->prev = NULL; +} + +static inline void +_list_add(struct list_head *_new, struct list_head *prev, + struct list_head *next) +{ + + next->prev = _new; + _new->next = next; + _new->prev = prev; + prev->next = _new; +} + +static inline void +list_del_init(struct list_head *entry) +{ + _list_del(entry); + INIT_LIST_HEAD(entry); +} + +#define list_entry(ptr, type, field) container_of(ptr, type, field) +#define list_first_entry(ptr, type, field) list_entry((ptr)->next, type, field) +#define list_last_entry(ptr, type, field) list_entry((ptr)->prev, type, field) + +#define list_for_each(p, head) \ + for (p = (head)->next; p != (head); p = p->next) + +#define list_for_each_safe(p, n, head) \ + for (p = (head)->next, n = p->next; p != (head); p = n, n = p->next) + +#define list_for_each_entry(p, h, field) \ + for (p = list_first_entry(h, typeof(*p), field); &p->field != (h); \ + p = list_entry(p->field.next, typeof(*p), field)) + +#define list_for_each_entry_safe(p, n, h, field) \ + for (p = list_first_entry(h, typeof(*p), field), \ + n = list_entry(p->field.next, typeof(*p), field); &p->field != (h);\ + p = n, n = list_entry(n->field.next, typeof(*n), field)) + +#define list_for_each_entry_reverse(p, h, field) \ + for (p = list_last_entry(h, typeof(*p), field); &p->field != (h); \ + p = list_entry(p->field.prev, typeof(*p), field)) + +#define list_for_each_prev(p, h) for (p = (h)->prev; p != (h); p = p->prev) +#define list_for_each_prev_safe(p, n, h) for (p = (h)->prev, n = p->prev; p != (h); p = n, n = p->prev) + +static inline void +list_add(struct list_head *_new, struct list_head *head) +{ + _list_add(_new, head, head->next); +} + +static inline void +list_add_tail(struct list_head *_new, struct list_head *head) +{ + _list_add(_new, head->prev, head); +} + +static inline void +list_move(struct list_head *list, struct list_head *head) +{ + _list_del(list); + list_add(list, head); +} + +static inline void +list_move_tail(struct list_head *entry, struct list_head *head) +{ + _list_del(entry); + list_add_tail(entry, head); +} + +static inline void +_list_splice(const struct list_head *list, struct list_head *prev, + struct list_head *next) +{ + struct list_head *first; + struct list_head *last; + + if (list_empty(list)) + return; + + first = list->next; + last = list->prev; + first->prev = prev; + prev->next = first; + last->next = next; + next->prev = last; +} + +static inline void +list_splice(const struct list_head *list, struct list_head *head) +{ + _list_splice(list, head, head->next); +} + +static inline void +list_splice_tail(struct list_head *list, struct list_head *head) +{ + _list_splice(list, head->prev, head); +} + +static inline void +list_splice_init(struct list_head *list, struct list_head *head) +{ + _list_splice(list, head, head->next); + INIT_LIST_HEAD(list); +} + +static inline void +list_splice_tail_init(struct list_head *list, struct list_head *head) +{ + _list_splice(list, head->prev, head); + INIT_LIST_HEAD(list); +} + +#endif /* _LINUX_LIST_H_ */ diff --git a/src/uhttp.c b/src/uhttp.c new file mode 100755 index 0000000..abd8f50 --- /dev/null +++ b/src/uhttp.c @@ -0,0 +1,543 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "uhttp.h" +#include "uhttp_internal.h" + +static struct { + int code; + const char *reason; +} http_status_message[] = { + {200, "OK"}, + {301, "Moved"}, + {302, "Found"}, + {400, "Bad Request"}, + {401, "Unauthorized"}, + {403, "Forbidden"}, + {404, "Not Found"}, + {500, "Internal Server Error"}, + {501, "Not Implemented"}, + {502, "Bad Gateway"}, + {503, "Service Unavailable"}, + {-1, NULL} +}; + +const char *uh_version() +{ + return UHTTP_VERSION_STRING; +} + +static const char *get_http_status_message(int code) +{ + int i; + const char *reason = "OK"; + + for (i = 0; http_status_message[i].reason; i++) { + if (code == http_status_message[i].code) + reason = http_status_message[i].reason; + } + return reason; +} + +static void uh_connection_destroy(struct uh_connection *con) +{ + if (con) { + struct ev_loop *loop = con->srv->loop; + + if (con->sock > 0) + close(con->sock); + + uh_buf_free(&con->read_buf); + uh_buf_free(&con->write_buf); + + ev_io_stop(loop, &con->read_watcher); + ev_io_stop(loop, &con->write_watcher); + ev_timer_stop(loop, &con->timer_watcher); + + list_del(&con->list); + free(con); + } +} + +static void connection_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) +{ + struct uh_connection *con = container_of(w, struct uh_connection, timer_watcher); + uh_log_info("connection(%p) timeout", con); + uh_connection_destroy(con); +} + +static int on_message_begin(http_parser *parser) +{ + struct uh_connection *con = container_of(parser, struct uh_connection, parser); + struct ev_loop *loop = con->srv->loop; + ev_timer *timer_watcher = &con->timer_watcher; + + uh_buf_init(&con->read_buf, UH_BUFFER_SIZE); + uh_buf_init(&con->write_buf, UH_BUFFER_SIZE); + + memset(&con->req, 0, sizeof(struct uh_request)); + + ev_timer_stop(loop, timer_watcher); + ev_timer_init(timer_watcher, connection_timeout_cb, UH_CONNECTION_TIMEOUT, 0); + ev_timer_start(loop, timer_watcher); + + return 0; +} + +static int on_url(http_parser *parser, const char *at, size_t len) +{ + struct uh_connection *con = container_of(parser, struct uh_connection, parser); + + if (!con->req.url.at) + con->req.url.at = at; + + con->req.url.len += len; + return 0; +} + +static int on_header_field(http_parser *parser, const char *at, size_t len) +{ + struct uh_connection *con = container_of(parser, struct uh_connection, parser); + struct uh_header *header = con->req.header; + + if (!header[con->req.header_num].field.at) { + header[con->req.header_num].field.at = at; + } + header[con->req.header_num].field.len += len; + + return 0; +} + +static int on_header_value(http_parser *parser, const char *at, size_t len) +{ + struct uh_connection *con = container_of(parser, struct uh_connection, parser); + struct uh_header *header = con->req.header; + + con->req.header_num += 1; + + if (!header[con->req.header_num - 1].value.at) + header[con->req.header_num - 1].value.at = at; + header[con->req.header_num - 1].value.len += len; + + return 0; +} + +static int on_body(http_parser *parser, const char *at, size_t len) +{ + struct uh_connection *con = container_of(parser, struct uh_connection, parser); + + if (!con->req.body.at) + con->req.body.at = at; + + con->req.body.len += len; + return 0; +} + + +/* Return 1 for equal */ +static int uh_value_cmp(struct uh_value *uv, const char *str) +{ + if (uv->len != strlen(str)) + return 0; + + return (!strncasecmp(uv->at, str, uv->len)); +} + +static int on_message_complete(http_parser *parser) +{ + struct uh_connection *con = container_of(parser, struct uh_connection, parser); + struct uh_route *r; +#ifdef UH_DEBUG + int i; + struct uh_header *header = con->req.header; + + printf("Url:[%.*s]\n", (int)con->req.url.len, con->req.url.at); + + for (i = 0; i < con->req.header_num; i++) { + printf("[%.*s:%.*s]\n", (int)header[i].field.len, header[i].field.at, + (int)header[i].value.len, header[i].value.at); + } + + printf("Body:[%.*s]\n", (int)con->req.body.len, con->req.body.at); +#endif + + list_for_each_entry(r, &con->srv->routes, list) { + if (uh_value_cmp(&con->req.url, r->path)) { + r->cb(con); + return 0; + } + } + + uh_send_error(con, 404, NULL); + + return 0; +} + + +static http_parser_settings parser_settings = { + .on_message_begin = on_message_begin, + .on_url = on_url, + .on_header_field = on_header_field, + .on_header_value = on_header_value, + .on_body = on_body, + .on_message_complete = on_message_complete +}; + +static void connection_read_cb(struct ev_loop *loop, ev_io *w, int revents) +{ + struct uh_connection *con = container_of(w, struct uh_connection, read_watcher); + struct uh_buf *buf = &con->read_buf; + char *base; + int len, parsered; + + if (uh_buf_available(buf) < UH_BUFFER_SIZE) + uh_buf_grow(buf, UH_BUFFER_SIZE); + + base = buf->base + buf->len; + + len = read(w->fd, base, UH_BUFFER_SIZE); + if (unlikely(len < 0)) { + if (errno == EINTR) + return; + uh_log_err("read"); + uh_send_error(con, 500, NULL); + return; + } + + if (len == 0) { + uh_connection_destroy(con); + return; + } + + buf->len += len; + +#ifdef UH_DEBUG + printf("read:[%.*s]\n", len, base); +#endif + + parsered = http_parser_execute(&con->parser, &parser_settings, base, len); + if (unlikely(parsered != len)){ + uh_log_err("http parser failed:%s", http_errno_description(HTTP_PARSER_ERRNO(&con->parser))); + uh_send_error(con, 400, NULL); + } +} + +static void connection_write_cb(struct ev_loop *loop, ev_io *w, int revents) +{ + struct uh_connection *con = container_of(w, struct uh_connection, write_watcher); + struct uh_buf *buf = &con->write_buf; + + if (buf->len > 0) { + int len = write(w->fd, buf->base, buf->len); + uh_buf_remove(buf, len); + } + + if (buf->len == 0) { + ev_io_stop(loop, w); + + if (!http_should_keep_alive(&con->parser)) + con->flags |= UH_CONNECTION_CLOSE; + } + + if (con->flags & UH_CONNECTION_CLOSE) + uh_connection_destroy(con); +} + +static void uh_accept_cb(struct ev_loop *loop, ev_io *w, int revents) +{ + int sock = -1; + struct sockaddr_in addr; + socklen_t addr_len = sizeof(addr); + struct uh_server *srv = container_of(w, struct uh_server, read_watcher); + struct uh_connection *con = NULL; + ev_io *read_watcher, *write_watcher; + ev_timer *timer_watcher; + + con = calloc(1, sizeof(struct uh_connection)); + if (unlikely(!con)) { + uh_log_err("calloc"); + return; + } + + con->srv = srv; + list_add(&con->list, &srv->connections); + + sock = accept4(w->fd, (struct sockaddr *)&addr, &addr_len, SOCK_NONBLOCK | SOCK_CLOEXEC); + if (unlikely(sock < 0)) { + if (errno != EINTR) + uh_log_err("accept"); + goto err; + } + + con->sock = sock; + + read_watcher = &con->read_watcher; + ev_io_init(read_watcher, connection_read_cb, sock, EV_READ); + ev_io_start(loop,read_watcher); + + write_watcher = &con->write_watcher; + ev_io_init(write_watcher, connection_write_cb, sock, EV_WRITE); + + timer_watcher = &con->timer_watcher; + ev_timer_init(timer_watcher, connection_timeout_cb, UH_CONNECTION_TIMEOUT, 0); + ev_timer_start(loop, timer_watcher); + + http_parser_init(&con->parser, HTTP_REQUEST); + + uh_log_info("new connection:%p", con); + return; +err: + uh_connection_destroy(con); +} + +struct uh_server *uh_server_new(struct ev_loop *loop, const char *ipaddr, int port) +{ + struct uh_server *srv = NULL; + struct sockaddr_in addr; + int sock = -1, on = 1; + ev_io *read_watcher; + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + if (inet_pton(AF_INET, ipaddr, &addr.sin_addr) <= 0) { + uh_log_err("invalid ipaddr"); + return NULL; + } + + srv = calloc(1, sizeof(struct uh_server)); + if (!srv) { + uh_log_err("calloc"); + return NULL; + } + + INIT_LIST_HEAD(&srv->routes); + INIT_LIST_HEAD(&srv->connections); + + sock = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + if (sock < 0) { + uh_log_err("socket"); + goto err; + } + + srv->sock = sock; + srv->loop = loop; + + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + if (bind(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0) { + uh_log_err("bind"); + goto err; + } + + if (listen(sock, SOMAXCONN) < 0) { + uh_log_err("listen"); + goto err; + } + + read_watcher = &srv->read_watcher; + ev_io_init(read_watcher, uh_accept_cb, sock, EV_READ); + ev_io_start(loop, read_watcher); + + return srv; + +err: + uh_server_free(srv); + return NULL; +} + +void uh_server_free(struct uh_server *srv) +{ + if (srv) { + struct uh_connection *con, *tmp_c; + struct uh_route *r, *tmp_r; + + if (srv->sock > 0) + close(srv->sock); + + ev_io_stop(srv->loop, &srv->read_watcher); + + list_for_each_entry_safe(con, tmp_c, &srv->connections, list) { + uh_connection_destroy(con); + } + + list_for_each_entry_safe(r, tmp_r, &srv->routes, list) { + list_del(&r->list); + free(r->path); + free(r); + } + + free(srv); + } +} + +int uh_send(struct uh_connection *con, const void *buf, int len) +{ + len = uh_buf_append(&con->write_buf, buf, len); + if (len > 0) + ev_io_start(con->srv->loop, &con->write_watcher); + return len; +} + +int uh_printf(struct uh_connection *con, const char *fmt, ...) +{ + int len = 0; + va_list ap; + char *str = NULL; + + assert(fmt); + + if (*fmt) { + va_start(ap, fmt); + len = vasprintf(&str, fmt, ap); + va_end(ap); + } + + if (len >= 0) { + len = uh_send(con, str, len); + free(str); + } + return len; +} + +static void send_status_line(struct uh_connection *con, int code) +{ + const char *reason = get_http_status_message(code); + uh_printf(con, "HTTP/1.1 %d %s\r\nServer: Libuhttp %s\r\n", + code, reason, UHTTP_VERSION_STRING); +} + +void uh_send_head(struct uh_connection *con, int status, int length, const char *extra_headers) +{ + send_status_line(con, status); + + if (length < 0) + uh_printf(con, "%s", "Transfer-Encoding: chunked\r\n"); + else + uh_printf(con, "Content-Length: %d\r\n", length); + + if (extra_headers) + uh_send(con, extra_headers, strlen(extra_headers)); + + uh_send(con, "\r\n", 2); +} + +void uh_send_error(struct uh_connection *con, int code, const char *reason) +{ + http_parser *parser = &con->parser; + + if (!reason) + reason = get_http_status_message(code); + + if (http_should_keep_alive(parser) && code < 400) { + uh_send_head(con, code, strlen(reason), "Content-Type: text/plain\r\nConnection: keep-alive\r\n"); + } else { + uh_send_head(con, code, strlen(reason), "Content-Type: text/plain\r\nConnection: close\r\n"); + } + + if (parser->method != HTTP_HEAD) + uh_send(con, reason, strlen(reason)); + + con->flags |= UH_CONNECTION_CLOSE; +} + +void uh_redirect(struct uh_connection *con, int code, const char *location) +{ + char body[128] = ""; + http_parser *parser = &con->parser; + + snprintf(body, sizeof(body), "

Moved here

", location); + + send_status_line(con, code); + + uh_printf(con, + "Location: %s\r\n" + "Content-Type: text/html\r\n" + "Content-Length: %zu\r\n" + "Cache-Control: no-cache\r\n", location, strlen(body)); + + uh_send(con, "\r\n", 2); + + if (parser->method != HTTP_HEAD) + uh_send(con, body, strlen(body)); +} + +int uh_send_chunk(struct uh_connection *con, const char *buf, int len) +{ + int slen = 0; + slen += uh_printf(con, "%X\r\n", len); + slen += uh_send(con, buf, len); + slen += uh_send(con, "\r\n", 2); + return slen; +} + +int uh_printf_chunk(struct uh_connection *con, const char *fmt, ...) +{ + int len = 0; + va_list ap; + char *str = NULL; + + assert(fmt); + + if (*fmt) { + va_start(ap, fmt); + len = vasprintf(&str, fmt, ap); + va_end(ap); + } + + if (len >= 0) { + len = uh_send_chunk(con, str, len); + free(str); + } + + return len; +} + +int uh_register_route(struct uh_server *srv, const char *path, uh_route_handler_t cb) +{ + struct uh_route *r; + + assert(path); + + r = calloc(1, sizeof(struct uh_route)); + if (!r) { + uh_log_err("calloc"); + return -1; + } + + r->path = strdup(path); + if (!r->path) { + uh_log_err("strdup"); + free(r); + return -1; + } + + r->cb = cb; + list_add(&r->list, &srv->routes); + + return 0; +} + +inline struct uh_value *uh_get_url(struct uh_connection *con) +{ + return &con->req.url; +} + +struct uh_value *uh_get_header(struct uh_connection *con, const char *name) +{ + int i; + struct uh_header *header = con->req.header; + + for (i = 0; i < con->req.header_num; i++) { + if (uh_value_cmp(&header[i].field, name)) + return &header[i].value; + } + return NULL; +} + diff --git a/src/uhttp.h b/src/uhttp.h new file mode 100755 index 0000000..3ee8ae8 --- /dev/null +++ b/src/uhttp.h @@ -0,0 +1,89 @@ +#ifndef _UHTTP_H +#define _UHTTP_H + +#include +#include "uhttp_config.h" +#include "uhttp_log.h" +#include "uhttp_buf.h" + +struct uh_server; +struct uh_connection; + +struct uh_value { + const char *at; + size_t len; +}; + +typedef void (*uh_route_handler_t)(struct uh_connection *con); + +const char *uh_version(); + +/* creates a new uhttp server instance. */ +struct uh_server *uh_server_new(struct ev_loop *loop, const char *ipaddr, int port); + +/* frees a uhttp server instance. */ +void uh_server_free(struct uh_server *srv); + +/* Sends data to the connection. */ +int uh_send(struct uh_connection *con, const void *buf, int len); + +/* Sends printf-formatted data to the connection. */ +int uh_printf(struct uh_connection *con, const char *fmt, ...); + +/* + * Sends the response line and headers. + * This function sends the response line with the `status`, and + * automatically sends one header: either "Content-Length" or "Transfer-Encoding". + * If `length` is negative, then "Transfer-Encoding: chunked" is sent, otherwise, + * "Content-Length" is sent. + * + * NOTE: If `Transfer-Encoding` is `chunked`, then message body must be sent + * using `uh_send_chunk()` or `uh_printf_chunk()` functions. + * Otherwise, `uh_send()` or `uh_printf()` must be used. + * Extra headers could be set through `extra_headers`. + * + * NOTE: `extra_headers` must NOT be terminated by a new line. + */ +void uh_send_head(struct uh_connection *con, int status, int length, const char *extra_headers); + +/* + * Sends a http error response. If reason is NULL, the message will be inferred + * from the error code (if supported). + */ +void uh_send_error(struct uh_connection *con, int code, const char *reason); + +/* + * Sends a http redirect response. `code` should be either 301 + * or 302 and `location` point to the new location. + */ +void uh_redirect(struct uh_connection *con, int code, const char *location); + +/* + * Sends data to the connection using chunked HTTP encoding. + * + * NOTE: The HTTP header "Transfer-Encoding: chunked" should be sent prior to + * using this function. + * + * NOTE: do not forget to send an empty chunk at the end of the response, + * to tell the client that everything was sent. + * + * Example: + * char data[] = "Hello World"; + * uh_send_chunk(con, data, strlen(data)); + * uh_send_chunk(con, NULL, 0); // Tell the client we're finished + */ +int uh_send_chunk(struct uh_connection *con, const char *buf, int len); + +/* + * Sends a printf-formatted HTTP chunk. + * Functionality is similar to `uh_send_chunk()`. + */ +int uh_printf_chunk(struct uh_connection *con, const char *fmt, ...); + +/* sets a callback to be executed on a specific path */ +int uh_register_route(struct uh_server *srv, const char *path, uh_route_handler_t cb); + +struct uh_value *uh_get_url(struct uh_connection *con); +struct uh_value *uh_get_header(struct uh_connection *con, const char *name); + +#endif diff --git a/src/uhttp_buf.c b/src/uhttp_buf.c new file mode 100755 index 0000000..00fc82d --- /dev/null +++ b/src/uhttp_buf.c @@ -0,0 +1,63 @@ +#include +#include +#include "uhttp_buf.h" + +int uh_buf_init(struct uh_buf *buf, size_t initial_size) +{ + buf->len = buf->size = 0; + buf->base = NULL; + + if (initial_size > 0) { + buf->base = malloc(initial_size); + if (!buf->base) + return -1; + buf->size = initial_size; + } + + return 0; +} + +int uh_buf_grow(struct uh_buf *buf, size_t size) +{ + void *base = realloc(buf->base, buf->size + size); + if (!base) + return -1; + + buf->base = base; + buf->size += size; + return 0; +} + +void uh_buf_free(struct uh_buf *buf) +{ + if (buf->base) { + free(buf->base); + uh_buf_init(buf, 0); + } +} + +size_t uh_buf_append(struct uh_buf *buf, const void *data, size_t len) +{ + assert(buf); + + if (!data) + return 0; + + if (buf->len + len > buf->size) { + if (uh_buf_grow(buf, len * UH_BUF_SIZE_MULTIPLIER) == -1) + len = buf->size - buf->len; + } + + memcpy(buf->base + buf->len, data, len); + buf->len += len; + + return len; +} + +void uh_buf_remove(struct uh_buf *buf, size_t n) +{ + if (n > 0 && n <= buf->len) { + memmove(buf->base, buf->base + n, buf->len - n); + buf->len -= n; + } +} \ No newline at end of file diff --git a/src/uhttp_buf.h b/src/uhttp_buf.h new file mode 100755 index 0000000..8458f5f --- /dev/null +++ b/src/uhttp_buf.h @@ -0,0 +1,30 @@ +#ifndef _UHTTP_BUF_H +#define _UHTTP_BUF_H + +#include +#include +#include + +#define UH_BUF_SIZE_MULTIPLIER 1.5 + +struct uh_buf { + char *base; /* Buffer pointer */ + size_t len; /* Data length */ + size_t size; /* Buffer size */ +}; + +#define uh_buf_available(b) ((b)->size - (b)->len) + +/* Return 0 for successful or -1 if out of memory */ +int uh_buf_init(struct uh_buf *buf, size_t initial_size); +int uh_buf_grow(struct uh_buf *buf, size_t size); + +void uh_buf_free(struct uh_buf *buf); + +/* Append data to the buf. Return the number of bytes appended. */ +size_t uh_buf_append(struct uh_buf *buf, const void *data, size_t len); + +/* Remove n bytes of data from the beginning of the buffer. */ +void uh_buf_remove(struct uh_buf *buf, size_t n); + +#endif diff --git a/src/uhttp_config.h.in b/src/uhttp_config.h.in new file mode 100755 index 0000000..c692e49 --- /dev/null +++ b/src/uhttp_config.h.in @@ -0,0 +1,8 @@ +#ifndef _UHTTP_CONFIG_H +#define _UHTTP_CONFIG_H + +#define UHTTP_VERSION_MAJOR @UHTTP_VERSION_MAJOR@ +#define UHTTP_VERSION_MINOR @UHTTP_VERSION_MINOR@ +#define UHTTP_VERSION_STRING "@UHTTP_VERSION_MAJOR@.@UHTTP_VERSION_MINOR@" + +#endif diff --git a/src/uhttp_internal.h b/src/uhttp_internal.h new file mode 100755 index 0000000..6ee16af --- /dev/null +++ b/src/uhttp_internal.h @@ -0,0 +1,56 @@ +#ifndef _UHTTP_INTERNAL_H +#define _UHTTP_INTERNAL_H + +#include "list.h" +#include "uhttp.h" + +#define UH_BUFFER_SIZE 2048 +#define UH_CONNECTION_TIMEOUT 30 +#define UH_MAX_HTTP_HEADERS 20 + +#define UH_CONNECTION_CLOSE (1 << 0) + +#define likely(x) (__builtin_expect(!!(x), 1)) +#define unlikely(x) (__builtin_expect(!!(x), 0)) + +struct uh_route { + char *path; + uh_route_handler_t cb; + struct list_head list; +}; + +struct uh_server { + int sock; + ev_io read_watcher; + struct ev_loop *loop; + struct list_head routes; + struct list_head connections; +}; + +struct uh_header { + struct uh_value field; + struct uh_value value; +}; + +struct uh_request { + struct uh_value url; + struct uh_value body; + int header_num; + struct uh_header header[UH_MAX_HTTP_HEADERS]; +}; + +struct uh_connection { + int sock; + unsigned char flags; + struct uh_buf read_buf; + struct uh_buf write_buf; + ev_io read_watcher; + ev_io write_watcher; + ev_timer timer_watcher; + struct uh_request req; + http_parser parser; + struct list_head list; + struct uh_server *srv; +}; + +#endif \ No newline at end of file diff --git a/src/uhttp_log.c b/src/uhttp_log.c new file mode 100755 index 0000000..39ec33d --- /dev/null +++ b/src/uhttp_log.c @@ -0,0 +1,25 @@ +#include "uhttp_log.h" + +void __uh_log(const char *filename, int line, int priority, const char *format, ...) +{ + va_list ap; + static char buf[128]; + + snprintf(buf, sizeof(buf), "(%s:%d) ", filename, line); + + va_start(ap, format); + vsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), format, ap); + va_end(ap); + + if (priority == LOG_ERR && errno > 0) { + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ":%s", strerror(errno)); + errno = 0; + } + + syslog(priority, "%s", buf); + +#ifdef UH_DEBUG + fprintf(stderr, "%s\n", buf); +#endif +} + diff --git a/src/uhttp_log.h b/src/uhttp_log.h new file mode 100755 index 0000000..c7a106e --- /dev/null +++ b/src/uhttp_log.h @@ -0,0 +1,28 @@ +#ifndef _UHTTP_LOG_H +#define _UHTTP_LOG_H + +#include +#include +#include +#include +#include + +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) + +/* + * Use the syslog output log and include the name and number of rows at the call + */ +#define uh_log(priority, format...) __uh_log(__FILENAME__, __LINE__, priority, format) + +#ifdef UH_DEBUG +#define uh_log_debug(format...) uh_log(LOG_DEBUG, format) +#else +#define uh_log_debug(format...) +#endif + +#define uh_log_info(format...) uh_log(LOG_INFO, format) +#define uh_log_err(format...) uh_log(LOG_ERR, format) + +void __uh_log(const char *filename, int line, int priority, const char *format, ...); + +#endif