Full rewrite
This commit is contained in:
+2
-2
@@ -1,2 +1,2 @@
|
||||
*.out
|
||||
*.exe
|
||||
misc
|
||||
coverage_report
|
||||
@@ -1,119 +1,14 @@
|
||||
CC = gcc
|
||||
RM = rm
|
||||
MKDIR = mkdir
|
||||
.PHONY: all report
|
||||
|
||||
EXT_WINDOWS = exe
|
||||
EXT_LINUX = out
|
||||
#LFLAGS = -lws2_32
|
||||
LFLAGS = -lssl -lcrypto -ggdb
|
||||
|
||||
# Can be either RELEASE or DEBUG
|
||||
BUILD = RELEASE
|
||||
all:
|
||||
#gcc -o test tests/test.c tests/test_branch_coverage.c http.c -fprofile-arcs -ftest-coverage
|
||||
|
||||
SUFFIX_RELEASE =
|
||||
SUFFIX_DEBUG = _debug
|
||||
|
||||
# ------------------------------------------------------ #
|
||||
|
||||
TEST_CFILES = tinyhttp.c tests/picohttpparser.c tests/test.c tests/test_reuse.c tests/test_chunking.c tests/test_parse_request.c
|
||||
TEST_HFILES = tinyhttp.h tests/picohttpparser.h
|
||||
|
||||
TEST_FLAGS = -Wall -Wextra
|
||||
TEST_FLAGS_DEBUG = -ggdb
|
||||
TEST_FLAGS_RELEASE = -O2 -DNDEBUG
|
||||
|
||||
TEST_FLAGS_WINDOWS = -lws2_32
|
||||
TEST_FLAGS_WINDOWS_DEBUG =
|
||||
TEST_FLAGS_WINDOWS_RELEASE =
|
||||
|
||||
TEST_FLAGS_LINUX =
|
||||
TEST_FLAGS_LINUX_DEBUG =
|
||||
TEST_FLAGS_LINUX_RELEASE =
|
||||
|
||||
# ------------------------------------------------------ #
|
||||
|
||||
DEMO0_CFILES = tinyhttp.c examples/server_api.c
|
||||
DEMO0_HFILES = tinyhttp.h
|
||||
|
||||
DEMO0_FLAGS =
|
||||
DEMO0_FLAGS_DEBUG = -ggdb
|
||||
DEMO0_FLAGS_RELEASE = -O2 -DNDEBUG
|
||||
|
||||
DEMO0_FLAGS_WINDOWS = -lws2_32
|
||||
DEMO0_FLAGS_WINDOWS_DEBUG =
|
||||
DEMO0_FLAGS_WINDOWS_RELEASE =
|
||||
|
||||
DEMO0_FLAGS_LINUX =
|
||||
DEMO0_FLAGS_LINUX_DEBUG =
|
||||
DEMO0_FLAGS_LINUX_RELEASE =
|
||||
|
||||
# ------------------------------------------------------ #
|
||||
|
||||
DEMO1_CFILES = tinyhttp.c examples/stream_api_with_select.c
|
||||
DEMO1_HFILES = tinyhttp.h
|
||||
DEMO1_FLAGS =
|
||||
|
||||
DEMO1_FLAGS =
|
||||
DEMO1_FLAGS_DEBUG = -ggdb
|
||||
DEMO1_FLAGS_RELEASE = -O2 -DNDEBUG
|
||||
|
||||
DEMO1_FLAGS_WINDOWS = -lws2_32
|
||||
DEMO1_FLAGS_WINDOWS_DEBUG =
|
||||
DEMO1_FLAGS_WINDOWS_RELEASE =
|
||||
|
||||
DEMO1_FLAGS_LINUX =
|
||||
DEMO1_FLAGS_LINUX_DEBUG =
|
||||
DEMO1_FLAGS_LINUX_RELEASE =
|
||||
|
||||
# ------------------------------------------------------ #
|
||||
# ------------------------------------------------------ #
|
||||
# ------------------------------------------------------ #
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
OSTAG = WINDOWS
|
||||
else
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
OSTAG = LINUX
|
||||
endif
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
OSTAG = OSX
|
||||
endif
|
||||
endif
|
||||
|
||||
EXT = ${EXT_$(OSTAG)}
|
||||
|
||||
TEST_FLAGS += ${TEST_FLAGS_$(BUILD)}
|
||||
TEST_FLAGS += ${TEST_FLAGS_$(OSTAG)}
|
||||
TEST_FLAGS += ${TEST_FLAGS_$(OSTAG)_$(BUILD)}
|
||||
|
||||
DEMO0_FLAGS += ${DEMO0_FLAGS_$(BUILD)}
|
||||
DEMO0_FLAGS += ${DEMO0_FLAGS_$(OSTAG)}
|
||||
DEMO0_FLAGS += ${DEMO0_FLAGS_$(OSTAG)_$(BUILD)}
|
||||
|
||||
DEMO1_FLAGS += ${DEMO1_FLAGS_$(BUILD)}
|
||||
DEMO1_FLAGS += ${DEMO1_FLAGS_$(OSTAG)}
|
||||
DEMO1_FLAGS += ${DEMO1_FLAGS_$(OSTAG)_$(BUILD)}
|
||||
|
||||
SUFFIX = ${SUFFIX_$(BUILD)}
|
||||
|
||||
# ------------------------------------------------------ #
|
||||
# ------------------------------------------------------ #
|
||||
# ------------------------------------------------------ #
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: out/test$(SUFFIX).$(EXT) out/demo0$(SUFFIX).$(EXT) out/demo1$(SUFFIX).$(EXT)
|
||||
|
||||
out:
|
||||
$(MKDIR) out
|
||||
|
||||
out/test$(SUFFIX).$(EXT): out $(TEST_CFILES) $(TEST_HFILES)
|
||||
$(CC) -o $@ $(TEST_CFILES) $(TEST_FLAGS)
|
||||
|
||||
out/demo0$(SUFFIX).$(EXT): out $(DEMO0_CFILES) $(DEMO0_HFILES)
|
||||
$(CC) -o $@ $(DEMO0_CFILES) $(DEMO0_FLAGS)
|
||||
|
||||
out/demo1$(SUFFIX).$(EXT): out $(DEMO1_CFILES) $(DEMO1_HFILES)
|
||||
$(CC) -o $@ $(DEMO1_CFILES) $(DEMO1_FLAGS)
|
||||
report:
|
||||
lcov --capture --directory . --output-file coverage.info --rc lcov_branch_coverage=1
|
||||
genhtml coverage.info --output-directory coverage_report --rc lcov_branch_coverage=1 --rc genhtml_branch_coverage=1
|
||||
|
||||
clean:
|
||||
$(RM) -fr out
|
||||
rm *.gcda *.gcno coverage.info test test.exe
|
||||
|
||||
@@ -1,199 +1,206 @@
|
||||
# TinyHTTP
|
||||
|
||||
TinyHTTP is an HTTP server library. It's small, robust, and fast.
|
||||
TinyHTTP is a library for building cross-platform and fully non-blocking HTTP 1.1 clients and servers in C.
|
||||
|
||||
**NOTE**: This is still a prototype! I got the basic version working and am spending some time making it more robust. After that, I will add HTTPS support, and work on compliancy to RFC 9112.
|
||||
## Roadmap and status
|
||||
|
||||
## Features
|
||||
* Self-contained
|
||||
* Cross-Platform (Windows, Linux)
|
||||
* HTTP/1.1 fully compliant to RFC 9112 with pipelining, chunked encoding, connection reuse
|
||||
* Fully non-blocking (epoll on Linux, iocp on Windows)
|
||||
* HTTPS (OpenSSL on Linux, Schannel on Windows) (in progress)
|
||||
* Zero-copy interface
|
||||
The project is still in the prototyping phase. I'm working on testing for robustness and compliance to RFC 9110, 9111, 9112, and 3986. The server is missing timers and HTTPS. The client is just a proof of concept at this point.
|
||||
|
||||
## Limitations
|
||||
* Single-threaded
|
||||
* IPv4 only
|
||||
## Overview
|
||||
|
||||
## How it works / Getting Started
|
||||
The architecture looks like this:
|
||||
|
||||
There are two ways to use TinyHTTP: the server interface and the stream interface.
|
||||
|
||||
The server interface is a complete server implementation designed to easily set up a new server.
|
||||
|
||||
The stream interface is more involved but completely stand-alone, doesn't performs I/O directly, and is easily embeddable in custom event loops. You can think of this as the core of the library where HTTP is implemented. It's designed to work well with readiness-based (select, poll, epoll) and completion-based event loops (iocp, io_uring).
|
||||
|
||||
To use TinyHTTP, be sure to enable the modules you are interested and tweak the configurations in from `tinyhttp.h`
|
||||
|
||||
```c
|
||||
#define TINYHTTP_SERVER_ENABLE 1
|
||||
#define TINYHTTP_ROUTER_ENABLE 0
|
||||
#define TINYHTTP_HTTPS_ENABLE 0
|
||||
|
||||
#define TINYHTTP_ROUTER_MAX_PATH_COMPONENTS 32
|
||||
|
||||
#define TINYHTTP_HEADER_LIMIT 32
|
||||
#define TINYHTTP_SERVER_CONN_LIMIT (1<<10)
|
||||
#define TINYHTTP_SERVER_EPOLL_BATCH_SIZE (1<<10)
|
||||
```
|
||||
+--------+
|
||||
| ROUTER |
|
||||
+--------+--------+
|
||||
| CLIENT | SERVER |
|
||||
+--------+--------+
|
||||
| ENGINE |
|
||||
+-----------------+
|
||||
| PARSER |
|
||||
+-----------------+
|
||||
```
|
||||
|
||||
then, drop the `tinyhttp.c` and `tinyhttp.h` in your project as they were your files.
|
||||
|
||||
## Example
|
||||
|
||||
This is the code needed to create an HTTP/HTTPS server using the server API:
|
||||
At the lowest level there are HTTP request, HTTP respons, and URI parser. Then comes the HTTP "engine", which contains the HTTP 1.1 state machine. These two layers don't depend on basically anything. Probably only freestanding libc headers. The engine is designed in such a way that it does not perform I/O. Instead, applications feed it bytes from the network and eventually get a request or response object from it. When data needs to be output, the engine lets that know to the application. An HTTP engine represents the communication between one server and one client, so a non-blocking server would typically use an array of engines.
|
||||
|
||||
To give you the general idea, a simple blocking server using the engine would look somewhat like this:
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "tinyhttp.h"
|
||||
|
||||
static void *memfunc(TinyHTTPMemoryFuncTag tag,
|
||||
void *ptr, int len, void *data)
|
||||
{
|
||||
(void) data;
|
||||
switch (tag) {
|
||||
|
||||
case TINYHTTP_MEM_MALLOC:
|
||||
return malloc(len);
|
||||
|
||||
case TINYHTTP_MEM_REALLOC:
|
||||
return realloc(ptr, len);
|
||||
|
||||
case TINYHTTP_MEM_FREE:
|
||||
free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
TinyHTTPServerConfig config = {
|
||||
|
||||
.reuse = 1,
|
||||
|
||||
.plain_addr = "127.0.0.1",
|
||||
.plain_port = 8080,
|
||||
.plain_backlog = 32,
|
||||
|
||||
.secure = 1,
|
||||
.secure_addr = "127.0.0.1",
|
||||
.secure_port = 8443,
|
||||
.secure_backlog = 32,
|
||||
|
||||
.cert_file = "cert.pem",
|
||||
.private_key_file = "privkey.pem",
|
||||
};
|
||||
|
||||
TinyHTTPServer *server = tinyhttp_server_init(config, memfunc, NULL);
|
||||
if (server == NULL)
|
||||
return -1;
|
||||
SOCKET listen_fd = start_server("127.0.0.1", 8080);
|
||||
|
||||
for (;;) {
|
||||
TinyHTTPRequest *req;
|
||||
TinyHTTPResponse res;
|
||||
int ret = tinyhttp_server_wait(server, &req, &res, 1000);
|
||||
if (ret < 0) return -1; // Error
|
||||
if (ret > 0) continue; // Timeout
|
||||
tinyhttp_response_status(res, 200);
|
||||
tinyhttp_response_send(res);
|
||||
}
|
||||
|
||||
tinyhttp_server_free(server);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
SOCKET client_fd = accept(listen_fd, NULL, NULL);
|
||||
|
||||
And this is an example of a server using the stream API. It's more verbose but offers a high degree of control.
|
||||
int is_client = 0; // If this were true, the engine would behave as the client instead
|
||||
|
||||
(NOTE: This code needs to be updated)
|
||||
HTTP_Engine eng;
|
||||
http_engine_init(&eng, is_client, memfunc, NULL);
|
||||
|
||||
```c
|
||||
void respond(TinyHTTPStream *stream)
|
||||
// Loop until the engine is in the CLOSED state
|
||||
for (int closed = 0; !closed; ) {
|
||||
|
||||
// The engine may be in one of 4 states:
|
||||
// RECV_BUF -> Bytes are expected from the network
|
||||
// SEND_BUF -> Bytes need to be sent on the network
|
||||
// PREP_STATUS -> A request is available
|
||||
// CLOSED -> The connection was shut down at the HTTP level
|
||||
switch (http_engine_state(&eng)) {
|
||||
|
||||
case HTTP_ENGINE_STATE_SERVER_CLOSED:
|
||||
closed = 1;
|
||||
break;
|
||||
|
||||
case HTTP_ENGINE_STATE_SERVER_RECV_BUF:
|
||||
{
|
||||
TinyHTTPRequest *req = tinyhttp_stream_request(stream);
|
||||
if (req->method != TINYHTTP_METHOD_GET)
|
||||
tinyhttp_stream_status(stream, 405);
|
||||
else
|
||||
tinyhttp_stream_status(stream, 200);
|
||||
tinyhttp_stream_send(stream);
|
||||
int cap;
|
||||
char *dst = http_engine_recvbuf(&eng, &cap);
|
||||
int ret = recv(client_fd, dst, cap, 0);
|
||||
if (ret <= 0) {
|
||||
http_engine_close(&eng);
|
||||
break;
|
||||
}
|
||||
http_engine_recvack(&eng, ret);
|
||||
}
|
||||
break;
|
||||
|
||||
int main(void)
|
||||
case HTTP_ENGINE_STATE_SERVER_SEND_BUF:
|
||||
{
|
||||
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
struct sockaddr_in buf;
|
||||
buf.sin_family = AF_INET;
|
||||
buf.sin_port = htons(port);
|
||||
buf.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
bind(listen_fd, (struct sockaddr*) &buf, sizeof(buf));
|
||||
|
||||
listen(listen_fd, 32);
|
||||
|
||||
int num_conns = 0;
|
||||
int fds[1000];
|
||||
TinyHTTPStream streams[1000];
|
||||
|
||||
for (int i = 0; i < 1000; i++)
|
||||
fds[i] = -1;
|
||||
|
||||
for (;;) {
|
||||
// TODO: timeouts
|
||||
|
||||
fd_set readset;
|
||||
fd_set writeset;
|
||||
FD_ZERO(&readset);
|
||||
FD_ZERO(&writeset);
|
||||
|
||||
FD_SET(&readset);
|
||||
int max_fd = listen_fd;
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
if (fds[i] == -1) continue;
|
||||
int state = tinyhttp_stream_state(&streams[i]);
|
||||
if (state & TINYHTTP_STREAM_RECV)
|
||||
FD_SET(fds[i], &readset);
|
||||
if (state & TINYHTTP_STREAM_SEND)
|
||||
FD_SET(fds[i], &writeset);
|
||||
if (state & (TINYHTTP_STREAM_RECV | TINYHTTP_STREAM_SEND))
|
||||
if (max_fd < fds[i]) max_fd = fds[i];
|
||||
int len;
|
||||
char *src = http_engine_sendbuf(&eng, &len);
|
||||
int ret = send(client_fd, src, len, 0);
|
||||
if (ret <= 0) {
|
||||
http_engine_close(&eng);
|
||||
break;
|
||||
}
|
||||
|
||||
int num = select(max_fd+1, &readset, &writeset, NULL, NULL);
|
||||
|
||||
if (FD_ISSET(liste_fd, &readset)) {
|
||||
// TODO
|
||||
http_engine_sendack(&eng, ret);
|
||||
}
|
||||
break;
|
||||
|
||||
int ready_queue[1000];
|
||||
int ready_head = 0;
|
||||
int ready_count = 0;
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
// TODO
|
||||
case HTTP_ENGINE_STATE_SERVER_PREP_STATUS:
|
||||
{
|
||||
HTTP_Request *req = http_engine_getreq(&eng);
|
||||
|
||||
http_engine_status(&eng, 200);
|
||||
http_engine_header(&eng, "Server: tinyhttp", 16);
|
||||
http_engine_body(&eng, "Hello, world!", 13);
|
||||
http_engine_done(&eng);
|
||||
}
|
||||
|
||||
while (ready_count > 0) {
|
||||
|
||||
int idx = ready_queue[ready_head];
|
||||
TinyHTTPStream *stream = &streams[idx];
|
||||
|
||||
TinyHTTPRequest *req = tinyhttp_stream_request(stream);
|
||||
assert(req);
|
||||
|
||||
respond(stream);
|
||||
|
||||
ready_head = (ready_head + 1) % 1000;
|
||||
ready_count--;
|
||||
if (tinyhttp_stream_request(stream)) {
|
||||
ready_queue[(ready_head + ready_count) % 1000] = idx;
|
||||
ready_count++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
http_engine_free(&eng);
|
||||
close(client_fd);
|
||||
}
|
||||
|
||||
close(listen_fd);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
This interface allows TinyHTTP to decouple the HTTP logic from the I/O. This has many advantages such as simplifying testing and decoupling TLS from the HTTP logic.
|
||||
|
||||
On top of the engine, TinyHTTP implements a fully functional and easy to use client and server. Both use `poll()` to handle non-blocking operations. The server API looks like this:
|
||||
|
||||
```c
|
||||
#include "http.h"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int ret;
|
||||
HTTP_Server server;
|
||||
|
||||
ret = http_server_init(&server, "127.0.0.1", 8080);
|
||||
if (ret < 0) return -1;
|
||||
|
||||
for (;;) {
|
||||
HTTP_Request *req;
|
||||
HTTP_ResponseHandle res;
|
||||
|
||||
ret = http_server_wait(&server, &req, &res, -1);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
if (ret == 0)
|
||||
continue;
|
||||
|
||||
http_response_status(res, 200);
|
||||
http_response_header(res, "Server: tinyhttp");
|
||||
http_response_body(res, "Hello, world!", 13);
|
||||
http_response_done(res);
|
||||
}
|
||||
|
||||
http_server_free(&server);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
while the client interface looks like this (note that I omitted error checking for making easier to digest)
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include "http.h"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
HTTP_Client client;
|
||||
HTTP_TLSContext tls;
|
||||
|
||||
// Initialize the TLS stuff
|
||||
http_tls_global_init();
|
||||
http_tls_init(&tls);
|
||||
|
||||
// Initialize the client context
|
||||
http_client_init(&clients[0]);
|
||||
|
||||
// Start the request
|
||||
http_client_startreq(&clients[0], HTTP_METHOD_GET, "https://coz.is/hello.html", NULL, 0, NULL, 0, &tls);
|
||||
|
||||
// Wait for the request to complete
|
||||
// (you could wait for more multiple request at once)
|
||||
HTTP_Client *wait_list[] = { &client };
|
||||
http_client_waitall(waitlist, 1, -1);
|
||||
|
||||
// Read the response
|
||||
HTTP_Response *res;
|
||||
http_client_result(&clients[0], &res);
|
||||
fwrite(res->body.ptr, 1, res->body.len, stdout);
|
||||
|
||||
// Free the client context
|
||||
http_client_free(&clients);
|
||||
|
||||
// Free the TLS stuff
|
||||
http_tls_free(&tls);
|
||||
http_tls_global_free();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
The client supports HTTPS by using OpenSSL, but the implementation is incomplete. You can get basic requests going but most things are not working yet. The server only supports plain text HTTP, but the interface is more mature. I think it's a great choice for small to medium websites (up to 500 concurrent users I'd say).
|
||||
|
||||
The last layer is the router, which sits on top of the HTTP server. This simplifies the work of serving files from disk or setting up routes with dynamic content in an easy and safe way. Here's an example:
|
||||
|
||||
```c
|
||||
#include "http.h"
|
||||
|
||||
void endpoint_login(HTTP_Request *req, HTTP_ResponseHandle res, void *ctx)
|
||||
{
|
||||
http_response_status(res, 200);
|
||||
http_response_body(res, "Hello!", -1);
|
||||
http_response_done(res);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
HTTP_Router *router = http_router_init();
|
||||
|
||||
// Make /say_hello a dynamic resource
|
||||
http_router_func(router, HTTP_METHOD_POST, HTTP_STR("/say_hello"), callback, NULL);
|
||||
|
||||
// Requests to resources in the root folder are served from the examples folder
|
||||
http_router_dir(router, HTTP_STR("/"), HTTP_STR("/examples"));
|
||||
|
||||
return http_serve("127.0.0.1", 8080, router);
|
||||
}
|
||||
```
|
||||
@@ -1,232 +0,0 @@
|
||||
# TinyHTTP
|
||||
|
||||
TinyHTTP is a C library for implementing web servers. It offers two interfaces:
|
||||
1. stream interface
|
||||
1. server interface
|
||||
|
||||
The stream interface is a lower level interface to TinyHTTP's HTTP state machine. It's completely stand-alone and performs no internal I/O, making it ideal for embedding in applications with custom constraints. The only dependency are freestanding libc headers.
|
||||
|
||||
The server interface is based on the stream interface and adds to it a platform-dependant I/O system. It uses the most performant I/O model available on the platform it's compiled for, but is single-threaded. The design goal of the server interface is ease of use and reasonable performance. It does also depend on libc.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. Stream Interface
|
||||
1. Initialize a Stream
|
||||
1. Stream I/O
|
||||
1. Response Builer API
|
||||
1. Basic Usage
|
||||
1. Special Headers
|
||||
1. Zero-Copy Response Building
|
||||
1. Error Management
|
||||
1. The State Bitset
|
||||
1. Embedding in Custom Event Loops
|
||||
1. Ready-based Event Loops
|
||||
1. Completion-based Event Loops
|
||||
|
||||
## Stream Interface
|
||||
|
||||
The stream interface is based on the `TinyHTTPStream` object, which abstracts the communication between the server and one client. Generally, a non-blocking web server will hold an array of stream objects.
|
||||
|
||||
Applications must feed input bytes from the network into the stream and flush any bytes from the stream to the network. When the stream becomes "ready", the application creates a response using the response builder interface and submits it to the stream. If at any point something goes wrong, the stream is marked as DIED.
|
||||
|
||||
Since the stream object doesn't perform read/write operations on the socket directly but waits for the application to provide I/O bytes, it is trivial to add HTTPS support. Before bytes are read/written into the stream, applications can perform a TLS encryption/decryption step.
|
||||
|
||||
### Initialize a Stream
|
||||
|
||||
The only resources held by the stream object are the input and output buffers, which are contiguous buffers resized as needed. Therefore the only thing the stream depends on is a general purpose allocator. To keep the core of TinyHTTP dependency-free, allocation is done through a callback:
|
||||
|
||||
```c
|
||||
static void *memfunc(TinyHTTPMemoryFuncTag tag,
|
||||
void *ptr, int len, void *data)
|
||||
{
|
||||
(void) data;
|
||||
switch (tag) {
|
||||
|
||||
case TINYHTTP_MEM_MALLOC:
|
||||
// ptr is null
|
||||
// len contains the allocation size
|
||||
return malloc(len);
|
||||
|
||||
case TINYHTTP_MEM_FREE:
|
||||
// ptr is the previous allocation
|
||||
// len contains the size of the allocation (as requested during malloc)
|
||||
free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
TinyHTTPStream stream;
|
||||
tinyhttp_stream_init(&stream, memfunc, NULL);
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
in most cases this will be a wrapper of malloc/free.
|
||||
|
||||
### Stream I/O
|
||||
|
||||
When the stream is ready to receive bytes, applications can use the `tinyhttp_stream_recv_buf` to start a receive operation. This function will return a pointer into the stream's input buffer where data can be written from the network. The function returns from an output argument the capacity of the returned region. No more bytes than the capacity must be written.
|
||||
|
||||
When the write input the input buffer is complete, applications must call the `tinyhttp_stream_recv_ack` to complete the operation and let the stream object know how many bytes were written into the buffer.
|
||||
|
||||
It's not possible to start multiple recv operations at once. In other words, once you call `tinyhttp_stream_recv_buf`, you can't call it again until `tinyhttp_stream_recv_ack` is called.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```c
|
||||
ptrdiff_t cap;
|
||||
char *dst;
|
||||
|
||||
// Get the input buffer's pointer
|
||||
dst = tinyhttp_stream_recv_buf(stream, &cap);
|
||||
|
||||
// Write to the buffer
|
||||
int num = recv(socket_fd, dst, cap, 0);
|
||||
if (num < 0) {
|
||||
// ... error ...
|
||||
}
|
||||
if (num == 0) {
|
||||
// ... peer disconnected ...
|
||||
}
|
||||
|
||||
// Tell the stream there are new bytes available
|
||||
tinyhttp_stream_recv_ack(stream, num);
|
||||
```
|
||||
|
||||
Flushing data from the stream works the same exact way, except you copy data out of the returned pointer instead of writing to it:
|
||||
|
||||
```c
|
||||
ptrdiff_t len;
|
||||
char *src;
|
||||
|
||||
// Get the output buffer's pointer
|
||||
src = tinyhttp_stream_send_buf(stream, &len);
|
||||
|
||||
// Write to the buffer
|
||||
int num = send(socket_fd, src, len, 0);
|
||||
if (num < 0) {
|
||||
// ... error ...
|
||||
}
|
||||
|
||||
// Tell the stream how many bytes were flushed
|
||||
tinyhttp_stream_send_ack(stream, num);
|
||||
```
|
||||
|
||||
Note that if an error occurred and the stream is in the DIED state (more on that later), the `tinyhttp_stream_recv_buf` and `tinyhttp_stream_send_buf` will return a null pointer and a zero length/capacity. In this scenario, it doesn't matter what you do since an unrecoverable error occurred in the stream which will be shortly freed.
|
||||
|
||||
### Response Builder API
|
||||
|
||||
#### Basic Usage
|
||||
|
||||
When the stream reaches the "ready" state, applications can use the response builder interface to generate a response:
|
||||
|
||||
```c
|
||||
TinyHTTPRequest *req = tinyhttp_stream_request(stream);
|
||||
if (req == NULL) {
|
||||
// Not ready yet
|
||||
} else {
|
||||
// Ready!
|
||||
|
||||
// Get the parsed request
|
||||
TinyHTTPRequest *request = tinyhttp_stream_request(stream);
|
||||
|
||||
// .. read the request ..
|
||||
|
||||
tinyhttp_stream_response_status(stream, 200);
|
||||
tinyhttp_stream_response_header(stream, "Some-header: %d", 100);
|
||||
tinyhttp_stream_response_header(stream, "Other-header: %s", "hello");
|
||||
tinyhttp_stream_response_body(stream, "Hello, world!", -1);
|
||||
tinyhttp_stream_response_send(stream);
|
||||
}
|
||||
```
|
||||
|
||||
The `tinyhttp_stream_request` function returns the parsed version of the buffered request. You must always access request information through the returned pointer and not copy and pointers from it since they may be invalidated if the stream decides to move data in the input buffer.
|
||||
|
||||
These response functions follow a strict state machine. You must first call the status function one time, then the header functions zero or more times, then the body functions zero or more times, and finally the send function. At any point you can drop the response and start from scratch by using the undo function:
|
||||
|
||||
```c
|
||||
tinyhttp_stream_response_status(stream, 200);
|
||||
if (error_occurred) {
|
||||
tinyhttp_stream_response_undo(stream);
|
||||
tinyhttp_stream_response_status(stream, 500);
|
||||
}
|
||||
tinyhttp_stream_response_send(stream);
|
||||
```
|
||||
|
||||
#### Special Headers
|
||||
|
||||
TinyHTTP adds some special headers under the hood:
|
||||
|
||||
* Content-Length
|
||||
* Transfer-Encoding
|
||||
* Connection
|
||||
|
||||
so you should avoid adding these manually.
|
||||
|
||||
#### Zero-Copy Response Building
|
||||
|
||||
TODO: Talk about body_buf/ack/setmincap
|
||||
|
||||
#### Error Management
|
||||
|
||||
The response builder interface also uses sticky errors to lift the burdain of error management from the application.
|
||||
|
||||
If an error occurs while building the response, tinyhttp either sends a code 500 response or sets the DIED state. Either way, this is totally transparent to the application.
|
||||
|
||||
### The State Bitset
|
||||
|
||||
To know which operations are allowed on a stream at any given time, applications can use the `tinyhttp_stream_state` function:
|
||||
|
||||
```c
|
||||
int state = tinyhttp_stream_state(stream);
|
||||
|
||||
if (state & TINYHTTP_STREAM_RECV) {
|
||||
// stream is ready for input bytes
|
||||
}
|
||||
|
||||
if (state & TINYHTTP_STREAM_SEND) {
|
||||
// stream is ready for output
|
||||
}
|
||||
|
||||
if (state & TINYHTTP_STREAM_RECV_STARTED) {
|
||||
// A recv operation is in progress
|
||||
}
|
||||
|
||||
if (state & TINYHTTP_STREAM_SEND_STARTED) {
|
||||
// A send operation is in progress
|
||||
}
|
||||
|
||||
if (state & TINYHTTP_STREAM_READY) {
|
||||
// Request was buffered so it's now possible
|
||||
// to build a response
|
||||
}
|
||||
|
||||
if (state & TINYHTTP_STREAM_DIED) {
|
||||
// The stream died
|
||||
}
|
||||
```
|
||||
|
||||
The `TINYHTTP_STREAM_RECV` and `TINYHTTP_STREAM_SEND` flags tell the application when it should call `tinyhttp_stream_recv_buf` or `tinyhttp_stream_send_buf`.
|
||||
|
||||
The `TINYHTTP_STREAM_RECV_STARTED` and `TINYHTTP_STREAM_SEND_STARTED` tell the application when calls to `tinyhttp_stream_recv_ack` or `tinyhttp_stream_send_ack` are expected due to previous calls to `tinyhttp_stream_recv_buf` or `tinyhttp_stream_send_buf`.
|
||||
|
||||
The `TINYHTTP_STREAM_READY` tells the application a request was buffered and it is now possible to build a response. The parsed request can by accessed with `tinyhttp_stream_request`.
|
||||
|
||||
The `TINYHTTP_STREAM_DIED` flag indicates an unreacoverable error occurred and all resources associated to the stream and the stream itself should be freed. DIED connections keep their input and output buffers intact to allow any pending asynchronous operations reading or writing to the stream's buffers to complete safely. When no one is using the stream's buffers, applications should free the stream. If you are handling states with a chain of if statements, you should handle the DIED state last since the code before it may cause the stream to go into the DIED state.
|
||||
|
||||
### Embedding in Custom Event Loops
|
||||
In this sections there are some pseudocode examples of how one would use stream objects with various event loop models.
|
||||
|
||||
#### Ready-based Event Loops
|
||||
Ready-based event loops are those which report that an operation can be performed on a given resource in a non-blocking way. Examples of ready-based event loops are those based on epoll, poll, and select.
|
||||
|
||||
TODO
|
||||
|
||||
#### Completion-based Event Loops
|
||||
Completion-based event loops are an other way of referring to asynchronous I/O. With the completion-based model, when a thread performs a read or write operation on a resource, it does not wait for its completion. Instead, it continues running by doing other stuff. When no more work can be performed until an read/write operation completes, it waits until the next completion. Examples of this model are Windows's I/O completion ports and Linux's io_uring.
|
||||
|
||||
TODO
|
||||
@@ -1,51 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include "../tinyhttp.h"
|
||||
|
||||
sig_atomic_t should_exit = 0;
|
||||
|
||||
static void
|
||||
signal_handler(int sig)
|
||||
{
|
||||
if (sig == SIGINT)
|
||||
should_exit = 1;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
TinyHTTPServerConfig config = {
|
||||
.reuse = 1,
|
||||
.plain_addr = "127.0.0.1",
|
||||
.plain_port = 8080,
|
||||
.plain_backlog = 32,
|
||||
};
|
||||
|
||||
TinyHTTPServer *server = tinyhttp_server_init(config);
|
||||
if (server == NULL)
|
||||
return -1;
|
||||
|
||||
while (!should_exit) {
|
||||
|
||||
int ret;
|
||||
TinyHTTPRequest *req;
|
||||
TinyHTTPResponse res;
|
||||
|
||||
ret = tinyhttp_server_wait(server, &req, &res, 1000);
|
||||
if (ret < 0) return -1; // Error
|
||||
if (ret > 0) continue; // Timeout
|
||||
|
||||
tinyhttp_response_status(res, 200);
|
||||
tinyhttp_response_body(res, "Hello, world!", -1);
|
||||
|
||||
//tinyhttp_response_undo(res);
|
||||
//tinyhttp_response_status(res, 500);
|
||||
|
||||
tinyhttp_response_send(res);
|
||||
}
|
||||
|
||||
tinyhttp_server_free(server);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
#include "../tinyhttp.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// CONFIGURATION
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
// Network stuff
|
||||
#define ADDR "127.0.0.1"
|
||||
#define PORT 8080
|
||||
#define REUSE 1
|
||||
#define BACKLOG 32
|
||||
|
||||
// Social stuff
|
||||
#define MAX_USERS 32
|
||||
#define MAX_POSTS 32
|
||||
#define MAX_USERNAME 128
|
||||
#define MAX_PASSWORD 256
|
||||
#define MAX_BIO 512
|
||||
#define MAX_CONTENT 512
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// UTILITIES
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
typedef struct {
|
||||
char *ptr;
|
||||
ptrdiff_t len;
|
||||
} string;
|
||||
|
||||
#define S(X) ((string) {(X), sizeof(X)-1})
|
||||
|
||||
static string trim(string s)
|
||||
{
|
||||
int i = 0;
|
||||
while (i < s.len && (s.ptr[i] == ' ' || s.ptr[i] == '\t'))
|
||||
i++;
|
||||
|
||||
if (i == s.len) {
|
||||
s.ptr = NULL;
|
||||
s.len = 0;
|
||||
} else {
|
||||
s.ptr += i;
|
||||
s.len -= i;
|
||||
while (s.ptr[s.len-1] == ' ' || s.ptr[s.len-1] == '\n')
|
||||
s.len--;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static int copy(string src, string *dst, char *mem, int cap)
|
||||
{
|
||||
if (src.len > cap)
|
||||
return -1;
|
||||
memcpy(mem, src.ptr, src.len);
|
||||
dst->ptr = mem;
|
||||
dst->len = src.len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// BUSINESS LOGIC
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
typedef struct {
|
||||
string name;
|
||||
string pass;
|
||||
string bio;
|
||||
char name_mem[MAX_USERNAME];
|
||||
char pass_mem[MAX_PASSWORD];
|
||||
char bio_mem[MAX_BIO];
|
||||
} User;
|
||||
|
||||
typedef struct {
|
||||
int id;
|
||||
string author;
|
||||
string content;
|
||||
char author_mem[MAX_USERNAME];
|
||||
char content_mem[MAX_CONTENT];
|
||||
} Post;
|
||||
|
||||
static User users[MAX_USERS];
|
||||
static int user_count = 0;
|
||||
|
||||
static Post posts[MAX_POSTS];
|
||||
static int posts_head = 0;
|
||||
static int posts_count = 0;
|
||||
|
||||
static void init_users_and_posts(void)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
static int find_user(string name)
|
||||
{
|
||||
for (int i = 0; i < user_count; i++)
|
||||
if (streq(name, users[i].name))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int create_user(string name, string pass)
|
||||
{
|
||||
name = trim(name);
|
||||
pass = trim(pass);
|
||||
|
||||
if (name.len == 0 || pass.len == 0)
|
||||
return -1;
|
||||
|
||||
if (find_user(name) != -1)
|
||||
return -1;
|
||||
|
||||
if (user_count == MAX_USERS)
|
||||
return -1;
|
||||
|
||||
User *user = &users[user_count];
|
||||
|
||||
if (copy(name, &user->name, user->name_mem, sizeof(user->name_mem)) < 0 &&
|
||||
copy(pass, &user->pass, user->pass_mem, sizeof(user->pass_mem)) < 0)
|
||||
return -1;
|
||||
|
||||
user_count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int delete_user(string name)
|
||||
{
|
||||
int i = find_user(name);
|
||||
if (i == -1)
|
||||
return -1;
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
static int create_post(string author, string content)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
static int delete_post(int id)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
static int modify_post(int id, string new_content)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// ENDPOINTS
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
#define MAX_SESSIONS 32
|
||||
|
||||
typedef struct {
|
||||
int id;
|
||||
string name;
|
||||
char name_mem[MAX_USERNAME];
|
||||
} Session;
|
||||
|
||||
static Session sessions[MAX_SESSIONS];
|
||||
static int session_count = 0;
|
||||
|
||||
static void init_sessions(void)
|
||||
{
|
||||
for (int i = 0; i < MAX_SESSIONS; i++)
|
||||
sessions[i].id = -1;
|
||||
}
|
||||
|
||||
static int find_session(int id)
|
||||
{
|
||||
for (int i = 0; i < MAX_SESSIONS; i++)
|
||||
if (sessions[i].id == id)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int create_session(string name)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
static int delete_session(string name)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// ENTRY POINT
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
static sig_atomic_t should_exit = 0;
|
||||
|
||||
static void
|
||||
signal_handler(int sig)
|
||||
{
|
||||
if (sig == SIGINT)
|
||||
should_exit = 1;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
init_users_and_posts();
|
||||
init_sessions();
|
||||
|
||||
TinyHTTPServerConfig config = {
|
||||
.reuse = REUSE,
|
||||
.plain_addr = ADDR,
|
||||
.plain_port = PORT,
|
||||
.plain_backlog = BACKLOG,
|
||||
};
|
||||
|
||||
TinyHTTPServer *server = tinyhttp_server_init(config);
|
||||
if (server == NULL)
|
||||
return -1;
|
||||
|
||||
while (!should_exit) {
|
||||
|
||||
int ret;
|
||||
TinyHTTPRequest *req;
|
||||
TinyHTTPResponse res;
|
||||
|
||||
ret = tinyhttp_server_wait(server, &req, &res, 1000);
|
||||
if (ret < 0) return -1; // Error
|
||||
if (ret > 0) continue; // Timeout
|
||||
|
||||
char path_mem[1<<10];
|
||||
TinyHTTPString path;
|
||||
if (tinyhttp_normalizepath(req->path, &path, path_mem, sizeof(path_mem)) < 0) {
|
||||
tinyhttp_response_status(res, 200);
|
||||
tinyhttp_response_send(res);
|
||||
continue;
|
||||
}
|
||||
|
||||
TinyHTTPString sessid = tinyhttp_getcookie(req, TINYHTTP_STRING("sessid"));
|
||||
|
||||
if (tinyhttp_streq(path, TINYHTTP_STRING("/users"))) {
|
||||
tinyhttp_response_status(res, 200);
|
||||
tinyhttp_response_body(res, "<html><head><title>Users</title></head><body><ul>", -1);
|
||||
for (int i = 0; i < user_count; i++)
|
||||
tinyhttp_response_body(res, "<li><a href=\"/users/???\">???</a></li>", -1);
|
||||
tinyhttp_response_body(res, "</ul></body></html>", -1);
|
||||
tinyhttp_response_send(res);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tinyhttp_streq(path, TINYHTTP_STRING("/posts"))) {
|
||||
tinyhttp_response_status(res, 200);
|
||||
tinyhttp_response_body(res, "<html><head><title>Posts</title></head><body><ul>", -1);
|
||||
for (int i = 0; i < user_count; i++)
|
||||
tinyhttp_response_body(res, "<li><a href=\"/posts/???\">???</a></li>", -1);
|
||||
tinyhttp_response_body(res, "</ul></body></html>", -1);
|
||||
tinyhttp_response_send(res);
|
||||
continue;
|
||||
}
|
||||
|
||||
tinyhttp_response_status(res, 404);
|
||||
tinyhttp_response_body(res, "<html><head><title>Not Found</title></head><body>Nothing here!</body></html>", -1);
|
||||
tinyhttp_response_send(res);
|
||||
}
|
||||
|
||||
tinyhttp_server_free(server);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
#include "../tinyhttp.h"
|
||||
|
||||
// TODO: Complete this example
|
||||
|
||||
int main(void)
|
||||
{
|
||||
SOCKET listen_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (listen_fd == INVALID_SOCKET)
|
||||
return -1;
|
||||
|
||||
for (;;) {
|
||||
|
||||
SOCKET client_fd = accept(listen_fd, NULL, NULL);
|
||||
if (client_fd == INVALID_SOCKET)
|
||||
continue;
|
||||
|
||||
TinyHTTPStream stream;
|
||||
tinyhttp_stream_init(&stream, memfunc, NULL);
|
||||
|
||||
int state = tinyhttp_stream_state(&stream);
|
||||
while (!(state & TINYHTTP_STREAM_READY)) {
|
||||
|
||||
ASSERT(state & TINYHTTP_STREAM_RECV);
|
||||
|
||||
char *dst;
|
||||
ptrdiff_t cap;
|
||||
|
||||
dst = tinyhttp_stream_recv_buf(&stream, &cap);
|
||||
|
||||
int ret = recv(client_fd, dst, cap, 0);
|
||||
if (ret < 0) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
tinyhttp_stream_recv_ack(&stream, ret);
|
||||
state = tinyhttp_stream_state(&stream);
|
||||
}
|
||||
|
||||
TinyHTTPRequest *request = tinyhttp_stream_request(&stream);
|
||||
|
||||
tinyhttp_stream_response_status(&stream, 200);
|
||||
tinyhttp_stream_response_body(&stream, "Hello, world!", -1);
|
||||
tinyhttp_stream_response_send(&stream);
|
||||
|
||||
while (state & TINYHTTP_STREAM_SEND) {
|
||||
|
||||
char *src;
|
||||
ptrdiff_t len;
|
||||
|
||||
src = tinyhttp_stream_send_buf(&stream, &len);
|
||||
|
||||
int ret = send(client_fd, src, len, 0);
|
||||
if (ret < 0) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
tinyhttp_stream_send_ack(&stream, ret);
|
||||
state = tinyhttp_stream_state(&stream);
|
||||
}
|
||||
|
||||
tinyhttp_stream_free(&stream);
|
||||
CLOSESOCKET(client_fd);
|
||||
}
|
||||
|
||||
CLOSESOCKET(listen_fd);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
#include <assert.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <winsock2.h>
|
||||
#define CLOSESOCKET closesocket
|
||||
#elif defined(__linux__)
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <arpa/inet.h>
|
||||
#define CLOSESOCKET close
|
||||
#else
|
||||
#error "Only Windows and Linux are supported"
|
||||
#endif
|
||||
|
||||
#include "../tinyhttp.h"
|
||||
|
||||
#define PORT 8080
|
||||
#define ADDR "127.0.0.1"
|
||||
|
||||
// TODO: Complete this
|
||||
|
||||
void respond(TinyHTTPStream *stream)
|
||||
{
|
||||
TinyHTTPRequest *req = tinyhttp_stream_request(stream);
|
||||
if (req->method != TINYHTTP_METHOD_GET)
|
||||
tinyhttp_stream_response_status(stream, 405);
|
||||
else
|
||||
tinyhttp_stream_response_status(stream, 200);
|
||||
tinyhttp_stream_response_send(stream);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
struct sockaddr_in buf;
|
||||
buf.sin_family = AF_INET;
|
||||
buf.sin_port = htons(PORT);
|
||||
buf.sin_addr.s_addr = inet_addr(ADDR);
|
||||
bind(listen_fd, (struct sockaddr*) &buf, sizeof(buf));
|
||||
|
||||
listen(listen_fd, 32);
|
||||
|
||||
int num_conns = 0;
|
||||
int fds[1000];
|
||||
TinyHTTPStream streams[1000];
|
||||
|
||||
for (int i = 0; i < 1000; i++)
|
||||
fds[i] = -1;
|
||||
|
||||
for (;;) {
|
||||
// TODO: timeouts
|
||||
|
||||
fd_set readset;
|
||||
fd_set writeset;
|
||||
FD_ZERO(&readset);
|
||||
FD_ZERO(&writeset);
|
||||
|
||||
FD_SET(listen_fd, &readset);
|
||||
int max_fd = listen_fd;
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
if (fds[i] == -1) continue;
|
||||
int state = tinyhttp_stream_state(&streams[i]);
|
||||
if (state & TINYHTTP_STREAM_RECV)
|
||||
FD_SET(fds[i], &readset);
|
||||
if (state & TINYHTTP_STREAM_SEND)
|
||||
FD_SET(fds[i], &writeset);
|
||||
if (state & (TINYHTTP_STREAM_RECV | TINYHTTP_STREAM_SEND))
|
||||
if (max_fd < fds[i]) max_fd = fds[i];
|
||||
}
|
||||
|
||||
int num = select(max_fd+1, &readset, &writeset, NULL, NULL);
|
||||
|
||||
if (FD_ISSET(listen_fd, &readset)) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
int ready_queue[1000];
|
||||
int ready_head = 0;
|
||||
int ready_count = 0;
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
while (ready_count > 0) {
|
||||
|
||||
int idx = ready_queue[ready_head];
|
||||
TinyHTTPStream *stream = &streams[idx];
|
||||
|
||||
TinyHTTPRequest *req = tinyhttp_stream_request(stream);
|
||||
assert(req);
|
||||
|
||||
respond(stream);
|
||||
|
||||
ready_head = (ready_head + 1) % 1000;
|
||||
ready_count--;
|
||||
if (tinyhttp_stream_request(stream)) {
|
||||
ready_queue[(ready_head + ready_count) % 1000] = idx;
|
||||
ready_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CLOSESOCKET(listen_fd);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
#ifndef HTTP_INCLUDED
|
||||
#define HTTP_INCLUDED
|
||||
|
||||
#ifndef HTTP_PARSE
|
||||
#define HTTP_PARSE 1
|
||||
#endif
|
||||
|
||||
#ifndef HTTP_ENGINE
|
||||
#define HTTP_ENGINE 1
|
||||
#endif
|
||||
|
||||
#ifndef HTTP_CLIENT
|
||||
#define HTTP_CLIENT 1
|
||||
#endif
|
||||
|
||||
#ifndef HTTP_SERVER
|
||||
#define HTTP_SERVER 1
|
||||
#endif
|
||||
|
||||
#ifndef HTTP_ROUTER
|
||||
#define HTTP_ROUTER 1
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// UTILITIES
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define HTTP_STR(X) ((HTTP_String) {(X), sizeof(X)-1})
|
||||
#define HTTP_CEIL(X, Y) (((X) + (Y) - 1) / (Y))
|
||||
|
||||
typedef struct {
|
||||
char *ptr;
|
||||
long len;
|
||||
} HTTP_String;
|
||||
|
||||
int http_streq (HTTP_String s1, HTTP_String s2);
|
||||
int http_streqcase (HTTP_String s1, HTTP_String s2);
|
||||
HTTP_String http_trim (HTTP_String s);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// HTTP PARSER
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
#if HTTP_PARSE
|
||||
|
||||
#define HTTP_MAX_HEADERS 32
|
||||
|
||||
typedef struct {
|
||||
unsigned int data;
|
||||
} HTTP_IPv4;
|
||||
|
||||
typedef struct {
|
||||
unsigned short data[8];
|
||||
} HTTP_IPv6;
|
||||
|
||||
typedef enum {
|
||||
HTTP_HOST_MODE_VOID = 0,
|
||||
HTTP_HOST_MODE_NAME,
|
||||
HTTP_HOST_MODE_IPV4,
|
||||
HTTP_HOST_MODE_IPV6,
|
||||
} HTTP_HostMode;
|
||||
|
||||
typedef struct {
|
||||
HTTP_HostMode mode;
|
||||
HTTP_String text;
|
||||
union {
|
||||
HTTP_String name;
|
||||
HTTP_IPv4 ipv4;
|
||||
HTTP_IPv6 ipv6;
|
||||
};
|
||||
} HTTP_Host;
|
||||
|
||||
typedef struct {
|
||||
HTTP_String userinfo;
|
||||
HTTP_Host host;
|
||||
int port;
|
||||
} HTTP_Authority;
|
||||
|
||||
// ZII
|
||||
typedef struct {
|
||||
HTTP_String scheme;
|
||||
HTTP_Authority authority;
|
||||
HTTP_String path;
|
||||
HTTP_String query;
|
||||
HTTP_String fragment;
|
||||
} HTTP_URL;
|
||||
|
||||
typedef enum {
|
||||
HTTP_METHOD_GET,
|
||||
HTTP_METHOD_HEAD,
|
||||
HTTP_METHOD_POST,
|
||||
HTTP_METHOD_PUT,
|
||||
HTTP_METHOD_DELETE,
|
||||
HTTP_METHOD_CONNECT,
|
||||
HTTP_METHOD_OPTIONS,
|
||||
HTTP_METHOD_TRACE,
|
||||
HTTP_METHOD_PATCH,
|
||||
} HTTP_Method;
|
||||
|
||||
typedef struct {
|
||||
HTTP_String name;
|
||||
HTTP_String value;
|
||||
} HTTP_Header;
|
||||
|
||||
typedef struct {
|
||||
HTTP_Method method;
|
||||
HTTP_URL url;
|
||||
int minor;
|
||||
int num_headers;
|
||||
HTTP_Header headers[HTTP_MAX_HEADERS];
|
||||
HTTP_String body;
|
||||
} HTTP_Request;
|
||||
|
||||
typedef struct {
|
||||
int minor;
|
||||
int status;
|
||||
HTTP_String reason;
|
||||
int num_headers;
|
||||
HTTP_Header headers[HTTP_MAX_HEADERS];
|
||||
HTTP_String body;
|
||||
} HTTP_Response;
|
||||
|
||||
int http_parse_ipv4 (char *src, int len, HTTP_IPv4 *ipv4);
|
||||
int http_parse_ipv6 (char *src, int len, HTTP_IPv6 *ipv6);
|
||||
int http_parse_url (char *src, int len, HTTP_URL *url);
|
||||
int http_parse_request (char *src, int len, HTTP_Request *req);
|
||||
int http_parse_response (char *src, int len, HTTP_Response *res);
|
||||
|
||||
HTTP_String http_getbodyparam (HTTP_Request *req, HTTP_String name);
|
||||
HTTP_String http_getcookie (HTTP_Request *req, HTTP_String name);
|
||||
|
||||
#endif // HTTP_PARSE
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// HTTP BYTE QUEUE
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
#if HTTP_ENGINE
|
||||
|
||||
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;
|
||||
|
||||
#endif // HTTP_ENGINE
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// HTTP ENGINE
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
#if HTTP_ENGINE
|
||||
|
||||
#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 (1 << 10)
|
||||
#define HTTP_ENGINE_STATEBIT_PREP_ERROR (1 << 11)
|
||||
#define HTTP_ENGINE_STATEBIT_PREP_URL (1 << 12)
|
||||
#define HTTP_ENGINE_STATEBIT_PREP_STATUS (1 << 13)
|
||||
#define HTTP_ENGINE_STATEBIT_CLOSING (1 << 14)
|
||||
|
||||
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 = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY,
|
||||
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 = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY,
|
||||
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);
|
||||
|
||||
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, char *url, int minor);
|
||||
void http_engine_status (HTTP_Engine *eng, int status);
|
||||
void http_engine_header (HTTP_Engine *eng, const char *src, int len);
|
||||
void http_engine_body (HTTP_Engine *eng, void *src, int len);
|
||||
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
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// HTTP CLIENT AND SERVER
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
#if HTTP_CLIENT || HTTP_SERVER
|
||||
|
||||
typedef unsigned long long HTTP_Socket;
|
||||
|
||||
#endif // HTTP_CLIENT || HTTP_SERVER
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// HTTP CLIENT
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
#if HTTP_CLIENT
|
||||
|
||||
#define HTTP_CLIENT_TLS 0
|
||||
|
||||
#define HTTP_CLIENT_WAIT_LIMIT (1<<9)
|
||||
|
||||
typedef struct { _Alignas(void*) char data[32]; } HTTP_TLSContext;
|
||||
typedef struct { _Alignas(void*) char data[32]; } HTTP_TLSClientContext;
|
||||
|
||||
typedef enum {
|
||||
HTTP_STATE_CLIENT_IDLE,
|
||||
HTTP_STATE_CLIENT_CONNECT,
|
||||
HTTP_STATE_CLIENT_TLS_HANDSHAKE_RECV,
|
||||
HTTP_STATE_CLIENT_TLS_HANDSHAKE_SEND,
|
||||
HTTP_STATE_CLIENT_RECV,
|
||||
HTTP_STATE_CLIENT_SEND,
|
||||
HTTP_STATE_CLIENT_CLOSED,
|
||||
HTTP_STATE_CLIENT_READY,
|
||||
} HTTP_ClientState;
|
||||
|
||||
enum {
|
||||
HTTP_CLIENT_OK = 0,
|
||||
HTTP_CLIENT_ERROR_INVURL = -1,
|
||||
HTTP_CLIENT_ERROR_NOSYS = -2,
|
||||
HTTP_CLIENT_ERROR_INVPROTO = -3,
|
||||
HTTP_CLIENT_ERROR_FSOCK = -4,
|
||||
HTTP_CLIENT_ERROR_FCONNECT = -5,
|
||||
HTTP_CLIENT_ERROR_DNS = -6,
|
||||
HTTP_CLIENT_ERROR_FSSLNEW = -7,
|
||||
HTTP_CLIENT_ERROR_FRECV = -8,
|
||||
HTTP_CLIENT_ERROR_FSSLREAD = -9,
|
||||
HTTP_CLIENT_ERROR_FSEND = -10,
|
||||
HTTP_CLIENT_ERROR_FSSLWRITE = -11,
|
||||
HTTP_CLIENT_ERROR_FGETSOCKOPT = -12,
|
||||
HTTP_CLIENT_ERROR_FSSLCONNECT = -13,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int secure;
|
||||
int code;
|
||||
HTTP_Socket fd;
|
||||
HTTP_Engine eng;
|
||||
HTTP_ClientState state;
|
||||
HTTP_TLSClientContext tls;
|
||||
} HTTP_Client;
|
||||
|
||||
void http_tls_global_init(void);
|
||||
void http_tls_global_free(void);
|
||||
|
||||
int http_tls_init(HTTP_TLSContext *tls);
|
||||
void http_tls_free(HTTP_TLSContext *tls);
|
||||
|
||||
void http_client_init(HTTP_Client *client);
|
||||
void http_client_free(HTTP_Client *client);
|
||||
|
||||
void http_client_startreq(
|
||||
HTTP_Client *client, HTTP_Method method,
|
||||
const char *url, HTTP_String *headers,
|
||||
int num_headers, char *body, int body_len,
|
||||
HTTP_TLSContext *tls);
|
||||
|
||||
int http_client_waitany(HTTP_Client **clients, int num_clients, int timeout);
|
||||
int http_client_waitall(HTTP_Client **clients, int num_clients, int timeout);
|
||||
|
||||
int http_client_result(HTTP_Client *client, HTTP_Response **res);
|
||||
const char *http_client_strerror(int code);
|
||||
|
||||
#endif // HTTP_CLIENT
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// HTTP SERVER
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
#if HTTP_SERVER
|
||||
|
||||
#define HTTP_MAX_CLIENTS_PER_SERVER (1<<9)
|
||||
|
||||
typedef unsigned long long HTTP_BitsetWord;
|
||||
typedef struct {
|
||||
HTTP_BitsetWord data[HTTP_CEIL(HTTP_MAX_CLIENTS_PER_SERVER,
|
||||
sizeof(HTTP_BitsetWord))];
|
||||
} HTTP_Bitset;
|
||||
|
||||
typedef struct {
|
||||
int head;
|
||||
int count;
|
||||
int items[HTTP_MAX_CLIENTS_PER_SERVER];
|
||||
HTTP_Bitset set;
|
||||
} HTTP_IntQueue;
|
||||
|
||||
typedef struct {
|
||||
void *ptr;
|
||||
int idx;
|
||||
int gen;
|
||||
} HTTP_ResponseHandle;
|
||||
|
||||
typedef struct {
|
||||
HTTP_Socket fd;
|
||||
HTTP_Engine eng;
|
||||
unsigned short gen;
|
||||
} HTTP_ServerConnection;
|
||||
|
||||
typedef struct {
|
||||
HTTP_Socket listen_fd;
|
||||
HTTP_IntQueue ready;
|
||||
int num_conns;
|
||||
HTTP_ServerConnection conns[HTTP_MAX_CLIENTS_PER_SERVER];
|
||||
} HTTP_Server;
|
||||
|
||||
int http_server_init(HTTP_Server *server,
|
||||
const char *addr, int port);
|
||||
|
||||
void http_server_free(HTTP_Server *server);
|
||||
|
||||
int http_server_wait(HTTP_Server *server, HTTP_Request **req,
|
||||
HTTP_ResponseHandle *res, int timeout);
|
||||
|
||||
void http_response_status(HTTP_ResponseHandle res, int status);
|
||||
void http_response_header(HTTP_ResponseHandle res, const char *fmt, ...);
|
||||
void http_response_body(HTTP_ResponseHandle res, char *src, int len);
|
||||
void http_response_done(HTTP_ResponseHandle res);
|
||||
void http_response_undo(HTTP_ResponseHandle res);
|
||||
|
||||
#endif // HTTP_SERVER
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// HTTP ROUTER
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
#if HTTP_ROUTER
|
||||
|
||||
typedef struct HTTP_Router HTTP_Router;
|
||||
typedef void (*HTTP_RouterFunc)(HTTP_Request*, HTTP_ResponseHandle, void*);;
|
||||
|
||||
HTTP_Router* http_router_init (void);
|
||||
void http_router_free (HTTP_Router *router);
|
||||
void http_router_resolve (HTTP_Router *router, HTTP_Request *req, HTTP_ResponseHandle res);
|
||||
void http_router_dir (HTTP_Router *router, HTTP_String endpoint, HTTP_String path);
|
||||
void http_router_func (HTTP_Router *router, HTTP_Method method, HTTP_String endpoint, HTTP_RouterFunc func, void*);
|
||||
int http_serve (const char *addr, int port, HTTP_Router *router);
|
||||
|
||||
#endif // HTTP_ROUTER
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// THE END
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
#endif // HTTP_INCLUDED
|
||||
@@ -1,238 +0,0 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
// This is a bare-bones epoll HTTP server to get a practical
|
||||
// req/sec limit.
|
||||
//
|
||||
// Compile:
|
||||
// gcc fast_epoll.c -o fast_epoll -Wall -Wextra -O2 -DNDEBUG
|
||||
|
||||
int main()
|
||||
{
|
||||
int epoll_fd = epoll_create1(0);
|
||||
if (epoll_fd < 0) {
|
||||
perror("epoll_create1");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (listen_fd == -1) {
|
||||
perror("socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
{
|
||||
int flags = fcntl(listen_fd, F_GETFL, 0);
|
||||
if (flags < 0) {
|
||||
perror("fcntl");
|
||||
return -1;
|
||||
}
|
||||
flags |= O_NONBLOCK;
|
||||
if (fcntl(listen_fd, F_SETFL, flags) < 0) {
|
||||
perror("fcntl");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int one = 1;
|
||||
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (char*) &one, sizeof(one)) < 0) {
|
||||
perror("setsockopt");
|
||||
close(listen_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_in bind_buf;
|
||||
bind_buf.sin_family = AF_INET;
|
||||
bind_buf.sin_port = htons(3333);
|
||||
bind_buf.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) {
|
||||
perror("bind");
|
||||
close(listen_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (listen(listen_fd, 32) < 0) {
|
||||
perror("listen");
|
||||
close(listen_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct epoll_event epoll_buf;
|
||||
epoll_buf.data.fd = -1;
|
||||
epoll_buf.events = EPOLLIN;
|
||||
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &epoll_buf) < 0) {
|
||||
perror("epoll_ctl");
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define CAPACITY (1<<9)
|
||||
#define IBUFCAP (1<<9)
|
||||
#define OBUFCAP (1<<9)
|
||||
#define MAXEXCH 100
|
||||
|
||||
int num_fds = 0;
|
||||
int fds[CAPACITY];
|
||||
int free_idx[CAPACITY];
|
||||
|
||||
char ibufs[CAPACITY][IBUFCAP];
|
||||
char obufs[CAPACITY][OBUFCAP];
|
||||
int sent[CAPACITY];
|
||||
int received[CAPACITY];
|
||||
int exchanged[CAPACITY];
|
||||
|
||||
for (int i = 0; i < CAPACITY; i++)
|
||||
free_idx[i] = i;
|
||||
|
||||
for (;;) {
|
||||
|
||||
struct epoll_event batch[CAPACITY];
|
||||
int num = epoll_wait(epoll_fd, batch, CAPACITY, -1);
|
||||
|
||||
for (int i = 0; i < num; i++) {
|
||||
|
||||
if (batch[i].data.fd < 0) {
|
||||
while (num_fds < CAPACITY) {
|
||||
int accepted_fd = accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK);
|
||||
if (accepted_fd < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
break;
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
int num_free = CAPACITY - num_fds;
|
||||
int idx = free_idx[num_free-1];
|
||||
|
||||
struct epoll_event epoll_buf;
|
||||
epoll_buf.data.fd = idx;
|
||||
epoll_buf.events = EPOLLIN;
|
||||
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, accepted_fd, &epoll_buf)) {
|
||||
perror("epoll_ctl");
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
fds[idx] = accepted_fd;
|
||||
sent[idx] = 0;
|
||||
received[idx] = 0;
|
||||
exchanged[idx] = 0;
|
||||
|
||||
num_fds++;
|
||||
}
|
||||
} else {
|
||||
|
||||
int flags = batch[i].events;
|
||||
int idx = batch[i].data.fd;
|
||||
int fd = fds[idx];
|
||||
|
||||
char *ibuf = ibufs[idx];
|
||||
char *obuf = obufs[idx];
|
||||
|
||||
int remove = 0;
|
||||
if (flags & EPOLLIN) {
|
||||
|
||||
for (;;) {
|
||||
if (received[idx] == IBUFCAP)
|
||||
__builtin_trap();
|
||||
int ret = recv(fd, ibuf + received[idx], IBUFCAP - received[idx], 0);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
break;
|
||||
remove = 1;
|
||||
break;
|
||||
}
|
||||
if (ret == 0) {
|
||||
remove = 1;
|
||||
break;
|
||||
}
|
||||
received[idx] += ret;
|
||||
}
|
||||
|
||||
if (!remove) {
|
||||
int found = -1;
|
||||
for (int i = 0; i < received[idx]; i++) {
|
||||
if (3 < received[idx] - i
|
||||
&& ibuf[i+0] == '\r'
|
||||
&& ibuf[i+1] == '\n'
|
||||
&& ibuf[i+2] == '\r'
|
||||
&& ibuf[i+3] == '\n') {
|
||||
found = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found > -1) {
|
||||
|
||||
char resp[] = "HTTP/1.1 204 OK\r\nConnection: Keep-Alive\r\n\r\n";
|
||||
memcpy(obuf + sent[idx], resp, sizeof(resp)-1);
|
||||
sent[idx] += sizeof(resp)-1;
|
||||
|
||||
int head_len = found+4;
|
||||
memcpy(ibuf, ibuf + received[idx], received[idx] - head_len);
|
||||
received[idx] -= head_len;
|
||||
|
||||
struct epoll_event epoll_buf;
|
||||
epoll_buf.data.fd = idx;
|
||||
epoll_buf.events = EPOLLOUT;
|
||||
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &epoll_buf) < 0) {
|
||||
perror("epoll_ctl");
|
||||
__builtin_trap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!remove && (flags & EPOLLOUT)) {
|
||||
int flushed = 0;
|
||||
do {
|
||||
int ret = send(fd, obuf + flushed, sent[idx] - flushed, 0);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
break;
|
||||
__builtin_trap();
|
||||
}
|
||||
if (ret == 0)
|
||||
__builtin_trap();
|
||||
flushed += ret;
|
||||
} while (flushed < sent[idx]);
|
||||
|
||||
memmove(obuf, obuf + flushed, sent[idx] - flushed);
|
||||
sent[idx] -= flushed;
|
||||
|
||||
if (sent[idx] == 0) {
|
||||
struct epoll_event epoll_buf;
|
||||
epoll_buf.data.fd = idx;
|
||||
epoll_buf.events = EPOLLIN;
|
||||
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &epoll_buf) < 0) {
|
||||
perror("epoll_ctl");
|
||||
__builtin_trap();
|
||||
}
|
||||
}
|
||||
exchanged[idx]++;
|
||||
if (exchanged[idx] == MAXEXCH)
|
||||
remove = 1;
|
||||
}
|
||||
|
||||
if (remove) {
|
||||
close(fds[idx]);
|
||||
fds[idx] = -1;
|
||||
int num_free = CAPACITY - num_fds;
|
||||
free_idx[num_free] = idx;
|
||||
num_fds--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(epoll_fd);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
table = [
|
||||
(1<<6, "DIED"),
|
||||
(1<<5, "RECV"),
|
||||
(1<<4, "SEND"),
|
||||
(1<<3, "RECV_STARTED"),
|
||||
(1<<2, "SEND_STARTED"),
|
||||
(1<<1, "READY"),
|
||||
(1<<0, "CLOSE"),
|
||||
]
|
||||
|
||||
outstates = [
|
||||
"STATUS",
|
||||
"HEADER",
|
||||
"BODY",
|
||||
]
|
||||
|
||||
i = 0
|
||||
k = 0
|
||||
while i < 2**len(table):
|
||||
|
||||
tags = []
|
||||
for entry in table:
|
||||
if i & entry[0]:
|
||||
tags.append(entry[1])
|
||||
|
||||
statestr = "|".join(tags)
|
||||
|
||||
if ("DIED" in tags) and ("READY" in tags):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if ("READY" not in tags) and ("DIED" not in tags) and ("SEND" not in tags) and ("RECV" not in tags):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if ("READY" in tags) and ("CLOSE" in tags):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if ("CLOSE" in tags) and ("SEND" not in tags):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if (("CLOSE" in tags) or ("READY" in tags)) and (("RECV" in tags)):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if ("DIED" in tags) and (("RECV" in tags) or ("SEND" in tags)):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if ("SEND_STARTED" in tags) and ("SEND" not in tags) and ("DIED" not in tags):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if ("RECV_STARTED" in tags) and ("RECV" not in tags) and ("DIED" not in tags):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if "READY" in tags:
|
||||
for outstate in outstates:
|
||||
print(k, statestr + "|" + outstate)
|
||||
k += 1
|
||||
else:
|
||||
print(k, statestr)
|
||||
k += 1
|
||||
|
||||
i += 1
|
||||
|
||||
"""
|
||||
Constraints:
|
||||
- STATUS/HEADER/BODY only when READY
|
||||
"""
|
||||
@@ -1,685 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
|
||||
* Shigeo Mitsunari
|
||||
*
|
||||
* The software is licensed under either the MIT License (below) or the Perl
|
||||
* license.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#ifdef __SSE4_2__
|
||||
#ifdef _MSC_VER
|
||||
#include <nmmintrin.h>
|
||||
#else
|
||||
#include <x86intrin.h>
|
||||
#endif
|
||||
#endif
|
||||
#include "picohttpparser.h"
|
||||
|
||||
#if __GNUC__ >= 3
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
#else
|
||||
#define likely(x) (x)
|
||||
#define unlikely(x) (x)
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define ALIGNED(n) _declspec(align(n))
|
||||
#else
|
||||
#define ALIGNED(n) __attribute__((aligned(n)))
|
||||
#endif
|
||||
|
||||
#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u)
|
||||
|
||||
#define CHECK_EOF() \
|
||||
if (buf == buf_end) { \
|
||||
*ret = -2; \
|
||||
return NULL; \
|
||||
}
|
||||
|
||||
#define EXPECT_CHAR_NO_CHECK(ch) \
|
||||
if (*buf++ != ch) { \
|
||||
*ret = -1; \
|
||||
return NULL; \
|
||||
}
|
||||
|
||||
#define EXPECT_CHAR(ch) \
|
||||
CHECK_EOF(); \
|
||||
EXPECT_CHAR_NO_CHECK(ch);
|
||||
|
||||
#define ADVANCE_TOKEN(tok, toklen) \
|
||||
do { \
|
||||
const char *tok_start = buf; \
|
||||
static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; \
|
||||
int found2; \
|
||||
buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \
|
||||
if (!found2) { \
|
||||
CHECK_EOF(); \
|
||||
} \
|
||||
while (1) { \
|
||||
if (*buf == ' ') { \
|
||||
break; \
|
||||
} else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \
|
||||
if ((unsigned char)*buf < '\040' || *buf == '\177') { \
|
||||
*ret = -1; \
|
||||
return NULL; \
|
||||
} \
|
||||
} \
|
||||
++buf; \
|
||||
CHECK_EOF(); \
|
||||
} \
|
||||
tok = tok_start; \
|
||||
toklen = buf - tok_start; \
|
||||
} while (0)
|
||||
|
||||
static const char *token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0"
|
||||
"\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1"
|
||||
"\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
|
||||
|
||||
static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found)
|
||||
{
|
||||
*found = 0;
|
||||
#if __SSE4_2__
|
||||
if (likely(buf_end - buf >= 16)) {
|
||||
__m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges);
|
||||
|
||||
size_t left = (buf_end - buf) & ~15;
|
||||
do {
|
||||
__m128i b16 = _mm_loadu_si128((const __m128i *)buf);
|
||||
int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS);
|
||||
if (unlikely(r != 16)) {
|
||||
buf += r;
|
||||
*found = 1;
|
||||
break;
|
||||
}
|
||||
buf += 16;
|
||||
left -= 16;
|
||||
} while (likely(left != 0));
|
||||
}
|
||||
#else
|
||||
/* suppress unused parameter warning */
|
||||
(void)buf_end;
|
||||
(void)ranges;
|
||||
(void)ranges_size;
|
||||
#endif
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret)
|
||||
{
|
||||
const char *token_start = buf;
|
||||
|
||||
#ifdef __SSE4_2__
|
||||
static const char ALIGNED(16) ranges1[16] = "\0\010" /* allow HT */
|
||||
"\012\037" /* allow SP and up to but not including DEL */
|
||||
"\177\177"; /* allow chars w. MSB set */
|
||||
int found;
|
||||
buf = findchar_fast(buf, buf_end, ranges1, 6, &found);
|
||||
if (found)
|
||||
goto FOUND_CTL;
|
||||
#else
|
||||
/* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */
|
||||
while (likely(buf_end - buf >= 8)) {
|
||||
#define DOIT() \
|
||||
do { \
|
||||
if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \
|
||||
goto NonPrintable; \
|
||||
++buf; \
|
||||
} while (0)
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
DOIT();
|
||||
#undef DOIT
|
||||
continue;
|
||||
NonPrintable:
|
||||
if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
|
||||
goto FOUND_CTL;
|
||||
}
|
||||
++buf;
|
||||
}
|
||||
#endif
|
||||
for (;; ++buf) {
|
||||
CHECK_EOF();
|
||||
if (unlikely(!IS_PRINTABLE_ASCII(*buf))) {
|
||||
if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
|
||||
goto FOUND_CTL;
|
||||
}
|
||||
}
|
||||
}
|
||||
FOUND_CTL:
|
||||
if (likely(*buf == '\015')) {
|
||||
++buf;
|
||||
EXPECT_CHAR('\012');
|
||||
*token_len = buf - 2 - token_start;
|
||||
} else if (*buf == '\012') {
|
||||
*token_len = buf - token_start;
|
||||
++buf;
|
||||
} else {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
*token = token_start;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret)
|
||||
{
|
||||
int ret_cnt = 0;
|
||||
buf = last_len < 3 ? buf : buf + last_len - 3;
|
||||
|
||||
while (1) {
|
||||
CHECK_EOF();
|
||||
if (*buf == '\015') {
|
||||
++buf;
|
||||
CHECK_EOF();
|
||||
EXPECT_CHAR('\012');
|
||||
++ret_cnt;
|
||||
} else if (*buf == '\012') {
|
||||
++buf;
|
||||
++ret_cnt;
|
||||
} else {
|
||||
++buf;
|
||||
ret_cnt = 0;
|
||||
}
|
||||
if (ret_cnt == 2) {
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
*ret = -2;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define PARSE_INT(valp_, mul_) \
|
||||
if (*buf < '0' || '9' < *buf) { \
|
||||
buf++; \
|
||||
*ret = -1; \
|
||||
return NULL; \
|
||||
} \
|
||||
*(valp_) = (mul_) * (*buf++ - '0');
|
||||
|
||||
#define PARSE_INT_3(valp_) \
|
||||
do { \
|
||||
int res_ = 0; \
|
||||
PARSE_INT(&res_, 100) \
|
||||
*valp_ = res_; \
|
||||
PARSE_INT(&res_, 10) \
|
||||
*valp_ += res_; \
|
||||
PARSE_INT(&res_, 1) \
|
||||
*valp_ += res_; \
|
||||
} while (0)
|
||||
|
||||
/* returned pointer is always within [buf, buf_end), or null */
|
||||
static const char *parse_token(const char *buf, const char *buf_end, const char **token, size_t *token_len, char next_char,
|
||||
int *ret)
|
||||
{
|
||||
/* We use pcmpestri to detect non-token characters. This instruction can take no more than eight character ranges (8*2*8=128
|
||||
* bits that is the size of a SSE register). Due to this restriction, characters `|` and `~` are handled in the slow loop. */
|
||||
static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up to SP */
|
||||
"\"\"" /* 0x22 */
|
||||
"()" /* 0x28,0x29 */
|
||||
",," /* 0x2c */
|
||||
"//" /* 0x2f */
|
||||
":@" /* 0x3a-0x40 */
|
||||
"[]" /* 0x5b-0x5d */
|
||||
"{\xff"; /* 0x7b-0xff */
|
||||
const char *buf_start = buf;
|
||||
int found;
|
||||
buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found);
|
||||
if (!found) {
|
||||
CHECK_EOF();
|
||||
}
|
||||
while (1) {
|
||||
if (*buf == next_char) {
|
||||
break;
|
||||
} else if (!token_char_map[(unsigned char)*buf]) {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
++buf;
|
||||
CHECK_EOF();
|
||||
}
|
||||
*token = buf_start;
|
||||
*token_len = buf - buf_start;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* returned pointer is always within [buf, buf_end), or null */
|
||||
static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret)
|
||||
{
|
||||
/* we want at least [HTTP/1.<two chars>] to try to parse */
|
||||
if (buf_end - buf < 9) {
|
||||
*ret = -2;
|
||||
return NULL;
|
||||
}
|
||||
EXPECT_CHAR_NO_CHECK('H');
|
||||
EXPECT_CHAR_NO_CHECK('T');
|
||||
EXPECT_CHAR_NO_CHECK('T');
|
||||
EXPECT_CHAR_NO_CHECK('P');
|
||||
EXPECT_CHAR_NO_CHECK('/');
|
||||
EXPECT_CHAR_NO_CHECK('1');
|
||||
EXPECT_CHAR_NO_CHECK('.');
|
||||
PARSE_INT(minor_version, 1);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers,
|
||||
size_t max_headers, int *ret)
|
||||
{
|
||||
for (;; ++*num_headers) {
|
||||
CHECK_EOF();
|
||||
if (*buf == '\015') {
|
||||
++buf;
|
||||
EXPECT_CHAR('\012');
|
||||
break;
|
||||
} else if (*buf == '\012') {
|
||||
++buf;
|
||||
break;
|
||||
}
|
||||
if (*num_headers == max_headers) {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) {
|
||||
/* parsing name, but do not discard SP before colon, see
|
||||
* http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */
|
||||
if ((buf = parse_token(buf, buf_end, &headers[*num_headers].name, &headers[*num_headers].name_len, ':', ret)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (headers[*num_headers].name_len == 0) {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
++buf;
|
||||
for (;; ++buf) {
|
||||
CHECK_EOF();
|
||||
if (!(*buf == ' ' || *buf == '\t')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
headers[*num_headers].name = NULL;
|
||||
headers[*num_headers].name_len = 0;
|
||||
}
|
||||
const char *value;
|
||||
size_t value_len;
|
||||
if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, ret)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
/* remove trailing SPs and HTABs */
|
||||
const char *value_end = value + value_len;
|
||||
for (; value_end != value; --value_end) {
|
||||
const char c = *(value_end - 1);
|
||||
if (!(c == ' ' || c == '\t')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
headers[*num_headers].value = value;
|
||||
headers[*num_headers].value_len = value_end - value;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path,
|
||||
size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers,
|
||||
size_t max_headers, int *ret)
|
||||
{
|
||||
/* skip first empty line (some clients add CRLF after POST content) */
|
||||
CHECK_EOF();
|
||||
if (*buf == '\015') {
|
||||
++buf;
|
||||
EXPECT_CHAR('\012');
|
||||
} else if (*buf == '\012') {
|
||||
++buf;
|
||||
}
|
||||
|
||||
/* parse request line */
|
||||
if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
do {
|
||||
++buf;
|
||||
CHECK_EOF();
|
||||
} while (*buf == ' ');
|
||||
ADVANCE_TOKEN(*path, *path_len);
|
||||
do {
|
||||
++buf;
|
||||
CHECK_EOF();
|
||||
} while (*buf == ' ');
|
||||
if (*method_len == 0 || *path_len == 0) {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (*buf == '\015') {
|
||||
++buf;
|
||||
EXPECT_CHAR('\012');
|
||||
} else if (*buf == '\012') {
|
||||
++buf;
|
||||
} else {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
|
||||
}
|
||||
|
||||
int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path,
|
||||
size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len)
|
||||
{
|
||||
const char *buf = buf_start, *buf_end = buf_start + len;
|
||||
size_t max_headers = *num_headers;
|
||||
int r;
|
||||
|
||||
*method = NULL;
|
||||
*method_len = 0;
|
||||
*path = NULL;
|
||||
*path_len = 0;
|
||||
*minor_version = -1;
|
||||
*num_headers = 0;
|
||||
|
||||
/* if last_len != 0, check if the request is complete (a fast countermeasure
|
||||
againt slowloris */
|
||||
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers,
|
||||
&r)) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return (int)(buf - buf_start);
|
||||
}
|
||||
|
||||
static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg,
|
||||
size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret)
|
||||
{
|
||||
/* parse "HTTP/1.x" */
|
||||
if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
/* skip space */
|
||||
if (*buf != ' ') {
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
do {
|
||||
++buf;
|
||||
CHECK_EOF();
|
||||
} while (*buf == ' ');
|
||||
/* parse status code, we want at least [:digit:][:digit:][:digit:]<other char> to try to parse */
|
||||
if (buf_end - buf < 4) {
|
||||
*ret = -2;
|
||||
return NULL;
|
||||
}
|
||||
PARSE_INT_3(status);
|
||||
|
||||
/* get message including preceding space */
|
||||
if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (*msg_len == 0) {
|
||||
/* ok */
|
||||
} else if (**msg == ' ') {
|
||||
/* Remove preceding space. Successful return from `get_token_to_eol` guarantees that we would hit something other than SP
|
||||
* before running past the end of the given buffer. */
|
||||
do {
|
||||
++*msg;
|
||||
--*msg_len;
|
||||
} while (**msg == ' ');
|
||||
} else {
|
||||
/* garbage found after status code */
|
||||
*ret = -1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
|
||||
}
|
||||
|
||||
int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
|
||||
struct phr_header *headers, size_t *num_headers, size_t last_len)
|
||||
{
|
||||
const char *buf = buf_start, *buf_end = buf + len;
|
||||
size_t max_headers = *num_headers;
|
||||
int r;
|
||||
|
||||
*minor_version = -1;
|
||||
*status = 0;
|
||||
*msg = NULL;
|
||||
*msg_len = 0;
|
||||
*num_headers = 0;
|
||||
|
||||
/* if last_len != 0, check if the response is complete (a fast countermeasure
|
||||
against slowloris */
|
||||
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return (int)(buf - buf_start);
|
||||
}
|
||||
|
||||
int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len)
|
||||
{
|
||||
const char *buf = buf_start, *buf_end = buf + len;
|
||||
size_t max_headers = *num_headers;
|
||||
int r;
|
||||
|
||||
*num_headers = 0;
|
||||
|
||||
/* if last_len != 0, check if the response is complete (a fast countermeasure
|
||||
against slowloris */
|
||||
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return (int)(buf - buf_start);
|
||||
}
|
||||
|
||||
enum {
|
||||
CHUNKED_IN_CHUNK_SIZE,
|
||||
CHUNKED_IN_CHUNK_EXT,
|
||||
CHUNKED_IN_CHUNK_DATA,
|
||||
CHUNKED_IN_CHUNK_CRLF,
|
||||
CHUNKED_IN_TRAILERS_LINE_HEAD,
|
||||
CHUNKED_IN_TRAILERS_LINE_MIDDLE
|
||||
};
|
||||
|
||||
static int decode_hex(int ch)
|
||||
{
|
||||
if ('0' <= ch && ch <= '9') {
|
||||
return ch - '0';
|
||||
} else if ('A' <= ch && ch <= 'F') {
|
||||
return ch - 'A' + 0xa;
|
||||
} else if ('a' <= ch && ch <= 'f') {
|
||||
return ch - 'a' + 0xa;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz)
|
||||
{
|
||||
size_t dst = 0, src = 0, bufsz = *_bufsz;
|
||||
ssize_t ret = -2; /* incomplete */
|
||||
|
||||
decoder->_total_read += bufsz;
|
||||
|
||||
while (1) {
|
||||
switch (decoder->_state) {
|
||||
case CHUNKED_IN_CHUNK_SIZE:
|
||||
for (;; ++src) {
|
||||
int v;
|
||||
if (src == bufsz)
|
||||
goto Exit;
|
||||
if ((v = decode_hex(buf[src])) == -1) {
|
||||
if (decoder->_hex_count == 0) {
|
||||
ret = -1;
|
||||
goto Exit;
|
||||
}
|
||||
/* the only characters that may appear after the chunk size are BWS, semicolon, or CRLF */
|
||||
switch (buf[src]) {
|
||||
case ' ':
|
||||
case '\011':
|
||||
case ';':
|
||||
case '\012':
|
||||
case '\015':
|
||||
break;
|
||||
default:
|
||||
ret = -1;
|
||||
goto Exit;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (decoder->_hex_count == sizeof(size_t) * 2) {
|
||||
ret = -1;
|
||||
goto Exit;
|
||||
}
|
||||
decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v;
|
||||
++decoder->_hex_count;
|
||||
}
|
||||
decoder->_hex_count = 0;
|
||||
decoder->_state = CHUNKED_IN_CHUNK_EXT;
|
||||
/* fallthru */
|
||||
case CHUNKED_IN_CHUNK_EXT:
|
||||
/* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */
|
||||
for (;; ++src) {
|
||||
if (src == bufsz)
|
||||
goto Exit;
|
||||
if (buf[src] == '\012')
|
||||
break;
|
||||
}
|
||||
++src;
|
||||
if (decoder->bytes_left_in_chunk == 0) {
|
||||
if (decoder->consume_trailer) {
|
||||
decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
|
||||
break;
|
||||
} else {
|
||||
goto Complete;
|
||||
}
|
||||
}
|
||||
decoder->_state = CHUNKED_IN_CHUNK_DATA;
|
||||
/* fallthru */
|
||||
case CHUNKED_IN_CHUNK_DATA: {
|
||||
size_t avail = bufsz - src;
|
||||
if (avail < decoder->bytes_left_in_chunk) {
|
||||
if (dst != src)
|
||||
memmove(buf + dst, buf + src, avail);
|
||||
src += avail;
|
||||
dst += avail;
|
||||
decoder->bytes_left_in_chunk -= avail;
|
||||
goto Exit;
|
||||
}
|
||||
if (dst != src)
|
||||
memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk);
|
||||
src += decoder->bytes_left_in_chunk;
|
||||
dst += decoder->bytes_left_in_chunk;
|
||||
decoder->bytes_left_in_chunk = 0;
|
||||
decoder->_state = CHUNKED_IN_CHUNK_CRLF;
|
||||
}
|
||||
/* fallthru */
|
||||
case CHUNKED_IN_CHUNK_CRLF:
|
||||
for (;; ++src) {
|
||||
if (src == bufsz)
|
||||
goto Exit;
|
||||
if (buf[src] != '\015')
|
||||
break;
|
||||
}
|
||||
if (buf[src] != '\012') {
|
||||
ret = -1;
|
||||
goto Exit;
|
||||
}
|
||||
++src;
|
||||
decoder->_state = CHUNKED_IN_CHUNK_SIZE;
|
||||
break;
|
||||
case CHUNKED_IN_TRAILERS_LINE_HEAD:
|
||||
for (;; ++src) {
|
||||
if (src == bufsz)
|
||||
goto Exit;
|
||||
if (buf[src] != '\015')
|
||||
break;
|
||||
}
|
||||
if (buf[src++] == '\012')
|
||||
goto Complete;
|
||||
decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE;
|
||||
/* fallthru */
|
||||
case CHUNKED_IN_TRAILERS_LINE_MIDDLE:
|
||||
for (;; ++src) {
|
||||
if (src == bufsz)
|
||||
goto Exit;
|
||||
if (buf[src] == '\012')
|
||||
break;
|
||||
}
|
||||
++src;
|
||||
decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
|
||||
break;
|
||||
default:
|
||||
assert(!"decoder is corrupt");
|
||||
}
|
||||
}
|
||||
|
||||
Complete:
|
||||
ret = bufsz - src;
|
||||
Exit:
|
||||
if (dst != src)
|
||||
memmove(buf + dst, buf + src, bufsz - src);
|
||||
*_bufsz = dst;
|
||||
/* if incomplete but the overhead of the chunked encoding is >=100KB and >80%, signal an error */
|
||||
if (ret == -2) {
|
||||
decoder->_total_overhead += bufsz - dst;
|
||||
if (decoder->_total_overhead >= 100 * 1024 && decoder->_total_read - decoder->_total_overhead < decoder->_total_read / 4)
|
||||
ret = -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder)
|
||||
{
|
||||
return decoder->_state == CHUNKED_IN_CHUNK_DATA;
|
||||
}
|
||||
|
||||
#undef CHECK_EOF
|
||||
#undef EXPECT_CHAR
|
||||
#undef ADVANCE_TOKEN
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
|
||||
* Shigeo Mitsunari
|
||||
*
|
||||
* The software is licensed under either the MIT License (below) or the Perl
|
||||
* license.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef picohttpparser_h
|
||||
#define picohttpparser_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define ssize_t intptr_t
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* contains name and value of a header (name == NULL if is a continuing line
|
||||
* of a multiline header */
|
||||
struct phr_header {
|
||||
const char *name;
|
||||
size_t name_len;
|
||||
const char *value;
|
||||
size_t value_len;
|
||||
};
|
||||
|
||||
/* returns number of bytes consumed if successful, -2 if request is partial,
|
||||
* -1 if failed */
|
||||
int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len,
|
||||
int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||
|
||||
/* ditto */
|
||||
int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
|
||||
struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||
|
||||
/* ditto */
|
||||
int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||
|
||||
/* should be zero-filled before start */
|
||||
struct phr_chunked_decoder {
|
||||
size_t bytes_left_in_chunk; /* number of bytes left in current chunk */
|
||||
char consume_trailer; /* if trailing headers should be consumed */
|
||||
char _hex_count;
|
||||
char _state;
|
||||
uint64_t _total_read;
|
||||
uint64_t _total_overhead;
|
||||
};
|
||||
|
||||
/* the function rewrites the buffer given as (buf, bufsz) removing the chunked-
|
||||
* encoding headers. When the function returns without an error, bufsz is
|
||||
* updated to the length of the decoded data available. Applications should
|
||||
* repeatedly call the function while it returns -2 (incomplete) every time
|
||||
* supplying newly arrived data. If the end of the chunked-encoded data is
|
||||
* found, the function returns a non-negative number indicating the number of
|
||||
* octets left undecoded, that starts from the offset returned by `*bufsz`.
|
||||
* Returns -1 on error.
|
||||
*/
|
||||
ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz);
|
||||
|
||||
/* returns if the chunked decoder is in middle of chunked data */
|
||||
int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+10
-426
@@ -1,438 +1,22 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "../http.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "picohttpparser.h"
|
||||
#define TEST(X) {if (!(X)) __builtin_trap(); }
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// TEST CASES
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define BASIC_REQUEST_STRING \
|
||||
"GET / HTTP/1.1\r\n" \
|
||||
"Host: 127.0.0.1:8080\r\n" \
|
||||
"User-Agent: curl/7.81.0\r\n" \
|
||||
"Accept: */*\r\n" \
|
||||
"\r\n"
|
||||
|
||||
static void test_init(void)
|
||||
{
|
||||
TinyHTTPStream stream;
|
||||
|
||||
TEST_START
|
||||
tinyhttp_stream_init(&stream, memfunc, NULL);
|
||||
|
||||
int state = tinyhttp_stream_state(&stream);
|
||||
|
||||
// These flags must be set on init
|
||||
TEST(state & TINYHTTP_STREAM_RECV);
|
||||
|
||||
// These must be unset
|
||||
TEST(!(state & TINYHTTP_STREAM_DIED));
|
||||
TEST(!(state & TINYHTTP_STREAM_READY));
|
||||
TEST(!(state & TINYHTTP_STREAM_REUSE));
|
||||
TEST(!(state & TINYHTTP_STREAM_RECV_STARTED));
|
||||
TEST(!(state & TINYHTTP_STREAM_SEND_STARTED));
|
||||
|
||||
tinyhttp_stream_free(&stream);
|
||||
TEST_END
|
||||
}
|
||||
|
||||
static void test_kill(void)
|
||||
{
|
||||
TinyHTTPStream stream;
|
||||
|
||||
TEST_START
|
||||
tinyhttp_stream_init(&stream, memfunc, NULL);
|
||||
|
||||
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_DIED));
|
||||
|
||||
tinyhttp_stream_kill(&stream);
|
||||
|
||||
TEST(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_DIED);
|
||||
|
||||
tinyhttp_stream_free(&stream);
|
||||
TEST_END
|
||||
}
|
||||
|
||||
static void
|
||||
test_recv_started_flag(void)
|
||||
{
|
||||
ptrdiff_t cap;
|
||||
TinyHTTPStream stream;
|
||||
|
||||
TEST_START
|
||||
tinyhttp_stream_init(&stream, memfunc, NULL);
|
||||
|
||||
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_RECV_STARTED));
|
||||
|
||||
tinyhttp_stream_recv_buf(&stream, &cap);
|
||||
|
||||
TEST(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_RECV_STARTED);
|
||||
|
||||
tinyhttp_stream_recv_ack(&stream, 0);
|
||||
|
||||
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_RECV_STARTED));
|
||||
|
||||
tinyhttp_stream_free(&stream);
|
||||
TEST_END
|
||||
}
|
||||
|
||||
static void
|
||||
test_send_started_flag(void)
|
||||
{
|
||||
ptrdiff_t cap;
|
||||
TinyHTTPStream stream;
|
||||
|
||||
TEST_START
|
||||
tinyhttp_stream_init(&stream, memfunc, NULL);
|
||||
|
||||
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_SEND_STARTED));
|
||||
|
||||
tinyhttp_stream_send_buf(&stream, &cap);
|
||||
|
||||
TEST(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_SEND_STARTED);
|
||||
|
||||
tinyhttp_stream_send_ack(&stream, 0);
|
||||
|
||||
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_SEND_STARTED));
|
||||
|
||||
tinyhttp_stream_free(&stream);
|
||||
TEST_END
|
||||
}
|
||||
|
||||
static void test_basic_exchange()
|
||||
{
|
||||
TinyHTTPStream stream;
|
||||
|
||||
TEST_START
|
||||
tinyhttp_stream_init(&stream, memfunc, NULL);
|
||||
|
||||
// Send request
|
||||
send_request(&stream, BASIC_REQUEST_STRING);
|
||||
|
||||
// Build response
|
||||
tinyhttp_stream_response_status(&stream, 200);
|
||||
tinyhttp_stream_response_send(&stream);
|
||||
|
||||
// Receive response
|
||||
char buf[1<<12];
|
||||
Response res;
|
||||
recv_response(&stream, &res, buf, sizeof(buf));
|
||||
|
||||
// We expect the status line:
|
||||
// HTTP/1.1 200 OK
|
||||
TEST(res.minor == 1);
|
||||
TEST(res.status_code == 200);
|
||||
TEST(tinyhttp_streq(res.status_text, TINYHTTP_STRING("OK")));
|
||||
|
||||
tinyhttp_stream_free(&stream);
|
||||
TEST_END
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// ENTRY POINT
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
void test_branch_coverage(void);
|
||||
|
||||
int main(void)
|
||||
{
|
||||
test_init();
|
||||
test_kill();
|
||||
test_recv_started_flag();
|
||||
test_send_started_flag();
|
||||
test_basic_exchange();
|
||||
test_reuse();
|
||||
test_chunking();
|
||||
test_parse_request();
|
||||
printf("OK\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// Helper Functions
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void *memfunc(TinyHTTPMemoryFuncTag tag, void *ptr, int len, void *data)
|
||||
{
|
||||
(void) data;
|
||||
switch (tag) {
|
||||
|
||||
case TINYHTTP_MEM_MALLOC:
|
||||
return malloc(len);
|
||||
|
||||
case TINYHTTP_MEM_FREE:
|
||||
free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
buffer_into_stream(TinyHTTPStream *stream, const char *src, int len)
|
||||
{
|
||||
int state = tinyhttp_stream_state(stream);
|
||||
|
||||
ptrdiff_t copied = 0;
|
||||
while (copied < len && (state & TINYHTTP_STREAM_RECV)) {
|
||||
|
||||
char *dst;
|
||||
ptrdiff_t cap;
|
||||
ptrdiff_t cpy;
|
||||
|
||||
dst = tinyhttp_stream_recv_buf(stream, &cap);
|
||||
|
||||
cpy = len - copied;
|
||||
if (cpy > cap) cpy = cap;
|
||||
|
||||
memcpy(dst, src + copied, cpy);
|
||||
|
||||
tinyhttp_printbytes(" >> ", src + copied, cpy);
|
||||
|
||||
copied += cpy;
|
||||
tinyhttp_stream_recv_ack(stream, cpy);
|
||||
state = tinyhttp_stream_state(stream);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
return copied;
|
||||
}
|
||||
|
||||
static int
|
||||
stream_into_buffer(TinyHTTPStream *stream, char *dst, int cap)
|
||||
{
|
||||
int state = tinyhttp_stream_state(stream);
|
||||
|
||||
int copied = 0;
|
||||
while (copied < cap && (state & TINYHTTP_STREAM_SEND)) {
|
||||
|
||||
char *src;
|
||||
ptrdiff_t len;
|
||||
ptrdiff_t cpy;
|
||||
|
||||
src = tinyhttp_stream_send_buf(stream, &len);
|
||||
|
||||
cpy = cap - copied;
|
||||
if (cpy > len) cpy = len;
|
||||
|
||||
memcpy(dst + copied, src, cpy);
|
||||
|
||||
tinyhttp_printbytes(" << ", src, cpy);
|
||||
|
||||
copied += cpy;
|
||||
tinyhttp_stream_send_ack(stream, cpy);
|
||||
state = tinyhttp_stream_state(stream);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
return copied;
|
||||
}
|
||||
|
||||
int match_request(TinyHTTPRequest *r1, TinyHTTPRequest *r2)
|
||||
{
|
||||
if (r1->method != r2->method)
|
||||
return 0;
|
||||
|
||||
if (r1->minor != r2->minor)
|
||||
return 0;
|
||||
|
||||
if (!tinyhttp_streq(r1->path, r2->path))
|
||||
return 0;
|
||||
|
||||
if (r1->num_headers != r2->num_headers)
|
||||
return 0;
|
||||
|
||||
for (int i = 0; i < r2->num_headers; i++) {
|
||||
if (!tinyhttp_streq(r1->headers[i].name, r2->headers[i].name))
|
||||
return 0;
|
||||
if (!tinyhttp_streq(r1->headers[i].value, r2->headers[i].value))
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (r1->body_len != r2->body_len)
|
||||
return 0;
|
||||
|
||||
if (r2->body_len == 0) {
|
||||
if (r1->body != NULL)
|
||||
return 0;
|
||||
} else {
|
||||
if (memcmp(r1->body, r2->body, r2->body_len))
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
expect_request(TinyHTTPStream *stream, TinyHTTPRequest expreq)
|
||||
{
|
||||
int state = tinyhttp_stream_state(stream);
|
||||
TEST(state & TINYHTTP_STREAM_READY);
|
||||
|
||||
TinyHTTPRequest *req = tinyhttp_stream_request(stream);
|
||||
TEST(req);
|
||||
TEST(match_request(req, &expreq));
|
||||
}
|
||||
|
||||
int parse_request(TinyHTTPString txt, TinyHTTPRequest *req, char *buf, int max)
|
||||
{
|
||||
const char *method;
|
||||
size_t method_len;
|
||||
|
||||
const char *path;
|
||||
size_t path_len;
|
||||
|
||||
int minor;
|
||||
|
||||
struct phr_header headers[TINYHTTP_HEADER_LIMIT];
|
||||
size_t num_headers = TINYHTTP_HEADER_LIMIT;
|
||||
|
||||
int ret = phr_parse_request(
|
||||
txt.ptr, txt.len,
|
||||
&method, &method_len,
|
||||
&path, &path_len,
|
||||
&minor,
|
||||
headers, &num_headers,
|
||||
0);
|
||||
ptrdiff_t head_len = ret;
|
||||
|
||||
if (method_len == 3 && !memcmp("GET", method, 3)) {
|
||||
req->method = TINYHTTP_METHOD_GET;
|
||||
} else if (method_len == 4 && !memcmp("POST", method, 4)) {
|
||||
req->method = TINYHTTP_METHOD_POST;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
req->minor = minor;
|
||||
req->path = (TinyHTTPString) { path, path_len };
|
||||
req->num_headers = num_headers;
|
||||
|
||||
for (int i = 0; i < (int) num_headers; i++) {
|
||||
req->headers[i].name = (TinyHTTPString) {
|
||||
headers[i].name,
|
||||
headers[i].name_len,
|
||||
};
|
||||
req->headers[i].value = (TinyHTTPString) {
|
||||
headers[i].value,
|
||||
headers[i].value_len,
|
||||
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);
|
||||
}
|
||||
|
||||
int transfer_encoding_index = tinyhttp_findheader(req, TINYHTTP_STRING("Transfer-Encoding"));
|
||||
if (transfer_encoding_index != -1) {
|
||||
// TODO: For now, we consider request as chunked just for having the Transfer-Encoding header
|
||||
|
||||
if (txt.len - head_len > max)
|
||||
return -1;
|
||||
memcpy(buf, txt.ptr + head_len, txt.len - head_len);
|
||||
|
||||
struct phr_chunked_decoder decoder;
|
||||
memset(&decoder, 0, sizeof(decoder));
|
||||
decoder.consume_trailer = 1; // Process any trailing headers
|
||||
|
||||
// Decode the chunked body
|
||||
size_t decoded_size = txt.len - head_len;
|
||||
ssize_t ret = phr_decode_chunked(&decoder, buf, &decoded_size);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
req->body = buf;
|
||||
req->body_len = decoded_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int content_length_index = tinyhttp_findheader(req, TINYHTTP_STRING("Content-Length"));
|
||||
if (content_length_index != -1) {
|
||||
|
||||
__builtin_trap(); // TODO
|
||||
test_branch_coverage();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
req->body = NULL;
|
||||
req->body_len = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
parse_response(TinyHTTPString txt, Response *res)
|
||||
{
|
||||
int minor;
|
||||
|
||||
int status_code;
|
||||
const char *status_text;
|
||||
size_t status_text_len;
|
||||
|
||||
struct phr_header headers[TINYHTTP_HEADER_LIMIT];
|
||||
size_t num_headers = TINYHTTP_HEADER_LIMIT;
|
||||
|
||||
int ret = phr_parse_response(
|
||||
txt.ptr, txt.len,
|
||||
&minor,
|
||||
&status_code, &status_text, &status_text_len,
|
||||
headers, &num_headers,
|
||||
0);
|
||||
TEST(ret == txt.len);
|
||||
|
||||
res->minor = minor;
|
||||
res->status_code = status_code;
|
||||
res->status_text = (TinyHTTPString) { status_text, status_text_len };
|
||||
|
||||
res->num_headers = num_headers;
|
||||
for (int i = 0; i < (int) num_headers; i++) {
|
||||
res->headers[i].name = (TinyHTTPString) {
|
||||
headers[i].name,
|
||||
headers[i].name_len
|
||||
};
|
||||
res->headers[i].value = (TinyHTTPString) {
|
||||
headers[i].value,
|
||||
headers[i].value_len
|
||||
};
|
||||
}
|
||||
|
||||
res->body = NULL; // TODO
|
||||
res->body_len = 0; // TODO
|
||||
}
|
||||
|
||||
void send_request(TinyHTTPStream *stream, const char *str)
|
||||
{
|
||||
int received = buffer_into_stream(stream, str, strlen(str));
|
||||
TEST(received == (int) strlen(str));
|
||||
|
||||
TinyHTTPRequest req;
|
||||
char buf[1<<12];
|
||||
TEST(parse_request((TinyHTTPString) {str, strlen(str)}, &req, buf, sizeof(buf)) == 0);
|
||||
|
||||
expect_request(stream, req);
|
||||
}
|
||||
|
||||
void recv_response(TinyHTTPStream *stream, Response *res, char *dst, int cap)
|
||||
{
|
||||
int len = stream_into_buffer(stream, dst, cap);
|
||||
|
||||
int state = tinyhttp_stream_state(stream);
|
||||
TEST((state & TINYHTTP_STREAM_SEND) == 0);
|
||||
|
||||
parse_response((TinyHTTPString) { dst, len }, res);
|
||||
}
|
||||
|
||||
int header_exists(Response *res, TinyHTTPString name)
|
||||
{
|
||||
for (int i = 0; i < res->num_headers; i++)
|
||||
if (tinyhttp_streqcase(res->headers[i].name, name))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int header_exists_with_value(Response *res, TinyHTTPString name, TinyHTTPString value)
|
||||
{
|
||||
for (int i = 0; i < res->num_headers; i++)
|
||||
if (tinyhttp_streqcase(res->headers[i].name, name))
|
||||
return tinyhttp_streqcase(res->headers[i].value, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// THE END
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -1,47 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include "../tinyhttp.h"
|
||||
|
||||
typedef struct {
|
||||
|
||||
int minor;
|
||||
|
||||
int status_code;
|
||||
TinyHTTPString status_text;
|
||||
|
||||
int num_headers;
|
||||
TinyHTTPHeader headers[TINYHTTP_HEADER_LIMIT];
|
||||
|
||||
char *body;
|
||||
int body_len;
|
||||
} Response;
|
||||
|
||||
// Memory function used to initialize TinyHTTPStream
|
||||
// TODO: Maybe choose a better name for this
|
||||
void *memfunc(TinyHTTPMemoryFuncTag tag, void *ptr, int len, void *data);
|
||||
|
||||
// Moves the request "str" into the stream, checks that the stream
|
||||
// became ready and that it parsed the request correctly. When this
|
||||
// functions returns the stream is ready for a response.
|
||||
void send_request(TinyHTTPStream *stream, const char *str);
|
||||
|
||||
// Copies into the "dst" buffer the output bytes from the stream
|
||||
// (up to "cap" bytes) and parses them as an HTTP response into
|
||||
// "res".
|
||||
void recv_response(TinyHTTPStream *stream, Response *res, char *dst, int cap);
|
||||
|
||||
int parse_request(TinyHTTPString txt, TinyHTTPRequest *req, char *buf, int max);
|
||||
|
||||
int match_request(TinyHTTPRequest *r1, TinyHTTPRequest *r2);
|
||||
|
||||
int header_exists(Response *res, TinyHTTPString name);
|
||||
int header_exists_with_value(Response *res, TinyHTTPString name, TinyHTTPString value);
|
||||
|
||||
#define TEST(X) {if (!(X)) { printf("Test failed at %s:%d\n", __FILE__, __LINE__); fflush(stdout); __builtin_trap(); }}
|
||||
|
||||
#define TEST_START printf("Test %s:%d\n", __FILE__, __LINE__);
|
||||
#define TEST_START2(file, line) printf("Test %s:%d\n", (file), (line));
|
||||
#define TEST_END
|
||||
|
||||
void test_reuse(void);
|
||||
void test_chunking(void);
|
||||
void test_parse_request(void);
|
||||
@@ -0,0 +1,518 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../http.h"
|
||||
|
||||
#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(); }}
|
||||
|
||||
#define TEST_EQ(X, Y) _Generic((X), HTTP_String: testeq_str, int: testeq_int)((X), (Y), S(#X), S(#Y), __FILE__, __LINE__)
|
||||
|
||||
static 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",
|
||||
(int) uneval_l.len, uneval_l.ptr,
|
||||
(int) uneval_r.len, uneval_r.ptr,
|
||||
l, r);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static 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",
|
||||
(int) uneval_l.len, uneval_l.ptr,
|
||||
(int) uneval_r.len, uneval_r.ptr,
|
||||
(int) l.len, l.ptr,
|
||||
(int) r.len, r.ptr);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
#define S HTTP_STR
|
||||
|
||||
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(void)
|
||||
{
|
||||
test_branch_coverage_parse_request();
|
||||
test_branch_coverage_parse_response();
|
||||
test_branch_coverage_parse_url();
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#include "test.h"
|
||||
|
||||
void test_chunking(void)
|
||||
{
|
||||
{
|
||||
TinyHTTPStream stream;
|
||||
|
||||
TEST_START
|
||||
tinyhttp_stream_init(&stream, memfunc, NULL);
|
||||
|
||||
// Send request
|
||||
send_request(&stream,
|
||||
"POST / HTTP/1.1\r\n"
|
||||
"Host: 127.0.0.1:8080\r\n"
|
||||
"User-Agent: curl/7.81.0\r\n"
|
||||
"Accept: */*\r\n"
|
||||
"Transfer-Encoding: Chunked\r\n"
|
||||
"Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
"\r\n"
|
||||
"d\r\n"
|
||||
"Hello, world!\r\n"
|
||||
"0\r\n"
|
||||
"\r\n"
|
||||
);
|
||||
|
||||
// Build response
|
||||
tinyhttp_stream_response_status(&stream, 200);
|
||||
tinyhttp_stream_response_send(&stream);
|
||||
|
||||
// Receive response
|
||||
char buf[1<<12];
|
||||
Response res;
|
||||
recv_response(&stream, &res, buf, sizeof(buf));
|
||||
|
||||
// We expect the status line:
|
||||
// HTTP/1.1 200 OK
|
||||
TEST(res.minor == 1);
|
||||
TEST(res.status_code == 200);
|
||||
TEST(tinyhttp_streq(res.status_text, TINYHTTP_STRING("OK")));
|
||||
|
||||
tinyhttp_stream_free(&stream);
|
||||
TEST_END
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
#include <string.h>
|
||||
#include "test.h"
|
||||
|
||||
static void test_helper(char *src, int len, int expret, TinyHTTPRequest *expreq)
|
||||
{
|
||||
if (len < 0) len = strlen(src);
|
||||
TinyHTTPRequest req;
|
||||
int ret = tinyhttp_parserequest(src, len, -1ULL, &req);
|
||||
TEST(ret == expret);
|
||||
if (expret > 0) {
|
||||
TEST(match_request(&req, expreq));
|
||||
}
|
||||
}
|
||||
|
||||
void test_parse_request(void)
|
||||
{
|
||||
{
|
||||
char src[] =
|
||||
"GET / HTTP/1.1\r\n"
|
||||
"Host: 127.0.0.1:8080\r\n"
|
||||
"Connection: Keep-Alive\r\n"
|
||||
"\r\n";
|
||||
TinyHTTPRequest req;
|
||||
req.method = TINYHTTP_METHOD_GET;
|
||||
req.minor = 1;
|
||||
req.path = TINYHTTP_STRING("/");
|
||||
req.num_headers = 2;
|
||||
req.headers[0].name = TINYHTTP_STRING("Host");
|
||||
req.headers[0].value = TINYHTTP_STRING("127.0.0.1:8080");
|
||||
req.headers[1].name = TINYHTTP_STRING("Connection");
|
||||
req.headers[1].value = TINYHTTP_STRING("Keep-Alive");
|
||||
req.body = NULL;
|
||||
req.body_len = 0;
|
||||
|
||||
for (int i = 0; i < strlen(src)-1; i++)
|
||||
test_helper(src, i, 0, &req);
|
||||
|
||||
test_helper(src, strlen(src), strlen(src), &req);
|
||||
}
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
#include "test.h"
|
||||
|
||||
// This file tests the behavior of the "Connection" header
|
||||
|
||||
// HTTP/1.1, No "Connection" header
|
||||
#define REQUEST_HTTP1_1_NO_CONNECTION_HEADER \
|
||||
"GET / HTTP/1.1\r\n" \
|
||||
"Host: 127.0.0.1:8080\r\n" \
|
||||
"\r\n"
|
||||
|
||||
// HTTP/1.1, "Connection" header with invalid value
|
||||
#define REQUEST_HTTP1_1_CONNECTION_INVALID \
|
||||
"GET / HTTP/1.1\r\n" \
|
||||
"Host: 127.0.0.1:8080\r\n" \
|
||||
"Connection: zzz\r\n" \
|
||||
"\r\n"
|
||||
|
||||
// HTTP/1.1, "Connection: Keep-Alive" header
|
||||
#define REQUEST_HTTP1_1_CONNECTION_KEEPALIVE \
|
||||
"GET / HTTP/1.1\r\n" \
|
||||
"Host: 127.0.0.1:8080\r\n" \
|
||||
"Connection: Keep-Alive\r\n" \
|
||||
"\r\n"
|
||||
|
||||
// HTTP/1.1, "Connection: Close" header
|
||||
#define REQUEST_HTTP1_1_CONNECTION_CLOSE \
|
||||
"GET / HTTP/1.1\r\n" \
|
||||
"Host: 127.0.0.1:8080\r\n" \
|
||||
"Connection: Close\r\n" \
|
||||
"\r\n"
|
||||
|
||||
// HTTP/1.0, No "Connection" header
|
||||
#define REQUEST_HTTP1_0_NO_CONNECTION_HEADER \
|
||||
"GET / HTTP/1.0\r\n" \
|
||||
"\r\n"
|
||||
|
||||
// HTTP/1.0, "Connection" header with invalid value
|
||||
#define REQUEST_HTTP1_0_CONNECTION_INVALID \
|
||||
"GET / HTTP/1.0\r\n" \
|
||||
"Connection: zzz\r\n" \
|
||||
"\r\n"
|
||||
|
||||
// HTTP/1.0, "Connection: Keep-Alive" header
|
||||
#define REQUEST_HTTP1_0_CONNECTION_KEEPALIVE \
|
||||
"GET / HTTP/1.0\r\n" \
|
||||
"Connection: Keep-Alive\r\n" \
|
||||
"\r\n"
|
||||
|
||||
// HTTP/1.0, "Connection: Close" header
|
||||
#define REQUEST_HTTP1_0_CONNECTION_CLOSE \
|
||||
"GET / HTTP/1.0\r\n" \
|
||||
"Connection: Close\r\n" \
|
||||
"\r\n"
|
||||
|
||||
static void test_setreuse(void)
|
||||
{
|
||||
TinyHTTPStream stream;
|
||||
|
||||
TEST_START
|
||||
tinyhttp_stream_init(&stream, memfunc, NULL);
|
||||
|
||||
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_REUSE));
|
||||
|
||||
tinyhttp_stream_setreuse(&stream, 1);
|
||||
|
||||
TEST(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_REUSE);
|
||||
|
||||
tinyhttp_stream_setreuse(&stream, 0);
|
||||
|
||||
TEST(!(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_REUSE));
|
||||
|
||||
tinyhttp_stream_setreuse(&stream, 5847295);
|
||||
|
||||
TEST(tinyhttp_stream_state(&stream) & TINYHTTP_STREAM_REUSE);
|
||||
|
||||
tinyhttp_stream_free(&stream);
|
||||
TEST_END
|
||||
}
|
||||
|
||||
|
||||
typedef enum {
|
||||
DONT_REUSE,
|
||||
ALLOW_REUSE,
|
||||
} ServerReuse;
|
||||
|
||||
typedef enum {
|
||||
INCONNHDR_NONE,
|
||||
INCONNHDR_KEEPALIVE,
|
||||
INCONNHDR_CLOSE,
|
||||
INCONNHDR_INVALID,
|
||||
} InputConnectionHeader;
|
||||
|
||||
typedef enum {
|
||||
OUTCONNHDR_MISSING_OR_KEEPALIVE,
|
||||
OUTCONNHDR_CLOSE,
|
||||
} OutputConnectionHeader;
|
||||
|
||||
static void test_reuse_helper(
|
||||
ServerReuse server_reuse,
|
||||
InputConnectionHeader input_conn_header,
|
||||
int input_http_minor_version,
|
||||
OutputConnectionHeader expect_output_conn_header,
|
||||
int expect_output_http_minor_version,
|
||||
const char *file, int line)
|
||||
{
|
||||
TinyHTTPStream stream;
|
||||
|
||||
TEST_START2(file, line)
|
||||
tinyhttp_stream_init(&stream, memfunc, NULL);
|
||||
tinyhttp_stream_setreuse(&stream, server_reuse);
|
||||
|
||||
if (input_http_minor_version == 1) {
|
||||
switch (input_conn_header) {
|
||||
case INCONNHDR_NONE : send_request(&stream, REQUEST_HTTP1_1_NO_CONNECTION_HEADER); break;
|
||||
case INCONNHDR_KEEPALIVE: send_request(&stream, REQUEST_HTTP1_1_CONNECTION_KEEPALIVE); break;
|
||||
case INCONNHDR_CLOSE : send_request(&stream, REQUEST_HTTP1_1_CONNECTION_CLOSE); break;
|
||||
case INCONNHDR_INVALID : send_request(&stream, REQUEST_HTTP1_1_CONNECTION_INVALID); break;
|
||||
}
|
||||
} else {
|
||||
switch (input_conn_header) {
|
||||
case INCONNHDR_NONE : send_request(&stream, REQUEST_HTTP1_0_NO_CONNECTION_HEADER); break;
|
||||
case INCONNHDR_KEEPALIVE: send_request(&stream, REQUEST_HTTP1_0_CONNECTION_KEEPALIVE); break;
|
||||
case INCONNHDR_CLOSE : send_request(&stream, REQUEST_HTTP1_0_CONNECTION_CLOSE); break;
|
||||
case INCONNHDR_INVALID : send_request(&stream, REQUEST_HTTP1_0_CONNECTION_INVALID); break;
|
||||
}
|
||||
}
|
||||
|
||||
// Build a dummy response
|
||||
tinyhttp_stream_response_status(&stream, 200);
|
||||
tinyhttp_stream_response_send(&stream);
|
||||
|
||||
Response res;
|
||||
char buf[1<<10];
|
||||
recv_response(&stream, &res, buf, sizeof(buf));
|
||||
|
||||
TEST(res.minor == expect_output_http_minor_version);
|
||||
|
||||
int state = tinyhttp_stream_state(&stream);
|
||||
switch (expect_output_conn_header) {
|
||||
|
||||
case OUTCONNHDR_MISSING_OR_KEEPALIVE:
|
||||
{
|
||||
TEST(!header_exists(&res, TINYHTTP_STRING("Connection"))
|
||||
|| header_exists_with_value(&res, TINYHTTP_STRING("Connection"), TINYHTTP_STRING("Keep-Alive")));
|
||||
TEST((state & TINYHTTP_STREAM_DIED) == 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case OUTCONNHDR_CLOSE:
|
||||
{
|
||||
TEST(header_exists_with_value(&res, TINYHTTP_STRING("Connection"), TINYHTTP_STRING("Close")));
|
||||
TEST(state & TINYHTTP_STREAM_DIED);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
tinyhttp_stream_free(&stream);
|
||||
TEST_END
|
||||
}
|
||||
|
||||
void test_reuse(void)
|
||||
{
|
||||
// Relevant specs:
|
||||
// RFC 9112, Section 9.3. (Persistence)
|
||||
// RFC 9112, Section 9.6. (Tear-down)
|
||||
// RFC 9110, Section 7.6.1. (Connection)
|
||||
|
||||
test_setreuse();
|
||||
test_reuse_helper(DONT_REUSE, INCONNHDR_NONE, 1, OUTCONNHDR_CLOSE, 1, __FILE__, __LINE__);
|
||||
test_reuse_helper(DONT_REUSE, INCONNHDR_KEEPALIVE, 1, OUTCONNHDR_CLOSE, 1, __FILE__, __LINE__);
|
||||
test_reuse_helper(DONT_REUSE, INCONNHDR_CLOSE, 1, OUTCONNHDR_CLOSE, 1, __FILE__, __LINE__);
|
||||
test_reuse_helper(DONT_REUSE, INCONNHDR_INVALID, 1, OUTCONNHDR_CLOSE, 1, __FILE__, __LINE__);
|
||||
test_reuse_helper(ALLOW_REUSE, INCONNHDR_NONE, 1, OUTCONNHDR_MISSING_OR_KEEPALIVE, 1, __FILE__, __LINE__);
|
||||
test_reuse_helper(ALLOW_REUSE, INCONNHDR_KEEPALIVE, 1, OUTCONNHDR_MISSING_OR_KEEPALIVE, 1, __FILE__, __LINE__);
|
||||
test_reuse_helper(ALLOW_REUSE, INCONNHDR_CLOSE, 1, OUTCONNHDR_CLOSE, 1, __FILE__, __LINE__);
|
||||
test_reuse_helper(ALLOW_REUSE, INCONNHDR_INVALID, 1, OUTCONNHDR_MISSING_OR_KEEPALIVE, 1, __FILE__, __LINE__);
|
||||
test_reuse_helper(DONT_REUSE, INCONNHDR_NONE, 0, OUTCONNHDR_CLOSE, 0, __FILE__, __LINE__);
|
||||
test_reuse_helper(DONT_REUSE, INCONNHDR_KEEPALIVE, 0, OUTCONNHDR_CLOSE, 0, __FILE__, __LINE__);
|
||||
test_reuse_helper(DONT_REUSE, INCONNHDR_CLOSE, 0, OUTCONNHDR_CLOSE, 0, __FILE__, __LINE__);
|
||||
test_reuse_helper(DONT_REUSE, INCONNHDR_INVALID, 0, OUTCONNHDR_CLOSE, 0, __FILE__, __LINE__);
|
||||
test_reuse_helper(ALLOW_REUSE, INCONNHDR_NONE, 0, OUTCONNHDR_CLOSE, 0, __FILE__, __LINE__);
|
||||
test_reuse_helper(ALLOW_REUSE, INCONNHDR_KEEPALIVE, 0, OUTCONNHDR_CLOSE, 0, __FILE__, __LINE__);
|
||||
test_reuse_helper(ALLOW_REUSE, INCONNHDR_CLOSE, 0, OUTCONNHDR_CLOSE, 0, __FILE__, __LINE__);
|
||||
test_reuse_helper(ALLOW_REUSE, INCONNHDR_INVALID, 0, OUTCONNHDR_CLOSE, 0, __FILE__, __LINE__);
|
||||
}
|
||||
-2605
File diff suppressed because it is too large
Load Diff
-348
@@ -1,348 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// 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.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifdef TINYHTTP_CUSTOMCONFIG
|
||||
#include "tinyhttp_config.h"
|
||||
#else
|
||||
#define TINYHTTP_SERVER_ENABLE 1
|
||||
#define TINYHTTP_HTTPS_ENABLE 0
|
||||
#define TINYHTTP_HEADER_LIMIT 32
|
||||
#define TINYHTTP_SERVER_CONN_LIMIT (1<<10)
|
||||
#define TINYHTTP_SERVER_EPOLL_BATCH_SIZE (1<<10)
|
||||
#define TINYHTTP_CHUNK_LIMIT (1<<10)
|
||||
#endif
|
||||
|
||||
#define TINYHTTP_LINESTR_HELPER1(X) #X
|
||||
#define TINYHTTP_LINESTR_HELPER2(X) TINYHTTP_LINESTR_HELPER1(X)
|
||||
#define TINYHTTP_LINESTR TINYHTTP_LINESTR_HELPER2(__LINE__)
|
||||
|
||||
// Opaque types
|
||||
typedef struct TinyHTTPServer TinyHTTPServer;
|
||||
|
||||
typedef struct {
|
||||
const char *ptr;
|
||||
ptrdiff_t len;
|
||||
} TinyHTTPString;
|
||||
|
||||
#define TINYHTTP_STRING(X) ((TinyHTTPString) {(X), sizeof(X)-1})
|
||||
|
||||
typedef enum {
|
||||
TINYHTTP_METHOD_GET,
|
||||
TINYHTTP_METHOD_POST,
|
||||
} TinyHTTPMethod;
|
||||
|
||||
typedef struct {
|
||||
TinyHTTPString name;
|
||||
TinyHTTPString value;
|
||||
} TinyHTTPHeader;
|
||||
|
||||
typedef struct {
|
||||
TinyHTTPMethod method;
|
||||
int minor;
|
||||
TinyHTTPString path;
|
||||
int num_headers;
|
||||
TinyHTTPHeader headers[TINYHTTP_HEADER_LIMIT];
|
||||
char* body;
|
||||
ptrdiff_t body_len;
|
||||
} TinyHTTPRequest;
|
||||
|
||||
typedef struct {
|
||||
TinyHTTPServer *server;
|
||||
unsigned short idx;
|
||||
unsigned short gen;
|
||||
} TinyHTTPResponse;
|
||||
|
||||
typedef struct {
|
||||
|
||||
int reuse;
|
||||
|
||||
// HTTP
|
||||
const char *plain_addr;
|
||||
int plain_port;
|
||||
int plain_backlog;
|
||||
|
||||
// HTTPS
|
||||
int secure;
|
||||
const char *secure_addr;
|
||||
int secure_port;
|
||||
int secure_backlog;
|
||||
const char *cert_file;
|
||||
const char *private_key_file;
|
||||
|
||||
} TinyHTTPServerConfig;
|
||||
|
||||
typedef enum {
|
||||
TINYHTTP_MEM_MALLOC,
|
||||
TINYHTTP_MEM_FREE,
|
||||
} TinyHTTPMemoryFuncTag;
|
||||
|
||||
typedef void*(*TinyHTTPMemoryFunc)(TinyHTTPMemoryFuncTag tag,
|
||||
void *ptr, int len, void *data);
|
||||
|
||||
// See [tinyhttp_stream_state]
|
||||
enum {
|
||||
TINYHTTP_STREAM_DIED = 1 << 0,
|
||||
TINYHTTP_STREAM_READY = 1 << 1,
|
||||
TINYHTTP_STREAM_RECV = 1 << 2,
|
||||
TINYHTTP_STREAM_SEND = 1 << 3,
|
||||
TINYHTTP_STREAM_CLOSE = 1 << 4,
|
||||
TINYHTTP_STREAM_REUSE = 1 << 5,
|
||||
TINYHTTP_STREAM_SEND_STARTED = 1 << 6,
|
||||
TINYHTTP_STREAM_RECV_STARTED = 1 << 7,
|
||||
};
|
||||
|
||||
// Internal use
|
||||
typedef enum {
|
||||
TINYHTTP_OUTPUT_STATE_NONE,
|
||||
TINYHTTP_OUTPUT_STATE_STATUS,
|
||||
TINYHTTP_OUTPUT_STATE_HEADER,
|
||||
TINYHTTP_OUTPUT_STATE_BODY,
|
||||
TINYHTTP_OUTPUT_STATE_ERROR,
|
||||
} TinyHTTPConnectionOutputState;
|
||||
|
||||
// Internal use
|
||||
enum {
|
||||
BYTE_QUEUE_ERROR = 1 << 0,
|
||||
BYTE_QUEUE_LOCK = 1 << 1,
|
||||
BYTE_QUEUE_READ = 1 << 2,
|
||||
BYTE_QUEUE_WRITE = 1 << 3,
|
||||
};
|
||||
|
||||
// Internal use
|
||||
typedef struct {
|
||||
|
||||
TinyHTTPMemoryFunc memfunc;
|
||||
void *memfuncdata;
|
||||
|
||||
unsigned long long curs;
|
||||
unsigned int lock; // TODO: Should this be u64?
|
||||
|
||||
char* data;
|
||||
unsigned int head;
|
||||
unsigned int size;
|
||||
unsigned int used;
|
||||
unsigned int limit;
|
||||
|
||||
char* read_target;
|
||||
unsigned int read_target_size;
|
||||
|
||||
int flags;
|
||||
} TinyHTTPByteQueue;
|
||||
|
||||
// Internal use
|
||||
typedef unsigned long long TinyHTTPByteQueueOffset;
|
||||
|
||||
typedef struct {
|
||||
int state;
|
||||
int numexch;
|
||||
int chunked;
|
||||
int keepalive;
|
||||
ptrdiff_t reqsize;
|
||||
unsigned long long bodylimit;
|
||||
TinyHTTPConnectionOutputState output_state;
|
||||
TinyHTTPByteQueueOffset content_length_value_offset;
|
||||
TinyHTTPByteQueueOffset content_length_offset;
|
||||
TinyHTTPByteQueue in;
|
||||
TinyHTTPByteQueue out;
|
||||
TinyHTTPRequest req;
|
||||
} TinyHTTPStream;
|
||||
|
||||
// TODO: Comment
|
||||
int tinyhttp_streq(TinyHTTPString s1, TinyHTTPString s2);
|
||||
|
||||
// TODO: Comment
|
||||
int tinyhttp_streqcase(TinyHTTPString s1, TinyHTTPString s2);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_printbytes(char *prefix, const char *src, int len);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_printstate_(int state, const char *file, const char *line);
|
||||
|
||||
// TODO: Comment
|
||||
#define tinyhttp_printstate(state) tinyhttp_printstate_(state, __FILE__, TINYHTTP_LINESTR)
|
||||
|
||||
int tinyhttp_findheader(TinyHTTPRequest *req, TinyHTTPString name);
|
||||
|
||||
int tinyhttp_parserequest(char *src, ptrdiff_t len,
|
||||
unsigned long long body_limit, TinyHTTPRequest *req);
|
||||
|
||||
// Initializes an HTTP stream
|
||||
//
|
||||
// TODO: Comment on memfunc
|
||||
void
|
||||
tinyhttp_stream_init(TinyHTTPStream *stream,
|
||||
TinyHTTPMemoryFunc memfunc, void *memfuncdata);
|
||||
|
||||
// Deinitializes an HTTP stream
|
||||
//
|
||||
// This function may be called at any time on a memory
|
||||
// region where [tinyhttp_stream_init] was called at
|
||||
// least once.
|
||||
//
|
||||
// From the moment this function is called until the
|
||||
// next call to [tinyhttp_stream_init], all [tinyhttp_stream_*]
|
||||
// calls become no-ops.
|
||||
void tinyhttp_stream_free(TinyHTTPStream *stream);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_stream_kill(TinyHTTPStream *stream);
|
||||
|
||||
// Returns the current state of the connection
|
||||
//
|
||||
// TODO: List the possible states
|
||||
//
|
||||
// Note:
|
||||
// - Calling this function is always valid on a
|
||||
// memory region where [tinyhttp_stream_init] was
|
||||
// called at least once.
|
||||
int tinyhttp_stream_state(TinyHTTPStream *stream);
|
||||
|
||||
// Returns the pointer and capacity of a memory region
|
||||
// where data should be read from the network.
|
||||
//
|
||||
// You can write at least [*cap] bytes at the returned
|
||||
// pointer, and need to call [tinyhttp_stream_net_recv_ack]
|
||||
// when the write is complete.
|
||||
//
|
||||
// Note:
|
||||
// - Due to sticky errors, this function may return
|
||||
// the null pointer and a capacity of 0. The caller
|
||||
// must not write to the pointer but act as if
|
||||
// everything went okay.
|
||||
char *tinyhttp_stream_recv_buf(TinyHTTPStream *stream, ptrdiff_t *cap);
|
||||
|
||||
// Acknowledge the bytes written from the network
|
||||
//
|
||||
// The write must have been initiated by calling
|
||||
// [tinyhttp_stream_net_recv_buf].
|
||||
void tinyhttp_stream_recv_ack(TinyHTTPStream *stream, ptrdiff_t num);
|
||||
|
||||
// Returns the pointer and capacity of a memory region
|
||||
// where data should be written to the network.
|
||||
//
|
||||
// When the write is complete, [tinyhttp_stream_net_send_ack]
|
||||
// should be called with the number of consumed bytes.
|
||||
char *tinyhttp_stream_send_buf(TinyHTTPStream *stream, ptrdiff_t *len);
|
||||
|
||||
// See [tinyhttp_stream_net_send_buf]
|
||||
void tinyhttp_stream_send_ack(TinyHTTPStream *stream, ptrdiff_t num);
|
||||
|
||||
// Enable/disable connection reuse for this HTTP
|
||||
// connection. This change takes effect at the start
|
||||
// of the next request.
|
||||
//
|
||||
// Note that even if the reuse option is set, the
|
||||
// connection may still be closed due to errors or
|
||||
// the client deciding otherwise.
|
||||
//
|
||||
// On the other hand, if this option is set the next
|
||||
// request will definitely be the last.
|
||||
//
|
||||
// NOTE: This is turned off by default
|
||||
void tinyhttp_stream_setreuse(TinyHTTPStream *stream, int value);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_stream_setbodylimit(TinyHTTPStream *stream, unsigned long long value);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_stream_setinbuflimit(TinyHTTPStream *stream, unsigned int value);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_stream_setoutbuflimit(TinyHTTPStream *stream, unsigned int value);
|
||||
|
||||
// TODO: Comment
|
||||
TinyHTTPRequest *tinyhttp_stream_request(TinyHTTPStream *stream);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_stream_response_status(TinyHTTPStream *stream, int status);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_stream_response_header(TinyHTTPStream *stream, const char *fmt, ...);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_stream_response_header_fmt(TinyHTTPStream *stream, const char *fmt, va_list args);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_stream_response_body(TinyHTTPStream *stream, const char *src, int len);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_stream_response_body_setmincap(TinyHTTPStream *stream, ptrdiff_t mincap);
|
||||
|
||||
// TODO: Comment
|
||||
char *tinyhttp_stream_response_body_buf(TinyHTTPStream *stream, ptrdiff_t *cap);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_stream_response_body_ack(TinyHTTPStream *stream, ptrdiff_t num);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_stream_response_send(TinyHTTPStream *stream);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_stream_response_undo(TinyHTTPStream *stream);
|
||||
|
||||
// TODO: Comment
|
||||
TinyHTTPServer *tinyhttp_server_init(TinyHTTPServerConfig config);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_server_free(TinyHTTPServer *server);
|
||||
|
||||
// TODO: Comment
|
||||
int tinyhttp_server_wait(TinyHTTPServer *server, TinyHTTPRequest **req,
|
||||
TinyHTTPResponse *res, int timeout);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_response_status(TinyHTTPResponse res, int status);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_response_header(TinyHTTPResponse res,
|
||||
const char *fmt, ...);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_response_header_fmt(TinyHTTPResponse res,
|
||||
const char *fmt, va_list args);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_response_body_setmincap(TinyHTTPResponse res,
|
||||
ptrdiff_t mincap);
|
||||
|
||||
// TODO: Comment
|
||||
char *tinyhttp_response_body_buf(TinyHTTPResponse res, ptrdiff_t *cap);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_response_body_ack(TinyHTTPResponse res, ptrdiff_t num);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_response_body(TinyHTTPResponse res, char *src, int len);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_response_send(TinyHTTPResponse res);
|
||||
|
||||
// TODO: Comment
|
||||
void tinyhttp_response_undo(TinyHTTPResponse res);
|
||||
Reference in New Issue
Block a user