view seobeo/tests/seobeo_http_framing_test.c @ 281:c57149ad216e default tip

Copilot-Session: f68442b1-fa8f-46a0-9689-81710613bbd4
author MrJuneJune <me@mrjunejune.com>
date Tue, 18 Aug 2026 22:18:15 -0700
parents b3b547563ec7
children
line wrap: on
line source

#include "seobeo/seobeo.h"

#include <arpa/inet.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>

typedef struct {
  int listener;
} Framing_Server;

static void *serve_close_delimited_response(void *argument)
{
  Framing_Server *server = argument;
  for (int response_index = 0; response_index < 2; response_index++) {
    int client = accept(server->listener, NULL, NULL);
    if (client < 0)
      continue;
    char request[1024];
    (void)read(client, request, sizeof(request));
    if (response_index == 0) {
      const char response[] =
          "HTTP/1.1 200 OK\r\n"
          "Content-Type: text/plain\r\n"
          "Connection: close\r\n"
          "\r\n"
          "buffered-body";
      (void)write(client, response, sizeof(response) - 1);
    } else {
      const char response[] =
          "HTTP/1.1 200 OK\r\n"
          "Content-Type: application/json\r\n"
          "Transfer-Encoding: chunked\r\n"
          "Connection: close\r\n"
          "\r\n"
          "7;sample=yes\r\n"
          "{\"ok\":t\r\n"
          "4\r\n"
          "rue}\r\n"
          "0\r\n"
          "X-Trailer: ignored\r\n"
          "\r\n";
      (void)write(client, response, sizeof(response) - 1);
    }
    close(client);
  }
  return NULL;
}

int main(void)
{
  int listener = socket(AF_INET, SOCK_STREAM, 0);
  if (listener < 0)
    return 1;

  struct sockaddr_in address = {
    .sin_family = AF_INET,
    .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
    .sin_port = 0,
  };
  if (bind(listener, (struct sockaddr *)&address, sizeof(address)) != 0 ||
      listen(listener, 1) != 0) {
    close(listener);
    return 1;
  }

  socklen_t address_length = sizeof(address);
  if (getsockname(listener, (struct sockaddr *)&address, &address_length) != 0) {
    close(listener);
    return 1;
  }

  Framing_Server server = {.listener = listener};
  pthread_t thread;
  if (pthread_create(&thread, NULL, serve_close_delimited_response, &server) != 0) {
    close(listener);
    return 1;
  }

  char url[128];
  snprintf(
      url, sizeof(url), "http://127.0.0.1:%u/close",
      ntohs(address.sin_port));
  Seobeo_Client_Request *request = Seobeo_Client_Request_Create(url);
  Seobeo_Client_Request_Set_Timeout_Milliseconds(request, 1000);
  Seobeo_Client_Response *response = Seobeo_Client_Request_Execute(request);

  int failed = !response ||
               response->status_code != 200 ||
               response->body_length != strlen("buffered-body") ||
               memcmp(response->body, "buffered-body", strlen("buffered-body")) != 0;
  if (failed)
    fprintf(stderr, "Close-delimited response body was not preserved\n");

  Seobeo_Client_Response_Destroy(response);
  Seobeo_Client_Request_Destroy(request);

  snprintf(
      url, sizeof(url), "http://127.0.0.1:%u/chunked",
      ntohs(address.sin_port));
  request = Seobeo_Client_Request_Create(url);
  Seobeo_Client_Request_Set_Timeout_Milliseconds(request, 1000);
  response = Seobeo_Client_Request_Execute(request);
  int chunked_failed = !response ||
      response->status_code != 200 ||
      response->body_length != strlen("{\"ok\":true}") ||
      memcmp(response->body, "{\"ok\":true}", strlen("{\"ok\":true}")) != 0;
  if (chunked_failed)
    fprintf(stderr, "Chunked response body was not decoded\n");
  failed = failed || chunked_failed;

  Seobeo_Client_Response_Destroy(response);
  Seobeo_Client_Request_Destroy(request);
  pthread_join(thread, NULL);
  close(listener);
  return failed;
}