view seobeo/seobeo.h @ 166:78ea8d5ccc87

[ThirdParty] Added sqlite3 to the third_party.
author MrJuneJune <me@mrjunejune.com>
date Mon, 19 Jan 2026 16:28:34 -0800
parents 058de208e640
children 827c6ac504cd a8976a008a9d
line wrap: on
line source

#ifndef SEOBEO_SERVER_H
#define SEOBEO_SERVER_H

/**
 * Seobeo
 * ------
 *
 * Library for starting TCP, UDP server and serving static file or path.
 */

#include "seobeo/seobeo_internal.h"

#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <fcntl.h>
#include <pthread.h>


#define INITIAL_BUFFER_CAPACITY 4096

// HTTP STATUS CODE
#define HTTP_OK 200
#define HTTP_CREATED 201
#define HTTP_MOVED_PERMANENTLY 301
#define HTTP_FOUND 302
#define HTTP_BAD_REQUEST 400
#define HTTP_UNAUTHORIZED 401
#define HTTP_FORBIDDEN 403
#define HTTP_NOT_FOUND 404
#define HTTP_INTERNAL_ERROR 500

#define CREATE_REDIRECT_HANDLER(name, target_url) \
Seobeo_Request_Entry* GetRedirect##name(Seobeo_Request_Entry *req, Dowa_Arena *arena) \
{ \
  Seobeo_Request_Entry *resp = NULL; \
  Dowa_HashMap_Push_Arena(resp, "status", "301", arena); \
  Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); \
  Dowa_HashMap_Push_Arena(resp, "Body", "", arena); \
  Dowa_HashMap_Push_Arena(resp, "Location", target_url, arena); \
  return resp; \
}

extern volatile sig_atomic_t stop_server;

// --- TCP --- //
// --- Generate a Server Handle. ---//
extern Seobeo_Handle *Seobeo_Stream_Handle_Server_Create(const char *host,  const char* port);
// --- Generate a Client Handle. ---//
extern Seobeo_Handle *Seobeo_Stream_Handle_Client_Create(const char *host,  const char* port, boolean use_tls);
// --- Generate a Client Handle from given Server Handle. ---//
extern Seobeo_Handle *Seobeo_Stream_Handle_Server_Accept(Seobeo_Handle *p_server_handle);

// --- Web --- //
/* Generate HTTP 1.1 Header value with given content_types and length. */
extern void           Seobeo_Web_Header_Generate(void *buffer, int status, const char *content_type, const int content_length);
/* Start a Generic HTTP static file server with given folder. It will store folder into memory. */
extern int            Seobeo_Web_Server_Start(const char *folder_path, const char *port, Seobeo_ServerMode mode, int thread_count);
/* Generic HTTP GET Rquest to given host and port with path. It will mimic chrome. */
extern int            Seobeo_Web_Client_Get(const char *host, const char *port, const char *path);

// --- HTTP Client (curl-like API) --- //
/* Create a new HTTP client request with the given URL. */
extern Seobeo_Client_Request  *Seobeo_Client_Request_Create(const char *url);
/* Set HTTP method (GET, POST, PUT, DELETE, etc.). Default is GET. */
extern void                    Seobeo_Client_Request_Set_Method(Seobeo_Client_Request *p_req, const char *method);
/* Add a header using key-value pairs (stored in HashMap). */
extern void                    Seobeo_Client_Request_Add_Header_Map(Seobeo_Client_Request *p_req, const char *key, const char *value);
/* Add a header as a string "Key: Value" (stored in Array). */
extern void                    Seobeo_Client_Request_Add_Header_Array(Seobeo_Client_Request *p_req, const char *header);
/* Set request body with given data and length. */
extern void                    Seobeo_Client_Request_Set_Body(Seobeo_Client_Request *p_req, const char *body, size_t length);
/* Enable/disable following redirects with max redirect count. Default is FALSE with 10 max. */
extern void                    Seobeo_Client_Request_Set_Follow_Redirects(Seobeo_Client_Request *p_req, boolean follow, int32 max_redirects);
/* Set download path to save response body to file instead of memory. */
extern void                    Seobeo_Client_Request_Set_Download_Path(Seobeo_Client_Request *p_req, const char *path);
/* Execute the HTTP request and return response. */
extern Seobeo_Client_Response *Seobeo_Client_Request_Execute(Seobeo_Client_Request *p_req);
/* Destroy request and free all resources. */
extern void                    Seobeo_Client_Request_Destroy(Seobeo_Client_Request *p_req);
/* Destroy response and free all resources. */
extern void                    Seobeo_Client_Response_Destroy(Seobeo_Client_Response *p_resp);

