view seobeo/seobeo.h @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -0700
parents 04fee26ecce0
children
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 "seobeo/seobeo_worker.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
#define SEOBEO_SSE_RETRY_NONE -1
#define SEOBEO_SSE_EVENT_TOO_LARGE -2
#define SEOBEO_SSE_INVALID_EVENT -3
#define SEOBEO_SSE_BACKPRESSURE -4

// HTTP STATUS CODE
#define HTTP_OK 200
#define HTTP_CREATED 201
#define HTTP_NO_CONTENT 204
#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_PAYLOAD_TOO_LARGE 413
#define HTTP_UNPROCESSABLE_CONTENT 422
#define HTTP_TOO_MANY_REQUESTS 429
#define HTTP_SERVICE_UNAVAILABLE 503
#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);
/* Generate HTTP 1.1 Header with keep-alive option. */
extern void           Seobeo_Web_Header_Generate_KeepAlive(void *buffer, int status, const char *content_type, const int content_length, boolean keep_alive);
/* 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);
/* Start a web server bound to a specific host. Pass NULL to bind all interfaces. */
extern int            Seobeo_Web_Server_Start_On(const char *host, const char *folder_path, const char *port, Seobeo_ServerMode mode, int thread_count);
/* Request a running server to stop and return from its start function. */
extern void           Seobeo_Web_Server_Stop(void);
/* 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 the maximum idle time for HTTP response reads. Zero disables the timeout. */
extern void                    Seobeo_Client_Request_Set_Timeout_Milliseconds(Seobeo_Client_Request *p_req, int32 timeout_ms);
/* 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);

// --- HTTP Web related helper functions --- //
extern void    Seobeo_Url_Decode(char *dst, const char *src);

/**
 * 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);
/* Register a streaming route handler. Handler receives client handle for direct streaming. */
extern void                  Seobeo_Router_Register_Stream(const char *method, const char *path_pattern, Seobeo_Stream_Handler handler);
/* Register an SSE route for a specific HTTP method. */
extern void                  Seobeo_Router_Register_SSE_Method(const char *method, const char *path_pattern, Seobeo_SSE_Handler handler);
/* Register a GET SSE route for compatibility. */
extern void                  Seobeo_Router_Register_SSE(const char *path_pattern, Seobeo_SSE_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);
/* Send HTTP response with keep-alive option */
extern void                  Seobeo_Router_Send_Response_KeepAlive(Seobeo_Handle *p_handle, Seobeo_Request_Entry *p_response_map, Dowa_Arena *p_arena, boolean keep_alive);
extern char                 *Seobeo_Web_LoadFile(const char *file_path, size_t *p_file_size);
/* Being a proxy and keeping the client open */
extern Seobeo_Stream_Handler Seobeo_Router_Find_Stream_Handler(const char *method, const char *path, Seobeo_Request_Entry **pp_request_map, Dowa_Arena *p_arena);
extern Seobeo_SSE_Handler    Seobeo_Router_Find_SSE_Handler(const char *method, const char *path, Seobeo_Request_Entry **pp_request_map, Dowa_Arena *p_arena);

/* Start an SSE response and send its required HTTP headers. */
extern int32          Seobeo_SSE_Start(Seobeo_SSE_Stream *p_stream, Seobeo_Handle *p_handle);
/* Send one SSE record. retry_ms may be SEOBEO_SSE_RETRY_NONE. */
extern int32          Seobeo_SSE_Send(Seobeo_SSE_Stream *p_stream, const Seobeo_SSE_Event *p_event);
/* Send a data-only SSE record. */
extern int32          Seobeo_SSE_Send_Data(Seobeo_SSE_Stream *p_stream, const char *data);
/* Send a comment/heartbeat record. */
extern int32          Seobeo_SSE_Send_Comment(Seobeo_SSE_Stream *p_stream, const char *comment);
/* Flush queued SSE bytes. Returns 1 when the socket would block. */
extern int32          Seobeo_SSE_Flush(Seobeo_SSE_Stream *p_stream);
/* Return TRUE while the stream can still accept records. */
extern boolean        Seobeo_SSE_Is_Open(const Seobeo_SSE_Stream *p_stream);
/* Retain a stream before storing it beyond the connection callback. */
extern boolean        Seobeo_SSE_Retain(Seobeo_SSE_Stream *p_stream);
/* Release a previously retained stream. */
extern void           Seobeo_SSE_Release(Seobeo_SSE_Stream *p_stream);
/* Stop accepting records. The router retains ownership of the handle. */
extern void           Seobeo_SSE_Close(Seobeo_SSE_Stream *p_stream);
/*
 * Register a callback invoked when the client disconnects (handle detached).
 * The callback is called AFTER g_sse_mutex is released and may safely acquire
 * other locks.  The stream pointer remains valid for the callback's duration
 * (the caller's retained reference keeps it alive).
 * cb may be NULL to clear a previously registered callback.
 */
extern void           Seobeo_SSE_Set_Detach_Callback(
    Seobeo_SSE_Stream *p_stream,
    void (*cb)(Seobeo_SSE_Stream *, void *),
    void *ctx);

// --- 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 int32          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 int32          Seobeo_Handle_Queue(Seobeo_Handle *p_handle, const uint8 *data, uint32 data_size);
/* Read to socket from read_buffer in the handle. */
extern int32          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