The basic framework is implemented
Signed-off-by: Jianhui Zhao <jianhuizhao329@gmail.com>main
parent
cfc851e673
commit
38f1f37467
|
@ -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)
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -0,0 +1,2 @@
|
||||||
|
add_executable(helloworld helloworld.c)
|
||||||
|
target_link_libraries(helloworld uhttp)
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
|
@ -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_ */
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue