Files
cHTTP/examples/engine/blocking_server.c
T
2025-07-20 23:53:23 +02:00

244 lines
5.9 KiB
C

#include <stdio.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2ipdef.h>
#include <ws2tcpip.h>
#define CLOSE_SOCKET closesocket
#else
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define SOCKET int
#define INVALID_SOCKET -1
#define CLOSE_SOCKET close
#endif
#include <chttp.h>
// This example showcases how to use the engine interface
// to build a blocking HTTP server that works on Windows
// and Linux.
// Callback used by the engine to manage dynamic memory
static void *memfunc(HTTP_MemoryFuncTag tag,
void *ptr, int len, void *data)
{
(void) data;
switch (tag) {
case HTTP_MEMFUNC_MALLOC:
return malloc(len);
case HTTP_MEMFUNC_FREE:
free(ptr);
return NULL;
}
return NULL;
}
static void produce_response(HTTP_Engine *eng)
{
// All considerations in simple_server.c for how responses
// are built also applies here, where http_engine_XXX functions
// are used instead of http_response_XXX functions.
// Set the response status
http_engine_status(eng, 200);
// Set zero or more headers
http_engine_header(eng, HTTP_STR("Server: tinyhttp"));
// Set some bytes in the body
http_engine_body(eng, HTTP_STR("Hello, world!"));
// This is one difference from the http_response_XXX API.
// It's possible to write response content directly into
// the engine's output buffer. This avoids copies in some
// circumstances.
char msg[] = " What's up??";
// First, set how many bytes the output buffer will need
// to hold at least:
http_engine_bodycap(eng, sizeof(msg)-1);
// Now get the location for the write. The returned pointer
// points to a region of size "cap", which equal or greater
// to the previously set minimum capacity.
int cap;
char *dst = http_engine_bodybuf(eng, &cap);
// If an error occurred internally, the returned pointer will
// be NULL and the capacity 0. In this case, you can just skip
// this write. The engine will automatically be closed when the
// "http_engine_done" function is called.
if (dst) {
memcpy(dst, msg, sizeof(msg)-1);
// Tell the engine how many bytes the application wrote to the
// provided buffer.
http_engine_bodyack(eng, sizeof(msg)-1);
}
// If an error occurs, you can undo all progress and start
// from scratch
int error = rand() & 1;
if (error) {
http_engine_undo(eng);
http_engine_status(eng, 500);
http_engine_done(eng);
return;
}
http_engine_done(eng);
}
int main(void)
{
// Interface and port the server will be listening on.
char *addr = "127.0.0.1";
int port = 8080;
#ifdef _WIN32
WSADATA wd;
if (WSAStartup(MAKEWORD(2, 2), &wd))
return -1;
#endif
// Create the listening socket
SOCKET listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == INVALID_SOCKET)
return -1;
// Ignore the cooldown time for the bound interface to
// avoid that annoying "address in use" error
int reuse = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &reuse, sizeof(reuse));
// Fill out the address struct
struct sockaddr_in bind_buf;
{
struct in_addr addr_buf;
if (inet_pton(AF_INET, addr, &addr_buf) != 1)
return -1;
bind_buf.sin_family = AF_INET;
bind_buf.sin_port = htons(port);
bind_buf.sin_addr = addr_buf;
memset(&bind_buf.sin_zero, 0, sizeof(bind_buf.sin_zero));
}
// Associate the listening socket to the interface
if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0)
return -1;
// Allow incoming connections
if (listen(listen_fd, 32) < 0)
return -1;
for (;;) {
printf("Waiting for a connection\n");
// Get an incoming connection from the kernel
SOCKET client_fd = accept(listen_fd, NULL, NULL);
if (client_fd == INVALID_SOCKET)
continue;
printf("New connection\n");
// Initialize the HTTP state machine
HTTP_Engine eng;
http_engine_init(&eng, 0, memfunc, NULL);
for (;;) {
// At this point, the engine can be in one
// of four states:
// 1) RECV_BUF: The engine is waiting for bytes from the network
// 2) SEND_BUF: The engine wants to write bytes from the network
// 3) CLOSED: The connection shut down at the HTTP layer
// 4) PREP_STATUS: A request was received and the associated response
// needs to be generated.
HTTP_EngineState state = http_engine_state(&eng);
if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) {
produce_response(&eng);
} else if (state == HTTP_ENGINE_STATE_SERVER_RECV_BUF) {
printf("Receiving bytes\n");
// Get a pointer to the engine's input buffer
int cap;
char *dst = http_engine_recvbuf(&eng, &cap);
// The application can write up to "cap" bytes
// to the "dst" buffer.
int ret = recv(client_fd, dst, cap, 0);
if (ret <= 0) {
// If the peer disconnected or an error occurred,
// we can "close" the engine. This makes it so any
// further operation on the engine will be a no-op
// and the next time we query the state we will get
// CLOSED.
http_engine_close(&eng);
ret = 0;
}
printf("Received %d bytes\n", ret);
// Tell the engine how many bytes we wrote to
// the buffer.
http_engine_recvack(&eng, ret);
} else if (state == HTTP_ENGINE_STATE_SERVER_SEND_BUF) {
// This code is the same as the recv case except
// we read from the buffer instead of writing.
printf("Sending bytes\n");
int len;
char *src = http_engine_sendbuf(&eng, &len);
// Here "src" points to "len" bytes that need to
// be sent over the network.
int ret = send(client_fd, src, len, 0);
if (ret < 0) {
http_engine_close(&eng);
ret = 0;
}
printf("Sent %d bytes\n", ret);
http_engine_sendack(&eng, ret);
} else {
// HTTP_ENGINE_STATE_SERVER_CLOSED
printf("HTTP close\n");
break;
}
}
printf("Closing connection\n");
http_engine_free(&eng);
CLOSE_SOCKET(client_fd);
}
CLOSE_SOCKET(listen_fd);
#ifdef _WIN32
WSACleanup();
#endif
return 0;
}