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
+55
View File
@@ -0,0 +1,55 @@
#include <http.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../http.h"
int main(int argc, char **argv) {
if (argc < 2) {
printf("Usage: %s <url1> [url2 ...]\n", argv[0]);
return 1;
}
http_global_init();
HTTP_Client *client = http_client_init();
HTTP_RequestHandle reqs[100];
for (int i = 1; i < argc; i++) {
int k = i-1;
if (http_client_request(client, &reqs[k]) < 0) {
printf("request creation error\n");
return -1;
}
http_request_line(reqs[k], HTTP_METHOD_GET, (HTTP_String) { argv[i], strlen(argv[i]) });
http_request_submit(reqs[k]);
printf("request submitted\n");
}
for (int i = 1; i < argc; i++)
if (http_client_wait(client, NULL) < 0) {
printf("request wait error\n");
return -1;
}
printf("all requests completed\n");
for (int i = 1; i < argc; i++) {
HTTP_Response *result = http_request_result(reqs[i-1]);
if (!result) {
fprintf(stderr, "No result from HTTP request\n");
http_request_free(reqs[i-1]);
return 1;
}
printf("Status: %d\n", result->status);
printf("Body: %.*s\n", (int)result->body.len, result->body.ptr);
http_request_free(reqs[i-1]);
}
http_client_free(client);
http_global_free();
return 0;
}
+199
View File
@@ -0,0 +1,199 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <http.h>
#define COUNT(X) (sizeof(X) / sizeof((X)[0]))
#define BLK "\e[0;30m"
#define RED "\e[0;31m"
#define GRN "\e[0;32m"
#define YEL "\e[0;33m"
#define BLU "\e[0;34m"
#define MAG "\e[0;35m"
#define CYN "\e[0;36m"
#define WHT "\e[0;37m"
#define RST "\e[0m"
HTTP_String *already_crawled = NULL;
int num_already_crawled = 0;
int cap_already_crawled = 0;
HTTP_String copystr(HTTP_String str)
{
char *copy = malloc(str.len);
if (copy == NULL)
abort();
memcpy(copy, str.ptr, str.len);
return (HTTP_String) { copy, str.len };
}
int normalize_url(HTTP_String url, char *dst, int max)
{
HTTP_URL parsed_url;
if (http_parse_url(url.ptr, url.len, &parsed_url) <= 0)
return -1;
int len = snprintf(dst, max, "http://%.*s%.*s",
(int) parsed_url.authority.host.text.len,
parsed_url.authority.host.text.ptr,
(int) parsed_url.path.len,
parsed_url.path.ptr
);
if (len < 0 || len >= max)
return -1;
return len;
}
void add_to_crawled_list(HTTP_String url)
{
if (num_already_crawled == cap_already_crawled) {
int new_cap = 2 * cap_already_crawled;
if (new_cap == 0)
new_cap = 8;
HTTP_String *new_ptr = malloc(new_cap * sizeof(HTTP_String));
if (new_ptr == NULL)
abort();
if (cap_already_crawled > 0) {
for (int i = 0; i < num_already_crawled; i++)
new_ptr[i] = already_crawled[i];
free(already_crawled);
}
already_crawled = new_ptr;
cap_already_crawled = new_cap;
}
char buf[1<<10];
int len = normalize_url(url, buf, (int) sizeof(buf));
if (len < 0) return;
already_crawled[num_already_crawled++] = copystr((HTTP_String) { buf, len });
}
bool is_already_crawled(HTTP_String url)
{
char buf[1<<10];
int len = normalize_url(url, buf, (int) sizeof(buf));
if (len < 0 || len >= (int) sizeof(buf))
return false;
for (int i = 0; i < num_already_crawled; i++)
if (http_streq(already_crawled[i], (HTTP_String) { buf, len }))
return true;
return false;
}
HTTP_String next_link(HTTP_String src, int *pcur)
{
int cur = *pcur;
for (;;) {
while (cur < src.len && src.ptr[cur] != 'h')
cur++;
if (cur == src.len)
break;
int off = cur;
HTTP_URL parsed_url;
int len = http_parse_url(src.ptr + cur, src.len - cur, &parsed_url);
if (len <= 0) {
cur++;
continue;
}
cur += len;
if (!http_streq(parsed_url.scheme, HTTP_STR("http")) &&
!http_streq(parsed_url.scheme, HTTP_STR("https")))
continue;
*pcur = cur;
return (HTTP_String) { src.ptr + off, len };
}
*pcur = cur;
return (HTTP_String) { NULL, 0 };
}
int main(int argc, char **argv)
{
http_global_init();
if (argc < 2) {
printf("Usage: %s <URL>\n", argv[0]);
return -1;
}
HTTP_String start_url = { argv[1], strlen(argv[1]) };
HTTP_Client *client = http_client_init();
if (client == NULL) {
printf("Couldn't initialize HTTP client object\n");
return -1;
}
HTTP_RequestHandle req;
int ret = http_client_request(client, &req);
if (ret < 0) {
printf("Couldn't start request\n");
http_client_free(client);
return -1;
}
http_request_line(req, HTTP_METHOD_GET, start_url);
http_request_header(req, "User-Agent: Simple crawler", -1);
http_request_submit(req);
for (;;) {
HTTP_RequestHandle req;
ret = http_client_wait(client, &req);
if (ret < 0) {
// TODO
return -1;
}
HTTP_Response *res = http_request_result(req);
if (res == NULL) {
http_request_free(req);
continue; // Request didn't complete
}
HTTP_String body = res->body;
int cursor = 0;
for (;;) {
HTTP_String url = next_link(body, &cursor);
if (url.len == 0) break;
if (is_already_crawled(url)) {
printf("Ignoring " RED "%.*s" RST "\n", (int) url.len, url.ptr);
continue;
}
printf("Fetching " GRN "%.*s" RST "\n", (int) url.len, url.ptr);
add_to_crawled_list(url);
HTTP_RequestHandle req;
ret = http_client_request(client, &req);
if (ret < 0)
continue;
http_request_line(req, HTTP_METHOD_GET, url);
http_request_header(req, "User-Agent: Simple crawler", -1);
http_request_submit(req);
}
}
http_client_free(client);
http_global_free();
return 0;
}
@@ -18,7 +18,7 @@
#define CLOSE_SOCKET close
#endif
#include "../tinyhttp.h"
#include <http.h>
// This example showcases how to use the engine interface
// to build a blocking HTTP server that works on Windows
@@ -1,8 +1,7 @@
#include <winsock2.h>
#include <ws2tcpip.h>
#include <mswsock.h>
#include "../tinyhttp.h"
#include <http.h>
#define MAX_CLIENTS (1<<10)
+95
View File
@@ -0,0 +1,95 @@
#include <stdbool.h>
#include <http.h>
// This example shows how to set up a basic HTTP server
int main(void)
{
// Choose the interface to listen on and the port.
// Currently, servers can only bind to IPv4 addresses.
HTTP_String addr = HTTP_STR("127.0.0.1");
uint16_t port = 8080;
bool all_interfaces = false;
// If you want to bind to all interfaces, you can
// set the address to an empty string.
if (all_interfaces)
addr = HTTP_STR("");
// Instanciate the HTTP server object
HTTP_Server *server = http_server_init(addr, port);
if (server == NULL)
return -1;
// Now we loop forever. Every iteration will serve
// a single HTTP request
for (;;) {
HTTP_Request *req;
HTTP_ResponseHandle res;
// Block until a request is available
int ret = http_server_wait(server, &res, &res);
// The wait functions returns 0 on success and -1
// on error. By "error" I mean an unrecoverable
// condition. There is no other option than kill
// the process.
if (ret < 0)
return -1;
// The request information is accessible from
// the [req] variable. Most fields in the request
// struct are reference to the original request
// string. They use type HTTP_String and are not
// null-terminated. This means you'll have to make
// sure to express the length when interacting with
// libc:
HTTP_String path = req->url.path;
printf("requested path [%.*s]\n", (int) path.len, path.ptr);
// To find a specific header value, you can either
// iterate over the [req->headers] array or use
// a helper function. Note that this compares header
// names case-insensitively.
int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Some-Header-Name"));
if (idx == -1) {
// Header wasn't found
} else {
// Found
HTTP_String value = req->headers[idx].value;
printf("Header has value [%.*s]\n", (int) value.len, value.ptr);
}
// To create a response, you will need to specify
// status code, headers, and content in the proper
// order.
// First the status code
http_response_status(res, 200);
// Then zero or more headers
http_response_header(res, "Content-Type: text/plain");
// Then you can write zero or more chunks of the response body
http_response_body(res, HTTP_STR("Hello"));
http_response_body(res, HTTP_STR(", world!"));
// Then, mark the request as complete (Very important or the server will hang!)
http_response_done(res);
// Note that none of the http_response_* functions return errors.
// This is by design to simplify user endpoint code. If at any point
// something goes wrong, the server will send a code 4xx or 5xx to
// the client or abort the TCP connection entirely.
}
// This program will loop forever, but if you write
// your server in a way to exit gracefully, this is
// you the server object is freed:
http_server_free(server);
// Have fun. Bye!
return 0;
}
+93
View File
@@ -0,0 +1,93 @@
#include <http.h>
// This example shows how to generate response bodies
// using the zero-copy API.
int main(void)
{
// All the setup is identical to the previous example.
// The only thing that changes where "http_response_body"
// is called.
HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080);
if (server == NULL)
return -1;
for (;;) {
HTTP_Request *req;
HTTP_ResponseHandle res;
int ret = http_server_wait(server, &res, &res);
if (ret < 0) return -1;
http_response_status(res, 200);
http_response_header(res, "Content-Type: text/plain");
// The previous example used the *_body function to
// write the response body in chunks:
//
// http_response_body(res, HTTP_STR("Hello"));
// http_response_body(res, HTTP_STR(", world!"));
//
// This function reads from an user buffer and copies
// the data in the connection's output buffer. If the
// data is not in a contiguous region that's fine as
// the function can be called repeatedly on separate
// chunks.
//
// This function assumes the user is holding in memory
// the data to be sent beforehand, but this may not
// be true. If for instance the data comes from a file,
// the user will need to read from the file, copy in
// memory and then write to the response body.
//
// The zero-copy API allows copying directly from the
// source of the data (such as the read() system call
// on a file descriptor) to the server's output buffer
char example_data[] = "I'm some example data!";
int example_data_len = sizeof(example_data)-1;
// Tell the server how much data we are going to write
http_response_bodycap(res, example_data_len);
int cap;
char *dst;
// Get a pointer to the server's output buffer. The
// output parameter [cap] is the capacity of the region
// and is equal or larger than the data we requested
// with *_bodycap
dst = http_response_bodybuf(res, &cap);
// Write the data directly into the output buffer. In
// this example we are copying from memory, but you could
// read from a file or a socket
if (dst) {
memcpy(dst, example_data, example_data_len);
}
// Tell the server how much bytes we have written to
// the provided region.
http_response_bodyack(res, example_data_len);
// The reason we had to guard the [memcpy] by checking the
// [dst] pointer is that if an error occurred internally
// then *_bodybuf will return NULL. This will cause the
// server to either return an internally generated error
// response or drop the connection. The correct thing to
// do in that situation is not access the pointer and do
// as nothing bad happened.
// As usual, mark the response as complete
http_response_done(res);
// If we're being being honest, this is not a zero-copy
// interface. It's more like an N-1 copy interface as in
// it just avoids one copy from userspace to userspace!
}
http_server_free(server);
return 0;
}
+55
View File
@@ -0,0 +1,55 @@
#include <http.h>
// This example shows how undo a response that is being built
// when an error occurs.
int main(void)
{
HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080);
if (server == NULL)
return -1;
for (;;) {
HTTP_Request *req;
HTTP_ResponseHandle res;
int ret = http_server_wait(server, &res, &res);
if (ret < 0) return -1;
// Say we are building a request..
http_response_status(res, 200);
http_response_header(res, "Content-Type: text/plain");
// .. and in the middle of building an error condition
// occurs. Maybe a file was missing or an allocation fails.
// The proper response in this case would be a code 500
// with an error message, but we already wrote the first
// part of the response assuming the operation would succede.
//
// You can use the *_undo function to reset the response
// building process
bool error_occurred = true;
if (error_occurred) {
http_response_undo(res);
// Now we are back to setting the status code
http_response_status(res, 500);
http_response_header(res, "Content-Type: text/plain");
http_response_body(res, HTTP_STR("An error occurred!"));
http_response_done(res);
} else {
// If no error occures, we finish as planned
http_response_body(res, HTTP_STR("Hello, world!"));
http_response_done(res);
}
}
http_server_free(server);
return 0;
}
+213
View File
@@ -0,0 +1,213 @@
#include <stdbool.h>
#include <http.h>
// NOTE: This example doesn't work yet!
// This example shows how to delegate the response creation
// process to other threads.
//
// Your server may have some endpoints that require considerable
// computation or may be waiting for some external system to
// complete. If we used the current pattern we've been using for
// generating requests, following request will have to wait until
// this processing has concluded.
//
// One solution for this situation is to create a separate thread
// to do the waiting or processing. When a request is received
// that requires processing, it is passed to the second thread.
// In the mean time, the main thread can process the next request.
// When the thread has finished, it can just call the usual
// functions to produce a response.
// The following types are used to describe a job the worker
// needs to work on.
typedef enum {
// Special value used to tell the worker the program is terminating
NO_JOB,
// We assume jobs may be of two different types we call A and B
JOB_A,
JOB_B,
} JobType;
typedef struct {
JobType type;
HTTP_ResponseHandle res;
} Job;
// Maximum number of jobs that can be buffered at once
#define MAX_JOBS 100
void init_job_queue(void);
void free_job_queue(void);
// This function pops an item from the job queue. If the
// queue is empty, the thread will block until one is
// available.
Job pop_job(void);
// This function adds a job to the queue. The block argument
// changes the behavior when the queue is full and there is
// no space for a new job. If the block argument is true and
// there is no space, the thread waits. If the argument is
// false the function exits immediately by returning false
// with no new job pushed.
bool push_job(Job job, bool block);
void *worker(void*)
{
for (bool exit = false; !exit; ) {
Job job = pop_job();
switch (job.type) {
case NO_JOB:
exit = true;
break;
case JOB_A:
http_response_status(job.res, 200);
http_response_body(job.res, HTTP_STR("Job A completed"));
http_response_done(job.res);
break;
case JOB_B:
http_response_status(job.res, 200);
http_response_body(job.res, HTTP_STR("Job B completed"));
http_response_done(job.res);
break;
}
}
return NULL;
}
int main(void)
{
init_job_queue();
HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080);
if (server == NULL)
return -1;
for (;;) {
HTTP_Request *req;
HTTP_ResponseHandle res;
int ret = http_server_wait(server, &res, &res);
if (ret < 0) return -1;
if (http_streq(req->url.path, HTTP_STR("/endpoint_A"))) {
// Endpoint A sends the job to the worker.
// If too many jobs are queued, it blocks
Job job;
job.type = JOB_A;
job.res = res;
push_job(job, true);
} else if (http_streq(req->url.path, HTTP_STR("/endpoint_B"))) {
// Endpoint B sends the job to the worker
// but fails if the queue is full, in which
// case the "503 Service Unavailable" response
// is generated.
Job job;
job.type = JOB_B;
job.res = res;
if (!push_job(job, false)) {
http_response_status(res, 503);
http_response_done(res);
}
} else {
// Other endpoints may resolve immediately
http_response_status(res, 404);
http_response_done(res);
}
}
// Stop the worker by sending an empty job
Job job;
job.type = NO_JOB;
push_job(job, true);
http_server_free(server);
free_job_queue();
return 0;
}
//////////////////////////////////////////////
// This is a pretty standard condition variable-based
// producer-consumer queue. In this example we are using
// one worker, but we could easily have more than that.
Job queue[MAX_JOBS];
int queue_head = 0;
int queue_count = 0;
Mutex queue_lock;
Condvar queue_consume_event;
Condvar queue_produce_event;
void init_job_queue(void)
{
mutex_init(&queue_lock);
condvar_init(&queue_consume_event);
condvar_init(&queue_produce_event);
}
void free_job_queue(void)
{
condvar_free(&queue_produce_event);
condvar_free(&queue_consume_event);
mutex_free(&queue_lock);
}
Job pop_job(void)
{
mutex_lock(&queue_lock);
while (queue_count == 0);
condvar_wait(&queue_produce_event, &queue_lock, -1);
Job job = queue[queue_head];
queue_head = (queue_head + 1) % MAX_JOBS;
queue_count--;
condvar_signal(&queue_consume_event);
mutex_unlock(&queue_lock);
return job;
}
bool push_job(Job job, bool block)
{
mutex_lock(&queue_lock);
if (queue_count == 0) {
if (!block) {
mutex_unlock(&queue_lock);
return false;
}
do
condvar_wait(&queue_consume_event, &queue_lock, -1);
while (queue_count == 0);
}
int tail = (queue_head + queue_count) % MAX_JOBS;
queue[tail] = job;
queue_count++;
condvar_signal(&queue_produce_event);
mutex_unlock(&queue_lock);
return true;
}
View File
+74
View File
@@ -0,0 +1,74 @@
#include <stdbool.h>
#include <http.h>
// This example shows how to set up an HTTPS (HTTP over TLS)
// server.
int main(void)
{
// To setup an HTTPS server, we need to use the *_ex variant
// of the server initialization function as it offers more
// control. Server objects can serve HTTP traffic, HTTPS
// traffic, or both at the same time. The init_ex function
// allows us to control this behavior.
// The first argument is the local interface address. It
// works just as the other examples but is shared between
// HTTP and HTTPS. Then come the HTTP port and HTTPS port
// arguments. If you want to disable HTTP or HTTPS you can
// pass zero to its port argument. If the HTTPS port is
// not zero, you need to pass the file names of the server's
// certificate and private key.
HTTP_Server *server = http_server_init_ex(
HTTP_STR("127.0.0.1"), // HTTP and HTTPS port
8080, // HTTP port
8443, // HTTPS port
HTTP_STR("cert.pem"),
HTTP_STR("privkey.pem")
);
if (server == NULL)
return -1;
// Just to be clear, to initialize a plain HTTP server
// using the *_ex function we would do this:
//
// HTTP_Server *server = http_server_init_ex(
// HTTP_STR("127.0.0.1"), // HTTP and HTTPS port
// 8080, // HTTP port
// 0, // HTTPS disabled
// HTTP_STR(""), // ignore
// HTTP_STR("") // ignore
// );
//
// and if we wanted and HTTPS-only server we would
// do this:
//
// HTTP_Server *server = http_server_init_ex(
// HTTP_STR("127.0.0.1"), // HTTP and HTTPS port
// 0, // HTTP disabled
// 8443, // HTTPS port
// HTTP_STR("cert.pem"),
// HTTP_STR("privkey.pem")
// );
// Everything else is identical to the simple HTTP server
// example.
for (;;) {
HTTP_Request *req;
HTTP_ResponseHandle res;
int ret = http_server_wait(server, &res, &res);
if (ret < 0) return -1;
http_response_status(res, 200);
http_response_header(res, "Content-Type: text/plain");
http_response_body(res, HTTP_STR("Hello"));
http_response_body(res, HTTP_STR(", world!"));
http_response_done(res);
}
http_server_free(server);
return 0;
}
@@ -0,0 +1,154 @@
#include <http.h>
// This is an example of how to serve different websites
// over a single HTTPS server instance.
int setup_test_certificates(void);
int main(void)
{
// First, create three certificates for the domains:
//
// websiteA.com
// websiteB.com
// websiteC.com
//
// This will create a number of certificate files
// and private key files
//
// websiteA_cert.pem websiteA_key.pem
// websiteB_cert.pem websiteB_key.pem
// websiteC_cert.pem websiteC_key.pem
//
// Of course this is just for testing. It is expected
// you have your own.
int ret = setup_test_certificates();
if (ret < 0)
return -1;
// First, set up an HTTPS server instance with one
// of the certificate. This will act as default certificate
// when ecrypted connections don't target a specific domain.
HTTP_Server *server = http_server_init(
HTTP_STR("127.0.0.1"), 8080, 8443,
HTTP_STR("websiteA_cert.pem"),
HTTP_STR("websiteA_key.pem")
);
if (server == NULL)
return -1;
// Then we can add an arbitrary number of additional
// certificates using the add_website function
ret = http_server_add_website(server,
HTTP_STR("websiteB_cert.pem"),
HTTP_STR("websiteB_key.pem")
);
if (ret < 0)
return -1;
ret = http_server_add_website(server,
HTTP_STR("websiteC_cert.pem"),
HTTP_STR("websiteC_key.pem")
);
if (ret < 0)
return -1;
// Now the server is ready to accept incoming HTTP
// or HTTPS connections.
//
// Note that the add_website function is only used
// to serve the correct certificate to the client.
// The HTTP request itself may very well hold a
// different domain name in the host header:
//
// [client] [server]
// | |
// | TLS hanshake to domain1.com |
// | -------------------------------> |
// | |
// | cert for domain1.com |
// | <------------------------------- |
// | |
// | HTTP request to domain2.com |
// | over the encrypted connection |
// | established with domain1.com |
// | -------------------------------> |
// | |
// | response as domain2.com |
// | <------------------------------- |
// | |
for (;;) {
HTTP_Request *req;
HTTP_ResponseHandle res;
ret = http_server_wait(server, &req, &res);
if (ret < 0)
break;
int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Host"));
HTTP_ASSERT(idx != -1); // Requests without the host header are always rejected
HTTP_String host = req->headers[idx].value;
if (http_streq(host, HTTP_STR("websiteB.com"))) {
http_response_status(res, 200);
http_response_body(res, "Hello from websiteB.com!");
http_response_done(res);
} else if (http_streq(host, HTTP_STR("websiteC.com"))) {
http_response_status(res, 200);
http_response_body(res, "Hello from websiteC.com!");
http_response_done(res);
} else {
// Serve websiteA.com by default to be consistent
// with the certificate setup
http_response_status(res, 200);
http_response_body(res, "Hello from websiteA.com!");
http_response_done(res);
}
}
http_server_free(server);
return 0;
}
int setup_test_certificates(void)
{
int ret = http_create_test_certificate(
HTTP_STR("IT"),
HTTP_STR("Organization A"),
HTTP_STR("websiteA.com"),
HTTP_STR("websiteA_cert.pem"),
HTTP_STR("websiteA_key.pem")
);
if (ret < 0)
return -1;
ret = http_create_test_certificate(
HTTP_STR("IT"),
HTTP_STR("Organization B"),
HTTP_STR("websiteB.com"),
HTTP_STR("websiteB_cert.pem"),
HTTP_STR("websiteB_key.pem")
);
if (ret < 0)
return -1;
ret = http_create_test_certificate(
HTTP_STR("IT"),
HTTP_STR("Organization C"),
HTTP_STR("websiteC.com"),
HTTP_STR("websiteC_cert.pem"),
HTTP_STR("websiteC_key.pem")
);
if (ret < 0)
return -1;
return 0;
}
-18
View File
@@ -1,18 +0,0 @@
#include <stdio.h>
#include "../tinyhttp.h"
int main(void)
{
HTTP_Proxy proxy;
int ret = http_proxy_init(&proxy, "127.0.0.1", 8080);
if (ret < 0) {
printf("http_proxy_init failed\n");
return -1;
}
for (;;)
http_proxy_wait(&proxy, -1);
http_proxy_free(&proxy);
return 0;
}
-114
View File
@@ -1,114 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include "../tinyhttp.h"
// This is an example of how to use the TinyHTTP simplified
// server API to build a cross-platform HTTP server.
int main(void)
{
HTTP_Server server;
// Initialize a server listening on the given interface
// and with the given port.
int ret = http_server_init(&server, "127.0.0.1", 8080);
if (ret < 0) {
printf("http_server_init failed\n");
return -1;
}
for (;;) {
// Set for how many milliseconds the server will wait for
// requests this iteration. If -1, the function will not
// timeout.
int timeout_ms = 1000;
HTTP_Request *req;
HTTP_ResponseHandle res;
ret = http_server_wait(&server, &req, &res, timeout_ms);
if (ret == 0)
continue; // Timeout
if (ret < 0)
break; // An unrecoverable error occurred
// You can access the request data through the
// "req" pointer. For this example, we only allow
// GET requests to the "/hello" endpoint
if (req->method != HTTP_METHOD_GET) {
// Respond with the status code 405
http_response_status(res, 405);
// Mark the response as complete. If you don't
// call this, the client will just hang!
http_response_done(res);
// Go back to waiting
continue;
}
// Compare the requested resource with "/hello".
// The HTTP_STR macro can be used on string literals to
// get the length automatically. It is equivalent to:
//
// (HTTP_String) { literal, sizeof(literal)-1 }
//
if (!http_streq(req->url.path, HTTP_STR("/hello"))) {
// Some other resource was requested
http_response_status(res, 404);
http_response_done(res);
continue;
}
// Now we send the success response
http_response_status(res, 200);
// Set zero or more headers
// You must pass a string in the form:
//
// <name>: <spaces?> <value> <spaces?>
//
// It's important you don't use the \r character
// and there are no spaces before the ':' character.
//
// You should avoid adding the "Connection",
// "Transfer-Encoding", or "Content-Length" headers
// since they are added automatically.
http_response_header(res, "first-header: %d", 99);
http_response_header(res, "second-header: %s", "Some string");
// After having set any headers, we can optionally
// add some content to the request
// Add some bytes to the payload in terms of a pointer
// and length pair. If the length is -1, the bytes are
// assumed to be null-terminated.
http_response_body(res, "Hello, world!", -1);
// Now let's say we are in the middle of building a
// response and an error occurres. In that case, we
// can undo all the progress since the first status
// call and start all over:
int error = rand() & 1;
if (error) {
http_response_undo(res);
http_response_status(res, 500);
http_response_done(res);
continue;
}
// Let's add some more bytes
http_response_body(res, " How's it going??", -1);
// Ok. Done!
http_response_done(res);
}
http_server_free(&server);
return 0;
}