First version of the refactor
This commit is contained in:
-13
@@ -1,13 +0,0 @@
|
||||
coverage_report
|
||||
|
||||
# Coverage stuff
|
||||
*.info
|
||||
*.gcda
|
||||
*.gcno
|
||||
|
||||
*.out
|
||||
*.exe
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.pem
|
||||
@@ -1,76 +1,12 @@
|
||||
.PHONY: all clean example
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -I. -Wall -Wextra -O0 -g3
|
||||
LFLAGS =
|
||||
AR = ar
|
||||
all: chttp.c chttp.h example
|
||||
|
||||
CFILES = $(shell find src -name "*.c")
|
||||
HFILES = $(shell find src -name "*.h")
|
||||
OFILES = $(patsubst %.c,%.o,$(CFILES))
|
||||
|
||||
# Library names
|
||||
LIBNAME = chttp
|
||||
STATIC_LIB = lib$(LIBNAME).a
|
||||
|
||||
# Detect OS and set executable extension
|
||||
ifeq ($(OS),Windows_NT)
|
||||
EXT = .exe
|
||||
LFLAGS = -lws2_32
|
||||
else
|
||||
EXT = .out
|
||||
LFLAGS += -lssl -lcrypto
|
||||
endif
|
||||
|
||||
HTTPS ?= 0
|
||||
ifneq ($(HTTPS),0)
|
||||
CFLAGS += -DHTTPS_ENABLED
|
||||
LFLAGS += -lssl -lcrypto
|
||||
endif
|
||||
|
||||
# Installation directories
|
||||
PREFIX ?= /usr/local
|
||||
LIBDIR = $(PREFIX)/lib
|
||||
INCDIR = $(PREFIX)/include
|
||||
|
||||
EXAMPLES_CLIENT_SRC := $(shell ls examples/client/*.c 2>/dev/null)
|
||||
EXAMPLES_SERVER_SRC := $(shell ls examples/server/*.c 2>/dev/null)
|
||||
EXAMPLES_ENGINE_SRC := $(shell ls examples/engine/*.c 2>/dev/null)
|
||||
|
||||
EXAMPLES_CLIENT := $(patsubst %.c,%$(EXT),$(EXAMPLES_CLIENT_SRC))
|
||||
EXAMPLES_SERVER := $(patsubst %.c,%$(EXT),$(EXAMPLES_SERVER_SRC))
|
||||
EXAMPLES_ENGINE := $(patsubst %.c,%$(EXT),$(EXAMPLES_ENGINE_SRC))
|
||||
|
||||
all: chttp.c chttp.h examples lib
|
||||
|
||||
lib: $(STATIC_LIB)
|
||||
|
||||
chttp.c chttp.h: $(HFILES) $(CFILES)
|
||||
chttp.c chttp.h: $(wildcard src/*.c src/*.h) misc/amalg.py Makefile
|
||||
python misc/amalg.py
|
||||
|
||||
# Object files from source files
|
||||
%.o: %.c $(HFILES)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
# Static library
|
||||
$(STATIC_LIB): $(OFILES)
|
||||
$(AR) rcs $@ $^
|
||||
|
||||
examples: $(EXAMPLES_CLIENT) $(EXAMPLES_SERVER) $(EXAMPLES_ENGINE)
|
||||
|
||||
examples/client/%$(EXT): examples/client/%.c chttp.c chttp.h
|
||||
$(CC) $(CFLAGS) $< chttp.c -o $@ $(LFLAGS)
|
||||
|
||||
examples/server/%$(EXT): examples/server/%.c chttp.c chttp.h
|
||||
$(CC) $(CFLAGS) $< chttp.c -o $@ $(LFLAGS)
|
||||
|
||||
examples/engine/%$(EXT): examples/engine/%.c chttp.c chttp.h
|
||||
$(CC) $(CFLAGS) $< chttp.c -o $@ $(LFLAGS)
|
||||
example: main.c chttp.c chttp.h
|
||||
gcc main.c chttp.c -o example.exe
|
||||
|
||||
clean:
|
||||
rm -f client_example server_example
|
||||
rm -f examples/client/*$(EXT) examples/server/*$(EXT) examples/engine/*$(EXT)
|
||||
rm -f $(OFILES)
|
||||
rm -f $(STATIC_LIB)
|
||||
rm -f chttp.c chttp.h
|
||||
|
||||
.PHONY: all lib examples install install-lib install-headers uninstall clean
|
||||
rm chttp.c chttp.h
|
||||
|
||||
@@ -1,114 +1,12 @@
|
||||
# cHTTP
|
||||
cHTTP is an HTTP **client and server** library for C with minimal dependencies and **distributed as a single chttp.c** file.
|
||||
This is an HTTP library for C, featuring an HTTP(S) server, HTTP(S) client, and much more!
|
||||
|
||||
Here is a list of features:
|
||||
## Contributing
|
||||
|
||||
* HTTP 1.1 client and server
|
||||
* Non-blocking
|
||||
* Cross-Platform
|
||||
* Minimal dependencies
|
||||
* HTTPS support
|
||||
* Virtual Hosts
|
||||
* Single-threaded
|
||||
* Ergonomic API
|
||||
Contributions are welcome! The following are some notes on how to work with the codebase. Don't worry if you get something wrong. I will remind you.
|
||||
|
||||
Note: This library is beta-quality software as some essential features are still being implemented.
|
||||
|
||||
## Getting Started
|
||||
|
||||
The library is distributed as a single amalgamated `chttp.c` file or as a static library, so you can:
|
||||
|
||||
1) Download `chttp.c` and `chttp.h` from the repository (no need to clone the project) and include them in your soource tree
|
||||
|
||||
2) or clone the project and build the static library by running:
|
||||
|
||||
```
|
||||
make libchttp.a
|
||||
```
|
||||
|
||||
If you used the amalgamated files, these are the flags required to build a project:
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
gcc your_app.c chttp.c
|
||||
|
||||
# Windows
|
||||
gcc your_app.c chttp.c -lws2_32
|
||||
```
|
||||
|
||||
If you are using the static library, instead of adding `chttp.c`, you will need to add the `-lchttp` flag.
|
||||
|
||||
By default, the library is built without HTTPS. To enable it, add the flags `-DHTTPS_ENABLED -lssl -lcrypto`.
|
||||
|
||||
```bash
|
||||
# Linux + HTTPS
|
||||
gcc your_app.c chttp.c -DHTTPS_ENABLED -lssl -lcrypto
|
||||
|
||||
# Windows + HTTPS
|
||||
gcc your_app.c chttp.c -lws2_32 -DHTTPS_ENABLED -lssl -lcrypto
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
To give you a feel of the library, here are some examples of programs using cHTTP. To learn more, you can look at the files in `examples/` (they are intended to be skimmed in order).
|
||||
|
||||
Here is a client performing a GET request:
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <chttp.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_init();
|
||||
|
||||
HTTP_String url = HTTP_STR("http://example.com/index.html");
|
||||
|
||||
HTTP_String headers[] = {
|
||||
HTTP_STR("User-Agent: cHTTP"),
|
||||
};
|
||||
|
||||
HTTP_Response *res = http_get(url, headers, 1);
|
||||
|
||||
fwrite(res->body.ptr, 1, res->body.len, stdout);
|
||||
|
||||
http_response_free(res);
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
And this is a server:
|
||||
```c
|
||||
#include <chttp.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_init();
|
||||
HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080);
|
||||
|
||||
for (;;) {
|
||||
|
||||
HTTP_Request *req;
|
||||
HTTP_ResponseBuilder builder;
|
||||
http_server_wait(server, &req, &builder);
|
||||
|
||||
http_response_builder_status(builder, 200);
|
||||
http_response_builder_header(builder, "Content-Type: text/plain");
|
||||
http_response_builder_body(builder, HTTP_STR("Hello, world!"));
|
||||
http_response_builder_done(builder);
|
||||
}
|
||||
|
||||
http_server_free(server);
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Platform Support
|
||||
cHTTP officially supports Linux and Windows.
|
||||
|
||||
## HTTPS support
|
||||
Currently, HTTPS is implemented using OpenSSL. If you are on Windows, you'll need to install it manually or disabling it by not defining `HTTPS_ENABLED`.
|
||||
|
||||
## Scalability
|
||||
cHTTP is designed to reach moderate scale to allow a compact and easy to work with implementation. The non-blocking I/O is based on `poll()` which I would say works up to about 500 concurrent connections. If you have more than that, you should consider APIs like epoll, io_uring, and I/O completion ports. If you do go that route, you can still reuse the cHTTP I/O independent core (see HTTP_Engine) to handle the HTTP protocol for you, both for client and server.
|
||||
The source code in the `src/` directory is intended to be be amalgamated into a single file before compilation. The amalgamation is not only intended as a distribution method, but also as easy-access documentation, and therefore need to be readable. For this reasons:
|
||||
1. You never need need to include other cHTTP source files
|
||||
2. All inclusions of third-party headers are to be placed inside `src/includes.h`
|
||||
3. All files must start with a single empty line, unless they start with an overview comment of the file, in which case they must have no empty lines at the beginning of the file.
|
||||
4. All files must end with a single empty line.
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <chttp.h>
|
||||
|
||||
// This is an example of how to use cHTTP to perform
|
||||
// a basic GET request.
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_init();
|
||||
|
||||
// List any headers the request should hold
|
||||
HTTP_String headers[] = {
|
||||
HTTP_STR("User-Agent: cHTTP"),
|
||||
};
|
||||
|
||||
// Perform the request. This will block the thread
|
||||
// until an error occurs or the request completes.
|
||||
HTTP_Response *res = http_get(
|
||||
HTTP_STR("http://example.com/index.html"),
|
||||
headers, HTTP_COUNT(headers)
|
||||
);
|
||||
|
||||
// The http_get function returns NULL if the request
|
||||
// couldn't be performed.
|
||||
if (res == NULL) return -1;
|
||||
|
||||
// If the request succeded (note that responses with
|
||||
// status 4xx and 5xx are not considered as errors in
|
||||
// this context) the returned value holds the parsed
|
||||
// version of the response and the output handle is set.
|
||||
|
||||
printf("status code: %d\n", res->status);
|
||||
|
||||
for (int i = 0; i < res->num_headers; i++) {
|
||||
HTTP_Header header = res->headers[i];
|
||||
printf(
|
||||
"header %d: [%.*s] [%.*s]\n",
|
||||
i,
|
||||
HTTP_UNPACK(header.name),
|
||||
HTTP_UNPACK(header.value)
|
||||
);
|
||||
}
|
||||
|
||||
printf("body: %.*s\n", HTTP_UNPACK(res->body));
|
||||
|
||||
// When we are done reading from the response object
|
||||
// we must free the request's resources.
|
||||
http_response_free(res);
|
||||
|
||||
// All done. Deinitialize the library.
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
#include <stddef.h>
|
||||
#include <chttp.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_init();
|
||||
HTTP_Client *client = http_client_init();
|
||||
if (client == NULL)
|
||||
return -1;
|
||||
|
||||
HTTP_Response *responses[2];
|
||||
|
||||
HTTP_String urls[] = {
|
||||
HTTP_STR("http://coz.is"),
|
||||
HTTP_STR("http://coz.is"),
|
||||
};
|
||||
|
||||
bool trace = false;
|
||||
|
||||
HTTP_RequestBuilder builder;
|
||||
if (http_client_get_builder(client, &builder) < 0) return -1;
|
||||
http_request_builder_trace(builder, trace);
|
||||
http_request_builder_user_data(builder, &responses[0]);
|
||||
http_request_builder_line(builder, HTTP_METHOD_GET, urls[0]);
|
||||
http_request_builder_submit(builder);
|
||||
|
||||
if (http_client_get_builder(client, &builder) < 0) return -1;
|
||||
http_request_builder_trace(builder, trace);
|
||||
http_request_builder_user_data(builder, &responses[1]);
|
||||
http_request_builder_line(builder, HTTP_METHOD_GET, urls[1]);
|
||||
http_request_builder_submit(builder);
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
void **dst;
|
||||
HTTP_Response *output;
|
||||
if (http_client_wait(client, &output, (void*) &dst) < 0)
|
||||
return -1;
|
||||
*dst = output;
|
||||
}
|
||||
|
||||
HTTP_Response *responseA = responses[0];
|
||||
HTTP_Response *responseB = responses[1];
|
||||
|
||||
// ... process responses ...
|
||||
|
||||
http_response_free(responseA);
|
||||
http_response_free(responseB);
|
||||
http_client_free(client);
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <chttp.h>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2) {
|
||||
printf("Usage: %s <url1> [url2 ...]\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
http_global_init();
|
||||
HTTP_Client *client = http_client_init();
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
HTTP_RequestBuilder builder;
|
||||
if (http_client_get_builder(client, &builder) < 0) {
|
||||
printf("request creation error\n");
|
||||
return -1;
|
||||
}
|
||||
http_request_builder_line(builder, HTTP_METHOD_GET, (HTTP_String) { argv[i], strlen(argv[i]) });
|
||||
http_request_builder_submit(builder);
|
||||
printf("request submitted\n");
|
||||
}
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
|
||||
HTTP_Response *res;
|
||||
if (http_client_wait(client, &res, NULL) < 0) {
|
||||
printf("request wait error\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Status: %d\n", res->status);
|
||||
printf("Body: %.*s\n", HTTP_UNPACK(res->body));
|
||||
|
||||
http_response_free(res);
|
||||
}
|
||||
|
||||
http_client_free(client);
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <chttp.h>
|
||||
|
||||
#define BLK "\e[0;30m"
|
||||
#define RED "\e[0;31m"
|
||||
#define GRN "\e[0;32m"
|
||||
#define YEL "\e[0;33m"
|
||||
#define BLU "\e[0;34m"
|
||||
#define MAG "\e[0;35m"
|
||||
#define CYN "\e[0;36m"
|
||||
#define WHT "\e[0;37m"
|
||||
#define RST "\e[0m"
|
||||
|
||||
HTTP_String *already_crawled = NULL;
|
||||
int num_already_crawled = 0;
|
||||
int cap_already_crawled = 0;
|
||||
|
||||
HTTP_String copystr(HTTP_String str)
|
||||
{
|
||||
char *copy = malloc(str.len);
|
||||
if (copy == NULL)
|
||||
abort();
|
||||
memcpy(copy, str.ptr, str.len);
|
||||
return (HTTP_String) { copy, str.len };
|
||||
}
|
||||
|
||||
int normalize_url(HTTP_String url, char *dst, int max)
|
||||
{
|
||||
HTTP_URL parsed_url;
|
||||
if (http_parse_url(url.ptr, url.len, &parsed_url) <= 0)
|
||||
return -1;
|
||||
|
||||
int len = snprintf(dst, max, "http://%.*s%.*s",
|
||||
(int) parsed_url.authority.host.text.len,
|
||||
parsed_url.authority.host.text.ptr,
|
||||
(int) parsed_url.path.len,
|
||||
parsed_url.path.ptr
|
||||
);
|
||||
if (len < 0 || len >= max)
|
||||
return -1;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void add_to_crawled_list(HTTP_String url)
|
||||
{
|
||||
if (num_already_crawled == cap_already_crawled) {
|
||||
|
||||
int new_cap = 2 * cap_already_crawled;
|
||||
if (new_cap == 0)
|
||||
new_cap = 8;
|
||||
|
||||
HTTP_String *new_ptr = malloc(new_cap * sizeof(HTTP_String));
|
||||
if (new_ptr == NULL)
|
||||
abort();
|
||||
|
||||
if (cap_already_crawled > 0) {
|
||||
for (int i = 0; i < num_already_crawled; i++)
|
||||
new_ptr[i] = already_crawled[i];
|
||||
free(already_crawled);
|
||||
}
|
||||
|
||||
already_crawled = new_ptr;
|
||||
cap_already_crawled = new_cap;
|
||||
}
|
||||
|
||||
char buf[1<<10];
|
||||
int len = normalize_url(url, buf, (int) sizeof(buf));
|
||||
if (len < 0) return;
|
||||
|
||||
already_crawled[num_already_crawled++] = copystr((HTTP_String) { buf, len });
|
||||
}
|
||||
|
||||
bool is_already_crawled(HTTP_String url)
|
||||
{
|
||||
char buf[1<<10];
|
||||
int len = normalize_url(url, buf, (int) sizeof(buf));
|
||||
if (len < 0 || len >= (int) sizeof(buf))
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < num_already_crawled; i++)
|
||||
if (http_streq(already_crawled[i], (HTTP_String) { buf, len }))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
HTTP_String next_link(HTTP_String src, int *pcur)
|
||||
{
|
||||
int cur = *pcur;
|
||||
|
||||
for (;;) {
|
||||
|
||||
while (cur < src.len && src.ptr[cur] != 'h')
|
||||
cur++;
|
||||
|
||||
if (cur == src.len)
|
||||
break;
|
||||
|
||||
int off = cur;
|
||||
|
||||
HTTP_URL parsed_url;
|
||||
int len = http_parse_url(src.ptr + cur, src.len - cur, &parsed_url);
|
||||
if (len <= 0) {
|
||||
cur++;
|
||||
continue;
|
||||
}
|
||||
|
||||
cur += len;
|
||||
|
||||
if (!http_streq(parsed_url.scheme, HTTP_STR("http")) &&
|
||||
!http_streq(parsed_url.scheme, HTTP_STR("https")))
|
||||
continue;
|
||||
|
||||
*pcur = cur;
|
||||
return (HTTP_String) { src.ptr + off, len };
|
||||
}
|
||||
|
||||
*pcur = cur;
|
||||
return (HTTP_String) { NULL, 0 };
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
http_global_init();
|
||||
|
||||
if (argc < 2) {
|
||||
printf("Usage: %s <URL>\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
HTTP_String start_url = { argv[1], strlen(argv[1]) };
|
||||
|
||||
HTTP_Client *client = http_client_init();
|
||||
if (client == NULL) {
|
||||
printf("Couldn't initialize HTTP client object\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
HTTP_RequestBuilder builder;
|
||||
int ret = http_client_get_builder(client, &builder);
|
||||
if (ret < 0) {
|
||||
printf("Couldn't start request\n");
|
||||
http_client_free(client);
|
||||
return -1;
|
||||
}
|
||||
http_request_builder_line(builder, HTTP_METHOD_GET, start_url);
|
||||
http_request_builder_header(builder, HTTP_STR("User-Agent: Simple crawler"));
|
||||
http_request_builder_submit(builder);
|
||||
|
||||
for (;;) {
|
||||
|
||||
HTTP_Response *res;
|
||||
ret = http_client_wait(client, &res, NULL);
|
||||
if (ret < 0) {
|
||||
// TODO
|
||||
return -1;
|
||||
}
|
||||
if (res == NULL)
|
||||
continue;
|
||||
|
||||
HTTP_String body = res->body;
|
||||
|
||||
int cursor = 0;
|
||||
for (;;) {
|
||||
|
||||
HTTP_String url = next_link(body, &cursor);
|
||||
if (url.len == 0) break;
|
||||
|
||||
if (is_already_crawled(url)) {
|
||||
printf("Ignoring " RED "%.*s" RST "\n", HTTP_UNPACK(url));
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("Fetching " GRN "%.*s" RST "\n", HTTP_UNPACK(url));
|
||||
add_to_crawled_list(url);
|
||||
|
||||
HTTP_RequestBuilder builder;
|
||||
ret = http_client_get_builder(client, &builder);
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
http_request_builder_line(builder, HTTP_METHOD_GET, url);
|
||||
http_request_builder_header(builder, HTTP_STR("User-Agent: Simple crawler"));
|
||||
http_request_builder_submit(builder);
|
||||
}
|
||||
}
|
||||
|
||||
http_client_free(client);
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2ipdef.h>
|
||||
#include <ws2tcpip.h>
|
||||
#define CLOSE_SOCKET closesocket
|
||||
#else
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#define SOCKET int
|
||||
#define INVALID_SOCKET -1
|
||||
#define CLOSE_SOCKET close
|
||||
#endif
|
||||
|
||||
#include <chttp.h>
|
||||
|
||||
// This example showcases how to use the engine interface
|
||||
// to build a blocking HTTP server that works on Windows
|
||||
// and Linux.
|
||||
|
||||
// Callback used by the engine to manage dynamic memory
|
||||
static void *memfunc(HTTP_MemoryFuncTag tag,
|
||||
void *ptr, int len, void *data)
|
||||
{
|
||||
(void) data;
|
||||
switch (tag) {
|
||||
|
||||
case HTTP_MEMFUNC_MALLOC:
|
||||
return malloc(len);
|
||||
|
||||
case HTTP_MEMFUNC_FREE:
|
||||
free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void produce_response(HTTP_Engine *eng)
|
||||
{
|
||||
// All considerations in simple_server.c for how responses
|
||||
// are built also applies here, where http_engine_XXX functions
|
||||
// are used instead of http_response_XXX functions.
|
||||
|
||||
// Set the response status
|
||||
http_engine_status(eng, 200);
|
||||
|
||||
// Set zero or more headers
|
||||
http_engine_header(eng, HTTP_STR("Server: tinyhttp"));
|
||||
|
||||
// Set some bytes in the body
|
||||
http_engine_body(eng, HTTP_STR("Hello, world!"));
|
||||
|
||||
// This is one difference from the http_response_XXX API.
|
||||
// It's possible to write response content directly into
|
||||
// the engine's output buffer. This avoids copies in some
|
||||
// circumstances.
|
||||
|
||||
char msg[] = " What's up??";
|
||||
|
||||
// First, set how many bytes the output buffer will need
|
||||
// to hold at least:
|
||||
http_engine_bodycap(eng, sizeof(msg)-1);
|
||||
|
||||
// Now get the location for the write. The returned pointer
|
||||
// points to a region of size "cap", which equal or greater
|
||||
// to the previously set minimum capacity.
|
||||
int cap;
|
||||
char *dst = http_engine_bodybuf(eng, &cap);
|
||||
|
||||
// If an error occurred internally, the returned pointer will
|
||||
// be NULL and the capacity 0. In this case, you can just skip
|
||||
// this write. The engine will automatically be closed when the
|
||||
// "http_engine_done" function is called.
|
||||
if (dst) {
|
||||
memcpy(dst, msg, sizeof(msg)-1);
|
||||
|
||||
// Tell the engine how many bytes the application wrote to the
|
||||
// provided buffer.
|
||||
http_engine_bodyack(eng, sizeof(msg)-1);
|
||||
}
|
||||
|
||||
// If an error occurs, you can undo all progress and start
|
||||
// from scratch
|
||||
int error = rand() & 1;
|
||||
if (error) {
|
||||
http_engine_undo(eng);
|
||||
http_engine_status(eng, 500);
|
||||
http_engine_done(eng);
|
||||
return;
|
||||
}
|
||||
|
||||
http_engine_done(eng);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
// Interface and port the server will be listening on.
|
||||
char *addr = "127.0.0.1";
|
||||
int port = 8080;
|
||||
|
||||
#ifdef _WIN32
|
||||
WSADATA wd;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wd))
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
// Create the listening socket
|
||||
SOCKET listen_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (listen_fd == INVALID_SOCKET)
|
||||
return -1;
|
||||
|
||||
// Ignore the cooldown time for the bound interface to
|
||||
// avoid that annoying "address in use" error
|
||||
int reuse = 1;
|
||||
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &reuse, sizeof(reuse));
|
||||
|
||||
// Fill out the address struct
|
||||
struct sockaddr_in bind_buf;
|
||||
{
|
||||
struct in_addr addr_buf;
|
||||
if (inet_pton(AF_INET, addr, &addr_buf) != 1)
|
||||
return -1;
|
||||
|
||||
bind_buf.sin_family = AF_INET;
|
||||
bind_buf.sin_port = htons(port);
|
||||
bind_buf.sin_addr = addr_buf;
|
||||
memset(&bind_buf.sin_zero, 0, sizeof(bind_buf.sin_zero));
|
||||
}
|
||||
|
||||
// Associate the listening socket to the interface
|
||||
if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0)
|
||||
return -1;
|
||||
|
||||
// Allow incoming connections
|
||||
if (listen(listen_fd, 32) < 0)
|
||||
return -1;
|
||||
|
||||
for (;;) {
|
||||
|
||||
printf("Waiting for a connection\n");
|
||||
|
||||
// Get an incoming connection from the kernel
|
||||
SOCKET client_fd = accept(listen_fd, NULL, NULL);
|
||||
if (client_fd == INVALID_SOCKET)
|
||||
continue;
|
||||
|
||||
printf("New connection\n");
|
||||
|
||||
// Initialize the HTTP state machine
|
||||
HTTP_Engine eng;
|
||||
http_engine_init(&eng, 0, memfunc, NULL);
|
||||
|
||||
for (;;) {
|
||||
|
||||
// At this point, the engine can be in one
|
||||
// of four states:
|
||||
// 1) RECV_BUF: The engine is waiting for bytes from the network
|
||||
// 2) SEND_BUF: The engine wants to write bytes from the network
|
||||
// 3) CLOSED: The connection shut down at the HTTP layer
|
||||
// 4) PREP_STATUS: A request was received and the associated response
|
||||
// needs to be generated.
|
||||
HTTP_EngineState state = http_engine_state(&eng);
|
||||
|
||||
if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) {
|
||||
|
||||
produce_response(&eng);
|
||||
|
||||
} else if (state == HTTP_ENGINE_STATE_SERVER_RECV_BUF) {
|
||||
|
||||
printf("Receiving bytes\n");
|
||||
|
||||
// Get a pointer to the engine's input buffer
|
||||
int cap;
|
||||
char *dst = http_engine_recvbuf(&eng, &cap);
|
||||
|
||||
// The application can write up to "cap" bytes
|
||||
// to the "dst" buffer.
|
||||
|
||||
int ret = recv(client_fd, dst, cap, 0);
|
||||
if (ret <= 0) {
|
||||
// If the peer disconnected or an error occurred,
|
||||
// we can "close" the engine. This makes it so any
|
||||
// further operation on the engine will be a no-op
|
||||
// and the next time we query the state we will get
|
||||
// CLOSED.
|
||||
http_engine_close(&eng);
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
printf("Received %d bytes\n", ret);
|
||||
|
||||
// Tell the engine how many bytes we wrote to
|
||||
// the buffer.
|
||||
http_engine_recvack(&eng, ret);
|
||||
|
||||
} else if (state == HTTP_ENGINE_STATE_SERVER_SEND_BUF) {
|
||||
|
||||
// This code is the same as the recv case except
|
||||
// we read from the buffer instead of writing.
|
||||
|
||||
printf("Sending bytes\n");
|
||||
|
||||
int len;
|
||||
char *src = http_engine_sendbuf(&eng, &len);
|
||||
|
||||
// Here "src" points to "len" bytes that need to
|
||||
// be sent over the network.
|
||||
int ret = send(client_fd, src, len, 0);
|
||||
if (ret < 0) {
|
||||
http_engine_close(&eng);
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
printf("Sent %d bytes\n", ret);
|
||||
|
||||
http_engine_sendack(&eng, ret);
|
||||
|
||||
} else {
|
||||
// HTTP_ENGINE_STATE_SERVER_CLOSED
|
||||
printf("HTTP close\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
printf("Closing connection\n");
|
||||
|
||||
http_engine_free(&eng);
|
||||
CLOSE_SOCKET(client_fd);
|
||||
}
|
||||
|
||||
CLOSE_SOCKET(listen_fd);
|
||||
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <mswsock.h>
|
||||
#include <chttp.h>
|
||||
|
||||
#define MAX_CLIENTS (1<<10)
|
||||
|
||||
// Callback used by the engine to manage dynamic memory
|
||||
static void *memfunc(HTTP_MemoryFuncTag tag,
|
||||
void *ptr, int len, void *data)
|
||||
{
|
||||
(void) data;
|
||||
switch (tag) {
|
||||
|
||||
case HTTP_MEMFUNC_MALLOC:
|
||||
return malloc(len);
|
||||
|
||||
case HTTP_MEMFUNC_FREE:
|
||||
free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static SOCKET start_accept(LPFN_ACCEPTEX AcceptEx, SOCKET listen_fd, OVERLAPPED *accept_ov, char *buf, int bufsize)
|
||||
{
|
||||
SOCKET accept_target = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (accept_target == INVALID_SOCKET)
|
||||
return INVALID_SOCKET;
|
||||
|
||||
unsigned long num;
|
||||
memset(accept_ov, 0, sizeof(OVERLAPPED));
|
||||
BOOL ok = AcceptEx(listen_fd, accept_target, buf, bufsize - ((sizeof(struct sockaddr_in) + 16) * 2),
|
||||
sizeof(struct sockaddr_in) + 16,
|
||||
sizeof(struct sockaddr_in) + 16,
|
||||
&num, accept_ov);
|
||||
if (!ok && WSAGetLastError() != ERROR_IO_PENDING) {
|
||||
closesocket(accept_target);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
return accept_target;
|
||||
}
|
||||
|
||||
static void start_recv_or_send(SOCKET sock, OVERLAPPED *recv_ov, OVERLAPPED *send_ov, HTTP_Engine *eng)
|
||||
{
|
||||
HTTP_EngineState state = http_engine_state(eng);
|
||||
|
||||
if (state == HTTP_ENGINE_STATE_SERVER_RECV_BUF) {
|
||||
|
||||
int cap;
|
||||
char *dst = http_engine_recvbuf(eng, &cap);
|
||||
|
||||
memset(recv_ov, 0, sizeof(OVERLAPPED));
|
||||
int ok = ReadFile((HANDLE) sock, dst, cap, NULL, recv_ov);
|
||||
if (!ok && GetLastError() != ERROR_IO_PENDING)
|
||||
http_engine_close(eng);
|
||||
|
||||
} else if (state == HTTP_ENGINE_STATE_SERVER_SEND_BUF) {
|
||||
|
||||
int len;
|
||||
char *src = http_engine_sendbuf(eng, &len);
|
||||
|
||||
memset(send_ov, 0, sizeof(OVERLAPPED));
|
||||
int ok = WriteFile((HANDLE) sock, src, len, NULL, send_ov);
|
||||
if (!ok && GetLastError() != ERROR_IO_PENDING)
|
||||
http_engine_close(eng);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
// Interface and port the server will be listening on.
|
||||
char *addr = "127.0.0.1";
|
||||
int port = 8080;
|
||||
|
||||
// Initialize the winsock subsystem
|
||||
WSADATA wd;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wd))
|
||||
return -1;
|
||||
|
||||
// Create the I/O completion port object
|
||||
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
|
||||
if (iocp == INVALID_HANDLE_VALUE)
|
||||
return -1;
|
||||
|
||||
// Create the listening socket
|
||||
SOCKET listen_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (listen_fd == INVALID_SOCKET)
|
||||
return -1;
|
||||
|
||||
// Register the socket into the IOCP
|
||||
if (CreateIoCompletionPort((HANDLE) listen_fd, iocp, 0, 0) == NULL)
|
||||
return -1;
|
||||
|
||||
// Ignore the cooldown time for the bound interface to
|
||||
// avoid that annoying "address in use" error
|
||||
int reuse = 1;
|
||||
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &reuse, sizeof(reuse));
|
||||
|
||||
// Fill out the address struct
|
||||
struct sockaddr_in bind_buf;
|
||||
{
|
||||
struct in_addr addr_buf;
|
||||
if (inet_pton(AF_INET, addr, &addr_buf) != 1)
|
||||
return -1;
|
||||
|
||||
bind_buf.sin_family = AF_INET;
|
||||
bind_buf.sin_port = htons(port);
|
||||
bind_buf.sin_addr = addr_buf;
|
||||
memset(&bind_buf.sin_zero, 0, sizeof(bind_buf.sin_zero));
|
||||
}
|
||||
|
||||
// Associate the listening socket to the interface
|
||||
if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0)
|
||||
return -1;
|
||||
|
||||
// Allow incoming connections
|
||||
if (listen(listen_fd, 32) < 0)
|
||||
return -1;
|
||||
|
||||
// Get the AcceptEx function pointer
|
||||
LPFN_ACCEPTEX AcceptEx = NULL;
|
||||
GUID GuidAcceptEx = WSAID_ACCEPTEX;
|
||||
unsigned long num;
|
||||
int ret = WSAIoctl(listen_fd,
|
||||
SIO_GET_EXTENSION_FUNCTION_POINTER,
|
||||
&GuidAcceptEx, sizeof(GuidAcceptEx),
|
||||
&AcceptEx, sizeof(AcceptEx),
|
||||
&num, NULL, NULL);
|
||||
if (ret == SOCKET_ERROR)
|
||||
return -1;
|
||||
|
||||
OVERLAPPED accept_ov;
|
||||
char accept_buf[2 * (sizeof(struct sockaddr_in) + 16)];
|
||||
SOCKET accept_target = start_accept(AcceptEx, listen_fd, &accept_ov, accept_buf, (int) sizeof(accept_buf));
|
||||
if (accept_target == INVALID_SOCKET)
|
||||
return -1;
|
||||
|
||||
OVERLAPPED recv_overlapped[MAX_CLIENTS];
|
||||
OVERLAPPED send_overlapped[MAX_CLIENTS];
|
||||
SOCKET sockets[MAX_CLIENTS];
|
||||
HTTP_Engine engs[MAX_CLIENTS];
|
||||
|
||||
for (int i = 0; i < MAX_CLIENTS; i++)
|
||||
sockets[i] = INVALID_SOCKET;
|
||||
|
||||
for (;;) {
|
||||
DWORD timeout = INFINITE;
|
||||
|
||||
DWORD transferred;
|
||||
ULONG_PTR key;
|
||||
OVERLAPPED *overlapped;
|
||||
BOOL result = GetQueuedCompletionStatus(iocp, &transferred, &key, &overlapped, timeout);
|
||||
if (!result && overlapped == NULL) {
|
||||
if (GetLastError() == WAIT_TIMEOUT)
|
||||
continue; // Go back to waiting
|
||||
return -1; // Error
|
||||
}
|
||||
|
||||
if (overlapped == &accept_ov) {
|
||||
// A new client connected
|
||||
|
||||
int i = 0;
|
||||
while (i < MAX_CLIENTS && sockets[i] != INVALID_SOCKET)
|
||||
i++;
|
||||
|
||||
if (i == MAX_CLIENTS)
|
||||
closesocket(accept_target); // Server limit reached
|
||||
else {
|
||||
|
||||
sockets[i] = accept_target;
|
||||
http_engine_init(&engs[i], 0, memfunc, NULL);
|
||||
|
||||
// Register the socket into the IOCP
|
||||
if (CreateIoCompletionPort((HANDLE) sockets[i], iocp, i, 0) == NULL)
|
||||
return -1;
|
||||
|
||||
start_recv_or_send(sockets[i], &recv_overlapped[i], &send_overlapped[i], &engs[i]);
|
||||
|
||||
// Check that the recv or send operation was started.
|
||||
// If now, remove the connection
|
||||
if (http_engine_state(&engs[i]) == HTTP_ENGINE_STATE_CLIENT_CLOSED) {
|
||||
closesocket(sockets[i]);
|
||||
sockets[i] = INVALID_SOCKET;
|
||||
http_engine_free(&engs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
accept_target = start_accept(AcceptEx, listen_fd, &accept_ov, accept_buf, (int) sizeof(accept_buf));
|
||||
if (accept_target == INVALID_SOCKET)
|
||||
return -1;
|
||||
|
||||
// Go back to waiting
|
||||
continue;
|
||||
}
|
||||
|
||||
// Complete the current operation
|
||||
if (0) {}
|
||||
else if (overlapped == &recv_overlapped[key]) http_engine_recvack(&engs[key], transferred);
|
||||
else if (overlapped == &send_overlapped[key]) http_engine_sendack(&engs[key], transferred);
|
||||
|
||||
if (http_engine_state(&engs[key]) == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) {
|
||||
|
||||
// See blocking_server_with_engine.c to learn about how to
|
||||
// build a response
|
||||
http_engine_status(&engs[key], 200);
|
||||
http_engine_body(&engs[key], HTTP_STR("Hello, world!"));
|
||||
http_engine_done(&engs[key]);
|
||||
}
|
||||
|
||||
start_recv_or_send(sockets[key],
|
||||
&recv_overlapped[key],
|
||||
&send_overlapped[key],
|
||||
&engs[key]);
|
||||
|
||||
if (http_engine_state(&engs[key]) == HTTP_ENGINE_STATE_SERVER_CLOSED) {
|
||||
http_engine_free(&engs[key]);
|
||||
closesocket(sockets[key]);
|
||||
sockets[key] = INVALID_SOCKET;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("This example only works on windows!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,99 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <chttp.h>
|
||||
|
||||
// This example shows how to set up a basic HTTP server
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_init();
|
||||
|
||||
// Choose the interface to listen on and the port.
|
||||
// Currently, servers can only bind to IPv4 addresses.
|
||||
HTTP_String addr = HTTP_STR("127.0.0.1");
|
||||
uint16_t port = 8080;
|
||||
|
||||
bool all_interfaces = false;
|
||||
|
||||
// If you want to bind to all interfaces, you can
|
||||
// set the address to an empty string.
|
||||
if (all_interfaces)
|
||||
addr = HTTP_STR("");
|
||||
|
||||
// Instanciate the HTTP server object
|
||||
HTTP_Server *server = http_server_init(addr, port);
|
||||
if (server == NULL)
|
||||
return -1;
|
||||
|
||||
// Now we loop forever. Every iteration will serve
|
||||
// a single HTTP request
|
||||
for (;;) {
|
||||
|
||||
HTTP_Request *req;
|
||||
HTTP_ResponseBuilder builder;
|
||||
|
||||
// Block until a request is available
|
||||
int ret = http_server_wait(server, &req, &builder);
|
||||
|
||||
// The wait functions returns 0 on success and -1
|
||||
// on error. By "error" I mean an unrecoverable
|
||||
// condition. There is no other option than kill
|
||||
// the process.
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
// The request information is accessible from
|
||||
// the [req] variable. Most fields in the request
|
||||
// struct are reference to the original request
|
||||
// string. They use type HTTP_String and are not
|
||||
// null-terminated. This means you'll have to make
|
||||
// sure to express the length when interacting with
|
||||
// libc:
|
||||
HTTP_String path = req->url.path;
|
||||
printf("requested path [%.*s]\n", HTTP_UNPACK(path));
|
||||
|
||||
// To find a specific header value, you can either
|
||||
// iterate over the [req->headers] array or use
|
||||
// a helper function. Note that this compares header
|
||||
// names case-insensitively.
|
||||
int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Some-Header-Name"));
|
||||
if (idx == -1) {
|
||||
// Header wasn't found
|
||||
} else {
|
||||
// Found
|
||||
HTTP_String value = req->headers[idx].value;
|
||||
printf("Header has value [%.*s]\n", HTTP_UNPACK(value));
|
||||
}
|
||||
|
||||
// To create a response, you will need to specify
|
||||
// status code, headers, and content in the proper
|
||||
// order.
|
||||
|
||||
// First the status code
|
||||
http_response_builder_status(builder, 200);
|
||||
|
||||
// Then zero or more headers
|
||||
http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain"));
|
||||
|
||||
// Then you can write zero or more chunks of the response body
|
||||
http_response_builder_body(builder, HTTP_STR("Hello"));
|
||||
http_response_builder_body(builder, HTTP_STR(", world!"));
|
||||
|
||||
// Then, mark the request as complete (Very important or the server will hang!)
|
||||
http_response_builder_done(builder);
|
||||
|
||||
// Note that none of the http_response_builder_* functions return errors.
|
||||
// This is by design to simplify user endpoint code. If at any point
|
||||
// something goes wrong, the server will send a code 4xx or 5xx to
|
||||
// the client or abort the TCP connection entirely.
|
||||
}
|
||||
|
||||
// This program will loop forever, but if you write
|
||||
// your server in a way to exit gracefully, this is
|
||||
// you the server object is freed:
|
||||
http_server_free(server);
|
||||
http_global_free();
|
||||
|
||||
// Have fun. Bye!
|
||||
return 0;
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
#include <string.h>
|
||||
#include <chttp.h>
|
||||
|
||||
// This example shows how to generate response bodies
|
||||
// using the zero-copy API.
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_init();
|
||||
|
||||
// All the setup is identical to the previous example.
|
||||
// The only thing that changes where "http_response_builder_body"
|
||||
// is called.
|
||||
|
||||
HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080);
|
||||
if (server == NULL)
|
||||
return -1;
|
||||
|
||||
for (;;) {
|
||||
|
||||
HTTP_Request *req;
|
||||
HTTP_ResponseBuilder builder;
|
||||
|
||||
int ret = http_server_wait(server, &req, &builder);
|
||||
if (ret < 0) return -1;
|
||||
|
||||
http_response_builder_status(builder, 200);
|
||||
http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain"));
|
||||
|
||||
// The previous example used the *_body function to
|
||||
// write the response body in chunks:
|
||||
//
|
||||
// http_response_builder_body(builder, HTTP_STR("Hello"));
|
||||
// http_response_builder_body(builder, HTTP_STR(", world!"));
|
||||
//
|
||||
// This function reads from an user buffer and copies
|
||||
// the data in the connection's output buffer. If the
|
||||
// data is not in a contiguous region that's fine as
|
||||
// the function can be called repeatedly on separate
|
||||
// chunks.
|
||||
//
|
||||
// This function assumes the user is holding in memory
|
||||
// the data to be sent beforehand, but this may not
|
||||
// be true. If for instance the data comes from a file,
|
||||
// the user will need to read from the file, copy in
|
||||
// memory and then write to the response body.
|
||||
//
|
||||
// The zero-copy API allows copying directly from the
|
||||
// source of the data (such as the read() system call
|
||||
// on a file descriptor) to the server's output buffer
|
||||
|
||||
char example_data[] = "I'm some example data!";
|
||||
int example_data_len = sizeof(example_data)-1;
|
||||
|
||||
// Tell the server how much data we are going to write
|
||||
http_response_builder_bodycap(builder, example_data_len);
|
||||
|
||||
int cap;
|
||||
char *dst;
|
||||
|
||||
// Get a pointer to the server's output buffer. The
|
||||
// output parameter [cap] is the capacity of the region
|
||||
// and is equal or larger than the data we requested
|
||||
// with *_bodycap
|
||||
dst = http_response_builder_bodybuf(builder, &cap);
|
||||
|
||||
// Write the data directly into the output buffer. In
|
||||
// this example we are copying from memory, but you could
|
||||
// read from a file or a socket
|
||||
if (dst) {
|
||||
memcpy(dst, example_data, example_data_len);
|
||||
}
|
||||
|
||||
// Tell the server how much bytes we have written to
|
||||
// the provided region.
|
||||
http_response_builder_bodyack(builder, example_data_len);
|
||||
|
||||
// The reason we had to guard the [memcpy] by checking the
|
||||
// [dst] pointer is that if an error occurred internally
|
||||
// then *_bodybuf will return NULL. This will cause the
|
||||
// server to either return an internally generated error
|
||||
// response or drop the connection. The correct thing to
|
||||
// do in that situation is not access the pointer and do
|
||||
// as nothing bad happened.
|
||||
|
||||
// As usual, mark the response as complete
|
||||
http_response_builder_done(builder);
|
||||
|
||||
// If we're being being honest, this is not a zero-copy
|
||||
// interface. It's more like an N-1 copy interface as in
|
||||
// it just avoids one copy from userspace to userspace!
|
||||
}
|
||||
|
||||
http_server_free(server);
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
#include <stddef.h>
|
||||
#include <chttp.h>
|
||||
|
||||
// This example shows how undo a response that is being built
|
||||
// when an error occurs.
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_init();
|
||||
|
||||
HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080);
|
||||
if (server == NULL)
|
||||
return -1;
|
||||
|
||||
for (;;) {
|
||||
|
||||
HTTP_Request *req;
|
||||
HTTP_ResponseBuilder builder;
|
||||
|
||||
int ret = http_server_wait(server, &req, &builder);
|
||||
if (ret < 0) return -1;
|
||||
|
||||
// Say we are building a request..
|
||||
|
||||
http_response_builder_status(builder, 200);
|
||||
http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain"));
|
||||
|
||||
// .. and in the middle of building an error condition
|
||||
// occurs. Maybe a file was missing or an allocation fails.
|
||||
// The proper response in this case would be a code 500
|
||||
// with an error message, but we already wrote the first
|
||||
// part of the response assuming the operation would succede.
|
||||
//
|
||||
// You can use the *_undo function to reset the response
|
||||
// building process
|
||||
|
||||
bool error_occurred = true;
|
||||
if (error_occurred) {
|
||||
|
||||
http_response_builder_undo(builder);
|
||||
|
||||
// Now we are back to setting the status code
|
||||
http_response_builder_status(builder, 500);
|
||||
http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain"));
|
||||
http_response_builder_body(builder, HTTP_STR("An error occurred!"));
|
||||
http_response_builder_done(builder);
|
||||
|
||||
} else {
|
||||
|
||||
// If no error occures, we finish as planned
|
||||
http_response_builder_body(builder, HTTP_STR("Hello, world!"));
|
||||
http_response_builder_done(builder);
|
||||
}
|
||||
}
|
||||
|
||||
http_server_free(server);
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,386 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <chttp.h>
|
||||
|
||||
// !!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //
|
||||
// //
|
||||
// This example is just a proof of concept for now as the library //
|
||||
// still isn't thread-safe. //
|
||||
// //
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //
|
||||
|
||||
// This example shows how to delegate the response creation
|
||||
// process to other threads.
|
||||
//
|
||||
// Your server may have some endpoints that require considerable
|
||||
// computation or may be waiting for some external system to
|
||||
// complete. If we used the current pattern we've been using for
|
||||
// generating requests, following request will have to wait until
|
||||
// this processing has concluded.
|
||||
//
|
||||
// One solution for this situation is to create a separate thread
|
||||
// to do the waiting or processing. When a request is received
|
||||
// that requires processing, it is passed to the second thread.
|
||||
// In the mean time, the main thread can process the next request.
|
||||
// When the thread has finished, it can just call the usual
|
||||
// functions to produce a response.
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
typedef void* Thread;
|
||||
typedef unsigned long ThreadReturn;
|
||||
typedef CRITICAL_SECTION Mutex;
|
||||
typedef CONDITION_VARIABLE Condvar;
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <pthread.h>
|
||||
typedef pthread_t Thread;
|
||||
typedef void* ThreadReturn;
|
||||
typedef pthread_mutex_t Mutex;
|
||||
typedef pthread_cond_t Condvar;
|
||||
#endif
|
||||
|
||||
// The following types are used to describe a job the worker
|
||||
// needs to work on.
|
||||
typedef enum {
|
||||
|
||||
// Special value used to tell the worker the program is terminating
|
||||
NO_JOB,
|
||||
|
||||
// We assume jobs may be of two different types we call A and B
|
||||
JOB_A,
|
||||
JOB_B,
|
||||
|
||||
} JobType;
|
||||
|
||||
typedef struct {
|
||||
JobType type;
|
||||
HTTP_ResponseBuilder builder;
|
||||
} Job;
|
||||
|
||||
// Maximum number of jobs that can be buffered at once
|
||||
#define MAX_JOBS 100
|
||||
|
||||
void init_job_queue(void);
|
||||
void free_job_queue(void);
|
||||
|
||||
// This function pops an item from the job queue. If the
|
||||
// queue is empty, the thread will block until one is
|
||||
// available.
|
||||
Job pop_job(void);
|
||||
|
||||
// This function adds a job to the queue. The block argument
|
||||
// changes the behavior when the queue is full and there is
|
||||
// no space for a new job. If the block argument is true and
|
||||
// there is no space, the thread waits. If the argument is
|
||||
// false the function exits immediately by returning false
|
||||
// with no new job pushed.
|
||||
bool push_job(Job job, bool block);
|
||||
|
||||
void thread_create(Thread *thread, void *arg, ThreadReturn (*func)(void*));
|
||||
ThreadReturn thread_join(Thread thread);
|
||||
|
||||
void mutex_init(Mutex *mutex);
|
||||
void mutex_free(Mutex *mutex);
|
||||
void mutex_lock(Mutex *mutex);
|
||||
void mutex_unlock(Mutex *mutex);
|
||||
|
||||
void condvar_init(Condvar *condvar);
|
||||
void condvar_free(Condvar *condvar);
|
||||
void condvar_wait(Condvar *condvar, Mutex *mutex);
|
||||
void condvar_signal(Condvar *condvar);
|
||||
|
||||
ThreadReturn worker(void*)
|
||||
{
|
||||
for (bool exit = false; !exit; ) {
|
||||
|
||||
Job job = pop_job();
|
||||
|
||||
switch (job.type) {
|
||||
|
||||
case NO_JOB:
|
||||
exit = true;
|
||||
break;
|
||||
|
||||
case JOB_A:
|
||||
http_response_builder_status(job.builder, 200);
|
||||
http_response_builder_body(job.builder, HTTP_STR("Job A completed"));
|
||||
http_response_builder_done(job.builder);
|
||||
break;
|
||||
|
||||
case JOB_B:
|
||||
http_response_builder_status(job.builder, 200);
|
||||
http_response_builder_body(job.builder, HTTP_STR("Job B completed"));
|
||||
http_response_builder_done(job.builder);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_free();
|
||||
init_job_queue();
|
||||
|
||||
HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080);
|
||||
if (server == NULL)
|
||||
return -1;
|
||||
|
||||
Thread worker_id;
|
||||
thread_create(&worker_id, NULL, worker);
|
||||
|
||||
for (;;) {
|
||||
|
||||
HTTP_Request *req;
|
||||
HTTP_ResponseBuilder builder;
|
||||
|
||||
int ret = http_server_wait(server, &req, &builder);
|
||||
if (ret < 0) return -1;
|
||||
|
||||
if (http_streq(req->url.path, HTTP_STR("/endpoint_A"))) {
|
||||
|
||||
// Endpoint A sends the job to the worker.
|
||||
// If too many jobs are queued, it blocks
|
||||
|
||||
Job job;
|
||||
job.type = JOB_A;
|
||||
job.builder = builder;
|
||||
push_job(job, true);
|
||||
|
||||
} else if (http_streq(req->url.path, HTTP_STR("/endpoint_B"))) {
|
||||
|
||||
// Endpoint B sends the job to the worker
|
||||
// but fails if the queue is full, in which
|
||||
// case the "503 Service Unavailable" response
|
||||
// is generated.
|
||||
|
||||
Job job;
|
||||
job.type = JOB_B;
|
||||
job.builder = builder;
|
||||
if (!push_job(job, false)) {
|
||||
http_response_builder_status(builder, 503);
|
||||
http_response_builder_done(builder);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Other endpoints may resolve immediately
|
||||
|
||||
http_response_builder_status(builder, 404);
|
||||
http_response_builder_done(builder);
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the worker by sending an empty job
|
||||
Job job;
|
||||
job.type = NO_JOB;
|
||||
push_job(job, true);
|
||||
thread_join(worker_id);
|
||||
|
||||
http_server_free(server);
|
||||
free_job_queue();
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////
|
||||
|
||||
// This is a pretty standard condition variable-based
|
||||
// producer-consumer queue. In this example we are using
|
||||
// one worker, but we could easily have more than that.
|
||||
|
||||
Job queue[MAX_JOBS];
|
||||
int queue_head = 0;
|
||||
int queue_count = 0;
|
||||
Mutex queue_lock;
|
||||
Condvar queue_consume_event;
|
||||
Condvar queue_produce_event;
|
||||
|
||||
void init_job_queue(void)
|
||||
{
|
||||
mutex_init(&queue_lock);
|
||||
condvar_init(&queue_consume_event);
|
||||
condvar_init(&queue_produce_event);
|
||||
}
|
||||
|
||||
void free_job_queue(void)
|
||||
{
|
||||
condvar_free(&queue_produce_event);
|
||||
condvar_free(&queue_consume_event);
|
||||
mutex_free(&queue_lock);
|
||||
}
|
||||
|
||||
Job pop_job(void)
|
||||
{
|
||||
mutex_lock(&queue_lock);
|
||||
|
||||
while (queue_count == 0)
|
||||
condvar_wait(&queue_produce_event, &queue_lock);
|
||||
|
||||
Job job = queue[queue_head];
|
||||
queue_head = (queue_head + 1) % MAX_JOBS;
|
||||
queue_count--;
|
||||
|
||||
condvar_signal(&queue_consume_event);
|
||||
mutex_unlock(&queue_lock);
|
||||
return job;
|
||||
}
|
||||
|
||||
bool push_job(Job job, bool block)
|
||||
{
|
||||
mutex_lock(&queue_lock);
|
||||
if (queue_count == 0) {
|
||||
|
||||
if (!block) {
|
||||
mutex_unlock(&queue_lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
do
|
||||
condvar_wait(&queue_consume_event, &queue_lock);
|
||||
while (queue_count == 0);
|
||||
}
|
||||
|
||||
int tail = (queue_head + queue_count) % MAX_JOBS;
|
||||
queue[tail] = job;
|
||||
queue_count++;
|
||||
|
||||
condvar_signal(&queue_produce_event);
|
||||
mutex_unlock(&queue_lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////
|
||||
|
||||
void thread_create(Thread *thread, void *arg, ThreadReturn (*func)(void*))
|
||||
{
|
||||
#ifdef _WIN32
|
||||
Thread thread_ = CreateThread(NULL, 0, func, arg, 0, NULL);
|
||||
if (thread_ == INVALID_HANDLE_VALUE)
|
||||
abort();
|
||||
*thread = thread_;
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
int ret = pthread_create(thread, NULL, func, arg);
|
||||
if (ret) abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
ThreadReturn thread_join(Thread thread)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
ThreadReturn result;
|
||||
WaitForSingleObject(thread, INFINITE);
|
||||
if (!GetExitCodeThread(thread, &result))
|
||||
abort();
|
||||
CloseHandle(thread);
|
||||
return result;
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
ThreadReturn result;
|
||||
int ret = pthread_join(thread, &result);
|
||||
if (ret) abort();
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
void mutex_init(Mutex *mutex)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
InitializeCriticalSection(mutex);
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
if (pthread_mutex_init(mutex, NULL))
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
void mutex_free(Mutex *mutex)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DeleteCriticalSection(mutex);
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
if (pthread_mutex_destroy(mutex))
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
void mutex_lock(Mutex *mutex)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection(mutex);
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
if (pthread_mutex_lock(mutex))
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
void mutex_unlock(Mutex *mutex)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
LeaveCriticalSection(mutex);
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
if (pthread_mutex_unlock(mutex))
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
void condvar_init(Condvar *condvar)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
InitializeConditionVariable(condvar);
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
if (pthread_cond_init(condvar, NULL))
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
void condvar_free(Condvar *condvar)
|
||||
{
|
||||
#ifdef __linux__
|
||||
if (pthread_cond_destroy(condvar))
|
||||
abort();
|
||||
#else
|
||||
(void) condvar;
|
||||
#endif
|
||||
}
|
||||
|
||||
void condvar_wait(Condvar *condvar, Mutex *mutex)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!SleepConditionVariableCS(condvar, mutex, INFINITE))
|
||||
abort();
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
int err = pthread_cond_wait(condvar, mutex);
|
||||
if (err) abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
void condvar_signal(Condvar *condvar)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WakeConditionVariable(condvar);
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
if (pthread_cond_signal(condvar))
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
|
||||
int main(void) {}
|
||||
@@ -1,78 +0,0 @@
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <chttp.h>
|
||||
|
||||
// This example shows how to set up an HTTPS (HTTP over TLS)
|
||||
// server.
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_init();
|
||||
|
||||
// To setup an HTTPS server, we need to use the *_ex variant
|
||||
// of the server initialization function as it offers more
|
||||
// control. Server objects can serve HTTP traffic, HTTPS
|
||||
// traffic, or both at the same time. The init_ex function
|
||||
// allows us to control this behavior.
|
||||
|
||||
// The first argument is the local interface address. It
|
||||
// works just as the other examples but is shared between
|
||||
// HTTP and HTTPS. Then come the HTTP port and HTTPS port
|
||||
// arguments. If you want to disable HTTP or HTTPS you can
|
||||
// pass zero to its port argument. If the HTTPS port is
|
||||
// not zero, you need to pass the file names of the server's
|
||||
// certificate and private key.
|
||||
HTTP_Server *server = http_server_init_ex(
|
||||
HTTP_STR("127.0.0.1"), // HTTP and HTTPS port
|
||||
8080, // HTTP port
|
||||
8443, // HTTPS port
|
||||
HTTP_STR("cert.pem"),
|
||||
HTTP_STR("privkey.pem")
|
||||
);
|
||||
if (server == NULL)
|
||||
return -1;
|
||||
|
||||
// Just to be clear, to initialize a plain HTTP server
|
||||
// using the *_ex function we would do this:
|
||||
//
|
||||
// HTTP_Server *server = http_server_init_ex(
|
||||
// HTTP_STR("127.0.0.1"), // HTTP and HTTPS port
|
||||
// 8080, // HTTP port
|
||||
// 0, // HTTPS disabled
|
||||
// HTTP_STR(""), // ignore
|
||||
// HTTP_STR("") // ignore
|
||||
// );
|
||||
//
|
||||
// and if we wanted and HTTPS-only server we would
|
||||
// do this:
|
||||
//
|
||||
// HTTP_Server *server = http_server_init_ex(
|
||||
// HTTP_STR("127.0.0.1"), // HTTP and HTTPS port
|
||||
// 0, // HTTP disabled
|
||||
// 8443, // HTTPS port
|
||||
// HTTP_STR("cert.pem"),
|
||||
// HTTP_STR("privkey.pem")
|
||||
// );
|
||||
|
||||
// Everything else is identical to the simple HTTP server
|
||||
// example.
|
||||
|
||||
for (;;) {
|
||||
|
||||
HTTP_Request *req;
|
||||
HTTP_ResponseBuilder builder;
|
||||
|
||||
int ret = http_server_wait(server, &req, &builder);
|
||||
if (ret < 0) return -1;
|
||||
|
||||
http_response_builder_status(builder, 200);
|
||||
http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain"));
|
||||
http_response_builder_body(builder, HTTP_STR("Hello"));
|
||||
http_response_builder_body(builder, HTTP_STR(", world!"));
|
||||
http_response_builder_done(builder);
|
||||
}
|
||||
|
||||
http_server_free(server);
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
#include <stddef.h>
|
||||
#include <chttp.h>
|
||||
|
||||
// This is an example of how to serve different websites
|
||||
// over a single HTTPS server instance.
|
||||
|
||||
int setup_test_certificates(void);
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_init();
|
||||
|
||||
// To test this program you need to add the following
|
||||
// lines to your hosts file:
|
||||
//
|
||||
// 127.0.0.1 websiteA.com
|
||||
// 127.0.0.1 websiteB.com
|
||||
// 127.0.0.1 websiteC.com
|
||||
//
|
||||
// That you can find at /etc/hosts on Linux and
|
||||
// C:\Windows\System32\drivers\etc\hosts on Windows
|
||||
|
||||
// First, create three certificates for the domains:
|
||||
//
|
||||
// websiteA.com
|
||||
// websiteB.com
|
||||
// websiteC.com
|
||||
//
|
||||
// This will create a number of certificate files
|
||||
// and private key files
|
||||
//
|
||||
// websiteA_cert.pem websiteA_key.pem
|
||||
// websiteB_cert.pem websiteB_key.pem
|
||||
// websiteC_cert.pem websiteC_key.pem
|
||||
//
|
||||
// Of course this is just for testing. It is expected
|
||||
// you have your own.
|
||||
int ret = setup_test_certificates();
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
// First, set up an HTTPS server instance with one
|
||||
// of the certificate. This will act as default certificate
|
||||
// when ecrypted connections don't target a specific domain.
|
||||
HTTP_Server *server = http_server_init_ex(
|
||||
HTTP_STR("127.0.0.1"), 8080, 8443,
|
||||
HTTP_STR("websiteA_cert.pem"),
|
||||
HTTP_STR("websiteA_key.pem")
|
||||
);
|
||||
if (server == NULL)
|
||||
return -1;
|
||||
|
||||
// Then we can add an arbitrary number of additional
|
||||
// certificates using the add_website function
|
||||
|
||||
ret = http_server_add_website(server,
|
||||
HTTP_STR("websiteB.com"),
|
||||
HTTP_STR("websiteB_cert.pem"),
|
||||
HTTP_STR("websiteB_key.pem")
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
ret = http_server_add_website(server,
|
||||
HTTP_STR("websiteC.com"),
|
||||
HTTP_STR("websiteC_cert.pem"),
|
||||
HTTP_STR("websiteC_key.pem")
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
// Now the server is ready to accept incoming HTTP
|
||||
// or HTTPS connections.
|
||||
//
|
||||
// Note that the add_website function is only used
|
||||
// to serve the correct certificate to the client.
|
||||
// The HTTP request itself may very well hold a
|
||||
// different domain name in the host header:
|
||||
//
|
||||
// [client] [server]
|
||||
// | |
|
||||
// | TLS hanshake to domain1.com |
|
||||
// | -------------------------------> |
|
||||
// | |
|
||||
// | cert for domain1.com |
|
||||
// | <------------------------------- |
|
||||
// | |
|
||||
// | HTTP request to domain2.com |
|
||||
// | over the encrypted connection |
|
||||
// | established with domain1.com |
|
||||
// | -------------------------------> |
|
||||
// | |
|
||||
// | response as domain2.com |
|
||||
// | <------------------------------- |
|
||||
// | |
|
||||
|
||||
for (;;) {
|
||||
|
||||
HTTP_Request *req;
|
||||
HTTP_ResponseBuilder builder;
|
||||
ret = http_server_wait(server, &req, &builder);
|
||||
if (ret < 0)
|
||||
break;
|
||||
|
||||
if (http_match_host(req, HTTP_STR("websiteB.com"), 8080) ||
|
||||
http_match_host(req, HTTP_STR("websiteB.com"), 8443)) {
|
||||
|
||||
http_response_builder_status(builder, 200);
|
||||
http_response_builder_body(builder, HTTP_STR("Hello from websiteB.com!"));
|
||||
http_response_builder_done(builder);
|
||||
|
||||
} else if (http_match_host(req, HTTP_STR("websiteC.com"), 8080) ||
|
||||
http_match_host(req, HTTP_STR("websiteC.com"), 8443)) {
|
||||
|
||||
http_response_builder_status(builder, 200);
|
||||
http_response_builder_body(builder, HTTP_STR("Hello from websiteC.com!"));
|
||||
http_response_builder_done(builder);
|
||||
|
||||
} else {
|
||||
|
||||
// Serve websiteA.com by default to be consistent
|
||||
// with the certificate setup
|
||||
|
||||
http_response_builder_status(builder, 200);
|
||||
http_response_builder_body(builder, HTTP_STR("Hello from websiteA.com!"));
|
||||
http_response_builder_done(builder);
|
||||
}
|
||||
}
|
||||
|
||||
http_server_free(server);
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int setup_test_certificates(void)
|
||||
{
|
||||
int ret = http_create_test_certificate(
|
||||
HTTP_STR("IT"),
|
||||
HTTP_STR("Organization A"),
|
||||
HTTP_STR("websiteA.com"),
|
||||
HTTP_STR("websiteA_cert.pem"),
|
||||
HTTP_STR("websiteA_key.pem")
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
ret = http_create_test_certificate(
|
||||
HTTP_STR("IT"),
|
||||
HTTP_STR("Organization B"),
|
||||
HTTP_STR("websiteB.com"),
|
||||
HTTP_STR("websiteB_cert.pem"),
|
||||
HTTP_STR("websiteB_key.pem")
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
ret = http_create_test_certificate(
|
||||
HTTP_STR("IT"),
|
||||
HTTP_STR("Organization C"),
|
||||
HTTP_STR("websiteC.com"),
|
||||
HTTP_STR("websiteC_cert.pem"),
|
||||
HTTP_STR("websiteC_key.pem")
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
(The following are thoughts that came to mind that I didn't have the time to add to docs but don't want to forget.)
|
||||
|
||||
The immediate mode API allows writing directly to the library's output buffer avoiding intermediate allocations
|
||||
@@ -1,8 +0,0 @@
|
||||
Allow resolving requests from different threads
|
||||
Find a way to make sure chttp.h and chttp.c are always up to date
|
||||
Find a way to compile OpenSSL on windows
|
||||
handle 3xx client redirections
|
||||
add discussion on string management
|
||||
add discussion on error management
|
||||
add timers
|
||||
add debug and release builds
|
||||
+51
-26
@@ -6,56 +6,81 @@ class Amalgamator:
|
||||
self.out += text
|
||||
|
||||
def append_file(self, file):
|
||||
|
||||
self.out += "\n"
|
||||
self.out += "////////////////////////////////////////////////////////////////////////////////////////\n"
|
||||
self.out += "// " + file + "\n"
|
||||
self.out += "////////////////////////////////////////////////////////////////////////////////////////\n"
|
||||
self.out += "\n"
|
||||
self.out += "#line 1 \"" + file + "\"\n"
|
||||
self.out += open(file).read()
|
||||
|
||||
if len(self.out) > 0 and self.out[len(self.out)-1] != '\n':
|
||||
if len(self.out) > 0 and self.out[len(self.out) - 1] != "\n":
|
||||
self.out += "\n"
|
||||
|
||||
def save(self, file):
|
||||
open(file, 'w').write(self.out)
|
||||
open(file, "w").write(self.out)
|
||||
|
||||
desc = """
|
||||
// This file was generated automatically. Do not modify directly!
|
||||
|
||||
desc = """// cHTTP, an HTTP client and server library!
|
||||
//
|
||||
// This file was generated automatically. Do not modify directly.
|
||||
//
|
||||
// Refer to the end of this file for the license"""
|
||||
|
||||
license = """
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright 2025 Francesco Cozzuto
|
||||
//
|
||||
// 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.
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
"""
|
||||
|
||||
header = Amalgamator()
|
||||
header.append_text("#ifndef HTTP_AMALGAMATION\n")
|
||||
header.append_text("#define HTTP_AMALGAMATION\n")
|
||||
header.append_text(desc)
|
||||
header.append_file("src/includes.h")
|
||||
header.append_file("src/basic.h")
|
||||
header.append_file("src/parse.h")
|
||||
header.append_file("src/engine.h")
|
||||
header.append_file("src/secure_context.h")
|
||||
header.append_file("src/socket.h")
|
||||
header.append_file("src/thread.h")
|
||||
header.append_file("src/byte_queue.h")
|
||||
header.append_file("src/cert.h")
|
||||
header.append_file("src/client.h")
|
||||
header.append_file("src/parse.h")
|
||||
header.append_file("src/server.h")
|
||||
header.append_text("#endif // HTTP_AMALGAMATION\n")
|
||||
header.append_text(license)
|
||||
header.save("chttp.h")
|
||||
|
||||
source = Amalgamator()
|
||||
|
||||
source.append_text("#ifndef HTTP_NOINCLUDE\n")
|
||||
source.append_text("#include \"chttp.h\"\n")
|
||||
source.append_text(desc)
|
||||
source.append_text("\n")
|
||||
source.append_text("#ifndef HTTP_DONT_INCLUDE\n")
|
||||
source.append_text('#include "chttp.h"\n')
|
||||
source.append_text("#endif\n")
|
||||
|
||||
source.append_file("src/sec.h")
|
||||
source.append_file("src/socket_raw.h")
|
||||
source.append_file("src/socket.h")
|
||||
source.append_file("src/socket_pool.h")
|
||||
source.append_file("src/basic.c")
|
||||
source.append_file("src/parse.c")
|
||||
source.append_file("src/engine.c")
|
||||
source.append_file("src/cert.c")
|
||||
source.append_file("src/sec.c")
|
||||
source.append_file("src/socket_raw.c")
|
||||
source.append_file("src/secure_context.c")
|
||||
source.append_file("src/socket.c")
|
||||
source.append_file("src/socket_pool.c")
|
||||
source.append_file("src/thread.c")
|
||||
source.append_file("src/byte_queue.c")
|
||||
source.append_file("src/cert.c")
|
||||
source.append_file("src/client.c")
|
||||
source.append_file("src/parse.c")
|
||||
source.append_file("src/server.c")
|
||||
header.append_text(license)
|
||||
source.save("chttp.c")
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "basic.h"
|
||||
#endif
|
||||
|
||||
bool http_streq(HTTP_String s1, HTTP_String s2)
|
||||
{
|
||||
|
||||
-31
@@ -1,7 +1,3 @@
|
||||
#ifndef CHTTP_BASIC_INCLUDED
|
||||
#define CHTTP_BASIC_INCLUDED
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
// String type used throughout cHTTP.
|
||||
typedef struct {
|
||||
@@ -52,30 +48,3 @@ void print_bytes(HTTP_String prefix, HTTP_String src);
|
||||
|
||||
// TODO: comment
|
||||
#define HTTP_UNPACK(X) (X).len, (X).ptr
|
||||
|
||||
// Macro used to make invariants of the code more explicit.
|
||||
//
|
||||
// Say you have some function that operates on two integers
|
||||
// and that by design their sum is always 100. This macro is
|
||||
// useful to make that explicit:
|
||||
//
|
||||
// void func(int a, int b)
|
||||
// {
|
||||
// HTTP_ASSERT(a + b == 100);
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// Assertions are about documentation, *not* error management.
|
||||
//
|
||||
// In non-release builds (where NDEBUG is not defined) asserted
|
||||
// expressions are evaluated and if not true, the program is halted.
|
||||
// This is quite nice as they offer a way to document code in
|
||||
// a way that can be checked at runtime, unlike regular comments
|
||||
// like this one.
|
||||
#ifdef NDEBUG
|
||||
#define HTTP_ASSERT(X) ((void) 0)
|
||||
#else
|
||||
#define HTTP_ASSERT(X) {if (!(X)) { __builtin_trap(); }}
|
||||
#endif
|
||||
|
||||
#endif // CHTTP_BASIC_INCLUDED
|
||||
@@ -0,0 +1,258 @@
|
||||
|
||||
void byte_queue_init(ByteQueue *queue, uint32_t limit)
|
||||
{
|
||||
queue->flags = 0;
|
||||
queue->head = 0;
|
||||
queue->size = 0;
|
||||
queue->used = 0;
|
||||
queue->curs = 0;
|
||||
queue->limit = limit;
|
||||
queue->data = NULL;
|
||||
queue->read_target = NULL;
|
||||
}
|
||||
|
||||
// Deinitialize the queue
|
||||
void byte_queue_free(ByteQueue *queue)
|
||||
{
|
||||
if (queue->read_target) {
|
||||
if (queue->read_target != queue->data)
|
||||
free(queue->read_target);
|
||||
queue->read_target = NULL;
|
||||
queue->read_target_size = 0;
|
||||
}
|
||||
|
||||
free(queue->data);
|
||||
queue->data = NULL;
|
||||
}
|
||||
|
||||
int byte_queue_error(ByteQueue *queue)
|
||||
{
|
||||
return queue->flags & BYTE_QUEUE_ERROR;
|
||||
}
|
||||
|
||||
int byte_queue_empty(ByteQueue *queue)
|
||||
{
|
||||
return queue->used == 0;
|
||||
}
|
||||
|
||||
int byte_queue_full(ByteQueue *queue)
|
||||
{
|
||||
return queue->used == queue->limit;
|
||||
}
|
||||
|
||||
ByteView byte_queue_read_buf(ByteQueue *queue)
|
||||
{
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return (ByteView) {NULL, 0};
|
||||
|
||||
assert((queue->flags & BYTE_QUEUE_READ) == 0);
|
||||
queue->flags |= BYTE_QUEUE_READ;
|
||||
queue->read_target = queue->data;
|
||||
queue->read_target_size = queue->size;
|
||||
|
||||
if (queue->data == NULL)
|
||||
return (ByteView) {NULL, 0};
|
||||
|
||||
return (ByteView) { queue->data + queue->head, queue->used };
|
||||
}
|
||||
|
||||
void byte_queue_read_ack(ByteQueue *queue, uint32_t num)
|
||||
{
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return;
|
||||
|
||||
if ((queue->flags & BYTE_QUEUE_READ) == 0)
|
||||
return;
|
||||
|
||||
queue->flags &= ~BYTE_QUEUE_READ;
|
||||
|
||||
assert((uint32_t) num <= queue->used);
|
||||
queue->head += (uint32_t) num;
|
||||
queue->used -= (uint32_t) num;
|
||||
queue->curs += (uint32_t) num;
|
||||
|
||||
if (queue->read_target) {
|
||||
if (queue->read_target != queue->data)
|
||||
free(queue->read_target);
|
||||
queue->read_target = NULL;
|
||||
queue->read_target_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ByteView byte_queue_write_buf(ByteQueue *queue)
|
||||
{
|
||||
if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL)
|
||||
return (ByteView) {NULL, 0};
|
||||
|
||||
assert((queue->flags & BYTE_QUEUE_WRITE) == 0);
|
||||
queue->flags |= BYTE_QUEUE_WRITE;
|
||||
|
||||
return (ByteView) {
|
||||
queue->data + (queue->head + queue->used),
|
||||
queue->size - (queue->head + queue->used),
|
||||
};
|
||||
}
|
||||
|
||||
void byte_queue_write_ack(ByteQueue *queue, uint32_t num)
|
||||
{
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return;
|
||||
|
||||
if ((queue->flags & BYTE_QUEUE_WRITE) == 0)
|
||||
return;
|
||||
|
||||
queue->flags &= ~BYTE_QUEUE_WRITE;
|
||||
queue->used += num;
|
||||
}
|
||||
|
||||
int byte_queue_write_setmincap(ByteQueue *queue, uint32_t mincap)
|
||||
{
|
||||
// Sticky error
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return 0;
|
||||
|
||||
// In general, the queue's contents look like this:
|
||||
//
|
||||
// size
|
||||
// v
|
||||
// [___xxxxxxxxxxxx________]
|
||||
// ^ ^ ^
|
||||
// 0 head head + used
|
||||
//
|
||||
// This function needs to make sure that at least [mincap]
|
||||
// bytes are available on the right side of the content.
|
||||
//
|
||||
// We have 3 cases:
|
||||
//
|
||||
// 1) If there is enough memory already, this function doesn't
|
||||
// need to do anything.
|
||||
//
|
||||
// 2) If there isn't enough memory on the right but there is
|
||||
// enough free memory if we cound the left unused region,
|
||||
// then the content is moved back to the
|
||||
// start of the buffer.
|
||||
//
|
||||
// 3) If there isn't enough memory considering both sides, this
|
||||
// function needs to allocate a new buffer.
|
||||
//
|
||||
// If there are pending read or write operations, the application
|
||||
// is holding pointers to the buffer, so we need to make sure
|
||||
// to not invalidate them. The only real problem is pending reads
|
||||
// since this function can only be called before starting a write
|
||||
// opearation.
|
||||
//
|
||||
// To avoid invalidating the read pointer when we allocate a new
|
||||
// buffer, we don't free the old buffer. Instead, we store the
|
||||
// pointer in the "old" field so that the read ack function can
|
||||
// free it.
|
||||
//
|
||||
// To avoid invalidating the pointer when we are moving back the
|
||||
// content since there is enough memory at the start of the buffer,
|
||||
// we just avoid that. Even if there is enough memory considering
|
||||
// left and right free regions, we allocate a new buffer.
|
||||
|
||||
assert((queue->flags & BYTE_QUEUE_WRITE) == 0);
|
||||
|
||||
uint32_t total_free_space = queue->size - queue->used;
|
||||
uint32_t free_space_after_data = queue->size - queue->used - queue->head;
|
||||
|
||||
int moved = 0;
|
||||
if (free_space_after_data < mincap) {
|
||||
|
||||
if (total_free_space < mincap || (queue->read_target == queue->data)) {
|
||||
// Resize required
|
||||
|
||||
if (queue->used + mincap > queue->limit) {
|
||||
queue->flags |= BYTE_QUEUE_ERROR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t size;
|
||||
if (queue->size > UINT32_MAX / 2)
|
||||
size = UINT32_MAX;
|
||||
else
|
||||
size = 2 * queue->size;
|
||||
|
||||
if (size < queue->used + mincap)
|
||||
size = queue->used + mincap;
|
||||
|
||||
if (size > queue->limit)
|
||||
size = queue->limit;
|
||||
|
||||
uint8_t *data = malloc(size);
|
||||
if (!data) {
|
||||
queue->flags |= BYTE_QUEUE_ERROR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (queue->used > 0)
|
||||
memcpy(data, queue->data + queue->head, queue->used);
|
||||
|
||||
if (queue->read_target != queue->data)
|
||||
free(queue->data);
|
||||
|
||||
queue->data = data;
|
||||
queue->head = 0;
|
||||
queue->size = size;
|
||||
|
||||
} else {
|
||||
// Move required
|
||||
memmove(queue->data, queue->data + queue->head, queue->used);
|
||||
queue->head = 0;
|
||||
}
|
||||
|
||||
moved = 1;
|
||||
}
|
||||
|
||||
return moved;
|
||||
}
|
||||
|
||||
void byte_queue_write(ByteQueue *queue, void *ptr, uint32_t len)
|
||||
{
|
||||
byte_queue_write_setmincap(queue, len);
|
||||
ByteView dst = byte_queue_write_buf(queue);
|
||||
if (dst.ptr) {
|
||||
memcpy(dst.ptr, ptr, len);
|
||||
byte_queue_write_ack(queue, len);
|
||||
}
|
||||
}
|
||||
|
||||
ByteQueueOffset byte_queue_offset(ByteQueue *queue)
|
||||
{
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return (ByteQueueOffset) { 0 };
|
||||
return (ByteQueueOffset) { queue->curs + queue->used };
|
||||
}
|
||||
|
||||
void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off,
|
||||
void *src, uint32_t len)
|
||||
{
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return;
|
||||
|
||||
// Check that the offset is in range
|
||||
assert(off >= queue->curs && off - queue->curs < queue->used);
|
||||
|
||||
// Check that the length is in range
|
||||
assert(len <= queue->used - (off - queue->curs));
|
||||
|
||||
// Perform the patch
|
||||
uint8_t *dst = queue->data + queue->head + (off - queue->curs);
|
||||
memcpy(dst, src, len);
|
||||
}
|
||||
|
||||
uint32_t byte_queue_size_from_offset(ByteQueue *queue, ByteQueueOffset off)
|
||||
{
|
||||
return queue->curs + queue->used - off;
|
||||
}
|
||||
|
||||
void byte_queue_remove_from_offset(ByteQueue *queue, ByteQueueOffset offset)
|
||||
{
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return;
|
||||
|
||||
uint64_t num = (queue->curs + queue->used) - offset;
|
||||
assert(num <= queue->used);
|
||||
|
||||
queue->used -= num;
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// This is the implementation of a byte queue useful
|
||||
// for systems that need to process engs of bytes.
|
||||
//
|
||||
// It features sticky errors, a zero-copy interface,
|
||||
// and a safe mechanism to patch previously written
|
||||
// bytes.
|
||||
//
|
||||
// Only up to 4GB of data can be stored at once.
|
||||
|
||||
// Internal use only
|
||||
enum {
|
||||
BYTE_QUEUE_ERROR = 1 << 0,
|
||||
BYTE_QUEUE_READ = 1 << 1,
|
||||
BYTE_QUEUE_WRITE = 1 << 2,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t *ptr;
|
||||
size_t len;
|
||||
} ByteView;
|
||||
|
||||
// Fields are for internal use only
|
||||
typedef struct {
|
||||
uint64_t curs;
|
||||
uint8_t* data;
|
||||
uint32_t head;
|
||||
uint32_t size;
|
||||
uint32_t used;
|
||||
uint32_t limit;
|
||||
uint8_t* read_target;
|
||||
uint32_t read_target_size;
|
||||
int flags;
|
||||
} ByteQueue;
|
||||
|
||||
// Represents an offset inside the queue relative
|
||||
// to the first byte ever appended to the queue,
|
||||
// therefore consuming bytes from the queue does
|
||||
// not invalidate this type of offset.
|
||||
typedef uint64_t ByteQueueOffset;
|
||||
|
||||
// Initialize the queue with a given capacity limit.
|
||||
// This is just a soft limit. The queue will allocate
|
||||
// dynamically as needed up to this limit and won't
|
||||
// grow further. When the limit is reached, http_queue_full
|
||||
// returns true.
|
||||
void byte_queue_init(ByteQueue *queue, uint32_t limit);
|
||||
|
||||
// Free resources associated to this queue
|
||||
void byte_queue_free(ByteQueue *queue);
|
||||
|
||||
// Check whether an error occurred inside the queue
|
||||
int byte_queue_error(ByteQueue *queue);
|
||||
|
||||
// Returns 1 if the queue has no bytes inside it,
|
||||
// or 0 otherwise.
|
||||
int byte_queue_empty(ByteQueue *queue);
|
||||
|
||||
// Returns 1 if the queue reached its limit, or 0
|
||||
// otherwise.
|
||||
int byte_queue_full(ByteQueue *queue);
|
||||
|
||||
// These two functions are to be used together.
|
||||
// read_buf returns a view into the queue of the
|
||||
// bytes that can be read from it. The caller can
|
||||
// decide how many of those bytes can be removed
|
||||
// by passing the count to the read_ack function.
|
||||
// If an error occurred inside the queue, this
|
||||
// function returns an empty view.
|
||||
//
|
||||
// Note that the calls to read_buf and read_ack
|
||||
// may be far apart. Other operations won't interfere
|
||||
// with the read. The only rule is you can't call
|
||||
// read_buf multiple times before calling read_ack.
|
||||
ByteView byte_queue_read_buf(ByteQueue *queue);
|
||||
void byte_queue_read_ack(ByteQueue *queue, uint32_t num);
|
||||
|
||||
// Similar to the read_buf/read_ack functions,
|
||||
// but write_buf returns a view of the unused
|
||||
// memory inside the queue, and write_ack is
|
||||
// used to tell the queue how many bytes were
|
||||
// written into it. Note that to ensure there
|
||||
// is a minimum amount of free space in the queue,
|
||||
// the user needs to call byte_queue_setmincap.
|
||||
// If an error occurred inside the queue, this
|
||||
// function returns an empty view.
|
||||
//
|
||||
// Note that the calls to write_buf and write_ack
|
||||
// may be far apart. Other operations won't interfere
|
||||
// with the write (except for other byte_queue_write_*
|
||||
// functions). The only rule is you can't call
|
||||
// write_buf multiple times before calling write_ack.
|
||||
ByteView byte_queue_write_buf(ByteQueue *queue);
|
||||
void byte_queue_write_ack(ByteQueue *queue, uint32_t num);
|
||||
|
||||
// Sets the minimum capacity for the next write
|
||||
// operation and returns 1 if the content of the
|
||||
// queue was moved, else 0 is returned.
|
||||
//
|
||||
// You must not call this function while a write
|
||||
// is pending. In other words, you must do this:
|
||||
//
|
||||
// byte_queue_write_setmincap(queue, mincap);
|
||||
// dst = byte_queue_write_buf(queue, &cap);
|
||||
// ...
|
||||
// byte_queue_write_ack(num);
|
||||
//
|
||||
// And NOT this:
|
||||
//
|
||||
// dst = byte_queue_write_buf(queue);
|
||||
// byte_queue_write_setmincap(queue, mincap); <-- BAD
|
||||
// ...
|
||||
// byte_queue_write_ack(num);
|
||||
//
|
||||
int byte_queue_write_setmincap(ByteQueue *queue, uint32_t mincap);
|
||||
|
||||
// Write some bytes to the queue. This is a
|
||||
// short hand for write_buf/memcpy/write_ack
|
||||
void byte_queue_write(ByteQueue *queue, void *ptr, uint32_t len);
|
||||
|
||||
// Returns the current offset inside the queue
|
||||
ByteQueueOffset byte_queue_offset(ByteQueue *queue);
|
||||
|
||||
// Writes some bytes at the specified offset. It's
|
||||
// the responsibility of the user to make sure that
|
||||
// the offset still refers to content inside the queue.
|
||||
void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off, void *src, uint32_t len);
|
||||
|
||||
// Returns the number of bytes from the given offset
|
||||
// to the end of the queue.
|
||||
uint32_t byte_queue_size_from_offset(ByteQueue *queue, ByteQueueOffset off);
|
||||
|
||||
// Removes all bytes from the given offset to the the
|
||||
// end of the queue.
|
||||
void byte_queue_remove_from_offset(ByteQueue *queue, ByteQueueOffset offset);
|
||||
-16
@@ -1,19 +1,3 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef HTTPS_ENABLED
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/bn.h>
|
||||
#endif
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "cert.h"
|
||||
#endif
|
||||
|
||||
#ifdef HTTPS_ENABLED
|
||||
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
#ifndef CERT_INCLUDED
|
||||
#define CERT_INCLUDED
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "basic.h"
|
||||
#endif
|
||||
|
||||
// This is an utility to create self-signed certificates
|
||||
// useful when testing HTTPS servers locally. This is only
|
||||
// meant to be used by people starting out with a library
|
||||
@@ -21,5 +14,3 @@
|
||||
// key file with the key used to sign the certificate.
|
||||
int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN,
|
||||
HTTP_String cert_file, HTTP_String key_file);
|
||||
|
||||
#endif // CERT_INCLUDED
|
||||
+65
-406
@@ -1,453 +1,112 @@
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <winsock2.h>
|
||||
#define POLL WSAPoll
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <poll.h>
|
||||
#define POLL poll
|
||||
#endif
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "client.h"
|
||||
#include "engine.h"
|
||||
#include "socket_pool.h"
|
||||
#endif
|
||||
|
||||
#define CLIENT_MAX_CONNS 256
|
||||
|
||||
typedef enum {
|
||||
CLIENT_CONNECTION_FREE,
|
||||
CLIENT_CONNECTION_INIT,
|
||||
CLIENT_CONNECTION_INIT_ERROR,
|
||||
CLIENT_CONNECTION_WAIT,
|
||||
CLIENT_CONNECTION_DONE,
|
||||
} ClientConnectionState;
|
||||
|
||||
typedef struct {
|
||||
ClientConnectionState state;
|
||||
uint16_t gen;
|
||||
SocketHandle sock;
|
||||
HTTP_Engine eng;
|
||||
bool trace;
|
||||
void* user_data;
|
||||
} ClientConnection;
|
||||
|
||||
struct HTTP_Client {
|
||||
|
||||
SocketPool *socket_pool;
|
||||
|
||||
int num_conns;
|
||||
ClientConnection conns[CLIENT_MAX_CONNS];
|
||||
|
||||
int ready_head;
|
||||
int ready_count;
|
||||
int ready[CLIENT_MAX_CONNS];
|
||||
};
|
||||
|
||||
int http_global_init(void)
|
||||
int http_client_init(HTTP_Client *client)
|
||||
{
|
||||
int ret = socket_pool_global_init();
|
||||
if (ret < 0)
|
||||
client->num_conns = 0;
|
||||
for (int i = 0; i < HTTP_CLIENT_CAPACITY; i++)
|
||||
client->conns[i].state = HTTP_CLIENT_CONN_FREE;
|
||||
|
||||
client->num_ready = 0;
|
||||
client->ready_head = 0;
|
||||
|
||||
if (socket_manager_init(&client->sockets,
|
||||
client->socket_pool, HTTP_CLIENT_CAPACITY) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void http_global_free(void)
|
||||
{
|
||||
socket_pool_global_free();
|
||||
}
|
||||
|
||||
// Rename the memory function
|
||||
static void* client_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) {
|
||||
(void)data;
|
||||
switch (tag) {
|
||||
case HTTP_MEMFUNC_MALLOC:
|
||||
return malloc(len);
|
||||
case HTTP_MEMFUNC_FREE:
|
||||
free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
HTTP_Client *http_client_init(void)
|
||||
{
|
||||
HTTP_Client *client = malloc(sizeof(HTTP_Client));
|
||||
if (client == NULL)
|
||||
return NULL;
|
||||
|
||||
int max_socks = 100;
|
||||
SocketPool *socket_pool = socket_pool_init(HTTP_STR(""), 0, 0, max_socks, false, 0, HTTP_STR(""), HTTP_STR(""));
|
||||
if (socket_pool == NULL) {
|
||||
free(client);
|
||||
return NULL;
|
||||
}
|
||||
client->socket_pool = socket_pool;
|
||||
|
||||
for (int i = 0; i < CLIENT_MAX_CONNS; i++) {
|
||||
client->conns[i].state = CLIENT_CONNECTION_FREE;
|
||||
client->conns[i].gen = 1;
|
||||
}
|
||||
|
||||
client->num_conns = 0;
|
||||
client->ready_head = 0;
|
||||
client->ready_count = 0;
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
void http_client_free(HTTP_Client *client)
|
||||
{
|
||||
for (int i = 0, j = 0; j < client->num_conns; i++) {
|
||||
socket_manager_free(&client->sockets);
|
||||
|
||||
if (client->conns[i].state == CLIENT_CONNECTION_FREE)
|
||||
for (int i = 0, j = 0; j < client->num_conns; i++) {
|
||||
HTTP_ClientConn *conn = &client->conns[i];
|
||||
if (conn->state == HTTP_CLIENT_CONN_FREE)
|
||||
continue;
|
||||
j++;
|
||||
|
||||
// TODO
|
||||
http_client_conn_free(conn);
|
||||
}
|
||||
}
|
||||
|
||||
socket_pool_free(client->socket_pool);
|
||||
free(client);
|
||||
int http_client_wakeup(HTTP_Client *client)
|
||||
{
|
||||
if (socket_manager_wakeup(&client->sockets) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder)
|
||||
{
|
||||
if (client->num_conns == CLIENT_MAX_CONNS)
|
||||
return -1;
|
||||
|
||||
int i = 0;
|
||||
while (client->conns[i].state != CLIENT_CONNECTION_FREE)
|
||||
i++;
|
||||
|
||||
client->conns[i].sock = -1;
|
||||
client->conns[i].user_data = NULL;
|
||||
client->conns[i].trace = false;
|
||||
client->conns[i].state = CLIENT_CONNECTION_INIT;
|
||||
http_engine_init(&client->conns[i].eng, 1, client_memfunc, NULL);
|
||||
|
||||
client->num_conns++;
|
||||
|
||||
*builder = (HTTP_RequestBuilder) { client, i, client->conns[i].gen };
|
||||
return 0;
|
||||
// TODO
|
||||
}
|
||||
|
||||
int http_client_wait(HTTP_Client *client, HTTP_Response **result, void **user_data)
|
||||
void http_request_builder_line(HTTP_RequestBuilder builder, xxxx)
|
||||
{
|
||||
while (client->ready_count == 0) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
SocketEvent event = socket_pool_wait(client->socket_pool);
|
||||
switch (event.type) {
|
||||
|
||||
case SOCKET_EVENT_DIED:
|
||||
void http_request_builder_header(HTTP_RequestBuilder builder, String str)
|
||||
{
|
||||
ClientConnection *conn = event.user_data;
|
||||
conn->state = CLIENT_CONNECTION_DONE;
|
||||
|
||||
int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS;
|
||||
client->ready[tail] = conn - client->conns;
|
||||
client->ready_count++;
|
||||
// TODO
|
||||
}
|
||||
break;
|
||||
|
||||
case SOCKET_EVENT_READY:
|
||||
void http_request_builder_body(HTTP_RequestBuilder builder, String str)
|
||||
{
|
||||
ClientConnection *conn = event.user_data;
|
||||
|
||||
if (conn->sock == -1)
|
||||
conn->sock = event.handle;
|
||||
|
||||
HTTP_EngineState engine_state;
|
||||
engine_state = http_engine_state(&conn->eng);
|
||||
|
||||
if (engine_state == HTTP_ENGINE_STATE_CLIENT_RECV_BUF) {
|
||||
int len;
|
||||
char *buf;
|
||||
buf = http_engine_recvbuf(&conn->eng, &len);
|
||||
if (buf) {
|
||||
int ret = socket_pool_read(client->socket_pool, conn->sock, buf, len);
|
||||
if (conn->trace)
|
||||
print_bytes(HTTP_STR(">> "), (HTTP_String) { buf, ret });
|
||||
http_engine_recvack(&conn->eng, ret);
|
||||
}
|
||||
} else if (engine_state == HTTP_ENGINE_STATE_CLIENT_SEND_BUF) {
|
||||
int len;
|
||||
char *buf;
|
||||
buf = http_engine_sendbuf(&conn->eng, &len);
|
||||
if (buf) {
|
||||
int ret = socket_pool_write(client->socket_pool, conn->sock, buf, len);
|
||||
if (conn->trace)
|
||||
print_bytes(HTTP_STR("<< "), (HTTP_String) { buf, ret });
|
||||
http_engine_sendack(&conn->eng, ret);
|
||||
}
|
||||
// TODO
|
||||
}
|
||||
|
||||
engine_state = http_engine_state(&conn->eng);
|
||||
|
||||
if (engine_state == HTTP_ENGINE_STATE_CLIENT_CLOSED ||
|
||||
engine_state == HTTP_ENGINE_STATE_CLIENT_READY)
|
||||
socket_pool_close(client->socket_pool, conn->sock);
|
||||
}
|
||||
break;
|
||||
|
||||
case SOCKET_EVENT_ERROR:
|
||||
return -1;
|
||||
|
||||
case SOCKET_EVENT_SIGNAL:
|
||||
return 1;
|
||||
}
|
||||
int http_request_builder_send(HTTP_RequestBuilder builder)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
int index = client->ready[client->ready_head];
|
||||
client->ready_head = (client->ready_head + 1) % CLIENT_MAX_CONNS;
|
||||
client->ready_count--;
|
||||
int http_client_register_events(HTTP_Client *client,
|
||||
struct pollfd *polled, int max_polled)
|
||||
{
|
||||
return socket_manager_register_events(
|
||||
&client->sockets, polled, max_polled);
|
||||
}
|
||||
|
||||
ClientConnection *conn = &client->conns[index];
|
||||
int http_client_process_events(HTTP_Client *client,
|
||||
struct pollfd *polled, int num_polled)
|
||||
{
|
||||
SocketEvent events[HTTP_CLIENT_CAPACITY];
|
||||
int num_events = socket_manager_translate_events(
|
||||
&client->sockets, polled, num_polled);
|
||||
|
||||
HTTP_Response *result2 = http_engine_getres(&conn->eng);
|
||||
for (int i = 0; i < num_events; i++) {
|
||||
|
||||
if (result)
|
||||
*result = result2;
|
||||
if (events[i].type == SOCKET_EVENT_DISCONNECT) {
|
||||
|
||||
if (user_data)
|
||||
*user_data = conn->user_data;
|
||||
// TODO
|
||||
|
||||
if (result2 == NULL) {
|
||||
http_engine_free(&conn->eng);
|
||||
conn->state = CLIENT_CONNECTION_FREE;
|
||||
client->num_conns--;
|
||||
} else {
|
||||
result2->context = client;
|
||||
} else if (events[i].type == SOCKET_EVENT_READY) {
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ClientConnection *client_builder_to_conn(HTTP_RequestBuilder handle)
|
||||
bool http_client_next_response(HTTP_Client *client,
|
||||
HTTP_Response **response);
|
||||
{
|
||||
if (handle.data0 == NULL)
|
||||
return NULL;
|
||||
if (server->num_ready == 0)
|
||||
return false;
|
||||
|
||||
HTTP_Client *client = handle.data0;
|
||||
HTTP_ClientConn *conn = &client->conns[client->ready_head];
|
||||
client->ready_head = (client->ready_head + 1) % HTTP_CLIENT_CAPACITY;
|
||||
client->num_ready--;
|
||||
|
||||
if (handle.data1 >= CLIENT_MAX_CONNS)
|
||||
return NULL;
|
||||
|
||||
ClientConnection *conn = &client->conns[handle.data1];
|
||||
|
||||
if (handle.data2 != conn->gen)
|
||||
return NULL;
|
||||
|
||||
return conn;
|
||||
assert(conn->state == HTTP_CLIENT_CONN_COMPLETE);
|
||||
*response = &conn->response;
|
||||
return true;
|
||||
}
|
||||
|
||||
void http_request_builder_user_data(HTTP_RequestBuilder builder, void *user_data)
|
||||
void http_free_response(HTTP_Response *res)
|
||||
{
|
||||
ClientConnection *conn = client_builder_to_conn(builder);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
if (conn->state != CLIENT_CONNECTION_INIT)
|
||||
return;
|
||||
|
||||
conn->user_data = user_data;
|
||||
}
|
||||
|
||||
void http_request_builder_trace(HTTP_RequestBuilder builder, bool trace)
|
||||
{
|
||||
ClientConnection *conn = client_builder_to_conn(builder);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
if (conn->state != CLIENT_CONNECTION_INIT)
|
||||
return;
|
||||
|
||||
conn->trace = trace;
|
||||
}
|
||||
|
||||
void http_request_builder_line(HTTP_RequestBuilder builder, HTTP_Method method, HTTP_String url)
|
||||
{
|
||||
ClientConnection *conn = client_builder_to_conn(builder);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
if (conn->state != CLIENT_CONNECTION_INIT)
|
||||
return;
|
||||
|
||||
HTTP_Client *client = builder.data0;
|
||||
|
||||
HTTP_URL parsed_url;
|
||||
int ret = http_parse_url(url.ptr, url.len, &parsed_url);
|
||||
if (ret != url.len) {
|
||||
conn->state = CLIENT_CONNECTION_INIT_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
bool secure = false;
|
||||
if (http_streq(parsed_url.scheme, HTTP_STR("https"))) {
|
||||
secure = true;
|
||||
} else if (!http_streq(parsed_url.scheme, HTTP_STR("http"))) {
|
||||
conn->state = CLIENT_CONNECTION_INIT_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
int port = parsed_url.authority.port;
|
||||
if (port == 0) {
|
||||
if (secure)
|
||||
port = 443;
|
||||
else
|
||||
port = 80;
|
||||
}
|
||||
|
||||
switch (parsed_url.authority.host.mode) {
|
||||
case HTTP_HOST_MODE_IPV4: ret = socket_pool_connect_ipv4(client->socket_pool, secure, parsed_url.authority.host.ipv4, port, conn); break;
|
||||
case HTTP_HOST_MODE_IPV6: ret = socket_pool_connect_ipv6(client->socket_pool, secure, parsed_url.authority.host.ipv6, port, conn); break;
|
||||
case HTTP_HOST_MODE_NAME: ret = socket_pool_connect (client->socket_pool, secure, parsed_url.authority.host.name, port, conn); break;
|
||||
case HTTP_HOST_MODE_VOID: ret = -1; return;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
conn->state = CLIENT_CONNECTION_INIT_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
http_engine_url(&conn->eng, method, url, 1);
|
||||
}
|
||||
|
||||
void http_request_builder_header(HTTP_RequestBuilder handle, HTTP_String str)
|
||||
{
|
||||
ClientConnection *conn = client_builder_to_conn(handle);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
if (conn->state != CLIENT_CONNECTION_INIT)
|
||||
return;
|
||||
|
||||
http_engine_header(&conn->eng, str);
|
||||
}
|
||||
|
||||
void http_request_builder_body(HTTP_RequestBuilder handle, HTTP_String str)
|
||||
{
|
||||
ClientConnection *conn = client_builder_to_conn(handle);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
if (conn->state != CLIENT_CONNECTION_INIT)
|
||||
return;
|
||||
|
||||
http_engine_body(&conn->eng, str);
|
||||
}
|
||||
|
||||
void http_request_builder_submit(HTTP_RequestBuilder handle)
|
||||
{
|
||||
HTTP_Client *client = handle.data0;
|
||||
ClientConnection *conn = client_builder_to_conn(handle);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
if (conn->state != CLIENT_CONNECTION_INIT &&
|
||||
conn->state != CLIENT_CONNECTION_INIT_ERROR)
|
||||
return;
|
||||
|
||||
// TODO: invalidate the handle
|
||||
|
||||
if (conn->state == CLIENT_CONNECTION_INIT_ERROR) {
|
||||
|
||||
conn->state = CLIENT_CONNECTION_DONE;
|
||||
|
||||
int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS;
|
||||
client->ready[tail] = conn - client->conns;
|
||||
client->ready_count++;
|
||||
|
||||
} else {
|
||||
http_engine_done(&conn->eng);
|
||||
conn->state = CLIENT_CONNECTION_WAIT;
|
||||
}
|
||||
}
|
||||
|
||||
void http_response_free(HTTP_Response *res)
|
||||
{
|
||||
if (res == NULL)
|
||||
return;
|
||||
|
||||
HTTP_Client *client = res->context;
|
||||
|
||||
ClientConnection *conn = NULL;
|
||||
for (int i = 0, j = 0; j < client->num_conns; i++) {
|
||||
|
||||
if (client->conns[i].state == CLIENT_CONNECTION_FREE)
|
||||
continue;
|
||||
j++;
|
||||
|
||||
if (client->conns[i].state != CLIENT_CONNECTION_DONE)
|
||||
continue;
|
||||
|
||||
if (http_engine_getres(&client->conns[i].eng) == res) {
|
||||
conn = &client->conns[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
HTTP_ASSERT(conn);
|
||||
|
||||
http_engine_free(&conn->eng);
|
||||
conn->state = CLIENT_CONNECTION_FREE;
|
||||
client->num_conns--;
|
||||
}
|
||||
|
||||
static HTTP_Client *default_client___; // TODO: deinitialize the default client when http_global_free is called
|
||||
|
||||
static HTTP_Client *get_default_client(void)
|
||||
{
|
||||
if (default_client___ == NULL)
|
||||
default_client___ = http_client_init();
|
||||
return default_client___;
|
||||
}
|
||||
|
||||
HTTP_Response *http_get(HTTP_String url, HTTP_String *headers, int num_headers)
|
||||
{
|
||||
HTTP_Client *client = get_default_client();
|
||||
if (client == NULL)
|
||||
return NULL;
|
||||
|
||||
HTTP_RequestBuilder builder;
|
||||
int ret = http_client_get_builder(client, &builder);
|
||||
if (ret < 0)
|
||||
return NULL;
|
||||
http_request_builder_line(builder, HTTP_METHOD_GET, url);
|
||||
for (int i = 0; i < num_headers; i++)
|
||||
http_request_builder_header(builder, headers[i]);
|
||||
http_request_builder_submit(builder);
|
||||
|
||||
HTTP_Response *res;
|
||||
ret = http_client_wait(client, &res, NULL); // TODO: it's assumed there is only one request pending
|
||||
if (ret < 0)
|
||||
return NULL;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
HTTP_Response *http_post(HTTP_String url, HTTP_String *headers, int num_headers, HTTP_String body)
|
||||
{
|
||||
HTTP_Client *client = get_default_client();
|
||||
if (client == NULL)
|
||||
return NULL;
|
||||
|
||||
HTTP_RequestBuilder builder;
|
||||
int ret = http_client_get_builder(client, &builder);
|
||||
if (ret < 0)
|
||||
return NULL;
|
||||
http_request_builder_line(builder, HTTP_METHOD_POST, url);
|
||||
for (int i = 0; i < num_headers; i++)
|
||||
http_request_builder_header(builder, headers[i]);
|
||||
http_request_builder_body(builder, body);
|
||||
http_request_builder_submit(builder);
|
||||
|
||||
HTTP_Response *res;
|
||||
ret = http_client_wait(client, &res, NULL); // TODO: it's assumed there is only one request pending
|
||||
if (ret < 0)
|
||||
return NULL;
|
||||
|
||||
return res;
|
||||
// TODO
|
||||
}
|
||||
+99
-79
@@ -1,98 +1,118 @@
|
||||
#ifndef CLIENT_INCLUDED
|
||||
#define CLIENT_INCLUDED
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "parse.h"
|
||||
#ifndef HTTP_CLIENT_CAPACITY
|
||||
// The maximum ammount of requests that can be performed
|
||||
// in parallel.
|
||||
#define HTTP_CLIENT_CAPACITY (1<<7)
|
||||
#endif
|
||||
|
||||
// Initialize the global state of cHTTP.
|
||||
//
|
||||
// cHTTP tries to avoid global state. What this function
|
||||
// does is call the global initialization functions of
|
||||
// its dependencies (OpenSSL and Winsock)
|
||||
int http_global_init(void);
|
||||
typedef enum {
|
||||
HTTP_CLIENT_CONN_FREE,
|
||||
HTTP_CLIENT_CONN_WAIT_LINE,
|
||||
HTTP_CLIENT_CONN_WAIT_HEADER,
|
||||
HTTP_CLIENT_CONN_WAIT_BODY,
|
||||
HTTP_CLIENT_CONN_FLUSHING,
|
||||
HTTP_CLIENT_CONN_BUFFERING,
|
||||
HTTP_CLIENT_CONN_COMPLETE,
|
||||
} HTTP_ClientConnState;
|
||||
|
||||
// Free the global state of cHTTP.
|
||||
void http_global_free(void);
|
||||
|
||||
// Opaque type describing an "HTTP client". Any request
|
||||
// that is started must always be associated to an HTTP
|
||||
// client object.
|
||||
typedef struct HTTP_Client HTTP_Client;
|
||||
|
||||
// Handle for a pending request. This should be considered
|
||||
// opaque. Don't read or modify its fields!
|
||||
// Fields of this struct are private
|
||||
typedef struct {
|
||||
void *data0;
|
||||
int data1;
|
||||
int data2;
|
||||
} HTTP_RequestBuilder;
|
||||
HTTP_ClientConnState state;
|
||||
ByteQueue input;
|
||||
ByteQueue output;
|
||||
} HTTP_ClientConn;
|
||||
|
||||
// Initialize a client object. If something goes wrong,
|
||||
// NULL is returned.
|
||||
HTTP_Client *http_client_init(void);
|
||||
// Fields of this struct are private
|
||||
typedef struct {
|
||||
|
||||
// Deinitialize a client object
|
||||
// Array of connections. The counter contains the
|
||||
// number of structs such that state=FREE.
|
||||
int num_conns;
|
||||
HTTP_ClientConn conns[HTTP_CLIENT_CAPACITY];
|
||||
|
||||
// Queue of indices referring to connections that
|
||||
// are in the COMPLETE state.
|
||||
int num_ready;
|
||||
int ready_head;
|
||||
int ready[HTTP_CLIENT_CAPACITY];
|
||||
|
||||
// Asynchronous TCP and TLS socket abstraction
|
||||
SocketManager sockets;
|
||||
|
||||
// The server object doesn't interact with this
|
||||
// field directly, it just initializes the socket
|
||||
// manager with a pointer to it. This allows
|
||||
// allocating the exact number of sockets we
|
||||
// will need.
|
||||
Socket socket_pool[HTTP_CLIENT_CAPACITY];
|
||||
|
||||
} HTTP_Client;
|
||||
|
||||
// Initialize an HTTP client object. This allows one to
|
||||
// perform a number of requests in parallel.
|
||||
int http_client_init(HTTP_Client *client);
|
||||
|
||||
// Release resources associated to a client object.
|
||||
void http_client_free(HTTP_Client *client);
|
||||
|
||||
// Create a request object associated to the given client.
|
||||
// On success, 0 is returned and the handle is initialized.
|
||||
// On error, -1 is returned.
|
||||
int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder);
|
||||
// When a thread is blocked waiting for client events,
|
||||
// other threads can call this function to wake it up.
|
||||
int http_client_wakeup(HTTP_Client *client);
|
||||
|
||||
void http_request_builder_user_data(HTTP_RequestBuilder builder, void *user_data);
|
||||
typedef struct {
|
||||
HTTP_Client *client;
|
||||
uint16_t index;
|
||||
uint16_t gen;
|
||||
} HTTP_RequestBuilder;
|
||||
|
||||
// Enable/disable I/O tracing for the specified request.
|
||||
// This must be done when the request is in the initialization
|
||||
// phase.
|
||||
void http_request_builder_trace(HTTP_RequestBuilder builder, bool trace);
|
||||
// Create a new request builder object. If the response
|
||||
// pointer is NULL, a brand new builder is created. If
|
||||
// response isn't NULL (and http_free_response wasn't
|
||||
// called on it yet), the connection associated to that
|
||||
// previous exchange is reused. Note that it's up to the
|
||||
// user to make sure the requests are targeting the same
|
||||
// host. Returns 0 on success, -1 on error.
|
||||
int http_client_get_builder(HTTP_Client *client,
|
||||
HTTP_Response *response, HTTP_RequestBuilder *builder);
|
||||
|
||||
// Set the method and URL of the specified request object.
|
||||
// This must be the first thing you do after http_client_request
|
||||
// is called (you may http_request_trace before, but nothing
|
||||
// else!)
|
||||
void http_request_builder_line(HTTP_RequestBuilder builder, HTTP_Method method, HTTP_String url);
|
||||
// Set the URL of the current request. This is the first
|
||||
// function of the request builder that the user must call.
|
||||
void http_request_builder_url(HTTP_RequestBuilder builder, String url);
|
||||
|
||||
// Append a header to the specified request. You must call
|
||||
// this after http_request_line and may do so multiple times.
|
||||
void http_request_builder_header(HTTP_RequestBuilder builder, HTTP_String str);
|
||||
// After the URL, the user may set zero or more headers.
|
||||
void http_request_builder_header(HTTP_RequestBuilder builder, String str);
|
||||
|
||||
// Append some data to the request's body. You must call
|
||||
// this after either http_request_line or http_request_header.
|
||||
void http_request_builder_body(HTTP_RequestBuilder builder, HTTP_String str);
|
||||
// Append bytes to the request's body. You can call this
|
||||
// any amount of times, as long as it's after having set
|
||||
// the URL.
|
||||
void http_request_builder_body(HTTP_RequestBuilder builder, String str);
|
||||
|
||||
// Mark the initialization of the request as completed and
|
||||
// perform the request.
|
||||
void http_request_builder_submit(HTTP_RequestBuilder builder);
|
||||
// Mark this request as complete. This invalidates the
|
||||
// builder.
|
||||
void http_request_builder_send(HTTP_RequestBuilder builder);
|
||||
|
||||
// Free resources associated to a request. This must be called
|
||||
// after the request has completed.
|
||||
//
|
||||
// TODO: allow aborting pending requests
|
||||
void http_response_free(HTTP_Response *res);
|
||||
// List all low-level socket events the client is
|
||||
// waiting for such that the caller can call poll()
|
||||
// with it.
|
||||
int http_client_register_events(HTTP_Client *client,
|
||||
struct pollfd *polled, int max_polled);
|
||||
|
||||
// Wait for the completion of one request associated to
|
||||
// the client. The handle of the resolved request is returned
|
||||
// through the handle output parameter. If you're not
|
||||
// interested in which request completed (like when you
|
||||
// have only one pending request), you can pass NULL.
|
||||
//
|
||||
// On error -1 is retutned, else 0 is returned and the
|
||||
// handle is initialized.
|
||||
//
|
||||
// Note that calling this function when no requests are
|
||||
// pending is considered an error.
|
||||
int http_client_wait(HTTP_Client *client, HTTP_Response **res, void **user_data);
|
||||
// The caller has waited for poll() to return and some
|
||||
// I/O events to be triggered, so now the HTTP client
|
||||
// can continue its buffering and flushing operations.
|
||||
int http_client_process_events(HTTP_Client *client,
|
||||
struct pollfd *polled, int num_polled);
|
||||
|
||||
// TODO: comment
|
||||
HTTP_Response *http_get(HTTP_String url,
|
||||
HTTP_String *headers, int num_headers);
|
||||
// After some I/O events were processes, some responses
|
||||
// may be availabe. This function returns one of the
|
||||
// buffered responses. If a request was available, true
|
||||
// is returned. If no more are avaiable, false is returned.
|
||||
// The returned response must either be freed using the
|
||||
// http_free_response function or reused by passing it
|
||||
// to http_client_get_builder.
|
||||
bool http_client_next_response(HTTP_Client *client,
|
||||
HTTP_Response **response);
|
||||
|
||||
// TODO: comment
|
||||
HTTP_Response *http_post(HTTP_String url,
|
||||
HTTP_String *headers, int num_headers,
|
||||
HTTP_String body);
|
||||
|
||||
#endif // CLIENT_INCLUDED
|
||||
// Free a response object. You can't access its fields
|
||||
// again after this.
|
||||
void http_free_response(HTTP_Response *res);
|
||||
|
||||
-988
@@ -1,988 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <assert.h> // TODO: remove some of these headers
|
||||
#include <stddef.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "basic.h"
|
||||
#include "engine.h"
|
||||
#endif
|
||||
|
||||
// This is the implementation of a byte queue useful
|
||||
// for systems that need to process engs of bytes.
|
||||
//
|
||||
// It features sticky errors, a zero-copy interface,
|
||||
// and a safe mechanism to patch previously written
|
||||
// bytes.
|
||||
//
|
||||
// Only up to 4GB of data can be stored at once.
|
||||
|
||||
enum {
|
||||
BYTE_QUEUE_ERROR = 1 << 0,
|
||||
BYTE_QUEUE_READ = 1 << 1,
|
||||
BYTE_QUEUE_WRITE = 1 << 2,
|
||||
};
|
||||
|
||||
static void*
|
||||
callback_malloc(HTTP_ByteQueue *queue, int len)
|
||||
{
|
||||
return queue->memfunc(HTTP_MEMFUNC_MALLOC, NULL, len, queue->memfuncdata);
|
||||
}
|
||||
|
||||
static void
|
||||
callback_free(HTTP_ByteQueue *queue, void *ptr, int len)
|
||||
{
|
||||
queue->memfunc(HTTP_MEMFUNC_FREE, ptr, len, queue->memfuncdata);
|
||||
}
|
||||
|
||||
// Initialize the queue
|
||||
static void
|
||||
byte_queue_init(HTTP_ByteQueue *queue, unsigned int limit, HTTP_MemoryFunc memfunc, void *memfuncdata)
|
||||
{
|
||||
queue->flags = 0;
|
||||
queue->head = 0;
|
||||
queue->size = 0;
|
||||
queue->used = 0;
|
||||
queue->curs = 0;
|
||||
queue->limit = limit;
|
||||
queue->data = NULL;
|
||||
queue->read_target = NULL;
|
||||
queue->memfunc = memfunc;
|
||||
queue->memfuncdata = memfuncdata;
|
||||
}
|
||||
|
||||
// Deinitialize the queue
|
||||
static void
|
||||
byte_queue_free(HTTP_ByteQueue *queue)
|
||||
{
|
||||
if (queue->read_target) {
|
||||
if (queue->read_target != queue->data)
|
||||
callback_free(queue, queue->read_target, queue->read_target_size);
|
||||
queue->read_target = NULL;
|
||||
queue->read_target_size = 0;
|
||||
}
|
||||
|
||||
callback_free(queue, queue->data, queue->size);
|
||||
queue->data = NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
byte_queue_error(HTTP_ByteQueue *queue)
|
||||
{
|
||||
return queue->flags & BYTE_QUEUE_ERROR;
|
||||
}
|
||||
|
||||
static int
|
||||
byte_queue_empty(HTTP_ByteQueue *queue)
|
||||
{
|
||||
return queue->used == 0;
|
||||
}
|
||||
|
||||
// Start a read operation on the queue.
|
||||
//
|
||||
// This function returnes the pointer to the memory region containing the bytes
|
||||
// to read. Callers can't read more than [*len] bytes from it. To complete the
|
||||
// read, the [byte_queue_read_ack] function must be called with the number of
|
||||
// bytes that were acknowledged by the caller.
|
||||
//
|
||||
// Note:
|
||||
// - You can't have more than one pending read.
|
||||
static char*
|
||||
byte_queue_read_buf(HTTP_ByteQueue *queue, int *len)
|
||||
{
|
||||
if (queue->flags & BYTE_QUEUE_ERROR) {
|
||||
*len = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
HTTP_ASSERT((queue->flags & BYTE_QUEUE_READ) == 0);
|
||||
queue->flags |= BYTE_QUEUE_READ;
|
||||
queue->read_target = queue->data;
|
||||
queue->read_target_size = queue->size;
|
||||
|
||||
*len = queue->used;
|
||||
if (queue->data == NULL)
|
||||
return NULL;
|
||||
return queue->data + queue->head;
|
||||
}
|
||||
|
||||
// Complete a previously started operation on the queue.
|
||||
static void
|
||||
byte_queue_read_ack(HTTP_ByteQueue *queue, int num)
|
||||
{
|
||||
HTTP_ASSERT(num >= 0);
|
||||
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return;
|
||||
|
||||
if ((queue->flags & BYTE_QUEUE_READ) == 0)
|
||||
return;
|
||||
|
||||
queue->flags &= ~BYTE_QUEUE_READ;
|
||||
|
||||
HTTP_ASSERT((unsigned int) num <= queue->used);
|
||||
queue->head += (unsigned int) num;
|
||||
queue->used -= (unsigned int) num;
|
||||
queue->curs += (unsigned int) num;
|
||||
|
||||
if (queue->read_target) {
|
||||
if (queue->read_target != queue->data)
|
||||
callback_free(queue, queue->read_target, queue->read_target_size);
|
||||
queue->read_target = NULL;
|
||||
queue->read_target_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static char*
|
||||
byte_queue_write_buf(HTTP_ByteQueue *queue, int *cap)
|
||||
{
|
||||
if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL) {
|
||||
*cap = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
HTTP_ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0);
|
||||
queue->flags |= BYTE_QUEUE_WRITE;
|
||||
|
||||
unsigned int ucap = queue->size - (queue->head + queue->used);
|
||||
if (ucap > INT_MAX) ucap = INT_MAX;
|
||||
|
||||
*cap = (int) ucap;
|
||||
return queue->data + (queue->head + queue->used);
|
||||
}
|
||||
|
||||
static void
|
||||
byte_queue_write_ack(HTTP_ByteQueue *queue, int num)
|
||||
{
|
||||
HTTP_ASSERT(num >= 0);
|
||||
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return;
|
||||
|
||||
if ((queue->flags & BYTE_QUEUE_WRITE) == 0)
|
||||
return;
|
||||
|
||||
queue->flags &= ~BYTE_QUEUE_WRITE;
|
||||
queue->used += (unsigned int) num;
|
||||
}
|
||||
|
||||
// Sets the minimum capacity for the next write operation
|
||||
// and returns 1 if the content of the queue was moved, else
|
||||
// 0 is returned.
|
||||
//
|
||||
// You must not call this function while a write is pending.
|
||||
// In other words, you must do this:
|
||||
//
|
||||
// byte_queue_write_setmincap(queue, mincap);
|
||||
// dst = byte_queue_write_buf(queue, &cap);
|
||||
// ...
|
||||
// byte_queue_write_ack(num);
|
||||
//
|
||||
// And NOT this:
|
||||
//
|
||||
// dst = byte_queue_write_buf(queue, &cap);
|
||||
// byte_queue_write_setmincap(queue, mincap); <-- BAD
|
||||
// ...
|
||||
// byte_queue_write_ack(num);
|
||||
//
|
||||
static int
|
||||
byte_queue_write_setmincap(HTTP_ByteQueue *queue, int mincap)
|
||||
{
|
||||
HTTP_ASSERT(mincap >= 0);
|
||||
unsigned int umincap = (unsigned int) mincap;
|
||||
|
||||
// Sticky error
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return 0;
|
||||
|
||||
// In general, the queue's contents look like this:
|
||||
//
|
||||
// size
|
||||
// v
|
||||
// [___xxxxxxxxxxxx________]
|
||||
// ^ ^ ^
|
||||
// 0 head head + used
|
||||
//
|
||||
// This function needs to make sure that at least [mincap]
|
||||
// bytes are available on the right side of the content.
|
||||
//
|
||||
// We have 3 cases:
|
||||
//
|
||||
// 1) If there is enough memory already, this function doesn't
|
||||
// need to do anything.
|
||||
//
|
||||
// 2) If there isn't enough memory on the right but there is
|
||||
// enough free memory if we cound the left unused region,
|
||||
// then the content is moved back to the
|
||||
// start of the buffer.
|
||||
//
|
||||
// 3) If there isn't enough memory considering both sides, this
|
||||
// function needs to allocate a new buffer.
|
||||
//
|
||||
// If there are pending read or write operations, the application
|
||||
// is holding pointers to the buffer, so we need to make sure
|
||||
// to not invalidate them. The only real problem is pending reads
|
||||
// since this function can only be called before starting a write
|
||||
// opearation.
|
||||
//
|
||||
// To avoid invalidating the read pointer when we allocate a new
|
||||
// buffer, we don't free the old buffer. Instead, we store the
|
||||
// pointer in the "old" field so that the read ack function can
|
||||
// free it.
|
||||
//
|
||||
// To avoid invalidating the pointer when we are moving back the
|
||||
// content since there is enough memory at the start of the buffer,
|
||||
// we just avoid that. Even if there is enough memory considering
|
||||
// left and right free regions, we allocate a new buffer.
|
||||
|
||||
HTTP_ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0);
|
||||
|
||||
unsigned int total_free_space = queue->size - queue->used;
|
||||
unsigned int free_space_after_data = queue->size - queue->used - queue->head;
|
||||
|
||||
int moved = 0;
|
||||
if (free_space_after_data < umincap) {
|
||||
|
||||
if (total_free_space < umincap || (queue->read_target == queue->data)) {
|
||||
// Resize required
|
||||
|
||||
if (queue->used + umincap > queue->limit) {
|
||||
queue->flags |= BYTE_QUEUE_ERROR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int size;
|
||||
if (queue->size > UINT32_MAX / 2)
|
||||
size = UINT32_MAX;
|
||||
else
|
||||
size = 2 * queue->size;
|
||||
|
||||
if (size < queue->used + umincap)
|
||||
size = queue->used + umincap;
|
||||
|
||||
if (size > queue->limit)
|
||||
size = queue->limit;
|
||||
|
||||
char *data = callback_malloc(queue, size);
|
||||
if (!data) {
|
||||
queue->flags |= BYTE_QUEUE_ERROR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (queue->used > 0)
|
||||
memcpy(data, queue->data + queue->head, queue->used);
|
||||
|
||||
if (queue->read_target != queue->data)
|
||||
callback_free(queue, queue->data, queue->size);
|
||||
|
||||
queue->data = data;
|
||||
queue->head = 0;
|
||||
queue->size = size;
|
||||
|
||||
} else {
|
||||
// Move required
|
||||
memmove(queue->data, queue->data + queue->head, queue->used);
|
||||
queue->head = 0;
|
||||
}
|
||||
|
||||
moved = 1;
|
||||
}
|
||||
|
||||
return moved;
|
||||
}
|
||||
|
||||
static HTTP_ByteQueueOffset
|
||||
byte_queue_offset(HTTP_ByteQueue *queue)
|
||||
{
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return (HTTP_ByteQueueOffset) { 0 };
|
||||
return (HTTP_ByteQueueOffset) { queue->curs + queue->used };
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
byte_queue_size_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off)
|
||||
{
|
||||
return queue->curs + queue->used - off;
|
||||
}
|
||||
|
||||
static void
|
||||
byte_queue_patch(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off,
|
||||
char *src, unsigned int len)
|
||||
{
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return;
|
||||
|
||||
// Check that the offset is in range
|
||||
HTTP_ASSERT(off >= queue->curs && off - queue->curs < queue->used);
|
||||
|
||||
// Check that the length is in range
|
||||
HTTP_ASSERT(len <= queue->used - (off - queue->curs));
|
||||
|
||||
// Perform the patch
|
||||
char *dst = queue->data + queue->head + (off - queue->curs);
|
||||
memcpy(dst, src, len);
|
||||
}
|
||||
|
||||
static void
|
||||
byte_queue_remove_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset offset)
|
||||
{
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return;
|
||||
|
||||
unsigned long long num = (queue->curs + queue->used) - offset;
|
||||
HTTP_ASSERT(num <= queue->used);
|
||||
|
||||
queue->used -= num;
|
||||
}
|
||||
|
||||
static void
|
||||
byte_queue_write(HTTP_ByteQueue *queue, const char *str, int len)
|
||||
{
|
||||
if (str == NULL) str = "";
|
||||
if (len < 0) len = strlen(str);
|
||||
|
||||
int cap;
|
||||
byte_queue_write_setmincap(queue, len);
|
||||
char *dst = byte_queue_write_buf(queue, &cap);
|
||||
if (dst) memcpy(dst, str, len);
|
||||
byte_queue_write_ack(queue, len);
|
||||
}
|
||||
|
||||
static void
|
||||
byte_queue_write_fmt2(HTTP_ByteQueue *queue, const char *fmt, va_list args)
|
||||
{
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return;
|
||||
|
||||
va_list args2;
|
||||
va_copy(args2, args);
|
||||
|
||||
int cap;
|
||||
byte_queue_write_setmincap(queue, 128);
|
||||
char *dst = byte_queue_write_buf(queue, &cap);
|
||||
|
||||
int len = vsnprintf(dst, cap, fmt, args);
|
||||
if (len < 0) {
|
||||
queue->flags |= BYTE_QUEUE_ERROR;
|
||||
va_end(args2);
|
||||
return;
|
||||
}
|
||||
|
||||
if (len > cap) {
|
||||
byte_queue_write_ack(queue, 0);
|
||||
byte_queue_write_setmincap(queue, len+1);
|
||||
dst = byte_queue_write_buf(queue, &cap);
|
||||
vsnprintf(dst, cap, fmt, args2);
|
||||
}
|
||||
|
||||
byte_queue_write_ack(queue, len);
|
||||
|
||||
va_end(args2);
|
||||
}
|
||||
|
||||
static void
|
||||
byte_queue_write_fmt(HTTP_ByteQueue *queue, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
byte_queue_write_fmt2(queue, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
#define TEN_SPACES " "
|
||||
|
||||
void http_engine_init(HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata)
|
||||
{
|
||||
if (client)
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_URL;
|
||||
else
|
||||
eng->state = HTTP_ENGINE_STATE_SERVER_RECV_BUF;
|
||||
|
||||
eng->closing = 0;
|
||||
eng->numexch = 0;
|
||||
|
||||
byte_queue_init(&eng->input, 1<<20, memfunc, memfuncdata);
|
||||
byte_queue_init(&eng->output, 1<<20, memfunc, memfuncdata);
|
||||
}
|
||||
|
||||
void http_engine_free(HTTP_Engine *eng)
|
||||
{
|
||||
byte_queue_free(&eng->input);
|
||||
byte_queue_free(&eng->output);
|
||||
eng->state = HTTP_ENGINE_STATE_NONE;
|
||||
}
|
||||
|
||||
void http_engine_close(HTTP_Engine *eng)
|
||||
{
|
||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
|
||||
else
|
||||
eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED;
|
||||
}
|
||||
|
||||
HTTP_EngineState http_engine_state(HTTP_Engine *eng)
|
||||
{
|
||||
return eng->state;
|
||||
}
|
||||
|
||||
const char* http_engine_statestr(HTTP_EngineState state) { // TODO: remove
|
||||
switch (state) {
|
||||
case HTTP_ENGINE_STATE_NONE: return "NONE";
|
||||
case HTTP_ENGINE_STATE_CLIENT_PREP_URL: return "CLIENT_PREP_URL";
|
||||
case HTTP_ENGINE_STATE_CLIENT_PREP_HEADER: return "CLIENT_PREP_HEADER";
|
||||
case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF: return "CLIENT_PREP_BODY_BUF";
|
||||
case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK: return "CLIENT_PREP_BODY_ACK";
|
||||
case HTTP_ENGINE_STATE_CLIENT_PREP_ERROR: return "CLIENT_PREP_ERROR";
|
||||
case HTTP_ENGINE_STATE_CLIENT_SEND_BUF: return "CLIENT_SEND_BUF";
|
||||
case HTTP_ENGINE_STATE_CLIENT_SEND_ACK: return "CLIENT_SEND_ACK";
|
||||
case HTTP_ENGINE_STATE_CLIENT_RECV_BUF: return "CLIENT_RECV_BUF";
|
||||
case HTTP_ENGINE_STATE_CLIENT_RECV_ACK: return "CLIENT_RECV_ACK";
|
||||
case HTTP_ENGINE_STATE_CLIENT_READY: return "CLIENT_READY";
|
||||
case HTTP_ENGINE_STATE_CLIENT_CLOSED: return "CLIENT_CLOSED";
|
||||
case HTTP_ENGINE_STATE_SERVER_RECV_BUF: return "SERVER_RECV_BUF";
|
||||
case HTTP_ENGINE_STATE_SERVER_RECV_ACK: return "SERVER_RECV_ACK";
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_STATUS: return "SERVER_PREP_STATUS";
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_HEADER: return "SERVER_PREP_HEADER";
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF: return "SERVER_PREP_BODY_BUF";
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK: return "SERVER_PREP_BODY_ACK";
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_ERROR: return "SERVER_PREP_ERROR";
|
||||
case HTTP_ENGINE_STATE_SERVER_SEND_BUF: return "SERVER_SEND_BUF";
|
||||
case HTTP_ENGINE_STATE_SERVER_SEND_ACK: return "SERVER_SEND_ACK";
|
||||
case HTTP_ENGINE_STATE_SERVER_CLOSED: return "SERVER_CLOSED";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
char *http_engine_recvbuf(HTTP_Engine *eng, int *cap)
|
||||
{
|
||||
if ((eng->state & HTTP_ENGINE_STATEBIT_RECV_BUF) == 0) {
|
||||
*cap = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
eng->state &= ~HTTP_ENGINE_STATEBIT_RECV_BUF;
|
||||
eng->state |= HTTP_ENGINE_STATEBIT_RECV_ACK;
|
||||
|
||||
byte_queue_write_setmincap(&eng->input, 1<<9);
|
||||
if (byte_queue_error(&eng->input)) {
|
||||
*cap = 0;
|
||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
|
||||
else
|
||||
eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return byte_queue_write_buf(&eng->input, cap);
|
||||
}
|
||||
|
||||
static int
|
||||
should_keep_alive(HTTP_Engine *eng)
|
||||
{
|
||||
HTTP_ASSERT(eng->state & HTTP_ENGINE_STATEBIT_PREP);
|
||||
|
||||
#if 0
|
||||
// If the parent system doesn't want us to reuse
|
||||
// the connection, we certainly can't keep alive.
|
||||
if ((eng->state & TINYHTTP_STREAM_REUSE) == 0)
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
if (eng->numexch >= 100) // TODO: Make this a parameter
|
||||
return 0;
|
||||
|
||||
HTTP_Request *req = &eng->result.req;
|
||||
|
||||
// If the client is using HTTP/1.0, we can't
|
||||
// keep alive.
|
||||
if (req->minor == 0)
|
||||
return 0;
|
||||
|
||||
// TODO: This assumes "Connection" can only hold a single token,
|
||||
// but this is not true.
|
||||
int i = http_find_header(req->headers, req->num_headers, HTTP_STR("Connection"));
|
||||
if (i >= 0 && http_streqcase(req->headers[i].value, HTTP_STR("Close")))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void process_incoming_request(HTTP_Engine *eng)
|
||||
{
|
||||
HTTP_ASSERT(eng->state == HTTP_ENGINE_STATE_SERVER_RECV_ACK
|
||||
|| eng->state == HTTP_ENGINE_STATE_SERVER_SEND_ACK
|
||||
|| eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF
|
||||
|| eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR);
|
||||
|
||||
char *src;
|
||||
int len;
|
||||
src = byte_queue_read_buf(&eng->input, &len);
|
||||
|
||||
int ret = http_parse_request(src, len, &eng->result.req);
|
||||
|
||||
if (ret == 0) {
|
||||
byte_queue_read_ack(&eng->input, 0);
|
||||
eng->state = HTTP_ENGINE_STATE_SERVER_RECV_BUF;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
byte_queue_read_ack(&eng->input, 0);
|
||||
byte_queue_write(&eng->output,
|
||||
"HTTP/1.1 400 Bad Request\r\n"
|
||||
"Connection: Close\r\n"
|
||||
"Content-Length: 0\r\n"
|
||||
"\r\n", -1
|
||||
);
|
||||
if (byte_queue_error(&eng->output))
|
||||
eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED;
|
||||
else {
|
||||
eng->closing = 1;
|
||||
eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
HTTP_ASSERT(ret > 0);
|
||||
|
||||
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_STATUS;
|
||||
eng->reqsize = ret;
|
||||
eng->keepalive = should_keep_alive(eng);
|
||||
eng->response_offset = byte_queue_offset(&eng->output);
|
||||
}
|
||||
|
||||
void http_engine_recvack(HTTP_Engine *eng, int num)
|
||||
{
|
||||
if ((eng->state & HTTP_ENGINE_STATEBIT_RECV_ACK) == 0)
|
||||
return;
|
||||
|
||||
byte_queue_write_ack(&eng->input, num);
|
||||
|
||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) {
|
||||
|
||||
char *src;
|
||||
int len;
|
||||
src = byte_queue_read_buf(&eng->input, &len);
|
||||
|
||||
int ret = http_parse_response(src, len, &eng->result.res);
|
||||
|
||||
if (ret == 0) {
|
||||
byte_queue_read_ack(&eng->input, 0);
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_RECV_BUF;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
|
||||
return;
|
||||
}
|
||||
|
||||
HTTP_ASSERT(ret > 0);
|
||||
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_READY;
|
||||
|
||||
} else {
|
||||
process_incoming_request(eng);
|
||||
}
|
||||
}
|
||||
|
||||
char *http_engine_sendbuf(HTTP_Engine *eng, int *len)
|
||||
{
|
||||
if ((eng->state & HTTP_ENGINE_STATEBIT_SEND_BUF) == 0) {
|
||||
*len = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
eng->state &= ~HTTP_ENGINE_STATEBIT_SEND_BUF;
|
||||
eng->state |= HTTP_ENGINE_STATEBIT_SEND_ACK;
|
||||
|
||||
return byte_queue_read_buf(&eng->output, len);
|
||||
}
|
||||
|
||||
void http_engine_sendack(HTTP_Engine *eng, int num)
|
||||
{
|
||||
if (eng->state != HTTP_ENGINE_STATE_SERVER_SEND_ACK &&
|
||||
eng->state != HTTP_ENGINE_STATE_CLIENT_SEND_ACK)
|
||||
return;
|
||||
|
||||
byte_queue_read_ack(&eng->output, num);
|
||||
|
||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) {
|
||||
if (byte_queue_empty(&eng->output))
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_RECV_BUF;
|
||||
else
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_SEND_BUF;
|
||||
} else {
|
||||
if (byte_queue_empty(&eng->output)) {
|
||||
if (!eng->closing && eng->keepalive)
|
||||
process_incoming_request(eng);
|
||||
else
|
||||
eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED;
|
||||
} else
|
||||
eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF;
|
||||
}
|
||||
}
|
||||
|
||||
HTTP_Request *http_engine_getreq(HTTP_Engine *eng)
|
||||
{
|
||||
if ((eng->state & HTTP_ENGINE_STATEBIT_REQUEST) == 0)
|
||||
return NULL;
|
||||
return &eng->result.req;
|
||||
}
|
||||
|
||||
HTTP_Response *http_engine_getres(HTTP_Engine *eng)
|
||||
{
|
||||
if ((eng->state & HTTP_ENGINE_STATEBIT_RESPONSE) == 0)
|
||||
return NULL;
|
||||
return &eng->result.res;
|
||||
}
|
||||
|
||||
void http_engine_url(HTTP_Engine *eng, HTTP_Method method, HTTP_String url, int minor)
|
||||
{
|
||||
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_URL)
|
||||
return;
|
||||
|
||||
eng->response_offset = byte_queue_offset(&eng->output); // TODO: rename response_offset to something that makes sense for clients
|
||||
|
||||
HTTP_URL parsed_url;
|
||||
int ret = http_parse_url(url.ptr, url.len, &parsed_url);
|
||||
if (ret != url.len) {
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
HTTP_String method_and_space = HTTP_STR("???");
|
||||
switch (method) {
|
||||
case HTTP_METHOD_GET : method_and_space = HTTP_STR("GET "); break;
|
||||
case HTTP_METHOD_HEAD : method_and_space = HTTP_STR("HEAD "); break;
|
||||
case HTTP_METHOD_POST : method_and_space = HTTP_STR("POST "); break;
|
||||
case HTTP_METHOD_PUT : method_and_space = HTTP_STR("PUT "); break;
|
||||
case HTTP_METHOD_DELETE : method_and_space = HTTP_STR("DELETE "); break;
|
||||
case HTTP_METHOD_CONNECT: method_and_space = HTTP_STR("CONNECT "); break;
|
||||
case HTTP_METHOD_OPTIONS: method_and_space = HTTP_STR("OPTIONS "); break;
|
||||
case HTTP_METHOD_TRACE : method_and_space = HTTP_STR("TRACE "); break;
|
||||
case HTTP_METHOD_PATCH : method_and_space = HTTP_STR("PATCH "); break;
|
||||
}
|
||||
|
||||
HTTP_String path = parsed_url.path;
|
||||
if (path.len == 0)
|
||||
path = HTTP_STR("/");
|
||||
|
||||
byte_queue_write(&eng->output, method_and_space.ptr, method_and_space.len);
|
||||
byte_queue_write(&eng->output, path.ptr, path.len);
|
||||
byte_queue_write(&eng->output, parsed_url.query.ptr, parsed_url.query.len);
|
||||
byte_queue_write(&eng->output, minor ? " HTTP/1.1\r\nHost: " : " HTTP/1.0\r\nHost: ", -1);
|
||||
byte_queue_write(&eng->output, parsed_url.authority.host.text.ptr, parsed_url.authority.host.text.len);
|
||||
if (parsed_url.authority.port > 0)
|
||||
byte_queue_write_fmt(&eng->output, "%d", parsed_url.authority.port);
|
||||
byte_queue_write(&eng->output, "\r\n", 2);
|
||||
|
||||
eng->keepalive = 1; // TODO
|
||||
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_HEADER;
|
||||
}
|
||||
|
||||
|
||||
static const char*
|
||||
get_status_text(int code)
|
||||
{
|
||||
switch(code) {
|
||||
|
||||
case 100: return "Continue";
|
||||
case 101: return "Switching Protocols";
|
||||
case 102: return "Processing";
|
||||
|
||||
case 200: return "OK";
|
||||
case 201: return "Created";
|
||||
case 202: return "Accepted";
|
||||
case 203: return "Non-Authoritative Information";
|
||||
case 204: return "No Content";
|
||||
case 205: return "Reset Content";
|
||||
case 206: return "Partial Content";
|
||||
case 207: return "Multi-Status";
|
||||
case 208: return "Already Reported";
|
||||
|
||||
case 300: return "Multiple Choices";
|
||||
case 301: return "Moved Permanently";
|
||||
case 302: return "Found";
|
||||
case 303: return "See Other";
|
||||
case 304: return "Not Modified";
|
||||
case 305: return "Use Proxy";
|
||||
case 306: return "Switch Proxy";
|
||||
case 307: return "Temporary Redirect";
|
||||
case 308: return "Permanent Redirect";
|
||||
|
||||
case 400: return "Bad Request";
|
||||
case 401: return "Unauthorized";
|
||||
case 402: return "Payment Required";
|
||||
case 403: return "Forbidden";
|
||||
case 404: return "Not Found";
|
||||
case 405: return "Method Not Allowed";
|
||||
case 406: return "Not Acceptable";
|
||||
case 407: return "Proxy Authentication Required";
|
||||
case 408: return "Request Timeout";
|
||||
case 409: return "Conflict";
|
||||
case 410: return "Gone";
|
||||
case 411: return "Length Required";
|
||||
case 412: return "Precondition Failed";
|
||||
case 413: return "Request Entity Too Large";
|
||||
case 414: return "Request-URI Too Long";
|
||||
case 415: return "Unsupported Media Type";
|
||||
case 416: return "Requested Range Not Satisfiable";
|
||||
case 417: return "Expectation Failed";
|
||||
case 418: return "I'm a teapot";
|
||||
case 420: return "Enhance your calm";
|
||||
case 422: return "Unprocessable Entity";
|
||||
case 426: return "Upgrade Required";
|
||||
case 429: return "Too many requests";
|
||||
case 431: return "Request Header Fields Too Large";
|
||||
case 449: return "Retry With";
|
||||
case 451: return "Unavailable For Legal Reasons";
|
||||
|
||||
case 500: return "Internal Server Error";
|
||||
case 501: return "Not Implemented";
|
||||
case 502: return "Bad Gateway";
|
||||
case 503: return "Service Unavailable";
|
||||
case 504: return "Gateway Timeout";
|
||||
case 505: return "HTTP Version Not Supported";
|
||||
case 509: return "Bandwidth Limit Exceeded";
|
||||
}
|
||||
return "???";
|
||||
}
|
||||
|
||||
void http_engine_status(HTTP_Engine *eng, int status)
|
||||
{
|
||||
if (eng->state != HTTP_ENGINE_STATE_SERVER_PREP_STATUS)
|
||||
return;
|
||||
|
||||
byte_queue_write_fmt(&eng->output,
|
||||
"HTTP/1.1 %d %s\r\n",
|
||||
status, get_status_text(status));
|
||||
|
||||
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_HEADER;
|
||||
}
|
||||
|
||||
void http_engine_header(HTTP_Engine *eng, HTTP_String str)
|
||||
{
|
||||
if ((eng->state & HTTP_ENGINE_STATEBIT_PREP_HEADER) == 0)
|
||||
return;
|
||||
|
||||
// TODO: Check that the header is valid
|
||||
|
||||
byte_queue_write(&eng->output, str.ptr, str.len);
|
||||
byte_queue_write(&eng->output, "\r\n", 2);
|
||||
}
|
||||
|
||||
void http_engine_header_fmt2(HTTP_Engine *eng, const char *fmt, va_list args)
|
||||
{
|
||||
if ((eng->state & HTTP_ENGINE_STATEBIT_PREP_HEADER) == 0)
|
||||
return;
|
||||
|
||||
// TODO: Check that the header is valid
|
||||
|
||||
byte_queue_write_fmt2(&eng->output, fmt, args);
|
||||
byte_queue_write(&eng->output, "\r\n", 2);
|
||||
}
|
||||
|
||||
void http_engine_header_fmt(HTTP_Engine *eng, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
http_engine_header_fmt2(eng, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
static void
|
||||
complete_message_head(HTTP_Engine *eng)
|
||||
{
|
||||
if (eng->keepalive) byte_queue_write(&eng->output, "Connection: Keep-Alive\r\n", -1);
|
||||
else byte_queue_write(&eng->output, "Connection: Close\r\n", -1);
|
||||
|
||||
byte_queue_write(&eng->output, "Content-Length: ", -1);
|
||||
eng->content_length_value_offset = byte_queue_offset(&eng->output);
|
||||
byte_queue_write(&eng->output, TEN_SPACES "\r\n", -1);
|
||||
|
||||
byte_queue_write(&eng->output, "\r\n", -1);
|
||||
eng->content_length_offset = byte_queue_offset(&eng->output);
|
||||
}
|
||||
|
||||
static void complete_message_body(HTTP_Engine *eng)
|
||||
{
|
||||
unsigned int content_length = byte_queue_size_from_offset(&eng->output, eng->content_length_offset);
|
||||
|
||||
if (content_length > UINT32_MAX) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
char tmp[10];
|
||||
|
||||
tmp[0] = '0' + content_length / 1000000000; content_length %= 1000000000;
|
||||
tmp[1] = '0' + content_length / 100000000; content_length %= 100000000;
|
||||
tmp[2] = '0' + content_length / 10000000; content_length %= 10000000;
|
||||
tmp[3] = '0' + content_length / 1000000; content_length %= 1000000;
|
||||
tmp[4] = '0' + content_length / 100000; content_length %= 100000;
|
||||
tmp[5] = '0' + content_length / 10000; content_length %= 10000;
|
||||
tmp[6] = '0' + content_length / 1000; content_length %= 1000;
|
||||
tmp[7] = '0' + content_length / 100; content_length %= 100;
|
||||
tmp[8] = '0' + content_length / 10; content_length %= 10;
|
||||
tmp[9] = '0' + content_length;
|
||||
|
||||
int i = 0;
|
||||
while (i < 9 && tmp[i] == '0')
|
||||
i++;
|
||||
|
||||
byte_queue_patch(&eng->output, eng->content_length_value_offset, tmp + i, 10 - i);
|
||||
}
|
||||
|
||||
void http_engine_body(HTTP_Engine *eng, HTTP_String str)
|
||||
{
|
||||
http_engine_bodycap(eng, str.len);
|
||||
int cap;
|
||||
char *buf = http_engine_bodybuf(eng, &cap);
|
||||
if (buf) {
|
||||
memcpy(buf, str.ptr, str.len);
|
||||
http_engine_bodyack(eng, str.len);
|
||||
}
|
||||
}
|
||||
|
||||
static void ensure_body_entered(HTTP_Engine *eng)
|
||||
{
|
||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) {
|
||||
|
||||
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) {
|
||||
complete_message_head(eng);
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) {
|
||||
complete_message_head(eng);
|
||||
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void http_engine_bodycap(HTTP_Engine *eng, int mincap)
|
||||
{
|
||||
ensure_body_entered(eng);
|
||||
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF &&
|
||||
eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF)
|
||||
return;
|
||||
|
||||
byte_queue_write_setmincap(&eng->output, mincap);
|
||||
}
|
||||
|
||||
char *http_engine_bodybuf(HTTP_Engine *eng, int *cap)
|
||||
{
|
||||
ensure_body_entered(eng);
|
||||
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF &&
|
||||
eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) {
|
||||
*cap = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK;
|
||||
else
|
||||
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK;
|
||||
|
||||
return byte_queue_write_buf(&eng->output, cap);
|
||||
}
|
||||
|
||||
void http_engine_bodyack(HTTP_Engine *eng, int num)
|
||||
{
|
||||
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK &&
|
||||
eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK)
|
||||
return;
|
||||
|
||||
byte_queue_write_ack(&eng->output, num);
|
||||
|
||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF;
|
||||
else
|
||||
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF;
|
||||
}
|
||||
|
||||
void http_engine_done(HTTP_Engine *eng)
|
||||
{
|
||||
if ((eng->state & HTTP_ENGINE_STATEBIT_PREP) == 0)
|
||||
return;
|
||||
|
||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) {
|
||||
|
||||
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_URL) {
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
|
||||
return;
|
||||
}
|
||||
|
||||
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) {
|
||||
complete_message_head(eng);
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF;
|
||||
}
|
||||
|
||||
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF)
|
||||
complete_message_body(eng);
|
||||
|
||||
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_ERROR) {
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
|
||||
return;
|
||||
}
|
||||
|
||||
if (byte_queue_error(&eng->output)) {
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
|
||||
return;
|
||||
}
|
||||
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_SEND_BUF;
|
||||
|
||||
} else {
|
||||
|
||||
if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) {
|
||||
complete_message_head(eng);
|
||||
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF;
|
||||
}
|
||||
|
||||
if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF)
|
||||
complete_message_body(eng);
|
||||
|
||||
if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR) {
|
||||
byte_queue_remove_from_offset(&eng->output, eng->response_offset);
|
||||
byte_queue_write(&eng->output,
|
||||
"HTTP/1.1 500 Internal Server Error\r\n"
|
||||
"Content-Length: 0\r\n"
|
||||
"Connection: Close\r\n"
|
||||
"\r\n",
|
||||
-1
|
||||
);
|
||||
}
|
||||
|
||||
if (byte_queue_error(&eng->output)) {
|
||||
eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED;
|
||||
return;
|
||||
}
|
||||
|
||||
byte_queue_read_ack(&eng->input, eng->reqsize);
|
||||
eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF;
|
||||
}
|
||||
}
|
||||
|
||||
void http_engine_undo(HTTP_Engine *eng)
|
||||
{
|
||||
if ((eng->state & HTTP_ENGINE_STATEBIT_PREP) == 0)
|
||||
return;
|
||||
|
||||
byte_queue_write_ack(&eng->output, 0);
|
||||
byte_queue_remove_from_offset(&eng->output, eng->response_offset);
|
||||
|
||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
|
||||
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_URL;
|
||||
else
|
||||
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_STATUS;
|
||||
}
|
||||
-122
@@ -1,122 +0,0 @@
|
||||
#ifndef HTTP_ENGINE_INCLUDED
|
||||
#define HTTP_ENGINE_INCLUDED
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "parse.h"
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
HTTP_MEMFUNC_MALLOC,
|
||||
HTTP_MEMFUNC_FREE,
|
||||
} HTTP_MemoryFuncTag;
|
||||
|
||||
typedef void*(*HTTP_MemoryFunc)(HTTP_MemoryFuncTag tag,
|
||||
void *ptr, int len, void *data);
|
||||
|
||||
typedef struct {
|
||||
|
||||
HTTP_MemoryFunc memfunc;
|
||||
void *memfuncdata;
|
||||
|
||||
unsigned long long curs;
|
||||
|
||||
char* data;
|
||||
unsigned int head;
|
||||
unsigned int size;
|
||||
unsigned int used;
|
||||
unsigned int limit;
|
||||
|
||||
char* read_target;
|
||||
unsigned int read_target_size;
|
||||
|
||||
int flags;
|
||||
} HTTP_ByteQueue;
|
||||
|
||||
typedef unsigned long long HTTP_ByteQueueOffset;
|
||||
|
||||
#define HTTP_ENGINE_STATEBIT_CLIENT (1 << 0)
|
||||
#define HTTP_ENGINE_STATEBIT_CLOSED (1 << 1)
|
||||
#define HTTP_ENGINE_STATEBIT_RECV_BUF (1 << 2)
|
||||
#define HTTP_ENGINE_STATEBIT_RECV_ACK (1 << 3)
|
||||
#define HTTP_ENGINE_STATEBIT_SEND_BUF (1 << 4)
|
||||
#define HTTP_ENGINE_STATEBIT_SEND_ACK (1 << 5)
|
||||
#define HTTP_ENGINE_STATEBIT_REQUEST (1 << 6)
|
||||
#define HTTP_ENGINE_STATEBIT_RESPONSE (1 << 7)
|
||||
#define HTTP_ENGINE_STATEBIT_PREP (1 << 8)
|
||||
#define HTTP_ENGINE_STATEBIT_PREP_HEADER (1 << 9)
|
||||
#define HTTP_ENGINE_STATEBIT_PREP_BODY_BUF (1 << 10)
|
||||
#define HTTP_ENGINE_STATEBIT_PREP_BODY_ACK (1 << 11)
|
||||
#define HTTP_ENGINE_STATEBIT_PREP_ERROR (1 << 12)
|
||||
#define HTTP_ENGINE_STATEBIT_PREP_URL (1 << 13)
|
||||
#define HTTP_ENGINE_STATEBIT_PREP_STATUS (1 << 14)
|
||||
#define HTTP_ENGINE_STATEBIT_CLOSING (1 << 15)
|
||||
|
||||
typedef enum {
|
||||
HTTP_ENGINE_STATE_NONE = 0,
|
||||
HTTP_ENGINE_STATE_CLIENT_PREP_URL = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_URL,
|
||||
HTTP_ENGINE_STATE_CLIENT_PREP_HEADER = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER,
|
||||
HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF,
|
||||
HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK,
|
||||
HTTP_ENGINE_STATE_CLIENT_PREP_ERROR = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR,
|
||||
HTTP_ENGINE_STATE_CLIENT_SEND_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_BUF,
|
||||
HTTP_ENGINE_STATE_CLIENT_SEND_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_ACK,
|
||||
HTTP_ENGINE_STATE_CLIENT_RECV_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_BUF,
|
||||
HTTP_ENGINE_STATE_CLIENT_RECV_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_ACK,
|
||||
HTTP_ENGINE_STATE_CLIENT_READY = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RESPONSE,
|
||||
HTTP_ENGINE_STATE_CLIENT_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_CLOSED,
|
||||
HTTP_ENGINE_STATE_SERVER_RECV_BUF = HTTP_ENGINE_STATEBIT_RECV_BUF,
|
||||
HTTP_ENGINE_STATE_SERVER_RECV_ACK = HTTP_ENGINE_STATEBIT_RECV_ACK,
|
||||
HTTP_ENGINE_STATE_SERVER_PREP_STATUS = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_STATUS,
|
||||
HTTP_ENGINE_STATE_SERVER_PREP_HEADER = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER,
|
||||
HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF,
|
||||
HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK,
|
||||
HTTP_ENGINE_STATE_SERVER_PREP_ERROR = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR,
|
||||
HTTP_ENGINE_STATE_SERVER_SEND_BUF = HTTP_ENGINE_STATEBIT_SEND_BUF,
|
||||
HTTP_ENGINE_STATE_SERVER_SEND_ACK = HTTP_ENGINE_STATEBIT_SEND_ACK,
|
||||
HTTP_ENGINE_STATE_SERVER_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT,
|
||||
} HTTP_EngineState;
|
||||
|
||||
typedef struct {
|
||||
HTTP_EngineState state;
|
||||
HTTP_ByteQueue input;
|
||||
HTTP_ByteQueue output;
|
||||
int numexch;
|
||||
int reqsize;
|
||||
int closing;
|
||||
int keepalive;
|
||||
HTTP_ByteQueueOffset response_offset;
|
||||
HTTP_ByteQueueOffset content_length_offset;
|
||||
HTTP_ByteQueueOffset content_length_value_offset;
|
||||
union {
|
||||
HTTP_Request req;
|
||||
HTTP_Response res;
|
||||
} result;
|
||||
} HTTP_Engine;
|
||||
|
||||
void http_engine_init (HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata);
|
||||
void http_engine_free (HTTP_Engine *eng);
|
||||
|
||||
void http_engine_close (HTTP_Engine *eng);
|
||||
HTTP_EngineState http_engine_state (HTTP_Engine *eng);
|
||||
|
||||
const char* http_engine_statestr(HTTP_EngineState state); // TODO: remove
|
||||
|
||||
char* http_engine_recvbuf (HTTP_Engine *eng, int *cap);
|
||||
void http_engine_recvack (HTTP_Engine *eng, int num);
|
||||
char* http_engine_sendbuf (HTTP_Engine *eng, int *len);
|
||||
void http_engine_sendack (HTTP_Engine *eng, int num);
|
||||
|
||||
HTTP_Request* http_engine_getreq (HTTP_Engine *eng);
|
||||
HTTP_Response* http_engine_getres (HTTP_Engine *eng);
|
||||
|
||||
void http_engine_url (HTTP_Engine *eng, HTTP_Method method, HTTP_String url, int minor);
|
||||
void http_engine_status (HTTP_Engine *eng, int status);
|
||||
void http_engine_header (HTTP_Engine *eng, HTTP_String str);
|
||||
void http_engine_body (HTTP_Engine *eng, HTTP_String str);
|
||||
void http_engine_bodycap (HTTP_Engine *eng, int mincap);
|
||||
char* http_engine_bodybuf (HTTP_Engine *eng, int *cap);
|
||||
void http_engine_bodyack (HTTP_Engine *eng, int num);
|
||||
void http_engine_done (HTTP_Engine *eng);
|
||||
void http_engine_undo (HTTP_Engine *eng);
|
||||
|
||||
#endif // HTTP_ENGINE_INCLUDED
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
+3
-16
@@ -1,16 +1,3 @@
|
||||
#include <stdio.h> // snprintf
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "parse.h"
|
||||
#include "basic.h"
|
||||
#endif
|
||||
|
||||
// From RFC 9112
|
||||
// request-target = origin-form
|
||||
// / absolute-form
|
||||
@@ -658,7 +645,7 @@ static int parse_request_target(Scanner *s, HTTP_URL *url)
|
||||
|
||||
bool consume_str(Scanner *scan, HTTP_String token)
|
||||
{
|
||||
HTTP_ASSERT(token.len > 0);
|
||||
assert(token.len > 0);
|
||||
|
||||
if (token.len > scan->len - scan->cur)
|
||||
return false;
|
||||
@@ -1304,12 +1291,12 @@ int http_get_param_i(HTTP_String body, HTTP_String str)
|
||||
bool http_match_host(HTTP_Request *req, HTTP_String domain, int port)
|
||||
{
|
||||
int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Host"));
|
||||
HTTP_ASSERT(idx != -1); // Requests without the host header are always rejected
|
||||
assert(idx != -1); // Requests without the host header are always rejected
|
||||
|
||||
char tmp[1<<8];
|
||||
if (port > -1 && port != 80) {
|
||||
int ret = snprintf(tmp, sizeof(tmp), "%.*s:%d", domain.len, domain.ptr, port);
|
||||
HTTP_ASSERT(ret > 0);
|
||||
assert(ret > 0);
|
||||
domain = (HTTP_String) { tmp, ret };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
#ifndef PARSE_INCLUDED
|
||||
#define PARSE_INCLUDED
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "basic.h"
|
||||
#endif
|
||||
|
||||
#define HTTP_MAX_HEADERS 32
|
||||
|
||||
@@ -99,5 +93,3 @@ int http_get_param_i (HTTP_String body, HTTP_String str);
|
||||
// Checks whether the request was meant for the host with the given
|
||||
// domain an port. If port is -1, the default value of 80 is assumed.
|
||||
bool http_match_host(HTTP_Request *req, HTTP_String domain, int port);
|
||||
|
||||
#endif // PARSE_INCLUDED
|
||||
@@ -1,220 +0,0 @@
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "sec.h"
|
||||
#endif
|
||||
|
||||
#ifndef HTTPS_ENABLED
|
||||
|
||||
void secure_context_global_init(void)
|
||||
{
|
||||
}
|
||||
|
||||
void secure_context_global_free(void)
|
||||
{
|
||||
}
|
||||
|
||||
int secure_context_init_as_client(SecureContext *sec)
|
||||
{
|
||||
(void) sec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int secure_context_init_as_server(SecureContext *sec,
|
||||
HTTP_String cert_file, HTTP_String key_file)
|
||||
{
|
||||
(void) sec;
|
||||
(void) cert_file;
|
||||
(void) key_file;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int secure_context_add_cert(SecureContext *sec,
|
||||
HTTP_String domain, HTTP_String cert_file,
|
||||
HTTP_String key_file)
|
||||
{
|
||||
(void) sec;
|
||||
(void) domain;
|
||||
(void) cert_file;
|
||||
(void) key_file;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void secure_context_free(SecureContext *sec)
|
||||
{
|
||||
(void) sec;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void secure_context_global_init(void)
|
||||
{
|
||||
SSL_library_init();
|
||||
SSL_load_error_strings();
|
||||
OpenSSL_add_all_algorithms();
|
||||
}
|
||||
|
||||
void secure_context_global_free(void)
|
||||
{
|
||||
EVP_cleanup();
|
||||
}
|
||||
|
||||
int secure_context_init_as_client(SecureContext *sec)
|
||||
{
|
||||
SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
|
||||
if (!ctx)
|
||||
return -1;
|
||||
|
||||
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
|
||||
|
||||
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
|
||||
|
||||
if (SSL_CTX_set_default_verify_paths(ctx) != 1) {
|
||||
SSL_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sec->is_server = false;
|
||||
sec->ctx = ctx;
|
||||
sec->num_certs = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int servername_callback(SSL *ssl, int *ad, void *arg)
|
||||
{
|
||||
SecureContext *sec = arg;
|
||||
|
||||
(void) ad; // TODO: use this?
|
||||
|
||||
const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
|
||||
if (servername == NULL)
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
|
||||
for (int i = 0; i < sec->num_certs; i++) {
|
||||
CertData *cert = &sec->certs[i];
|
||||
if (!strcmp(cert->domain, servername)) {
|
||||
SSL_set_SSL_CTX(ssl, cert->ctx);
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
}
|
||||
|
||||
int secure_context_init_as_server(SecureContext *sec,
|
||||
HTTP_String cert_file, HTTP_String key_file)
|
||||
{
|
||||
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
|
||||
if (!ctx)
|
||||
return -1;
|
||||
|
||||
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
|
||||
|
||||
char cert_buffer[1024];
|
||||
if (cert_file.len >= (int) sizeof(cert_buffer)) {
|
||||
SSL_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
memcpy(cert_buffer, cert_file.ptr, cert_file.len);
|
||||
cert_buffer[cert_file.len] = '\0';
|
||||
|
||||
// Copy private key file path to static buffer
|
||||
char key_buffer[1024];
|
||||
if (key_file.len >= (int) sizeof(key_buffer)) {
|
||||
SSL_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
memcpy(key_buffer, key_file.ptr, key_file.len);
|
||||
key_buffer[key_file.len] = '\0';
|
||||
|
||||
// Load certificate and private key
|
||||
if (SSL_CTX_use_certificate_file(ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) {
|
||||
SSL_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (SSL_CTX_use_PrivateKey_file(ctx, key_buffer, SSL_FILETYPE_PEM) != 1) {
|
||||
SSL_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Verify that the private key matches the certificate
|
||||
if (SSL_CTX_check_private_key(ctx) != 1) {
|
||||
SSL_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
SSL_CTX_set_tlsext_servername_callback(ctx, servername_callback);
|
||||
SSL_CTX_set_tlsext_servername_arg(ctx, sec);
|
||||
|
||||
sec->is_server = true;
|
||||
sec->ctx = ctx;
|
||||
sec->num_certs = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void secure_context_free(SecureContext *sec)
|
||||
{
|
||||
SSL_CTX_free(sec->ctx);
|
||||
for (int i = 0; i < sec->num_certs; i++)
|
||||
SSL_CTX_free(sec->certs[i].ctx);
|
||||
}
|
||||
|
||||
int secure_context_add_cert(SecureContext *sec,
|
||||
HTTP_String domain, HTTP_String cert_file,
|
||||
HTTP_String key_file)
|
||||
{
|
||||
if (!sec->is_server)
|
||||
return -1;
|
||||
|
||||
if (sec->num_certs == MAX_CERTS)
|
||||
return -1;
|
||||
|
||||
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
|
||||
if (!ctx)
|
||||
return -1;
|
||||
|
||||
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
|
||||
|
||||
char cert_buffer[1024];
|
||||
if (cert_file.len >= (int) sizeof(cert_buffer)) {
|
||||
SSL_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
memcpy(cert_buffer, cert_file.ptr, cert_file.len);
|
||||
cert_buffer[cert_file.len] = '\0';
|
||||
|
||||
char key_buffer[1024];
|
||||
if (key_file.len >= (int) sizeof(key_buffer)) {
|
||||
SSL_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
memcpy(key_buffer, key_file.ptr, key_file.len);
|
||||
key_buffer[key_file.len] = '\0';
|
||||
|
||||
if (SSL_CTX_use_certificate_file(ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) {
|
||||
SSL_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (SSL_CTX_use_PrivateKey_file(ctx, key_buffer, SSL_FILETYPE_PEM) != 1) {
|
||||
SSL_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (SSL_CTX_check_private_key(ctx) != 1) {
|
||||
SSL_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
CertData *cert = &sec->certs[sec->num_certs];
|
||||
if (domain.len >= (int) sizeof(cert->domain)) {
|
||||
SSL_CTX_free(ctx);
|
||||
return -1;
|
||||
}
|
||||
memcpy(cert->domain, domain.ptr, domain.len);
|
||||
cert->domain[domain.len] = '\0';
|
||||
cert->ctx = ctx;
|
||||
sec->num_certs++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,55 +0,0 @@
|
||||
#ifndef SEC_INCLUDED
|
||||
#define SEC_INCLUDED
|
||||
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "basic.h"
|
||||
#endif
|
||||
|
||||
#ifndef HTTPS_ENABLED
|
||||
|
||||
typedef struct {
|
||||
} SecureContext;
|
||||
|
||||
#else
|
||||
|
||||
#define MAX_CERTS 10
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
typedef struct {
|
||||
char domain[128];
|
||||
SSL_CTX *ctx;
|
||||
} CertData;
|
||||
|
||||
typedef struct {
|
||||
|
||||
bool is_server;
|
||||
|
||||
SSL_CTX *ctx;
|
||||
|
||||
// Only used when server
|
||||
int num_certs;
|
||||
CertData certs[MAX_CERTS];
|
||||
|
||||
} SecureContext;
|
||||
|
||||
#endif
|
||||
|
||||
void secure_context_global_init(void);
|
||||
void secure_context_global_free(void);
|
||||
|
||||
int secure_context_init_as_client(SecureContext *sec);
|
||||
|
||||
int secure_context_init_as_server(SecureContext *sec,
|
||||
HTTP_String cert_file, HTTP_String key_file);
|
||||
|
||||
int secure_context_add_cert(SecureContext *sec,
|
||||
HTTP_String domain, HTTP_String cert_file,
|
||||
HTTP_String key_file);
|
||||
|
||||
void secure_context_free(SecureContext *sec);
|
||||
|
||||
#endif // SEC_INCLUDED
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
int global_secure_context_init(void)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
int global_secure_context_free(void)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
int client_secure_context_init(ClientSecureContext *ctx)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
int client_secure_context_free(ClientSecureContext *ctx)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
int server_secure_context_init(ServerSecureContext *ctx)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
int server_secure_context_free(ServerSecureContext *ctx)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
int server_secure_context_add_certificate(ServerSecureContext *ctx,
|
||||
String domain, String cert_file)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
int global_secure_context_init(void);
|
||||
int global_secure_context_free(void);
|
||||
|
||||
typedef struct {
|
||||
#ifdef HTTPS_ENABLED
|
||||
// TODO
|
||||
SSL_CTX *p;
|
||||
#endif
|
||||
} ClientSecureContext;
|
||||
|
||||
int client_secure_context_init(ClientSecureContext *ctx);
|
||||
int client_secure_context_free(ClientSecureContext *ctx);
|
||||
|
||||
typedef struct {
|
||||
|
||||
} SecureDomain;
|
||||
|
||||
typedef struct {
|
||||
#ifdef HTTPS_ENABLED
|
||||
// TODO
|
||||
SSL_CTX *p;
|
||||
#endif
|
||||
} ServerSecureContext;
|
||||
|
||||
int server_secure_context_init(ServerSecureContext *ctx);
|
||||
int server_secure_context_free(ServerSecureContext *ctx);
|
||||
int server_secure_context_add_certificate(ServerSecureContext *ctx,
|
||||
String domain, String cert_file, String key_file);
|
||||
+267
-249
@@ -1,307 +1,325 @@
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "engine.h"
|
||||
#include "server.h"
|
||||
#include "socket_pool.h"
|
||||
#endif
|
||||
|
||||
#define MAX_CONNS (1<<10)
|
||||
|
||||
typedef struct {
|
||||
bool used;
|
||||
uint16_t gen;
|
||||
HTTP_Engine engine;
|
||||
SocketHandle sock;
|
||||
} Connection;
|
||||
|
||||
struct HTTP_Server {
|
||||
|
||||
SocketPool *socket_pool;
|
||||
|
||||
int num_conns;
|
||||
Connection conns[MAX_CONNS];
|
||||
|
||||
int ready_head;
|
||||
int ready_count;
|
||||
int ready[MAX_CONNS];
|
||||
};
|
||||
|
||||
HTTP_Server *http_server_init(HTTP_String addr, uint16_t port)
|
||||
int http_server_init(HTTP_Server *server)
|
||||
{
|
||||
return http_server_init_ex(addr, port, 0, HTTP_STR(""), HTTP_STR(""));
|
||||
}
|
||||
|
||||
HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port,
|
||||
uint16_t secure_port, HTTP_String cert_file, HTTP_String key_file)
|
||||
{
|
||||
HTTP_Server *server = malloc(sizeof(HTTP_Server));
|
||||
if (server == NULL)
|
||||
return NULL;
|
||||
|
||||
int backlog = 32;
|
||||
bool reuse_addr = true;
|
||||
SocketPool *socket_pool = socket_pool_init(addr, port, secure_port, MAX_CONNS, reuse_addr, backlog, cert_file, key_file);
|
||||
if (socket_pool == NULL) {
|
||||
free(server);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
server->socket_pool = socket_pool;
|
||||
server->num_conns = 0;
|
||||
for (int i = 0; i < HTTP_SERVER_CAPACITY; i++)
|
||||
server->conns[i].state = HTTP_SERVER_CONN_FREE;
|
||||
|
||||
server->num_ready = 0;
|
||||
server->ready_head = 0;
|
||||
server->ready_count = 0;
|
||||
|
||||
for (int i = 0; i < MAX_CONNS; i++) {
|
||||
server->conns[i].used = false;
|
||||
server->conns[i].gen = 1;
|
||||
}
|
||||
|
||||
return server;
|
||||
if (socket_manager_init(&server->sockets,
|
||||
&server->socket_pool, HTTP_SERVER_CAPACITY) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void http_server_free(HTTP_Server *server)
|
||||
{
|
||||
for (int i = 0, j = 0; j < server->num_conns; i++) {
|
||||
socket_manager_free(&server->sockets);
|
||||
|
||||
if (!server->conns[i].used)
|
||||
for (int i = 0, j = 0; j < server->num_conns; i++) {
|
||||
HTTP_ServerConn *conn = &server->conns[i];
|
||||
if (conn->state != HTTP_SERVER_CONN_FREE)
|
||||
continue;
|
||||
j++;
|
||||
|
||||
// TODO
|
||||
http_server_conn_free(conn);
|
||||
}
|
||||
}
|
||||
|
||||
socket_pool_free(server->socket_pool);
|
||||
free(server);
|
||||
}
|
||||
|
||||
int http_server_add_website(HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file)
|
||||
int http_server_listen_tcp(HTTP_Server *server,
|
||||
String addr, Port port)
|
||||
{
|
||||
return socket_pool_add_cert(server->socket_pool, domain, cert_file, key_file);
|
||||
}
|
||||
|
||||
static void* server_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) {
|
||||
(void)data;
|
||||
switch (tag) {
|
||||
case HTTP_MEMFUNC_MALLOC:
|
||||
return malloc(len);
|
||||
case HTTP_MEMFUNC_FREE:
|
||||
free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseBuilder *builder)
|
||||
{
|
||||
while (server->ready_count == 0) {
|
||||
|
||||
SocketEvent event = socket_pool_wait(server->socket_pool);
|
||||
switch (event.type) {
|
||||
|
||||
case SOCKET_EVENT_DIED:
|
||||
{
|
||||
Connection *conn = event.user_data;
|
||||
if (conn) {
|
||||
http_engine_free(&conn->engine);
|
||||
conn->used = false;
|
||||
conn->gen++;
|
||||
server->num_conns--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SOCKET_EVENT_READY:
|
||||
{
|
||||
Connection *conn = event.user_data;
|
||||
if (conn == NULL) {
|
||||
|
||||
// Connection was just accepted
|
||||
|
||||
if (server->num_conns == MAX_CONNS) {
|
||||
socket_pool_close(server->socket_pool, event.handle);
|
||||
break;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (server->conns[i].used)
|
||||
i++;
|
||||
|
||||
conn = &server->conns[i];
|
||||
conn->used = true;
|
||||
conn->sock = event.handle;
|
||||
http_engine_init(&conn->engine, 0, server_memfunc, NULL);
|
||||
socket_pool_set_user_data(server->socket_pool, event.handle, conn);
|
||||
server->num_conns++;
|
||||
}
|
||||
|
||||
switch (http_engine_state(&conn->engine)) {
|
||||
|
||||
int len;
|
||||
char *buf;
|
||||
|
||||
case HTTP_ENGINE_STATE_SERVER_RECV_BUF:
|
||||
buf = http_engine_recvbuf(&conn->engine, &len);
|
||||
if (buf) {
|
||||
int ret = socket_pool_read(server->socket_pool, conn->sock, buf, len);
|
||||
http_engine_recvack(&conn->engine, ret);
|
||||
}
|
||||
break;
|
||||
|
||||
case HTTP_ENGINE_STATE_SERVER_SEND_BUF:
|
||||
buf = http_engine_sendbuf(&conn->engine, &len);
|
||||
if (buf) {
|
||||
int ret = socket_pool_write(server->socket_pool, conn->sock, buf, len);
|
||||
http_engine_sendack(&conn->engine, ret);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (http_engine_state(&conn->engine)) {
|
||||
|
||||
int tail;
|
||||
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_STATUS:
|
||||
tail = (server->ready_head + server->ready_count) % MAX_CONNS;
|
||||
server->ready[tail] = conn - server->conns;
|
||||
server->ready_count++;
|
||||
break;
|
||||
|
||||
case HTTP_ENGINE_STATE_SERVER_CLOSED:
|
||||
socket_pool_close(server->socket_pool, conn->sock);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SOCKET_EVENT_ERROR:
|
||||
if (socket_manager_listen_tcp(&server->sockets, addr, port) < 0)
|
||||
return -1;
|
||||
|
||||
case SOCKET_EVENT_SIGNAL:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int index = server->ready[server->ready_head];
|
||||
server->ready_head = (server->ready_head + 1) % MAX_CONNS;
|
||||
server->ready_count--;
|
||||
|
||||
*req = http_engine_getreq(&server->conns[index].engine);
|
||||
(*req)->secure = socket_pool_secure(server->socket_pool, server->conns[index].sock);
|
||||
|
||||
*builder = (HTTP_ResponseBuilder) { server, index, server->conns[index].gen };
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Connection*
|
||||
server_builder_to_conn(HTTP_ResponseBuilder builder)
|
||||
int http_server_listen_tls(HTTP_Server *server,
|
||||
String addr, Port port, String cert_file_name,
|
||||
String key_file_name)
|
||||
{
|
||||
HTTP_Server *server = builder.data0;
|
||||
if (builder.data1 >= MAX_CONNS)
|
||||
if (socket_manager_listen_tls(&server->sockets, addr,
|
||||
port, cert_file_name, key_file_name) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int http_server_add_certificate(HTTP_Server *server,
|
||||
String domain, String cert_file, String key_file)
|
||||
{
|
||||
if (socket_manager_add_certificate(&server->sockets,
|
||||
domain, cert_file, key_file) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int http_server_wakeup(HTTP_Server *server)
|
||||
{
|
||||
if (socket_manager_wakeup(&server->sockets) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int http_server_register_events(HTTP_Server *server,
|
||||
struct pollfd *polled, int max_polled)
|
||||
{
|
||||
return socket_manager_register_events(&server->sockets, polled, max_polled);
|
||||
}
|
||||
|
||||
// Look at the head of the input buffer to see if
|
||||
// a request was buffered. If it was, change the
|
||||
// connection's status to WAIT_STATUS and push it
|
||||
// to the ready queue. If the request is invalid,
|
||||
// close the socket.
|
||||
static void
|
||||
check_request_buffer(HTTP_Server *server, HTTP_ServerConn *conn)
|
||||
{
|
||||
assert(conn->state == HTTP_SERVER_CONN_BUFFERING);
|
||||
|
||||
ByteView src = byte_queue_read_buf(&conn->input);
|
||||
int ret = http_parse_request(src.ptr, src.len, &conn->request);
|
||||
if (ret < 0) {
|
||||
|
||||
// Invalid request
|
||||
byte_queue_read_ack(&conn->input, 0);
|
||||
socket_close(&server->sockets, conn->handle);
|
||||
|
||||
} else if (ret == 0) {
|
||||
|
||||
// Still waiting
|
||||
byte_queue_read_ack(&conn->input, 0);
|
||||
|
||||
// If the queue reached its limit and we still didn't receive
|
||||
// a complete request, abort the exchange.
|
||||
if (byte_queue_full(&conn->input))
|
||||
socket_close(&server->sockets, conn->handle);
|
||||
|
||||
} else {
|
||||
|
||||
// Ready
|
||||
assert(ret == 1);
|
||||
|
||||
conn->state = HTTP_SERVER_CONN_STATUS;
|
||||
conn->request_len = ret;
|
||||
conn->response_offset = byte_queue_offset(&conn->output);
|
||||
|
||||
// Push to the ready queue
|
||||
assert(server->num_ready < HTTP_SERVER_CAPACITY);
|
||||
int tail = (server->ready_head + server->num_ready) % HTTP_SERVER_CAPACITY;
|
||||
server->ready[tail] = conn - server->conns;
|
||||
server->num_ready++;
|
||||
}
|
||||
}
|
||||
|
||||
bool http_server_next_request(HTTP_Server *server,
|
||||
HTTP_Request **request, HTTP_ResponseBuilder *builder)
|
||||
{
|
||||
if (server->num_ready == 0)
|
||||
return false;
|
||||
|
||||
HTTP_ServerConn *conn = &server->conns[server->ready_head];
|
||||
server->ready_head = (server->ready_head + 1) % HTTP_SERVER_CAPACITY;
|
||||
server->num_ready--;
|
||||
|
||||
assert(conn->state == HTTP_SERVER_CONN_WAIT_STATUS);
|
||||
*request = &conn->request;
|
||||
*builder = (HTTP_ResponseBuilder) { server, conn - server->conns, conn->gen };
|
||||
return true;
|
||||
}
|
||||
|
||||
int http_server_process_events(HTTP_Server *server,
|
||||
struct pollfd *polled, int num_polled)
|
||||
{
|
||||
SocketEvent events[HTTP_SERVER_CAPACITY];
|
||||
int num_events = socket_manger_translate_events(&server->sockets, polled, num_polled);
|
||||
if (num_events < 0)
|
||||
return -1;
|
||||
|
||||
for (int i = 0; i < num_events; i++) {
|
||||
|
||||
HTTP_ServerConn *conn = events[i].user;
|
||||
|
||||
if (events[i].type == SOCKET_EVENT_DISCONNECT) {
|
||||
|
||||
http_server_conn_free(conn);
|
||||
server->num_conns--;
|
||||
|
||||
} else if (events[i].type == SOCKET_EVENT_READY) {
|
||||
|
||||
if (events[i].user == NULL) {
|
||||
|
||||
if (server->num_conns == HTTP_SERVER_CAPACITY) {
|
||||
socket_close(&server->sockets, events[i].handle);
|
||||
continue;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (server->conns[i].state != HTTP_SERVER_CONN_FREE) {
|
||||
i++;
|
||||
assert(i < HTTP_SERVER_CAPACITY);
|
||||
}
|
||||
|
||||
conn = &server->conns[i];
|
||||
http_server_conn_init(conn, events[i].handle);
|
||||
server->num_conns++;
|
||||
|
||||
socket_set_user(&server->sockets, events[i].handle, conn);
|
||||
}
|
||||
|
||||
if (conn->state == HTTP_SERVER_CONN_BUFFERING) {
|
||||
|
||||
int min_recv = 1<<10;
|
||||
byte_queue_write_setmincap(&conn->input, min_recv);
|
||||
|
||||
// Note that it's extra important that we don't
|
||||
// buffer while the user is building the response.
|
||||
// If we did that, a resize would invalidate all
|
||||
// pointers on the parsed request structure.
|
||||
int num = 0;
|
||||
ByteView dst = byte_queue_write_buf(&conn->input);
|
||||
if (dst.len) num = socket_recv(&server->sockets, conn->handle, dst.ptr, dst.len);
|
||||
byte_queue_write_ack(&conn->input, num);
|
||||
|
||||
if (byte_queue_error(&conn->output))
|
||||
socket_close(&server->sockets, conn->handle);
|
||||
else
|
||||
check_request_buffer(server, conn);
|
||||
|
||||
} else if (conn->state == HTTP_SERVER_CONN_FLUSHING) {
|
||||
|
||||
int num = 0;
|
||||
ByteView src = byte_queue_read_buf(&conn->output);
|
||||
if (src.len) num = socket_recv(&server->sockets, conn->handle, src.ptr, src.len);
|
||||
byte_queue_read_ack(&conn->output, num);
|
||||
|
||||
if (byte_queue_error(&conn->output))
|
||||
socket_close(&server->sockets, conn->handle);
|
||||
else if (byte_queue_empty(&conn->output)) {
|
||||
// We finished sending the response. Now we can
|
||||
// either close the connection or process a new
|
||||
// buffered request.
|
||||
if (conn->closing) {
|
||||
socket_close(&server->sockets, conn->handle);
|
||||
} else {
|
||||
check_request_buffer(server, conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get a connection pointer from a response builder.
|
||||
// If the builder is invalid, returns NULL.
|
||||
// Note that only connections in the responding states
|
||||
// can be returned, as any builder is invalidated by
|
||||
// incrementing the connection's generation counter
|
||||
// when a response is completed.
|
||||
static HTTP_ServerConn*
|
||||
builder_to_conn(HTTP_ResponseBuilder builder)
|
||||
{
|
||||
HTTP_Server *server = builder.server;
|
||||
if (server == NULL)
|
||||
return NULL;
|
||||
|
||||
Connection *conn = &server->conns[builder.data1];
|
||||
if (conn->gen != builder.data2)
|
||||
if (server->index > HTTP_SERVER_CAPACITY)
|
||||
return NULL;
|
||||
|
||||
HTTP_ServerConn *conn = server->conns[server->index];
|
||||
if (conn->gen != builder.gen)
|
||||
return NULL;
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
void http_response_builder_status(HTTP_ResponseBuilder res, int status)
|
||||
static void
|
||||
write_status(HTTP_ServerConn *conn, int status)
|
||||
{
|
||||
Connection *conn = server_builder_to_conn(res);
|
||||
byte_queue_write(&conn->output, xxx);
|
||||
}
|
||||
|
||||
void http_response_builder_status(HTTP_ResponseBuilder builder, int status)
|
||||
{
|
||||
HTTP_ServerConn *conn = builder_to_conn(builder);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
|
||||
http_engine_status(&conn->engine, status);
|
||||
if (conn->state != HTTP_SERVER_CONN_WAIT_STATUS) {
|
||||
// Reset all response content and start from scrach.
|
||||
byte_queue_remove_from_offset(&conn->output, conn->response_offset);
|
||||
conn->state = HTTP_SERVER_CONN_WAIT_STATUS;
|
||||
}
|
||||
|
||||
void http_response_builder_header(HTTP_ResponseBuilder res, HTTP_String str)
|
||||
write_status(conn, status);
|
||||
|
||||
conn->state = HTTP_SERVER_CONN_WAIT_HEADER;
|
||||
}
|
||||
|
||||
void http_response_builder_header(HTTP_ResponseBuilder builder, String str)
|
||||
{
|
||||
Connection *conn = server_builder_to_conn(res);
|
||||
HTTP_ServerConn *conn = builder_to_conn(builder);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
|
||||
http_engine_header(&conn->engine, str);
|
||||
if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER)
|
||||
return;
|
||||
|
||||
byte_queue_write(&conn->output, xxx);
|
||||
}
|
||||
|
||||
void http_response_builder_body(HTTP_ResponseBuilder res, HTTP_String str)
|
||||
static void append_special_headers(HTTP_ServerConn *conn)
|
||||
{
|
||||
Connection *conn = server_builder_to_conn(res);
|
||||
// TODO
|
||||
}
|
||||
|
||||
static void patch_special_headers(HTTP_ServerConn *conn)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
void http_response_builder_body(HTTP_ResponseBuilder builder, String str)
|
||||
{
|
||||
HTTP_ServerConn *conn = builder_to_conn(builder);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
|
||||
http_engine_body(&conn->engine, str);
|
||||
if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER) {
|
||||
append_special_headers(conn);
|
||||
conn->state = HTTP_SERVER_CONN_WAIT_BODY;
|
||||
}
|
||||
|
||||
void http_response_builder_bodycap(HTTP_ResponseBuilder res, int mincap)
|
||||
if (conn->state != HTTP_SERVER_CONN_WAIT_BODY)
|
||||
return;
|
||||
|
||||
byte_queue_write(&conn->output, str);
|
||||
}
|
||||
|
||||
void http_response_builder_send(HTTP_ResponseBuilder builder, String str)
|
||||
{
|
||||
Connection *conn = server_builder_to_conn(res);
|
||||
HTTP_ServerConn *conn = builder_to_conn(builder);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
|
||||
http_engine_bodycap(&conn->engine, mincap);
|
||||
if (conn->state == HTTP_SERVER_CONN_WAIT_STATUS) {
|
||||
write_status(conn, 500);
|
||||
conn->state = HTTP_SERVER_CONN_WAIT_HEADER;
|
||||
}
|
||||
|
||||
char *http_response_builder_bodybuf(HTTP_ResponseBuilder res, int *cap)
|
||||
{
|
||||
Connection *conn = server_builder_to_conn(res);
|
||||
if (conn == NULL) {
|
||||
*cap = 0;
|
||||
return NULL;
|
||||
if (conn->state == HTTP_SERVER_CONN_WAIT_HEADER) {
|
||||
append_special_headers(conn);
|
||||
conn->state = HTTP_SERVER_CONN_WAIT_BODY;
|
||||
}
|
||||
|
||||
return http_engine_bodybuf(&conn->engine, cap);
|
||||
}
|
||||
assert(conn->state == HTTP_SERVER_CONN_WAIT_BODY);
|
||||
patch_special_headers(conn);
|
||||
|
||||
void http_response_builder_bodyack(HTTP_ResponseBuilder res, int num)
|
||||
{
|
||||
Connection *conn = server_builder_to_conn(res);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
|
||||
http_engine_bodyack(&conn->engine, num);
|
||||
}
|
||||
|
||||
void http_response_builder_undo(HTTP_ResponseBuilder res)
|
||||
{
|
||||
Connection *conn = server_builder_to_conn(res);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
|
||||
http_engine_undo(&conn->engine);
|
||||
}
|
||||
|
||||
void http_response_builder_done(HTTP_ResponseBuilder res)
|
||||
{
|
||||
HTTP_Server *server = res.data0;
|
||||
Connection *conn = server_builder_to_conn(res);
|
||||
if (conn == NULL)
|
||||
return;
|
||||
|
||||
http_engine_done(&conn->engine);
|
||||
// Remove the buffered request
|
||||
byte_queue_read_ack(&conn->input, conn->request_len);
|
||||
|
||||
conn->state = HTTP_SERVER_CONN_FLUSHING;
|
||||
conn->gen++;
|
||||
if (conn->gen == 0 || conn->gen == UINT16_MAX)
|
||||
conn->gen = 1;
|
||||
|
||||
HTTP_EngineState state = http_engine_state(&conn->engine);
|
||||
|
||||
if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) {
|
||||
int tail = (server->ready_head + server->ready_count) % MAX_CONNS;
|
||||
server->ready[tail] = res.data1;
|
||||
server->ready_count++;
|
||||
}
|
||||
|
||||
if (state == HTTP_ENGINE_STATE_SERVER_CLOSED)
|
||||
socket_pool_close(server->socket_pool, conn->sock);
|
||||
}
|
||||
+164
-25
@@ -1,35 +1,174 @@
|
||||
#ifndef HTTP_SERVER_INCLUDED
|
||||
#define HTTP_SERVER_INCLUDED
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "parse.h"
|
||||
#ifndef HTTP_SERVER_CAPACITY
|
||||
// The maximum ammount of requests that can be handled
|
||||
// in parallel.
|
||||
#define HTTP_SERVER_CAPACITY (1<<9)
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
|
||||
// This struct is unused
|
||||
HTTP_SERVER_CONN_FREE,
|
||||
|
||||
// No request was buffered yet.
|
||||
HTTP_SERVER_CONN_BUFFERING,
|
||||
|
||||
// A request was just buffered and is waiting for
|
||||
// the user to build a response. To be specific,
|
||||
// it's waiting for the user to set a response status.
|
||||
HTTP_SERVER_CONN_WAIT_STATUS,
|
||||
|
||||
// A request is buffered and a status was set. Now
|
||||
// the user can set a header or append the first
|
||||
// bytes of the response body.
|
||||
HTTP_SERVER_CONN_WAIT_HEADER,
|
||||
|
||||
// A request is buffered and some bytes were appended
|
||||
// to the response. Now the user can either append more
|
||||
// bytes or send out the response.
|
||||
HTTP_SERVER_CONN_WAIT_BODY,
|
||||
|
||||
// A response has been produced and it's being flushed.
|
||||
HTTP_SERVER_CONN_FLUSHING,
|
||||
|
||||
} HTTP_ServerConnState;
|
||||
|
||||
// This structure represents the HTTP connection to
|
||||
// a client.
|
||||
typedef struct {
|
||||
void *data0;
|
||||
int data1;
|
||||
int data2;
|
||||
|
||||
// If false, this struct is unused
|
||||
HTTP_ServerConnState state;
|
||||
|
||||
// Handle to the socket
|
||||
SocketHandle handle;
|
||||
|
||||
// Data received by the client
|
||||
ByteQueue input;
|
||||
|
||||
// Data being sent to the client
|
||||
ByteQueue output;
|
||||
|
||||
// When the state is WAIT_STATUS, WAIT_HEADER,
|
||||
// or WAIT_BODY, this contains the parsed version
|
||||
// of the buffered request.
|
||||
HTTP_Request request;
|
||||
|
||||
// Length of the buffered request when the request
|
||||
// field is valid.
|
||||
int request_len;
|
||||
|
||||
// Offset of the first response byte in the output
|
||||
// buffer. This is useful when the user wants to
|
||||
// undo the response it's building and start from
|
||||
// scratch.
|
||||
ByteQueueOffset response_offset;
|
||||
|
||||
} HTTP_ServerConn;
|
||||
|
||||
typedef struct {
|
||||
|
||||
// Array of connections. The counter contains the
|
||||
// number of structs such that state=FREE.
|
||||
int num_conns;
|
||||
HTTP_ServerConn conns[HTTP_SERVER_CAPACITY];
|
||||
|
||||
// Queue of indices referring to connections that
|
||||
// are in the WAIT_STATUS state.
|
||||
int num_ready;
|
||||
int ready_head;
|
||||
int ready[HTTP_SERVER_CAPACITY];
|
||||
|
||||
// Asynchronous TCP and TLS socket abstraction
|
||||
SocketManager sockets;
|
||||
|
||||
// The server object doesn't interact with this
|
||||
// field directly, it just initializes the socket
|
||||
// manager with a pointer to it. This allows
|
||||
// allocating the exact number of sockets we
|
||||
// will need.
|
||||
Socket socket_pool[HTTP_SERVER_CAPACITY];
|
||||
|
||||
} HTTP_Server;
|
||||
|
||||
// Initialize the HTTP server object. By default, it won't
|
||||
// listen for connections. You need to call
|
||||
//
|
||||
// http_server_listen_tcp
|
||||
// http_server_listen_tls
|
||||
//
|
||||
// to listen for connection. Note that you can have a
|
||||
// single server listening for HTTP and HTTPS requests
|
||||
// by calling both.
|
||||
int http_server_init(HTTP_Server *server);
|
||||
|
||||
// Release resources associated to the server.
|
||||
void http_server_free(HTTP_Server *server);
|
||||
|
||||
// Enable listening for plain HTTP requests at the
|
||||
// specified interface.
|
||||
int http_server_listen_tcp(HTTP_Server *server,
|
||||
String addr, Port port);
|
||||
|
||||
// Enable listening for HTTPS requests at the specified
|
||||
// interfact, using the specified certificate and key
|
||||
// to verify the connection.
|
||||
int http_server_listen_tls(HTTP_Server *server,
|
||||
String addr, Port port, String cert_file_name,
|
||||
String key_file_name);
|
||||
|
||||
// Add the certificate for an additional domain when
|
||||
// the server is listening for HTTPS requests.
|
||||
int http_server_add_certificate(HTTP_Server *server,
|
||||
String domain, String cert_file, String key_file);
|
||||
|
||||
// When a thread is blocked waiting for server events,
|
||||
// other threads can call this function to wake it up.
|
||||
int http_server_wakeup(HTTP_Server *server);
|
||||
|
||||
// List all low-level socket events the server is
|
||||
// waiting for such that the caller can call poll()
|
||||
// with it.
|
||||
int http_server_register_events(HTTP_Server *server,
|
||||
struct pollfd *polled, int max_polled);
|
||||
|
||||
// The caller has waited for poll() to return and some
|
||||
// I/O events to be triggered, so now the HTTP server
|
||||
// can continue its buffering and flushing operations.
|
||||
int http_server_process_events(HTTP_Server *server,
|
||||
struct pollfd *polled, int num_polled);
|
||||
|
||||
typedef struct {
|
||||
HTTP_Server *server;
|
||||
uint16_t index;
|
||||
uint16_t gen;
|
||||
} HTTP_ResponseBuilder;
|
||||
|
||||
typedef struct HTTP_Server HTTP_Server;
|
||||
// After some I/O events were processes, some requests
|
||||
// may be availabe. This function returns one of the
|
||||
// buffered requests. If a request was available, true
|
||||
// is returned. If no more are avaiable, false is returned.
|
||||
// Note that It's possible to get multiple requests to
|
||||
// respond in batches.
|
||||
// For each request returned by this function, the user
|
||||
// must build a response using the response builder API.
|
||||
bool http_server_next_request(HTTP_Server *server,
|
||||
HTTP_Request **request, HTTP_ResponseBuilder *builder);
|
||||
|
||||
HTTP_Server *http_server_init(HTTP_String addr, uint16_t port);
|
||||
// This function is called to set the status code of
|
||||
// a request's response. If this function is called
|
||||
// after the other response builder functions, it will
|
||||
// reset the response and set a new status.
|
||||
void http_response_builder_status(HTTP_ResponseBuilder builder, int status);
|
||||
|
||||
HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port,
|
||||
uint16_t secure_port, HTTP_String cert_key, HTTP_String private_key);
|
||||
// Append a header to the response. This can only be
|
||||
// used after having set the status and before appending
|
||||
// to the body.
|
||||
void http_response_builder_header(HTTP_ResponseBuilder builder, String str);
|
||||
|
||||
void http_server_free (HTTP_Server *server);
|
||||
int http_server_wait (HTTP_Server *server, HTTP_Request **req, HTTP_ResponseBuilder *handle);
|
||||
int http_server_add_website (HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file);
|
||||
void http_response_builder_status (HTTP_ResponseBuilder res, int status);
|
||||
void http_response_builder_header (HTTP_ResponseBuilder res, HTTP_String str);
|
||||
void http_response_builder_body (HTTP_ResponseBuilder res, HTTP_String str);
|
||||
void http_response_builder_bodycap (HTTP_ResponseBuilder res, int mincap);
|
||||
char* http_response_builder_bodybuf (HTTP_ResponseBuilder res, int *cap);
|
||||
void http_response_builder_bodyack (HTTP_ResponseBuilder res, int num);
|
||||
void http_response_builder_undo (HTTP_ResponseBuilder res);
|
||||
void http_response_builder_done (HTTP_ResponseBuilder res);
|
||||
// Append some bytes to the response's body
|
||||
void http_response_builder_body(HTTP_ResponseBuilder builder, String str);
|
||||
|
||||
#endif // HTTP_SERVER_INCLUDED
|
||||
// Mark the response as complete. This will invalidate
|
||||
// the response builder handle.
|
||||
void http_response_builder_send(HTTP_ResponseBuilder builder, String str);
|
||||
|
||||
+748
-615
File diff suppressed because it is too large
Load Diff
+282
-55
@@ -1,75 +1,302 @@
|
||||
#ifndef SOCKET_INCLUDED
|
||||
#define SOCKET_INCLUDED
|
||||
// This file (and its relative .c file) implements an asynchronous TCP/TLS
|
||||
// server and client abstraction.
|
||||
//
|
||||
// It introduces the concept of a "socket manager", which is a pool of
|
||||
// connection sockets and a listener socket. The listener is managed
|
||||
// internally, which means the manager automatically accepts sockets
|
||||
// from it and adds them to the pool.
|
||||
//
|
||||
// If the listener is configured using the function:
|
||||
//
|
||||
// socket_manager_listen_tcp
|
||||
//
|
||||
// the resulting connections will not use TLS. If instead the listener
|
||||
// is configured using:
|
||||
//
|
||||
// socket_manager_listen_tls
|
||||
//
|
||||
// the listener will use TLS. Note that both functions can be used on
|
||||
// the same manager to allow both plaintext and encrypted connections.
|
||||
// Users may enable zero listeners, in which case only outgoing
|
||||
// connections are allowed (more on this later).
|
||||
//
|
||||
// Once the manager is set up, one can wait for events by following
|
||||
// this pattern:
|
||||
//
|
||||
// struct pollfd polled[...];
|
||||
// int num_polled = socket_manager_register_events(sm, polled, max_polled);
|
||||
// poll(polled, num_polled, -1);
|
||||
//
|
||||
// #define MAX_EVENTS ...
|
||||
// SocketEvent events[MAX_EVENTS];
|
||||
// int num_events = socket_manager_translate_events(sm, events, MAX_EVENTS, polled, num_polled);
|
||||
// for (int i = 0; i < num_events; i++) {
|
||||
// ... Here call socket_recv, socket_send, socket_close, ...
|
||||
// }
|
||||
//
|
||||
// Note that from the user's perspective, there is no difference
|
||||
// between connections that use plain TCP and those that use TCP/TLS.
|
||||
//
|
||||
// Users can also establish outgoing connections by calling the
|
||||
// function:
|
||||
//
|
||||
// socket_connect
|
||||
//
|
||||
// Which allows the creation of a connection towards an host given
|
||||
// its domain, IPv4, IPv6, or an array of them. This can be done both
|
||||
// for TCP and TCP/TLS connection. Note that users that only intend
|
||||
// to establish outgoing connection may omit the configuration of
|
||||
// listeners entirely.
|
||||
|
||||
// This module implements the socket state machine to encapsulate
|
||||
// the complexity of non-blocking TCP and TLS sockets.
|
||||
//
|
||||
// A socket is represented by the "Socket" structure, which may
|
||||
// be in a number of states. As far as an user of the interface
|
||||
// is concerned, the socket may be DIED, READY, or in an internal
|
||||
// state that requires waiting for an event. Therefore, if the
|
||||
// socket is not DIED or READY, the user needs to wait for the
|
||||
// events specified in the [socket->events] field, then call the
|
||||
// socket_update function. At some point the socket will become
|
||||
// either READY or DIED.
|
||||
//
|
||||
// When the socket reaches the DIED state, the user must call
|
||||
// socket_free.
|
||||
//
|
||||
// If the socket is ESTABLISHED_READY, the user may call socket_read,
|
||||
// socket_write, or socket_close on it.
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "sec.h"
|
||||
#include "parse.h"
|
||||
#include "socket_raw.h"
|
||||
#ifdef _WIN32
|
||||
#define NATIVE_SOCKET SOCKET
|
||||
#define NATIVE_SOCKET_INVALID SOCKET_ERROR
|
||||
#define CLOSE_NATIVE_SOCKET closesocket
|
||||
#else
|
||||
#define NATIVE_SOCKET int
|
||||
#define NATIVE_SOCKET_INVALID -1
|
||||
#define CLOSE_NATIVE_SOCKET close
|
||||
#endif
|
||||
|
||||
typedef struct PendingConnect PendingConnect;
|
||||
typedef uint32_t SocketHandle;
|
||||
#define SOCKET_HANDLE_INVALID ((SocketHandle) 0)
|
||||
|
||||
typedef struct {
|
||||
char *ptr;
|
||||
int len;
|
||||
} String;
|
||||
|
||||
typedef uint16_t Port;
|
||||
|
||||
typedef struct {
|
||||
uint32_t data;
|
||||
} IPv4;
|
||||
|
||||
typedef struct {
|
||||
uint16_t data[8];
|
||||
} IPv6;
|
||||
|
||||
// These should only be relevant to socket.c
|
||||
typedef enum {
|
||||
SOCKET_EVENT_READY,
|
||||
SOCKET_EVENT_DISCONNECT,
|
||||
} SocketEventType;
|
||||
|
||||
typedef struct {
|
||||
SocketEventType type;
|
||||
SocketHandle handle;
|
||||
void* user;
|
||||
} SocketEvent;
|
||||
|
||||
// Internal use only
|
||||
typedef enum {
|
||||
|
||||
// The Socket struct is unused
|
||||
SOCKET_STATE_FREE,
|
||||
SOCKET_STATE_DIED,
|
||||
SOCKET_STATE_ESTABLISHED_WAIT,
|
||||
SOCKET_STATE_ESTABLISHED_READY,
|
||||
|
||||
// The state associated to a socket created
|
||||
// by a connect operation that hasn't been
|
||||
// processed yet.
|
||||
SOCKET_STATE_PENDING,
|
||||
SOCKET_STATE_ACCEPTED,
|
||||
SOCKET_STATE_CONNECTED,
|
||||
|
||||
// A connect() operation was started but is
|
||||
// still pending.
|
||||
SOCKET_STATE_CONNECTING,
|
||||
|
||||
// The connection was estabished, but a TLS
|
||||
// handshake may need to be performed.
|
||||
SOCKET_STATE_CONNECTED,
|
||||
|
||||
// The connection was esablished, but the user
|
||||
// wants to perform a read or write operation that
|
||||
// would block.
|
||||
SOCKET_STATE_ESTABLISHED_WAIT,
|
||||
|
||||
// The connection was established and it's possible
|
||||
// to perform read or write operations on it without
|
||||
// blocking.
|
||||
SOCKET_STATE_ESTABLISHED_READY,
|
||||
|
||||
// The socket was marked to be closed.
|
||||
SOCKET_STATE_SHUTDOWN,
|
||||
|
||||
// The current socket is was closed. The only
|
||||
// valid thing to do here is free its resources.
|
||||
SOCKET_STATE_DIED,
|
||||
} SocketState;
|
||||
|
||||
// Internal use only
|
||||
typedef struct {
|
||||
union {
|
||||
IPv4 ipv4;
|
||||
IPv6 ipv6;
|
||||
};
|
||||
bool is_ipv4;
|
||||
Port port;
|
||||
} AddressAndPort;
|
||||
|
||||
// Internal use only
|
||||
typedef struct {
|
||||
SocketState state;
|
||||
|
||||
RAW_SOCKET raw;
|
||||
// OS-specific socket type
|
||||
NATIVE_SOCKET sock;
|
||||
|
||||
// Native socket events that need to be monitored
|
||||
int events;
|
||||
|
||||
void *user_data;
|
||||
PendingConnect *pending_connect;
|
||||
|
||||
#ifdef HTTPS_ENABLED
|
||||
SSL *ssl;
|
||||
#endif
|
||||
|
||||
SecureContext *sec;
|
||||
// User-provided context pointer
|
||||
void *user;
|
||||
|
||||
// A single connect operation may involve
|
||||
// trying to establish a connection towards
|
||||
// one of a set of addresses.
|
||||
int num_addr;
|
||||
int next_addr;
|
||||
union {
|
||||
AddressAndPort addr; // When num_addr=1
|
||||
AddressAndPort *addrs; // Dynamically allocated when num_addr>1
|
||||
};
|
||||
} Socket;
|
||||
|
||||
void socket_connect(Socket *sock, SecureContext *sec, HTTP_String hostname, uint16_t port, void *user_data);
|
||||
void socket_connect_ipv4(Socket *sock, SecureContext *sec, HTTP_IPv4 addr, uint16_t port, void *user_data);
|
||||
void socket_connect_ipv6(Socket *sock, SecureContext *sec, HTTP_IPv6 addr, uint16_t port, void *user_data);
|
||||
void socket_accept(Socket *sock, SecureContext *sec, RAW_SOCKET raw);
|
||||
void socket_update(Socket *sock);
|
||||
void socket_close(Socket *sock);
|
||||
bool socket_ready(Socket *sock);
|
||||
bool socket_died(Socket *sock);
|
||||
int socket_read(Socket *sock, char *dst, int max);
|
||||
int socket_write(Socket *sock, char *src, int len);
|
||||
void socket_free(Socket *sock);
|
||||
bool socket_secure(Socket *sock);
|
||||
void socket_set_user_data(Socket *sock, void *user_data);
|
||||
void* socket_get_user_data(Socket *sock);
|
||||
// Glorified array of sockets. This structure
|
||||
// is private to the .c file associated to this
|
||||
// header.
|
||||
typedef struct {
|
||||
|
||||
#endif // SOCKET_INCLUDED
|
||||
// This guards access to the main thread using
|
||||
// the manager from other threads calling the
|
||||
// wakeup function.
|
||||
Mutex mutex;
|
||||
|
||||
// TCP listener sockets. The first is intended
|
||||
// for plaintext, while the second is for TLS.
|
||||
// The socket manager will accept and add new
|
||||
// sockets to the pool automatically. Note that
|
||||
// either may be unset. If both are unset, users
|
||||
// can only create outgoing connections.
|
||||
NATIVE_SOCKET plain_sock;
|
||||
NATIVE_SOCKET secure_sock;
|
||||
|
||||
// Handles for the self-pipe trick necessary for
|
||||
// other threads to wake up sockets blocked on
|
||||
// poll().
|
||||
NATIVE_SOCKET wait_sock;
|
||||
NATIVE_SOCKET signal_sock;
|
||||
|
||||
// TLS contexts. One is used for outgoing connections
|
||||
// (the client context) and one for incoming
|
||||
// connections (server). If the secure_sock is
|
||||
// set, the server context is initialized. If at
|
||||
// least one connect was performed using TLS
|
||||
// (and the flag is set), the client context is
|
||||
// initialized.
|
||||
bool at_least_one_secure_connect;
|
||||
ClientSecureContext client_secure_context;
|
||||
ServerSecureContext server_secure_context;
|
||||
|
||||
// Array of sockets. Structs with state FREE
|
||||
// are unused.
|
||||
int num_used;
|
||||
int max_used;
|
||||
Socket *sockets;
|
||||
|
||||
} SocketManager;
|
||||
|
||||
// Instanciate a socket manager. Returns 0 on
|
||||
// success and -1 on error.
|
||||
int socket_manager_init(SocketManager *sm, Socket *socks,
|
||||
int num_socks);
|
||||
|
||||
// Deinitialize a socket manager
|
||||
void socket_manager_free(SocketManager *sm);
|
||||
|
||||
// Configure the socket manager to listen on
|
||||
// the specified interface for TCP connections.
|
||||
// Incoming connections will be automatically
|
||||
// added to the internal pool. This function
|
||||
// can only be used once per manager.
|
||||
// Returns 0 on success, -1 on error.
|
||||
int socket_manager_listen_tcp(SocketManager *sm,
|
||||
String addr, Port port);
|
||||
|
||||
// Same as the previous function, but incoming
|
||||
// connections will be interpreted as TLS. You
|
||||
// can only call this function once per manager,
|
||||
// but you can call this and the plaintext variant
|
||||
// on the same manager to accept both plaintext
|
||||
// and secure connections.
|
||||
// Returns 0 on success, -1 on error.
|
||||
int socket_manager_listen_tls(SocketManager *sm,
|
||||
String addr, Port port, String cert_file_name,
|
||||
String key_file_name);
|
||||
|
||||
// If the socket manager was configures to accept
|
||||
// TLS connections, this adds additional certificates
|
||||
// the client can use to verify the server's
|
||||
// authenticity.
|
||||
// Returns 0 on success, -1 on error.
|
||||
int socket_manager_add_certificate(SocketManager *sm,
|
||||
String domain, String cert_file, String key_file);
|
||||
|
||||
// When a thread is blocked on a poll() call for
|
||||
// descriptors associated to this socket manager,
|
||||
// other threads can call this function to wake
|
||||
// up that blocked thread.
|
||||
// Returns 0 on success, -1 on error.
|
||||
int socket_manager_wakeup(SocketManager *sm);
|
||||
|
||||
// Writes to the polled array the list of sockets
|
||||
// that the socket manager is monitoring and which
|
||||
// events associated to that socket.
|
||||
// Returns the number of items written to the polled
|
||||
// array.
|
||||
int socket_manager_register_events(SocketManager *sm,
|
||||
struct pollfd *polled, int max_polled);
|
||||
|
||||
// After poll() is called on the previously registered
|
||||
// pollfd array and the revents fields are set, this
|
||||
// function processes those events to produce higher-level
|
||||
// socket events. Returns the number of socket events
|
||||
// written to the output array, or -1 on error.
|
||||
int socket_manager_translate_events(SocketManager *sm,
|
||||
SocketEvent *events, int max_events, struct pollfd *polled,
|
||||
int num_polled);
|
||||
|
||||
typedef enum {
|
||||
CONNECT_TARGET_NAME,
|
||||
CONNECT_TARGET_IPV4,
|
||||
CONNECT_TARGET_IPV6,
|
||||
} ConnectTargetType;
|
||||
|
||||
typedef struct {
|
||||
ConnectTargetType type;
|
||||
Port port;
|
||||
union {
|
||||
IPv4 ipv4;
|
||||
IPv6 ipv6;
|
||||
String name;
|
||||
};
|
||||
} ConnectTarget;
|
||||
|
||||
// Connect to one of the given targets. The socket
|
||||
// manager will try to connecting to addresses until
|
||||
// one succedes. If secure=true, the socket uses TLS.
|
||||
// Returns 0 on success, -1 on error.
|
||||
int socket_connect(SocketManager *sm, int num_targets,
|
||||
ConnectTarget *targets, bool secure, void *user);
|
||||
|
||||
int socket_recv(SocketManager *sm, SocketHandle handle,
|
||||
char *dst, int max);
|
||||
|
||||
int socket_send(SocketManager *sm, SocketHandle handle,
|
||||
char *src, int len);
|
||||
|
||||
void socket_close(SocketManager *sm, SocketHandle handle);
|
||||
|
||||
// Returns -1 on error, 0 if the socket was accepted
|
||||
// from the plaintext listener, or 1 if it was accepted
|
||||
// by the secure listener.
|
||||
int socket_is_secure(SocketManager *sm, SocketHandle handle);
|
||||
|
||||
// Set the user pointer of a socket
|
||||
void socket_set_user(SocketManager *sm, SocketHandle handle);
|
||||
|
||||
@@ -1,357 +0,0 @@
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "socket_pool.h"
|
||||
#endif
|
||||
|
||||
#define SOCKET_HARD_LIMIT (1<<10)
|
||||
#define MAX_CERTS 10
|
||||
|
||||
struct SocketPool {
|
||||
|
||||
SecureContext sec;
|
||||
|
||||
RAW_SOCKET listen_sock;
|
||||
RAW_SOCKET secure_sock;
|
||||
|
||||
int num_socks;
|
||||
int max_socks;
|
||||
Socket socks[];
|
||||
};
|
||||
|
||||
int socket_pool_global_init(void)
|
||||
{
|
||||
int ret = socket_raw_global_init();
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
secure_context_global_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void socket_pool_global_free(void)
|
||||
{
|
||||
secure_context_global_free();
|
||||
socket_raw_global_free();
|
||||
}
|
||||
|
||||
SocketPool *socket_pool_init(HTTP_String addr,
|
||||
uint16_t port, uint16_t secure_port, int max_socks,
|
||||
bool reuse_addr, int backlog, HTTP_String cert_file,
|
||||
HTTP_String key_file)
|
||||
{
|
||||
if (max_socks > SOCKET_HARD_LIMIT)
|
||||
return NULL;
|
||||
|
||||
SocketPool *pool = malloc(sizeof(SocketPool) + max_socks * sizeof(Socket));
|
||||
if (pool == NULL)
|
||||
return NULL;
|
||||
|
||||
pool->num_socks = 0;
|
||||
pool->max_socks = max_socks;
|
||||
|
||||
for (int i = 0; i < pool->max_socks; i++)
|
||||
pool->socks[i].state = SOCKET_STATE_FREE;
|
||||
|
||||
if (port == 0)
|
||||
pool->listen_sock = BAD_SOCKET;
|
||||
else {
|
||||
pool->listen_sock = listen_socket(addr, port, reuse_addr, backlog);
|
||||
if (pool->listen_sock == BAD_SOCKET) {
|
||||
free(pool);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (secure_port == 0)
|
||||
pool->secure_sock = BAD_SOCKET;
|
||||
else {
|
||||
#ifndef HTTPS_ENABLED
|
||||
(void) cert_file;
|
||||
(void) key_file;
|
||||
if (pool->listen_sock != BAD_SOCKET)
|
||||
CLOSE_SOCKET(pool->listen_sock);
|
||||
free(pool);
|
||||
return NULL;
|
||||
#else
|
||||
if (secure_context_init_as_server(&pool->sec, cert_file, key_file) < 0) {
|
||||
if (pool->listen_sock != BAD_SOCKET)
|
||||
CLOSE_SOCKET(pool->listen_sock);
|
||||
free(pool);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pool->secure_sock = listen_socket(addr, secure_port, reuse_addr, backlog);
|
||||
if (pool->secure_sock == BAD_SOCKET) {
|
||||
if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock);
|
||||
free(pool);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef HTTPS_ENABLED
|
||||
if (port == 0 && secure_port == 0) {
|
||||
if (secure_context_init_as_client(&pool->sec) < 0) {
|
||||
if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock);
|
||||
if (pool->secure_sock != BAD_SOCKET) CLOSE_SOCKET(pool->secure_sock);
|
||||
free(pool);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < max_socks; i++)
|
||||
pool->socks[i].state = SOCKET_STATE_FREE;
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
void socket_pool_free(SocketPool *pool)
|
||||
{
|
||||
for (int i = 0, j = 0; j < pool->num_socks; i++) {
|
||||
|
||||
Socket *sock = &pool->socks[i];
|
||||
|
||||
if (sock->state == SOCKET_STATE_FREE)
|
||||
continue;
|
||||
j++;
|
||||
|
||||
socket_free(sock);
|
||||
}
|
||||
|
||||
secure_context_free(&pool->sec);
|
||||
|
||||
if (pool->secure_sock != BAD_SOCKET) CLOSE_SOCKET(pool->secure_sock);
|
||||
if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock);
|
||||
}
|
||||
|
||||
int socket_pool_add_cert(SocketPool *pool, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file)
|
||||
{
|
||||
return secure_context_add_cert(&pool->sec, domain, cert_file, key_file);
|
||||
}
|
||||
|
||||
void socket_pool_set_user_data(SocketPool *pool, SocketHandle handle, void *user_data)
|
||||
{
|
||||
Socket *sock = &pool->socks[handle];
|
||||
socket_set_user_data(sock, user_data);
|
||||
}
|
||||
|
||||
void socket_pool_close(SocketPool *pool, SocketHandle handle)
|
||||
{
|
||||
Socket *sock = &pool->socks[handle];
|
||||
socket_close(sock);
|
||||
}
|
||||
|
||||
static Socket *find_free_socket(SocketPool *pool)
|
||||
{
|
||||
if (pool->num_socks == pool->max_socks)
|
||||
return NULL;
|
||||
|
||||
int i = 0;
|
||||
while (pool->socks[i].state != SOCKET_STATE_FREE)
|
||||
i++;
|
||||
|
||||
return &pool->socks[i];
|
||||
}
|
||||
|
||||
int socket_pool_connect(SocketPool *pool, bool secure,
|
||||
HTTP_String addr, uint16_t port, void *user_data)
|
||||
{
|
||||
Socket *sock = find_free_socket(pool);
|
||||
if (sock == NULL)
|
||||
return -1;
|
||||
|
||||
socket_connect(sock, secure ? &pool->sec : NULL, addr, port, user_data);
|
||||
|
||||
if (socket_died(sock)) {
|
||||
socket_free(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pool->num_socks++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int socket_pool_connect_ipv4(SocketPool *pool, bool secure,
|
||||
HTTP_IPv4 addr, uint16_t port, void *user_data)
|
||||
{
|
||||
Socket *sock = find_free_socket(pool);
|
||||
if (sock == NULL)
|
||||
return -1;
|
||||
|
||||
socket_connect_ipv4(sock, secure ? &pool->sec : NULL, addr, port, user_data);
|
||||
|
||||
if (socket_died(sock)) {
|
||||
socket_free(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pool->num_socks++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int socket_pool_connect_ipv6(SocketPool *pool, bool secure,
|
||||
HTTP_IPv6 addr, uint16_t port, void *user_data)
|
||||
{
|
||||
Socket *sock = find_free_socket(pool);
|
||||
if (sock == NULL)
|
||||
return -1;
|
||||
|
||||
socket_connect_ipv6(sock, secure ? &pool->sec : NULL, addr, port, user_data);
|
||||
|
||||
if (socket_died(sock)) {
|
||||
socket_free(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pool->num_socks++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#include <stdio.h> // TODO: remove
|
||||
|
||||
SocketEvent socket_pool_wait(SocketPool *pool)
|
||||
{
|
||||
for (;;) {
|
||||
|
||||
// First, iterate over all sockets to find one that
|
||||
// died or is ready.
|
||||
|
||||
for (int i = 0, j = 0; j < pool->num_socks; i++) {
|
||||
|
||||
Socket *sock = &pool->socks[i];
|
||||
|
||||
if (sock->state == SOCKET_STATE_FREE)
|
||||
continue;
|
||||
j++;
|
||||
|
||||
if (socket_died(sock)) {
|
||||
void *user_data = socket_get_user_data(sock);
|
||||
socket_free(sock);
|
||||
pool->num_socks--;
|
||||
return (SocketEvent) { SOCKET_EVENT_DIED, -1, user_data };
|
||||
}
|
||||
|
||||
if (socket_ready(sock))
|
||||
return (SocketEvent) { SOCKET_EVENT_READY, i, socket_get_user_data(sock) };
|
||||
|
||||
assert(sock->events);
|
||||
}
|
||||
|
||||
// If we reached this point, we either have no sockets
|
||||
// or all sockets need to wait for some event. Waiting
|
||||
// when no sockets are available is only allowed when
|
||||
// the pool is in server mode.
|
||||
|
||||
int indices[SOCKET_HARD_LIMIT+2];
|
||||
struct pollfd polled[SOCKET_HARD_LIMIT+2];
|
||||
int num_polled = 0;
|
||||
|
||||
if (pool->num_socks < pool->max_socks) {
|
||||
|
||||
if (pool->listen_sock != BAD_SOCKET) {
|
||||
indices[num_polled] = -1;
|
||||
polled[num_polled].fd = pool->listen_sock;
|
||||
polled[num_polled].events = POLLIN;
|
||||
polled[num_polled].revents = 0;
|
||||
num_polled++;
|
||||
}
|
||||
|
||||
if (pool->secure_sock != BAD_SOCKET) {
|
||||
indices[num_polled] = -1;
|
||||
polled[num_polled].fd = pool->secure_sock;
|
||||
polled[num_polled].events = POLLIN;
|
||||
polled[num_polled].revents = 0;
|
||||
num_polled++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0, j = 0; j < pool->num_socks; i++) {
|
||||
|
||||
Socket *sock = &pool->socks[i];
|
||||
|
||||
if (sock->state == SOCKET_STATE_FREE)
|
||||
continue;
|
||||
j++;
|
||||
|
||||
indices[num_polled] = i;
|
||||
polled[num_polled].fd = sock->raw;
|
||||
polled[num_polled].events = sock->events;
|
||||
polled[num_polled].revents = 0;
|
||||
num_polled++;
|
||||
}
|
||||
|
||||
if (num_polled == 0)
|
||||
return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL };
|
||||
|
||||
int ret = POLL(polled, num_polled, -1);
|
||||
if (ret < 0) {
|
||||
|
||||
if (errno == EINTR)
|
||||
return (SocketEvent) { SOCKET_EVENT_SIGNAL, -1, NULL };
|
||||
|
||||
return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL };
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_polled; i++) {
|
||||
|
||||
Socket *sock;
|
||||
|
||||
if (polled[i].fd == pool->listen_sock || polled[i].fd == pool->secure_sock) {
|
||||
|
||||
bool secure = false;
|
||||
if (polled[i].fd == pool->secure_sock)
|
||||
secure = true;
|
||||
|
||||
Socket *sock = find_free_socket(pool);
|
||||
if (sock == NULL)
|
||||
continue;
|
||||
|
||||
RAW_SOCKET raw = accept(polled[i].fd, NULL, NULL);
|
||||
if (raw == BAD_SOCKET)
|
||||
continue;
|
||||
|
||||
socket_accept(sock, secure ? &pool->sec : NULL, raw);
|
||||
|
||||
if (socket_died(sock)) {
|
||||
socket_free(sock);
|
||||
continue;
|
||||
}
|
||||
|
||||
pool->num_socks++;
|
||||
|
||||
} else {
|
||||
int j = indices[i];
|
||||
sock = &pool->socks[j];
|
||||
|
||||
if (polled[i].revents)
|
||||
socket_update(sock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This branch is unreachable
|
||||
return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL };
|
||||
}
|
||||
|
||||
int socket_pool_read(SocketPool *pool, SocketHandle handle, char *dst, int len)
|
||||
{
|
||||
return socket_read(&pool->socks[handle], dst, len);
|
||||
}
|
||||
|
||||
int socket_pool_write(SocketPool *pool, SocketHandle handle, char *src, int len)
|
||||
{
|
||||
return socket_write(&pool->socks[handle], src, len);
|
||||
}
|
||||
|
||||
bool socket_pool_secure(SocketPool *pool, SocketHandle handle)
|
||||
{
|
||||
return socket_secure(&pool->socks[handle]);
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
#ifndef SOCKET_POOL_INCLUDED
|
||||
#define SOCKET_POOL_INCLUDED
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "basic.h"
|
||||
#include "socket.h"
|
||||
#include "socket_raw.h"
|
||||
#endif
|
||||
|
||||
typedef struct SocketPool SocketPool;
|
||||
|
||||
typedef int SocketHandle;
|
||||
|
||||
typedef enum {
|
||||
SOCKET_EVENT_DIED,
|
||||
SOCKET_EVENT_READY,
|
||||
SOCKET_EVENT_ERROR,
|
||||
SOCKET_EVENT_SIGNAL,
|
||||
} SocketEventType;
|
||||
|
||||
typedef struct {
|
||||
SocketEventType type;
|
||||
SocketHandle handle;
|
||||
void *user_data;
|
||||
} SocketEvent;
|
||||
|
||||
int socket_pool_global_init(void);
|
||||
void socket_pool_global_free(void);
|
||||
|
||||
SocketPool *socket_pool_init(HTTP_String addr,
|
||||
uint16_t port, uint16_t secure_port, int max_socks,
|
||||
bool reuse_addr, int backlog, HTTP_String cert_file,
|
||||
HTTP_String key_file);
|
||||
|
||||
void socket_pool_free(SocketPool *pool);
|
||||
|
||||
int socket_pool_add_cert(SocketPool *pool, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file);
|
||||
|
||||
SocketEvent socket_pool_wait(SocketPool *pool);
|
||||
|
||||
void socket_pool_set_user_data(SocketPool *pool, SocketHandle handle, void *user_data);
|
||||
|
||||
void socket_pool_close(SocketPool *pool, SocketHandle handle);
|
||||
|
||||
int socket_pool_connect(SocketPool *pool, bool secure,
|
||||
HTTP_String addr, uint16_t port, void *user_data);
|
||||
|
||||
int socket_pool_connect_ipv4(SocketPool *pool, bool secure,
|
||||
HTTP_IPv4 addr, uint16_t port, void *user_data);
|
||||
|
||||
int socket_pool_connect_ipv6(SocketPool *pool, bool secure,
|
||||
HTTP_IPv6 addr, uint16_t port, void *user_data);
|
||||
|
||||
int socket_pool_read(SocketPool *pool, SocketHandle handle, char *dst, int len);
|
||||
|
||||
int socket_pool_write(SocketPool *pool, SocketHandle handle, char *src, int len);
|
||||
|
||||
bool socket_pool_secure(SocketPool *pool, SocketHandle handle);
|
||||
|
||||
#endif // SOCKET_POOL_INCLUDED
|
||||
@@ -1,106 +0,0 @@
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <ws2tcpip.h>
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "socket_raw.h"
|
||||
#endif
|
||||
|
||||
int socket_raw_global_init(void)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||
if (result != 0)
|
||||
return 1;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
void socket_raw_global_free(void)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
int set_socket_blocking(RAW_SOCKET sock, bool value)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
u_long mode = !value;
|
||||
if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR)
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
int flags = fcntl(sock, F_GETFL, 0);
|
||||
if (flags < 0)
|
||||
return -1;
|
||||
if (value) flags &= ~O_NONBLOCK;
|
||||
else flags |= O_NONBLOCK;
|
||||
if (fcntl(sock, F_SETFL, flags) < 0)
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
RAW_SOCKET listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog)
|
||||
{
|
||||
RAW_SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock == BAD_SOCKET)
|
||||
return BAD_SOCKET;
|
||||
|
||||
if (set_socket_blocking(sock, false) < 0) {
|
||||
CLOSE_SOCKET(sock);
|
||||
return BAD_SOCKET;
|
||||
}
|
||||
|
||||
if (reuse_addr) {
|
||||
int one = 1;
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &one, sizeof(one));
|
||||
}
|
||||
|
||||
struct in_addr addr_buf;
|
||||
if (addr.len == 0)
|
||||
addr_buf.s_addr = htonl(INADDR_ANY);
|
||||
else {
|
||||
|
||||
char copy[100];
|
||||
if (addr.len >= (int) sizeof(copy)) {
|
||||
CLOSE_SOCKET(sock);
|
||||
return BAD_SOCKET;
|
||||
}
|
||||
memcpy(copy, addr.ptr, addr.len);
|
||||
copy[addr.len] = '\0';
|
||||
|
||||
if (inet_pton(AF_INET, copy, &addr_buf) < 0) {
|
||||
CLOSE_SOCKET(sock);
|
||||
return BAD_SOCKET;
|
||||
}
|
||||
}
|
||||
|
||||
struct sockaddr_in bind_buf;
|
||||
bind_buf.sin_family = AF_INET;
|
||||
bind_buf.sin_addr = addr_buf;
|
||||
bind_buf.sin_port = htons(port);
|
||||
if (bind(sock, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) { // TODO: how does bind fail on windows?
|
||||
CLOSE_SOCKET(sock);
|
||||
return BAD_SOCKET;
|
||||
}
|
||||
|
||||
if (listen(sock, backlog) < 0) { // TODO: how does listen fail on windows?
|
||||
CLOSE_SOCKET(sock);
|
||||
return BAD_SOCKET;
|
||||
}
|
||||
|
||||
return sock;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
#ifndef SOCKET_RAW_INCLUDED
|
||||
#define SOCKET_RAW_INCLUDED
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#define RAW_SOCKET SOCKET
|
||||
#define BAD_SOCKET INVALID_SOCKET
|
||||
#define POLL WSAPoll
|
||||
#define CLOSE_SOCKET closesocket
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
#define RAW_SOCKET int
|
||||
#define BAD_SOCKET -1
|
||||
#define POLL poll
|
||||
#define CLOSE_SOCKET close
|
||||
#endif
|
||||
|
||||
#ifndef HTTP_AMALGAMATION
|
||||
#include "basic.h"
|
||||
#endif
|
||||
|
||||
int socket_raw_global_init(void);
|
||||
void socket_raw_global_free(void);
|
||||
|
||||
int set_socket_blocking(RAW_SOCKET sock, bool value);
|
||||
|
||||
RAW_SOCKET listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog);
|
||||
|
||||
#endif // SOCKET_RAW_INCLUDED
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
int mutex_init(Mutex *mutex)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
InitializeCriticalSection(mutex); // TODO: mock?
|
||||
return 0;
|
||||
#else
|
||||
if (pthread_mutex_init(mutex, NULL)) // TODO: mock
|
||||
return -1;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int mutex_free(Mutex *mutex)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DeleteCriticalSection(mutex); // TODO: mock?
|
||||
return 0;
|
||||
#else
|
||||
if (pthread_mutex_destroy(mutex)) // TODO: mock
|
||||
return -1;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int mutex_lock(Mutex *mutex)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection(mutex); // TODO: mock?
|
||||
return 0;
|
||||
#else
|
||||
if (pthread_mutex_lock(mutex)) // TODO: mock
|
||||
return -1;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int mutex_unlock(Mutex *mutex)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
LeaveCriticalSection(mutex); // TODO: mock?
|
||||
return 0;
|
||||
#else
|
||||
if (pthread_mutex_unlock(mutex)) // TODO: mock
|
||||
return -1;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
typedef struct {
|
||||
// TODO
|
||||
} Mutex;
|
||||
|
||||
int mutex_init(Mutex *mutex);
|
||||
int mutex_free(Mutex *mutex);
|
||||
int mutex_lock(Mutex *mutex);
|
||||
int mutex_unlock(Mutex *mutex);
|
||||
@@ -1,81 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "test.h"
|
||||
|
||||
static const char *statestr(HTTP_EngineState state)
|
||||
{
|
||||
switch (state) {
|
||||
case HTTP_ENGINE_STATE_NONE : return "NONE";
|
||||
case HTTP_ENGINE_STATE_CLIENT_PREP_URL : return "CLIENT_PREP_URL";
|
||||
case HTTP_ENGINE_STATE_CLIENT_PREP_HEADER : return "CLIENT_PREP_HEADER";
|
||||
case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF: return "CLIENT_PREP_BODY_BUF";
|
||||
case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK: return "CLIENT_PREP_BODY_ACK";
|
||||
case HTTP_ENGINE_STATE_CLIENT_PREP_ERROR : return "CLIENT_PREP_ERROR";
|
||||
case HTTP_ENGINE_STATE_CLIENT_SEND_BUF : return "CLIENT_SEND_BUF";
|
||||
case HTTP_ENGINE_STATE_CLIENT_SEND_ACK : return "CLIENT_SEND_ACK";
|
||||
case HTTP_ENGINE_STATE_CLIENT_RECV_BUF : return "CLIENT_RECV_BUF";
|
||||
case HTTP_ENGINE_STATE_CLIENT_RECV_ACK : return "CLIENT_RECV_ACK";
|
||||
case HTTP_ENGINE_STATE_CLIENT_READY : return "CLIENT_READY";
|
||||
case HTTP_ENGINE_STATE_CLIENT_CLOSED : return "CLIENT_CLOSED";
|
||||
case HTTP_ENGINE_STATE_SERVER_RECV_BUF : return "SERVER_RECV_BUF";
|
||||
case HTTP_ENGINE_STATE_SERVER_RECV_ACK : return "SERVER_RECV_ACK";
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_STATUS : return "SERVER_PREP_STATUS";
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_HEADER : return "SERVER_PREP_HEADER";
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF: return "SERVER_PREP_BODY_BUF";
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK: return "SERVER_PREP_BODY_ACK";
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_ERROR : return "SERVER_PREP_ERROR";
|
||||
case HTTP_ENGINE_STATE_SERVER_SEND_BUF : return "SERVER_SEND_BUF";
|
||||
case HTTP_ENGINE_STATE_SERVER_SEND_ACK : return "SERVER_SEND_ACK";
|
||||
case HTTP_ENGINE_STATE_SERVER_CLOSED : return "SERVER_CLOSED";
|
||||
}
|
||||
return "???";
|
||||
}
|
||||
|
||||
void testeq_engstate(HTTP_EngineState l, HTTP_EngineState r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line)
|
||||
{
|
||||
if (l != r) {
|
||||
printf("Test failed at %s:%d\n", file, line);
|
||||
printf(" TEST_EQ(%.*s, %.*s) -> TEST_EQ(%s, %s)\n", HTTP_UNPACK(uneval_l), HTTP_UNPACK(uneval_r), statestr(l), statestr(r));
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void testeq_int(int l, int r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line)
|
||||
{
|
||||
if (l != r) {
|
||||
printf("Test failed at %s:%d\n", file, line);
|
||||
printf(" TEST_EQ(%.*s, %.*s) -> TEST_EQ(%d, %d)\n", HTTP_UNPACK(uneval_l), HTTP_UNPACK(uneval_r), l, r);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void testeq_str(HTTP_String l, HTTP_String r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line)
|
||||
{
|
||||
if (!http_streq(l, r)) {
|
||||
printf("Test failed at %s:%d\n", file, line);
|
||||
printf(" TEST_EQ(\"%.*s\", \"%.*s\") -> TEST_EQ(%.*s, %.*s)\n", HTTP_UNPACK(uneval_l), HTTP_UNPACK(uneval_r), HTTP_UNPACK(l), HTTP_UNPACK(r));
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void test_branch_coverage_parse(void);
|
||||
void test_branch_coverage_engine(void);
|
||||
void test_fuzz_engine(void);
|
||||
|
||||
int main(void)
|
||||
{
|
||||
char *tests[] = {
|
||||
};
|
||||
|
||||
for (int i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) {
|
||||
HTTP_Request req;
|
||||
int ret = http_parse_request(tests[i], strlen(tests[i]), &req);
|
||||
TEST(ret == 0);
|
||||
}
|
||||
|
||||
//test_branch_coverage_parse();
|
||||
test_branch_coverage_engine();
|
||||
//test_fuzz_engine();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include "../tinyhttp.h"
|
||||
|
||||
#define S HTTP_STR
|
||||
|
||||
#define COUNT(X) (int) (sizeof(X)/sizeof((X)[0]))
|
||||
#define TEST(X) {if (!(X)) { fprintf(stderr, "Failed test at %s:%d\n", __FILE__, __LINE__); __builtin_trap(); }}
|
||||
|
||||
void testeq_engstate(HTTP_EngineState l, HTTP_EngineState r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line);
|
||||
void testeq_int(int l, int r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line);
|
||||
void testeq_str(HTTP_String l, HTTP_String r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line);
|
||||
#define TEST_EQ(X, Y) _Generic((X), HTTP_String: testeq_str, int: testeq_int, HTTP_EngineState: testeq_engstate)((X), (Y), S(#X), S(#Y), __FILE__, __LINE__)
|
||||
@@ -1,138 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "test.h"
|
||||
|
||||
static void *memfunc(HTTP_MemoryFuncTag tag,
|
||||
void *ptr, int len, void *data)
|
||||
{
|
||||
(void) data;
|
||||
switch (tag) {
|
||||
|
||||
case HTTP_MEMFUNC_MALLOC:
|
||||
return malloc(len);
|
||||
|
||||
case HTTP_MEMFUNC_FREE:
|
||||
free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void test_engine_server(void)
|
||||
{
|
||||
HTTP_Engine eng;
|
||||
|
||||
{
|
||||
int client = 0;
|
||||
memset(&eng, 0, sizeof(eng));
|
||||
http_engine_init(&eng, client, memfunc, NULL);
|
||||
TEST_EQ((int) http_engine_state(&eng), HTTP_ENGINE_STATE_SERVER_RECV_BUF);
|
||||
http_engine_free(&eng);
|
||||
}
|
||||
|
||||
{
|
||||
int client = 1;
|
||||
memset(&eng, 0, sizeof(eng));
|
||||
http_engine_init(&eng, client, memfunc, NULL);
|
||||
TEST_EQ((int) http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_URL);
|
||||
http_engine_free(&eng);
|
||||
}
|
||||
}
|
||||
|
||||
static int read_from_client_engine(HTTP_Engine *eng, char *dst, int cap)
|
||||
{
|
||||
TEST_EQ(http_engine_state(eng), HTTP_ENGINE_STATE_CLIENT_SEND_BUF);
|
||||
|
||||
int num = 0;
|
||||
do {
|
||||
int len;
|
||||
char *src = http_engine_sendbuf(eng, &len);
|
||||
TEST_EQ(http_engine_state(eng), HTTP_ENGINE_STATE_CLIENT_SEND_ACK);
|
||||
|
||||
TEST(src != NULL);
|
||||
TEST(cap - num >= len);
|
||||
memcpy(dst + num, src, len);
|
||||
|
||||
num += len;
|
||||
http_engine_sendack(eng, len);
|
||||
} while (http_engine_state(eng) == HTTP_ENGINE_STATEBIT_SEND_BUF);
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
static void send_into_client_engine(HTTP_Engine *eng, char *src, int len)
|
||||
{
|
||||
int num = 0;
|
||||
while (num < len) {
|
||||
TEST_EQ(http_engine_state(eng), HTTP_ENGINE_STATE_CLIENT_RECV_BUF);
|
||||
|
||||
int cap;
|
||||
char *dst = http_engine_recvbuf(eng, &cap);
|
||||
TEST_EQ(http_engine_state(eng), HTTP_ENGINE_STATE_CLIENT_RECV_ACK);
|
||||
TEST(dst != NULL);
|
||||
TEST(cap > 0);
|
||||
|
||||
int cpy = len - num;
|
||||
if (cpy > cap) cpy = cap;
|
||||
|
||||
memcpy(dst, src + num, cpy);
|
||||
|
||||
num += cpy;
|
||||
http_engine_recvack(eng, cpy);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_engine_client_basic_flow(void)
|
||||
{
|
||||
int client = 1;
|
||||
HTTP_Engine eng;
|
||||
http_engine_init(&eng, client, memfunc, NULL);
|
||||
TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_URL);
|
||||
|
||||
http_engine_url(&eng, HTTP_METHOD_GET, "http://some.url.com/some/endpoint", 1);
|
||||
TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_HEADER);
|
||||
|
||||
http_engine_header(&eng, "headerA: valueA", -1);
|
||||
TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_HEADER);
|
||||
|
||||
http_engine_body(&eng, "Hello, world!", -1);
|
||||
TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF);
|
||||
|
||||
http_engine_done(&eng);
|
||||
TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_SEND_BUF);
|
||||
|
||||
{
|
||||
char sendbuf[1<<10];
|
||||
int sendnum;
|
||||
sendnum = read_from_client_engine(&eng, sendbuf, (int) sizeof(sendbuf));
|
||||
|
||||
char expect[] =
|
||||
"GET /some/endpoint HTTP/1.1\r\n"
|
||||
"Host: some.url.com\r\n"
|
||||
"headerA: valueA\r\n"
|
||||
"Connection: Keep-Alive\r\n"
|
||||
"Content-Length: 13 \r\n"
|
||||
"\r\n"
|
||||
"Hello, world!";
|
||||
|
||||
TEST_EQ(sendnum, strlen(expect));
|
||||
TEST_EQ(memcmp(expect, sendbuf, strlen(expect)), 0);
|
||||
}
|
||||
|
||||
{
|
||||
char response[] =
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"\r\n";
|
||||
send_into_client_engine(&eng, response, strlen(response));
|
||||
}
|
||||
|
||||
TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_READY);
|
||||
|
||||
http_engine_free(&eng);
|
||||
}
|
||||
|
||||
void test_branch_coverage_engine(void)
|
||||
{
|
||||
test_engine_server();
|
||||
test_engine_client_basic_flow();
|
||||
}
|
||||
@@ -1,485 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "test.h"
|
||||
|
||||
static void test_branch_coverage_parse_request(void)
|
||||
{
|
||||
struct {
|
||||
int line;
|
||||
int ret;
|
||||
char *str;
|
||||
} error_reqs[] = {
|
||||
{ __LINE__, -1, "G * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "G@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "GE * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "GE@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, 18, "GET * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET@ * HTTP/1.1\r\n\r\n" },
|
||||
|
||||
{ __LINE__, -1, "P * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "P@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "PO * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "PO@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "POS * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "POS@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, 19, "POST * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "POST@ * HTTP/1.1\r\n\r\n" },
|
||||
|
||||
{ __LINE__, -1, "PU * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "PU@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, 18, "PUT * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "PUT@ * HTTP/1.1\r\n\r\n" },
|
||||
|
||||
{ __LINE__, -1, "H * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "H@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "HE * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "HE@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "HEA * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "HEA@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, 19, "HEAD * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "HEAD@ * HTTP/1.1\r\n\r\n" },
|
||||
|
||||
{ __LINE__, -1, "D * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "D@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "DE * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "DE@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "DEL * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "DEL@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "DELE * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "DELE@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "DELET * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "DELET@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, 21, "DELETE * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "DELETE@ * HTTP/1.1\r\n\r\n" },
|
||||
|
||||
{ __LINE__, -1, "C * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "C@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "CO * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "CO@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "CON * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "CON@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "CONN * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "CONN@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "CONNE * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "CONNE@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "CONNEC * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "CONNEC@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, 22, "CONNECT * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "CONNECT@ * HTTP/1.1\r\n\r\n" },
|
||||
|
||||
{ __LINE__, -1, "O * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "O@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "PO * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "PO@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "OPT * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "OPT@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "OPTI * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "OPTI@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "OPTIO * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "OPTIO@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "OPTION * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "OPTION@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, 22, "OPTIONS * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "OPTIONS@ * HTTP/1.1\r\n\r\n" },
|
||||
|
||||
{ __LINE__, -1, "T * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "T@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "TR * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "TR@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "TRA * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "TRA@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "TRAC * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "TRAC@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, 20, "TRACE * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "TRACE@ * HTTP/1.1\r\n\r\n" },
|
||||
|
||||
{ __LINE__, -1, "P * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "P@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "PA * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "PA@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "PAT * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "PAT@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "PATC * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "PATC@ * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, 20, "PATCH * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "PATCH@ * HTTP/1.1\r\n\r\n" },
|
||||
|
||||
{ __LINE__, -1, "GET *@\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * @\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * H\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * H@\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HT\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HT@\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTT\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTT@\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTTP\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTTP@\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTTP/\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTTP/@\r\n\r\n" },
|
||||
{ __LINE__, 16, "GET * HTTP/1\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTTP/1@\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTTP/1.\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTTP/1.@\r\n\r\n" },
|
||||
{ __LINE__, 18, "GET * HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTTP/1.1@\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTTP/1.1@\nname:\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTTP/1.1\r@name:\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTTP/1.1\r\n@\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTTP/1.1\r\nn@\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTTP/1.1\r\nname\r\n\r\n" },
|
||||
{ __LINE__, 25, "GET * HTTP/1.1\r\nname:\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTTP/1.1\r\nname:\x1B\r\n\r\n" },
|
||||
{ __LINE__, 30, "GET * HTTP/1.1\r\nname:value\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTTP/1.1\r\nname :value\r\n\r\n" },
|
||||
{ __LINE__, -1, "GET * HTTP/1.1\r\nname:val\rue\r\n\r\n" },
|
||||
|
||||
{ __LINE__, 0, NULL },
|
||||
};
|
||||
|
||||
for (int i = 0; error_reqs[i].str; i++) {
|
||||
HTTP_Request req;
|
||||
int ret = http_parse_request(error_reqs[i].str, strlen(error_reqs[i].str), &req);
|
||||
if (ret != error_reqs[i].ret) {
|
||||
fprintf(stderr, "Failed test at %s:%d (ret=%d, expected=%d)\n", __FILE__, error_reqs[i].line, ret, error_reqs[i].ret);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
char str[] = "GET * HTTP/1.1\r\n\r\n";
|
||||
HTTP_Request req;
|
||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
||||
TEST(ret == 18);
|
||||
TEST(req.method == HTTP_METHOD_GET);
|
||||
}
|
||||
|
||||
{
|
||||
char str[] = "POST * HTTP/1.1\r\n\r\n";
|
||||
HTTP_Request req;
|
||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
||||
TEST(ret == 19);
|
||||
TEST(req.method == HTTP_METHOD_POST);
|
||||
}
|
||||
|
||||
{
|
||||
char str[] = "PUT * HTTP/1.1\r\n\r\n";
|
||||
HTTP_Request req;
|
||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
||||
TEST(ret == 18);
|
||||
TEST(req.method == HTTP_METHOD_PUT);
|
||||
}
|
||||
|
||||
{
|
||||
char str[] = "HEAD * HTTP/1.1\r\n\r\n";
|
||||
HTTP_Request req;
|
||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
||||
TEST(ret == 19);
|
||||
TEST(req.method == HTTP_METHOD_HEAD);
|
||||
}
|
||||
|
||||
{
|
||||
char str[] = "DELETE * HTTP/1.1\r\n\r\n";
|
||||
HTTP_Request req;
|
||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
||||
TEST(ret == 21);
|
||||
TEST(req.method == HTTP_METHOD_DELETE);
|
||||
}
|
||||
|
||||
{
|
||||
char str[] = "CONNECT * HTTP/1.1\r\n\r\n";
|
||||
HTTP_Request req;
|
||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
||||
TEST(ret == 22);
|
||||
TEST(req.method == HTTP_METHOD_CONNECT);
|
||||
}
|
||||
|
||||
{
|
||||
char str[] = "OPTIONS * HTTP/1.1\r\n\r\n";
|
||||
HTTP_Request req;
|
||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
||||
TEST(ret == 22);
|
||||
TEST(req.method == HTTP_METHOD_OPTIONS);
|
||||
}
|
||||
|
||||
{
|
||||
char str[] = "TRACE * HTTP/1.1\r\n\r\n";
|
||||
HTTP_Request req;
|
||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
||||
TEST(ret == 20);
|
||||
TEST(req.method == HTTP_METHOD_TRACE);
|
||||
}
|
||||
|
||||
{
|
||||
char str[] = "PATCH * HTTP/1.1\r\n\r\n";
|
||||
HTTP_Request req;
|
||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
||||
TEST(ret == 20);
|
||||
TEST(req.method == HTTP_METHOD_PATCH);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_branch_coverage_parse_response(void)
|
||||
{
|
||||
struct {
|
||||
int line;
|
||||
int ret;
|
||||
char *str;
|
||||
} error_ress[] = {
|
||||
{ __LINE__, -1, "@\r\n\r\n" },
|
||||
{ __LINE__, -1, "H\r\n\r\n" },
|
||||
{ __LINE__, -1, "H@\r\n\r\n" },
|
||||
{ __LINE__, -1, "HT\r\n\r\n" },
|
||||
{ __LINE__, -1, "HT@\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTT\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTT@\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP@\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/@\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1@\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.@\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1@\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 \r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 @\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 4\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 4@\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 40\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 40@\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 404\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 404@\r\n\r\n" },
|
||||
{ __LINE__, 17, "HTTP/1.1 404 \r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 404 \x1B\r\n\r\n" },
|
||||
{ __LINE__, 26, "HTTP/1.1 404 Not Found\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\x1B\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\x1B\nname:\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\x1Bname:\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\n@\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\nn@\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\nname\r\n\r\n" },
|
||||
{ __LINE__, 33, "HTTP/1.1 404 Not Found\r\nname:\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\nname:\x1B\r\n\r\n" },
|
||||
{ __LINE__, 38, "HTTP/1.1 404 Not Found\r\nname:value\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\nname :value\r\n\r\n" },
|
||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\nname:val\rue\r\n\r\n" },
|
||||
|
||||
{ __LINE__, 0, NULL },
|
||||
};
|
||||
|
||||
for (int i = 0; error_ress[i].str; i++) {
|
||||
HTTP_Response res;
|
||||
int ret = http_parse_response(error_ress[i].str, strlen(error_ress[i].str), &res);
|
||||
if (ret != error_ress[i].ret) {
|
||||
fprintf(stderr, "Failed test at %s:%d (ret=%d, expected=%d)\n", __FILE__, error_ress[i].line, ret, error_ress[i].ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
char *buf;
|
||||
int cap;
|
||||
int num;
|
||||
} Buffer;
|
||||
|
||||
static void appendc(Buffer *b, char c)
|
||||
{
|
||||
if (b->num < b->cap)
|
||||
b->buf[b->num] = c;
|
||||
b->num++;
|
||||
}
|
||||
|
||||
static void appends(Buffer *b, HTTP_String s)
|
||||
{
|
||||
if (b->num < b->cap) {
|
||||
int cpy = s.len;
|
||||
if (cpy > b->cap - b->num)
|
||||
cpy = b->cap - b->num;
|
||||
for (int i = 0; i < cpy; i++)
|
||||
b->buf[b->num + i] = s.ptr[i];
|
||||
}
|
||||
b->num += s.len;
|
||||
}
|
||||
|
||||
static void appendi(Buffer *buf, unsigned int num)
|
||||
{
|
||||
char tmp[10];
|
||||
|
||||
tmp[0] = num / 1000000000; num %= 1000000000;
|
||||
tmp[1] = num / 100000000; num %= 100000000;
|
||||
tmp[2] = num / 10000000; num %= 10000000;
|
||||
tmp[3] = num / 1000000; num %= 1000000;
|
||||
tmp[4] = num / 100000; num %= 100000;
|
||||
tmp[5] = num / 10000; num %= 10000;
|
||||
tmp[6] = num / 1000; num %= 1000;
|
||||
tmp[7] = num / 100; num %= 100;
|
||||
tmp[8] = num / 10; num %= 10;
|
||||
tmp[9] = num;
|
||||
|
||||
int leading_zeros = 0;
|
||||
while (leading_zeros < 9 && tmp[leading_zeros] == 0)
|
||||
leading_zeros++;
|
||||
|
||||
for (int i = leading_zeros; i < 10; i++)
|
||||
tmp[i] += '0';
|
||||
|
||||
appends(buf, (HTTP_String) {
|
||||
tmp + leading_zeros,
|
||||
10 - leading_zeros
|
||||
});
|
||||
}
|
||||
|
||||
static int build_url(
|
||||
HTTP_String scheme,
|
||||
HTTP_String userinfo,
|
||||
HTTP_String host,
|
||||
HTTP_HostMode mode,
|
||||
int port,
|
||||
HTTP_String path,
|
||||
HTTP_String query,
|
||||
HTTP_String fragment,
|
||||
char* dst,
|
||||
int cap)
|
||||
{
|
||||
Buffer buf = {dst, cap, 0};
|
||||
appends(&buf, scheme);
|
||||
appendc(&buf, ':');
|
||||
if (mode != HTTP_HOST_MODE_VOID) {
|
||||
appendc(&buf, '/');
|
||||
appendc(&buf, '/');
|
||||
if (userinfo.len) {
|
||||
appends(&buf, userinfo);
|
||||
appendc(&buf, '@');
|
||||
}
|
||||
if (mode == HTTP_HOST_MODE_IPV6)
|
||||
appendc(&buf, '[');
|
||||
appends(&buf, host);
|
||||
if (mode == HTTP_HOST_MODE_IPV6)
|
||||
appendc(&buf, ']');
|
||||
if (port == -2)
|
||||
appendc(&buf, ':');
|
||||
else if (port != -1) {
|
||||
appendc(&buf, ':');
|
||||
appendi(&buf, port);
|
||||
}
|
||||
}
|
||||
appends(&buf, path);
|
||||
appends(&buf, query);
|
||||
appends(&buf, fragment);
|
||||
return buf.num;
|
||||
}
|
||||
|
||||
static void test_url(HTTP_String scheme, HTTP_String userinfo,
|
||||
HTTP_String host, HTTP_HostMode mode, int port,
|
||||
HTTP_String path, HTTP_String query, HTTP_String fragment)
|
||||
{
|
||||
char mem[1<<12];
|
||||
int num = build_url(scheme, userinfo, host, mode, port, path, query, fragment, mem, sizeof(mem));
|
||||
TEST(num < sizeof(mem));
|
||||
|
||||
printf("Testing %.*s\n", num, mem);
|
||||
|
||||
HTTP_URL url;
|
||||
int ret = http_parse_url(mem, num, &url);
|
||||
|
||||
TEST_EQ(ret, num);
|
||||
TEST_EQ(url.scheme, scheme);
|
||||
TEST_EQ(url.authority.userinfo, userinfo);
|
||||
if (port < 0)
|
||||
TEST_EQ(url.authority.port, 0);
|
||||
else
|
||||
TEST_EQ(url.authority.port, port);
|
||||
TEST_EQ((int) url.authority.host.mode, (int) mode);
|
||||
if (mode == HTTP_HOST_MODE_IPV4) {
|
||||
char tmp[1<<12];
|
||||
TEST(sizeof(tmp) > host.len);
|
||||
memcpy(tmp, host.ptr, host.len);
|
||||
tmp[host.len] = '\0';
|
||||
HTTP_IPv4 buf;
|
||||
TEST_EQ(inet_pton(AF_INET, tmp, &buf), 1);
|
||||
TEST_EQ((int) buf.data, (int) url.authority.host.ipv4.data);
|
||||
} else if (mode == HTTP_HOST_MODE_IPV6) {
|
||||
char tmp[1<<12];
|
||||
TEST(sizeof(tmp) > host.len);
|
||||
memcpy(tmp, host.ptr, host.len);
|
||||
tmp[host.len] = '\0';
|
||||
HTTP_IPv6 buf;
|
||||
TEST_EQ(inet_pton(AF_INET6, tmp, &buf), 1);
|
||||
TEST(!memcmp(&buf, &url.authority.host.ipv6, sizeof(HTTP_IPv6)));
|
||||
} else if (mode == HTTP_HOST_MODE_NAME) {
|
||||
TEST_EQ(host, url.authority.host.name);
|
||||
}
|
||||
TEST_EQ(url.path, path);
|
||||
TEST_EQ(url.query, query);
|
||||
TEST_EQ(url.fragment, fragment);
|
||||
}
|
||||
|
||||
static void test_branch_coverage_parse_url(void)
|
||||
{
|
||||
HTTP_String scheme_values[] = { S("http") };
|
||||
|
||||
HTTP_String userinfo_values[] = { S(""), S("xxx:yyy") };
|
||||
|
||||
struct host_values {
|
||||
HTTP_HostMode mode;
|
||||
HTTP_String text;
|
||||
} host_values[] = {
|
||||
{ HTTP_HOST_MODE_VOID, S("") },
|
||||
{ HTTP_HOST_MODE_IPV4, S("1.2.3.4") },
|
||||
{ HTTP_HOST_MODE_IPV6, S("::") },
|
||||
{ HTTP_HOST_MODE_IPV6, S("::1") },
|
||||
{ HTTP_HOST_MODE_IPV6, S("1:2:3:4:A:B:C:D") },
|
||||
{ HTTP_HOST_MODE_NAME, S("example.com") },
|
||||
{ HTTP_HOST_MODE_NAME, S("1.2.3.256") },
|
||||
};
|
||||
|
||||
int port_values[] = { -1, 1, 8080 };
|
||||
|
||||
HTTP_String path_values[] = { S(""), S("/"), S("/some/path.html") };
|
||||
|
||||
HTTP_String query_values[] = { S(""), S("?"), S("?param1=hello¶m2=sup") };
|
||||
|
||||
HTTP_String fragment_values[] = { S(""), S("#"), S("#section0") };
|
||||
|
||||
for (int i = 0; i < COUNT(scheme_values); i++)
|
||||
for (int j = 0; j < COUNT(userinfo_values); j++)
|
||||
for (int k = 0; k < COUNT(host_values); k++)
|
||||
for (int g = 0; g < COUNT(port_values); g++)
|
||||
for (int t = 0; t < COUNT(path_values); t++)
|
||||
for (int p = 0; p < COUNT(query_values); p++)
|
||||
for (int q = 0; q < COUNT(fragment_values); q++) {
|
||||
|
||||
// Don't test URLs where the host isn't specified but the port or userinfo is
|
||||
if (host_values[k].mode == HTTP_HOST_MODE_VOID && (port_values[g] > -1 || userinfo_values[j].len))
|
||||
continue;
|
||||
|
||||
// If the authority is missing, the path must not be empty
|
||||
if (host_values[k].mode == HTTP_HOST_MODE_VOID && path_values[t].len == 0)
|
||||
continue;
|
||||
|
||||
test_url(
|
||||
scheme_values[i],
|
||||
userinfo_values[j],
|
||||
host_values[k].text,
|
||||
host_values[k].mode,
|
||||
port_values[g],
|
||||
path_values[t],
|
||||
query_values[p],
|
||||
fragment_values[q]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void test_branch_coverage_parse(void)
|
||||
{
|
||||
test_branch_coverage_parse_request();
|
||||
test_branch_coverage_parse_response();
|
||||
test_branch_coverage_parse_url();
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include "test.h"
|
||||
|
||||
static void *memfunc(HTTP_MemoryFuncTag tag,
|
||||
void *ptr, int len, void *data)
|
||||
{
|
||||
(void) data;
|
||||
switch (tag) {
|
||||
|
||||
case HTTP_MEMFUNC_MALLOC:
|
||||
{
|
||||
void *ptr = NULL;
|
||||
|
||||
int x = rand() & 31;
|
||||
if (x == 0)
|
||||
ptr = malloc(len);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
case HTTP_MEMFUNC_FREE:
|
||||
free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *http_statestr(int state)
|
||||
{
|
||||
switch (state) {
|
||||
case HTTP_ENGINE_STATE_NONE : return "NONE";
|
||||
case HTTP_ENGINE_STATE_CLIENT_PREP_URL : return "CLIENT_PREP_URL";
|
||||
case HTTP_ENGINE_STATE_CLIENT_PREP_HEADER : return "CLIENT_PREP_HEADER";
|
||||
case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF: return "CLIENT_PREP_BODY_BUF";
|
||||
case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK: return "CLIENT_PREP_BODY_ACK";
|
||||
case HTTP_ENGINE_STATE_CLIENT_PREP_ERROR : return "CLIENT_PREP_ERROR";
|
||||
case HTTP_ENGINE_STATE_CLIENT_SEND_BUF : return "CLIENT_SEND_BUF";
|
||||
case HTTP_ENGINE_STATE_CLIENT_SEND_ACK : return "CLIENT_SEND_ACK";
|
||||
case HTTP_ENGINE_STATE_CLIENT_RECV_BUF : return "CLIENT_RECV_BUF";
|
||||
case HTTP_ENGINE_STATE_CLIENT_RECV_ACK : return "CLIENT_RECV_ACK";
|
||||
case HTTP_ENGINE_STATE_CLIENT_READY : return "CLIENT_READY";
|
||||
case HTTP_ENGINE_STATE_CLIENT_CLOSED : return "CLIENT_CLOSED";
|
||||
case HTTP_ENGINE_STATE_SERVER_RECV_BUF : return "SERVER_RECV_BUF";
|
||||
case HTTP_ENGINE_STATE_SERVER_RECV_ACK : return "SERVER_RECV_ACK";
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_STATUS : return "SERVER_PREP_STATUS";
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_HEADER : return "SERVER_PREP_HEADER";
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF: return "SERVER_PREP_BODY_BUF";
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK: return "SERVER_PREP_BODY_ACK";
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_ERROR : return "SERVER_PREP_ERROR";
|
||||
case HTTP_ENGINE_STATE_SERVER_SEND_BUF : return "SERVER_SEND_BUF";
|
||||
case HTTP_ENGINE_STATE_SERVER_SEND_ACK : return "SERVER_SEND_ACK";
|
||||
case HTTP_ENGINE_STATE_SERVER_CLOSED : return "SERVER_CLOSED";
|
||||
}
|
||||
return "???";
|
||||
}
|
||||
|
||||
static sig_atomic_t stop = 0;
|
||||
|
||||
static void handle_signal(int dummy)
|
||||
{
|
||||
stop = 1;
|
||||
}
|
||||
|
||||
void test_fuzz_engine(void)
|
||||
{
|
||||
signal(SIGINT, handle_signal);
|
||||
|
||||
char txt1[] = "";
|
||||
char txt2[] = "GET / HTTP/1.1\r\n\r\n";
|
||||
char txt3[] = "GET / HTTP/1.1\n";
|
||||
char txt4[] = "HTTP/1.1 200 OK\r\n";
|
||||
char txt5[] = "HTTP/1.1 200 OK\r\n\r\n";
|
||||
|
||||
int next_recvack = 0;
|
||||
int next_sendack = 0;
|
||||
int next_bodyack = 0;
|
||||
int curr_bodycap = 0;
|
||||
int client = 0;
|
||||
HTTP_Engine eng;
|
||||
http_engine_init(&eng, client, memfunc, NULL);
|
||||
while (!stop) {
|
||||
//printf("%s\n", http_statestr(http_engine_state(&eng)));
|
||||
switch (rand() % 21) {
|
||||
|
||||
int max;
|
||||
char *buf;
|
||||
|
||||
case 0:
|
||||
//printf("http_engine_free/http_engine_init\n");
|
||||
client = !client;
|
||||
http_engine_free(&eng);
|
||||
http_engine_init(&eng, client, memfunc, NULL);
|
||||
next_recvack = 0;
|
||||
next_sendack = 0;
|
||||
next_bodyack = 0;
|
||||
curr_bodycap = 0;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
//printf("http_engine_recvbuf (1)\n");
|
||||
buf = http_engine_recvbuf(&eng, &max);
|
||||
memcpy(buf, txt1, strlen(txt1));
|
||||
next_recvack = strlen(txt1);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
//printf("http_engine_recvbuf (2)\n");
|
||||
buf = http_engine_recvbuf(&eng, &max);
|
||||
if (buf) {
|
||||
memcpy(buf, txt2, strlen(txt2));
|
||||
next_recvack = strlen(txt2);
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
//printf("http_engine_recvbuf (3)\n");
|
||||
buf = http_engine_recvbuf(&eng, &max);
|
||||
if (buf) {
|
||||
memcpy(buf, txt3, strlen(txt3));
|
||||
next_recvack = strlen(txt3);
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
//printf("http_engine_recvbuf (4)\n");
|
||||
buf = http_engine_recvbuf(&eng, &max);
|
||||
if (buf) {
|
||||
memcpy(buf, txt4, strlen(txt4));
|
||||
next_recvack = strlen(txt4);
|
||||
}
|
||||
break;
|
||||
|
||||
case 5:
|
||||
//printf("http_engine_recvbuf (5)\n");
|
||||
buf = http_engine_recvbuf(&eng, &max);
|
||||
if (buf) {
|
||||
memcpy(buf, txt5, strlen(txt5));
|
||||
next_recvack = strlen(txt5);
|
||||
}
|
||||
break;
|
||||
|
||||
case 6:
|
||||
//printf("http_engine_recvack\n");
|
||||
http_engine_recvack(&eng, next_recvack);
|
||||
next_recvack = 0;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
//printf("http_engine_sendbuf\n");
|
||||
buf = http_engine_sendbuf(&eng, &max);
|
||||
if (max)
|
||||
next_sendack = rand() % max;
|
||||
break;
|
||||
|
||||
case 8:
|
||||
//printf("http_engine_sendack\n");
|
||||
http_engine_sendack(&eng, next_sendack);
|
||||
next_sendack = 0;
|
||||
break;
|
||||
|
||||
case 9:
|
||||
//printf("http_engine_getreq\n");
|
||||
http_engine_getreq(&eng);
|
||||
break;
|
||||
|
||||
case 10:
|
||||
//printf("http_engine_getres\n");
|
||||
http_engine_getres(&eng);
|
||||
break;
|
||||
|
||||
case 11:
|
||||
//printf("http_engine_url\n");
|
||||
http_engine_url(&eng, HTTP_METHOD_GET, "", 0);
|
||||
break;
|
||||
|
||||
case 12:
|
||||
//printf("http_engine_url\n");
|
||||
http_engine_url(&eng, HTTP_METHOD_GET, "http://127.0.0.1/hello", 0);
|
||||
break;
|
||||
|
||||
case 13:
|
||||
//printf("http_engine_status\n");
|
||||
http_engine_status(&eng, 200);
|
||||
break;
|
||||
|
||||
case 14:
|
||||
//printf("http_engine_header\n");
|
||||
http_engine_header(&eng, "x:y", -1);
|
||||
break;
|
||||
|
||||
case 15:
|
||||
//printf("http_engine_bodycap\n");
|
||||
http_engine_bodycap(&eng, rand() % 1000);
|
||||
break;
|
||||
|
||||
case 16:
|
||||
//printf("http_engine_bodybuf\n");
|
||||
buf = http_engine_bodybuf(&eng, &max);
|
||||
if (buf && max) {
|
||||
max = rand() % max;
|
||||
memset(buf, 0xFF, max);
|
||||
next_bodyack = max;
|
||||
}
|
||||
break;
|
||||
|
||||
case 17:
|
||||
//printf("http_engine_bodyack\n");
|
||||
http_engine_bodyack(&eng, next_bodyack);
|
||||
next_bodyack = 0;
|
||||
break;
|
||||
|
||||
case 18:
|
||||
//printf("http_engine_done\n");
|
||||
http_engine_done(&eng);
|
||||
break;
|
||||
|
||||
case 19:
|
||||
//printf("http_engine_undo\n");
|
||||
http_engine_undo(&eng);
|
||||
break;
|
||||
|
||||
case 20:
|
||||
//printf("http_engine_close\n");
|
||||
http_engine_close(&eng);
|
||||
break;
|
||||
}
|
||||
}
|
||||
http_engine_free(&eng);
|
||||
}
|
||||
Reference in New Issue
Block a user