changeset 67:6626ec933933

[Seobeo] Separated out Client Server logic. Created test tools.
author June Park <parkjune1995@gmail.com>
date Wed, 24 Dec 2025 09:15:55 -0800
parents a0f0ad5e42eb
children 70ca1d99f3fd
files mrjunejune/BUILD mrjunejune/test/create_snapshots.c mrjunejune/test/integration_test.c mrjunejune/test/snapshots/README.md mrjunejune/test/snapshots/does_not_exist.snapshot mrjunejune/test/snapshots/index.html.snapshot mrjunejune/test/snapshots/missing.html.snapshot mrjunejune/test/snapshots/nonexistent.snapshot mrjunejune/test/snapshots/root.snapshot seobeo/BUILD seobeo/s_linux_network.c seobeo/s_ssl.c seobeo/s_web.c seobeo/seobeo_http_client_test.c seobeo/seobeo_internal.h seobeo/snapshot_creator.c seobeo/snapshot_creator.h
diffstat 17 files changed, 2127 insertions(+), 84 deletions(-) [+]
line wrap: on
line diff
--- a/mrjunejune/BUILD	Wed Dec 24 06:34:19 2025 -0800
+++ b/mrjunejune/BUILD	Wed Dec 24 09:15:55 2025 -0800
@@ -20,7 +20,7 @@
 cc_binary(
   name = "mrjunejune_server",
   srcs = ["main.c"],
-  deps = ["//seobeo:seobeo"],
+  deps = ["//seobeo:seobeo_server"],  # Use server-only target (no OpenSSL)
   data = [":pages_files"],
 )
 
@@ -32,7 +32,7 @@
 cc_library(
   name = "mrjunejune_server_lib",
   srcs = ["server_entry.c"],
-  deps = ["//seobeo:seobeo"],
+  deps = ["//seobeo:seobeo_server"],  # Use server-only target (no OpenSSL)
   linkstatic = False,   # ensures dynamic linking
   visibility = ["//visibility:public"],
 )
@@ -47,3 +47,32 @@
   data = [":mrjunejune_server_lib"],
 )
 
