First version of the refactor
This commit is contained in:
@@ -1,53 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <chttp.h>
|
||||
|
||||
// This is an example of how to use cHTTP to perform
|
||||
// a basic GET request.
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_init();
|
||||
|
||||
// List any headers the request should hold
|
||||
HTTP_String headers[] = {
|
||||
HTTP_STR("User-Agent: cHTTP"),
|
||||
};
|
||||
|
||||
// Perform the request. This will block the thread
|
||||
// until an error occurs or the request completes.
|
||||
HTTP_Response *res = http_get(
|
||||
HTTP_STR("http://example.com/index.html"),
|
||||
headers, HTTP_COUNT(headers)
|
||||
);
|
||||
|
||||
// The http_get function returns NULL if the request
|
||||
// couldn't be performed.
|
||||
if (res == NULL) return -1;
|
||||
|
||||
// If the request succeded (note that responses with
|
||||
// status 4xx and 5xx are not considered as errors in
|
||||
// this context) the returned value holds the parsed
|
||||
// version of the response and the output handle is set.
|
||||
|
||||
printf("status code: %d\n", res->status);
|
||||
|
||||
for (int i = 0; i < res->num_headers; i++) {
|
||||
HTTP_Header header = res->headers[i];
|
||||
printf(
|
||||
"header %d: [%.*s] [%.*s]\n",
|
||||
i,
|
||||
HTTP_UNPACK(header.name),
|
||||
HTTP_UNPACK(header.value)
|
||||
);
|
||||
}
|
||||
|
||||
printf("body: %.*s\n", HTTP_UNPACK(res->body));
|
||||
|
||||
// When we are done reading from the response object
|
||||
// we must free the request's resources.
|
||||
http_response_free(res);
|
||||
|
||||
// All done. Deinitialize the library.
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
#include <stddef.h>
|
||||
#include <chttp.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_init();
|
||||
HTTP_Client *client = http_client_init();
|
||||
if (client == NULL)
|
||||
return -1;
|
||||
|
||||
HTTP_Response *responses[2];
|
||||
|
||||
HTTP_String urls[] = {
|
||||
HTTP_STR("http://coz.is"),
|
||||
HTTP_STR("http://coz.is"),
|
||||
};
|
||||
|
||||
bool trace = false;
|
||||
|
||||
HTTP_RequestBuilder builder;
|
||||
if (http_client_get_builder(client, &builder) < 0) return -1;
|
||||
http_request_builder_trace(builder, trace);
|
||||
http_request_builder_user_data(builder, &responses[0]);
|
||||
http_request_builder_line(builder, HTTP_METHOD_GET, urls[0]);
|
||||
http_request_builder_submit(builder);
|
||||
|
||||
if (http_client_get_builder(client, &builder) < 0) return -1;
|
||||
http_request_builder_trace(builder, trace);
|
||||
http_request_builder_user_data(builder, &responses[1]);
|
||||
http_request_builder_line(builder, HTTP_METHOD_GET, urls[1]);
|
||||
http_request_builder_submit(builder);
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
void **dst;
|
||||
HTTP_Response *output;
|
||||
if (http_client_wait(client, &output, (void*) &dst) < 0)
|
||||
return -1;
|
||||
*dst = output;
|
||||
}
|
||||
|
||||
HTTP_Response *responseA = responses[0];
|
||||
HTTP_Response *responseB = responses[1];
|
||||
|
||||
// ... process responses ...
|
||||
|
||||
http_response_free(responseA);
|
||||
http_response_free(responseB);
|
||||
http_client_free(client);
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <chttp.h>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2) {
|
||||
printf("Usage: %s <url1> [url2 ...]\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
http_global_init();
|
||||
HTTP_Client *client = http_client_init();
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
HTTP_RequestBuilder builder;
|
||||
if (http_client_get_builder(client, &builder) < 0) {
|
||||
printf("request creation error\n");
|
||||
return -1;
|
||||
}
|
||||
http_request_builder_line(builder, HTTP_METHOD_GET, (HTTP_String) { argv[i], strlen(argv[i]) });
|
||||
http_request_builder_submit(builder);
|
||||
printf("request submitted\n");
|
||||
}
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
|
||||
HTTP_Response *res;
|
||||
if (http_client_wait(client, &res, NULL) < 0) {
|
||||
printf("request wait error\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("Status: %d\n", res->status);
|
||||
printf("Body: %.*s\n", HTTP_UNPACK(res->body));
|
||||
|
||||
http_response_free(res);
|
||||
}
|
||||
|
||||
http_client_free(client);
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <chttp.h>
|
||||
|
||||
#define BLK "\e[0;30m"
|
||||
#define RED "\e[0;31m"
|
||||
#define GRN "\e[0;32m"
|
||||
#define YEL "\e[0;33m"
|
||||
#define BLU "\e[0;34m"
|
||||
#define MAG "\e[0;35m"
|
||||
#define CYN "\e[0;36m"
|
||||
#define WHT "\e[0;37m"
|
||||
#define RST "\e[0m"
|
||||
|
||||
HTTP_String *already_crawled = NULL;
|
||||
int num_already_crawled = 0;
|
||||
int cap_already_crawled = 0;
|
||||
|
||||
HTTP_String copystr(HTTP_String str)
|
||||
{
|
||||
char *copy = malloc(str.len);
|
||||
if (copy == NULL)
|
||||
abort();
|
||||
memcpy(copy, str.ptr, str.len);
|
||||
return (HTTP_String) { copy, str.len };
|
||||
}
|
||||
|
||||
int normalize_url(HTTP_String url, char *dst, int max)
|
||||
{
|
||||
HTTP_URL parsed_url;
|
||||
if (http_parse_url(url.ptr, url.len, &parsed_url) <= 0)
|
||||
return -1;
|
||||
|
||||
int len = snprintf(dst, max, "http://%.*s%.*s",
|
||||
(int) parsed_url.authority.host.text.len,
|
||||
parsed_url.authority.host.text.ptr,
|
||||
(int) parsed_url.path.len,
|
||||
parsed_url.path.ptr
|
||||
);
|
||||
if (len < 0 || len >= max)
|
||||
return -1;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void add_to_crawled_list(HTTP_String url)
|
||||
{
|
||||
if (num_already_crawled == cap_already_crawled) {
|
||||
|
||||
int new_cap = 2 * cap_already_crawled;
|
||||
if (new_cap == 0)
|
||||
new_cap = 8;
|
||||
|
||||
HTTP_String *new_ptr = malloc(new_cap * sizeof(HTTP_String));
|
||||
if (new_ptr == NULL)
|
||||
abort();
|
||||
|
||||
if (cap_already_crawled > 0) {
|
||||
for (int i = 0; i < num_already_crawled; i++)
|
||||
new_ptr[i] = already_crawled[i];
|
||||
free(already_crawled);
|
||||
}
|
||||
|
||||
already_crawled = new_ptr;
|
||||
cap_already_crawled = new_cap;
|
||||
}
|
||||
|
||||
char buf[1<<10];
|
||||
int len = normalize_url(url, buf, (int) sizeof(buf));
|
||||
if (len < 0) return;
|
||||
|
||||
already_crawled[num_already_crawled++] = copystr((HTTP_String) { buf, len });
|
||||
}
|
||||
|
||||
bool is_already_crawled(HTTP_String url)
|
||||
{
|
||||
char buf[1<<10];
|
||||
int len = normalize_url(url, buf, (int) sizeof(buf));
|
||||
if (len < 0 || len >= (int) sizeof(buf))
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < num_already_crawled; i++)
|
||||
if (http_streq(already_crawled[i], (HTTP_String) { buf, len }))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
HTTP_String next_link(HTTP_String src, int *pcur)
|
||||
{
|
||||
int cur = *pcur;
|
||||
|
||||
for (;;) {
|
||||
|
||||
while (cur < src.len && src.ptr[cur] != 'h')
|
||||
cur++;
|
||||
|
||||
if (cur == src.len)
|
||||
break;
|
||||
|
||||
int off = cur;
|
||||
|
||||
HTTP_URL parsed_url;
|
||||
int len = http_parse_url(src.ptr + cur, src.len - cur, &parsed_url);
|
||||
if (len <= 0) {
|
||||
cur++;
|
||||
continue;
|
||||
}
|
||||
|
||||
cur += len;
|
||||
|
||||
if (!http_streq(parsed_url.scheme, HTTP_STR("http")) &&
|
||||
!http_streq(parsed_url.scheme, HTTP_STR("https")))
|
||||
continue;
|
||||
|
||||
*pcur = cur;
|
||||
return (HTTP_String) { src.ptr + off, len };
|
||||
}
|
||||
|
||||
*pcur = cur;
|
||||
return (HTTP_String) { NULL, 0 };
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
http_global_init();
|
||||
|
||||
if (argc < 2) {
|
||||
printf("Usage: %s <URL>\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
HTTP_String start_url = { argv[1], strlen(argv[1]) };
|
||||
|
||||
HTTP_Client *client = http_client_init();
|
||||
if (client == NULL) {
|
||||
printf("Couldn't initialize HTTP client object\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
HTTP_RequestBuilder builder;
|
||||
int ret = http_client_get_builder(client, &builder);
|
||||
if (ret < 0) {
|
||||
printf("Couldn't start request\n");
|
||||
http_client_free(client);
|
||||
return -1;
|
||||
}
|
||||
http_request_builder_line(builder, HTTP_METHOD_GET, start_url);
|
||||
http_request_builder_header(builder, HTTP_STR("User-Agent: Simple crawler"));
|
||||
http_request_builder_submit(builder);
|
||||
|
||||
for (;;) {
|
||||
|
||||
HTTP_Response *res;
|
||||
ret = http_client_wait(client, &res, NULL);
|
||||
if (ret < 0) {
|
||||
// TODO
|
||||
return -1;
|
||||
}
|
||||
if (res == NULL)
|
||||
continue;
|
||||
|
||||
HTTP_String body = res->body;
|
||||
|
||||
int cursor = 0;
|
||||
for (;;) {
|
||||
|
||||
HTTP_String url = next_link(body, &cursor);
|
||||
if (url.len == 0) break;
|
||||
|
||||
if (is_already_crawled(url)) {
|
||||
printf("Ignoring " RED "%.*s" RST "\n", HTTP_UNPACK(url));
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("Fetching " GRN "%.*s" RST "\n", HTTP_UNPACK(url));
|
||||
add_to_crawled_list(url);
|
||||
|
||||
HTTP_RequestBuilder builder;
|
||||
ret = http_client_get_builder(client, &builder);
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
http_request_builder_line(builder, HTTP_METHOD_GET, url);
|
||||
http_request_builder_header(builder, HTTP_STR("User-Agent: Simple crawler"));
|
||||
http_request_builder_submit(builder);
|
||||
}
|
||||
}
|
||||
|
||||
http_client_free(client);
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2ipdef.h>
|
||||
#include <ws2tcpip.h>
|
||||
#define CLOSE_SOCKET closesocket
|
||||
#else
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#define SOCKET int
|
||||
#define INVALID_SOCKET -1
|
||||
#define CLOSE_SOCKET close
|
||||
#endif
|
||||
|
||||
#include <chttp.h>
|
||||
|
||||
// This example showcases how to use the engine interface
|
||||
// to build a blocking HTTP server that works on Windows
|
||||
// and Linux.
|
||||
|
||||
// Callback used by the engine to manage dynamic memory
|
||||
static void *memfunc(HTTP_MemoryFuncTag tag,
|
||||
void *ptr, int len, void *data)
|
||||
{
|
||||
(void) data;
|
||||
switch (tag) {
|
||||
|
||||
case HTTP_MEMFUNC_MALLOC:
|
||||
return malloc(len);
|
||||
|
||||
case HTTP_MEMFUNC_FREE:
|
||||
free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void produce_response(HTTP_Engine *eng)
|
||||
{
|
||||
// All considerations in simple_server.c for how responses
|
||||
// are built also applies here, where http_engine_XXX functions
|
||||
// are used instead of http_response_XXX functions.
|
||||
|
||||
// Set the response status
|
||||
http_engine_status(eng, 200);
|
||||
|
||||
// Set zero or more headers
|
||||
http_engine_header(eng, HTTP_STR("Server: tinyhttp"));
|
||||
|
||||
// Set some bytes in the body
|
||||
http_engine_body(eng, HTTP_STR("Hello, world!"));
|
||||
|
||||
// This is one difference from the http_response_XXX API.
|
||||
// It's possible to write response content directly into
|
||||
// the engine's output buffer. This avoids copies in some
|
||||
// circumstances.
|
||||
|
||||
char msg[] = " What's up??";
|
||||
|
||||
// First, set how many bytes the output buffer will need
|
||||
// to hold at least:
|
||||
http_engine_bodycap(eng, sizeof(msg)-1);
|
||||
|
||||
// Now get the location for the write. The returned pointer
|
||||
// points to a region of size "cap", which equal or greater
|
||||
// to the previously set minimum capacity.
|
||||
int cap;
|
||||
char *dst = http_engine_bodybuf(eng, &cap);
|
||||
|
||||
// If an error occurred internally, the returned pointer will
|
||||
// be NULL and the capacity 0. In this case, you can just skip
|
||||
// this write. The engine will automatically be closed when the
|
||||
// "http_engine_done" function is called.
|
||||
if (dst) {
|
||||
memcpy(dst, msg, sizeof(msg)-1);
|
||||
|
||||
// Tell the engine how many bytes the application wrote to the
|
||||
// provided buffer.
|
||||
http_engine_bodyack(eng, sizeof(msg)-1);
|
||||
}
|
||||
|
||||
// If an error occurs, you can undo all progress and start
|
||||
// from scratch
|
||||
int error = rand() & 1;
|
||||
if (error) {
|
||||
http_engine_undo(eng);
|
||||
http_engine_status(eng, 500);
|
||||
http_engine_done(eng);
|
||||
return;
|
||||
}
|
||||
|
||||
http_engine_done(eng);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
// Interface and port the server will be listening on.
|
||||
char *addr = "127.0.0.1";
|
||||
int port = 8080;
|
||||
|
||||
#ifdef _WIN32
|
||||
WSADATA wd;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wd))
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
// Create the listening socket
|
||||
SOCKET listen_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (listen_fd == INVALID_SOCKET)
|
||||
return -1;
|
||||
|
||||
// Ignore the cooldown time for the bound interface to
|
||||
// avoid that annoying "address in use" error
|
||||
int reuse = 1;
|
||||
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &reuse, sizeof(reuse));
|
||||
|
||||
// Fill out the address struct
|
||||
struct sockaddr_in bind_buf;
|
||||
{
|
||||
struct in_addr addr_buf;
|
||||
if (inet_pton(AF_INET, addr, &addr_buf) != 1)
|
||||
return -1;
|
||||
|
||||
bind_buf.sin_family = AF_INET;
|
||||
bind_buf.sin_port = htons(port);
|
||||
bind_buf.sin_addr = addr_buf;
|
||||
memset(&bind_buf.sin_zero, 0, sizeof(bind_buf.sin_zero));
|
||||
}
|
||||
|
||||
// Associate the listening socket to the interface
|
||||
if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0)
|
||||
return -1;
|
||||
|
||||
// Allow incoming connections
|
||||
if (listen(listen_fd, 32) < 0)
|
||||
return -1;
|
||||
|
||||
for (;;) {
|
||||
|
||||
printf("Waiting for a connection\n");
|
||||
|
||||
// Get an incoming connection from the kernel
|
||||
SOCKET client_fd = accept(listen_fd, NULL, NULL);
|
||||
if (client_fd == INVALID_SOCKET)
|
||||
continue;
|
||||
|
||||
printf("New connection\n");
|
||||
|
||||
// Initialize the HTTP state machine
|
||||
HTTP_Engine eng;
|
||||
http_engine_init(&eng, 0, memfunc, NULL);
|
||||
|
||||
for (;;) {
|
||||
|
||||
// At this point, the engine can be in one
|
||||
// of four states:
|
||||
// 1) RECV_BUF: The engine is waiting for bytes from the network
|
||||
// 2) SEND_BUF: The engine wants to write bytes from the network
|
||||
// 3) CLOSED: The connection shut down at the HTTP layer
|
||||
// 4) PREP_STATUS: A request was received and the associated response
|
||||
// needs to be generated.
|
||||
HTTP_EngineState state = http_engine_state(&eng);
|
||||
|
||||
if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) {
|
||||
|
||||
produce_response(&eng);
|
||||
|
||||
} else if (state == HTTP_ENGINE_STATE_SERVER_RECV_BUF) {
|
||||
|
||||
printf("Receiving bytes\n");
|
||||
|
||||
// Get a pointer to the engine's input buffer
|
||||
int cap;
|
||||
char *dst = http_engine_recvbuf(&eng, &cap);
|
||||
|
||||
// The application can write up to "cap" bytes
|
||||
// to the "dst" buffer.
|
||||
|
||||
int ret = recv(client_fd, dst, cap, 0);
|
||||
if (ret <= 0) {
|
||||
// If the peer disconnected or an error occurred,
|
||||
// we can "close" the engine. This makes it so any
|
||||
// further operation on the engine will be a no-op
|
||||
// and the next time we query the state we will get
|
||||
// CLOSED.
|
||||
http_engine_close(&eng);
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
printf("Received %d bytes\n", ret);
|
||||
|
||||
// Tell the engine how many bytes we wrote to
|
||||
// the buffer.
|
||||
http_engine_recvack(&eng, ret);
|
||||
|
||||
} else if (state == HTTP_ENGINE_STATE_SERVER_SEND_BUF) {
|
||||
|
||||
// This code is the same as the recv case except
|
||||
// we read from the buffer instead of writing.
|
||||
|
||||
printf("Sending bytes\n");
|
||||
|
||||
int len;
|
||||
char *src = http_engine_sendbuf(&eng, &len);
|
||||
|
||||
// Here "src" points to "len" bytes that need to
|
||||
// be sent over the network.
|
||||
int ret = send(client_fd, src, len, 0);
|
||||
if (ret < 0) {
|
||||
http_engine_close(&eng);
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
printf("Sent %d bytes\n", ret);
|
||||
|
||||
http_engine_sendack(&eng, ret);
|
||||
|
||||
} else {
|
||||
// HTTP_ENGINE_STATE_SERVER_CLOSED
|
||||
printf("HTTP close\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
printf("Closing connection\n");
|
||||
|
||||
http_engine_free(&eng);
|
||||
CLOSE_SOCKET(client_fd);
|
||||
}
|
||||
|
||||
CLOSE_SOCKET(listen_fd);
|
||||
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <mswsock.h>
|
||||
#include <chttp.h>
|
||||
|
||||
#define MAX_CLIENTS (1<<10)
|
||||
|
||||
// Callback used by the engine to manage dynamic memory
|
||||
static void *memfunc(HTTP_MemoryFuncTag tag,
|
||||
void *ptr, int len, void *data)
|
||||
{
|
||||
(void) data;
|
||||
switch (tag) {
|
||||
|
||||
case HTTP_MEMFUNC_MALLOC:
|
||||
return malloc(len);
|
||||
|
||||
case HTTP_MEMFUNC_FREE:
|
||||
free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static SOCKET start_accept(LPFN_ACCEPTEX AcceptEx, SOCKET listen_fd, OVERLAPPED *accept_ov, char *buf, int bufsize)
|
||||
{
|
||||
SOCKET accept_target = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (accept_target == INVALID_SOCKET)
|
||||
return INVALID_SOCKET;
|
||||
|
||||
unsigned long num;
|
||||
memset(accept_ov, 0, sizeof(OVERLAPPED));
|
||||
BOOL ok = AcceptEx(listen_fd, accept_target, buf, bufsize - ((sizeof(struct sockaddr_in) + 16) * 2),
|
||||
sizeof(struct sockaddr_in) + 16,
|
||||
sizeof(struct sockaddr_in) + 16,
|
||||
&num, accept_ov);
|
||||
if (!ok && WSAGetLastError() != ERROR_IO_PENDING) {
|
||||
closesocket(accept_target);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
return accept_target;
|
||||
}
|
||||
|
||||
static void start_recv_or_send(SOCKET sock, OVERLAPPED *recv_ov, OVERLAPPED *send_ov, HTTP_Engine *eng)
|
||||
{
|
||||
HTTP_EngineState state = http_engine_state(eng);
|
||||
|
||||
if (state == HTTP_ENGINE_STATE_SERVER_RECV_BUF) {
|
||||
|
||||
int cap;
|
||||
char *dst = http_engine_recvbuf(eng, &cap);
|
||||
|
||||
memset(recv_ov, 0, sizeof(OVERLAPPED));
|
||||
int ok = ReadFile((HANDLE) sock, dst, cap, NULL, recv_ov);
|
||||
if (!ok && GetLastError() != ERROR_IO_PENDING)
|
||||
http_engine_close(eng);
|
||||
|
||||
} else if (state == HTTP_ENGINE_STATE_SERVER_SEND_BUF) {
|
||||
|
||||
int len;
|
||||
char *src = http_engine_sendbuf(eng, &len);
|
||||
|
||||
memset(send_ov, 0, sizeof(OVERLAPPED));
|
||||
int ok = WriteFile((HANDLE) sock, src, len, NULL, send_ov);
|
||||
if (!ok && GetLastError() != ERROR_IO_PENDING)
|
||||
http_engine_close(eng);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
// Interface and port the server will be listening on.
|
||||
char *addr = "127.0.0.1";
|
||||
int port = 8080;
|
||||
|
||||
// Initialize the winsock subsystem
|
||||
WSADATA wd;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wd))
|
||||
return -1;
|
||||
|
||||
// Create the I/O completion port object
|
||||
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
|
||||
if (iocp == INVALID_HANDLE_VALUE)
|
||||
return -1;
|
||||
|
||||
// Create the listening socket
|
||||
SOCKET listen_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (listen_fd == INVALID_SOCKET)
|
||||
return -1;
|
||||
|
||||
// Register the socket into the IOCP
|
||||
if (CreateIoCompletionPort((HANDLE) listen_fd, iocp, 0, 0) == NULL)
|
||||
return -1;
|
||||
|
||||
// Ignore the cooldown time for the bound interface to
|
||||
// avoid that annoying "address in use" error
|
||||
int reuse = 1;
|
||||
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &reuse, sizeof(reuse));
|
||||
|
||||
// Fill out the address struct
|
||||
struct sockaddr_in bind_buf;
|
||||
{
|
||||
struct in_addr addr_buf;
|
||||
if (inet_pton(AF_INET, addr, &addr_buf) != 1)
|
||||
return -1;
|
||||
|
||||
bind_buf.sin_family = AF_INET;
|
||||
bind_buf.sin_port = htons(port);
|
||||
bind_buf.sin_addr = addr_buf;
|
||||
memset(&bind_buf.sin_zero, 0, sizeof(bind_buf.sin_zero));
|
||||
}
|
||||
|
||||
// Associate the listening socket to the interface
|
||||
if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0)
|
||||
return -1;
|
||||
|
||||
// Allow incoming connections
|
||||
if (listen(listen_fd, 32) < 0)
|
||||
return -1;
|
||||
|
||||
// Get the AcceptEx function pointer
|
||||
LPFN_ACCEPTEX AcceptEx = NULL;
|
||||
GUID GuidAcceptEx = WSAID_ACCEPTEX;
|
||||
unsigned long num;
|
||||
int ret = WSAIoctl(listen_fd,
|
||||
SIO_GET_EXTENSION_FUNCTION_POINTER,
|
||||
&GuidAcceptEx, sizeof(GuidAcceptEx),
|
||||
&AcceptEx, sizeof(AcceptEx),
|
||||
&num, NULL, NULL);
|
||||
if (ret == SOCKET_ERROR)
|
||||
return -1;
|
||||
|
||||
OVERLAPPED accept_ov;
|
||||
char accept_buf[2 * (sizeof(struct sockaddr_in) + 16)];
|
||||
SOCKET accept_target = start_accept(AcceptEx, listen_fd, &accept_ov, accept_buf, (int) sizeof(accept_buf));
|
||||
if (accept_target == INVALID_SOCKET)
|
||||
return -1;
|
||||
|
||||
OVERLAPPED recv_overlapped[MAX_CLIENTS];
|
||||
OVERLAPPED send_overlapped[MAX_CLIENTS];
|
||||
SOCKET sockets[MAX_CLIENTS];
|
||||
HTTP_Engine engs[MAX_CLIENTS];
|
||||
|
||||
for (int i = 0; i < MAX_CLIENTS; i++)
|
||||
sockets[i] = INVALID_SOCKET;
|
||||
|
||||
for (;;) {
|
||||
DWORD timeout = INFINITE;
|
||||
|
||||
DWORD transferred;
|
||||
ULONG_PTR key;
|
||||
OVERLAPPED *overlapped;
|
||||
BOOL result = GetQueuedCompletionStatus(iocp, &transferred, &key, &overlapped, timeout);
|
||||
if (!result && overlapped == NULL) {
|
||||
if (GetLastError() == WAIT_TIMEOUT)
|
||||
continue; // Go back to waiting
|
||||
return -1; // Error
|
||||
}
|
||||
|
||||
if (overlapped == &accept_ov) {
|
||||
// A new client connected
|
||||
|
||||
int i = 0;
|
||||
while (i < MAX_CLIENTS && sockets[i] != INVALID_SOCKET)
|
||||
i++;
|
||||
|
||||
if (i == MAX_CLIENTS)
|
||||
closesocket(accept_target); // Server limit reached
|
||||
else {
|
||||
|
||||
sockets[i] = accept_target;
|
||||
http_engine_init(&engs[i], 0, memfunc, NULL);
|
||||
|
||||
// Register the socket into the IOCP
|
||||
if (CreateIoCompletionPort((HANDLE) sockets[i], iocp, i, 0) == NULL)
|
||||
return -1;
|
||||
|
||||
start_recv_or_send(sockets[i], &recv_overlapped[i], &send_overlapped[i], &engs[i]);
|
||||
|
||||
// Check that the recv or send operation was started.
|
||||
// If now, remove the connection
|
||||
if (http_engine_state(&engs[i]) == HTTP_ENGINE_STATE_CLIENT_CLOSED) {
|
||||
closesocket(sockets[i]);
|
||||
sockets[i] = INVALID_SOCKET;
|
||||
http_engine_free(&engs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
accept_target = start_accept(AcceptEx, listen_fd, &accept_ov, accept_buf, (int) sizeof(accept_buf));
|
||||
if (accept_target == INVALID_SOCKET)
|
||||
return -1;
|
||||
|
||||
// Go back to waiting
|
||||
continue;
|
||||
}
|
||||
|
||||
// Complete the current operation
|
||||
if (0) {}
|
||||
else if (overlapped == &recv_overlapped[key]) http_engine_recvack(&engs[key], transferred);
|
||||
else if (overlapped == &send_overlapped[key]) http_engine_sendack(&engs[key], transferred);
|
||||
|
||||
if (http_engine_state(&engs[key]) == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) {
|
||||
|
||||
// See blocking_server_with_engine.c to learn about how to
|
||||
// build a response
|
||||
http_engine_status(&engs[key], 200);
|
||||
http_engine_body(&engs[key], HTTP_STR("Hello, world!"));
|
||||
http_engine_done(&engs[key]);
|
||||
}
|
||||
|
||||
start_recv_or_send(sockets[key],
|
||||
&recv_overlapped[key],
|
||||
&send_overlapped[key],
|
||||
&engs[key]);
|
||||
|
||||
if (http_engine_state(&engs[key]) == HTTP_ENGINE_STATE_SERVER_CLOSED) {
|
||||
http_engine_free(&engs[key]);
|
||||
closesocket(sockets[key]);
|
||||
sockets[key] = INVALID_SOCKET;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("This example only works on windows!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,99 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <chttp.h>
|
||||
|
||||
// This example shows how to set up a basic HTTP server
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_init();
|
||||
|
||||
// Choose the interface to listen on and the port.
|
||||
// Currently, servers can only bind to IPv4 addresses.
|
||||
HTTP_String addr = HTTP_STR("127.0.0.1");
|
||||
uint16_t port = 8080;
|
||||
|
||||
bool all_interfaces = false;
|
||||
|
||||
// If you want to bind to all interfaces, you can
|
||||
// set the address to an empty string.
|
||||
if (all_interfaces)
|
||||
addr = HTTP_STR("");
|
||||
|
||||
// Instanciate the HTTP server object
|
||||
HTTP_Server *server = http_server_init(addr, port);
|
||||
if (server == NULL)
|
||||
return -1;
|
||||
|
||||
// Now we loop forever. Every iteration will serve
|
||||
// a single HTTP request
|
||||
for (;;) {
|
||||
|
||||
HTTP_Request *req;
|
||||
HTTP_ResponseBuilder builder;
|
||||
|
||||
// Block until a request is available
|
||||
int ret = http_server_wait(server, &req, &builder);
|
||||
|
||||
// The wait functions returns 0 on success and -1
|
||||
// on error. By "error" I mean an unrecoverable
|
||||
// condition. There is no other option than kill
|
||||
// the process.
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
// The request information is accessible from
|
||||
// the [req] variable. Most fields in the request
|
||||
// struct are reference to the original request
|
||||
// string. They use type HTTP_String and are not
|
||||
// null-terminated. This means you'll have to make
|
||||
// sure to express the length when interacting with
|
||||
// libc:
|
||||
HTTP_String path = req->url.path;
|
||||
printf("requested path [%.*s]\n", HTTP_UNPACK(path));
|
||||
|
||||
// To find a specific header value, you can either
|
||||
// iterate over the [req->headers] array or use
|
||||
// a helper function. Note that this compares header
|
||||
// names case-insensitively.
|
||||
int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Some-Header-Name"));
|
||||
if (idx == -1) {
|
||||
// Header wasn't found
|
||||
} else {
|
||||
// Found
|
||||
HTTP_String value = req->headers[idx].value;
|
||||
printf("Header has value [%.*s]\n", HTTP_UNPACK(value));
|
||||
}
|
||||
|
||||
// To create a response, you will need to specify
|
||||
// status code, headers, and content in the proper
|
||||
// order.
|
||||
|
||||
// First the status code
|
||||
http_response_builder_status(builder, 200);
|
||||
|
||||
// Then zero or more headers
|
||||
http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain"));
|
||||
|
||||
// Then you can write zero or more chunks of the response body
|
||||
http_response_builder_body(builder, HTTP_STR("Hello"));
|
||||
http_response_builder_body(builder, HTTP_STR(", world!"));
|
||||
|
||||
// Then, mark the request as complete (Very important or the server will hang!)
|
||||
http_response_builder_done(builder);
|
||||
|
||||
// Note that none of the http_response_builder_* functions return errors.
|
||||
// This is by design to simplify user endpoint code. If at any point
|
||||
// something goes wrong, the server will send a code 4xx or 5xx to
|
||||
// the client or abort the TCP connection entirely.
|
||||
}
|
||||
|
||||
// This program will loop forever, but if you write
|
||||
// your server in a way to exit gracefully, this is
|
||||
// you the server object is freed:
|
||||
http_server_free(server);
|
||||
http_global_free();
|
||||
|
||||
// Have fun. Bye!
|
||||
return 0;
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
#include <string.h>
|
||||
#include <chttp.h>
|
||||
|
||||
// This example shows how to generate response bodies
|
||||
// using the zero-copy API.
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_init();
|
||||
|
||||
// All the setup is identical to the previous example.
|
||||
// The only thing that changes where "http_response_builder_body"
|
||||
// is called.
|
||||
|
||||
HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080);
|
||||
if (server == NULL)
|
||||
return -1;
|
||||
|
||||
for (;;) {
|
||||
|
||||
HTTP_Request *req;
|
||||
HTTP_ResponseBuilder builder;
|
||||
|
||||
int ret = http_server_wait(server, &req, &builder);
|
||||
if (ret < 0) return -1;
|
||||
|
||||
http_response_builder_status(builder, 200);
|
||||
http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain"));
|
||||
|
||||
// The previous example used the *_body function to
|
||||
// write the response body in chunks:
|
||||
//
|
||||
// http_response_builder_body(builder, HTTP_STR("Hello"));
|
||||
// http_response_builder_body(builder, HTTP_STR(", world!"));
|
||||
//
|
||||
// This function reads from an user buffer and copies
|
||||
// the data in the connection's output buffer. If the
|
||||
// data is not in a contiguous region that's fine as
|
||||
// the function can be called repeatedly on separate
|
||||
// chunks.
|
||||
//
|
||||
// This function assumes the user is holding in memory
|
||||
// the data to be sent beforehand, but this may not
|
||||
// be true. If for instance the data comes from a file,
|
||||
// the user will need to read from the file, copy in
|
||||
// memory and then write to the response body.
|
||||
//
|
||||
// The zero-copy API allows copying directly from the
|
||||
// source of the data (such as the read() system call
|
||||
// on a file descriptor) to the server's output buffer
|
||||
|
||||
char example_data[] = "I'm some example data!";
|
||||
int example_data_len = sizeof(example_data)-1;
|
||||
|
||||
// Tell the server how much data we are going to write
|
||||
http_response_builder_bodycap(builder, example_data_len);
|
||||
|
||||
int cap;
|
||||
char *dst;
|
||||
|
||||
// Get a pointer to the server's output buffer. The
|
||||
// output parameter [cap] is the capacity of the region
|
||||
// and is equal or larger than the data we requested
|
||||
// with *_bodycap
|
||||
dst = http_response_builder_bodybuf(builder, &cap);
|
||||
|
||||
// Write the data directly into the output buffer. In
|
||||
// this example we are copying from memory, but you could
|
||||
// read from a file or a socket
|
||||
if (dst) {
|
||||
memcpy(dst, example_data, example_data_len);
|
||||
}
|
||||
|
||||
// Tell the server how much bytes we have written to
|
||||
// the provided region.
|
||||
http_response_builder_bodyack(builder, example_data_len);
|
||||
|
||||
// The reason we had to guard the [memcpy] by checking the
|
||||
// [dst] pointer is that if an error occurred internally
|
||||
// then *_bodybuf will return NULL. This will cause the
|
||||
// server to either return an internally generated error
|
||||
// response or drop the connection. The correct thing to
|
||||
// do in that situation is not access the pointer and do
|
||||
// as nothing bad happened.
|
||||
|
||||
// As usual, mark the response as complete
|
||||
http_response_builder_done(builder);
|
||||
|
||||
// If we're being being honest, this is not a zero-copy
|
||||
// interface. It's more like an N-1 copy interface as in
|
||||
// it just avoids one copy from userspace to userspace!
|
||||
}
|
||||
|
||||
http_server_free(server);
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
#include <stddef.h>
|
||||
#include <chttp.h>
|
||||
|
||||
// This example shows how undo a response that is being built
|
||||
// when an error occurs.
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_init();
|
||||
|
||||
HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080);
|
||||
if (server == NULL)
|
||||
return -1;
|
||||
|
||||
for (;;) {
|
||||
|
||||
HTTP_Request *req;
|
||||
HTTP_ResponseBuilder builder;
|
||||
|
||||
int ret = http_server_wait(server, &req, &builder);
|
||||
if (ret < 0) return -1;
|
||||
|
||||
// Say we are building a request..
|
||||
|
||||
http_response_builder_status(builder, 200);
|
||||
http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain"));
|
||||
|
||||
// .. and in the middle of building an error condition
|
||||
// occurs. Maybe a file was missing or an allocation fails.
|
||||
// The proper response in this case would be a code 500
|
||||
// with an error message, but we already wrote the first
|
||||
// part of the response assuming the operation would succede.
|
||||
//
|
||||
// You can use the *_undo function to reset the response
|
||||
// building process
|
||||
|
||||
bool error_occurred = true;
|
||||
if (error_occurred) {
|
||||
|
||||
http_response_builder_undo(builder);
|
||||
|
||||
// Now we are back to setting the status code
|
||||
http_response_builder_status(builder, 500);
|
||||
http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain"));
|
||||
http_response_builder_body(builder, HTTP_STR("An error occurred!"));
|
||||
http_response_builder_done(builder);
|
||||
|
||||
} else {
|
||||
|
||||
// If no error occures, we finish as planned
|
||||
http_response_builder_body(builder, HTTP_STR("Hello, world!"));
|
||||
http_response_builder_done(builder);
|
||||
}
|
||||
}
|
||||
|
||||
http_server_free(server);
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,386 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <chttp.h>
|
||||
|
||||
// !!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //
|
||||
// //
|
||||
// This example is just a proof of concept for now as the library //
|
||||
// still isn't thread-safe. //
|
||||
// //
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //
|
||||
|
||||
// This example shows how to delegate the response creation
|
||||
// process to other threads.
|
||||
//
|
||||
// Your server may have some endpoints that require considerable
|
||||
// computation or may be waiting for some external system to
|
||||
// complete. If we used the current pattern we've been using for
|
||||
// generating requests, following request will have to wait until
|
||||
// this processing has concluded.
|
||||
//
|
||||
// One solution for this situation is to create a separate thread
|
||||
// to do the waiting or processing. When a request is received
|
||||
// that requires processing, it is passed to the second thread.
|
||||
// In the mean time, the main thread can process the next request.
|
||||
// When the thread has finished, it can just call the usual
|
||||
// functions to produce a response.
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
typedef void* Thread;
|
||||
typedef unsigned long ThreadReturn;
|
||||
typedef CRITICAL_SECTION Mutex;
|
||||
typedef CONDITION_VARIABLE Condvar;
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <pthread.h>
|
||||
typedef pthread_t Thread;
|
||||
typedef void* ThreadReturn;
|
||||
typedef pthread_mutex_t Mutex;
|
||||
typedef pthread_cond_t Condvar;
|
||||
#endif
|
||||
|
||||
// The following types are used to describe a job the worker
|
||||
// needs to work on.
|
||||
typedef enum {
|
||||
|
||||
// Special value used to tell the worker the program is terminating
|
||||
NO_JOB,
|
||||
|
||||
// We assume jobs may be of two different types we call A and B
|
||||
JOB_A,
|
||||
JOB_B,
|
||||
|
||||
} JobType;
|
||||
|
||||
typedef struct {
|
||||
JobType type;
|
||||
HTTP_ResponseBuilder builder;
|
||||
} Job;
|
||||
|
||||
// Maximum number of jobs that can be buffered at once
|
||||
#define MAX_JOBS 100
|
||||
|
||||
void init_job_queue(void);
|
||||
void free_job_queue(void);
|
||||
|
||||
// This function pops an item from the job queue. If the
|
||||
// queue is empty, the thread will block until one is
|
||||
// available.
|
||||
Job pop_job(void);
|
||||
|
||||
// This function adds a job to the queue. The block argument
|
||||
// changes the behavior when the queue is full and there is
|
||||
// no space for a new job. If the block argument is true and
|
||||
// there is no space, the thread waits. If the argument is
|
||||
// false the function exits immediately by returning false
|
||||
// with no new job pushed.
|
||||
bool push_job(Job job, bool block);
|
||||
|
||||
void thread_create(Thread *thread, void *arg, ThreadReturn (*func)(void*));
|
||||
ThreadReturn thread_join(Thread thread);
|
||||
|
||||
void mutex_init(Mutex *mutex);
|
||||
void mutex_free(Mutex *mutex);
|
||||
void mutex_lock(Mutex *mutex);
|
||||
void mutex_unlock(Mutex *mutex);
|
||||
|
||||
void condvar_init(Condvar *condvar);
|
||||
void condvar_free(Condvar *condvar);
|
||||
void condvar_wait(Condvar *condvar, Mutex *mutex);
|
||||
void condvar_signal(Condvar *condvar);
|
||||
|
||||
ThreadReturn worker(void*)
|
||||
{
|
||||
for (bool exit = false; !exit; ) {
|
||||
|
||||
Job job = pop_job();
|
||||
|
||||
switch (job.type) {
|
||||
|
||||
case NO_JOB:
|
||||
exit = true;
|
||||
break;
|
||||
|
||||
case JOB_A:
|
||||
http_response_builder_status(job.builder, 200);
|
||||
http_response_builder_body(job.builder, HTTP_STR("Job A completed"));
|
||||
http_response_builder_done(job.builder);
|
||||
break;
|
||||
|
||||
case JOB_B:
|
||||
http_response_builder_status(job.builder, 200);
|
||||
http_response_builder_body(job.builder, HTTP_STR("Job B completed"));
|
||||
http_response_builder_done(job.builder);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_free();
|
||||
init_job_queue();
|
||||
|
||||
HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080);
|
||||
if (server == NULL)
|
||||
return -1;
|
||||
|
||||
Thread worker_id;
|
||||
thread_create(&worker_id, NULL, worker);
|
||||
|
||||
for (;;) {
|
||||
|
||||
HTTP_Request *req;
|
||||
HTTP_ResponseBuilder builder;
|
||||
|
||||
int ret = http_server_wait(server, &req, &builder);
|
||||
if (ret < 0) return -1;
|
||||
|
||||
if (http_streq(req->url.path, HTTP_STR("/endpoint_A"))) {
|
||||
|
||||
// Endpoint A sends the job to the worker.
|
||||
// If too many jobs are queued, it blocks
|
||||
|
||||
Job job;
|
||||
job.type = JOB_A;
|
||||
job.builder = builder;
|
||||
push_job(job, true);
|
||||
|
||||
} else if (http_streq(req->url.path, HTTP_STR("/endpoint_B"))) {
|
||||
|
||||
// Endpoint B sends the job to the worker
|
||||
// but fails if the queue is full, in which
|
||||
// case the "503 Service Unavailable" response
|
||||
// is generated.
|
||||
|
||||
Job job;
|
||||
job.type = JOB_B;
|
||||
job.builder = builder;
|
||||
if (!push_job(job, false)) {
|
||||
http_response_builder_status(builder, 503);
|
||||
http_response_builder_done(builder);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Other endpoints may resolve immediately
|
||||
|
||||
http_response_builder_status(builder, 404);
|
||||
http_response_builder_done(builder);
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the worker by sending an empty job
|
||||
Job job;
|
||||
job.type = NO_JOB;
|
||||
push_job(job, true);
|
||||
thread_join(worker_id);
|
||||
|
||||
http_server_free(server);
|
||||
free_job_queue();
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////
|
||||
|
||||
// This is a pretty standard condition variable-based
|
||||
// producer-consumer queue. In this example we are using
|
||||
// one worker, but we could easily have more than that.
|
||||
|
||||
Job queue[MAX_JOBS];
|
||||
int queue_head = 0;
|
||||
int queue_count = 0;
|
||||
Mutex queue_lock;
|
||||
Condvar queue_consume_event;
|
||||
Condvar queue_produce_event;
|
||||
|
||||
void init_job_queue(void)
|
||||
{
|
||||
mutex_init(&queue_lock);
|
||||
condvar_init(&queue_consume_event);
|
||||
condvar_init(&queue_produce_event);
|
||||
}
|
||||
|
||||
void free_job_queue(void)
|
||||
{
|
||||
condvar_free(&queue_produce_event);
|
||||
condvar_free(&queue_consume_event);
|
||||
mutex_free(&queue_lock);
|
||||
}
|
||||
|
||||
Job pop_job(void)
|
||||
{
|
||||
mutex_lock(&queue_lock);
|
||||
|
||||
while (queue_count == 0)
|
||||
condvar_wait(&queue_produce_event, &queue_lock);
|
||||
|
||||
Job job = queue[queue_head];
|
||||
queue_head = (queue_head + 1) % MAX_JOBS;
|
||||
queue_count--;
|
||||
|
||||
condvar_signal(&queue_consume_event);
|
||||
mutex_unlock(&queue_lock);
|
||||
return job;
|
||||
}
|
||||
|
||||
bool push_job(Job job, bool block)
|
||||
{
|
||||
mutex_lock(&queue_lock);
|
||||
if (queue_count == 0) {
|
||||
|
||||
if (!block) {
|
||||
mutex_unlock(&queue_lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
do
|
||||
condvar_wait(&queue_consume_event, &queue_lock);
|
||||
while (queue_count == 0);
|
||||
}
|
||||
|
||||
int tail = (queue_head + queue_count) % MAX_JOBS;
|
||||
queue[tail] = job;
|
||||
queue_count++;
|
||||
|
||||
condvar_signal(&queue_produce_event);
|
||||
mutex_unlock(&queue_lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////
|
||||
|
||||
void thread_create(Thread *thread, void *arg, ThreadReturn (*func)(void*))
|
||||
{
|
||||
#ifdef _WIN32
|
||||
Thread thread_ = CreateThread(NULL, 0, func, arg, 0, NULL);
|
||||
if (thread_ == INVALID_HANDLE_VALUE)
|
||||
abort();
|
||||
*thread = thread_;
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
int ret = pthread_create(thread, NULL, func, arg);
|
||||
if (ret) abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
ThreadReturn thread_join(Thread thread)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
ThreadReturn result;
|
||||
WaitForSingleObject(thread, INFINITE);
|
||||
if (!GetExitCodeThread(thread, &result))
|
||||
abort();
|
||||
CloseHandle(thread);
|
||||
return result;
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
ThreadReturn result;
|
||||
int ret = pthread_join(thread, &result);
|
||||
if (ret) abort();
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
void mutex_init(Mutex *mutex)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
InitializeCriticalSection(mutex);
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
if (pthread_mutex_init(mutex, NULL))
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
void mutex_free(Mutex *mutex)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DeleteCriticalSection(mutex);
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
if (pthread_mutex_destroy(mutex))
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
void mutex_lock(Mutex *mutex)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
EnterCriticalSection(mutex);
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
if (pthread_mutex_lock(mutex))
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
void mutex_unlock(Mutex *mutex)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
LeaveCriticalSection(mutex);
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
if (pthread_mutex_unlock(mutex))
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
void condvar_init(Condvar *condvar)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
InitializeConditionVariable(condvar);
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
if (pthread_cond_init(condvar, NULL))
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
void condvar_free(Condvar *condvar)
|
||||
{
|
||||
#ifdef __linux__
|
||||
if (pthread_cond_destroy(condvar))
|
||||
abort();
|
||||
#else
|
||||
(void) condvar;
|
||||
#endif
|
||||
}
|
||||
|
||||
void condvar_wait(Condvar *condvar, Mutex *mutex)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!SleepConditionVariableCS(condvar, mutex, INFINITE))
|
||||
abort();
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
int err = pthread_cond_wait(condvar, mutex);
|
||||
if (err) abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
void condvar_signal(Condvar *condvar)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WakeConditionVariable(condvar);
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
if (pthread_cond_signal(condvar))
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
|
||||
int main(void) {}
|
||||
@@ -1,78 +0,0 @@
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <chttp.h>
|
||||
|
||||
// This example shows how to set up an HTTPS (HTTP over TLS)
|
||||
// server.
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_init();
|
||||
|
||||
// To setup an HTTPS server, we need to use the *_ex variant
|
||||
// of the server initialization function as it offers more
|
||||
// control. Server objects can serve HTTP traffic, HTTPS
|
||||
// traffic, or both at the same time. The init_ex function
|
||||
// allows us to control this behavior.
|
||||
|
||||
// The first argument is the local interface address. It
|
||||
// works just as the other examples but is shared between
|
||||
// HTTP and HTTPS. Then come the HTTP port and HTTPS port
|
||||
// arguments. If you want to disable HTTP or HTTPS you can
|
||||
// pass zero to its port argument. If the HTTPS port is
|
||||
// not zero, you need to pass the file names of the server's
|
||||
// certificate and private key.
|
||||
HTTP_Server *server = http_server_init_ex(
|
||||
HTTP_STR("127.0.0.1"), // HTTP and HTTPS port
|
||||
8080, // HTTP port
|
||||
8443, // HTTPS port
|
||||
HTTP_STR("cert.pem"),
|
||||
HTTP_STR("privkey.pem")
|
||||
);
|
||||
if (server == NULL)
|
||||
return -1;
|
||||
|
||||
// Just to be clear, to initialize a plain HTTP server
|
||||
// using the *_ex function we would do this:
|
||||
//
|
||||
// HTTP_Server *server = http_server_init_ex(
|
||||
// HTTP_STR("127.0.0.1"), // HTTP and HTTPS port
|
||||
// 8080, // HTTP port
|
||||
// 0, // HTTPS disabled
|
||||
// HTTP_STR(""), // ignore
|
||||
// HTTP_STR("") // ignore
|
||||
// );
|
||||
//
|
||||
// and if we wanted and HTTPS-only server we would
|
||||
// do this:
|
||||
//
|
||||
// HTTP_Server *server = http_server_init_ex(
|
||||
// HTTP_STR("127.0.0.1"), // HTTP and HTTPS port
|
||||
// 0, // HTTP disabled
|
||||
// 8443, // HTTPS port
|
||||
// HTTP_STR("cert.pem"),
|
||||
// HTTP_STR("privkey.pem")
|
||||
// );
|
||||
|
||||
// Everything else is identical to the simple HTTP server
|
||||
// example.
|
||||
|
||||
for (;;) {
|
||||
|
||||
HTTP_Request *req;
|
||||
HTTP_ResponseBuilder builder;
|
||||
|
||||
int ret = http_server_wait(server, &req, &builder);
|
||||
if (ret < 0) return -1;
|
||||
|
||||
http_response_builder_status(builder, 200);
|
||||
http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain"));
|
||||
http_response_builder_body(builder, HTTP_STR("Hello"));
|
||||
http_response_builder_body(builder, HTTP_STR(", world!"));
|
||||
http_response_builder_done(builder);
|
||||
}
|
||||
|
||||
http_server_free(server);
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
#include <stddef.h>
|
||||
#include <chttp.h>
|
||||
|
||||
// This is an example of how to serve different websites
|
||||
// over a single HTTPS server instance.
|
||||
|
||||
int setup_test_certificates(void);
|
||||
|
||||
int main(void)
|
||||
{
|
||||
http_global_init();
|
||||
|
||||
// To test this program you need to add the following
|
||||
// lines to your hosts file:
|
||||
//
|
||||
// 127.0.0.1 websiteA.com
|
||||
// 127.0.0.1 websiteB.com
|
||||
// 127.0.0.1 websiteC.com
|
||||
//
|
||||
// That you can find at /etc/hosts on Linux and
|
||||
// C:\Windows\System32\drivers\etc\hosts on Windows
|
||||
|
||||
// First, create three certificates for the domains:
|
||||
//
|
||||
// websiteA.com
|
||||
// websiteB.com
|
||||
// websiteC.com
|
||||
//
|
||||
// This will create a number of certificate files
|
||||
// and private key files
|
||||
//
|
||||
// websiteA_cert.pem websiteA_key.pem
|
||||
// websiteB_cert.pem websiteB_key.pem
|
||||
// websiteC_cert.pem websiteC_key.pem
|
||||
//
|
||||
// Of course this is just for testing. It is expected
|
||||
// you have your own.
|
||||
int ret = setup_test_certificates();
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
// First, set up an HTTPS server instance with one
|
||||
// of the certificate. This will act as default certificate
|
||||
// when ecrypted connections don't target a specific domain.
|
||||
HTTP_Server *server = http_server_init_ex(
|
||||
HTTP_STR("127.0.0.1"), 8080, 8443,
|
||||
HTTP_STR("websiteA_cert.pem"),
|
||||
HTTP_STR("websiteA_key.pem")
|
||||
);
|
||||
if (server == NULL)
|
||||
return -1;
|
||||
|
||||
// Then we can add an arbitrary number of additional
|
||||
// certificates using the add_website function
|
||||
|
||||
ret = http_server_add_website(server,
|
||||
HTTP_STR("websiteB.com"),
|
||||
HTTP_STR("websiteB_cert.pem"),
|
||||
HTTP_STR("websiteB_key.pem")
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
ret = http_server_add_website(server,
|
||||
HTTP_STR("websiteC.com"),
|
||||
HTTP_STR("websiteC_cert.pem"),
|
||||
HTTP_STR("websiteC_key.pem")
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
// Now the server is ready to accept incoming HTTP
|
||||
// or HTTPS connections.
|
||||
//
|
||||
// Note that the add_website function is only used
|
||||
// to serve the correct certificate to the client.
|
||||
// The HTTP request itself may very well hold a
|
||||
// different domain name in the host header:
|
||||
//
|
||||
// [client] [server]
|
||||
// | |
|
||||
// | TLS hanshake to domain1.com |
|
||||
// | -------------------------------> |
|
||||
// | |
|
||||
// | cert for domain1.com |
|
||||
// | <------------------------------- |
|
||||
// | |
|
||||
// | HTTP request to domain2.com |
|
||||
// | over the encrypted connection |
|
||||
// | established with domain1.com |
|
||||
// | -------------------------------> |
|
||||
// | |
|
||||
// | response as domain2.com |
|
||||
// | <------------------------------- |
|
||||
// | |
|
||||
|
||||
for (;;) {
|
||||
|
||||
HTTP_Request *req;
|
||||
HTTP_ResponseBuilder builder;
|
||||
ret = http_server_wait(server, &req, &builder);
|
||||
if (ret < 0)
|
||||
break;
|
||||
|
||||
if (http_match_host(req, HTTP_STR("websiteB.com"), 8080) ||
|
||||
http_match_host(req, HTTP_STR("websiteB.com"), 8443)) {
|
||||
|
||||
http_response_builder_status(builder, 200);
|
||||
http_response_builder_body(builder, HTTP_STR("Hello from websiteB.com!"));
|
||||
http_response_builder_done(builder);
|
||||
|
||||
} else if (http_match_host(req, HTTP_STR("websiteC.com"), 8080) ||
|
||||
http_match_host(req, HTTP_STR("websiteC.com"), 8443)) {
|
||||
|
||||
http_response_builder_status(builder, 200);
|
||||
http_response_builder_body(builder, HTTP_STR("Hello from websiteC.com!"));
|
||||
http_response_builder_done(builder);
|
||||
|
||||
} else {
|
||||
|
||||
// Serve websiteA.com by default to be consistent
|
||||
// with the certificate setup
|
||||
|
||||
http_response_builder_status(builder, 200);
|
||||
http_response_builder_body(builder, HTTP_STR("Hello from websiteA.com!"));
|
||||
http_response_builder_done(builder);
|
||||
}
|
||||
}
|
||||
|
||||
http_server_free(server);
|
||||
http_global_free();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int setup_test_certificates(void)
|
||||
{
|
||||
int ret = http_create_test_certificate(
|
||||
HTTP_STR("IT"),
|
||||
HTTP_STR("Organization A"),
|
||||
HTTP_STR("websiteA.com"),
|
||||
HTTP_STR("websiteA_cert.pem"),
|
||||
HTTP_STR("websiteA_key.pem")
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
ret = http_create_test_certificate(
|
||||
HTTP_STR("IT"),
|
||||
HTTP_STR("Organization B"),
|
||||
HTTP_STR("websiteB.com"),
|
||||
HTTP_STR("websiteB_cert.pem"),
|
||||
HTTP_STR("websiteB_key.pem")
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
ret = http_create_test_certificate(
|
||||
HTTP_STR("IT"),
|
||||
HTTP_STR("Organization C"),
|
||||
HTTP_STR("websiteC.com"),
|
||||
HTTP_STR("websiteC_cert.pem"),
|
||||
HTTP_STR("websiteC_key.pem")
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user