This commit is contained in:
2025-07-20 16:46:08 +02:00
parent 939b20abf0
commit 51cae898f8
37 changed files with 7837 additions and 2187 deletions
+244
View File
@@ -0,0 +1,244 @@
#include <stdio.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2ipdef.h>
#include <ws2tcpip.h>
#define CLOSE_SOCKET closesocket
#else
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define SOCKET int
#define INVALID_SOCKET -1
#define CLOSE_SOCKET close
#endif
#include <http.h>
// This example showcases how to use the engine interface
// to build a blocking HTTP server that works on Windows
// and Linux.
// Callback used by the engine to manage dynamic memory
static void *memfunc(HTTP_MemoryFuncTag tag,
void *ptr, int len, void *data)
{
(void) data;
switch (tag) {
case HTTP_MEMFUNC_MALLOC:
return malloc(len);
case HTTP_MEMFUNC_FREE:
free(ptr);
return NULL;
}
return NULL;
}
static void produce_response(HTTP_Engine *eng)
{
// All considerations in simple_server.c for how responses
// are built also applies here, where http_engine_XXX functions
// are used instead of http_response_XXX functions.
// Set the response status
http_engine_status(eng, 200);
// Set zero or more headers
http_engine_header(eng, "Server: tinyhttp", -1);
// Set some bytes in the body
http_engine_body(eng, "Hello, world!", 13);
// This is one difference from the http_response_XXX API.
// It's possible to write response content directly into
// the engine's output buffer. This avoids copies in some
// circumstances.
char msg[] = " What's up??";
// First, set how many bytes the output buffer will need
// to hold at least:
http_engine_bodycap(eng, sizeof(msg)-1);
// Now get the location for the write. The returned pointer
// points to a region of size "cap", which equal or greater
// to the previously set minimum capacity.
int cap;
char *dst = http_engine_bodybuf(eng, &cap);
// If an error occurred internally, the returned pointer will
// be NULL and the capacity 0. In this case, you can just skip
// this write. The engine will automatically be closed when the
// "http_engine_done" function is called.
if (dst) {
memcpy(dst, msg, sizeof(msg)-1);
// Tell the engine how many bytes the application wrote to the
// provided buffer.
http_engine_bodyack(eng, sizeof(msg)-1);
}
// If an error occurs, you can undo all progress and start
// from scratch
int error = rand() & 1;
if (error) {
http_engine_undo(eng);
http_engine_status(eng, 500);
http_engine_done(eng);
return;
}
http_engine_done(eng);
}
int main(void)
{
// Interface and port the server will be listening on.
char *addr = "127.0.0.1";
int port = 8080;
#ifdef _WIN32
WSADATA wd;
if (WSAStartup(MAKEWORD(2, 2), &wd))
return -1;
#endif
// Create the listening socket
SOCKET listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == INVALID_SOCKET)
return -1;
// Ignore the cooldown time for the bound interface to
// avoid that annoying "address in use" error
int reuse = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &reuse, sizeof(reuse));
// Fill out the address struct
struct sockaddr_in bind_buf;
{
struct in_addr addr_buf;
if (inet_pton(AF_INET, addr, &addr_buf) != 1)
return -1;
bind_buf.sin_family = AF_INET;
bind_buf.sin_port = htons(port);
bind_buf.sin_addr = addr_buf;
memset(&bind_buf.sin_zero, 0, sizeof(bind_buf.sin_zero));
}
// Associate the listening socket to the interface
if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0)
return -1;
// Allow incoming connections
if (listen(listen_fd, 32) < 0)
return -1;
for (;;) {
printf("Waiting for a connection\n");
// Get an incoming connection from the kernel
SOCKET client_fd = accept(listen_fd, NULL, NULL);
if (client_fd == INVALID_SOCKET)
continue;
printf("New connection\n");
// Initialize the HTTP state machine
HTTP_Engine eng;
http_engine_init(&eng, 0, memfunc, NULL);
for (;;) {
// At this point, the engine can be in one
// of four states:
// 1) RECV_BUF: The engine is waiting for bytes from the network
// 2) SEND_BUF: The engine wants to write bytes from the network
// 3) CLOSED: The connection shut down at the HTTP layer
// 4) PREP_STATUS: A request was received and the associated response
// needs to be generated.
HTTP_EngineState state = http_engine_state(&eng);
if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) {
produce_response(&eng);
} else if (state == HTTP_ENGINE_STATE_SERVER_RECV_BUF) {
printf("Receiving bytes\n");
// Get a pointer to the engine's input buffer
int cap;
char *dst = http_engine_recvbuf(&eng, &cap);
// The application can write up to "cap" bytes
// to the "dst" buffer.
int ret = recv(client_fd, dst, cap, 0);
if (ret <= 0) {
// If the peer disconnected or an error occurred,
// we can "close" the engine. This makes it so any
// further operation on the engine will be a no-op
// and the next time we query the state we will get
// CLOSED.
http_engine_close(&eng);
ret = 0;
}
printf("Received %d bytes\n", ret);
// Tell the engine how many bytes we wrote to
// the buffer.
http_engine_recvack(&eng, ret);
} else if (state == HTTP_ENGINE_STATE_SERVER_SEND_BUF) {
// This code is the same as the recv case except
// we read from the buffer instead of writing.
printf("Sending bytes\n");
int len;
char *src = http_engine_sendbuf(&eng, &len);
// Here "src" points to "len" bytes that need to
// be sent over the network.
int ret = send(client_fd, src, len, 0);
if (ret < 0) {
http_engine_close(&eng);
ret = 0;
}
printf("Sent %d bytes\n", ret);
http_engine_sendack(&eng, ret);
} else {
// HTTP_ENGINE_STATE_SERVER_CLOSED
printf("HTTP close\n");
break;
}
}
printf("Closing connection\n");
http_engine_free(&eng);
CLOSE_SOCKET(client_fd);
}
CLOSE_SOCKET(listen_fd);
#ifdef _WIN32
WSACleanup();
#endif
return 0;
}
+225
View File
@@ -0,0 +1,225 @@
#include <winsock2.h>
#include <ws2tcpip.h>
#include <mswsock.h>
#include <http.h>
#define MAX_CLIENTS (1<<10)
// Callback used by the engine to manage dynamic memory
static void *memfunc(HTTP_MemoryFuncTag tag,
void *ptr, int len, void *data)
{
(void) data;
switch (tag) {
case HTTP_MEMFUNC_MALLOC:
return malloc(len);
case HTTP_MEMFUNC_FREE:
free(ptr);
return NULL;
}
return NULL;
}
static SOCKET start_accept(LPFN_ACCEPTEX AcceptEx, SOCKET listen_fd, OVERLAPPED *accept_ov, char *buf, int bufsize)
{
SOCKET accept_target = socket(AF_INET, SOCK_STREAM, 0);
if (accept_target == INVALID_SOCKET)
return INVALID_SOCKET;
unsigned long num;
memset(accept_ov, 0, sizeof(OVERLAPPED));
BOOL ok = AcceptEx(listen_fd, accept_target, buf, bufsize - ((sizeof(struct sockaddr_in) + 16) * 2),
sizeof(struct sockaddr_in) + 16,
sizeof(struct sockaddr_in) + 16,
&num, accept_ov);
if (!ok && WSAGetLastError() != ERROR_IO_PENDING) {
closesocket(accept_target);
return INVALID_SOCKET;
}
return accept_target;
}
static void start_recv_or_send(SOCKET sock, OVERLAPPED *recv_ov, OVERLAPPED *send_ov, HTTP_Engine *eng)
{
HTTP_EngineState state = http_engine_state(eng);
if (state == HTTP_ENGINE_STATE_SERVER_RECV_BUF) {
int cap;
char *dst = http_engine_recvbuf(eng, &cap);
memset(recv_ov, 0, sizeof(OVERLAPPED));
int ok = ReadFile((HANDLE) sock, dst, cap, NULL, recv_ov);
if (!ok && GetLastError() != ERROR_IO_PENDING)
http_engine_close(eng);
} else if (state == HTTP_ENGINE_STATE_SERVER_SEND_BUF) {
int len;
char *src = http_engine_sendbuf(eng, &len);
memset(send_ov, 0, sizeof(OVERLAPPED));
int ok = WriteFile((HANDLE) sock, src, len, NULL, send_ov);
if (!ok && GetLastError() != ERROR_IO_PENDING)
http_engine_close(eng);
}
}
int main(void)
{
// Interface and port the server will be listening on.
char *addr = "127.0.0.1";
int port = 8080;
// Initialize the winsock subsystem
WSADATA wd;
if (WSAStartup(MAKEWORD(2, 2), &wd))
return -1;
// Create the I/O completion port object
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
if (iocp == INVALID_HANDLE_VALUE)
return -1;
// Create the listening socket
SOCKET listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == INVALID_SOCKET)
return -1;
// Register the socket into the IOCP
if (CreateIoCompletionPort((HANDLE) listen_fd, iocp, 0, 0) == NULL)
return -1;
// Ignore the cooldown time for the bound interface to
// avoid that annoying "address in use" error
int reuse = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &reuse, sizeof(reuse));
// Fill out the address struct
struct sockaddr_in bind_buf;
{
struct in_addr addr_buf;
if (inet_pton(AF_INET, addr, &addr_buf) != 1)
return -1;
bind_buf.sin_family = AF_INET;
bind_buf.sin_port = htons(port);
bind_buf.sin_addr = addr_buf;
memset(&bind_buf.sin_zero, 0, sizeof(bind_buf.sin_zero));
}
// Associate the listening socket to the interface
if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0)
return -1;
// Allow incoming connections
if (listen(listen_fd, 32) < 0)
return -1;
// Get the AcceptEx function pointer
LPFN_ACCEPTEX AcceptEx = NULL;
GUID GuidAcceptEx = WSAID_ACCEPTEX;
unsigned long num;
int ret = WSAIoctl(listen_fd,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidAcceptEx, sizeof(GuidAcceptEx),
&AcceptEx, sizeof(AcceptEx),
&num, NULL, NULL);
if (ret == SOCKET_ERROR)
return -1;
OVERLAPPED accept_ov;
char accept_buf[2 * (sizeof(struct sockaddr_in) + 16)];
SOCKET accept_target = start_accept(AcceptEx, listen_fd, &accept_ov, accept_buf, (int) sizeof(accept_buf));
if (accept_target == INVALID_SOCKET)
return -1;
OVERLAPPED recv_overlapped[MAX_CLIENTS];
OVERLAPPED send_overlapped[MAX_CLIENTS];
SOCKET sockets[MAX_CLIENTS];
HTTP_Engine engs[MAX_CLIENTS];
for (int i = 0; i < MAX_CLIENTS; i++)
sockets[i] = INVALID_SOCKET;
for (;;) {
DWORD timeout = INFINITE;
DWORD transferred;
ULONG_PTR key;
OVERLAPPED *overlapped;
BOOL result = GetQueuedCompletionStatus(iocp, &transferred, &key, &overlapped, timeout);
if (!result && overlapped == NULL) {
if (GetLastError() == WAIT_TIMEOUT)
continue; // Go back to waiting
return -1; // Error
}
if (overlapped == &accept_ov) {
// A new client connected
int i = 0;
while (i < MAX_CLIENTS && sockets[i] != INVALID_SOCKET)
i++;
if (i == MAX_CLIENTS)
closesocket(accept_target); // Server limit reached
else {
sockets[i] = accept_target;
http_engine_init(&engs[i], 0, memfunc, NULL);
// Register the socket into the IOCP
if (CreateIoCompletionPort((HANDLE) sockets[i], iocp, i, 0) == NULL)
return -1;
start_recv_or_send(sockets[i], &recv_overlapped[i], &send_overlapped[i], &engs[i]);
// Check that the recv or send operation was started.
// If now, remove the connection
if (http_engine_state(&engs[i]) == HTTP_ENGINE_STATE_CLIENT_CLOSED) {
closesocket(sockets[i]);
sockets[i] = INVALID_SOCKET;
http_engine_free(&engs[i]);
}
}
accept_target = start_accept(AcceptEx, listen_fd, &accept_ov, accept_buf, (int) sizeof(accept_buf));
if (accept_target == INVALID_SOCKET)
return -1;
// Go back to waiting
continue;
}
// Complete the current operation
if (0) {}
else if (overlapped == &recv_overlapped[key]) http_engine_recvack(&engs[key], transferred);
else if (overlapped == &send_overlapped[key]) http_engine_sendack(&engs[key], transferred);
if (http_engine_state(&engs[key]) == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) {
// See blocking_server_with_engine.c to learn about how to
// build a response
http_engine_status(&engs[key], 200);
http_engine_body(&engs[key], "Hello, world!", -1);
http_engine_done(&engs[key]);
}
start_recv_or_send(sockets[key],
&recv_overlapped[key],
&send_overlapped[key],
&engs[key]);
if (http_engine_state(&engs[key]) == HTTP_ENGINE_STATE_SERVER_CLOSED) {
http_engine_free(&engs[key]);
closesocket(sockets[key]);
sockets[key] = INVALID_SOCKET;
}
}
return 0;
}