The basic framework is implemented

Signed-off-by: Jianhui Zhao <jianhuizhao329@gmail.com>
main
Jianhui Zhao 2017-11-09 12:54:28 +08:00
parent cfc851e673
commit 38f1f37467
16 changed files with 1242 additions and 1 deletions

15
CMakeLists.txt 100755
View File

@ -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)

View File

@ -1,2 +1,6 @@
# libuhttp # 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.

View File

@ -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)

View File

@ -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)

View File

@ -0,0 +1,2 @@
add_executable(helloworld helloworld.c)
target_link_libraries(helloworld uhttp)

View File

@ -0,0 +1,64 @@
#include <ev.h>
#include <stdio.h>
#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, "<h1>Hello World</h1>");
uh_printf_chunk(con, "<h1>Libuhttp v%s</h1>", uh_version());
uh_printf_chunk(con, "<h1>Url: %.*s</h1>", (int)url->len, url->at);
if (header_host)
uh_printf_chunk(con, "<h1>Host: %.*s</h1>", (int)header_host->len, header_host->at);
if (header_ua)
uh_printf_chunk(con, "<h1>User-Agent: %.*s</h1>", (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;
}

30
src/CMakeLists.txt 100755
View File

@ -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
)

208
src/list.h 100644
View File

@ -0,0 +1,208 @@
/*-
* Copyright (c) 2011 Felix Fietkau <nbd@openwrt.org>
* 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 <stddef.h>
#include <stdbool.h>
#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_ */

543
src/uhttp.c 100755
View File

@ -0,0 +1,543 @@
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <assert.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <http_parser.h>
#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), "<p>Moved <a href=\"%s\">here</a></p>", 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;
}

89
src/uhttp.h 100755
View File

@ -0,0 +1,89 @@
#ifndef _UHTTP_H
#define _UHTTP_H
#include <ev.h>
#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

63
src/uhttp_buf.c 100755
View File

@ -0,0 +1,63 @@
#include <assert.h>
#include <string.h>
#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;
}
}

30
src/uhttp_buf.h 100755
View File

@ -0,0 +1,30 @@
#ifndef _UHTTP_BUF_H
#define _UHTTP_BUF_H
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#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

View File

@ -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

View File

@ -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

25
src/uhttp_log.c 100755
View File

@ -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
}

28
src/uhttp_log.h 100755
View File

@ -0,0 +1,28 @@
#ifndef _UHTTP_LOG_H
#define _UHTTP_LOG_H
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <syslog.h>
#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