/**
 * WebSocket Client API
 * ------
 *
 * # Overview 
 *
 * A clean, easy-to-use WebSocket client library following RFC 6455. It will auto handle over 64 bits long data into a continous stream.
 *
 * ## Examples
 * 
 * ### 1. Simple Text Echo
 * 
 * ```c
 * // Connect to WebSocket server
 * Seobeo_WebSocket *p_ws = Seobeo_WebSocket_Connect("wss://echo.websocket.org");
 * if (!p_ws)
 * {
 *   printf("Failed to connect\n");
 *   return;
 * }
 * 
 * // Send text message
 * const char *message = "Hello, WebSocket!";
 * Seobeo_WebSocket_Send_Text(p_ws, message);
 * 
 * // Receive echo response
 * Seobeo_WebSocket_Message *p_msg = Seobeo_WebSocket_Receive(p_ws);
 * if (p_msg && p_msg->opcode == SEOBEO_WS_OPCODE_TEXT)
 * {
 *   printf("Received: %.*s\n", (int)p_msg->length, (char*)p_msg->data);
 *   Seobeo_WebSocket_Message_Destroy(p_msg);
 * }
 * 
 * // Close connection
 * Seobeo_WebSocket_Close(p_ws, 1000, "Normal closure");
 * Seobeo_WebSocket_Destroy(p_ws);
 * ```
 * 
 * ### 2. Binary Data Transfer
 * 
 * ```c
 * Seobeo_WebSocket *p_ws = Seobeo_WebSocket_Connect("wss://example.com/data");
 * 
 * // Send binary data
 * uint8 data[] = {0x01, 0x02, 0x03, 0xAA, 0xBB, 0xCC};
 * Seobeo_WebSocket_Send_Binary(p_ws, data, sizeof(data));
 * 
 * // Receive binary response
 * Seobeo_WebSocket_Message *p_msg = Seobeo_WebSocket_Receive(p_ws);
 * if (p_msg && p_msg->opcode == SEOBEO_WS_OPCODE_BINARY)
 * {
 *   printf("Received %zu bytes\n", p_msg->length);
 *   // Process binary data...
 *   Seobeo_WebSocket_Message_Destroy(p_msg);
 * }
 * 
 * Seobeo_WebSocket_Destroy(p_ws);
 * ```
 * 
 * ### 3. Chat Application
 * 
 * ```c
 * Seobeo_WebSocket *p_ws = Seobeo_WebSocket_Connect("wss://chat.example.com");
 * 
 * // Send chat message
 * Seobeo_WebSocket_Send_Text(p_ws, "Hello everyone!");
 * 
 * // Continuous receive loop
 * while (1)
 * {
 *   Seobeo_WebSocket_Message *p_msg = Seobeo_WebSocket_Receive(p_ws);
 *   if (p_msg)
 *   {
 *     if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT)
 *     {
 *       printf("Chat: %.*s\n", (int)p_msg->length, (char*)p_msg->data);
 *     }
 *     Seobeo_WebSocket_Message_Destroy(p_msg);
 *   }
 * 
 *   usleep(10000);  // 10ms sleep to avoid busy waiting
 * }
 * 
 * Seobeo_WebSocket_Destroy(p_ws);
 * ```
 * 
 * ### 4. Ping/Pong Keep-Alive
 * 
 * ```c
 * Seobeo_WebSocket *p_ws = Seobeo_WebSocket_Connect("wss://api.example.com");
 * 
 * // Send ping to keep connection alive
 * Seobeo_WebSocket_Send_Ping(p_ws, "keep-alive");
 * 
 * // Server will automatically receive pong responses
 * // (pong responses are handled internally)
 * 
 * Seobeo_WebSocket_Destroy(p_ws);
 * ```
 * 
 * ### 5. Handling Different Message Types
 * 
 * ```c
 * Seobeo_WebSocket *p_ws = Seobeo_WebSocket_Connect("wss://example.com");
 * 
 * while (1)
 * {
 *   Seobeo_WebSocket_Message *p_msg = Seobeo_WebSocket_Receive(p_ws);
 *   if (p_msg)
 *   {
 *     switch (p_msg->opcode)
 *     {
 *       case SEOBEO_WS_OPCODE_TEXT:
 *         printf("Text: %.*s\n", (int)p_msg->length, (char*)p_msg->data);
 *         break;
 * 
 *       case SEOBEO_WS_OPCODE_BINARY:
 *         printf("Binary: %zu bytes\n", p_msg->length);
 *         break;
 * 
 *       default:
 *         printf("Unknown opcode: 0x%X\n", p_msg->opcode);
 *         break;
 *     }
 * 
 *     Seobeo_WebSocket_Message_Destroy(p_msg);
 *   }
 * 
 *   usleep(10000);
 * }
 * 
 * Seobeo_WebSocket_Destroy(p_ws);
 * ```
 * 
 * ### 6. Graceful Shutdown
 * 
 * ```c
 * Seobeo_WebSocket *p_ws = Seobeo_WebSocket_Connect("wss://example.com");
 * 
 * // Do work...
 * 
 * // Close with custom status code and reason
 * Seobeo_WebSocket_Close(p_ws, 1000, "Client shutting down");
 * Seobeo_WebSocket_Destroy(p_ws);
 * ```
 * 
 * ## WebSocket Close Codes
 * 
 * Common close status codes (RFC 6455):
 * 
 * - **1000**: Normal closure
 * - **1001**: Going away (e.g., server shutdown, browser navigation)
 * - **1002**: Protocol error
 * - **1003**: Unsupported data type
 * - **1006**: Abnormal closure (no close frame received)
 * - **1007**: Invalid frame payload data
 * - **1008**: Policy violation
 * - **1009**: Message too big
 * - **1010**: Mandatory extension missing
 * - **1011**: Internal server error
 * 
 * ## Building
 * 
 * ### Build the library:
 * ```bash
 * bazel build //seobeo:seobeo_client
 * ```
 * 
 * ### Build and run the WebSocket test:
 * ```bash
 * bazel test //seobeo:seobeo_websocket_test
 * ```
 * 
 * ## Message Structure
 * 
 * ```c
 * typedef struct {
 *   Seobeo_WebSocket_Opcode opcode;  // Message type
 *   uint8  *data;                    // Message payload
 *   size_t  length;                  // Payload length
 *   boolean is_final;                // Final fragment flag
 * } Seobeo_WebSocket_Message;
 * ```
 * 
 * ## Protocol Details
 * 
 * The implementation follows RFC 6455
 * 
 * 1. **Handshake**: HTTP Upgrade request with `Sec-WebSocket-Key`
 * 2. **Frame Format**: Proper FIN, opcode, mask, and payload length handling
 * 3. **Masking**: All client-to-server frames are masked (required by RFC)
 * 4. **Fragmentation**: Handles fragmented messages across multiple frames
 * 5. **Control Frames**: Proper handling of ping, pong, and close frames
 */

