Make net request async on linux

This commit is contained in:
luboslenco
2025-11-20 20:10:52 +01:00
parent 09a745b5f1
commit 142161c997
2 changed files with 253 additions and 40 deletions
+1
View File
@@ -87,6 +87,7 @@ else if (platform == "linux") {
project.add_lib("Xcursor");
project.add_lib("Xrandr");
project.add_lib("ssl");
project.add_lib("crypto");
project.add_lib("vulkan");
if (flags.with_audio) {
project.add_lib("asound");
+252 -40
View File
@@ -1,76 +1,288 @@
#include <fcntl.h>
#include <iron_net.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
static SSL_CTX *ctx = NULL;
static char *buf = NULL;
static int buf_len = 1024 * 1024;
typedef struct {
SSL *ssl;
int sock_fd;
FILE *fp;
char *buf;
int buf_len;
iron_https_callback_t callback;
void *callbackdata;
char *url_base;
char *url_path;
int port;
} async_context_t;
void iron_net_request(const char *url_base, const char *url_path, const char *data, int port, int method, iron_https_callback_t callback, void *callbackdata, const char *dst_path) {
typedef struct request {
char *response;
iron_https_callback_t callback;
void *callbackdata;
struct request *next;
} request_t;
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
struct hostent *server = gethostbyname(url_base);
struct sockaddr_in server_addr = {0};
server_addr.sin_family = AF_INET;
memmove(&server_addr.sin_addr.s_addr, server->h_addr_list[0], server->h_length);
server_addr.sin_port = htons(port);
volatile uint64_t iron_net_bytes_downloaded = 0;
static SSL_CTX *ctx = NULL;
static pthread_mutex_t ctx_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t pending_mutex = PTHREAD_MUTEX_INITIALIZER;
static request_t *pending_head = NULL;
static request_t *pending_tail = NULL;
if (connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
close(sock_fd);
callback(NULL, callbackdata);
return;
static void finish_request(async_context_t *async_ctx, char *response) {
if (async_ctx->fp) {
fflush(async_ctx->fp);
fclose(async_ctx->fp);
}
if (async_ctx->ssl) {
SSL_shutdown(async_ctx->ssl);
SSL_free(async_ctx->ssl);
}
if (async_ctx->sock_fd >= 0) {
close(async_ctx->sock_fd);
}
if (async_ctx->buf) {
free(async_ctx->buf);
}
if (async_ctx->url_base) {
free(async_ctx->url_base);
}
if (async_ctx->url_path) {
free(async_ctx->url_path);
}
request_t *req = malloc(sizeof(request_t));
req->response = response;
req->callback = async_ctx->callback;
req->callbackdata = async_ctx->callbackdata;
req->next = NULL;
pthread_mutex_lock(&pending_mutex);
if (pending_tail == NULL) {
pending_head = pending_tail = req;
}
else {
pending_tail->next = req;
pending_tail = req;
}
pthread_mutex_unlock(&pending_mutex);
free(async_ctx);
}
static void parse_url(const char *url, char **host, char **path, int *port) {
const char *start = url + 8;
*port = 443;
const char *slash = strchr(start, '/');
if (slash) {
*host = strndup(start, slash - start);
*path = strdup(slash + 1);
}
else {
*host = strdup(start);
*path = strdup("");
}
char *colon = strchr(*host, ':');
if (colon) {
*port = atoi(colon + 1);
*colon = '\0';
}
}
static int do_request(async_context_t *async_ctx) {
struct addrinfo hints = {0};
struct addrinfo *res = NULL;
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
char port_str[6];
snprintf(port_str, sizeof(port_str), "%d", async_ctx->port);
if (getaddrinfo(async_ctx->url_base, port_str, &hints, &res) != 0) {
return 0;
}
int sock_fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (connect(sock_fd, res->ai_addr, res->ai_addrlen) < 0) {
close(sock_fd);
freeaddrinfo(res);
return 0;
}
freeaddrinfo(res);
pthread_mutex_lock(&ctx_mutex);
if (ctx == NULL) {
ctx = SSL_CTX_new(TLS_client_method());
SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS);
}
pthread_mutex_unlock(&ctx_mutex);
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, sock_fd);
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
SSL_set_tlsext_host_name(ssl, async_ctx->url_base);
if (SSL_connect(ssl) <= 0) {
SSL_free(ssl);
close(sock_fd);
callback(NULL, callbackdata);
return;
return 0;
}
// For HTTP/1.1, implement "transfer-encoding: chunked"
char request[1024];
int request_len = snprintf(request, sizeof(request), "GET /%s HTTP/1.0\r\nHost: %s:%d\r\nConnection: close\r\n\r\n", url_path, url_base, port);
async_ctx->ssl = ssl;
async_ctx->sock_fd = sock_fd;
char request[4096];
int request_len = snprintf(request, sizeof(request), "GET /%s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", async_ctx->url_path, async_ctx->url_base);
SSL_write(ssl, request, request_len);
int pos = 0;
int n;
int header_buf_size = 16384;
char *header_buf = malloc(header_buf_size);
// Read
if (buf == NULL) {
buf = malloc(buf_len);
}
int pos = 0;
int n;
while ((n = SSL_read(ssl, buf + pos, buf_len - pos - 1)) > 0) {
while ((n = SSL_read(ssl, header_buf + pos, header_buf_size - pos - 1)) > 0) {
pos += n;
if (pos == buf_len - 1) {
buf_len *= 2;
buf = realloc(buf, buf_len);
header_buf[pos] = '\0';
char *headers_end = strstr(header_buf, "\r\n\r\n");
if (strncmp(header_buf, "HTTP/1.1 302", 12) == 0) {
char *location = strstr(header_buf, "Location: ");
if (location) {
location += 10;
char *location_end = strstr(location, "\r\n");
*location_end = '\0';
char *new_host = NULL;
char *new_path = NULL;
int new_port = 0;
parse_url(location, &new_host, &new_path, &new_port);
SSL_shutdown(async_ctx->ssl);
SSL_free(async_ctx->ssl);
close(async_ctx->sock_fd);
async_ctx->ssl = NULL;
async_ctx->sock_fd = -1;
free(async_ctx->url_base);
free(async_ctx->url_path);
async_ctx->url_base = new_host;
async_ctx->url_path = new_path;
async_ctx->port = new_port;
free(header_buf);
return do_request(async_ctx);
}
}
char *body_start = headers_end + 4;
int body_len = pos - (body_start - header_buf);
int is_chunked = strstr(header_buf, "transfer-encoding: chunked") != NULL;
if (async_ctx->fp) {
// No chunked support for fp yet
if (body_len > 0) {
fwrite(body_start, 1, body_len, async_ctx->fp);
iron_net_bytes_downloaded += body_len;
}
while ((n = SSL_read(ssl, async_ctx->buf, 64 * 1024)) > 0) {
fwrite(async_ctx->buf, 1, n, async_ctx->fp);
iron_net_bytes_downloaded += n;
}
free(header_buf);
finish_request(async_ctx, NULL);
return 1;
}
else {
if (body_len > 0) {
if (body_len >= async_ctx->buf_len) {
async_ctx->buf_len = body_len + 1;
async_ctx->buf = realloc(async_ctx->buf, async_ctx->buf_len);
}
memcpy(async_ctx->buf, body_start, body_len);
pos = body_len;
}
else {
pos = 0;
}
while ((n = SSL_read(ssl, async_ctx->buf + pos, async_ctx->buf_len - pos - 1)) > 0) {
pos += n;
if (pos >= async_ctx->buf_len - 1) {
async_ctx->buf_len *= 2;
async_ctx->buf = realloc(async_ctx->buf, async_ctx->buf_len);
}
}
async_ctx->buf[pos] = '\0';
free(header_buf);
if (is_chunked) {
char *chunk_ptr = async_ctx->buf;
char *out_ptr = async_ctx->buf;
while (1) {
char *end_ptr;
int chunk_size = strtol(chunk_ptr, &end_ptr, 16);
if (chunk_size == 0)
break;
if (end_ptr == chunk_ptr)
break;
chunk_ptr = end_ptr + 2; // \r\n
memmove(out_ptr, chunk_ptr, chunk_size);
out_ptr += chunk_size;
chunk_ptr += chunk_size + 2; // \r\n
}
*out_ptr = '\0';
}
char *response = strdup(async_ctx->buf);
finish_request(async_ctx, response);
return 1;
}
if (pos >= header_buf_size - 1) {
header_buf_size *= 2;
header_buf = realloc(header_buf, header_buf_size);
}
}
buf[pos] = '\0';
free(header_buf);
return 0;
}
// Parse
const char *body_ptr = "";
char *headers_end = strstr(buf, "\r\n\r\n");
body_ptr = headers_end + 4;
callback(body_ptr, callbackdata);
static void *download_thread(void *arg) {
async_context_t *async_ctx = (async_context_t *)arg;
if (!do_request(async_ctx)) {
finish_request(async_ctx, NULL);
}
return NULL;
}
SSL_free(ssl);
close(sock_fd);
void iron_net_request(const char *url_base, const char *url_path, const char *data, int port, int method, iron_https_callback_t callback, void *callbackdata,
const char *dst_path) {
async_context_t *async_ctx = calloc(1, sizeof(async_context_t));
async_ctx->callback = callback;
async_ctx->callbackdata = callbackdata;
async_ctx->buf_len = 1024 * 1024;
async_ctx->buf = malloc(async_ctx->buf_len);
async_ctx->fp = NULL;
async_ctx->sock_fd = -1;
async_ctx->ssl = NULL;
async_ctx->url_base = strdup(url_base);
async_ctx->url_path = strdup(url_path);
async_ctx->port = port;
if (dst_path) {
async_ctx->fp = fopen(dst_path, "wb");
setvbuf(async_ctx->fp, NULL, _IOFBF, 256 * 1024);
}
pthread_t thread;
pthread_create(&thread, NULL, download_thread, async_ctx);
pthread_detach(thread);
}
void iron_net_update() {
request_t *current;
pthread_mutex_lock(&pending_mutex);
current = pending_head;
pending_head = pending_tail = NULL;
pthread_mutex_unlock(&pending_mutex);
while (current) {
request_t *next = current->next;
current->callback(current->response, current->callbackdata);
if (current->response) {
free(current->response);
}
free(current);
current = next;
}
}
volatile uint64_t iron_net_bytes_downloaded = 0;