parent
b328506c3b
commit
78d4550ec4
|
@ -47,3 +47,5 @@ endif ()
|
|||
if (USED_LWIP OR VCPE_AGENT)
|
||||
ADD_SUBDIRECTORY(srcs/lwip)
|
||||
endif ()
|
||||
|
||||
ADD_SUBDIRECTORY(srcs/httpserver)
|
||||
|
|
|
@ -65,5 +65,13 @@ application:
|
|||
ip = "192.168.100.1";
|
||||
netmast = "255.255.0.0";
|
||||
gw = "192.168.100.1";
|
||||
};
|
||||
|
||||
# http server config
|
||||
http_svr:
|
||||
{
|
||||
listen_addr = "0.0.0.0";
|
||||
listen_port = 8000;
|
||||
tcp_nodelay = true;
|
||||
}
|
||||
}
|
|
@ -19,11 +19,13 @@ LIST(APPEND COMMON_LIBS "-ldl -lpthread -lzlog -lm -luv -lzmq -luuid -lconfig")
|
|||
|
||||
LIST(APPEND COMMON_LIBS "-lpthread")
|
||||
|
||||
INCLUDE_DIRECTORIES(include ./ ./include ./libs/include ./lwip/src/include ./lwip/src/arch_linux/include ${COMMON_INCLUDE})
|
||||
INCLUDE_DIRECTORIES(include ./ ./include ./libs/include ./lwip/src/include ./lwip/src/arch_linux/include
|
||||
./httpserver/include ${COMMON_INCLUDE})
|
||||
|
||||
SET(CMAKE_C_STANDARD 99)
|
||||
|
||||
FILE(GLOB VCPE_HEADS include/*.h include/uthash/*.h include/s2j/*.h)
|
||||
FILE(GLOB VCPE_HEADS include/*.h include/uthash/*.h include/s2j/*.h
|
||||
./httpserver/include/*.h ./httpserver/src/haywire/*.h ./httpserver/src/haywire/configuration/*.h)
|
||||
|
||||
if (USED_LWIP)
|
||||
AUX_SOURCE_DIRECTORY(pppoe VCPE_SRC)
|
||||
|
@ -76,7 +78,7 @@ ADD_CUSTOM_COMMAND(TARGET ${PROJECT_TARGET}
|
|||
COMMAND ${CMAKE_COMMAND} -E
|
||||
copy_if_different "${PROJECT_SOURCE_DIR}/../config/opendhcp.ini" "${CMAKE_CURRENT_BINARY_DIR}/config/"
|
||||
COMMAND ${CMAKE_COMMAND} -E
|
||||
copy_if_different "${PROJECT_SOURCE_DIR}/../config/agent.cfg" "${CMAKE_CURRENT_BINARY_DIR}/config/"
|
||||
copy_if_different "${PROJECT_SOURCE_DIR}/../config/vcpe.cfg" "${CMAKE_CURRENT_BINARY_DIR}/config/"
|
||||
COMMAND ${CMAKE_COMMAND} -E
|
||||
copy_if_different "${PROJECT_SOURCE_DIR}/../config/zlog.conf" "${CMAKE_CURRENT_BINARY_DIR}/config/"
|
||||
COMMAND ${CMAKE_COMMAND} -E
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
CMAKE_MINIMUM_REQUIRED(VERSION 3.10 FATAL_ERROR)
|
||||
|
||||
# ----------------------------------------
|
||||
# Haywire
|
||||
# ----------------------------------------
|
||||
project(haywire C)
|
||||
#set(CMAKE_BUILD_TYPE RelWithDebInfo)
|
||||
|
||||
add_definitions(-std=gnu99)
|
||||
#add_definitions(-mavx)
|
||||
add_definitions(-msse4.1)
|
||||
add_definitions(-pedantic)
|
||||
add_definitions(-O3)
|
||||
add_definitions(-Wall)
|
||||
add_definitions(-Wextra)
|
||||
add_definitions(-Wcast-align)
|
||||
add_definitions(-w)
|
||||
|
||||
if (UNIX)
|
||||
add_definitions(-DUNIX)
|
||||
endif (UNIX)
|
||||
|
||||
INCLUDE_DIRECTORIES(./include ./src)
|
||||
|
||||
file(GLOB_RECURSE HW_HEADS
|
||||
./src/haywire/*.h
|
||||
./src/haywire/configuration/*.h)
|
||||
|
||||
AUX_SOURCE_DIRECTORY(src/haywire HW_SRC)
|
||||
AUX_SOURCE_DIRECTORY(src/haywire/configuration HW_SRC)
|
||||
|
||||
INCLUDE_DIRECTORIES(. ./include)
|
||||
|
||||
#find_package(Threads REQUIRED)
|
||||
|
||||
add_library(haywire STATIC ${HW_SRC} ${HW_HEADS})
|
||||
target_link_libraries (haywire -luv -pthread)
|
||||
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
/* Windows - set up dll import/export decorators. */
|
||||
#ifdef BUILDING_HAYWIRE_SHARED
|
||||
/* Building shared library. */
|
||||
#define HAYWIRE_EXTERN __declspec(dllexport)
|
||||
#else
|
||||
#ifdef USING_HAYWIRE_SHARED
|
||||
/* Using shared library. */
|
||||
#define HAYWIRE_EXTERN __declspec(dllimport)
|
||||
#else
|
||||
/* Building static library. */
|
||||
#define HAYWIRE_EXTERN /* nothing */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define HAYWIRE_CALLING_CONVENTION __cdecl
|
||||
#else
|
||||
/* Building static library. */
|
||||
#define HAYWIRE_EXTERN /* nothing */
|
||||
#define HAYWIRE_CALLING_CONVENTION /* nothing */
|
||||
#endif
|
||||
|
||||
/* Informational 1xx */
|
||||
#define HTTP_STATUS_100 "100 Continue"
|
||||
#define HTTP_STATUS_101 "101 Switching Protocols"
|
||||
#define HTTP_STATUS_102 "102 Processing"
|
||||
/* Successful 2xx */
|
||||
#define HTTP_STATUS_200 "200 OK"
|
||||
#define HTTP_STATUS_201 "201 Created"
|
||||
#define HTTP_STATUS_202 "202 Accepted"
|
||||
#define HTTP_STATUS_203 "203 Non-Authoritative Information"
|
||||
#define HTTP_STATUS_204 "204 No Content"
|
||||
#define HTTP_STATUS_205 "205 Reset Content"
|
||||
#define HTTP_STATUS_206 "206 Partial Content"
|
||||
#define HTTP_STATUS_207 "207 Multi-Status"
|
||||
/* Redirection 3xx */
|
||||
#define HTTP_STATUS_300 "300 Multiple Choices"
|
||||
#define HTTP_STATUS_301 "301 Moved Permanently"
|
||||
#define HTTP_STATUS_302 "302 Moved Temporarily"
|
||||
#define HTTP_STATUS_303 "303 See Other"
|
||||
#define HTTP_STATUS_304 "304 Not Modified"
|
||||
#define HTTP_STATUS_305 "305 Use Proxy"
|
||||
#define HTTP_STATUS_307 "307 Temporary Redirect"
|
||||
/* Client Error 4xx */
|
||||
#define HTTP_STATUS_400 "400 Bad Request"
|
||||
#define HTTP_STATUS_401 "401 Unauthorized"
|
||||
#define HTTP_STATUS_402 "402 Payment Required"
|
||||
#define HTTP_STATUS_403 "403 Forbidden"
|
||||
#define HTTP_STATUS_404 "404 Not Found"
|
||||
#define HTTP_STATUS_405 "405 Method Not Allowed"
|
||||
#define HTTP_STATUS_406 "406 Not Acceptable"
|
||||
#define HTTP_STATUS_407 "407 Proxy Authentication Required"
|
||||
#define HTTP_STATUS_408 "408 Request Time-out"
|
||||
#define HTTP_STATUS_409 "409 Conflict"
|
||||
#define HTTP_STATUS_410 "410 Gone"
|
||||
#define HTTP_STATUS_411 "411 Length Required"
|
||||
#define HTTP_STATUS_412 "412 Precondition Failed"
|
||||
#define HTTP_STATUS_413 "413 Request Entity Too Large"
|
||||
#define HTTP_STATUS_414 "414 Request-URI Too Large"
|
||||
#define HTTP_STATUS_415 "415 Unsupported Media Type"
|
||||
#define HTTP_STATUS_416 "416 Requested Range Not Satisfiable"
|
||||
#define HTTP_STATUS_417 "417 Expectation Failed"
|
||||
#define HTTP_STATUS_418 "418 I'm a teapot"
|
||||
#define HTTP_STATUS_422 "422 Unprocessable Entity"
|
||||
#define HTTP_STATUS_423 "423 Locked"
|
||||
#define HTTP_STATUS_424 "424 Failed Dependency"
|
||||
#define HTTP_STATUS_425 "425 Unordered Collection"
|
||||
#define HTTP_STATUS_426 "426 Upgrade Required"
|
||||
#define HTTP_STATUS_428 "428 Precondition Required"
|
||||
#define HTTP_STATUS_429 "429 Too Many Requests"
|
||||
#define HTTP_STATUS_431 "431 Request Header Fields Too Large"
|
||||
/* Server Error 5xx */
|
||||
#define HTTP_STATUS_500 "500 Internal Server Error"
|
||||
#define HTTP_STATUS_501 "501 Not Implemented"
|
||||
#define HTTP_STATUS_502 "502 Bad Gateway"
|
||||
#define HTTP_STATUS_503 "503 Service Unavailable"
|
||||
#define HTTP_STATUS_504 "504 Gateway Time-out"
|
||||
#define HTTP_STATUS_505 "505 HTTP Version Not Supported"
|
||||
#define HTTP_STATUS_506 "506 Variant Also Negotiates"
|
||||
#define HTTP_STATUS_507 "507 Insufficient Storage"
|
||||
#define HTTP_STATUS_509 "509 Bandwidth Limit Exceeded"
|
||||
#define HTTP_STATUS_510 "510 Not Extended"
|
||||
#define HTTP_STATUS_511 "511 Network Authentication Required"
|
||||
|
||||
/* Request Methods */
|
||||
#define HW_HTTP_METHOD_MAP(XX) \
|
||||
XX(0, DELETE, DELETE) \
|
||||
XX(1, GET, GET) \
|
||||
XX(2, HEAD, HEAD) \
|
||||
XX(3, POST, POST) \
|
||||
XX(4, PUT, PUT) \
|
||||
/* pathological */ \
|
||||
XX(5, CONNECT, CONNECT) \
|
||||
XX(6, OPTIONS, OPTIONS) \
|
||||
XX(7, TRACE, TRACE) \
|
||||
/* webdav */ \
|
||||
XX(8, COPY, COPY) \
|
||||
XX(9, LOCK, LOCK) \
|
||||
XX(10, MKCOL, MKCOL) \
|
||||
XX(11, MOVE, MOVE) \
|
||||
XX(12, PROPFIND, PROPFIND) \
|
||||
XX(13, PROPPATCH, PROPPATCH) \
|
||||
XX(14, SEARCH, SEARCH) \
|
||||
XX(15, UNLOCK, UNLOCK) \
|
||||
/* subversion */ \
|
||||
XX(16, REPORT, REPORT) \
|
||||
XX(17, MKACTIVITY, MKACTIVITY) \
|
||||
XX(18, CHECKOUT, CHECKOUT) \
|
||||
XX(19, MERGE, MERGE) \
|
||||
/* upnp */ \
|
||||
XX(20, MSEARCH, M-SEARCH) \
|
||||
XX(21, NOTIFY, NOTIFY) \
|
||||
XX(22, SUBSCRIBE, SUBSCRIBE) \
|
||||
XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \
|
||||
/* RFC-5789 */ \
|
||||
XX(24, PATCH, PATCH) \
|
||||
XX(25, PURGE, PURGE) \
|
||||
|
||||
enum hw_http_method
|
||||
{
|
||||
#define XX(num, name, string) HW_HTTP_##name = num,
|
||||
HW_HTTP_METHOD_MAP(XX)
|
||||
#undef XX
|
||||
};
|
||||
|
||||
#define STRLENOF(s) strlen(s)
|
||||
#define SETSTRING(s,val) s.value=val; s.length=STRLENOF(val)
|
||||
#define APPENDSTRING(s,val) memcpy((char*)s->value + s->length, val, STRLENOF(val)); s->length+=STRLENOF(val)
|
||||
|
||||
typedef void* hw_http_response;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char* value;
|
||||
size_t length;
|
||||
} hw_string;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char* http_listen_address;
|
||||
unsigned int http_listen_port;
|
||||
unsigned int thread_count;
|
||||
char* balancer;
|
||||
char* parser;
|
||||
bool tcp_nodelay;
|
||||
unsigned int listen_backlog;
|
||||
unsigned int max_request_size;
|
||||
} configuration;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned short http_major;
|
||||
unsigned short http_minor;
|
||||
unsigned char method;
|
||||
int keep_alive;
|
||||
hw_string* url;
|
||||
void* headers;
|
||||
hw_string* body;
|
||||
size_t body_length;
|
||||
enum {OK, SIZE_EXCEEDED, BAD_REQUEST, INTERNAL_ERROR} state;
|
||||
} http_request;
|
||||
|
||||
typedef void (HAYWIRE_CALLING_CONVENTION *http_request_callback)(http_request* request, hw_http_response* response, void* user_data);
|
||||
typedef void (HAYWIRE_CALLING_CONVENTION *http_response_complete_callback)(void* user_data);
|
||||
|
||||
HAYWIRE_EXTERN int hw_init_from_config(char* configuration_filename);
|
||||
HAYWIRE_EXTERN int hw_init_with_config(configuration* config);
|
||||
HAYWIRE_EXTERN int hw_http_open();
|
||||
HAYWIRE_EXTERN void free_http_server();
|
||||
HAYWIRE_EXTERN void hw_http_add_route(char* route, http_request_callback callback, void* user_data);
|
||||
HAYWIRE_EXTERN hw_string* hw_get_header(http_request* request, hw_string* key);
|
||||
|
||||
HAYWIRE_EXTERN void hw_free_http_response(hw_http_response* response);
|
||||
HAYWIRE_EXTERN void hw_set_http_version(hw_http_response* response, unsigned short major, unsigned short minor);
|
||||
HAYWIRE_EXTERN void hw_set_response_status_code(hw_http_response* response, hw_string* status_code);
|
||||
HAYWIRE_EXTERN void hw_set_response_header(hw_http_response* response, hw_string* name, hw_string* value);
|
||||
HAYWIRE_EXTERN void hw_set_body(hw_http_response* response, hw_string* body);
|
||||
HAYWIRE_EXTERN void hw_http_response_send(hw_http_response* response, void* user_data, http_response_complete_callback callback);
|
||||
|
||||
HAYWIRE_EXTERN void hw_print_request_headers(http_request* request);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,38 @@
|
|||
#include <zlog.h>
|
||||
#include "configuration.h"
|
||||
#include "../hw_string.h"
|
||||
#include "../khash.h"
|
||||
#include "ini.h"
|
||||
|
||||
KHASH_MAP_INIT_STR(route_hashes, char*)
|
||||
|
||||
int configuration_handler(void* user, const char* section, const char* name, const char* value)
|
||||
{
|
||||
configuration* config = (configuration*)user;
|
||||
|
||||
#define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
|
||||
if (MATCH("http", "listen_address"))
|
||||
{
|
||||
config->http_listen_address = dupstr(value);
|
||||
}
|
||||
else if (MATCH("http", "listen_port"))
|
||||
{
|
||||
config->http_listen_port = atoi(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0; /* unknown section/name, error */
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
configuration* load_configuration(const char* filename)
|
||||
{
|
||||
configuration* config = malloc(sizeof(configuration));
|
||||
if (ini_parse(filename, configuration_handler, config) < 0)
|
||||
{
|
||||
dzlog_error("Can't load configuration\n");
|
||||
return NULL;
|
||||
}
|
||||
return config;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
#include "haywire.h"
|
||||
|
||||
configuration* load_configuration(const char* filename);
|
|
@ -0,0 +1,150 @@
|
|||
/* inih -- simple .INI file parser
|
||||
|
||||
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||
home page for more info:
|
||||
|
||||
http://code.google.com/p/inih/
|
||||
https://github.com/armon/hlld/tree/master/deps/inih
|
||||
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ini.h"
|
||||
|
||||
#define MAX_LINE 200
|
||||
#define MAX_SECTION 50
|
||||
#define MAX_NAME 50
|
||||
|
||||
/* Strip whitespace chars off end of given string, in place. Return s. */
|
||||
static char* rstrip(char* s)
|
||||
{
|
||||
char* p = s + strlen(s);
|
||||
while (p > s && isspace(*--p))
|
||||
*p = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Return pointer to first non-whitespace char in given string. */
|
||||
static char* lskip(const char* s)
|
||||
{
|
||||
while (*s && isspace(*s))
|
||||
s++;
|
||||
return (char*)s;
|
||||
}
|
||||
|
||||
/* Return pointer to first char c or ';' comment in given string, or pointer to
|
||||
null at end of string if neither found. ';' must be prefixed by a whitespace
|
||||
character to register as a comment. */
|
||||
static char* find_char_or_comment(const char* s, char c)
|
||||
{
|
||||
int was_whitespace = 0;
|
||||
while (*s && *s != c && !(was_whitespace && *s == ';')) {
|
||||
was_whitespace = isspace(*s);
|
||||
s++;
|
||||
}
|
||||
return (char*)s;
|
||||
}
|
||||
|
||||
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
|
||||
static char* strncpy0(char* dest, const char* src, size_t size)
|
||||
{
|
||||
strncpy(dest, src, size);
|
||||
dest[size - 1] = '\0';
|
||||
return dest;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int ini_parse_file(FILE* file,
|
||||
int (*handler)(void*, const char*, const char*,
|
||||
const char*),
|
||||
void* user)
|
||||
{
|
||||
/* Uses a fair bit of stack (use heap instead if you need to) */
|
||||
char line[MAX_LINE];
|
||||
char section[MAX_SECTION] = "";
|
||||
char prev_name[MAX_NAME] = "";
|
||||
|
||||
char* start;
|
||||
char* end;
|
||||
char* name;
|
||||
char* value;
|
||||
int lineno = 0;
|
||||
int error = 0;
|
||||
|
||||
/* Scan through file line by line */
|
||||
while (fgets(line, sizeof(line), file) != NULL) {
|
||||
lineno++;
|
||||
start = lskip(rstrip(line));
|
||||
|
||||
if (*start == ';' || *start == '#') {
|
||||
/* Per Python ConfigParser, allow '#' comments at start of line */
|
||||
}
|
||||
#if INI_ALLOW_MULTILINE
|
||||
else if (*prev_name && *start && start > line) {
|
||||
/* Non-black line with leading whitespace, treat as continuation
|
||||
of previous name's value (as per Python ConfigParser). */
|
||||
if (!handler(user, section, prev_name, start) && !error)
|
||||
error = lineno;
|
||||
}
|
||||
#endif
|
||||
else if (*start == '[') {
|
||||
/* A "[section]" line */
|
||||
end = find_char_or_comment(start + 1, ']');
|
||||
if (*end == ']') {
|
||||
*end = '\0';
|
||||
strncpy0(section, start + 1, sizeof(section));
|
||||
*prev_name = '\0';
|
||||
}
|
||||
else if (!error) {
|
||||
/* No ']' found on section line */
|
||||
error = lineno;
|
||||
}
|
||||
}
|
||||
else if (*start && *start != ';') {
|
||||
/* Not a comment, must be a name[=:]value pair */
|
||||
end = find_char_or_comment(start, '=');
|
||||
if (*end != '=') {
|
||||
end = find_char_or_comment(start, ':');
|
||||
}
|
||||
if (*end == '=' || *end == ':') {
|
||||
*end = '\0';
|
||||
name = rstrip(start);
|
||||
value = lskip(end + 1);
|
||||
end = find_char_or_comment(value, '\0');
|
||||
if (*end == ';')
|
||||
*end = '\0';
|
||||
rstrip(value);
|
||||
|
||||
/* Valid name[=:]value pair found, call handler */
|
||||
strncpy0(prev_name, name, sizeof(prev_name));
|
||||
if (!handler(user, section, name, value) && !error)
|
||||
error = lineno;
|
||||
}
|
||||
else if (!error) {
|
||||
/* No '=' or ':' found on name[=:]value line */
|
||||
error = lineno;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int ini_parse(const char* filename,
|
||||
int (*handler)(void*, const char*, const char*, const char*),
|
||||
void* user)
|
||||
{
|
||||
FILE* file;
|
||||
int error;
|
||||
|
||||
file = fopen(filename, "r");
|
||||
if (!file)
|
||||
return -1;
|
||||
error = ini_parse_file(file, handler, user);
|
||||
fclose(file);
|
||||
return error;
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/* inih -- simple .INI file parser
|
||||
|
||||
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||
home page for more info:
|
||||
|
||||
http://code.google.com/p/inih/
|
||||
https://github.com/armon/hlld/tree/master/deps/inih
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __INI_H__
|
||||
#define __INI_H__
|
||||
|
||||
/* Make this header file easier to include in C++ code */
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* Parse given INI-style file. May have [section]s, name=value pairs
|
||||
(whitespace stripped), and comments starting with ';' (semicolon). Section
|
||||
is "" if name=value pair parsed before any section heading. name:value
|
||||
pairs are also supported as a concession to Python's ConfigParser.
|
||||
|
||||
For each name=value pair parsed, call handler function with given user
|
||||
pointer as well as section, name, and value (data only valid for duration
|
||||
of handler call). Handler should return nonzero on success, zero on error.
|
||||
|
||||
Returns 0 on success, line number of first error on parse error (doesn't
|
||||
stop on first error), or -1 on file open error.
|
||||
*/
|
||||
int ini_parse(const char* filename,
|
||||
int (*handler)(void* user, const char* section,
|
||||
const char* name, const char* value),
|
||||
void* user);
|
||||
|
||||
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
|
||||
close the file when it's finished -- the caller must do that. */
|
||||
int ini_parse_file(FILE* file,
|
||||
int (*handler)(void* user, const char* section,
|
||||
const char* name, const char* value),
|
||||
void* user);
|
||||
|
||||
/* Nonzero to allow multi-line value parsing, in the style of Python's
|
||||
ConfigParser. If allowed, ini_parse() will call the handler with the same
|
||||
name for each subsequent line parsed. */
|
||||
#ifndef INI_ALLOW_MULTILINE
|
||||
#define INI_ALLOW_MULTILINE 1
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __INI_H__ */
|
|
@ -0,0 +1,128 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "uv.h"
|
||||
#include "connection_consumer.h"
|
||||
#include "http_server.h"
|
||||
#include "http_connection.h"
|
||||
#include "http_response_cache.h"
|
||||
|
||||
static bool tcp_nodelay;
|
||||
|
||||
void ipc_read_cb(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf)
|
||||
{
|
||||
int rc;
|
||||
struct ipc_client_ctx* ctx;
|
||||
uv_loop_t* loop;
|
||||
uv_handle_type type;
|
||||
uv_pipe_t* ipc_pipe;
|
||||
|
||||
ipc_pipe = (uv_pipe_t*)handle;
|
||||
ctx = container_of(ipc_pipe, struct ipc_client_ctx, ipc_pipe);
|
||||
loop = ipc_pipe->loop;
|
||||
|
||||
uv_pipe_pending_count(ipc_pipe);
|
||||
type = uv_pipe_pending_type(ipc_pipe);
|
||||
|
||||
if (type == UV_TCP) {
|
||||
rc = uv_tcp_init(loop, (uv_tcp_t*) ctx->server_handle);
|
||||
if (tcp_nodelay) {
|
||||
rc = uv_tcp_nodelay((uv_tcp_t*) ctx->server_handle, 1);
|
||||
}
|
||||
}
|
||||
else if (type == UV_NAMED_PIPE)
|
||||
rc = uv_pipe_init(loop, (uv_pipe_t*) ctx->server_handle, 0);
|
||||
|
||||
rc = uv_accept(handle, ctx->server_handle);
|
||||
uv_close((uv_handle_t*) &ctx->ipc_pipe, NULL);
|
||||
}
|
||||
|
||||
void ipc_alloc_cb(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf)
|
||||
{
|
||||
struct ipc_client_ctx* ctx;
|
||||
ctx = container_of(handle, struct ipc_client_ctx, ipc_pipe);
|
||||
buf->base = ctx->scratch;
|
||||
buf->len = sizeof(ctx->scratch);
|
||||
}
|
||||
|
||||
void ipc_connect_cb(uv_connect_t* req, int status)
|
||||
{
|
||||
int rc;
|
||||
struct ipc_client_ctx* ctx;
|
||||
ctx = container_of(req, struct ipc_client_ctx, connect_req);
|
||||
rc = uv_read_start((uv_stream_t*)&ctx->ipc_pipe, ipc_alloc_cb, ipc_read_cb);
|
||||
}
|
||||
|
||||
void connection_consumer_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf)
|
||||
{
|
||||
static char slab[32];
|
||||
buf->base = slab;
|
||||
buf->len = sizeof(slab);
|
||||
}
|
||||
|
||||
void connection_consumer_new_connection(uv_stream_t* server_handle, int status)
|
||||
{
|
||||
int rc = 0;
|
||||
http_connection* connection = create_http_connection();
|
||||
http_parser_init(&connection->parser, HTTP_REQUEST);
|
||||
|
||||
connection->parser.data = connection;
|
||||
connection->stream.data = connection;
|
||||
|
||||
rc = uv_tcp_init(server_handle->loop, &connection->stream);
|
||||
|
||||
if (tcp_nodelay) {
|
||||
rc = uv_tcp_nodelay((uv_tcp_t*)&connection->stream, 1);
|
||||
}
|
||||
|
||||
rc = uv_accept(server_handle, (uv_stream_t*)&connection->stream);
|
||||
rc = uv_read_start((uv_stream_t*)&connection->stream, http_stream_on_alloc, http_stream_on_read);
|
||||
}
|
||||
|
||||
void connection_consumer_close(uv_async_t* handle, int status)
|
||||
{
|
||||
struct server_ctx* ctx;
|
||||
ctx = container_of(handle, struct server_ctx, async_handle);
|
||||
uv_close((uv_handle_t*) &ctx->server_handle, NULL);
|
||||
uv_close((uv_handle_t*) &ctx->async_handle, NULL);
|
||||
}
|
||||
|
||||
void get_listen_handle(uv_loop_t* loop, uv_stream_t* server_handle)
|
||||
{
|
||||
int rc;
|
||||
struct ipc_client_ctx ctx;
|
||||
|
||||
ctx.server_handle = server_handle;
|
||||
ctx.server_handle->data = "server handle";
|
||||
|
||||
rc = uv_pipe_init(loop, &ctx.ipc_pipe, 1);
|
||||
uv_pipe_connect(&ctx.connect_req, &ctx.ipc_pipe, "HAYWIRE_CONNECTION_DISPATCH_PIPE_NAME", ipc_connect_cb);
|
||||
rc = uv_run(loop, UV_RUN_DEFAULT);
|
||||
}
|
||||
|
||||
void connection_consumer_start(void *arg)
|
||||
{
|
||||
int rc;
|
||||
struct server_ctx *ctx;
|
||||
uv_loop_t* loop;
|
||||
|
||||
ctx = arg;
|
||||
tcp_nodelay = ctx->tcp_nodelay;
|
||||
loop = uv_loop_new();
|
||||
listener_event_loops[ctx->index] = *loop;
|
||||
|
||||
http_request_cache_configure_listener(loop, &listener_async_handles[ctx->index]);
|
||||
uv_barrier_wait(listeners_created_barrier);
|
||||
|
||||
rc = uv_async_init(loop, &ctx->async_handle, connection_consumer_close);
|
||||
uv_unref((uv_handle_t*) &ctx->async_handle);
|
||||
|
||||
/* Wait until the main thread is ready. */
|
||||
uv_sem_wait(&ctx->semaphore);
|
||||
get_listen_handle(loop, (uv_stream_t*) &ctx->server_handle);
|
||||
uv_sem_post(&ctx->semaphore);
|
||||
|
||||
rc = uv_listen((uv_stream_t*)&ctx->server_handle, ctx->listen_backlog, connection_consumer_new_connection);
|
||||
rc = uv_run(loop, UV_RUN_DEFAULT);
|
||||
|
||||
uv_loop_delete(loop);
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
#include <stdbool.h>
|
||||
|
||||
#pragma once
|
||||
#include "uv.h"
|
||||
|
||||
#ifndef offsetof
|
||||
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
|
||||
#endif
|
||||
|
||||
#define container_of(ptr, type, member) ((type*)(((char*)(ptr)) - offsetof(type, member)))
|
||||
|
||||
|
||||
union stream_handle2
|
||||
{
|
||||
uv_pipe_t pipe;
|
||||
uv_tcp_t tcp;
|
||||
};
|
||||
|
||||
typedef unsigned char handle_storage_t[sizeof(union stream_handle2)];
|
||||
|
||||
struct server_ctx
|
||||
{
|
||||
int index;
|
||||
handle_storage_t server_handle;
|
||||
unsigned int num_connects;
|
||||
uv_async_t async_handle;
|
||||
uv_thread_t thread_id;
|
||||
uv_sem_t semaphore;
|
||||
bool tcp_nodelay;
|
||||
unsigned int listen_backlog;
|
||||
};
|
||||
|
||||
struct ipc_client_ctx
|
||||
{
|
||||
uv_connect_t connect_req;
|
||||
uv_stream_t* server_handle;
|
||||
uv_pipe_t ipc_pipe;
|
||||
char scratch[16];
|
||||
};
|
||||
|
||||
struct ipc_server_ctx
|
||||
{
|
||||
handle_storage_t server_handle;
|
||||
unsigned int num_connects;
|
||||
uv_pipe_t ipc_pipe;
|
||||
bool tcp_nodelay;
|
||||
};
|
||||
|
||||
void connection_consumer_start(void *arg);
|
||||
void connection_consumer_close(uv_async_t* handle, int status);
|
|
@ -0,0 +1,102 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "uv.h"
|
||||
#include "connection_dispatcher.h"
|
||||
#include "connection_consumer.h"
|
||||
|
||||
static struct sockaddr_in listen_addr;
|
||||
|
||||
void ipc_close_cb(uv_handle_t* handle)
|
||||
{
|
||||
struct ipc_peer_ctx* ctx;
|
||||
ctx = container_of(handle, struct ipc_peer_ctx, peer_handle);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
void ipc_write_cb(uv_write_t* req, int status)
|
||||
{
|
||||
struct ipc_peer_ctx* ctx;
|
||||
ctx = container_of(req, struct ipc_peer_ctx, write_req);
|
||||
uv_close((uv_handle_t*) &ctx->peer_handle, ipc_close_cb);
|
||||
}
|
||||
|
||||
void ipc_connection_cb(uv_stream_t* ipc_pipe, int status)
|
||||
{
|
||||
int rc;
|
||||
struct ipc_server_ctx* sc;
|
||||
struct ipc_peer_ctx* pc;
|
||||
uv_loop_t* loop;
|
||||
uv_buf_t buf;
|
||||
|
||||
loop = ipc_pipe->loop;
|
||||
buf = uv_buf_init("PING", 4);
|
||||
sc = container_of(ipc_pipe, struct ipc_server_ctx, ipc_pipe);
|
||||
pc = calloc(1, sizeof(*pc));
|
||||
//ASSERT(pc != NULL);
|
||||
|
||||
if (ipc_pipe->type == UV_TCP) {
|
||||
rc = uv_tcp_init(loop, (uv_tcp_t*) &pc->peer_handle);
|
||||
if (sc->tcp_nodelay) {
|
||||
rc = uv_tcp_nodelay((uv_tcp_t*) &pc->peer_handle, 1);
|
||||
}
|
||||
}
|
||||
else if (ipc_pipe->type == UV_NAMED_PIPE)
|
||||
rc = uv_pipe_init(loop, (uv_pipe_t*) &pc->peer_handle, 1);
|
||||
|
||||
rc = uv_accept(ipc_pipe, (uv_stream_t*) &pc->peer_handle);
|
||||
rc = uv_write2(&pc->write_req,
|
||||
(uv_stream_t*) &pc->peer_handle,
|
||||
&buf,
|
||||
1,
|
||||
(uv_stream_t*) &sc->server_handle,
|
||||
ipc_write_cb);
|
||||
|
||||
if (--sc->num_connects == 0)
|
||||
uv_close((uv_handle_t*) ipc_pipe, NULL);
|
||||
}
|
||||
|
||||
extern void print_configuration();
|
||||
/* Set up an IPC pipe server that hands out listen sockets to the worker
|
||||
* threads. It's kind of cumbersome for such a simple operation, maybe we
|
||||
* should revive uv_import() and uv_export().
|
||||
*/
|
||||
void start_connection_dispatching(uv_handle_type type, unsigned int num_servers, struct server_ctx* servers, char* listen_address, int listen_port, bool tcp_nodelay, int listen_backlog)
|
||||
{
|
||||
int rc;
|
||||
struct ipc_server_ctx ctx;
|
||||
uv_loop_t* loop;
|
||||
unsigned int i;
|
||||
|
||||
loop = uv_default_loop();
|
||||
ctx.num_connects = num_servers;
|
||||
ctx.tcp_nodelay = tcp_nodelay;
|
||||
|
||||
if (type == UV_TCP)
|
||||
{
|
||||
uv_ip4_addr(listen_address, listen_port, &listen_addr);
|
||||
|
||||
rc = uv_tcp_init(loop, (uv_tcp_t*) &ctx.server_handle);
|
||||
|
||||
if (ctx.tcp_nodelay) {
|
||||
rc = uv_tcp_nodelay((uv_tcp_t*) &ctx.server_handle, 1);
|
||||
}
|
||||
|
||||
rc = uv_tcp_bind((uv_tcp_t*) &ctx.server_handle, (const struct sockaddr*)&listen_addr, 0);
|
||||
print_configuration();
|
||||
printf("Listening...\n");
|
||||
}
|
||||
|
||||
rc = uv_pipe_init(loop, &ctx.ipc_pipe, 1);
|
||||
rc = uv_pipe_bind(&ctx.ipc_pipe, "HAYWIRE_CONNECTION_DISPATCH_PIPE_NAME");
|
||||
rc = uv_listen((uv_stream_t*) &ctx.ipc_pipe, listen_backlog, ipc_connection_cb);
|
||||
|
||||
for (i = 0; i < num_servers; i++)
|
||||
uv_sem_post(&servers[i].semaphore);
|
||||
|
||||
rc = uv_run(loop, UV_RUN_DEFAULT);
|
||||
uv_close((uv_handle_t*) &ctx.server_handle, NULL);
|
||||
rc = uv_run(loop, UV_RUN_DEFAULT);
|
||||
|
||||
for (i = 0; i < num_servers; i++)
|
||||
uv_sem_wait(&servers[i].semaphore);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include "uv.h"
|
||||
#include "connection_consumer.h"
|
||||
|
||||
struct ipc_peer_ctx
|
||||
{
|
||||
handle_storage_t peer_handle;
|
||||
uv_write_t write_req;
|
||||
};
|
||||
|
||||
void start_connection_dispatching(uv_handle_type type, unsigned int num_servers, struct server_ctx* servers, char* listen_address, int listen_port, bool tcp_nodelay, int listen_backlog);
|
|
@ -0,0 +1,21 @@
|
|||
#include <stddef.h>
|
||||
|
||||
#pragma once
|
||||
#include "uv.h"
|
||||
#include "http_parser.h"
|
||||
#include "http_request.h"
|
||||
#include "http_request_buffers.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uv_tcp_t stream;
|
||||
http_parser parser;
|
||||
uv_write_t write_req;
|
||||
http_request* request;
|
||||
hw_string current_header_key;
|
||||
hw_string current_header_value;
|
||||
int keep_alive;
|
||||
int last_was_value;
|
||||
enum {OPEN, CLOSING, CLOSED} state;
|
||||
hw_request_buffer* buffer;
|
||||
} http_connection;
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,329 @@
|
|||
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef http_parser_h
|
||||
#define http_parser_h
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Also update SONAME in the Makefile whenever you change these. */
|
||||
#define HTTP_PARSER_VERSION_MAJOR 2
|
||||
#define HTTP_PARSER_VERSION_MINOR 3
|
||||
#define HTTP_PARSER_VERSION_PATCH 0
|
||||
|
||||
#include <sys/types.h>
|
||||
#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600)
|
||||
#include <BaseTsd.h>
|
||||
#include <stddef.h>
|
||||
typedef __int8 int8_t;
|
||||
typedef unsigned __int8 uint8_t;
|
||||
typedef __int16 int16_t;
|
||||
typedef unsigned __int16 uint16_t;
|
||||
typedef __int32 int32_t;
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
|
||||
* faster
|
||||
*/
|
||||
#ifndef HTTP_PARSER_STRICT
|
||||
# define HTTP_PARSER_STRICT 1
|
||||
#endif
|
||||
|
||||
/* Maximium header size allowed. If the macro is not defined
|
||||
* before including this header then the default is used. To
|
||||
* change the maximum header size, define the macro in the build
|
||||
* environment (e.g. -DHTTP_MAX_HEADER_SIZE=<value>). To remove
|
||||
* the effective limit on the size of the header, define the macro
|
||||
* to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff)
|
||||
*/
|
||||
#ifndef HTTP_MAX_HEADER_SIZE
|
||||
# define HTTP_MAX_HEADER_SIZE (80*1024)
|
||||
#endif
|
||||
|
||||
typedef struct http_parser http_parser;
|
||||
typedef struct http_parser_settings http_parser_settings;
|
||||
|
||||
|
||||
/* Callbacks should return non-zero to indicate an error. The parser will
|
||||
* then halt execution.
|
||||
*
|
||||
* The one exception is on_headers_complete. In a HTTP_RESPONSE parser
|
||||
* returning '1' from on_headers_complete will tell the parser that it
|
||||
* should not expect a body. This is used when receiving a response to a
|
||||
* HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
|
||||
* chunked' headers that indicate the presence of a body.
|
||||
*
|
||||
* http_data_cb does not return data chunks. It will be called arbitrarily
|
||||
* many times for each string. E.G. you might get 10 callbacks for "on_url"
|
||||
* each providing just a few characters more data.
|
||||
*/
|
||||
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
|
||||
typedef int (*http_cb) (http_parser*);
|
||||
|
||||
/* Request Methods */
|
||||
#define HTTP_METHOD_MAP(XX) \
|
||||
XX(0, DELETE, DELETE) \
|
||||
XX(1, GET, GET) \
|
||||
XX(2, HEAD, HEAD) \
|
||||
XX(3, POST, POST) \
|
||||
XX(4, PUT, PUT) \
|
||||
/* pathological */ \
|
||||
XX(5, CONNECT, CONNECT) \
|
||||
XX(6, OPTIONS, OPTIONS) \
|
||||
XX(7, TRACE, TRACE) \
|
||||
/* webdav */ \
|
||||
XX(8, COPY, COPY) \
|
||||
XX(9, LOCK, LOCK) \
|
||||
XX(10, MKCOL, MKCOL) \
|
||||
XX(11, MOVE, MOVE) \
|
||||
XX(12, PROPFIND, PROPFIND) \
|
||||
XX(13, PROPPATCH, PROPPATCH) \
|
||||
XX(14, SEARCH, SEARCH) \
|
||||
XX(15, UNLOCK, UNLOCK) \
|
||||
/* subversion */ \
|
||||
XX(16, REPORT, REPORT) \
|
||||
XX(17, MKACTIVITY, MKACTIVITY) \
|
||||
XX(18, CHECKOUT, CHECKOUT) \
|
||||
XX(19, MERGE, MERGE) \
|
||||
/* upnp */ \
|
||||
XX(20, MSEARCH, M-SEARCH) \
|
||||
XX(21, NOTIFY, NOTIFY) \
|
||||
XX(22, SUBSCRIBE, SUBSCRIBE) \
|
||||
XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \
|
||||
/* RFC-5789 */ \
|
||||
XX(24, PATCH, PATCH) \
|
||||
XX(25, PURGE, PURGE) \
|
||||
/* CalDAV */ \
|
||||
XX(26, MKCALENDAR, MKCALENDAR) \
|
||||
|
||||
enum http_method
|
||||
{
|
||||
#define XX(num, name, string) HTTP_##name = num,
|
||||
HTTP_METHOD_MAP(XX)
|
||||
#undef XX
|
||||
};
|
||||
|
||||
|
||||
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
|
||||
|
||||
|
||||
/* Flag values for http_parser.flags field */
|
||||
enum flags
|
||||
{ F_CHUNKED = 1 << 0
|
||||
, F_CONNECTION_KEEP_ALIVE = 1 << 1
|
||||
, F_CONNECTION_CLOSE = 1 << 2
|
||||
, F_CONNECTION_UPGRADE = 1 << 3
|
||||
, F_TRAILING = 1 << 4
|
||||
, F_UPGRADE = 1 << 5
|
||||
, F_SKIPBODY = 1 << 6
|
||||
};
|
||||
|
||||
|
||||
/* Map for errno-related constants
|
||||
*
|
||||
* The provided argument should be a macro that takes 2 arguments.
|
||||
*/
|
||||
#define HTTP_ERRNO_MAP(XX) \
|
||||
/* No error */ \
|
||||
XX(OK, "success") \
|
||||
\
|
||||
/* Callback-related errors */ \
|
||||
XX(CB_message_begin, "the on_message_begin callback failed") \
|
||||
XX(CB_url, "the on_url callback failed") \
|
||||
XX(CB_header_field, "the on_header_field callback failed") \
|
||||
XX(CB_header_value, "the on_header_value callback failed") \
|
||||
XX(CB_headers_complete, "the on_headers_complete callback failed") \
|
||||
XX(CB_body, "the on_body callback failed") \
|
||||
XX(CB_message_complete, "the on_message_complete callback failed") \
|
||||
XX(CB_status, "the on_status callback failed") \
|
||||
\
|
||||
/* Parsing-related errors */ \
|
||||
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
|
||||
XX(HEADER_OVERFLOW, \
|
||||
"too many header bytes seen; overflow detected") \
|
||||
XX(CLOSED_CONNECTION, \
|
||||
"data received after completed connection: close message") \
|
||||
XX(INVALID_VERSION, "invalid HTTP version") \
|
||||
XX(INVALID_STATUS, "invalid HTTP status code") \
|
||||
XX(INVALID_METHOD, "invalid HTTP method") \
|
||||
XX(INVALID_URL, "invalid URL") \
|
||||
XX(INVALID_HOST, "invalid host") \
|
||||
XX(INVALID_PORT, "invalid port") \
|
||||
XX(INVALID_PATH, "invalid path") \
|
||||
XX(INVALID_QUERY_STRING, "invalid query string") \
|
||||
XX(INVALID_FRAGMENT, "invalid fragment") \
|
||||
XX(LF_EXPECTED, "LF character expected") \
|
||||
XX(INVALID_HEADER_TOKEN, "invalid character in header") \
|
||||
XX(INVALID_CONTENT_LENGTH, \
|
||||
"invalid character in content-length header") \
|
||||
XX(INVALID_CHUNK_SIZE, \
|
||||
"invalid character in chunk size header") \
|
||||
XX(INVALID_CONSTANT, "invalid constant string") \
|
||||
XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
|
||||
XX(STRICT, "strict mode assertion failed") \
|
||||
XX(PAUSED, "parser is paused") \
|
||||
XX(UNKNOWN, "an unknown error occurred")
|
||||
|
||||
|
||||
/* Define HPE_* values for each errno value above */
|
||||
#define HTTP_ERRNO_GEN(n, s) HPE_##n,
|
||||
enum http_errno {
|
||||
HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
|
||||
};
|
||||
#undef HTTP_ERRNO_GEN
|
||||
|
||||
|
||||
/* Get an http_errno value from an http_parser */
|
||||
#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
|
||||
|
||||
|
||||
struct http_parser {
|
||||
/** PRIVATE **/
|
||||
unsigned int type : 2; /* enum http_parser_type */
|
||||
unsigned int flags : 6; /* F_* values from 'flags' enum; semi-public */
|
||||
unsigned int state : 8; /* enum state from http_parser.c */
|
||||
unsigned int header_state : 8; /* enum header_state from http_parser.c */
|
||||
unsigned int index : 8; /* index into current matcher */
|
||||
|
||||
uint32_t nread; /* # bytes read in various scenarios */
|
||||
uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */
|
||||
|
||||
/** READ-ONLY **/
|
||||
unsigned short http_major;
|
||||
unsigned short http_minor;
|
||||
unsigned int status_code : 16; /* responses only */
|
||||
unsigned int method : 8; /* requests only */
|
||||
unsigned int http_errno : 7;
|
||||
|
||||
/* 1 = Upgrade header was present and the parser has exited because of that.
|
||||
* 0 = No upgrade header present.
|
||||
* Should be checked when http_parser_execute() returns in addition to
|
||||
* error checking.
|
||||
*/
|
||||
unsigned int upgrade : 1;
|
||||
|
||||
/** PUBLIC **/
|
||||
void *data; /* A pointer to get hook to the "connection" or "socket" object */
|
||||
};
|
||||
|
||||
|
||||
struct http_parser_settings {
|
||||
http_cb on_message_begin;
|
||||
http_data_cb on_url;
|
||||
http_data_cb on_status;
|
||||
http_data_cb on_header_field;
|
||||
http_data_cb on_header_value;
|
||||
http_cb on_headers_complete;
|
||||
http_data_cb on_body;
|
||||
http_cb on_message_complete;
|
||||
};
|
||||
|
||||
|
||||
enum http_parser_url_fields
|
||||
{ UF_SCHEMA = 0
|
||||
, UF_HOST = 1
|
||||
, UF_PORT = 2
|
||||
, UF_PATH = 3
|
||||
, UF_QUERY = 4
|
||||
, UF_FRAGMENT = 5
|
||||
, UF_USERINFO = 6
|
||||
, UF_MAX = 7
|
||||
};
|
||||
|
||||
|
||||
/* Result structure for http_parser_parse_url().
|
||||
*
|
||||
* Callers should index into field_data[] with UF_* values iff field_set
|
||||
* has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
|
||||
* because we probably have padding left over), we convert any port to
|
||||
* a uint16_t.
|
||||
*/
|
||||
struct http_parser_url {
|
||||
uint16_t field_set; /* Bitmask of (1 << UF_*) values */
|
||||
uint16_t port; /* Converted UF_PORT string */
|
||||
|
||||
struct {
|
||||
uint16_t off; /* Offset into buffer in which field starts */
|
||||
uint16_t len; /* Length of run in buffer */
|
||||
} field_data[UF_MAX];
|
||||
};
|
||||
|
||||
|
||||
/* Returns the library version. Bits 16-23 contain the major version number,
|
||||
* bits 8-15 the minor version number and bits 0-7 the patch level.
|
||||
* Usage example:
|
||||
*
|
||||
* unsigned long version = http_parser_version();
|
||||
* unsigned major = (version >> 16) & 255;
|
||||
* unsigned minor = (version >> 8) & 255;
|
||||
* unsigned patch = version & 255;
|
||||
* printf("http_parser v%u.%u.%u\n", major, minor, patch);
|
||||
*/
|
||||
unsigned long http_parser_version(void);
|
||||
|
||||
void http_parser_init(http_parser *parser, enum http_parser_type type);
|
||||
|
||||
|
||||
/* Executes the parser. Returns number of parsed bytes. Sets
|
||||
* `parser->http_errno` on error. */
|
||||
size_t http_parser_execute(http_parser *parser,
|
||||
const http_parser_settings *settings,
|
||||
const char *data,
|
||||
size_t len);
|
||||
|
||||
|
||||
/* If http_should_keep_alive() in the on_headers_complete or
|
||||
* on_message_complete callback returns 0, then this should be
|
||||
* the last message on the connection.
|
||||
* If you are the server, respond with the "Connection: close" header.
|
||||
* If you are the client, close the connection.
|
||||
*/
|
||||
int http_should_keep_alive(const http_parser *parser);
|
||||
|
||||
/* Returns a string version of the HTTP method. */
|
||||
const char *http_method_str(enum http_method m);
|
||||
|
||||
/* Return a string name of the given error */
|
||||
const char *http_errno_name(enum http_errno err);
|
||||
|
||||
/* Return a string description of the given error */
|
||||
const char *http_errno_description(enum http_errno err);
|
||||
|
||||
/* Parse a URL; return nonzero on failure */
|
||||
int http_parser_parse_url(const char *buf, size_t buflen,
|
||||
int is_connect,
|
||||
struct http_parser_url *u);
|
||||
|
||||
/* Pause or un-pause the parser; a nonzero value pauses */
|
||||
void http_parser_pause(http_parser *parser, int paused);
|
||||
|
||||
/* Checks if this is the final chunk of the body. */
|
||||
int http_body_is_final(const http_parser *parser);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
|
@ -0,0 +1,415 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <haywire.h>
|
||||
#include "hw_string.h"
|
||||
#include "khash.h"
|
||||
#include "http_request.h"
|
||||
#include "http_response.h"
|
||||
#include "http_server.h"
|
||||
#include "server_stats.h"
|
||||
#include "route_compare_method.h"
|
||||
|
||||
extern char* uv__strndup(const char* s, size_t n);
|
||||
|
||||
#define CRLF "\r\n"
|
||||
static const char response_404[] =
|
||||
"HTTP/1.1 404 Not Found" CRLF
|
||||
"Server: Haywire/master" CRLF
|
||||
"Date: Fri, 26 Aug 2011 00:31:53 GMT" CRLF
|
||||
"Connection: Keep-Alive" CRLF
|
||||
"Content-Type: text/html" CRLF
|
||||
"Content-Length: 16" CRLF
|
||||
CRLF
|
||||
"404 Not Found" CRLF
|
||||
;
|
||||
|
||||
static kh_inline khint_t hw_string_hash_func(hw_string* s)
|
||||
{
|
||||
khint_t h = s->length > 0 ? (khint_t)*s->value : 0;
|
||||
if (h) for (int i = 0; i < s->length; i++) h = (h << 5) - h + (khint_t)*(s->value + i);
|
||||
return h;
|
||||
}
|
||||
|
||||
#define hw_string_hash_equal(a, b) (hw_strcmp(a, b) == 0)
|
||||
|
||||
KHASH_INIT(hw_string_hashmap, hw_string*, hw_string*, 1, hw_string_hash_func, hw_string_hash_equal)
|
||||
KHASH_MAP_INIT_STR(string_hashmap, char*)
|
||||
KHASH_MAP_INIT_INT64(offset_hashmap, int)
|
||||
|
||||
void hw_print_request_headers(http_request* request)
|
||||
{
|
||||
hw_string* k;
|
||||
hw_string* v;
|
||||
|
||||
khash_t(hw_string_hashmap) *h = request->headers;
|
||||
kh_foreach(h, k, v, {
|
||||
char* key = uv__strndup(k->value, k->length + 1);
|
||||
char* value = uv__strndup(v->value, v->length + 1);
|
||||
printf("KEY: %s VALUE: %s\n", key, value);
|
||||
free(key);
|
||||
free(value);
|
||||
});
|
||||
}
|
||||
|
||||
void hw_print_body(http_request* request)
|
||||
{
|
||||
if (request->body->length > 0) {
|
||||
char* body = uv__strndup(request->body->value, request->body->length);
|
||||
printf("BODY: %s\n", body);
|
||||
free(body);
|
||||
}
|
||||
else {
|
||||
printf("BODY is empty!\n");
|
||||
}
|
||||
}
|
||||
|
||||
void* get_header(http_request* request, hw_string* name)
|
||||
{
|
||||
khash_t(hw_string_hashmap) *h = request->headers;
|
||||
khiter_t k = kh_get(hw_string_hashmap, h, name);
|
||||
|
||||
void* val;
|
||||
|
||||
int is_missing = (k == kh_end(h));
|
||||
if (is_missing) {
|
||||
val = NULL;
|
||||
} else {
|
||||
val = kh_value(h, k);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
void set_header(http_request* request, hw_string* name, hw_string* value)
|
||||
{
|
||||
int ret, i;
|
||||
khiter_t k;
|
||||
khash_t(hw_string_hashmap) *h = request->headers;
|
||||
|
||||
for (i = 0; i < name->length; i++)
|
||||
{
|
||||
name->value[i] = tolower(name->value[i]);
|
||||
}
|
||||
|
||||
void* prev = get_header(request, name);
|
||||
if (prev) {
|
||||
free(prev);
|
||||
}
|
||||
|
||||
k = kh_put(hw_string_hashmap, h, name, &ret);
|
||||
kh_value(h, k) = value;
|
||||
}
|
||||
|
||||
http_request* create_http_request(http_connection* connection)
|
||||
{
|
||||
http_request* request = calloc(1, sizeof(http_request));
|
||||
request->headers = kh_init(hw_string_hashmap);
|
||||
request->url = calloc(1, sizeof(hw_string));
|
||||
request->body = calloc(1, sizeof(hw_string));
|
||||
request->state = OK;
|
||||
INCREMENT_STAT(stat_requests_created_total);
|
||||
return request;
|
||||
}
|
||||
|
||||
void free_http_request(http_request* request)
|
||||
{
|
||||
khash_t(hw_string_hashmap) *h = request->headers;
|
||||
|
||||
hw_string* k;
|
||||
hw_string* v;
|
||||
|
||||
kh_foreach(h, k, v,
|
||||
{
|
||||
free((hw_string*)k);
|
||||
free((hw_string*)v);
|
||||
});
|
||||
|
||||
kh_destroy(hw_string_hashmap, h);
|
||||
|
||||
free(request->url);
|
||||
free(request->body);
|
||||
|
||||
free(request);
|
||||
|
||||
INCREMENT_STAT(stat_requests_destroyed_total);
|
||||
}
|
||||
|
||||
hw_string* hw_get_header(http_request* request, hw_string* key)
|
||||
{
|
||||
void* value = get_header(request, key);
|
||||
return value;
|
||||
}
|
||||
|
||||
int http_request_on_message_begin(http_parser* parser)
|
||||
{
|
||||
http_connection* connection = (http_connection*)parser->data;
|
||||
if (connection->request) {
|
||||
/* We're seeing a new request on the same connection, so it's time to free up the old one
|
||||
* and create a new one.
|
||||
*
|
||||
* Note: This assumes that the request callback is synchronous. If we're ever to support async callbacks,
|
||||
* we either need to copy all required data and pass it into the async callback or free on write instead. */
|
||||
free_http_request(connection->request);
|
||||
}
|
||||
connection->request = create_http_request(connection);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int http_request_on_url(http_parser *parser, const char *at, size_t length)
|
||||
{
|
||||
http_connection* connection = (http_connection*)parser->data;
|
||||
http_request* request = connection->request;
|
||||
hw_string* url = request->url;
|
||||
|
||||
if (url->length) {
|
||||
/* This is not the first URL chunk that has been seen in the buffer. Since we've already captured the initial pointer in the branch below,
|
||||
so we can recover the URL later on, we only increment the URL length and ignore the pointer passed in as a parameter. */
|
||||
url->length += length;
|
||||
} else {
|
||||
url->value = at;
|
||||
url->length = length;
|
||||
/* We've seen the URL start, so we need to pin it to recover it later */
|
||||
http_request_buffer_pin(connection->buffer, url, url->value);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void http_request_save_current_header(http_connection* connection) {
|
||||
hw_string* header_key_copy = hw_strdup(&connection->current_header_key);
|
||||
hw_string* header_value_copy = hw_strdup(&connection->current_header_value);
|
||||
|
||||
/* We duplicate the current header key/value, so we actually need to use the new pointers as pin ids, as
|
||||
* those will be the IDs we'll use later on to locate the headers */
|
||||
http_request_buffer_reassign_pin(connection->buffer, &connection->current_header_key, header_key_copy);
|
||||
http_request_buffer_reassign_pin(connection->buffer, &connection->current_header_value, header_value_copy);
|
||||
|
||||
/* Set headers is going to need to have the values of header key/value, so we need to make sure we get
|
||||
* the pointers pointing at the right place in the buffer */
|
||||
header_key_copy->value = http_request_buffer_locate(connection->buffer, header_key_copy,
|
||||
connection->current_header_key.value);
|
||||
|
||||
header_value_copy->value = http_request_buffer_locate(connection->buffer, header_value_copy,
|
||||
connection->current_header_value.value);
|
||||
|
||||
/* Save last header key/value pair that was read */
|
||||
set_header(connection->request, header_key_copy, header_value_copy);
|
||||
}
|
||||
|
||||
|
||||
int http_request_on_header_field(http_parser *parser, const char *at, size_t length)
|
||||
{
|
||||
http_connection* connection = (http_connection*)parser->data;
|
||||
|
||||
if (connection->last_was_value && connection->current_header_key.length > 0) {
|
||||
http_request_save_current_header(connection);
|
||||
|
||||
connection->current_header_key.length = 0;
|
||||
connection->current_header_value.length = 0;
|
||||
}
|
||||
|
||||
if (connection->current_header_key.length > 0) {
|
||||
/* This is not the first header chunk that has been seen in the buffer. Since we've already captured the initial pointer in the branch below,
|
||||
so we can recover the header later on, we only increment the header length and ignore the pointer passed in as a parameter. */
|
||||
connection->current_header_key.length += length;
|
||||
} else {
|
||||
/* Start of a new header key */
|
||||
connection->current_header_key.value = at;
|
||||
connection->current_header_key.length = length;
|
||||
/* Pin the header key */
|
||||
http_request_buffer_pin(connection->buffer, &connection->current_header_key, at);
|
||||
}
|
||||
|
||||
connection->last_was_value = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int http_request_on_header_value(http_parser *parser, const char *at, size_t length)
|
||||
{
|
||||
http_connection* connection = (http_connection*)parser->data;
|
||||
|
||||
if (!connection->last_was_value) {
|
||||
connection->current_header_value.value = at;
|
||||
connection->current_header_value.length = length;
|
||||
connection->last_was_value = 1;
|
||||
/* Pin the header value */
|
||||
http_request_buffer_pin(connection->buffer, &connection->current_header_value, at);
|
||||
} else {
|
||||
/* Another header value chunk, not necessarily contiguous to the other chunks seen before, so we only increment
|
||||
* the length. When the header value is located later on, it will be guaranteedly contiguous. */
|
||||
connection->current_header_value.length += length;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int http_request_on_headers_complete(http_parser* parser)
|
||||
{
|
||||
http_connection* connection = (http_connection*)parser->data;
|
||||
|
||||
if (connection->current_header_key.length > 0 && connection->current_header_value.length > 0) {
|
||||
http_request_save_current_header(connection);
|
||||
}
|
||||
connection->current_header_key.length = 0;
|
||||
connection->current_header_value.length = 0;
|
||||
|
||||
connection->request->http_major = parser->http_major;
|
||||
connection->request->http_minor = parser->http_minor;
|
||||
connection->request->method = parser->method;
|
||||
connection->keep_alive = http_should_keep_alive(parser);
|
||||
connection->request->keep_alive = connection->keep_alive;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int http_request_on_body(http_parser *parser, const char *at, size_t length)
|
||||
{
|
||||
http_connection* connection = (http_connection*)parser->data;
|
||||
http_request* request = connection->request;
|
||||
hw_string* body = request->body;
|
||||
if (length != 0)
|
||||
{
|
||||
if (body->length == 0) {
|
||||
body->value = at;
|
||||
body->length = length;
|
||||
/* Let's pin the body so we can recover it later, even if the underlying buffers change */
|
||||
http_request_buffer_pin(connection->buffer, body, body->value);
|
||||
} else {
|
||||
body->length += length;
|
||||
}
|
||||
|
||||
request->body_length = body->length;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
hw_route_entry* get_route_callback(hw_string* url)
|
||||
{
|
||||
hw_route_entry* route_entry = NULL;
|
||||
|
||||
const char* k;
|
||||
const char* v;
|
||||
|
||||
khash_t(string_hashmap) *h = routes;
|
||||
|
||||
kh_foreach(h, k, v,
|
||||
{
|
||||
int found = hw_route_compare_method(url, k);
|
||||
if (found)
|
||||
{
|
||||
route_entry = (hw_route_entry*)v;
|
||||
}
|
||||
});
|
||||
|
||||
return route_entry;
|
||||
}
|
||||
|
||||
void send_error_response(http_request* request, http_response* response, const char* error_code,
|
||||
const char* error_message)
|
||||
{
|
||||
hw_string status_code;
|
||||
hw_string content_type_name;
|
||||
hw_string content_type_value;
|
||||
hw_string body;
|
||||
hw_string keep_alive_name;
|
||||
hw_string keep_alive_value;
|
||||
|
||||
status_code.value = error_code;
|
||||
status_code.length = strlen(error_code);
|
||||
hw_set_response_status_code(response, &status_code);
|
||||
|
||||
SETSTRING(content_type_name, "Content-Type");
|
||||
|
||||
SETSTRING(content_type_value, "text/html");
|
||||
hw_set_response_header(response, &content_type_name, &content_type_value);
|
||||
|
||||
body.value = error_message;
|
||||
body.length = strlen(error_message);
|
||||
hw_set_body(response, &body);
|
||||
|
||||
if (request->keep_alive)
|
||||
{
|
||||
SETSTRING(keep_alive_name, "Connection");
|
||||
|
||||
SETSTRING(keep_alive_value, "Keep-Alive");
|
||||
hw_set_response_header(response, &keep_alive_name, &keep_alive_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
hw_set_http_version(response, 1, 0);
|
||||
}
|
||||
|
||||
hw_http_response_send(response, NULL, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the URL, body and header pointers are correct.
|
||||
* This is because the underlying buffers might have been reallocated/resized,
|
||||
* so we can't use the pointers of where data chunks (e.g. URL start) were originally seen.
|
||||
*/
|
||||
void http_request_locate_members(http_connection* connection)
|
||||
{
|
||||
hw_request_buffer* buffer = connection->buffer;
|
||||
http_request* request = connection->request;
|
||||
request->url->value = http_request_buffer_locate(buffer, request->url, request->url->value);
|
||||
request->body->value = http_request_buffer_locate(buffer, request->body, request->body->value);
|
||||
|
||||
hw_string* header_name;
|
||||
hw_string* header_value;
|
||||
|
||||
khash_t(hw_string_hashmap) *h = request->headers;
|
||||
kh_foreach(h, header_name, header_value, {
|
||||
header_name->value = http_request_buffer_locate(buffer, header_name, header_name->value);
|
||||
header_value->value = http_request_buffer_locate(buffer, header_value, header_value->value);
|
||||
});
|
||||
}
|
||||
|
||||
int http_request_on_message_complete(http_parser* parser)
|
||||
{
|
||||
http_connection* connection = (http_connection*)parser->data;
|
||||
http_request* request = connection->request;
|
||||
hw_http_response* response = hw_create_http_response(connection);
|
||||
|
||||
char* error = NULL;
|
||||
|
||||
if (connection->request->state == SIZE_EXCEEDED) {
|
||||
// 413 Request entity too large
|
||||
error = HTTP_STATUS_413;
|
||||
} else if (connection->request->state == BAD_REQUEST) {
|
||||
// 400 Bad Request
|
||||
error = HTTP_STATUS_400;
|
||||
} else if (connection->request->state == INTERNAL_ERROR) {
|
||||
// 500 Internal Server Error
|
||||
} else {
|
||||
http_request_locate_members(connection);
|
||||
|
||||
hw_route_entry* route_entry = request != NULL ? get_route_callback(request->url) : NULL;
|
||||
|
||||
if (route_entry != NULL)
|
||||
{
|
||||
route_entry->callback(request, response, route_entry->user_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 404 Not Found.
|
||||
error = HTTP_STATUS_404;
|
||||
}
|
||||
|
||||
/* Let's tell the buffer that we don't care about the data that was read before that is not currently
|
||||
* being processed, so it can be swept later on.
|
||||
*
|
||||
* Since the HTTP parser doesn't tell us exactly where the request ends, we can be exact when creating a mark,
|
||||
* otherwise we could discard everything up to, and including, the end of the current request.
|
||||
*
|
||||
* Instead, we create a mark that will be set at the end of the last chunk that was read before the one
|
||||
* currently being processed and on which callback fired. */
|
||||
http_request_buffer_mark(connection->buffer);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
send_error_response(request, (http_response*)response, error, error);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#include <stddef.h>
|
||||
|
||||
#pragma once
|
||||
#include "uv.h"
|
||||
#include "http_parser.h"
|
||||
|
||||
extern int last_was_value;
|
||||
|
||||
void free_http_request(http_request* request);
|
||||
int http_request_on_message_begin(http_parser *parser);
|
||||
int http_request_on_url(http_parser *parser, const char *at, size_t length);
|
||||
int http_request_on_header_field(http_parser *parser, const char *at, size_t length);
|
||||
int http_request_on_header_value(http_parser *parser, const char *at, size_t length);
|
||||
int http_request_on_body(http_parser *parser, const char *at, size_t length);
|
||||
int http_request_on_headers_complete(http_parser *parser);
|
||||
int http_request_on_message_complete(http_parser *parser);
|
|
@ -0,0 +1,239 @@
|
|||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
#include "haywire.h"
|
||||
#include "http_request_buffers.h"
|
||||
|
||||
#define DEFAULT_BUFFER_SHRINKSIZE 65536
|
||||
|
||||
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
|
||||
|
||||
KHASH_MAP_INIT_INT64(pointer_hashmap, void*)
|
||||
|
||||
typedef struct {
|
||||
size_t max_size;
|
||||
size_t size;
|
||||
size_t mark;
|
||||
size_t used;
|
||||
size_t used_before;
|
||||
void* current;
|
||||
khash_t(pointer_hashmap)* offsets;
|
||||
bool offsets_active;
|
||||
} http_request_buffer;
|
||||
|
||||
void http_request_buffer_consume(hw_request_buffer* buf, size_t consumed) {
|
||||
http_request_buffer* buffer = (http_request_buffer*) buf;
|
||||
buffer->used_before = buffer->used;
|
||||
buffer->used += consumed;
|
||||
}
|
||||
|
||||
void http_request_buffer_mark(hw_request_buffer* buf) {
|
||||
http_request_buffer* buffer = (http_request_buffer*) buf;
|
||||
/* unfortunately, the parser doesn't tell us where the request ends exactly,
|
||||
* so the only thing we can be sure is that it ends in the current buffer chunk, so anything before it can
|
||||
* effectively be swept, so we're placing the mark at that point now. */
|
||||
buffer->mark = buffer->used_before;
|
||||
}
|
||||
|
||||
void http_request_buffer_sweep(hw_request_buffer* buf) {
|
||||
http_request_buffer* buffer = (http_request_buffer*) buf;
|
||||
void* pointer;
|
||||
int offset;
|
||||
int used = buffer->used - buffer->mark;
|
||||
|
||||
if (buffer->mark > 0) {
|
||||
bool offsets_active = false;
|
||||
|
||||
if (buffer->used > 0) {
|
||||
/* Move data beyond the mark to the beginning of the buffer.
|
||||
* While we should avoid memory copies, this is relatively infrequent and will only really copy a
|
||||
* significant amount of data if requests are pipelined. Otherwise, we'll just be copying the last chunk
|
||||
* that was read back to the beginning of the buffer. */
|
||||
memcpy(buffer->current, buffer->current + buffer->mark, used);
|
||||
}
|
||||
|
||||
if (buffer->size > DEFAULT_BUFFER_SHRINKSIZE && buffer->used < DEFAULT_BUFFER_SHRINKSIZE) {
|
||||
/* Shrink buffer */
|
||||
buffer->size = DEFAULT_BUFFER_SHRINKSIZE;
|
||||
buffer->current = realloc(buffer->current, buffer->size);
|
||||
if (!buffer->current) {
|
||||
errno = ENOMEM;
|
||||
buffer->size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update offsets */
|
||||
if (buffer->used) {
|
||||
kh_foreach(buffer->offsets, pointer, offset, {
|
||||
khiter_t offset_key = kh_get(pointer_hashmap, buffer->offsets, pointer);
|
||||
|
||||
if (offset <= buffer->mark) {
|
||||
/* Delete offsets that pointed to bytes before the mark */
|
||||
kh_del(pointer_hashmap, buffer->offsets, offset_key);
|
||||
} else {
|
||||
/* There's at least one offset beyond the mark, so the offsets are active and should be considered
|
||||
* when locating pointers. We need to shift the offset back by the width of the mark */
|
||||
offsets_active = true;
|
||||
kh_value(buffer->offsets, offset_key) = offset - buffer->mark;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
/* the buffer is now empty, so we don't need to keep offsets */
|
||||
kh_clear(pointer_hashmap, buffer->offsets);
|
||||
}
|
||||
|
||||
buffer->mark = 0;
|
||||
buffer->used = used;
|
||||
buffer->offsets_active = offsets_active;
|
||||
}
|
||||
}
|
||||
|
||||
hw_request_buffer* http_request_buffer_init(size_t max_size) {
|
||||
http_request_buffer* buffer = malloc(sizeof(http_request_buffer));
|
||||
buffer->max_size = max_size;
|
||||
buffer->size = 0;
|
||||
buffer->used = 0;
|
||||
buffer->mark = 0;
|
||||
buffer->used_before = 0;
|
||||
buffer->current = NULL;
|
||||
buffer->offsets = kh_init(pointer_hashmap);
|
||||
buffer->offsets_active = false;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void http_request_buffer_chunk(hw_request_buffer* buf, hw_request_buffer_chunk* chunk) {
|
||||
http_request_buffer *buffer = (http_request_buffer *) buf;
|
||||
chunk->size = buffer->size ? buffer->size - buffer->used : 0;
|
||||
chunk->buffer = buffer->current + buffer->used;
|
||||
}
|
||||
|
||||
bool http_request_buffer_alloc(hw_request_buffer* buf, size_t requested_size) {
|
||||
http_request_buffer* buffer = (http_request_buffer*) buf;
|
||||
bool ret = true;
|
||||
void* previous = NULL;
|
||||
|
||||
size_t requested_size_capped = MIN(buffer->max_size, requested_size);
|
||||
|
||||
if (!buffer->current) {
|
||||
buffer->current = malloc(requested_size_capped);
|
||||
if (!buffer->current) {
|
||||
buffer->size = 0;
|
||||
errno = ENOMEM;
|
||||
ret = false;
|
||||
} else {
|
||||
buffer->size = requested_size_capped;
|
||||
}
|
||||
} else if (buffer->used * 2 < buffer->size) {
|
||||
/* ignoring allocation size unless we're above 50% usage */
|
||||
} else if (buffer->size + requested_size_capped <= buffer->max_size) {
|
||||
/* time to reallocate memory and re-point anything using the buffer */
|
||||
previous = buffer->current;
|
||||
|
||||
buffer->current = realloc(buffer->current, buffer->size + requested_size_capped);
|
||||
buffer->size += requested_size_capped;
|
||||
|
||||
if (!buffer->current) {
|
||||
buffer->size = 0;
|
||||
errno = ENOMEM;
|
||||
ret = false;
|
||||
} else if (buffer->current != previous) {
|
||||
buffer->offsets_active = true;
|
||||
}
|
||||
} else {
|
||||
/* maximum request size exceeded */
|
||||
errno = ERANGE;
|
||||
buffer->size = 0;
|
||||
ret = false;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void http_request_buffer_print(hw_request_buffer* buf) {
|
||||
http_request_buffer* buffer = (http_request_buffer*) buf;
|
||||
|
||||
printf("Buffer: current=%u; size=%u; used=%u\n", buffer->current, buffer->size, buffer->used);
|
||||
printf(" 0\t");
|
||||
for (int i = 0; i < buffer->used; i++) {
|
||||
if (((char*) buffer->current)[i] == '\n') {
|
||||
printf("\\n");
|
||||
} else if (((char*) buffer->current)[i] == '\r') {
|
||||
printf("\\r");
|
||||
} else {
|
||||
printf("%c", ((char*) buffer->current)[i]);
|
||||
}
|
||||
|
||||
if ((i + 1) % 10 == 0) {
|
||||
printf("\n%5d\t", (i + 1) / 10);
|
||||
} else {
|
||||
printf("\t");
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
void* pointer;
|
||||
int offset;
|
||||
kh_foreach(buffer->offsets, pointer, offset, {
|
||||
printf("\tPointer %u -> offset=%u\n", pointer, offset);
|
||||
});
|
||||
printf("----\n");
|
||||
}
|
||||
|
||||
void http_request_buffer_pin(hw_request_buffer* buf, void* key, void* pointer) {
|
||||
http_request_buffer* buffer = (http_request_buffer*) buf;
|
||||
|
||||
khiter_t offset_key = kh_get(pointer_hashmap, buffer->offsets, key);
|
||||
|
||||
int offset = pointer - buffer->current;
|
||||
int ret;
|
||||
|
||||
int is_missing = (offset_key == kh_end(buffer->offsets));
|
||||
if (is_missing) {
|
||||
offset_key = kh_put(pointer_hashmap, buffer->offsets, key, &ret);
|
||||
}
|
||||
|
||||
kh_value(buffer->offsets, offset_key) = offset;
|
||||
}
|
||||
|
||||
void http_request_buffer_reassign_pin(hw_request_buffer* buf, void* old_key, void* new_key) {
|
||||
http_request_buffer* buffer = (http_request_buffer*) buf;
|
||||
|
||||
khiter_t old_offset_key = kh_get(pointer_hashmap, buffer->offsets, old_key);
|
||||
|
||||
int offset;
|
||||
int ret;
|
||||
|
||||
int is_missing = (old_offset_key == kh_end(buffer->offsets));
|
||||
if (!is_missing) {
|
||||
offset = kh_val(buffer->offsets, old_offset_key);
|
||||
|
||||
khiter_t new_offset_key = kh_put(pointer_hashmap, buffer->offsets, new_key, &ret);
|
||||
kh_value(buffer->offsets, new_offset_key) = offset;
|
||||
old_offset_key = kh_get(pointer_hashmap, buffer->offsets, old_key);
|
||||
kh_del(pointer_hashmap, buffer->offsets, old_offset_key);
|
||||
}
|
||||
}
|
||||
|
||||
void* http_request_buffer_locate(hw_request_buffer* buf, void* key, void* default_pointer) {
|
||||
http_request_buffer* buffer = (http_request_buffer*) buf;
|
||||
void* location = default_pointer;
|
||||
khiter_t offset_key = kh_get(pointer_hashmap, buffer->offsets, key);
|
||||
|
||||
int offset, is_missing;
|
||||
|
||||
if (buffer->offsets_active) {
|
||||
is_missing = (offset_key == kh_end(buffer->offsets));
|
||||
if (!is_missing) {
|
||||
offset = kh_value(buffer->offsets, offset_key);
|
||||
location = buffer->current + offset;
|
||||
}
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
void http_request_buffer_destroy(hw_request_buffer* buf) {
|
||||
http_request_buffer* buffer = (http_request_buffer*) buf;
|
||||
kh_destroy(pointer_hashmap, buffer->offsets);
|
||||
free(buffer->current);
|
||||
free(buffer);
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include "khash.h"
|
||||
#include "haywire.h"
|
||||
|
||||
typedef struct {
|
||||
void* buffer;
|
||||
size_t size;
|
||||
} hw_request_buffer_chunk;
|
||||
|
||||
typedef void* hw_request_buffer;
|
||||
|
||||
/**
|
||||
* Initializes a new buffer. The caller is responsible for calling http_request_buffer_destroy to free up memory.
|
||||
*/
|
||||
hw_request_buffer* http_request_buffer_init(size_t max_size);
|
||||
|
||||
/**
|
||||
* Signals that "size" bytes from the buffer are now in use.
|
||||
*/
|
||||
void http_request_buffer_consume(hw_request_buffer* buffer, size_t size);
|
||||
|
||||
/**
|
||||
* Marks all buffer chunks up to the last one allocated (exclusive) for removal when
|
||||
* http_request_buffer_sweep gets called.
|
||||
*/
|
||||
void http_request_buffer_mark(hw_request_buffer* buffer);
|
||||
|
||||
/**
|
||||
* Sweeps all buffer chunks up to the mark. Data for the last chunk is copied to the beginning of the buffer
|
||||
* and the offsets are updates accordingly.
|
||||
*/
|
||||
void http_request_buffer_sweep(hw_request_buffer* buffer);
|
||||
|
||||
/**
|
||||
* Prints the buffer.
|
||||
*/
|
||||
void http_request_buffer_print(hw_request_buffer* buffer);
|
||||
|
||||
/**
|
||||
* Ensures that there's will be a sizable chunk available when the it's requested via http_request_buffer_chunk.
|
||||
* This will ensure that the requested size doesn't exceed the maximum buffer size and will try to
|
||||
* re-use the existing underlying buffers if there's sufficient space still to be used.
|
||||
* Therefore, "requested_size" is just a hint, and not necessarily the size that will be allocated.
|
||||
*
|
||||
* Returns true if the allocation succeeds, false otherwise (errno is set accordingly).
|
||||
*/
|
||||
bool http_request_buffer_alloc(hw_request_buffer* buf, size_t requested_size);
|
||||
|
||||
/**
|
||||
* Returns a new buffer chunk to be used by request reader.
|
||||
*/
|
||||
void http_request_buffer_chunk(hw_request_buffer* buffer, hw_request_buffer_chunk* chunk);
|
||||
|
||||
/**
|
||||
* Informs the buffer manager that *pointer is a memory region of interest and that will have to be made available to
|
||||
* the caller eventually, when http_request_buffer_locate is called. The pin is given a key by the caller so it can be
|
||||
* retrieved later. Pins will be overwritten if the same key is used multiple times.
|
||||
*/
|
||||
void http_request_buffer_pin(hw_request_buffer* buffer, void* key, void* pointer);
|
||||
|
||||
/**
|
||||
* Allows the caller to assign a new key to a pin.
|
||||
*/
|
||||
void http_request_buffer_reassign_pin(hw_request_buffer* buffer, void* old_key, void* new_key);
|
||||
|
||||
/**
|
||||
* Returns the pointer to the memory region associated with a pin. If there isn't a pin with that key, then
|
||||
* the value of "default_pointer" is returned.
|
||||
*
|
||||
* This call guarantees that the memory region returned is contiguous, i.e. even if multiple chunks non contiguous
|
||||
* chunks were used before, the pointer returned by this function points to a memory region that can be read
|
||||
* sequentially. It's the responsibility of the caller to know how long that region is, by keeping track of its
|
||||
* length as it's being read in.
|
||||
*/
|
||||
void* http_request_buffer_locate(hw_request_buffer* buffer, void* key, void* default_pointer);
|
||||
|
||||
/**
|
||||
* Destroys the buffer and any underlying buffer chunks.
|
||||
*/
|
||||
void http_request_buffer_destroy(hw_request_buffer* buffer);
|
|
@ -0,0 +1,152 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include "http_response.h"
|
||||
#include "http_server.h"
|
||||
#include "http_response_cache.h"
|
||||
#include "hw_string.h"
|
||||
|
||||
#define CRLF "\r\n"
|
||||
KHASH_MAP_INIT_STR(string_hashmap, char*)
|
||||
|
||||
hw_http_response hw_create_http_response(http_connection* connection)
|
||||
{
|
||||
http_response* response = malloc(sizeof(http_response));
|
||||
response->connection = connection;
|
||||
response->request = connection->request;
|
||||
response->http_major = 1;
|
||||
response->http_minor = 1;
|
||||
response->body.value = NULL;
|
||||
response->body.length = 0;
|
||||
response->number_of_headers = 0;
|
||||
return response;
|
||||
}
|
||||
|
||||
void hw_free_http_response(hw_http_response* response)
|
||||
{
|
||||
http_response* resp = (http_response*)response;
|
||||
free(resp);
|
||||
}
|
||||
|
||||
void hw_set_http_version(hw_http_response* response, unsigned short major, unsigned short minor)
|
||||
{
|
||||
http_response* resp = (http_response*)response;
|
||||
resp->http_major = major;
|
||||
resp->http_minor = minor;
|
||||
}
|
||||
|
||||
void hw_set_response_status_code(hw_http_response* response, hw_string* status_code)
|
||||
{
|
||||
http_response* resp = (http_response*)response;
|
||||
resp->status_code = *status_code;
|
||||
}
|
||||
|
||||
void hw_set_response_header(hw_http_response* response, hw_string* name, hw_string* value)
|
||||
{
|
||||
http_response* resp = (http_response*)response;
|
||||
http_header* header = &resp->headers[resp->number_of_headers];
|
||||
header->name = *name;
|
||||
header->value = *value;
|
||||
resp->headers[resp->number_of_headers] = *header;
|
||||
resp->number_of_headers++;
|
||||
}
|
||||
|
||||
void hw_set_body(hw_http_response* response, hw_string* body)
|
||||
{
|
||||
http_response* resp = (http_response*)response;
|
||||
resp->body = *body;
|
||||
}
|
||||
|
||||
int num_chars(int n) {
|
||||
int r = 1;
|
||||
if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
|
||||
while (n > 9) {
|
||||
n /= 10;
|
||||
r++;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
hw_string* create_response_buffer(hw_http_response* response)
|
||||
{
|
||||
http_response* resp = (http_response*)response;
|
||||
hw_string* response_string = malloc(sizeof(hw_string));
|
||||
hw_string* cached_entry = get_cached_request(resp->status_code.value);
|
||||
hw_string content_length;
|
||||
|
||||
int i = 0;
|
||||
|
||||
char length_header[] = "Content-Length: ";
|
||||
int line_sep_size = sizeof(CRLF);
|
||||
|
||||
int header_buffer_incr = 512;
|
||||
int body_size = resp->body.length;
|
||||
int header_size_remaining = header_buffer_incr;
|
||||
int response_size = header_size_remaining + sizeof(length_header) + num_chars(resp->body.length) + 2 * line_sep_size + body_size + line_sep_size;
|
||||
|
||||
response_string->value = malloc(response_size);
|
||||
|
||||
response_string->length = 0;
|
||||
append_string(response_string, cached_entry);
|
||||
|
||||
for (i=0; i< resp->number_of_headers; i++)
|
||||
{
|
||||
http_header header = resp->headers[i];
|
||||
|
||||
header_size_remaining -= header.name.length + 2 + header.value.length + line_sep_size;
|
||||
if (header_size_remaining < 0) {
|
||||
header_size_remaining += header_buffer_incr * ((-header_size_remaining/header_buffer_incr) + 1);
|
||||
response_size += header_size_remaining;
|
||||
response_string->value = realloc(response_string->value, response_size);
|
||||
}
|
||||
|
||||
append_string(response_string, &header.name);
|
||||
APPENDSTRING(response_string, ": ");
|
||||
append_string(response_string, &header.value);
|
||||
APPENDSTRING(response_string, CRLF);
|
||||
}
|
||||
|
||||
/* Add the body */
|
||||
APPENDSTRING(response_string, length_header);
|
||||
|
||||
string_from_int(&content_length, body_size, 10);
|
||||
|
||||
if (body_size > 0) {
|
||||
append_string(response_string, &content_length);
|
||||
}
|
||||
else {
|
||||
hw_string zero_content;
|
||||
zero_content.value = "0";
|
||||
zero_content.length = 1;
|
||||
append_string(response_string, &zero_content);
|
||||
}
|
||||
|
||||
APPENDSTRING(response_string, CRLF CRLF);
|
||||
|
||||
if (body_size > 0)
|
||||
{
|
||||
append_string(response_string, &resp->body);
|
||||
}
|
||||
|
||||
return response_string;
|
||||
}
|
||||
|
||||
|
||||
void hw_http_response_send(hw_http_response* response, void* user_data, http_response_complete_callback callback)
|
||||
{
|
||||
hw_write_context* write_context = malloc(sizeof(hw_write_context));
|
||||
http_response* resp = (http_response*)response;
|
||||
hw_string* response_buffer = create_response_buffer(response);
|
||||
|
||||
write_context->connection = resp->connection;
|
||||
write_context->user_data = user_data;
|
||||
write_context->callback = callback;
|
||||
write_context->request = resp->request;
|
||||
|
||||
http_server_write_response(write_context, response_buffer);
|
||||
resp->sent = 1;
|
||||
|
||||
free(response_buffer);
|
||||
hw_free_http_response(response);
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
#include "haywire.h"
|
||||
#include "http_connection.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
hw_string name;
|
||||
hw_string value;
|
||||
} http_header;
|
||||
|
||||
#define MAX_HEADERS 64
|
||||
typedef struct
|
||||
{
|
||||
http_connection* connection;
|
||||
http_request* request;
|
||||
unsigned short http_major;
|
||||
unsigned short http_minor;
|
||||
hw_string status_code;
|
||||
http_header headers[MAX_HEADERS];
|
||||
int number_of_headers;
|
||||
hw_string body;
|
||||
int sent;
|
||||
} http_response;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
http_connection* connection;
|
||||
http_request* request;
|
||||
void* user_data;
|
||||
http_response_complete_callback callback;
|
||||
} hw_write_context;
|
||||
|
||||
hw_http_response hw_create_http_response(http_connection* connection);
|
||||
hw_string* create_response_buffer(hw_http_response* response);
|
|
@ -0,0 +1,152 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <haywire.h>
|
||||
#include "uv.h"
|
||||
#include "haywire.h"
|
||||
#include "hw_string.h"
|
||||
#include "khash.h"
|
||||
#include "http_response_cache.h"
|
||||
#include "http_server.h"
|
||||
|
||||
#define CRLF "\r\n"
|
||||
KHASH_MAP_INIT_STR(string_hashmap, hw_string*)
|
||||
|
||||
static uv_timer_t cache_invalidation_timer;
|
||||
static uv_key_t thread_cache_key;
|
||||
|
||||
void initialize_http_request_cache();
|
||||
void free_http_request_cache();
|
||||
void http_request_cache_timer(uv_timer_t* handle);
|
||||
void create_cached_http_request(khash_t(string_hashmap)* http_request_cache, const char* http_status);
|
||||
void set_cached_request(khash_t(string_hashmap)* http_request_cache, char* http_status, hw_string* cache_entry);
|
||||
hw_string* get_cached_request(const char* http_status);
|
||||
|
||||
void initialize_http_request_cache()
|
||||
{
|
||||
uv_key_create(&thread_cache_key);
|
||||
}
|
||||
|
||||
void http_request_cache_configure_listener(uv_loop_t* loop, uv_async_t* handle)
|
||||
{
|
||||
uv_timer_t* cache_invalidation_timer = malloc(sizeof(uv_timer_t));
|
||||
uv_timer_init(loop, cache_invalidation_timer);
|
||||
uv_timer_start(cache_invalidation_timer, http_request_cache_timer, 500, 500);
|
||||
|
||||
if (handle != NULL)
|
||||
{
|
||||
uv_unref((uv_handle_t*) cache_invalidation_timer);
|
||||
}
|
||||
}
|
||||
|
||||
void http_request_cache_timer(uv_timer_t* timer)
|
||||
{
|
||||
khash_t(string_hashmap)* http_request_cache = uv_key_get(&thread_cache_key);
|
||||
if (http_request_cache != NULL)
|
||||
{
|
||||
free_http_request_cache(http_request_cache);
|
||||
}
|
||||
}
|
||||
|
||||
void free_http_request_cache(khash_t(string_hashmap)* http_request_cache)
|
||||
{
|
||||
const char* k;
|
||||
hw_string* v;
|
||||
|
||||
kh_foreach(http_request_cache, k, v,
|
||||
{
|
||||
free((char*)k);
|
||||
free(v->value);
|
||||
free(v);
|
||||
});
|
||||
|
||||
kh_destroy_string_hashmap(http_request_cache);
|
||||
uv_key_set(&thread_cache_key, NULL);
|
||||
}
|
||||
|
||||
void create_cached_http_request(khash_t(string_hashmap)* http_request_cache, const char* http_status)
|
||||
{
|
||||
hw_string* cache_entry = malloc(sizeof(hw_string));
|
||||
cache_entry->value = calloc(1024, 1);
|
||||
cache_entry->length = 0;
|
||||
hw_string status;
|
||||
status.length = strlen(http_status);
|
||||
status.value = http_status;
|
||||
|
||||
append_string(cache_entry, http_v1_1);
|
||||
append_string(cache_entry, &status);
|
||||
APPENDSTRING(cache_entry, CRLF);
|
||||
append_string(cache_entry, server_name);
|
||||
APPENDSTRING(cache_entry, CRLF);
|
||||
|
||||
// Add the current time.
|
||||
time_t curtime;
|
||||
time(&curtime);
|
||||
char* current_time = ctime(&curtime);
|
||||
hw_string current_datetime;
|
||||
current_datetime.value = current_time;
|
||||
current_datetime.length = strlen(current_time);
|
||||
APPENDSTRING(cache_entry, "Date: ");
|
||||
append_string(cache_entry, ¤t_datetime);
|
||||
|
||||
set_cached_request(http_request_cache, http_status, cache_entry);
|
||||
}
|
||||
|
||||
void set_cached_request(khash_t(string_hashmap)* http_request_cache, char* http_status, hw_string* cache_entry)
|
||||
{
|
||||
int ret;
|
||||
khiter_t key;
|
||||
|
||||
key = kh_get(string_hashmap, http_request_cache, http_status);
|
||||
if (key == kh_end(http_request_cache))
|
||||
{
|
||||
key = kh_put(string_hashmap, http_request_cache, dupstr(http_status), &ret);
|
||||
kh_value(http_request_cache, key) = cache_entry;
|
||||
}
|
||||
}
|
||||
|
||||
hw_string* get_cached_request(const char* http_status)
|
||||
{
|
||||
int is_missing = 0;
|
||||
void* val;
|
||||
khash_t(string_hashmap)* http_request_cache;
|
||||
khiter_t key = 0;
|
||||
|
||||
/* This thread hasn't created a response cache so create one */
|
||||
http_request_cache = uv_key_get(&thread_cache_key);
|
||||
if (http_request_cache == NULL)
|
||||
{
|
||||
http_request_cache = kh_init(string_hashmap);
|
||||
uv_key_set(&thread_cache_key, http_request_cache);
|
||||
}
|
||||
|
||||
key = kh_get(string_hashmap, http_request_cache, http_status);
|
||||
if (key == kh_end(http_request_cache))
|
||||
{
|
||||
create_cached_http_request(http_request_cache, http_status);
|
||||
}
|
||||
else
|
||||
{
|
||||
val = kh_value(http_request_cache, key);
|
||||
}
|
||||
|
||||
key = kh_get(string_hashmap, http_request_cache, http_status);
|
||||
if (key == kh_end(http_request_cache))
|
||||
{
|
||||
is_missing = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = kh_value(http_request_cache, key);
|
||||
is_missing = 0;
|
||||
}
|
||||
|
||||
if (is_missing)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
#include "uv.h"
|
||||
#include "haywire.h"
|
||||
|
||||
void initialize_http_request_cache();
|
||||
hw_string* get_cached_request(const char* http_status);
|
||||
void http_request_cache_configure_listener(uv_loop_t* loop, uv_async_t* handle);
|
|
@ -0,0 +1,524 @@
|
|||
#pragma comment (lib, "libuv.lib")
|
||||
#pragma comment (lib, "ws2_32.lib")
|
||||
#pragma comment(lib, "psapi.lib")
|
||||
#pragma comment(lib, "Iphlpapi.lib")
|
||||
|
||||
#ifdef PLATFORM_POSIX
|
||||
#include <signal.h>
|
||||
#endif // PLATFORM_POSIX
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <haywire.h>
|
||||
#include <zlog.h>
|
||||
#include "uv.h"
|
||||
#include "hw_string.h"
|
||||
#include "khash.h"
|
||||
#include "http_server.h"
|
||||
#include "connection_consumer.h"
|
||||
#include "connection_dispatcher.h"
|
||||
#include "http_response_cache.h"
|
||||
#include "server_stats.h"
|
||||
#include "configuration/configuration.h"
|
||||
#include "http_connection.h"
|
||||
#include "http_request.h"
|
||||
|
||||
#define UVERR(err, msg) dzlog_error("%s: %s\n", msg, uv_strerror(err))
|
||||
//fprintf(stderr, "%s: %s\n", msg, uv_strerror(err))
|
||||
#define CHECK(r, msg) \
|
||||
if (r) { \
|
||||
uv_err_t err = uv_last_error(uv_loop); \
|
||||
UVERR(err, msg); \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
KHASH_MAP_INIT_STR(string_hashmap, hw_route_entry*)
|
||||
|
||||
configuration* config;
|
||||
static uv_tcp_t server;
|
||||
static http_parser_settings parser_settings;
|
||||
static struct sockaddr_in listen_address;
|
||||
|
||||
uv_loop_t* uv_loop;
|
||||
void* routes;
|
||||
hw_string* http_v1_0;
|
||||
hw_string* http_v1_1;
|
||||
hw_string* server_name;
|
||||
int listener_count;
|
||||
uv_async_t* listener_async_handles;
|
||||
uv_loop_t* listener_event_loops;
|
||||
uv_barrier_t* listeners_created_barrier;
|
||||
|
||||
int hw_init_with_config(configuration* c)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
char route[] = "/stats";
|
||||
hw_http_add_route(route, get_server_stats, NULL);
|
||||
#endif /* DEBUG */
|
||||
/* Copy the configuration */
|
||||
config = malloc(sizeof(configuration));
|
||||
config->http_listen_address = dupstr(c->http_listen_address);
|
||||
config->http_listen_port = c->http_listen_port;
|
||||
config->thread_count = c->thread_count;
|
||||
config->tcp_nodelay = c->tcp_nodelay;
|
||||
config->listen_backlog = c->listen_backlog? c->listen_backlog : SOMAXCONN;
|
||||
config->parser = dupstr(c->parser);
|
||||
config->balancer = dupstr(c->balancer);
|
||||
config->max_request_size = c->max_request_size;
|
||||
|
||||
http_v1_0 = create_string("HTTP/1.0 ");
|
||||
http_v1_1 = create_string("HTTP/1.1 ");
|
||||
server_name = create_string("Server: Haywire/master");
|
||||
|
||||
if (strcmp(config->parser, "http_parser") == 0)
|
||||
{
|
||||
http_stream_on_read = &http_stream_on_read_http_parser;
|
||||
}
|
||||
http_server_write_response = &http_server_write_response_single;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hw_init_from_config(char* configuration_filename)
|
||||
{
|
||||
configuration* config = load_configuration(configuration_filename);
|
||||
if (config == NULL)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return hw_init_with_config(config);
|
||||
}
|
||||
|
||||
void print_configuration()
|
||||
{
|
||||
#if 0
|
||||
dzlog_debug("Address: %s\n\tPort: %d\n\tThreads: %d\n\tBalancer: %s\n\t"
|
||||
"Parser: %s\n\tTCP No Delay: %s\n\tListen backlog: %d\n\tMaximum request size: %d\n",
|
||||
config->http_listen_address,
|
||||
config->http_listen_port,
|
||||
config->thread_count,
|
||||
config->balancer,
|
||||
config->parser,
|
||||
config->tcp_nodelay? "on": "off",
|
||||
config->listen_backlog,
|
||||
config->max_request_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
http_connection* create_http_connection()
|
||||
{
|
||||
http_connection* connection = calloc(1, sizeof(http_connection));
|
||||
connection->buffer = http_request_buffer_init(config->max_request_size);
|
||||
INCREMENT_STAT(stat_connections_created_total);
|
||||
return connection;
|
||||
}
|
||||
|
||||
void free_http_connection(http_connection* connection)
|
||||
{
|
||||
if (connection->request)
|
||||
{
|
||||
free_http_request(connection->request);
|
||||
}
|
||||
http_request_buffer_destroy(connection->buffer);
|
||||
free(connection);
|
||||
INCREMENT_STAT(stat_connections_destroyed_total);
|
||||
}
|
||||
|
||||
void set_route(void* hashmap, char* name, hw_route_entry* route_entry)
|
||||
{
|
||||
int ret;
|
||||
khiter_t k;
|
||||
khash_t(string_hashmap) *h = hashmap;
|
||||
k = kh_put(string_hashmap, h, dupstr(name), &ret);
|
||||
kh_value(h, k) = route_entry;
|
||||
}
|
||||
|
||||
void hw_http_add_route(char *route, http_request_callback callback, void* user_data)
|
||||
{
|
||||
hw_route_entry* route_entry = malloc(sizeof(hw_route_entry));
|
||||
route_entry->callback = callback;
|
||||
route_entry->user_data = user_data;
|
||||
|
||||
if (routes == NULL)
|
||||
{
|
||||
routes = kh_init(string_hashmap);
|
||||
}
|
||||
set_route(routes, route, route_entry);
|
||||
dzlog_debug("Added route %s\n", route); // TODO: Replace with logging instead.
|
||||
}
|
||||
|
||||
void free_http_server()
|
||||
{
|
||||
/* TODO: Shut down accepting incoming requests */
|
||||
khash_t(string_hashmap) *h = routes;
|
||||
const char* k;
|
||||
const char* v;
|
||||
kh_foreach(h, k, v, { free((char*)k); free((char*)v); });
|
||||
kh_destroy(string_hashmap, routes);
|
||||
}
|
||||
|
||||
int hw_http_open()
|
||||
{
|
||||
int threads = config->thread_count;
|
||||
uv_async_t* service_handle = 0;
|
||||
|
||||
if (routes == NULL)
|
||||
{
|
||||
routes = kh_init(string_hashmap);
|
||||
}
|
||||
|
||||
parser_settings.on_header_field = http_request_on_header_field;
|
||||
parser_settings.on_header_value = http_request_on_header_value;
|
||||
parser_settings.on_headers_complete = http_request_on_headers_complete;
|
||||
parser_settings.on_body = http_request_on_body;
|
||||
parser_settings.on_message_begin = http_request_on_message_begin;
|
||||
parser_settings.on_message_complete = http_request_on_message_complete;
|
||||
parser_settings.on_url = http_request_on_url;
|
||||
|
||||
#ifdef UNIX
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
#endif // UNIX
|
||||
|
||||
listener_count = threads;
|
||||
|
||||
/* TODO: Use the return values from uv_tcp_init() and uv_tcp_bind() */
|
||||
uv_loop = uv_default_loop();
|
||||
|
||||
listener_async_handles = calloc(listener_count, sizeof(uv_async_t));
|
||||
listener_event_loops = calloc(listener_count, sizeof(uv_loop_t));
|
||||
|
||||
listeners_created_barrier = malloc(sizeof(uv_barrier_t));
|
||||
uv_barrier_init(listeners_created_barrier, listener_count + 1);
|
||||
|
||||
service_handle = malloc(sizeof(uv_async_t));
|
||||
uv_async_init(uv_loop, service_handle, NULL);
|
||||
|
||||
if (listener_count == 0)
|
||||
{
|
||||
/* If running single threaded there is no need to use the IPC pipe
|
||||
to distribute requests between threads so lets avoid the IPC overhead */
|
||||
|
||||
int rc;
|
||||
rc = uv_tcp_init_ex(uv_loop, &server, AF_INET);
|
||||
if (rc != 0)
|
||||
{
|
||||
dzlog_warn("TWO %d\n", rc);
|
||||
}
|
||||
|
||||
if (strcmp(config->balancer, "reuseport") == 0)
|
||||
{
|
||||
uv_os_fd_t fd;
|
||||
int on = 1;
|
||||
rc = uv_fileno(&server, &fd);
|
||||
if (rc != 0)
|
||||
{
|
||||
dzlog_warn("ONE %d\n", rc);
|
||||
}
|
||||
rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char*)&on, sizeof(on));
|
||||
if (rc != 0)
|
||||
{
|
||||
dzlog_warn("THREE %d\n", errno);
|
||||
}
|
||||
}
|
||||
|
||||
initialize_http_request_cache();
|
||||
http_request_cache_configure_listener(uv_loop, NULL);
|
||||
|
||||
uv_ip4_addr(config->http_listen_address, config->http_listen_port, &listen_address);
|
||||
uv_tcp_bind(&server, (const struct sockaddr*)&listen_address, 0);
|
||||
|
||||
if (config->tcp_nodelay) {
|
||||
uv_tcp_nodelay(&server, 1);
|
||||
}
|
||||
|
||||
uv_listen((uv_stream_t*)&server, config->listen_backlog, http_stream_on_connect);
|
||||
print_configuration();
|
||||
dzlog_debug("Listening...\n");
|
||||
//uv_run(uv_loop, UV_RUN_DEFAULT);
|
||||
}
|
||||
else if (listener_count > 0 && strcmp(config->balancer, "ipc") == 0)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
/* If we are running multi-threaded spin up the dispatcher that uses
|
||||
an IPC pipe to send socket connection requests to listening threads */
|
||||
struct server_ctx* servers;
|
||||
servers = calloc(threads, sizeof(servers[0]));
|
||||
for (i = 0; i < threads; i++)
|
||||
{
|
||||
int rc = 0;
|
||||
struct server_ctx* ctx = servers + i;
|
||||
ctx->index = i;
|
||||
ctx->listen_backlog = config->listen_backlog;
|
||||
|
||||
rc = uv_sem_init(&ctx->semaphore, 0);
|
||||
rc = uv_thread_create(&ctx->thread_id, connection_consumer_start, ctx);
|
||||
}
|
||||
|
||||
uv_barrier_wait(listeners_created_barrier);
|
||||
initialize_http_request_cache();
|
||||
|
||||
start_connection_dispatching(UV_TCP, threads, servers, config->http_listen_address, config->http_listen_port, config->tcp_nodelay, config->listen_backlog);
|
||||
}
|
||||
else if (listener_count > 0 && strcmp(config->balancer, "reuseport") == 0)
|
||||
{
|
||||
struct server_ctx* servers;
|
||||
servers = calloc(threads, sizeof(servers[0]));
|
||||
|
||||
for (int i = 0; i < threads; i++)
|
||||
{
|
||||
struct server_ctx* ctx = servers + i;
|
||||
ctx->index = i;
|
||||
int rc = uv_thread_create(&ctx->thread_id, reuseport_thread_start, ctx);
|
||||
}
|
||||
|
||||
print_configuration();
|
||||
dzlog_debug("Listening...\n");
|
||||
//uv_run(uv_loop, UV_RUN_DEFAULT);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void reuseport_thread_start(void *arg)
|
||||
{
|
||||
int rc;
|
||||
struct server_ctx* ctx;
|
||||
uv_loop_t* loop;
|
||||
uv_tcp_t svr;
|
||||
|
||||
ctx = arg;
|
||||
loop = uv_loop_new();
|
||||
listener_event_loops[ctx->index] = *loop;
|
||||
|
||||
initialize_http_request_cache();
|
||||
http_request_cache_configure_listener(loop, &listener_async_handles[ctx->index]);
|
||||
|
||||
struct sockaddr_in addr;
|
||||
uv_tcp_t server;
|
||||
|
||||
rc = uv_tcp_init_ex(loop, &server, AF_INET);
|
||||
uv_ip4_addr(config->http_listen_address, config->http_listen_port, &addr);
|
||||
|
||||
uv_os_fd_t fd;
|
||||
int on = 1;
|
||||
uv_fileno(&server, &fd);
|
||||
rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char*)&on, sizeof(on));
|
||||
if (rc != 0)
|
||||
{
|
||||
dzlog_warn("%d\n", errno);
|
||||
}
|
||||
|
||||
uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);
|
||||
int r = uv_listen((uv_stream_t*) &server, 128, http_stream_on_connect);
|
||||
|
||||
rc = uv_run(loop, UV_RUN_DEFAULT);
|
||||
uv_loop_delete(loop);
|
||||
}
|
||||
|
||||
void http_stream_on_connect(uv_stream_t* stream, int status)
|
||||
{
|
||||
http_connection* connection = create_http_connection();
|
||||
uv_tcp_init(stream->loop, &connection->stream);
|
||||
http_parser_init(&connection->parser, HTTP_REQUEST);
|
||||
|
||||
connection->parser.data = connection;
|
||||
connection->stream.data = connection;
|
||||
|
||||
/* TODO: Use the return values from uv_accept() and uv_read_start() */
|
||||
uv_accept(stream, (uv_stream_t*)&connection->stream);
|
||||
connection->state = OPEN;
|
||||
uv_read_start((uv_stream_t*)&connection->stream, http_stream_on_alloc, http_stream_on_read);
|
||||
}
|
||||
|
||||
void http_stream_on_alloc(uv_handle_t* client, size_t suggested_size, uv_buf_t* buf)
|
||||
{
|
||||
http_connection* connection = (http_connection*)client->data;
|
||||
|
||||
bool success = http_request_buffer_alloc(connection->buffer, suggested_size);
|
||||
hw_request_buffer_chunk chunk;
|
||||
chunk.size = 0;
|
||||
chunk.buffer = NULL;
|
||||
|
||||
if (success) {
|
||||
http_request_buffer_chunk(connection->buffer, &chunk);
|
||||
} else {
|
||||
/* TODO out of memory event - we should hook up an application callback to this */
|
||||
}
|
||||
|
||||
*buf = uv_buf_init(chunk.buffer, chunk.size);
|
||||
}
|
||||
|
||||
void http_stream_on_close(uv_handle_t* handle)
|
||||
{
|
||||
uv_handle_t* stream = handle;
|
||||
http_connection* connection = stream->data;
|
||||
|
||||
if (connection->state != CLOSED) {
|
||||
connection->state = CLOSED;
|
||||
http_connection* connection = (http_connection*)handle->data;
|
||||
free_http_connection(connection);
|
||||
}
|
||||
}
|
||||
|
||||
void http_stream_close_connection(http_connection* connection) {
|
||||
if (connection->state == OPEN) {
|
||||
connection->state = CLOSING;
|
||||
uv_close(&connection->stream, http_stream_on_close);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_request_error(http_connection* connection)
|
||||
{
|
||||
uv_handle_t* stream = &connection->stream;
|
||||
|
||||
if (connection->state == OPEN) {
|
||||
uv_read_stop(stream);
|
||||
}
|
||||
|
||||
connection->keep_alive = false;
|
||||
|
||||
if (connection->request) {
|
||||
if (connection->state == OPEN) {
|
||||
/* Send the error message back. */
|
||||
http_request_on_message_complete(&connection->parser);
|
||||
}
|
||||
} else {
|
||||
http_stream_close_connection(connection);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_bad_request(http_connection* connection)
|
||||
{
|
||||
if (connection->request) {
|
||||
connection->request->state = BAD_REQUEST;
|
||||
}
|
||||
|
||||
handle_request_error(connection);
|
||||
}
|
||||
|
||||
void handle_buffer_exceeded_error(http_connection* connection)
|
||||
{
|
||||
if (connection->request) {
|
||||
connection->request->state = SIZE_EXCEEDED;
|
||||
}
|
||||
|
||||
handle_request_error(connection);
|
||||
}
|
||||
|
||||
void handle_internal_error(http_connection* connection)
|
||||
{
|
||||
if (connection->request) {
|
||||
connection->request->state = INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
handle_request_error(connection);
|
||||
}
|
||||
|
||||
void http_stream_on_shutdown(uv_shutdown_t* req, int status)
|
||||
{
|
||||
http_connection* connection = req->data;
|
||||
uv_handle_t* stream = &connection->stream;
|
||||
if (connection->state == OPEN) {
|
||||
http_stream_close_connection(connection);
|
||||
}
|
||||
free(req);
|
||||
}
|
||||
|
||||
void http_stream_on_read_http_parser(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf)
|
||||
{
|
||||
http_connection* connection = (http_connection*)tcp->data;
|
||||
|
||||
if (nread > 0) {
|
||||
/* Need to tell the buffer that we care about the next nread bytes */
|
||||
http_request_buffer_consume(connection->buffer, nread);
|
||||
|
||||
http_parser_execute(&connection->parser, &parser_settings, (const char*) buf->base, nread);
|
||||
|
||||
if (connection->parser.http_errno) {
|
||||
handle_bad_request(connection);
|
||||
} else {
|
||||
/* We finished processing this chunk of data, therefore we can't get rid of any chunks that were read before
|
||||
* the current one we're reading.
|
||||
*
|
||||
* We can't get rid of the one we're currently processing as it may contain a partial request that will
|
||||
* only be complete with the next chunk coming into a subsequent call of this function. */
|
||||
http_request_buffer_sweep(connection->buffer);
|
||||
}
|
||||
} else if (nread == 0) {
|
||||
/* no-op - there's no data to be read, but there might be later */
|
||||
}
|
||||
else if (nread == UV_ENOBUFS) {
|
||||
handle_buffer_exceeded_error(connection);
|
||||
}
|
||||
else if (nread == UV_EOF){
|
||||
uv_shutdown_t* req = malloc(sizeof(uv_shutdown_t));
|
||||
req->data = connection;
|
||||
uv_shutdown(req, &connection->stream, http_stream_on_shutdown);
|
||||
}
|
||||
else if (nread == UV_ECONNRESET || nread == UV_ECONNABORTED) {
|
||||
/* Let's close the connection as the other peer just disappeared */
|
||||
http_stream_close_connection(connection);
|
||||
} else {
|
||||
/* We didn't see this coming, but an unexpected UV error code was passed in, so we'll
|
||||
* respond with a blanket 500 error if we can */
|
||||
handle_internal_error(connection);
|
||||
}
|
||||
}
|
||||
|
||||
void http_server_cleanup_write(char* response_string, hw_write_context* write_context, uv_write_t* write_req)
|
||||
{
|
||||
free(response_string);
|
||||
free(write_context);
|
||||
free(write_req);
|
||||
}
|
||||
|
||||
int http_server_write_response_single(hw_write_context* write_context, hw_string* response)
|
||||
{
|
||||
http_connection* connection = write_context->connection;
|
||||
|
||||
if (connection->state == OPEN) {
|
||||
uv_write_t *write_req = (uv_write_t *) malloc(sizeof(*write_req) + sizeof(uv_buf_t));
|
||||
uv_buf_t *resbuf = (uv_buf_t *) (write_req + 1);
|
||||
|
||||
resbuf->base = response->value;
|
||||
resbuf->len = response->length;
|
||||
|
||||
write_req->data = write_context;
|
||||
|
||||
uv_stream_t *stream = (uv_stream_t *) &write_context->connection->stream;
|
||||
|
||||
if (uv_is_writable(stream)) {
|
||||
/* Ensuring that the the response can still be written. */
|
||||
uv_write(write_req, stream, resbuf, 1, http_server_after_write);
|
||||
/* TODO: Use the return values from uv_write() */
|
||||
} else {
|
||||
/* The connection was closed, so we can write the response back, but we still need to free up things */
|
||||
http_server_cleanup_write(resbuf->base, write_context, write_req);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void http_server_after_write(uv_write_t* req, int status)
|
||||
{
|
||||
hw_write_context* write_context = (hw_write_context*)req->data;
|
||||
uv_buf_t *resbuf = (uv_buf_t *)(req+1);
|
||||
uv_handle_t* stream = (uv_handle_t*) req->handle;
|
||||
|
||||
http_connection* connection = write_context->connection;
|
||||
|
||||
if (!connection->keep_alive && connection->state == OPEN) {
|
||||
http_stream_close_connection(connection);
|
||||
}
|
||||
|
||||
if (write_context->callback)
|
||||
{
|
||||
write_context->callback(write_context->user_data);
|
||||
}
|
||||
|
||||
http_server_cleanup_write(resbuf->base, write_context, req);
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
#include "uv.h"
|
||||
#include "haywire.h"
|
||||
#include "http_connection.h"
|
||||
#include "http_parser.h"
|
||||
#include "http_response.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
http_request_callback callback;
|
||||
void* user_data;
|
||||
} hw_route_entry;
|
||||
|
||||
union stream_handle
|
||||
{
|
||||
uv_pipe_t pipe;
|
||||
uv_tcp_t tcp;
|
||||
};
|
||||
|
||||
extern void* routes;
|
||||
extern uv_loop_t* uv_loop;
|
||||
extern hw_string* http_v1_0;
|
||||
extern hw_string* http_v1_1;
|
||||
extern hw_string* server_name;
|
||||
extern int listener_count;
|
||||
extern uv_async_t* listener_async_handles;
|
||||
extern uv_loop_t* listener_event_loops;
|
||||
extern uv_barrier_t* listeners_created_barrier;
|
||||
|
||||
void (*http_stream_on_read)(uv_stream_t*, ssize_t, const uv_buf_t*);
|
||||
int (*http_server_write_response)(hw_write_context*, hw_string*);
|
||||
|
||||
http_connection* create_http_connection();
|
||||
void http_stream_on_connect(uv_stream_t* stream, int status);
|
||||
void http_stream_on_alloc(uv_handle_t* client, size_t suggested_size, uv_buf_t* buf);
|
||||
void http_stream_on_close(uv_handle_t* handle);
|
||||
int http_server_write_response_single(hw_write_context* write_context, hw_string* response);
|
||||
void http_server_after_write(uv_write_t* req, int status);
|
||||
void http_stream_on_read_http_parser(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf);
|
||||
void reuseport_thread_start(void *arg);
|
|
@ -0,0 +1,76 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "haywire.h"
|
||||
|
||||
hw_string* create_string(const char* value)
|
||||
{
|
||||
int length = (int)strlen(value);
|
||||
hw_string* str = malloc(sizeof(hw_string));
|
||||
str->value = malloc(length);
|
||||
memcpy(str->value, value, length);
|
||||
str->length = length;
|
||||
return str;
|
||||
}
|
||||
|
||||
hw_string* hw_strdup(hw_string* tocopy)
|
||||
{
|
||||
hw_string* str = malloc(sizeof(hw_string));
|
||||
str->value = tocopy->value;
|
||||
str->length = tocopy->length;
|
||||
return str;
|
||||
}
|
||||
|
||||
int hw_strcmp(hw_string* a, hw_string* b) {
|
||||
int ret;
|
||||
|
||||
if (a->length > b->length) {
|
||||
ret = strncmp(a->value, b->value, b->length);
|
||||
if (!ret) {
|
||||
ret = 1;
|
||||
}
|
||||
} else if (a->length == b->length) {
|
||||
ret = strncmp(a->value, b->value, a->length);
|
||||
} else {
|
||||
ret = strncmp(a->value, b->value, a->length);
|
||||
if (!ret) {
|
||||
ret = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void append_string(hw_string* destination, hw_string* source)
|
||||
{
|
||||
void* location = (void*) (destination->value + destination->length);
|
||||
memcpy(location, source->value, source->length);
|
||||
destination->length += source->length;
|
||||
}
|
||||
|
||||
/* Added because of http://stackoverflow.com/questions/8359966/strdup-returning-address-out-of-bounds */
|
||||
char* dupstr(const char *s)
|
||||
{
|
||||
char* result = malloc(strlen(s) + 1);
|
||||
if (result != NULL)
|
||||
{
|
||||
strcpy(result, s);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void string_from_int(hw_string* str, int val, int base)
|
||||
{
|
||||
static char buf[32] = {0};
|
||||
int i = 30;
|
||||
int length = 0;
|
||||
|
||||
for(; val && i ; --i, val /= base)
|
||||
{
|
||||
buf[i] = "0123456789abcdef"[val % base];
|
||||
length++;
|
||||
}
|
||||
|
||||
str->value = &buf[i+1];
|
||||
str->length = length;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
hw_string* create_string(const char* value);
|
||||
int hw_strcmp(hw_string* a, hw_string* b);
|
||||
hw_string* hw_strdup(hw_string* tocopy);
|
||||
void append_string(hw_string* destination, hw_string* source);
|
||||
char* dupstr(const char *s);
|
||||
void string_from_int(hw_string* str, int val, int base);
|
||||
int hw_strcmp(hw_string* a, hw_string* b);
|
|
@ -0,0 +1,617 @@
|
|||
/* The MIT License
|
||||
|
||||
Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
An example:
|
||||
|
||||
#include "khash.h"
|
||||
KHASH_MAP_INIT_INT(32, char)
|
||||
int main() {
|
||||
int ret, is_missing;
|
||||
khiter_t k;
|
||||
khash_t(32) *h = kh_init(32);
|
||||
k = kh_put(32, h, 5, &ret);
|
||||
kh_value(h, k) = 10;
|
||||
k = kh_get(32, h, 10);
|
||||
is_missing = (k == kh_end(h));
|
||||
k = kh_get(32, h, 5);
|
||||
kh_del(32, h, k);
|
||||
for (k = kh_begin(h); k != kh_end(h); ++k)
|
||||
if (kh_exist(h, k)) kh_value(h, k) = 1;
|
||||
kh_destroy(32, h);
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
2013-05-02 (0.2.8):
|
||||
|
||||
* Use quadratic probing. When the capacity is power of 2, stepping function
|
||||
i*(i+1)/2 guarantees to traverse each bucket. It is better than double
|
||||
hashing on cache performance and is more robust than linear probing.
|
||||
|
||||
In theory, double hashing should be more robust than quadratic probing.
|
||||
However, my implementation is probably not for large hash tables, because
|
||||
the second hash function is closely tied to the first hash function,
|
||||
which reduce the effectiveness of double hashing.
|
||||
|
||||
Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php
|
||||
|
||||
2011-12-29 (0.2.7):
|
||||
|
||||
* Minor code clean up; no actual effect.
|
||||
|
||||
2011-09-16 (0.2.6):
|
||||
|
||||
* The capacity is a power of 2. This seems to dramatically improve the
|
||||
speed for simple keys. Thank Zilong Tan for the suggestion. Reference:
|
||||
|
||||
- http://code.google.com/p/ulib/
|
||||
- http://nothings.org/computer/judy/
|
||||
|
||||
* Allow to optionally use linear probing which usually has better
|
||||
performance for random input. Double hashing is still the default as it
|
||||
is more robust to certain non-random input.
|
||||
|
||||
* Added Wang's integer hash function (not used by default). This hash
|
||||
function is more robust to certain non-random input.
|
||||
|
||||
2011-02-14 (0.2.5):
|
||||
|
||||
* Allow to declare global functions.
|
||||
|
||||
2009-09-26 (0.2.4):
|
||||
|
||||
* Improve portability
|
||||
|
||||
2008-09-19 (0.2.3):
|
||||
|
||||
* Corrected the example
|
||||
* Improved interfaces
|
||||
|
||||
2008-09-11 (0.2.2):
|
||||
|
||||
* Improved speed a little in kh_put()
|
||||
|
||||
2008-09-10 (0.2.1):
|
||||
|
||||
* Added kh_clear()
|
||||
* Fixed a compiling error
|
||||
|
||||
2008-09-02 (0.2.0):
|
||||
|
||||
* Changed to token concatenation which increases flexibility.
|
||||
|
||||
2008-08-31 (0.1.2):
|
||||
|
||||
* Fixed a bug in kh_get(), which has not been tested previously.
|
||||
|
||||
2008-08-31 (0.1.1):
|
||||
|
||||
* Added destructor
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __AC_KHASH_H
|
||||
#define __AC_KHASH_H
|
||||
|
||||
/*!
|
||||
@header
|
||||
|
||||
Generic hash table library.
|
||||
*/
|
||||
|
||||
#define AC_VERSION_KHASH_H "0.2.8"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
|
||||
/* compiler specific configuration */
|
||||
|
||||
#if UINT_MAX == 0xffffffffu
|
||||
typedef unsigned int khint32_t;
|
||||
#elif ULONG_MAX == 0xffffffffu
|
||||
typedef unsigned long khint32_t;
|
||||
#endif
|
||||
|
||||
#if ULONG_MAX == ULLONG_MAX
|
||||
typedef unsigned long khint64_t;
|
||||
#else
|
||||
typedef unsigned long long khint64_t;
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define kh_inline __inline
|
||||
#else
|
||||
#define kh_inline inline
|
||||
#endif
|
||||
|
||||
typedef khint32_t khint_t;
|
||||
typedef khint_t khiter_t;
|
||||
|
||||
#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)
|
||||
#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)
|
||||
#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)
|
||||
#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1)))
|
||||
#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1)))
|
||||
#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1)))
|
||||
#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1))
|
||||
|
||||
#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)
|
||||
|
||||
#ifndef kroundup32
|
||||
#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
|
||||
#endif
|
||||
|
||||
#ifndef kcalloc
|
||||
#define kcalloc(N,Z) calloc(N,Z)
|
||||
#endif
|
||||
#ifndef kmalloc
|
||||
#define kmalloc(Z) malloc(Z)
|
||||
#endif
|
||||
#ifndef krealloc
|
||||
#define krealloc(P,Z) realloc(P,Z)
|
||||
#endif
|
||||
#ifndef kfree
|
||||
#define kfree(P) free(P)
|
||||
#endif
|
||||
|
||||
static const double __ac_HASH_UPPER = 0.77;
|
||||
|
||||
#define __KHASH_TYPE(name, khkey_t, khval_t) \
|
||||
typedef struct { \
|
||||
khint_t n_buckets, size, n_occupied, upper_bound; \
|
||||
khint32_t *flags; \
|
||||
khkey_t *keys; \
|
||||
khval_t *vals; \
|
||||
} kh_##name##_t;
|
||||
|
||||
#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
|
||||
extern kh_##name##_t *kh_init_##name(void); \
|
||||
extern void kh_destroy_##name(kh_##name##_t *h); \
|
||||
extern void kh_clear_##name(kh_##name##_t *h); \
|
||||
extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \
|
||||
extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
|
||||
extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
|
||||
extern void kh_del_##name(kh_##name##_t *h, khint_t x);
|
||||
|
||||
#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
|
||||
SCOPE kh_##name##_t *kh_init_##name(void) { \
|
||||
return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \
|
||||
} \
|
||||
SCOPE void kh_destroy_##name(kh_##name##_t *h) \
|
||||
{ \
|
||||
if (h) { \
|
||||
kfree((void *)h->keys); kfree(h->flags); \
|
||||
kfree((void *)h->vals); \
|
||||
kfree(h); \
|
||||
} \
|
||||
} \
|
||||
SCOPE void kh_clear_##name(kh_##name##_t *h) \
|
||||
{ \
|
||||
if (h && h->flags) { \
|
||||
memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
|
||||
h->size = h->n_occupied = 0; \
|
||||
} \
|
||||
} \
|
||||
SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
|
||||
{ \
|
||||
if (h->n_buckets) { \
|
||||
khint_t k, i, last, mask, step = 0; \
|
||||
mask = h->n_buckets - 1; \
|
||||
k = __hash_func(key); i = k & mask; \
|
||||
last = i; \
|
||||
while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
|
||||
i = (i + (++step)) & mask; \
|
||||
if (i == last) return h->n_buckets; \
|
||||
} \
|
||||
return __ac_iseither(h->flags, i)? h->n_buckets : i; \
|
||||
} else return 0; \
|
||||
} \
|
||||
SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
|
||||
{ /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \
|
||||
khint32_t *new_flags = 0; \
|
||||
khint_t j = 1; \
|
||||
{ \
|
||||
kroundup32(new_n_buckets); \
|
||||
if (new_n_buckets < 4) new_n_buckets = 4; \
|
||||
if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \
|
||||
else { /* hash table size to be changed (shrink or expand); rehash */ \
|
||||
new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
|
||||
if (!new_flags) return -1; \
|
||||
memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
|
||||
if (h->n_buckets < new_n_buckets) { /* expand */ \
|
||||
khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
|
||||
if (!new_keys) return -1; \
|
||||
h->keys = new_keys; \
|
||||
if (kh_is_map) { \
|
||||
khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
|
||||
if (!new_vals) return -1; \
|
||||
h->vals = new_vals; \
|
||||
} \
|
||||
} /* otherwise shrink */ \
|
||||
} \
|
||||
} \
|
||||
if (j) { /* rehashing is needed */ \
|
||||
for (j = 0; j != h->n_buckets; ++j) { \
|
||||
if (__ac_iseither(h->flags, j) == 0) { \
|
||||
khkey_t key = h->keys[j]; \
|
||||
khval_t val; \
|
||||
khint_t new_mask; \
|
||||
new_mask = new_n_buckets - 1; \
|
||||
if (kh_is_map) val = h->vals[j]; \
|
||||
__ac_set_isdel_true(h->flags, j); \
|
||||
while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \
|
||||
khint_t k, i, step = 0; \
|
||||
k = __hash_func(key); \
|
||||
i = k & new_mask; \
|
||||
while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \
|
||||
__ac_set_isempty_false(new_flags, i); \
|
||||
if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \
|
||||
{ khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \
|
||||
if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \
|
||||
__ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \
|
||||
} else { /* write the element and jump out of the loop */ \
|
||||
h->keys[i] = key; \
|
||||
if (kh_is_map) h->vals[i] = val; \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
|
||||
h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
|
||||
if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
|
||||
} \
|
||||
kfree(h->flags); /* free the working space */ \
|
||||
h->flags = new_flags; \
|
||||
h->n_buckets = new_n_buckets; \
|
||||
h->n_occupied = h->size; \
|
||||
h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
|
||||
{ \
|
||||
khint_t x; \
|
||||
if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
|
||||
if (h->n_buckets > (h->size<<1)) { \
|
||||
if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \
|
||||
*ret = -1; return h->n_buckets; \
|
||||
} \
|
||||
} else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \
|
||||
*ret = -1; return h->n_buckets; \
|
||||
} \
|
||||
} /* TODO: to implement automatically shrinking; resize() already support shrinking */ \
|
||||
{ \
|
||||
khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \
|
||||
x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \
|
||||
if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \
|
||||
else { \
|
||||
last = i; \
|
||||
while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
|
||||
if (__ac_isdel(h->flags, i)) site = i; \
|
||||
i = (i + (++step)) & mask; \
|
||||
if (i == last) { x = site; break; } \
|
||||
} \
|
||||
if (x == h->n_buckets) { \
|
||||
if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \
|
||||
else x = i; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
if (__ac_isempty(h->flags, x)) { /* not present at all */ \
|
||||
h->keys[x] = key; \
|
||||
__ac_set_isboth_false(h->flags, x); \
|
||||
++h->size; ++h->n_occupied; \
|
||||
*ret = 1; \
|
||||
} else if (__ac_isdel(h->flags, x)) { /* deleted */ \
|
||||
h->keys[x] = key; \
|
||||
__ac_set_isboth_false(h->flags, x); \
|
||||
++h->size; \
|
||||
*ret = 2; \
|
||||
} else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
|
||||
return x; \
|
||||
} \
|
||||
SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
|
||||
{ \
|
||||
if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \
|
||||
__ac_set_isdel_true(h->flags, x); \
|
||||
--h->size; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define KHASH_DECLARE(name, khkey_t, khval_t) \
|
||||
__KHASH_TYPE(name, khkey_t, khval_t) \
|
||||
__KHASH_PROTOTYPES(name, khkey_t, khval_t)
|
||||
|
||||
#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
|
||||
__KHASH_TYPE(name, khkey_t, khval_t) \
|
||||
__KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
|
||||
|
||||
#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
|
||||
KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
|
||||
|
||||
/* --- BEGIN OF HASH FUNCTIONS --- */
|
||||
|
||||
/*! @function
|
||||
@abstract Integer hash function
|
||||
@param key The integer [khint32_t]
|
||||
@return The hash value [khint_t]
|
||||
*/
|
||||
#define kh_int_hash_func(key) (khint32_t)(key)
|
||||
/*! @function
|
||||
@abstract Integer comparison function
|
||||
*/
|
||||
#define kh_int_hash_equal(a, b) ((a) == (b))
|
||||
/*! @function
|
||||
@abstract 64-bit integer hash function
|
||||
@param key The integer [khint64_t]
|
||||
@return The hash value [khint_t]
|
||||
*/
|
||||
#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11)
|
||||
/*! @function
|
||||
@abstract 64-bit integer comparison function
|
||||
*/
|
||||
#define kh_int64_hash_equal(a, b) ((a) == (b))
|
||||
/*! @function
|
||||
@abstract const char* hash function
|
||||
@param s Pointer to a null terminated string
|
||||
@return The hash value
|
||||
*/
|
||||
static kh_inline khint_t __ac_X31_hash_string(const char *s)
|
||||
{
|
||||
khint_t h = (khint_t)*s;
|
||||
if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s;
|
||||
return h;
|
||||
}
|
||||
/*! @function
|
||||
@abstract Another interface to const char* hash function
|
||||
@param key Pointer to a null terminated string [const char*]
|
||||
@return The hash value [khint_t]
|
||||
*/
|
||||
#define kh_str_hash_func(key) __ac_X31_hash_string(key)
|
||||
/*! @function
|
||||
@abstract Const char* comparison function
|
||||
*/
|
||||
#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0)
|
||||
|
||||
static kh_inline khint_t __ac_Wang_hash(khint_t key)
|
||||
{
|
||||
key += ~(key << 15);
|
||||
key ^= (key >> 10);
|
||||
key += (key << 3);
|
||||
key ^= (key >> 6);
|
||||
key += ~(key << 11);
|
||||
key ^= (key >> 16);
|
||||
return key;
|
||||
}
|
||||
#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key)
|
||||
|
||||
/* --- END OF HASH FUNCTIONS --- */
|
||||
|
||||
/* Other convenient macros... */
|
||||
|
||||
/*!
|
||||
@abstract Type of the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define khash_t(name) kh_##name##_t
|
||||
|
||||
/*! @function
|
||||
@abstract Initiate a hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@return Pointer to the hash table [khash_t(name)*]
|
||||
*/
|
||||
#define kh_init(name) kh_init_##name()
|
||||
|
||||
/*! @function
|
||||
@abstract Destroy a hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
*/
|
||||
#define kh_destroy(name, h) kh_destroy_##name(h)
|
||||
|
||||
/*! @function
|
||||
@abstract Reset a hash table without deallocating memory.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
*/
|
||||
#define kh_clear(name, h) kh_clear_##name(h)
|
||||
|
||||
/*! @function
|
||||
@abstract Resize a hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param s New size [khint_t]
|
||||
*/
|
||||
#define kh_resize(name, h, s) kh_resize_##name(h, s)
|
||||
|
||||
/*! @function
|
||||
@abstract Insert a key to the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param k Key [type of keys]
|
||||
@param r Extra return code: 0 if the key is present in the hash table;
|
||||
1 if the bucket is empty (never used); 2 if the element in
|
||||
the bucket has been deleted [int*]
|
||||
@return Iterator to the inserted element [khint_t]
|
||||
*/
|
||||
#define kh_put(name, h, k, r) kh_put_##name(h, k, r)
|
||||
|
||||
/*! @function
|
||||
@abstract Retrieve a key from the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param k Key [type of keys]
|
||||
@return Iterator to the found element, or kh_end(h) if the element is absent [khint_t]
|
||||
*/
|
||||
#define kh_get(name, h, k) kh_get_##name(h, k)
|
||||
|
||||
/*! @function
|
||||
@abstract Remove a key from the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param k Iterator to the element to be deleted [khint_t]
|
||||
*/
|
||||
#define kh_del(name, h, k) kh_del_##name(h, k)
|
||||
|
||||
/*! @function
|
||||
@abstract Test whether a bucket contains data.
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param x Iterator to the bucket [khint_t]
|
||||
@return 1 if containing data; 0 otherwise [int]
|
||||
*/
|
||||
#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))
|
||||
|
||||
/*! @function
|
||||
@abstract Get key given an iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param x Iterator to the bucket [khint_t]
|
||||
@return Key [type of keys]
|
||||
*/
|
||||
#define kh_key(h, x) ((h)->keys[x])
|
||||
|
||||
/*! @function
|
||||
@abstract Get value given an iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param x Iterator to the bucket [khint_t]
|
||||
@return Value [type of values]
|
||||
@discussion For hash sets, calling this results in segfault.
|
||||
*/
|
||||
#define kh_val(h, x) ((h)->vals[x])
|
||||
|
||||
/*! @function
|
||||
@abstract Alias of kh_val()
|
||||
*/
|
||||
#define kh_value(h, x) ((h)->vals[x])
|
||||
|
||||
/*! @function
|
||||
@abstract Get the start iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return The start iterator [khint_t]
|
||||
*/
|
||||
#define kh_begin(h) (khint_t)(0)
|
||||
|
||||
/*! @function
|
||||
@abstract Get the end iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return The end iterator [khint_t]
|
||||
*/
|
||||
#define kh_end(h) ((h)->n_buckets)
|
||||
|
||||
/*! @function
|
||||
@abstract Get the number of elements in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return Number of elements in the hash table [khint_t]
|
||||
*/
|
||||
#define kh_size(h) ((h)->size)
|
||||
|
||||
/*! @function
|
||||
@abstract Get the number of buckets in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return Number of buckets in the hash table [khint_t]
|
||||
*/
|
||||
#define kh_n_buckets(h) ((h)->n_buckets)
|
||||
|
||||
/*! @function
|
||||
@abstract Iterate over the entries in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param kvar Variable to which key will be assigned
|
||||
@param vvar Variable to which value will be assigned
|
||||
@param code Block of code to execute
|
||||
*/
|
||||
#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \
|
||||
for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
|
||||
if (!kh_exist(h,__i)) continue; \
|
||||
(kvar) = kh_key(h,__i); \
|
||||
(vvar) = kh_val(h,__i); \
|
||||
code; \
|
||||
} }
|
||||
|
||||
/*! @function
|
||||
@abstract Iterate over the values in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param vvar Variable to which value will be assigned
|
||||
@param code Block of code to execute
|
||||
*/
|
||||
#define kh_foreach_value(h, vvar, code) { khint_t __i; \
|
||||
for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
|
||||
if (!kh_exist(h,__i)) continue; \
|
||||
(vvar) = kh_val(h,__i); \
|
||||
code; \
|
||||
} }
|
||||
|
||||
/* More conenient interfaces */
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash set containing integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define KHASH_SET_INIT_INT(name) \
|
||||
KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
@param khval_t Type of values [type]
|
||||
*/
|
||||
#define KHASH_MAP_INIT_INT(name, khval_t) \
|
||||
KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing 64-bit integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define KHASH_SET_INIT_INT64(name) \
|
||||
KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing 64-bit integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
@param khval_t Type of values [type]
|
||||
*/
|
||||
#define KHASH_MAP_INIT_INT64(name, khval_t) \
|
||||
KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)
|
||||
|
||||
typedef const char *kh_cstr_t;
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing const char* keys
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define KHASH_SET_INIT_STR(name) \
|
||||
KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing const char* keys
|
||||
@param name Name of the hash table [symbol]
|
||||
@param khval_t Type of values [type]
|
||||
*/
|
||||
#define KHASH_MAP_INIT_STR(name, khval_t) \
|
||||
KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
|
||||
|
||||
#endif /* __AC_KHASH_H */
|
||||
|
|
@ -0,0 +1,595 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
|
||||
* Shigeo Mitsunari
|
||||
*
|
||||
* The software is licensed under either the MIT License (below) or the Perl
|
||||
* license.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#ifdef __SSE4_2__
|
||||
#ifdef _MSC_VER
|
||||
#include <nmmintrin.h>
|
||||
#else
|
||||
#include <x86intrin.h>
|
||||
#endif
|
||||
#endif
|
||||
#include "picohttpparser.h"
|
||||
|
||||
/* $Id$ */
|
||||
|
||||
#if __GNUC__ >= 3
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
#else
|
||||
#define likely(x) (x)
|
||||
#define unlikely(x) (x)
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define ALIGNED(n) _declspec(align(n))
|
||||
#else
|
||||
#define ALIGNED(n) __attribute__((aligned(n)))
|
||||
#endif
|
||||
|
||||
#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u)
|
||||
|
||||
#define CHECK_EOF() \
|
||||
if (buf == buf_end) { \
|
||||
*ret = -2; \
|
||||
return NULL; \
|
||||
}
|
||||
|
||||
#define EXPECT_CHAR(ch) \
|
||||
CHECK_EOF(); \
|
||||
if (*buf++ != ch) { \
|
||||
*ret = -1; \
|
||||
return NULL; \
|
||||
}
|
||||
|
||||
#define ADVANCE_TOKEN(tok, toklen) \
|
||||
do { \
|
||||
const char *tok_start = buf; \
|
||||
static const char ALIGNED(16) ranges2[] = "\000\040\177\177"; \
|
||||
int found2; \
|
||||
buf = findchar_fast(buf, buf_end, ranges2, sizeof(ranges2) - 1, &found2); \
|
||||
if (!found2) { \
|
||||
CHECK_EOF(); \
|
||||
} \
|
||||
while (1) { \
|
||||
if (*buf == ' ') { \
|
||||
break; \
|
||||
} else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \
|
||||
if ((unsigned char)*buf < '\040' || *buf == '\177') { \
|
||||
*ret = -1; \
|
||||
return NULL; \
|
||||
} \
|
||||
} \
|
||||
++buf; \
|
||||
CHECK_EOF(); \
|
||||
} \
|
||||
tok = tok_start; \
|
||||
toklen = buf - tok_start; \
|
||||
} while (0)
|
||||
|
||||
static const char *token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\1\1\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0"
|
||||
"\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1"
|
||||
"\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
|
||||
|
||||
static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found)
|
||||
{
|
||||
*found = 0;
|
||||
#if __SSE4_2__
|
||||
if (likely(buf_end - buf >= 16)) {
|
||||
__m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges);
|
||||
|
||||
size_t left = (buf_end - buf) & ~15;
|
||||
do {
|
||||
__m128i b16 = _mm_loadu_si128((void *)buf);
|
||||
int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS);
|
||||
if (unlikely(r != 16)) {
|
||||
buf += r;
|
||||
*found = 1;
|
||||
break;
|
||||
}
|
||||
buf += 16;
|
||||
left -= 16;
|
||||
} while (likely(left != 0));
|
||||
}
|
||||
#endif
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret)
|
||||
{
|
||||
const char *token_start = buf;
|
||||
|
||||
#ifdef __SSE4_2__
|
||||
static const char ranges1[] = "\0\010"
|
||||
/* allow HT */
|
||||
"\012\037"
|
||||
/* allow SP and up to but not including DEL */
|
||||
"\177\177"
|
||||
/* allow chars w. MSB set */
|
||||
;
|
||||
int found;
|
||||
buf = findchar_fast(buf, buf_end, ranges1, sizeof(ranges1) - 1, &found);
|
||||
if (found)
|
||||
goto FOUND_CTL;
|
||||
#else
|
||||
/* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */
|
||||
while (likely(buf_end - buf >= 8)) {
|
||||
#define DOIT() \
|
||||
do { \
|
||||
if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \
|
||||
goto NonPrintable; \
|
||||
++buf; \
|
||||
} while (0)
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
#undef DOIT
|
||||
continue;
|
||||
NonPrintable:
|
||||
if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
|
||||
goto FOUND_CTL;
|
||||
}
|
||||
++buf;
|
||||
}
|
||||
#endif
|
||||
for (;; ++buf) {
|
||||
CHECK_EOF();
|
||||
if (unlikely(!IS_PRINTABLE_ASCII(*buf))) {
|
||||
if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
|
||||
goto FOUND_CTL;
|
||||
}
|
||||
}
|
||||
}
|
||||
FOUND_CTL:
|
||||
if (likely(*buf == '\015')) {
|
||||
++buf;
|
||||
EXPECT_CHAR('\012');
|
||||
*token_len = buf - 2 - token_start;
|
||||
} else if (*buf == '\012') {
|
||||
*token_len = buf - token_start;
|
||||
++buf;
|
||||
} else {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
*token = token_start;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret)
|
||||
{
|
||||
int ret_cnt = 0;
|
||||
buf = last_len < 3 ? buf : buf + last_len - 3;
|
||||
|
||||
while (1) {
|
||||
CHECK_EOF();
|
||||
if (*buf == '\015') {
|
||||
++buf;
|
||||
CHECK_EOF();
|
||||
EXPECT_CHAR('\012');
|
||||
++ret_cnt;
|
||||
} else if (*buf == '\012') {
|
||||
++buf;
|
||||
++ret_cnt;
|
||||
} else {
|
||||
++buf;
|
||||
ret_cnt = 0;
|
||||
}
|
||||
if (ret_cnt == 2) {
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
*ret = -2;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* *_buf is always within [buf, buf_end) upon success */
|
||||
static const char *parse_int(const char *buf, const char *buf_end, int *value, int *ret)
|
||||
{
|
||||
int v;
|
||||
CHECK_EOF();
|
||||
if (!('0' <= *buf && *buf <= '9')) {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
v = 0;
|
||||
for (;; ++buf) {
|
||||
CHECK_EOF();
|
||||
if ('0' <= *buf && *buf <= '9') {
|
||||
v = v * 10 + *buf - '0';
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*value = v;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* returned pointer is always within [buf, buf_end), or null */
|
||||
static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret)
|
||||
{
|
||||
EXPECT_CHAR('H');
|
||||
EXPECT_CHAR('T');
|
||||
EXPECT_CHAR('T');
|
||||
EXPECT_CHAR('P');
|
||||
EXPECT_CHAR('/');
|
||||
EXPECT_CHAR('1');
|
||||
EXPECT_CHAR('.');
|
||||
return parse_int(buf, buf_end, minor_version, ret);
|
||||
}
|
||||
|
||||
static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers,
|
||||
size_t max_headers, int *ret)
|
||||
{
|
||||
for (;; ++*num_headers) {
|
||||
CHECK_EOF();
|
||||
if (*buf == '\015') {
|
||||
++buf;
|
||||
EXPECT_CHAR('\012');
|
||||
break;
|
||||
} else if (*buf == '\012') {
|
||||
++buf;
|
||||
break;
|
||||
}
|
||||
if (*num_headers == max_headers) {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) {
|
||||
if (!token_char_map[(unsigned char)*buf]) {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
/* parsing name, but do not discard SP before colon, see
|
||||
* http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */
|
||||
headers[*num_headers].name = buf;
|
||||
static const char ALIGNED(16) ranges1[] = "::\x00\037";
|
||||
int found;
|
||||
buf = findchar_fast(buf, buf_end, ranges1, sizeof(ranges1) - 1, &found);
|
||||
if (!found) {
|
||||
CHECK_EOF();
|
||||
}
|
||||
while (1) {
|
||||
if (*buf == ':') {
|
||||
break;
|
||||
} else if (*buf < ' ') {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
++buf;
|
||||
CHECK_EOF();
|
||||
}
|
||||
headers[*num_headers].name_len = buf - headers[*num_headers].name;
|
||||
++buf;
|
||||
for (;; ++buf) {
|
||||
CHECK_EOF();
|
||||
if (!(*buf == ' ' || *buf == '\t')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
headers[*num_headers].name = NULL;
|
||||
headers[*num_headers].name_len = 0;
|
||||
}
|
||||
if ((buf = get_token_to_eol(buf, buf_end, &headers[*num_headers].value, &headers[*num_headers].value_len, ret)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path,
|
||||
size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers,
|
||||
size_t max_headers, int *ret)
|
||||
{
|
||||
/* skip first empty line (some clients add CRLF after POST content) */
|
||||
CHECK_EOF();
|
||||
if (*buf == '\015') {
|
||||
++buf;
|
||||
EXPECT_CHAR('\012');
|
||||
} else if (*buf == '\012') {
|
||||
++buf;
|
||||
}
|
||||
|
||||
/* parse request line */
|
||||
ADVANCE_TOKEN(*method, *method_len);
|
||||
++buf;
|
||||
ADVANCE_TOKEN(*path, *path_len);
|
||||
++buf;
|
||||
if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (*buf == '\015') {
|
||||
++buf;
|
||||
EXPECT_CHAR('\012');
|
||||
} else if (*buf == '\012') {
|
||||
++buf;
|
||||
} else {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
|
||||
}
|
||||
|
||||
int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path,
|
||||
size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len)
|
||||
{
|
||||
const char *buf = buf_start, *buf_end = buf_start + len;
|
||||
size_t max_headers = *num_headers;
|
||||
int r;
|
||||
|
||||
*method = NULL;
|
||||
*method_len = 0;
|
||||
*path = NULL;
|
||||
*path_len = 0;
|
||||
*minor_version = -1;
|
||||
*num_headers = 0;
|
||||
|
||||
/* if last_len != 0, check if the request is complete (a fast countermeasure
|
||||
againt slowloris */
|
||||
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers,
|
||||
&r)) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return (int)(buf - buf_start);
|
||||
}
|
||||
|
||||
static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg,
|
||||
size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret)
|
||||
{
|
||||
/* parse "HTTP/1.x" */
|
||||
if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
/* skip space */
|
||||
if (*buf++ != ' ') {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
/* parse status code */
|
||||
if ((buf = parse_int(buf, buf_end, status, ret)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
/* skip space */
|
||||
if (*buf++ != ' ') {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
/* get message */
|
||||
if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
|
||||
}
|
||||
|
||||
int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
|
||||
struct phr_header *headers, size_t *num_headers, size_t last_len)
|
||||
{
|
||||
const char *buf = buf_start, *buf_end = buf + len;
|
||||
size_t max_headers = *num_headers;
|
||||
int r;
|
||||
|
||||
*minor_version = -1;
|
||||
*status = 0;
|
||||
*msg = NULL;
|
||||
*msg_len = 0;
|
||||
*num_headers = 0;
|
||||
|
||||
/* if last_len != 0, check if the response is complete (a fast countermeasure
|
||||
against slowloris */
|
||||
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return (int)(buf - buf_start);
|
||||
}
|
||||
|
||||
int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len)
|
||||
{
|
||||
const char *buf = buf_start, *buf_end = buf + len;
|
||||
size_t max_headers = *num_headers;
|
||||
int r;
|
||||
|
||||
*num_headers = 0;
|
||||
|
||||
/* if last_len != 0, check if the response is complete (a fast countermeasure
|
||||
against slowloris */
|
||||
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return (int)(buf - buf_start);
|
||||
}
|
||||
|
||||
enum {
|
||||
CHUNKED_IN_CHUNK_SIZE,
|
||||
CHUNKED_IN_CHUNK_EXT,
|
||||
CHUNKED_IN_CHUNK_DATA,
|
||||
CHUNKED_IN_CHUNK_CRLF,
|
||||
CHUNKED_IN_TRAILERS_LINE_HEAD,
|
||||
CHUNKED_IN_TRAILERS_LINE_MIDDLE
|
||||
};
|
||||
|
||||
static int decode_hex(int ch)
|
||||
{
|
||||
if ('0' <= ch && ch <= '9') {
|
||||
return ch - '0';
|
||||
} else if ('A' <= ch && ch <= 'F') {
|
||||
return ch - 'A' + 0xa;
|
||||
} else if ('a' <= ch && ch <= 'f') {
|
||||
return ch - 'a' + 0xa;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz)
|
||||
{
|
||||
size_t dst = 0, src = 0, bufsz = *_bufsz;
|
||||
ssize_t ret = -2; /* incomplete */
|
||||
|
||||
while (1) {
|
||||
switch (decoder->_state) {
|
||||
case CHUNKED_IN_CHUNK_SIZE:
|
||||
for (;; ++src) {
|
||||
int v;
|
||||
if (src == bufsz)
|
||||
goto Exit;
|
||||
if ((v = decode_hex(buf[src])) == -1) {
|
||||
if (decoder->_hex_count == 0) {
|
||||
ret = -1;
|
||||
goto Exit;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (decoder->_hex_count == sizeof(size_t) * 2) {
|
||||
ret = -1;
|
||||
goto Exit;
|
||||
}
|
||||
decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v;
|
||||
++decoder->_hex_count;
|
||||
}
|
||||
decoder->_hex_count = 0;
|
||||
decoder->_state = CHUNKED_IN_CHUNK_EXT;
|
||||
/* fallthru */
|
||||
case CHUNKED_IN_CHUNK_EXT:
|
||||
/* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */
|
||||
for (;; ++src) {
|
||||
if (src == bufsz)
|
||||
goto Exit;
|
||||
if (buf[src] == '\012')
|
||||
break;
|
||||
}
|
||||
++src;
|
||||
if (decoder->bytes_left_in_chunk == 0) {
|
||||
if (decoder->consume_trailer) {
|
||||
decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
|
||||
break;
|
||||
} else {
|
||||
goto Complete;
|
||||
}
|
||||
}
|
||||
decoder->_state = CHUNKED_IN_CHUNK_DATA;
|
||||
/* fallthru */
|
||||
case CHUNKED_IN_CHUNK_DATA: {
|
||||
size_t avail = bufsz - src;
|
||||
if (avail < decoder->bytes_left_in_chunk) {
|
||||
if (dst != src)
|
||||
memmove(buf + dst, buf + src, avail);
|
||||
src += avail;
|
||||
dst += avail;
|
||||
decoder->bytes_left_in_chunk -= avail;
|
||||
goto Exit;
|
||||
}
|
||||
if (dst != src)
|
||||
memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk);
|
||||
src += decoder->bytes_left_in_chunk;
|
||||
dst += decoder->bytes_left_in_chunk;
|
||||
decoder->bytes_left_in_chunk = 0;
|
||||
decoder->_state = CHUNKED_IN_CHUNK_CRLF;
|
||||
}
|
||||
/* fallthru */
|
||||
case CHUNKED_IN_CHUNK_CRLF:
|
||||
for (;; ++src) {
|
||||
if (src == bufsz)
|
||||
goto Exit;
|
||||
if (buf[src] != '\015')
|
||||
break;
|
||||
}
|
||||
if (buf[src] != '\012') {
|
||||
ret = -1;
|
||||
goto Exit;
|
||||
}
|
||||
++src;
|
||||
decoder->_state = CHUNKED_IN_CHUNK_SIZE;
|
||||
break;
|
||||
case CHUNKED_IN_TRAILERS_LINE_HEAD:
|
||||
for (;; ++src) {
|
||||
if (src == bufsz)
|
||||
goto Exit;
|
||||
if (buf[src] != '\015')
|
||||
break;
|
||||
}
|
||||
if (buf[src++] == '\012')
|
||||
goto Complete;
|
||||
decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE;
|
||||
/* fallthru */
|
||||
case CHUNKED_IN_TRAILERS_LINE_MIDDLE:
|
||||
for (;; ++src) {
|
||||
if (src == bufsz)
|
||||
goto Exit;
|
||||
if (buf[src] == '\012')
|
||||
break;
|
||||
}
|
||||
++src;
|
||||
decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
|
||||
break;
|
||||
default:
|
||||
assert(!"decoder is corrupt");
|
||||
}
|
||||
}
|
||||
|
||||
Complete:
|
||||
ret = bufsz - src;
|
||||
Exit:
|
||||
if (dst != src)
|
||||
memmove(buf + dst, buf + src, bufsz - src);
|
||||
*_bufsz = dst;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#undef CHECK_EOF
|
||||
#undef EXPECT_CHAR
|
||||
#undef ADVANCE_TOKEN
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
|
||||
* Shigeo Mitsunari
|
||||
*
|
||||
* The software is licensed under either the MIT License (below) or the Perl
|
||||
* license.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef picohttpparser_h
|
||||
#define picohttpparser_h
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define ssize_t intptr_t
|
||||
#endif
|
||||
|
||||
/* $Id$ */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* contains name and value of a header (name == NULL if is a continuing line
|
||||
* of a multiline header */
|
||||
struct phr_header {
|
||||
const char *name;
|
||||
size_t name_len;
|
||||
const char *value;
|
||||
size_t value_len;
|
||||
};
|
||||
|
||||
/* returns number of bytes consumed if successful, -2 if request is partial,
|
||||
* -1 if failed */
|
||||
int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len,
|
||||
int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||
|
||||
/* ditto */
|
||||
int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
|
||||
struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||
|
||||
/* ditto */
|
||||
int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||
|
||||
/* should be zero-filled before start */
|
||||
struct phr_chunked_decoder {
|
||||
size_t bytes_left_in_chunk; /* number of bytes left in current chunk */
|
||||
char consume_trailer; /* if trailing headers should be consumed */
|
||||
char _hex_count;
|
||||
char _state;
|
||||
};
|
||||
|
||||
/* the function rewrites the buffer given as (buf, bufsz) removing the chunked-
|
||||
* encoding headers. When the function returns without an error, bufsz is
|
||||
* updated to the length of the decoded data available. Applications should
|
||||
* repeatedly call the function while it returns -2 (incomplete) every time
|
||||
* supplying newly arrived data. If the end of the chunked-encoded data is
|
||||
* found, the function returns a non-negative number indicating the number of
|
||||
* octets left undecoded at the tail of the supplied buffer. Returns -1 on
|
||||
* error.
|
||||
*/
|
||||
ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,91 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <haywire.h>
|
||||
#include "hw_string.h"
|
||||
#include "route_compare_method.h"
|
||||
|
||||
typedef struct hw_route_token_st {
|
||||
hw_string string;
|
||||
int start;
|
||||
} hw_route_token;
|
||||
|
||||
void hw_route_next_token(hw_string* url, int start, hw_route_token* result) {
|
||||
while (start < url->length && (url->value[start] == '/')) {
|
||||
start++;
|
||||
}
|
||||
|
||||
int end = start;
|
||||
|
||||
while (end < url->length && url->value[end] != '/') {
|
||||
end++;
|
||||
}
|
||||
|
||||
if (end != start) {
|
||||
result->string.value = url->value + start;
|
||||
result->string.length = end - start;
|
||||
result->start = start;
|
||||
}
|
||||
else {
|
||||
result->string.value = NULL;
|
||||
result->string.length = 0;
|
||||
result->start = -1;
|
||||
}
|
||||
}
|
||||
|
||||
int hw_route_compare_method(hw_string* url, char* route)
|
||||
{
|
||||
int equal = 0;
|
||||
int match = 0;
|
||||
|
||||
// TODO route should probably be a hw_string* too
|
||||
hw_string hw_route;
|
||||
hw_route.value = route;
|
||||
hw_route.length = strlen(route);
|
||||
|
||||
hw_route_token route_token;
|
||||
hw_route_token request_token;
|
||||
|
||||
hw_route_next_token(url, 0, &request_token);
|
||||
hw_route_next_token(&hw_route, 0, &route_token);
|
||||
|
||||
while (route_token.string.value != NULL && request_token.string.value != NULL)
|
||||
{
|
||||
if (route_token.string.value[0] == '*') {
|
||||
// wildcard support: any route fragment marked with '*' matches the corresponding url fragment
|
||||
equal = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
match = hw_strcmp(&route_token.string, &request_token.string);
|
||||
if (!match)
|
||||
{
|
||||
equal = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
equal = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
hw_route_next_token(url, request_token.start + request_token.string.length + 1, &request_token);
|
||||
hw_route_next_token(&hw_route, route_token.start + route_token.string.length + 1, &route_token);
|
||||
}
|
||||
|
||||
if (!equal)
|
||||
{
|
||||
match = hw_strcmp(url, &hw_route);
|
||||
if (!match)
|
||||
{
|
||||
equal = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ((route_token.string.value == NULL && request_token.string.value != NULL) || (route_token.string.value != NULL && request_token.string.value == NULL))
|
||||
{
|
||||
equal = 0;
|
||||
}
|
||||
|
||||
return equal;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
#include "haywire.h"
|
||||
|
||||
int hw_route_compare_method(hw_string* url, char* route);
|
|
@ -0,0 +1,61 @@
|
|||
#include <stdio.h>
|
||||
#include "server_stats.h"
|
||||
#include "haywire.h"
|
||||
|
||||
int stat_connections_created_total;
|
||||
int stat_connections_destroyed_total;
|
||||
int stat_requests_created_total;
|
||||
int stat_requests_destroyed_total;
|
||||
|
||||
#define CRLF "\r\n"
|
||||
|
||||
static const char stats_response[] =
|
||||
"HTTP/1.1 200 OK" CRLF
|
||||
"Server: Haywire/master" CRLF
|
||||
"Date: Fri, 26 Aug 2011 00:31:53 GMT" CRLF
|
||||
"Connection: Keep-Alive" CRLF
|
||||
"Content-Type: text/html" CRLF
|
||||
"Content-Length: 16" CRLF
|
||||
CRLF
|
||||
"stats printed" CRLF;
|
||||
|
||||
void get_server_stats(http_request* request, hw_http_response* response, void* user_data)
|
||||
{
|
||||
hw_string status_code;
|
||||
hw_string content_type_name;
|
||||
hw_string content_type_value;
|
||||
hw_string body;
|
||||
hw_string keep_alive_name;
|
||||
hw_string keep_alive_value;
|
||||
|
||||
SETSTRING(status_code, HTTP_STATUS_200);
|
||||
hw_set_response_status_code(response, &status_code);
|
||||
|
||||
SETSTRING(content_type_name, "Content-Type");
|
||||
|
||||
SETSTRING(content_type_value, "text/html");
|
||||
hw_set_response_header(response, &content_type_name, &content_type_value);
|
||||
|
||||
SETSTRING(body, "stats printed");
|
||||
hw_set_body(response, &body);
|
||||
|
||||
if (request->keep_alive)
|
||||
{
|
||||
SETSTRING(keep_alive_name, "Connection");
|
||||
|
||||
SETSTRING(keep_alive_value, "Keep-Alive");
|
||||
hw_set_response_header(response, &keep_alive_name, &keep_alive_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
hw_set_http_version(response, 1, 0);
|
||||
}
|
||||
|
||||
hw_http_response_send(response, NULL, NULL);
|
||||
|
||||
printf("connections_created_total: %d\nconnections_destroyed_total: %d\nrequests_created_total: %d\nrequests_destroyed_total: %d\n\n",
|
||||
stat_connections_created_total,
|
||||
stat_connections_destroyed_total,
|
||||
stat_requests_created_total,
|
||||
stat_requests_destroyed_total);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
#include "haywire.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
#define INCREMENT_STAT(stat) stat++
|
||||
#else
|
||||
#define INCREMENT_STAT(stat)
|
||||
#endif /* DEBUG */
|
||||
|
||||
|
||||
extern int stat_connections_created_total;
|
||||
extern int stat_connections_destroyed_total;
|
||||
extern int stat_requests_created_total;
|
||||
extern int stat_requests_destroyed_total;
|
||||
|
||||
void get_server_stats(http_request* request, hw_http_response* response, void* user_data);
|
|
@ -4,7 +4,10 @@ PROJECT(${LIB_PROJECT_TARGET})
|
|||
|
||||
include(ExternalProject)
|
||||
|
||||
INCLUDE_DIRECTORIES(include ./ ./include ../lwip/src/include ../lwip/src/arch_linux/include ../include ${COMMON_INCLUDE})
|
||||
INCLUDE_DIRECTORIES(include
|
||||
./ ./include ../lwip/src/include ../lwip/src/arch_linux/include ../include
|
||||
../httpserver/include ../httpserver/src/haywire ../httpserver/src/haywire/configuration
|
||||
${COMMON_INCLUDE})
|
||||
FILE(GLOB C_HEADS include/*.h include/uthash/*.h include/s2j/*.h)
|
||||
|
||||
AUX_SOURCE_DIRECTORY(json C_SRC)
|
||||
|
@ -27,4 +30,7 @@ SET(CMAKE_C_STANDARD 99)
|
|||
ADD_DEFINITIONS(-DBUILD_VERSION="${GIT_VERSION}")
|
||||
|
||||
LINK_LIBRARIES(${COMMON_LIBS})
|
||||
ADD_LIBRARY(${LIB_PROJECT_TARGET} ${C_SRC} ${C_HEADS})
|
||||
ADD_LIBRARY(${LIB_PROJECT_TARGET} ${C_SRC} ${C_HEADS})
|
||||
|
||||
TARGET_LINK_LIBRARIES(${LIB_PROJECT_TARGET} haywire)
|
||||
TARGET_LINK_LIBRARIES(${LIB_PROJECT_TARGET} opendhcpd)
|
|
@ -0,0 +1,15 @@
|
|||
//
|
||||
// Created by xajhuang on 2022/11/4.
|
||||
//
|
||||
|
||||
#ifndef VCPE_PROJECT_HTTP_SVR_H
|
||||
#define VCPE_PROJECT_HTTP_SVR_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
int http_svr_init();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif//VCPE_PROJECT_HTTP_SVR_H
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// Created by xajhuang on 2022/11/4.
|
||||
//
|
||||
#include "http_svr.h"
|
||||
#include "user_errno.h"
|
||||
#include "haywire.h"
|
||||
#include "misc.h"
|
||||
|
||||
int http_svr_init() {
|
||||
configuration config;
|
||||
config.http_listen_address = "0.0.0.0";
|
||||
config.http_listen_port = 8000;
|
||||
config.thread_count = 0;
|
||||
config.parser = "http_parser";
|
||||
config.balancer = "ipc";
|
||||
config.tcp_nodelay = TRUE;
|
||||
config.max_request_size = 1048576;
|
||||
|
||||
hw_init_with_config(&config);
|
||||
|
||||
hw_http_open();
|
||||
return ERR_SUCCESS;
|
||||
}
|
Loading…
Reference in New Issue