/* Connect to WebSocket server with given URL (ws:// or wss://). */
extern Seobeo_WebSocket         *Seobeo_WebSocket_Connect(const char *url);
/* Connect to WebSocket server with custom headers. Headers should be newline-separated "Key: Value" pairs. */
extern Seobeo_WebSocket         *Seobeo_WebSocket_Connect_With_Headers(const char *url, const char *headers);
/* Send text message over WebSocket. */
extern int32                     Seobeo_WebSocket_Send_Text(Seobeo_WebSocket *p_ws, const char *text);
/* Send binary message over WebSocket. */
extern int32                     Seobeo_WebSocket_Send_Binary(Seobeo_WebSocket *p_ws, const uint8 *data, size_t length);
/* Send ping frame (for keep-alive). */
extern int32                     Seobeo_WebSocket_Send_Ping(Seobeo_WebSocket *p_ws, const char *payload);
/* Send pong frame (usually in response to ping). */
extern int32                     Seobeo_WebSocket_Send_Pong(Seobeo_WebSocket *p_ws, const char *payload);
/* Receive a message from WebSocket. Returns NULL if no message available or on error. */
extern Seobeo_WebSocket_Message *Seobeo_WebSocket_Receive(Seobeo_WebSocket *p_ws);
/* Destroy received message. */
extern void                      Seobeo_WebSocket_Message_Destroy(Seobeo_WebSocket_Message *p_msg);
/* Close WebSocket connection with status code and optional reason. */
extern int32                     Seobeo_WebSocket_Close(Seobeo_WebSocket *p_ws, uint16 code, const char *reason);
/* Destroy WebSocket and free all resources. */
extern void                      Seobeo_WebSocket_Destroy(Seobeo_WebSocket *p_ws);

