Mercurial
comparison seobeo/tests/seobeo_sse_server_test.c @ 257:609d3c6aff4e
[seobeo] Add persistent SSE streams
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 04 Aug 2026 16:49:11 -0700 |
| parents | |
| children | 1f9877b637e9 |
comparison
equal
deleted
inserted
replaced
| 256:30c2196d03d4 | 257:609d3c6aff4e |
|---|---|
| 1 #include "seobeo/seobeo.h" | |
| 2 | |
| 3 #include <assert.h> | |
| 4 #include <errno.h> | |
| 5 #include <stdio.h> | |
| 6 #include <stdlib.h> | |
| 7 #include <string.h> | |
| 8 #include <sys/socket.h> | |
| 9 #include <sys/wait.h> | |
| 10 #include <unistd.h> | |
| 11 | |
| 12 static int reserve_port(void) | |
| 13 { | |
| 14 int socket_fd = socket(AF_INET, SOCK_STREAM, 0); | |
| 15 assert(socket_fd >= 0); | |
| 16 struct sockaddr_in address = { | |
| 17 .sin_family = AF_INET, | |
| 18 .sin_addr.s_addr = htonl(INADDR_LOOPBACK), | |
| 19 .sin_port = 0, | |
| 20 }; | |
| 21 assert(bind( | |
| 22 socket_fd, | |
| 23 (struct sockaddr *)&address, | |
| 24 sizeof(address)) == 0); | |
| 25 socklen_t length = sizeof(address); | |
| 26 assert(getsockname( | |
| 27 socket_fd, | |
| 28 (struct sockaddr *)&address, | |
| 29 &length) == 0); | |
| 30 int port = ntohs(address.sin_port); | |
| 31 close(socket_fd); | |
| 32 return port; | |
| 33 } | |
| 34 | |
| 35 static int connect_to(int port) | |
| 36 { | |
| 37 int socket_fd = socket(AF_INET, SOCK_STREAM, 0); | |
| 38 if (socket_fd < 0) | |
| 39 return -1; | |
| 40 struct timeval timeout = { | |
| 41 .tv_sec = 3, | |
| 42 }; | |
| 43 setsockopt( | |
| 44 socket_fd, | |
| 45 SOL_SOCKET, | |
| 46 SO_RCVTIMEO, | |
| 47 &timeout, | |
| 48 sizeof(timeout)); | |
| 49 struct sockaddr_in address = { | |
| 50 .sin_family = AF_INET, | |
| 51 .sin_addr.s_addr = htonl(INADDR_LOOPBACK), | |
| 52 .sin_port = htons((uint16)port), | |
| 53 }; | |
| 54 if (connect( | |
| 55 socket_fd, | |
| 56 (struct sockaddr *)&address, | |
| 57 sizeof(address)) != 0) | |
| 58 { | |
| 59 close(socket_fd); | |
| 60 return -1; | |
| 61 } | |
| 62 return socket_fd; | |
| 63 } | |
| 64 | |
| 65 static void send_request(int socket_fd, const char *path) | |
| 66 { | |
| 67 char request[512]; | |
| 68 int length = snprintf( | |
| 69 request, | |
| 70 sizeof(request), | |
| 71 "GET %s HTTP/1.1\r\n" | |
| 72 "Host: 127.0.0.1\r\n" | |
| 73 "Connection: keep-alive\r\n" | |
| 74 "\r\n", | |
| 75 path); | |
| 76 assert(length > 0 && (size_t)length < sizeof(request)); | |
| 77 assert(send(socket_fd, request, (size_t)length, 0) == length); | |
| 78 } | |
| 79 | |
| 80 static size_t receive_until( | |
| 81 int socket_fd, | |
| 82 char *buffer, | |
| 83 size_t capacity, | |
| 84 const char *needle) | |
| 85 { | |
| 86 size_t used = 0; | |
| 87 while (used + 1 < capacity) | |
| 88 { | |
| 89 ssize_t amount = recv( | |
| 90 socket_fd, | |
| 91 buffer + used, | |
| 92 capacity - used - 1, | |
| 93 0); | |
| 94 assert(amount > 0); | |
| 95 used += (size_t)amount; | |
| 96 buffer[used] = '\0'; | |
| 97 if (strstr(buffer, needle)) | |
| 98 return used; | |
| 99 } | |
| 100 assert(FALSE && "response exceeded test buffer"); | |
| 101 return 0; | |
| 102 } | |
| 103 | |
| 104 static pid_t start_server( | |
| 105 const char *binary, | |
| 106 const char *port) | |
| 107 { | |
| 108 pid_t pid = fork(); | |
| 109 assert(pid >= 0); | |
| 110 if (pid == 0) | |
| 111 { | |
| 112 execl(binary, binary, port, NULL); | |
| 113 _exit(127); | |
| 114 } | |
| 115 | |
| 116 int numeric_port = atoi(port); | |
| 117 for (int attempt = 0; attempt < 100; attempt++) | |
| 118 { | |
| 119 int socket_fd = connect_to(numeric_port); | |
| 120 if (socket_fd >= 0) | |
| 121 { | |
| 122 close(socket_fd); | |
| 123 return pid; | |
| 124 } | |
| 125 usleep(20000); | |
| 126 } | |
| 127 kill(pid, SIGTERM); | |
| 128 waitpid(pid, NULL, 0); | |
| 129 assert(FALSE && "SSE test server did not start"); | |
| 130 return -1; | |
| 131 } | |
| 132 | |
| 133 int main(int argc, char **argv) | |
| 134 { | |
| 135 assert(argc == 2); | |
| 136 int port = reserve_port(); | |
| 137 char port_text[16]; | |
| 138 snprintf(port_text, sizeof(port_text), "%d", port); | |
| 139 pid_t server = start_server(argv[1], port_text); | |
| 140 | |
| 141 int event_socket = connect_to(port); | |
| 142 assert(event_socket >= 0); | |
| 143 send_request(event_socket, "/events"); | |
| 144 char events[4096] = {0}; | |
| 145 receive_until( | |
| 146 event_socket, | |
| 147 events, | |
| 148 sizeof(events), | |
| 149 "data: later\n\n"); | |
| 150 assert(strstr(events, "HTTP/1.1 200 OK\r\n")); | |
| 151 assert(strstr(events, "Content-Type: text/event-stream\r\n")); | |
| 152 assert(strstr(events, "data: connected\n\n")); | |
| 153 | |
| 154 int health_socket = connect_to(port); | |
| 155 assert(health_socket >= 0); | |
| 156 send_request(health_socket, "/health"); | |
| 157 assert(shutdown(health_socket, SHUT_WR) == 0); | |
| 158 char health[1024] = {0}; | |
| 159 receive_until(health_socket, health, sizeof(health), "\r\n\r\nok"); | |
| 160 assert(strstr(health, "HTTP/1.1 200 OK\r\n")); | |
| 161 | |
| 162 close(health_socket); | |
| 163 close(event_socket); | |
| 164 kill(server, SIGTERM); | |
| 165 waitpid(server, NULL, 0); | |
| 166 printf("Seobeo SSE server integration test passed\n"); | |
| 167 return 0; | |
| 168 } |