130 lines
3.8 KiB
C
130 lines
3.8 KiB
C
#include "hygate.h"
|
|||
|
|
#include <stdio.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <unistd.h>
|
||
|
|
#include <signal.h>
|
||
|
|
#include <stdint.h>
|
||
|
|
#include <errno.h>
|
||
|
|
#include <pthread.h>
|
||
|
|
#include <semaphore.h>
|
||
|
|
#include <arpa/inet.h>
|
||
|
|
#include <netinet/in.h>
|
||
|
|
#include <sys/socket.h>
|
||
|
|
#include <curl/curl.h>
|
||
|
|
|
||
|
|
static config_t g_cfg;
|
||
|
|
static sem_t g_conn_sem;
|
||
|
|
|
||
|
|
static void *worker(void *arg) {
|
||
|
|
int fd = (int)(intptr_t)arg;
|
||
|
|
handle_connection(fd, &g_cfg);
|
||
|
|
close(fd);
|
||
|
|
sem_post(&g_conn_sem);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
int main(int argc, char **argv) {
|
||
|
|
config_t cfg;
|
||
|
|
cfg.port = DEFAULT_PORT;
|
||
|
|
cfg.endpoint = DEFAULT_ENDPOINT;
|
||
|
|
cfg.model = DEFAULT_MODEL;
|
||
|
|
cfg.user_agent = "";
|
||
|
|
cfg.max_body_bytes = (long)MAX_BODY_BYTES;
|
||
|
|
cfg.max_connections = MAX_CONCURRENT_THREADS;
|
||
|
|
|
||
|
|
/* Simple flag parsing: -key value or -key=value */
|
||
|
|
for (int i = 1; i < argc; i++) {
|
||
|
|
char *a = argv[i];
|
||
|
|
char *val = NULL;
|
||
|
|
if (a[0] == '-') {
|
||
|
|
char *eq = strchr(a, '=');
|
||
|
|
if (eq) {
|
||
|
|
*eq = 0;
|
||
|
|
val = eq + 1;
|
||
|
|
} else if (i + 1 < argc) {
|
||
|
|
val = argv[i + 1];
|
||
|
|
i++;
|
||
|
|
}
|
||
|
|
if (strcmp(a, "-port") == 0) cfg.port = val;
|
||
|
|
else if (strcmp(a, "-endpoint") == 0) cfg.endpoint = val;
|
||
|
|
else if (strcmp(a, "-model") == 0) cfg.model = val;
|
||
|
|
else if (strcmp(a, "-user-agent") == 0) cfg.user_agent = val;
|
||
|
|
else if (strcmp(a, "-ua") == 0) cfg.user_agent = val;
|
||
|
|
else if (strcmp(a, "-max-body") == 0) {
|
||
|
|
if (val) {
|
||
|
|
long v = (long)strtoul(val, NULL, 10);
|
||
|
|
if (v > 0) cfg.max_body_bytes = v;
|
||
|
|
}
|
||
|
|
} else if (strcmp(a, "-max-conn") == 0) {
|
||
|
|
if (val) {
|
||
|
|
int v = atoi(val);
|
||
|
|
if (v > 0) cfg.max_connections = v;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
fprintf(stderr, "unknown flag: %s\n", a);
|
||
|
|
return 2;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
g_cfg = cfg;
|
||
|
|
|
||
|
|
if (sem_init(&g_conn_sem, 0, (unsigned int)cfg.max_connections) != 0) {
|
||
|
|
perror("sem_init");
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
curl_global_init(CURL_GLOBAL_ALL);
|
||
|
|
signal(SIGPIPE, SIG_IGN);
|
||
|
|
|
||
|
|
int srv = socket(AF_INET, SOCK_STREAM, 0);
|
||
|
|
if (srv < 0) { perror("socket"); return 1; }
|
||
|
|
int one = 1;
|
||
|
|
setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
|
||
|
|
struct sockaddr_in addr;
|
||
|
|
memset(&addr, 0, sizeof addr);
|
||
|
|
addr.sin_family = AF_INET;
|
||
|
|
addr.sin_port = htons((uint16_t)atoi(cfg.port));
|
||
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
||
|
|
if (bind(srv, (struct sockaddr *)&addr, sizeof addr) < 0) { perror("bind"); return 1; }
|
||
|
|
if (listen(srv, 128) < 0) { perror("listen"); return 1; }
|
||
|
|
|
||
|
|
printf("hygate starting on port %s...\n", cfg.port);
|
||
|
|
printf("Target Endpoint: %s\n", cfg.endpoint);
|
||
|
|
printf("Model Name: %s\n", cfg.model);
|
||
|
|
printf("User-Agent: %s\n", cfg.user_agent && cfg.user_agent[0] ? cfg.user_agent : DEFAULT_UA);
|
||
|
|
printf("Max request body: %ld bytes\n", cfg.max_body_bytes);
|
||
|
|
printf("Max concurrent connections: %d\n", cfg.max_connections);
|
||
|
|
printf("Endpoints:\n");
|
||
|
|
printf(" GET http://localhost:%s/v1/models\n", cfg.port);
|
||
|
|
printf(" POST http://localhost:%s/v1/chat/completions\n", cfg.port);
|
||
|
|
|
||
|
|
for (;;) {
|
||
|
|
/* Wait for a free worker slot before accepting, so the number of
|
||
|
|
in-flight connections (and thus worker threads) is capped at
|
||
|
|
cfg.max_connections. Excess connections queue in the kernel
|
||
|
|
listen backlog. */
|
||
|
|
while (sem_wait(&g_conn_sem) != 0) {
|
||
|
|
if (errno == EINTR) continue;
|
||
|
|
perror("sem_wait");
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
int cfd = accept(srv, NULL, NULL);
|
||
|
|
if (cfd < 0) {
|
||
|
|
if (errno == EINTR) { sem_post(&g_conn_sem); continue; }
|
||
|
|
perror("accept");
|
||
|
|
sem_post(&g_conn_sem);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
pthread_t tid;
|
||
|
|
if (pthread_create(&tid, NULL, worker, (void *)(intptr_t)cfd) == 0) {
|
||
|
|
pthread_detach(tid);
|
||
|
|
} else {
|
||
|
|
handle_connection(cfd, &g_cfg);
|
||
|
|
close(cfd);
|
||
|
|
sem_post(&g_conn_sem);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|