Mercurial
view seobeo/seobeo.h @ 154:bdcc610eeed8
[Markdown Converter][GuiZe] Added markdown coverter in C and wasm rule sets. Needs further view on this as I haven't taken a look. Written by Claude.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Mon, 12 Jan 2026 09:11:58 -0800 |
| parents | f236c895604e |
| children | 6de849867459 058de208e640 |
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); /* 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