+cc_test(
+  name = "integration_test",
+  srcs = ["test/integration_test.c"],
+  deps = ["//seobeo:seobeo_client"],
+  data = [
+    "//mrjunejune:mrjunejune_server",
+    "//mrjunejune:pages_files",
+    "//mrjunejune:test_snapshots",
+  ],
+  size = "medium",
+  timeout = "moderate",
+  args = ["$(location //mrjunejune:mrjunejune_server)"],
+)
+
+cc_binary(
+  name = "create_snapshots",
+  srcs = ["test/create_snapshots.c"],
+  deps = ["//seobeo:seobeo_client"],
+  data = [
+    "//mrjunejune:mrjunejune_server",
+    "//mrjunejune:pages_files",
+  ],
+  args = ["$(location //mrjunejune:mrjunejune_server)"],
+)
+
+filegroup(
+  name = "test_snapshots",
+  srcs = glob(["test/snapshots/**"]),
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mrjunejune/test/create_snapshots.c	Wed Dec 24 09:15:55 2025 -0800
@@ -0,0 +1,173 @@
+#include "seobeo/seobeo.h"
+#include "seobeo/snapshot_creator.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+#define TEST_PORT "6969"
+#define TEST_HOST "127.0.0.1"
+#define SNAPSHOT_DIR "mrjunejune/test/snapshots"
+
+// Start the server process
+static pid_t start_server(const char *server_binary)
+{
+  pid_t server_pid = fork();
+
+  if (server_pid < 0)
+  {
+    perror("fork");
+    return -1;
+  }
+
+  if (server_pid == 0)
+  {
+    // Child process - run server
+    printf("Starting server on port %s...\n", TEST_PORT);
+    execl(server_binary, server_binary, NULL);
+    perror("execl failed");
+    exit(1);
+  }
+
+  // Parent - verify server started
+  printf("Server started (PID: %d)\n", server_pid);
+
+  usleep(100000);
+  int status;
+  pid_t result = waitpid(server_pid, &status, WNOHANG);
+  if (result != 0)
+  {
+    if (WIFEXITED(status))
+    {
+      fprintf(stderr, "Server exited with code: %d\n", WEXITSTATUS(status));
+    }
+    else if (WIFSIGNALED(status))
+    {
+      fprintf(stderr, "Server killed by signal: %d\n", WTERMSIG(status));
+    }
+    return -1;
+  }
+
+  sleep(2);
+  printf("Server ready\n\n");
+
+  return server_pid;
+}
+
+// Stop the server process
+static void stop_server_test(pid_t server_pid)
+{
+  if (server_pid > 0)
+  {
+    printf("\nStopping server (PID: %d)...\n", server_pid);
+    kill(server_pid, SIGTERM);
+    waitpid(server_pid, NULL, 0);
+    printf("Server stopped\n");
+  }
+}
+
+int main(int argc, char *argv[])
+{
+  printf("=== Seobeo Snapshot Creator ===\n\n");
+
+  // Get workspace directory (where the source files are)
+  const char *workspace_dir = getenv("BUILD_WORKSPACE_DIRECTORY");
+  if (!workspace_dir)
+  {
+    fprintf(stderr, "Error: BUILD_WORKSPACE_DIRECTORY not set\n");
+    fprintf(stderr, "This binary must be run with 'bazel run'\n");
+    return 1;
+  }
+
+  // Construct full path to snapshot directory
+  char snapshot_path[1024];
+  snprintf(snapshot_path, sizeof(snapshot_path), "%s/%s", workspace_dir, SNAPSHOT_DIR);
+
+  // Get server binary path
+  const char *server_binary = "./mrjunejune_server";
+  if (argc > 1)
+  {
+    server_binary = argv[1];
+  }
+
+  printf("Workspace: %s\n", workspace_dir);
+  printf("Server binary: %s\n", server_binary);
+  printf("Snapshot directory: %s\n\n", snapshot_path);
+
+  // Start server
+  pid_t server_pid = start_server(server_binary);
+  if (server_pid < 0)
+  {
+    fprintf(stderr, "Failed to start server\n");
+    return 1;
+  }
+
+  // Define snapshots to create - paths that should succeed (200 OK)
+  SnapshotConfig success_snapshots[] = {
+    {"/", 200, snapshot_path, TEST_HOST, TEST_PORT},
+    {"/index.html", 200, snapshot_path, TEST_HOST, TEST_PORT},
+  };
+  int num_success = sizeof(success_snapshots) / sizeof(success_snapshots[0]);
+
+  // Define snapshots for error paths (404)
+  SnapshotConfig error_snapshots[] = {
+    {"/nonexistent", 404, snapshot_path, TEST_HOST, TEST_PORT},
+    {"/does/not/exist", 404, snapshot_path, TEST_HOST, TEST_PORT},
+    {"/missing.html", 404, snapshot_path, TEST_HOST, TEST_PORT},
+  };
+  int num_errors = sizeof(error_snapshots) / sizeof(error_snapshots[0]);
+
+  int total_failed = 0;
+  int total_passed = 0;
+
+  // Create success snapshots
+  printf("Creating snapshots for successful paths:\n\n");
+  for (int i = 0; i < num_success; i++)
+  {
+    if (Seobeo_Snapshot_Create(&success_snapshots[i]) == 0)
+    {
+      total_passed++;
+    }
+    else
+    {
+      total_failed++;
+    }
+  }
+
+  // Create error snapshots
+  printf("Creating snapshots for error paths:\n\n");
+  for (int i = 0; i < num_errors; i++)
+  {
+    if (Seobeo_Snapshot_Create(&error_snapshots[i]) == 0)
+    {
+      total_passed++;
+    }
+    else
+    {
+      total_failed++;
+    }
+  }
+
+  // Stop server
+  stop_server_test(server_pid);
+
+  // Print summary
+  printf("\n=== Summary ===\n");
+  printf("Snapshots created: %d\n", total_passed);
+  printf("Failed: %d\n", total_failed);
+
+  if (total_failed == 0)
+  {
+    printf("\n✓ All snapshots created successfully!\n");
+    printf("\nSnapshots saved to: %s/\n", snapshot_path);
+    printf("\nRun tests to verify:\n");
+    printf("  bazel test //mrjunejune:integration_test\n");
+    return 0;
+  }
+  else
+  {
+    printf("\n✗ Some snapshots failed\n");
+    return 1;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mrjunejune/test/integration_test.c	Wed Dec 24 09:15:55 2025 -0800
@@ -0,0 +1,616 @@
+#include "seobeo/seobeo.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <assert.h>
+
+#define TEST_PORT "6969"
+#define TEST_HOST "127.0.0.1"
+#define MAX_RESPONSE_SIZE (1024 * 1024)
+#define SNAPSHOT_DIR "mrjunejune/test/snapshots"
+
+// Test case structure
+typedef struct {
+  const char *path;
+  int expected_status;
+  const char *expected_content;  // Loaded from file
+  char *expected_file_path;      // Path to expected snapshot file
+  char *actual_response;
+  size_t response_len;
+} TestCase;
+
+// Helper: Convert URL path to filename
+// "/" -> "root.snapshot"
+// "/index.html" -> "index.html.snapshot"
+// "/api/users" -> "api_users.snapshot"
+void path_to_filename(const char *path, char *filename, size_t max_len)
+{
+  if (strcmp(path, "/") == 0)
+  {
+    snprintf(filename, max_len, "root.snapshot");
+    return;
+  }
+
+  // Remove leading slash and convert remaining slashes to underscores
+  const char *p = path;
+  if (*p == '/')
+  {
+    p++;
+  }
+
+  char *out = filename;
+  size_t remaining = max_len - 1;
+
+  while (*p && remaining > 0)
+  {
+    if (*p == '/')
+    {
+      *out++ = '_';
+      remaining--;
+    }
+    else
+    {
+      *out++ = *p;
+      remaining--;
+    }
+    p++;
+  }
+
+  // Add .snapshot extension
+  snprintf(out, remaining, ".snapshot");
+}
+
+// Helper: Read file contents into buffer
+char* read_file(const char *filepath, size_t *size_out)
+{
+  FILE *f = fopen(filepath, "rb");
+  if (!f)
+  {
+    return NULL;
+  }
+
+  fseek(f, 0, SEEK_END);
+  long fsize = ftell(f);
+  fseek(f, 0, SEEK_SET);
+
+  char *buffer = malloc(fsize + 1);
+  if (!buffer)
+  {
+    fclose(f);
+    return NULL;
+  }
+
+  size_t read_size = fread(buffer, 1, fsize, f);
+  buffer[read_size] = '\0';
+  fclose(f);
+
+  if (size_out)
+  {
+    *size_out = read_size;
+  }
+
+  return buffer;
+}
+
+// Helper: Load expected content for a test case
+int load_expected_content(TestCase *test)
+{
+  if (!test->expected_file_path)
+  {
+    return -1;
+  }
+
+  size_t size;
+  test->expected_content = read_file(test->expected_file_path, &size);
+
+  if (!test->expected_content)
+  {
+    return -1;
+  }
+
+  return 0;
+}
+
+// Helper: Create test client
+Seobeo_Handle* create_test_client()
+{
+  Seobeo_Handle *client = Seobeo_Stream_Handle_Client_Create(TEST_HOST, TEST_PORT, FALSE);
+  if (!client || client->socket < 0)
+  {
+    if (client)
+    {
+      Seobeo_Handle_Destroy(client);
+    }
+    return NULL;
+  }
+  return client;
+}
+
+// Helper: Generate default HTTP GET request
+int generate_http_get_request(char *buffer, size_t buffer_size, const char *path)
+{
+  return snprintf(
+    buffer, buffer_size,
+    "GET %s HTTP/1.1\r\n"
+    "Host: %s\r\n"
+    "Connection: close\r\n"
+    "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
+    "User-Agent: SeobeoTestClient/1.0\r\n"
+    "\r\n",
+    path, TEST_HOST
+  );
+}
+
+// Helper: Send HTTP request
+int send_http_request(Seobeo_Handle *client, const char *path, const char *custom_request)
+{
+  char request_buffer[4096];
+  int request_len;
+
+  if (custom_request)
+  {
+    request_len = snprintf(request_buffer, sizeof(request_buffer), "%s", custom_request);
+  }
+  else
+  {
+    request_len = generate_http_get_request(request_buffer, sizeof(request_buffer), path);
+  }
+
+  if (request_len < 0 || request_len >= sizeof(request_buffer))
+  {
+    fprintf(stderr, "Request buffer too small\n");
+    return -1;
+  }
+
+  Seobeo_Handle_Queue(client, (uint8*)request_buffer, (uint32)request_len);
+  return Seobeo_Handle_Flush(client);
+}
+
+// Helper: Read HTTP response
+int read_http_response(Seobeo_Handle *client, char **response_out, size_t *response_len_out)
+{
+  char *response = malloc(MAX_RESPONSE_SIZE);
+  if (!response)
+  {
+    return -1;
+  }
+
+  size_t total_bytes = 0;
+  int attempts = 0;
+  const int max_attempts = 100;
+
+  while (attempts++ < max_attempts && total_bytes < MAX_RESPONSE_SIZE - 1)
+  {
+    int bytes_read = Seobeo_Handle_Read(client);
+
+    if (bytes_read > 0)
+    {
+      size_t to_copy = client->read_buffer_len;
+      if (total_bytes + to_copy > MAX_RESPONSE_SIZE - 1)
+      {
+        to_copy = MAX_RESPONSE_SIZE - 1 - total_bytes;
+      }
+
+      memcpy(response + total_bytes, client->read_buffer, to_copy);
+      total_bytes += to_copy;
+      Seobeo_Handle_Consume(client, (uint32)to_copy);
+    }
+    else if (bytes_read == -2)
+    {
+      // Connection closed
+      break;
+    }
+    else if (bytes_read == 0)
+    {
+      // Would block
+      usleep(10000);
+      continue;
+    }
+    else
+    {
+      free(response);
+      return -1;
+    }
+  }
+
+  response[total_bytes] = '\0';
+  *response_out = response;
+  *response_len_out = total_bytes;
+
+  return (total_bytes > 0) ? 0 : -1;
+}
+
+// Helper: Parse HTTP status code
+int parse_http_status(const char *response)
+{
+  if (!response || strlen(response) < 12)
+  {
+    return -1;
+  }
+
+  const char *status_start = strstr(response, "HTTP/1.1 ");
+  if (!status_start)
+  {
+    status_start = strstr(response, "HTTP/1.0 ");
+  }
+
+  if (!status_start)
+  {
+    return -1;
+  }
+
+  int status_code;
+  if (sscanf(status_start + 9, "%d", &status_code) == 1)
+  {
+    return status_code;
+  }
+
+  return -1;
+}
+
+// Helper: Check if status is a redirect
+int is_redirect_status(int status)
+{
+  return (status >= 300 && status < 400);
+}
+
+// Helper: Execute a test case
+int execute_test_case(TestCase *test, pid_t server_pid)
+{
+  printf("  Testing: GET %s (expecting %d)\n", test->path, test->expected_status);
+
+  Seobeo_Handle *client = create_test_client();
+  if (!client)
+  {
+    printf("    ✗ Failed to create client connection\n");
+    return -1;
+  }
+
+  if (send_http_request(client, test->path, NULL) < 0)
+  {
+    printf("    ✗ Failed to send request\n");
+    Seobeo_Handle_Destroy(client);
+    return -1;
+  }
+
+  char *response = NULL;
+  size_t response_len = 0;
+  if (read_http_response(client, &response, &response_len) < 0)
+  {
+    printf("    ✗ Failed to read response\n");
+    Seobeo_Handle_Destroy(client);
+    return -1;
+  }
+
+  test->actual_response = response;
+  test->response_len = response_len;
+
+  int actual_status = parse_http_status(response);
+  if (actual_status != test->expected_status)
+  {
+    printf("    ✗ Status mismatch: expected %d, got %d\n",
+           test->expected_status, actual_status);
+    Seobeo_Handle_Destroy(client);
+    return -1;
+  }
+
+  printf("    ✓ Status code: %d\n", actual_status);
+
+  // For redirects, skip content comparison
+  if (is_redirect_status(actual_status))
+  {
+    printf("    ⚠ Redirect status - skipping content comparison\n");
+    Seobeo_Handle_Destroy(client);
+    return 0;
+  }
+
+  // Only verify 200 OK responses against snapshots
+  if (actual_status == 200)
+  {
+    if (!test->expected_content)
+    {
+      printf("    ✗ No expected snapshot found: %s\n", test->expected_file_path);
+      printf("    → Run: bazel run //mrjunejune:create_snapshots\n");
+      Seobeo_Handle_Destroy(client);
+      return -1;
+    }
+
+    if (strcmp(response, test->expected_content) != 0)
+    {
+      printf("    ✗ Response does not match expected snapshot\n");
+      printf("    Expected file: %s\n", test->expected_file_path);
+      Seobeo_Handle_Destroy(client);
+      return -1;
+    }
+
+    printf("    ✓ Response matches snapshot (%zu bytes)\n", response_len);
+  }
+
+  Seobeo_Handle_Destroy(client);
+  return 0;
+}
+
+// Helper: Execute custom request test
+int execute_custom_request_test(const char *name, const char *custom_request,
+                                 int expected_status, pid_t server_pid)
+{
+  printf("  Testing: %s (expecting %d)\n", name, expected_status);
+
+  Seobeo_Handle *client = create_test_client();
+  if (!client)
+  {
+    printf("    ✗ Failed to create client connection\n");
+    return -1;
+  }
+
+  if (send_http_request(client, NULL, custom_request) < 0)
+  {
+    printf("    ✗ Failed to send request\n");
+    Seobeo_Handle_Destroy(client);
+    return -1;
+  }
+
+  char *response = NULL;
+  size_t response_len = 0;
+  if (read_http_response(client, &response, &response_len) < 0)
+  {
+    printf("    ✗ Failed to read response\n");
+    Seobeo_Handle_Destroy(client);
+    return -1;
+  }
+
+  int actual_status = parse_http_status(response);
+  if (actual_status != expected_status)
+  {
+    printf("    ✗ Status mismatch: expected %d, got %d\n",
+           expected_status, actual_status);
+    free(response);
+    Seobeo_Handle_Destroy(client);
+    return -1;
+  }
+
+  printf("    ✓ Status code: %d\n", actual_status);
+  printf("    ✓ Response received (%zu bytes)\n", response_len);
+
+  free(response);
+  Seobeo_Handle_Destroy(client);
+  return 0;
+}
+
+// Helper: Start test server
+pid_t start_test_server(const char *server_binary)
+{
+  pid_t server_pid = fork();
+
+  if (server_pid < 0)
+  {
+    perror("fork");
+    return -1;
+  }
+
+  if (server_pid == 0)
+  {
+    printf("Starting server on port %s...\n", TEST_PORT);
+    execl(server_binary, server_binary, NULL);
+    perror("execl failed");
+    exit(1);
+  }
+
+  printf("Server started (PID: %d)\n", server_pid);
+
+  usleep(100000);
+  int status;
+  pid_t result = waitpid(server_pid, &status, WNOHANG);
+  if (result != 0)
+  {
+    if (WIFEXITED(status))
+    {
+      fprintf(stderr, "Server exited immediately with code: %d\n", WEXITSTATUS(status));
+    }
+    else if (WIFSIGNALED(status))
+    {
+      fprintf(stderr, "Server was killed by signal: %d\n", WTERMSIG(status));
+    }
+    return -1;
+  }
+
+  sleep(2);
+  printf("Server ready\n\n");
+
+  return server_pid;
+}
+
+// Helper: Stop test server
+void stop_test_server(pid_t server_pid)
+{
+  if (server_pid > 0)
+  {
+    printf("\nStopping server (PID: %d)...\n", server_pid);
+    kill(server_pid, SIGTERM);
+    waitpid(server_pid, NULL, 0);
+    printf("Server stopped\n");
+  }
+}
+
+// Helper: Initialize test case with snapshot file
+void init_test_case(TestCase *test)
+{
+  char filename[256];
+  path_to_filename(test->path, filename, sizeof(filename));
+
+  test->expected_file_path = malloc(512);
+  snprintf(test->expected_file_path, 512, "%s/%s", SNAPSHOT_DIR, filename);
+
+  // Load expected content from snapshot file
+  load_expected_content(test);
+}
+
+// Helper: Cleanup test case
+void cleanup_test_case(TestCase *test)
+{
+  if (test->actual_response)
+  {
+    free(test->actual_response);
+  }
+  if (test->expected_file_path)
+  {
+    free(test->expected_file_path);
+  }
+  if (test->expected_content)
+  {
+    free((void*)test->expected_content);
+  }
+}
+
+// Main integration test
+int test_server_client_integration(const char *server_binary)
+{
+  printf("=== Server-Client Integration Test ===\n");
+  printf("MODE: Verifying Against Snapshots\n\n");
+
+  char cwd[1024];
+  if (getcwd(cwd, sizeof(cwd)) != NULL)
+  {
+    printf("Working directory: %s\n", cwd);
+  }
+
+  if (access(server_binary, X_OK) != 0)
+  {
+    printf("Server binary not found: %s\n", server_binary);
+    perror("access");
+    return -1;
+  }
+  printf("Server binary: %s\n", server_binary);
+  printf("Snapshot directory: %s\n\n", SNAPSHOT_DIR);
+
+  pid_t server_pid = start_test_server(server_binary);
+  if (server_pid < 0)
+  {
+    return -1;
+  }
+
+  int failed_tests = 0;
+  int passed_tests = 0;
+
+  // Define test cases - paths that should succeed (200 OK)
+  TestCase success_tests[] = {
+    {"/", 200, NULL, NULL, NULL, 0},
+    {"/index.html", 200, NULL, NULL, NULL, 0},
+  };
+  int num_success_tests = sizeof(success_tests) / sizeof(success_tests[0]);
+
+  // Define test cases - paths that should fail (404)
+  TestCase failure_tests[] = {
+    {"/nonexistent", 404, NULL, NULL, NULL, 0},
+    {"/does/not/exist", 404, NULL, NULL, NULL, 0},
+    {"/missing.html", 404, NULL, NULL, NULL, 0},
+  };
+  int num_failure_tests = sizeof(failure_tests) / sizeof(failure_tests[0]);
+
+  // Initialize all test cases
+  for (int i = 0; i < num_success_tests; i++)
+  {
+    init_test_case(&success_tests[i]);
+  }
+  for (int i = 0; i < num_failure_tests; i++)
+  {
+    init_test_case(&failure_tests[i]);
+  }
+
+  // Run success tests
+  printf("Running tests for paths that should succeed:\n");
+  for (int i = 0; i < num_success_tests; i++)
+  {
+    if (execute_test_case(&success_tests[i], server_pid) == 0)
+    {
+      passed_tests++;
+    }
+    else
+    {
+      failed_tests++;
+    }
+  }
+
+  printf("\n");
+
+  // Run failure tests
+  printf("Running tests for paths that should fail:\n");
+  for (int i = 0; i < num_failure_tests; i++)
+  {
+    if (execute_test_case(&failure_tests[i], server_pid) == 0)
+    {
+      passed_tests++;
+    }
+    else
+    {
+      failed_tests++;
+    }
+  }
+
+  printf("\n");
+
+  // Test with custom request
+  printf("Running tests with custom requests:\n");
+  char custom_request[4096];
+  snprintf(custom_request, sizeof(custom_request),
+           "GET / HTTP/1.1\r\n"
+           "Host: %s\r\n"
+           "Connection: close\r\n"
+           "X-Custom-Header: TestValue\r\n"
+           "\r\n",
+           TEST_HOST);
+
+  if (execute_custom_request_test("Custom headers GET /", custom_request, 200, server_pid) == 0)
+  {
+    passed_tests++;
+  }
+  else
+  {
+    failed_tests++;
+  }
+
+  // Cleanup test cases
+  for (int i = 0; i < num_success_tests; i++)
+  {
+    cleanup_test_case(&success_tests[i]);
+  }
+  for (int i = 0; i < num_failure_tests; i++)
+  {
+    cleanup_test_case(&failure_tests[i]);
+  }
+
+  stop_test_server(server_pid);
+
+  printf("\n=== Test Summary ===\n");
+  printf("Passed: %d\n", passed_tests);
+  printf("Failed: %d\n", failed_tests);
+
+  return (failed_tests == 0) ? 0 : -1;
+}
+
+int main(int argc, char *argv[])
+{
+  printf("=== Seobeo Integration Tests ===\n\n");
+
+  const char *server_binary = "./mrjunejune_server";
+  if (argc > 1)
+  {
+    server_binary = argv[1];
+  }
+
+  int result = test_server_client_integration(server_binary);
+
+  if (result == 0)
+  {
+    printf("\n✓ All tests passed!\n");
+  }
+  else
+  {
+    printf("\n✗ Some tests failed\n");
+  }
+
+  return result;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mrjunejune/test/snapshots/README.md	Wed Dec 24 09:15:55 2025 -0800
@@ -0,0 +1,76 @@
+# Test Snapshots
+
+This directory contains expected HTTP response snapshots for integration tests.
+
+## How It Works
+
+Each test case generates a snapshot file based on its URL path:
+
+- `/` → `root.snapshot`
+- `/index.html` → `index.html.snapshot`
+- `/api/users` → `api_users.snapshot`
+- `/path/to/resource` → `path_to_resource.snapshot`
+
+## Workflow
+
+### First time (no snapshots exist):
+
+1. Create snapshots:
+```bash
+bazel run //mrjunejune:create_snapshots
+```
+
+This will:
+- Start the test server
+- Make HTTP requests to all test paths
+- Save responses to `mrjunejune/test/snapshots/`
+
+2. Verify tests pass:
+```bash
+bazel test //mrjunejune:integration_test
+```
+
+All tests should now pass since snapshots exist.
+
+### Updating Snapshots
+
+When you change the server response format:
+
+1. Regenerate snapshots:
+```bash
+bazel run //mrjunejune:create_snapshots
+```
+
+2. Verify tests still pass:
+```bash
+bazel test //mrjunejune:integration_test
+```
+
+3. Commit the updated snapshot files
+
+## Two Separate Commands
+
+- **`bazel run //mrjunejune:create_snapshots`** - Creates/updates snapshot files (uses seobeo snapshot creator library)
+- **`bazel test //mrjunejune:integration_test`** - Verifies responses match snapshots
+
+This separation makes it clear when you're creating new snapshots vs. verifying against existing ones.
+
+## Implementation Details
+
+The snapshot creation is implemented in the seobeo library:
+- `seobeo/snapshot_creator.h` - Snapshot creation API
+- `seobeo/snapshot_creator.c` - Implementation
+- `mrjunejune/test/create_snapshots.c` - Binary that uses the library
+
+This makes snapshot creation reusable across different projects.
+
+## Special Cases
+
+### 200 OK Responses
+Only 200 OK responses are verified against snapshots. This ensures successful responses remain consistent.
+
+### Redirects (3xx status codes)
+Redirect responses skip content comparison - only the status code is verified.
+
+### Error Responses (4xx, 5xx)
+Error status codes are verified but content comparison is skipped to allow for response format flexibility.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mrjunejune/test/snapshots/does_not_exist.snapshot	Wed Dec 24 09:15:55 2025 -0800
@@ -0,0 +1,5 @@
+HTTP/1.1 404 Not Found
+Content-Type: text/html
+Content-Length: 0
+Connection: close
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mrjunejune/test/snapshots/index.html.snapshot	Wed Dec 24 09:15:55 2025 -0800
@@ -0,0 +1,245 @@
+HTTP/1.1 200 OK
+Content-Type: text/html; charset=utf-8
+Content-Length: 12120
+Connection: close
+
+<!doctype html>
+<html lang="en">
+  <head>
+    <BaseHead title="Resume" description="June's resume" />
+    <link rel="stylesheet" href="base.css" />
+    <link rel="stylesheet" href="resume.css" />
+  </head>
+  <body>
+     <main>
+       <div>
+         <p>Hi, my name is Juntae, but most people call me <b>June</b>.</p>
+         <p>I am a software engineer with experience spanning a wide range of companies, from small startups to FAANG. </p> 
+         <p>Feel free to check out my <a href="/resume.pdf"> resume </a> below, and if you're interested, don’t hesitate to contact me for contract work ranging from web/app development to embedded programming.</p>
+       </div>
+       <div class="info">
+         <p><span class="header-firstname-style"> JUNTAE </span><span class="header-lastname-style">PARK</span><p>
+         <p class="header-position-style"> FULL STACK DEVELOPER · SOFTWARE ENGINEER </p>
+         <div class="header-address-style"> 
+           Bay Area, CA, USA
+         </div>
+         <div class="header-social-style">
+           <p>📱(US) 650-531-1728 |📱(CA) 437-580-8026 | <a href="mailto:[email protected]"> ✉️  [email protected] </a>| <a href="https://github.com/mrjunejune"> 
+                <svg viewBox="0 0 16 16" aria-hidden="true" width="12" height="12">
+                  <path
+						fill="currentColor"
+						d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
+                        ></path>
+                </svg> mrjunejune
+           </a>| <a href="https://www.linkedin.com/in/junepark"> 
+              <svg width="12" height="12" fill="currentColor" class="bi bi-linkedin" viewBox="0 0 16 16">
+                <path d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z"/>
+              </svg>
+             junepark
+             </a>
+           </p>
+         </div>
+       </div>
+
+       <div class="sub-header">
+         <h2 class="section-style"> Summary </h2>
+         <div class="line"></div>
+       </div>
+       <p class="paragraph-style">Software Engineer with 8 years of hands-on experience across diverse tech stacks, from early-stage startups to FANG-scale systems. Adept in designing and delivering robust software solutions using modern languages, frameworks, and cloud platforms.  Open to impactful work.</p>
+       <div class="sub-header">
+
+         <h2 class="section-style"> Skills </h2>
+         <div class="line"></div>
+       </div>
+       <div class="paragraph-style">
+         <p>
+           <span class="entry-title-style"> Programming Languages:</span>
+           <span class="skill-type-style"> TypeScript, Python, C++/C, Ruby, Java, MATLAB </span>
+         </p>
+         <p> 
+           <span class="entry-title-style">Tools & Platforms:</span>
+           <span class="skill-type-style">Bazel, PostgresSQL, Mercurial, Git, Pands, Raylib, XCode</span>
+         </p>
+         <p>
+           <span class="entry-title-style"> Web Frameworks: </span>
+           <span class="skill-type-style"> Django, Rails, React, Flask</span>
+         </p>
+         <p>
+           <span class="entry-title-style"> DevOp:</span>
+           <span class="skill-type-style"> Plummi, Heroku, DigitalOcean, AWS, Google Cloud </span>
+         </p>
+         <p>
+           <span class="entry-title-style"> Language:</span>
+           <span class="skill-type-style"> English, Korean, Japanese </span>
+         </p>
+       </div>
+
+       <!-- Experiences -->
+      <div class="sub-header">
+         <h2 class="section-style"> Experience </h2>
+         <div class="line"></div>
+       </div>
+       <div class="flex-box">
+         <p class="entry-title-style">
+           <a href="https://www.meta.com/">Meta</a>
+         </p>
+         <p class="entry-location-style">San Francisco, CA, USA</p>
+       </div>
+       <div class="flex-box">
+         <p class="entry-position-style">SOFTWARE ENGINEER</p>
+         <p class="entry-date-style">Oct, 2024 - Present</p>
+       </div>
+         <ul class="description-style">
+           <li>
+             Took initiative on Channel Value Rule, targeting the 16% of ad traffic with both app and web destinations to improve value attribution and ROI.
+           </li>
+           <li>
+             Built full-stack features using React and Hack/GraphQL, contributing to scalable, production-ready systems.
+           </li>
+           <li>
+             Partnered with data science to design A/B tests and analyze revenue impact of ads destination.
+           </li>
+           <li>
+             Proposed and implemented alpha improvements to internal testing infrastructure, reducing test time by 50% and enhancing developer velocity.
+           </li>
+         </ul>
+       <div class="flex-box">
+         <p class="entry-title-style">
+           <a href="https://www.wmg.com/">Warner Music Group</a>
+         </p>
+         <p class="entry-location-style">Toronto, ON, Canada</p>
+       </div>
+       <div class="flex-box">
+         <p class="entry-position-style">TECHNICAL LEAD ENGINEER</p>
+         <p class="entry-date-style">July, 2023 - Sept, 2024</p>
+       </div>
+       <ul class="description-style">
+         <li>
+           Implements <a href="https://bazel.build/">bazel </a>structure for the company for TypeScript and JavaScript code base for hermiticity and stablishing standards for JavaScript and
+         </li>
+         <li>
+           TypeScript testing and code structures.
+         </li>
+         <li>
+           Led a team of five engineers in building GraphQL endpoints for client-facing applications using Apollo and AppSync, supporting over 2000 RPS and auto scaling depending on request values.
+         </li>
+         <li>
+           Improved application response times by up to 85% for graphQL response by updating database schema and SQL queries, eliminating N+1 queries and lack of indexes.
+         </li>
+         <li>
+           Developed CI/CD pipelines for backend structures.
+         </li>
+         <li>
+           Designed infrastructure for pub/sub, caching, and media processing logic.
+         </li>
+       </ul>
+
+       <div class="flex-box">
+         <p class="entry-title-style">
+           <a href="https://www.google.com/">Google</a>
+         </p>
+         <p class="entry-location-style">Toronto, ON, Canada</p>
+       </div>
+       <div class="flex-box">
+         <p class="entry-position-style">SOFTWARE ENGINEER</p>
+         <p class="entry-date-style">Feb, 2022 - July 2023</p>
+       </div>
+       <ul class="description-style">
+         <li>
+           Implements and maintained new features relating to App Script across google workspace platform including Gmail, sheets, and Docs.</li>
+         <li>
+           Improved a response time and render time of App Script hover card components.</li>
+         <li>
+           Collaborated with a team of developers to ensure timely and accurate delivery of features.</li>
+         <li>
+           Conducted user testing and gathered feedback to iterate on features for optimal user experience.</li>
+       </ul>
+
+       <div class="flex-box">
+         <p class="entry-title-style">
+           <a href="https://www.everlywell.com/">Everlywell</a>
+         </p>
+         <p class="entry-location-style">Toronto, ON, Canada</p>
+       </div>
+       <div class="flex-box">
+         <p class="entry-position-style">SOFTWARE ENGINEER</p>
+         <p class="entry-date-style">December, 2020 - Jan, 2022</p>
+       </div>
+       <ul class="description-style">
+         <li>
+           Maintained Amazon amplify apps to create and deploy React web applications for companies such as <a href="https://brooklynnets.everlywell.com/">NBA</a>, <a href="https://tinder.everlywell.com/">Tinder</a>, and other companies for COVID-19 at-home test kits.</li>
+         <li>
+           Implemented a script that helps accurately access and refund unused covid test kits; helping company save up to 200,000 USD.</li>
+         <li>
+           Created several Rails controllers for internal purposes; mocking end to end user experience for QA, mass refund features for CX department, and more, ultimately reducing support tickets amount by 50 percent.</li>
+         <li>
+           Implemented an audit table to help debug problems and logged which process was responsible for the change of the record using PaperTrail gems</li>
+       </ul>
+
+       <div class="flex-box">
+         <p class="entry-title-style">
+           <a href="https://www.spiria.com/">Spiria</a>
+         </p>
+         <p class="entry-location-style">Oakville, ON, Canada</p>
+       </div>
+       <div class="flex-box">
+         <p class="entry-position-style">SOFTWARE ENGINEER</p>
+         <p class="entry-date-style">October, 2018 - October, 2020</p>
+       </div>
+       <ul class="description-style">
+         <li>
+           Constructed RESTful API endpoints in multiple different frameworks such as Django, Ruby on Rails, and Flask and automated API documentation process using swagger.
+         </li>
+         <li>
+           Designed custom rake tasks for importing production data into newly updated data structure to meet client's needs.
+         </li>
+         <li>
+           Maintained or updated staging/productions servers. Debugged problems in production postgres database using ssh and postgres console on Heroku or AWS servers
+         </li>
+         <li>
+           Collaborated in creating automation python scripts for websites and application using selenium covering for QA eliminating 80% of QA's manual work
+         </li>
+       </ul>
+
+       <div class="flex-box">
+         <p class="entry-title-style">
+           <a href="https://www.apexscore.ai/">Apex Score</a>
+         </p>
+         <p class="entry-location-style">Oakville, ON, Canada</p>
+       </div>
+       <div class="flex-box">
+         <p class="entry-position-style">SOFTWARE ENGINEER</p>
+         <p class="entry-date-style">September, 2019 - October, 2020</p>
+       </div>
+       <ul class="description-style">
+         <li>
+           Developed custom Shapley value regression model to calculate importance of independent variables of data sets using sklearn, pandas, and numpy.
+         </li>
+         <li>
+           Created custom image uploader to Amazon s3 bucket using boto3 library.
+         </li>
+         <li>
+           Built RESTful API application using Flask framework and automated extensive API documentation pages using flask-restplus, pytest, and swagger, covering 95% of the code base.
+         </li>
+         <li>
+           Created an interactive graph using D3.js in Vue.js with data from Flask backend API. 
+         </li>
+       </ul>
+
+      <div class="sub-header">
+         <h2 class="section-style"> Education </h2>
+         <div class="line"></div>
+       </div>
+       <div class="flex-box">
+         <p class="entry-title-style">
+           University of British Columbia
+         </p>
+         <p class="entry-location-style">Kelowna, British Columbia</p>
+       </div>
+       <div class="flex-box">
+         <p class="entry-position-style">BACHELOR OF SCIENCE IN PHYSICS</p>
+         <p class="entry-date-style">2014 - 2018</p>
+       </div>
+     </main>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mrjunejune/test/snapshots/missing.html.snapshot	Wed Dec 24 09:15:55 2025 -0800
@@ -0,0 +1,5 @@
+HTTP/1.1 404 Not Found
+Content-Type: text/html
+Content-Length: 0
+Connection: close
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mrjunejune/test/snapshots/nonexistent.snapshot	Wed Dec 24 09:15:55 2025 -0800
@@ -0,0 +1,5 @@
+HTTP/1.1 404 Not Found
+Content-Type: text/html
+Content-Length: 0
+Connection: close
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mrjunejune/test/snapshots/root.snapshot	Wed Dec 24 09:15:55 2025 -0800
@@ -0,0 +1,245 @@
+HTTP/1.1 200 OK
+Content-Type: text/html; charset=utf-8
+Content-Length: 12120
+Connection: close
+
+<!doctype html>
+<html lang="en">
+  <head>
+    <BaseHead title="Resume" description="June's resume" />
+    <link rel="stylesheet" href="base.css" />
+    <link rel="stylesheet" href="resume.css" />
+  </head>
+  <body>
+     <main>
+       <div>
+         <p>Hi, my name is Juntae, but most people call me <b>June</b>.</p>
+         <p>I am a software engineer with experience spanning a wide range of companies, from small startups to FAANG. </p> 
+         <p>Feel free to check out my <a href="/resume.pdf"> resume </a> below, and if you're interested, don’t hesitate to contact me for contract work ranging from web/app development to embedded programming.</p>
+       </div>
+       <div class="info">
+         <p><span class="header-firstname-style"> JUNTAE </span><span class="header-lastname-style">PARK</span><p>
+         <p class="header-position-style"> FULL STACK DEVELOPER · SOFTWARE ENGINEER </p>
+         <div class="header-address-style"> 
+           Bay Area, CA, USA
+         </div>
+         <div class="header-social-style">
+           <p>📱(US) 650-531-1728 |📱(CA) 437-580-8026 | <a href="mailto:[email protected]"> ✉️  [email protected] </a>| <a href="https://github.com/mrjunejune"> 
+                <svg viewBox="0 0 16 16" aria-hidden="true" width="12" height="12">
+                  <path
+						fill="currentColor"
+						d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
+                        ></path>
+                </svg> mrjunejune
+           </a>| <a href="https://www.linkedin.com/in/junepark"> 
+              <svg width="12" height="12" fill="currentColor" class="bi bi-linkedin" viewBox="0 0 16 16">
+                <path d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z"/>
+              </svg>
+             junepark
+             </a>
+           </p>
+         </div>
+       </div>
+
+       <div class="sub-header">
+         <h2 class="section-style"> Summary </h2>
+         <div class="line"></div>
+       </div>
+       <p class="paragraph-style">Software Engineer with 8 years of hands-on experience across diverse tech stacks, from early-stage startups to FANG-scale systems. Adept in designing and delivering robust software solutions using modern languages, frameworks, and cloud platforms.  Open to impactful work.</p>
+       <div class="sub-header">
+
+         <h2 class="section-style"> Skills </h2>
+         <div class="line"></div>
+       </div>
+       <div class="paragraph-style">
+         <p>
+           <span class="entry-title-style"> Programming Languages:</span>
+           <span class="skill-type-style"> TypeScript, Python, C++/C, Ruby, Java, MATLAB </span>
+         </p>
+         <p> 
+           <span class="entry-title-style">Tools & Platforms:</span>
+           <span class="skill-type-style">Bazel, PostgresSQL, Mercurial, Git, Pands, Raylib, XCode</span>
+         </p>
+         <p>
+           <span class="entry-title-style"> Web Frameworks: </span>
+           <span class="skill-type-style"> Django, Rails, React, Flask</span>
+         </p>
+         <p>
+           <span class="entry-title-style"> DevOp:</span>
+           <span class="skill-type-style"> Plummi, Heroku, DigitalOcean, AWS, Google Cloud </span>
+         </p>
+         <p>
+           <span class="entry-title-style"> Language:</span>
+           <span class="skill-type-style"> English, Korean, Japanese </span>
+         </p>
+       </div>
+
+       <!-- Experiences -->
+      <div class="sub-header">
+         <h2 class="section-style"> Experience </h2>
+         <div class="line"></div>
+       </div>
+       <div class="flex-box">
+         <p class="entry-title-style">
+           <a href="https://www.meta.com/">Meta</a>
+         </p>
+         <p class="entry-location-style">San Francisco, CA, USA</p>
+       </div>
+       <div class="flex-box">
+         <p class="entry-position-style">SOFTWARE ENGINEER</p>
+         <p class="entry-date-style">Oct, 2024 - Present</p>
+       </div>
+         <ul class="description-style">
+           <li>
+             Took initiative on Channel Value Rule, targeting the 16% of ad traffic with both app and web destinations to improve value attribution and ROI.
+           </li>
+           <li>
+             Built full-stack features using React and Hack/GraphQL, contributing to scalable, production-ready systems.
+           </li>
+           <li>
+             Partnered with data science to design A/B tests and analyze revenue impact of ads destination.
+           </li>
+           <li>
+             Proposed and implemented alpha improvements to internal testing infrastructure, reducing test time by 50% and enhancing developer velocity.
+           </li>
+         </ul>
+       <div class="flex-box">
+         <p class="entry-title-style">
+           <a href="https://www.wmg.com/">Warner Music Group</a>
+         </p>
+         <p class="entry-location-style">Toronto, ON, Canada</p>
+       </div>
+       <div class="flex-box">
+         <p class="entry-position-style">TECHNICAL LEAD ENGINEER</p>
+         <p class="entry-date-style">July, 2023 - Sept, 2024</p>
+       </div>
+       <ul class="description-style">
+         <li>
+           Implements <a href="https://bazel.build/">bazel </a>structure for the company for TypeScript and JavaScript code base for hermiticity and stablishing standards for JavaScript and
+         </li>
+         <li>
+           TypeScript testing and code structures.
+         </li>
+         <li>
+           Led a team of five engineers in building GraphQL endpoints for client-facing applications using Apollo and AppSync, supporting over 2000 RPS and auto scaling depending on request values.
+         </li>
+         <li>
+           Improved application response times by up to 85% for graphQL response by updating database schema and SQL queries, eliminating N+1 queries and lack of indexes.
+         </li>
+         <li>
+           Developed CI/CD pipelines for backend structures.
+         </li>
+         <li>
+           Designed infrastructure for pub/sub, caching, and media processing logic.
+         </li>
+       </ul>
+
+       <div class="flex-box">
+         <p class="entry-title-style">
+           <a href="https://www.google.com/">Google</a>
+         </p>
+         <p class="entry-location-style">Toronto, ON, Canada</p>
+       </div>
+       <div class="flex-box">
+         <p class="entry-position-style">SOFTWARE ENGINEER</p>
+         <p class="entry-date-style">Feb, 2022 - July 2023</p>
+       </div>
+       <ul class="description-style">
+         <li>
+           Implements and maintained new features relating to App Script across google workspace platform including Gmail, sheets, and Docs.</li>
+         <li>
+           Improved a response time and render time of App Script hover card components.</li>
+         <li>
+           Collaborated with a team of developers to ensure timely and accurate delivery of features.</li>
+         <li>
+           Conducted user testing and gathered feedback to iterate on features for optimal user experience.</li>
+       </ul>
+
+       <div class="flex-box">
+         <p class="entry-title-style">
+           <a href="https://www.everlywell.com/">Everlywell</a>
+         </p>
+         <p class="entry-location-style">Toronto, ON, Canada</p>
+       </div>
+       <div class="flex-box">
+         <p class="entry-position-style">SOFTWARE ENGINEER</p>
+         <p class="entry-date-style">December, 2020 - Jan, 2022</p>
+       </div>
+       <ul class="description-style">
+         <li>
+           Maintained Amazon amplify apps to create and deploy React web applications for companies such as <a href="https://brooklynnets.everlywell.com/">NBA</a>, <a href="https://tinder.everlywell.com/">Tinder</a>, and other companies for COVID-19 at-home test kits.</li>
+         <li>
+           Implemented a script that helps accurately access and refund unused covid test kits; helping company save up to 200,000 USD.</li>
+         <li>
+           Created several Rails controllers for internal purposes; mocking end to end user experience for QA, mass refund features for CX department, and more, ultimately reducing support tickets amount by 50 percent.</li>
+         <li>
+           Implemented an audit table to help debug problems and logged which process was responsible for the change of the record using PaperTrail gems</li>
+       </ul>
+
+       <div class="flex-box">
+         <p class="entry-title-style">
+           <a href="https://www.spiria.com/">Spiria</a>
+         </p>
+         <p class="entry-location-style">Oakville, ON, Canada</p>
+       </div>
+       <div class="flex-box">
+         <p class="entry-position-style">SOFTWARE ENGINEER</p>
+         <p class="entry-date-style">October, 2018 - October, 2020</p>
+       </div>
+       <ul class="description-style">
+         <li>
+           Constructed RESTful API endpoints in multiple different frameworks such as Django, Ruby on Rails, and Flask and automated API documentation process using swagger.
+         </li>
+         <li>
+           Designed custom rake tasks for importing production data into newly updated data structure to meet client's needs.
+         </li>
+         <li>
+           Maintained or updated staging/productions servers. Debugged problems in production postgres database using ssh and postgres console on Heroku or AWS servers
+         </li>
+         <li>
+           Collaborated in creating automation python scripts for websites and application using selenium covering for QA eliminating 80% of QA's manual work
+         </li>
+       </ul>
+
+       <div class="flex-box">
+         <p class="entry-title-style">
+           <a href="https://www.apexscore.ai/">Apex Score</a>
+         </p>
+         <p class="entry-location-style">Oakville, ON, Canada</p>
+       </div>
+       <div class="flex-box">
+         <p class="entry-position-style">SOFTWARE ENGINEER</p>
+         <p class="entry-date-style">September, 2019 - October, 2020</p>
+       </div>
+       <ul class="description-style">
+         <li>
+           Developed custom Shapley value regression model to calculate importance of independent variables of data sets using sklearn, pandas, and numpy.
+         </li>
+         <li>
+           Created custom image uploader to Amazon s3 bucket using boto3 library.
+         </li>
+         <li>
+           Built RESTful API application using Flask framework and automated extensive API documentation pages using flask-restplus, pytest, and swagger, covering 95% of the code base.
+         </li>
+         <li>
+           Created an interactive graph using D3.js in Vue.js with data from Flask backend API. 
+         </li>
+       </ul>
+
+      <div class="sub-header">
+         <h2 class="section-style"> Education </h2>
+         <div class="line"></div>
+       </div>
+       <div class="flex-box">
+         <p class="entry-title-style">
+           University of British Columbia
+         </p>
+         <p class="entry-location-style">Kelowna, British Columbia</p>
+       </div>
+       <div class="flex-box">
+         <p class="entry-position-style">BACHELOR OF SCIENCE IN PHYSICS</p>
+         <p class="entry-date-style">2014 - 2018</p>
+       </div>
+     </main>
+  </body>
+</html>
--- a/seobeo/BUILD	Wed Dec 24 06:34:19 2025 -0800
+++ b/seobeo/BUILD	Wed Dec 24 09:15:55 2025 -0800
@@ -1,5 +1,6 @@
 load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
 load("@rules_cc//cc:cc_library.bzl", "cc_library")
+load("@rules_cc//cc:cc_test.bzl", "cc_test")
 
 
 filegroup(
@@ -7,25 +8,78 @@
   srcs = [
     "seobeo.h",
     "seobeo_internal.h",
+    "snapshot_creator.h",
   ],
   visibility = ["//visibility:public"],
 )
 
+# Server-only target (no SSL, no OpenSSL dependency)
 alias(
-  name = "seobeo",
+  name = "seobeo_server",
   actual = select({
-    "//config:macos":  ":seobeo_macos",
-    "//config:linux":  ":seobeo_linux",
-    "//conditions:default": ":seobeo_linux",
+    "//config:macos":  ":seobeo_server_macos",
+    "//config:linux":  ":seobeo_server_linux",
+    "//conditions:default": ":seobeo_server_linux",
   }),
   visibility = ["//visibility:public"],
 )
 
 cc_library(
-  name = "seobeo_macos",
+  name = "seobeo_server_macos",
+  srcs = [
+    "s_linux_network.c",
+    "s_web.c",
+    "s_ssl.c",
+    "os/s_macos_edge.c",
+  ],
+  hdrs = [":seobeo_hdrs"],
+  deps = [
+    "//dowa:dowa",
+  ],
+  defines = ["SEOBEO_NO_SSL"],
+  target_compatible_with = [
+    "@platforms//os:osx",
+  ],
+  visibility = ["//visibility:public"],
+)
+
+cc_library(
+  name = "seobeo_server_linux",
   srcs = [
     "s_linux_network.c",
     "s_web.c",
+    "s_ssl.c",
+    "os/s_linux_edge.c",
+  ],
+  hdrs = [":seobeo_hdrs"],
+  deps = [
+    "//dowa:dowa",
+  ],
+  defines = ["SEOBEO_NO_SSL"],
+  target_compatible_with = [
+    "@platforms//os:linux",
+  ],
+  visibility = ["//visibility:public"],
+)
+
+# Client target with SSL support (full functionality)
+alias(
+  name = "seobeo_client",
+  actual = select({
+    "//config:macos":  ":seobeo_client_macos",
+    "//config:linux":  ":seobeo_client_linux",
+    "//conditions:default": ":seobeo_client_linux",
+  }),
+  visibility = ["//visibility:public"],
+)
+
+cc_library(
+  name = "seobeo_client_macos",
+  srcs = [
+    "s_linux_network.c",
+    "s_web.c",
+    "s_ssl.c",
+    "snapshot_creator.c",
     "os/s_macos_edge.c",
   ],
   hdrs = [":seobeo_hdrs"],
@@ -40,10 +94,12 @@
 )
 
 cc_library(
-  name = "seobeo_linux",
+  name = "seobeo_client_linux",
   srcs = [
     "s_linux_network.c",
     "s_web.c",
+    "s_ssl.c",
+    "snapshot_creator.c",
     "os/s_linux_edge.c",
   ],
   hdrs = [":seobeo_hdrs"],
@@ -57,3 +113,19 @@
   visibility = ["//visibility:public"],
 )
 
+# Default target for backward compatibility (points to client with SSL)
+alias(
+  name = "seobeo",
+  actual = ":seobeo_client",
+  visibility = ["//visibility:public"],
+)
+
+# Tests
+cc_test(
+  name = "seobeo_http_client_test",
+  srcs = ["seobeo_http_client_test.c"],
+  deps = [":seobeo_client"],
+  size = "small",
+  timeout = "short",
+)
+
--- a/seobeo/s_linux_network.c	Wed Dec 24 06:34:19 2025 -0800
+++ b/seobeo/s_linux_network.c	Wed Dec 24 09:15:55 2025 -0800
@@ -105,30 +105,17 @@
 
   p_handle->socket = socket_fd;
   p_handle->type = SEOBEO_STREAM_TYPE_CLIENT;
+
+  p_handle->ssl_ctx = NULL;
+  p_handle->ssl = NULL;
+
   if (use_tls)
   {
-    printf("USE SSL\n\n");
-    Seobeo_Web_SSL_Init();
-    p_handle->ssl_ctx = SSL_CTX_new(TLS_client_method());
-    SSL_CTX_set_default_verify_paths(p_handle->ssl_ctx);
-
-    p_handle->ssl = SSL_new(p_handle->ssl_ctx);
-    SSL_set_fd(p_handle->ssl, p_handle->socket);
-
-    SSL_set_tlsext_host_name(p_handle->ssl, host);
-    // Blocking for TSL handshake
-  	fcntl(socket_fd, F_SETFL, 0);
-
-    if (SSL_connect(p_handle->ssl) != 1)
+    if (Seobeo_SSL_Setup_Client(p_handle, host, socket_fd) != 0)
     {
-      fprintf(stderr, "SSL_connect failed\n");
-      ERR_print_errors_fp(stderr);
+      free(p_handle);
       return NULL;
     }
-  }else
-  {
-    p_handle->ssl_ctx = NULL;
-    p_handle->ssl = NULL;
   }
   p_handle->connected = true;
 
@@ -212,18 +199,7 @@
   if (p_handle->host) Dowa_Free(p_handle->host);
   if (p_handle->port) Dowa_Free(p_handle->port);
 
-  if (p_handle->ssl)
-  {
-    SSL_shutdown(p_handle->ssl);
-    SSL_free(p_handle->ssl);
-    p_handle->ssl = NULL;
-  }
-
-  if (p_handle->ssl_ctx)
-  {
-    SSL_CTX_free(p_handle->ssl_ctx);
-    p_handle->ssl_ctx = NULL;
-  }
+  Seobeo_SSL_Cleanup(p_handle);
 
   if (p_handle->socket) {
     printf("Closing: %d", p_handle->socket);
@@ -248,18 +224,9 @@
   {
     if (p_handle->ssl)
     {
-      int n = SSL_write(p_handle->ssl, p_handle->write_buffer + sent, total - sent);
-      if (n < 0)
-      {
-        int err = SSL_get_error(p_handle->ssl, n);
-        if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)
-        {
-          // caller must wait for socket readiness and retry
-          return 0;
-        }
-        ERR_print_errors_fp(stderr);
-        return -1;
-      }
+      int n = Seobeo_SSL_Write(p_handle, p_handle->write_buffer + sent, total - sent);
+      if (n < 0) return -1;
+      if (n == 0) return 0;  // would block
       sent += (uint32)n;
     }else
     {
@@ -364,22 +331,9 @@
 
   if (p_handle->ssl)
   {
-    read_size = (int32)SSL_read(p_handle->ssl, p_handle->read_buffer,
-                                free_space);
-    if (read_size <= 0)
-    {
-      int err = SSL_get_error(p_handle->ssl, read_size);
-      switch (err)
-      {
-        case SSL_ERROR_WANT_READ:
-        case SSL_ERROR_WANT_WRITE:
-          return 0;
-        case SSL_ERROR_ZERO_RETURN:
-        default:
-          // TODO: Handle these errors
-          return -2;
-      }
-    }
+    read_size = Seobeo_SSL_Read(p_handle, p_handle->read_buffer + p_handle->read_buffer_len, free_space);
+    if (read_size < 0) return read_size;  // -1 for error, -2 for closed
+    if (read_size == 0) return 0;  // would block
   }else
   {
     read_size = (int32)read(p_handle->socket,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/seobeo/s_ssl.c	Wed Dec 24 09:15:55 2025 -0800
@@ -0,0 +1,144 @@
+#include "seobeo/seobeo.h"
+
+#ifndef SEOBEO_NO_SSL
+
+void Seobeo_Web_SSL_Init()
+{
+  SSL_load_error_strings();
+  OpenSSL_add_ssl_algorithms();
+}
+
+void Seobeo_Web_SSL_Cleanup(void)
+{
+  EVP_cleanup(); // I don't think these are needed...
+}
+
+int Seobeo_SSL_Setup_Client(Seobeo_Handle *p_handle, const char *host, int socket_fd)
+{
+  if (!p_handle) return -1;
+
+  printf("USE SSL\n\n");
+  Seobeo_Web_SSL_Init();
+  p_handle->ssl_ctx = SSL_CTX_new(TLS_client_method());
+  if (!p_handle->ssl_ctx)
+  {
+    fprintf(stderr, "SSL_CTX_new failed\n");
+    ERR_print_errors_fp(stderr);
+    return -1;
+  }
+
+  SSL_CTX_set_default_verify_paths(p_handle->ssl_ctx);
+
+  p_handle->ssl = SSL_new(p_handle->ssl_ctx);
+  if (!p_handle->ssl)
+  {
+    fprintf(stderr, "SSL_new failed\n");
+    ERR_print_errors_fp(stderr);
+    SSL_CTX_free(p_handle->ssl_ctx);
+    p_handle->ssl_ctx = NULL;
+    return -1;
+  }
+
+  SSL_set_fd(p_handle->ssl, socket_fd);
+  SSL_set_tlsext_host_name(p_handle->ssl, host);
+
+  // Blocking for TLS handshake
+  fcntl(socket_fd, F_SETFL, 0);
+
+  if (SSL_connect(p_handle->ssl) != 1)
+  {
+    fprintf(stderr, "SSL_connect failed\n");
+    ERR_print_errors_fp(stderr);
+    SSL_free(p_handle->ssl);
+    SSL_CTX_free(p_handle->ssl_ctx);
+    p_handle->ssl = NULL;
+    p_handle->ssl_ctx = NULL;
+    return -1;
+  }
+
+  return 0;
+}
+
+void Seobeo_SSL_Cleanup(Seobeo_Handle *p_handle)
+{
+  if (!p_handle) return;
+
+  if (p_handle->ssl)
+  {
+    SSL_shutdown(p_handle->ssl);
+    SSL_free(p_handle->ssl);
+    p_handle->ssl = NULL;
+  }
+
+  if (p_handle->ssl_ctx)
+  {
+    SSL_CTX_free(p_handle->ssl_ctx);
+    p_handle->ssl_ctx = NULL;
+  }
+}
+
+int32 Seobeo_SSL_Write(Seobeo_Handle *p_handle, const uint8 *data, uint32 length)
+{
+  if (!p_handle || !p_handle->ssl) return -1;
+
+  int n = SSL_write(p_handle->ssl, data, length);
+  if (n < 0)
+  {
+    int err = SSL_get_error(p_handle->ssl, n);
+    if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)
+    {
+      // caller must wait for socket readiness and retry
+      return 0;
+    }
+    ERR_print_errors_fp(stderr);
+    return -1;
+  }
+  return n;
+}
+
+int32 Seobeo_SSL_Read(Seobeo_Handle *p_handle, uint8 *buffer, uint32 length)
+{
+  if (!p_handle || !p_handle->ssl) return -1;
+
+  int32 read_size = (int32)SSL_read(p_handle->ssl, buffer, length);
+  if (read_size <= 0)
+  {
+    int err = SSL_get_error(p_handle->ssl, read_size);
+    switch (err)
+    {
+      case SSL_ERROR_WANT_READ:
+      case SSL_ERROR_WANT_WRITE:
+        return 0;
+      case SSL_ERROR_ZERO_RETURN:
+      default:
+        // TODO: Handle these errors
+        return -2;
+    }
+  }
+  return read_size;
+}
+
+#else
+
+// Stub implementations when SSL is disabled
+void Seobeo_Web_SSL_Init() {}
+void Seobeo_Web_SSL_Cleanup(void) {}
+int Seobeo_SSL_Setup_Client(Seobeo_Handle *p_handle, const char *host, int socket_fd)
+{
+  (void)p_handle; (void)host; (void)socket_fd;
+  fprintf(stderr, "SSL support not compiled in\n");
+  return -1;
+}
+void Seobeo_SSL_Cleanup(Seobeo_Handle *p_handle) { (void)p_handle; }
+int32 Seobeo_SSL_Write(Seobeo_Handle *p_handle, const uint8 *data, uint32 length)
+{
+  (void)p_handle; (void)data; (void)length;
+  return -1;
+}
+int32 Seobeo_SSL_Read(Seobeo_Handle *p_handle, uint8 *buffer, uint32 length)
+{
+  (void)p_handle; (void)buffer; (void)length;
+  return -1;
+}
+
+#endif
--- a/seobeo/s_web.c	Wed Dec 24 06:34:19 2025 -0800
+++ b/seobeo/s_web.c	Wed Dec 24 09:15:55 2025 -0800
@@ -578,14 +578,3 @@
   Seobeo_Handle_Destroy(h);
   return 0;
 }
-
-void Seobeo_Web_SSL_Init()
-{
-  SSL_load_error_strings();
-  OpenSSL_add_ssl_algorithms();
-}
-
-void Seobeo_Web_SSL_Cleanup(void)
-{
-  EVP_cleanup(); // I don't think these are needed...
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/seobeo/seobeo_http_client_test.c	Wed Dec 24 09:15:55 2025 -0800
@@ -0,0 +1,174 @@
+#include "seobeo/seobeo.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+// Test basic HTTP client functionality
+void test_http_client_without_ssl() {
+  printf("Testing HTTP client (non-SSL)...\n");
+
+  // Create a client handle without TLS
+  Seobeo_Handle *h = Seobeo_Stream_Handle_Client_Create("example.com", "80", FALSE);
+
+  if (!h || h->socket < 0) {
+    printf("  ✗ Failed to create client handle\n");
+    if (h) Seobeo_Handle_Destroy(h);
+    return;
+  }
+
+  printf("  ✓ Client handle created successfully\n");
+
+  // Generate and send HTTP request
+  char request[4096];
+  int request_len = sprintf(
+    request,
+    "GET / HTTP/1.1\r\n"
+    "Host: example.com\r\n"
+    "Connection: close\r\n"
+    "\r\n"
+  );
+
+  Seobeo_Handle_Queue(h, (uint8*)request, (uint32)request_len);
+  int flush_result = Seobeo_Handle_Flush(h);
+
+  if (flush_result < 0) {
+    printf("  ✗ Failed to send request\n");
+    Seobeo_Handle_Destroy(h);
+    return;
+  }
+
+  printf("  ✓ Request sent successfully\n");
+
+  // Read response
+  int bytes_read = 0;
+  int total_bytes = 0;
+  int attempts = 0;
+  const int max_attempts = 100;
+
+  while (attempts++ < max_attempts) {
+    bytes_read = Seobeo_Handle_Read(h);
+    if (bytes_read > 0) {
+      total_bytes += bytes_read;
+    } else if (bytes_read == -2) {
+      // Connection closed
+      break;
+    } else if (bytes_read == 0) {
+      // Would block, wait a bit
+      usleep(10000); // 10ms
+      continue;
+    } else {
+      printf("  ✗ Error reading response\n");
+      Seobeo_Handle_Destroy(h);
+      return;
+    }
+  }
+
+  if (total_bytes > 0) {
+    printf("  ✓ Received response (%d bytes)\n", total_bytes);
+
+    // Check if we got HTTP response
+    if (h->read_buffer_len > 0 && strstr((char*)h->read_buffer, "HTTP/1.1") != NULL) {
+      printf("  ✓ Valid HTTP response received\n");
+    } else {
+      printf("  ✗ Invalid HTTP response\n");
+    }
+  } else {
+    printf("  ⚠ No response received (this may be a network issue)\n");
+  }
+
+  Seobeo_Handle_Destroy(h);
+  printf("  ✓ Client handle destroyed\n");
+}
+
+void test_http_client_with_ssl() {
+  printf("\nTesting HTTP client (with SSL)...\n");
+
+#ifdef SEOBEO_NO_SSL
+  printf("  ⚠ SSL not compiled in, skipping SSL test\n");
+  return;
+#else
+  // Create a client handle with TLS
+  Seobeo_Handle *h = Seobeo_Stream_Handle_Client_Create("example.com", "443", TRUE);
+
+  if (!h || h->socket < 0) {
+    printf("  ✗ Failed to create SSL client handle\n");
+    if (h) Seobeo_Handle_Destroy(h);
+    return;
+  }
+
+  printf("  ✓ SSL client handle created successfully\n");
+  printf("  ✓ SSL handshake completed\n");
+
+  // Generate and send HTTPS request
+  char request[4096];
+  int request_len = sprintf(
+    request,
+    "GET / HTTP/1.1\r\n"
+    "Host: example.com\r\n"
+    "Connection: close\r\n"
+    "\r\n"
+  );
+
+  Seobeo_Handle_Queue(h, (uint8*)request, (uint32)request_len);
+  int flush_result = Seobeo_Handle_Flush(h);
+
+  if (flush_result < 0) {
+    printf("  ✗ Failed to send SSL request\n");
+    Seobeo_Handle_Destroy(h);
+    return;
+  }
+
+  printf("  ✓ SSL request sent successfully\n");
+
+  // Read response
+  int bytes_read = 0;
+  int total_bytes = 0;
+  int attempts = 0;
+  const int max_attempts = 100;
+
+  while (attempts++ < max_attempts) {
+    bytes_read = Seobeo_Handle_Read(h);
+    if (bytes_read > 0) {
+      total_bytes += bytes_read;
+    } else if (bytes_read == -2) {
+      // Connection closed
+      break;
+    } else if (bytes_read == 0) {
+      // Would block, wait a bit
+      usleep(10000); // 10ms
+      continue;
+    } else {
+      printf("  ✗ Error reading SSL response\n");
+      Seobeo_Handle_Destroy(h);
+      return;
+    }
+  }
+
+  if (total_bytes > 0) {
+    printf("  ✓ Received SSL response (%d bytes)\n", total_bytes);
+
+    // Check if we got HTTP response
+    if (h->read_buffer_len > 0 && strstr((char*)h->read_buffer, "HTTP/1.1") != NULL) {
+      printf("  ✓ Valid HTTPS response received\n");
+    } else {
+      printf("  ✗ Invalid HTTPS response\n");
+    }
+  } else {
+    printf("  ⚠ No SSL response received (this may be a network issue)\n");
+  }
+
+  Seobeo_Handle_Destroy(h);
+  printf("  ✓ SSL client handle destroyed\n");
+#endif
+}
+
+int main() {
+  printf("=== Seobeo HTTP Client Tests ===\n\n");
+
+  test_http_client_without_ssl();
+  test_http_client_with_ssl();
+
+  printf("\n=== Tests Complete ===\n");
+  return 0;
+}
--- a/seobeo/seobeo_internal.h	Wed Dec 24 06:34:19 2025 -0800
+++ b/seobeo/seobeo_internal.h	Wed Dec 24 09:15:55 2025 -0800
@@ -1,9 +1,16 @@
 #ifndef SEOBEO_SERVER_INTERNAL_H
 #define SEOBEO_SERVER_INTERNAL_H
 
-// SSL
-#include <openssl/ssl.h> 
+// SSL - conditionally included
+#ifndef SEOBEO_NO_SSL
+#include <openssl/ssl.h>
 #include <openssl/err.h>
+#define SSL_CTX_TYPE SSL_CTX
+#define SSL_TYPE SSL
+#else
+#define SSL_CTX_TYPE void
+#define SSL_TYPE void
+#endif
 
 #ifndef DIRECTORY
 #define DIRECTORY
@@ -22,9 +29,9 @@
 
   char        *host;
   char        *port;
- 
-  SSL_CTX     *ssl_ctx;
-  SSL         *ssl;
+
+  SSL_CTX_TYPE     *ssl_ctx;
+  SSL_TYPE         *ssl;
 
   uint8       *read_buffer;
   uint32      read_buffer_capacity;
@@ -64,6 +71,10 @@
 // --- SSL --- //
 extern void           Seobeo_Web_SSL_Init();
 extern void           Seobeo_Web_SSL_Cleanup(); // Not used
+extern int            Seobeo_SSL_Setup_Client(Seobeo_Handle *p_handle, const char *host, int socket_fd);
+extern void           Seobeo_SSL_Cleanup(Seobeo_Handle *p_handle);
+extern int32          Seobeo_SSL_Write(Seobeo_Handle *p_handle, const uint8 *data, uint32 length);
+extern int32          Seobeo_SSL_Read(Seobeo_Handle *p_handle, uint8 *buffer, uint32 length);
 
 // --- Serving using Edge --- //
 extern void          *Seobeo_Web_Edge_Worker(void *vargs); 
@@ -71,4 +82,3 @@
 extern void           Seobeo_Web_Edge_2(Seobeo_Handle *p_server_handle, Dowa_HashMap *p_html_cache);
 
 #endif
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/seobeo/snapshot_creator.c	Wed Dec 24 09:15:55 2025 -0800
@@ -0,0 +1,280 @@
+#include "seobeo/snapshot_creator.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define MAX_RESPONSE_SIZE (1024 * 1024)
+
+// Helper: Convert URL path to filename
+static void path_to_filename(const char *path, char *filename, size_t max_len)
+{
+  if (strcmp(path, "/") == 0)
+  {
+    snprintf(filename, max_len, "root.snapshot");
+    return;
+  }
+
+  const char *p = path;
+  if (*p == '/')
+  {
+    p++;
+  }
+
+  char *out = filename;
+  size_t remaining = max_len - 1;
+
+  while (*p && remaining > 0)
+  {
+    if (*p == '/')
+    {
+      *out++ = '_';
+      remaining--;
+    }
+    else
+    {
+      *out++ = *p;
+      remaining--;
+    }
+    p++;
+  }
+
+  snprintf(out, remaining, ".snapshot");
+}
+
+// Helper: Create directory recursively
+static int create_directory(const char *path)
+{
+  char tmp[1024];
+  char *p = NULL;
+  size_t len;
+
+  snprintf(tmp, sizeof(tmp), "%s", path);
+  len = strlen(tmp);
+  if (tmp[len - 1] == '/')
+  {
+    tmp[len - 1] = 0;
+  }
+
+  for (p = tmp + 1; *p; p++)
+  {
+    if (*p == '/')
+    {
+      *p = 0;
+      mkdir(tmp, 0755);
+      *p = '/';
+    }
+  }
+  return mkdir(tmp, 0755);
+}
+
+// Helper: Generate HTTP GET request
+static int generate_http_get(char *buffer, size_t size, const char *path, const char *host)
+{
+  return snprintf(
+    buffer, size,
+    "GET %s HTTP/1.1\r\n"
+    "Host: %s\r\n"
+    "Connection: close\r\n"
+    "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
+    "User-Agent: SeobeoSnapshotCreator/1.0\r\n"
+    "\r\n",
+    path, host
+  );
+}
+
+// Helper: Read full HTTP response
+static char* read_full_response(Seobeo_Handle *client, size_t *len_out)
+{
+  char *response = malloc(MAX_RESPONSE_SIZE);
+  if (!response)
+  {
+    return NULL;
+  }
+
+  size_t total = 0;
+  int attempts = 0;
+  const int max_attempts = 100;
+
+  while (attempts++ < max_attempts && total < MAX_RESPONSE_SIZE - 1)
+  {
+    int n = Seobeo_Handle_Read(client);
+
+    if (n > 0)
+    {
+      size_t to_copy = client->read_buffer_len;
+      if (total + to_copy > MAX_RESPONSE_SIZE - 1)
+      {
+        to_copy = MAX_RESPONSE_SIZE - 1 - total;
+      }
+
+      memcpy(response + total, client->read_buffer, to_copy);
+      total += to_copy;
+      Seobeo_Handle_Consume(client, (uint32)to_copy);
+    }
+    else if (n == -2)
+    {
+      break;
+    }
+    else if (n == 0)
+    {
+      usleep(10000);
+      continue;
+    }
+    else
+    {
+      free(response);
+      return NULL;
+    }
+  }
+
+  response[total] = '\0';
+  *len_out = total;
+  return response;
+}
+
+// Helper: Write snapshot to file
+static int write_snapshot(const char *filepath, const char *data, size_t size)
+{
+  // Create directory if needed
+  char dir_copy[1024];
+  snprintf(dir_copy, sizeof(dir_copy), "%s", filepath);
+
+  char *last_slash = strrchr(dir_copy, '/');
+  if (last_slash)
+  {
+    *last_slash = '\0';
+    create_directory(dir_copy);
+  }
+
+  FILE *f = fopen(filepath, "wb");
+  if (!f)
+  {
+    perror("fopen");
+    return -1;
+  }
+
+  size_t written = fwrite(data, 1, size, f);
+  fclose(f);
+
+  return (written == size) ? 0 : -1;
+}
+
+int Seobeo_Snapshot_Create(const SnapshotConfig *config)
+{
+  if (!config || !config->path || !config->snapshot_dir || !config->host || !config->port)
+  {
+    fprintf(stderr, "Invalid snapshot config\n");
+    return -1;
+  }
+
+  printf("Creating snapshot: %s (expecting %d)\n", config->path, config->expected_status);
+
+  // Connect to server
+  Seobeo_Handle *client = Seobeo_Stream_Handle_Client_Create(config->host, config->port, FALSE);
+  if (!client || client->socket < 0)
+  {
+    fprintf(stderr, "  ✗ Failed to connect to %s:%s\n", config->host, config->port);
+    if (client)
+    {
+      Seobeo_Handle_Destroy(client);
+    }
+    return -1;
+  }
+
+  // Send GET request
+  char request[4096];
+  int req_len = generate_http_get(request, sizeof(request), config->path, config->host);
+  Seobeo_Handle_Queue(client, (uint8*)request, (uint32)req_len);
+
+  if (Seobeo_Handle_Flush(client) < 0)
+  {
+    fprintf(stderr, "  ✗ Failed to send request\n");
+    Seobeo_Handle_Destroy(client);
+    return -1;
+  }
+
+  // Read response
+  size_t response_len = 0;
+  char *response = read_full_response(client, &response_len);
+  Seobeo_Handle_Destroy(client);
+
+  if (!response || response_len == 0)
+  {
+    fprintf(stderr, "  ✗ Failed to read response\n");
+    if (response)
+    {
+      free(response);
+    }
+    return -1;
+  }
+
+  // Parse status code
+  int status = -1;
+  const char *status_line = strstr(response, "HTTP/1.1 ");
+  if (!status_line)
+  {
+    status_line = strstr(response, "HTTP/1.0 ");
+  }
+  if (status_line)
+  {
+    sscanf(status_line + 9, "%d", &status);
+  }
+
+  if (status != config->expected_status)
+  {
+    fprintf(stderr, "  ✗ Status mismatch: expected %d, got %d\n",
+            config->expected_status, status);
+    free(response);
+    return -1;
+  }
+
+  printf("  ✓ Status: %d\n", status);
+
+  // Generate snapshot filename
+  char filename[256];
+  path_to_filename(config->path, filename, sizeof(filename));
+
+  char filepath[1024];
+  snprintf(filepath, sizeof(filepath), "%s/%s", config->snapshot_dir, filename);
+
+  // Write snapshot
+  if (write_snapshot(filepath, response, response_len) == 0)
+  {
+    printf("  ✓ Snapshot saved: %s (%zu bytes)\n", filepath, response_len);
+    free(response);
+    return 0;
+  }
+  else
+  {
+    fprintf(stderr, "  ✗ Failed to write snapshot: %s\n", filepath);
+    free(response);
+    return -1;
+  }
+}
+
+int Seobeo_Snapshots_Create_Batch(const SnapshotConfig configs[], int count)
+{
+  int failed = 0;
+  int passed = 0;
+
+  for (int i = 0; i < count; i++)
+  {
+    if (Seobeo_Snapshot_Create(&configs[i]) == 0)
+    {
+      passed++;
+    }
+    else
+    {
+      failed++;
+    }
+    printf("\n");
+  }
+
+  printf("=== Summary ===\n");
+  printf("Created: %d\n", passed);
+  printf("Failed: %d\n", failed);
+
+  return (failed == 0) ? 0 : -1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/seobeo/snapshot_creator.h	Wed Dec 24 09:15:55 2025 -0800
@@ -0,0 +1,21 @@
+#ifndef SEOBEO_SNAPSHOT_CREATOR_H
+#define SEOBEO_SNAPSHOT_CREATOR_H
+
+#include "seobeo/seobeo.h"
+
+typedef struct
+{
+  const char *path;
+  int expected_status;
+  const char *snapshot_dir;
+  const char *host;
+  const char *port;
+} SnapshotConfig;
+
+// Create a snapshot for a single path
+int Seobeo_Snapshot_Create(const SnapshotConfig *config);
+
+// Create snapshots for multiple paths
+int Seobeo_Snapshots_Create_Batch(const SnapshotConfig configs[], int count);
+
+#endif