// --- WebSocket Server API --- //
/* Initialize WebSocket server routing system. Call before registering routes. */
extern void                               Seobeo_WebSocket_Server_Init();
/* Register a WebSocket route handler for a specific path. */
extern void                               Seobeo_WebSocket_Server_Register(const char *path, Seobeo_WebSocket_Server_Handler handler, void *p_user_data);
/* Send text message to specific WebSocket client. */
extern int32                              Seobeo_WebSocket_Server_Send_Text(Seobeo_WebSocket_Server_Connection *p_conn, const char *text);
/* Send binary message to specific WebSocket client. */
extern int32                              Seobeo_WebSocket_Server_Send_Binary(Seobeo_WebSocket_Server_Connection *p_conn, const uint8 *data, size_t length);
/* Broadcast text message to all connected WebSocket clients. */
extern void                               Seobeo_WebSocket_Server_Broadcast_Text(const char *text, Seobeo_WebSocket_Server_Connection *origin_p_conn);
/* Broadcast binary message to all connected WebSocket clients. */
extern void                               Seobeo_WebSocket_Server_Broadcast_Binary(const uint8 *data, size_t length);
/* Close WebSocket connection with status code and reason. */
extern void                               Seobeo_WebSocket_Server_Connection_Close(Seobeo_WebSocket_Server_Connection *p_conn, uint16 code, const char *reason);

/* Initialize the router system (called automatically by Seobeo_Web_Server_Start) */
extern void           Seobeo_Router_Init();
/* Register an API route handler. Call before starting server. */
extern void           Seobeo_Router_Register(const char *method, const char *path_pattern, Seobeo_Route_Handler handler);
/* Clean up router resources */
extern void           Seobeo_Router_Destroy();
/* Find matching route handler (internal use) */
extern Seobeo_Route_Handler Seobeo_Router_Find_Handler(const char *method, const char *path, Seobeo_Request_Entry **pp_request_map, Dowa_Arena *p_arena);
/* Send HTTP response from response map (internal use) */
extern void           Seobeo_Router_Send_Response(Seobeo_Handle *p_handle, Seobeo_Request_Entry *p_response_map, Dowa_Arena *p_arena);
extern char          *Seobeo_Web_LoadFile(const char *file_path, size_t *p_file_size);

// --- Helper functions --- //
/* Destroy handle. It will handle all NULL poointers. */
extern void           Seobeo_Handle_Destroy(Seobeo_Handle *p_handle);
/* Write to socket from write_buffer in the handle. */
extern int            Seobeo_Handle_Flush(Seobeo_Handle *p_handle); 
/* Write to socket with given data source, if the data source is bigger than the write buffer for handle then we just directly write from the data source. */
extern int            Seobeo_Handle_Queue(Seobeo_Handle *p_handle, const uint8_t *data, uint32_t data_size); 
/* Read to socket from read_buffer in the handle. */
extern int            Seobeo_Handle_Read(Seobeo_Handle *p_handle); 
/* Move to read_buffer to front and we already consumed the given amount in the handle. */
extern void           Seobeo_Handle_Consume(Seobeo_Handle *p_handle, uint32 consumed);
/* Assign IP4 or IP6 to sockaddr. TODO: Maybe create my own struct for this? */
extern void          *Seobeo_Get_IP4_Or_IP6(struct sockaddr *sa);
/* Logging */
typedef enum {
  SEOBEO_INFO = 0,
  SEOBEO_WARNING,
  SEOBEO_ERROR,
  SEOBEO_DEBUG,
} Seobeo_Log_Level;
extern int            Seobeo_Log(Seobeo_Log_Level level, const char *format, ...);
#endif