use sendfile()

This commit is contained in:
2025-08-04 21:15:40 +02:00
parent a250346f22
commit b685941da3
2 changed files with 56 additions and 52 deletions

View File

@@ -1,10 +1,14 @@
#include "http/http.h" #include "http/http.h"
#include <errno.h> #include <errno.h>
#include <fcntl.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/sendfile.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
#include "utils/colors.h" #include "utils/colors.h"
#include "utils/utils.h" #include "utils/utils.h"
@@ -187,7 +191,10 @@ size_t cws_http_response_builder(char **response, char *http_version, cws_http_s
snprintf(*response, header_len + 1, "%s %s\r\nContent-Type: %s\r\nContent-Length: %ld\r\nConnection: %s\r\n\r\n", http_version, status_code, content_type, snprintf(*response, header_len + 1, "%s %s\r\nContent-Type: %s\r\nContent-Length: %ld\r\nConnection: %s\r\n\r\n", http_version, status_code, content_type,
body_len_bytes, connection); body_len_bytes, connection);
/* Only append body if we have it */
if (body && body_len_bytes > 0) {
memcpy(*response + header_len, body, body_len_bytes); memcpy(*response + header_len, body, body_len_bytes);
}
return total_len; return total_len;
} }
@@ -209,78 +216,71 @@ void cws_http_send_response(cws_http *request, cws_http_status status) {
int cws_http_send_resource(cws_http *request) { int cws_http_send_resource(cws_http *request) {
int keepalive = 1; int keepalive = 1;
const char *path = mcl_string_cstr(request->location_path);
FILE *file = fopen(mcl_string_cstr(request->location_path), "rb"); /* Open file */
if (file == NULL) { int fd = open(path, O_RDONLY);
if (fd < 0) {
cws_http_send_response(request, CWS_HTTP_NOT_FOUND); cws_http_send_response(request, CWS_HTTP_NOT_FOUND);
return 0; return 0;
} }
struct stat st;
if (fstat(fd, &st) < 0) {
close(fd);
cws_http_send_response(request, CWS_HTTP_NOT_FOUND);
}
size_t content_length = st.st_size;
/* Retrieve correct Content-Type */ /* Retrieve correct Content-Type */
char content_type[1024]; char content_type[1024];
int ret = cws_http_get_content_type(request, content_type); if (cws_http_get_content_type(request, content_type) < 0) {
if (ret < 0) { close(fd);
fclose(file);
cws_http_send_response(request, CWS_HTTP_NOT_FOUND); cws_http_send_response(request, CWS_HTTP_NOT_FOUND);
return 0; return 0;
} }
/* Retrieve file size */ /* Build header only */
fseek(file, 0, SEEK_END);
const size_t content_length = ftell(file);
rewind(file);
/* Retrieve file data */
char *file_data = malloc(content_length);
if (file_data == NULL) {
fclose(file);
CWS_LOG_ERROR("Unable to allocate file data");
return 0;
}
/* Read file data */
size_t read_bytes = fread(file_data, 1, content_length, file);
fclose(file);
if (read_bytes != content_length) {
free(file_data);
CWS_LOG_ERROR("Partial read from file");
return 0;
}
/* Check for keep-alive */
char conn[32] = "keep-alive"; char conn[32] = "keep-alive";
mcl_bucket *connection = mcl_hm_get(request->headers, "Connection"); mcl_bucket *hb = mcl_hm_get(request->headers, "Connection");
if (connection && strcmp((char *)connection->value, "keep-alive") == 0) { if (hb && strcmp((char *)hb->value, "close")) {
strcpy(conn, "keep-alive");
keepalive = 0; keepalive = 0;
strncpy(conn, "close", sizeof(conn));
} }
mcl_hm_free_bucket(connection); mcl_hm_free_bucket(hb);
char *response = NULL; char *header = NULL;
size_t response_len = cws_http_response_builder(&response, "HTTP/1.1", CWS_HTTP_OK, content_type, conn, file_data, content_length); size_t header_len = cws_http_response_builder(&header, "HTTP/1.1", CWS_HTTP_OK, content_type, conn, NULL, content_length);
if (!header_len) {
close(fd);
/* Send response in chunks to avoid blocking */ return keepalive;
size_t bytes_sent = 0;
ssize_t sent;
const size_t chunk_size = 8192;
while (bytes_sent < response_len) {
size_t to_send = (response_len - bytes_sent > chunk_size) ? chunk_size : (response_len - bytes_sent);
sent = send(request->sockfd, response + bytes_sent, to_send, MSG_NOSIGNAL);
if (sent <= 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
continue;
} }
/* Send headers */
size_t sent = 0;
while (sent < header_len) {
ssize_t n = send(request->sockfd, header + sent, header_len - sent, MSG_NOSIGNAL);
if (n <= 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) continue;
break;
}
sent += n;
}
free(header);
/* Zero-copy sendfile */
off_t offset = 0;
while (offset < (off_t)content_length) {
ssize_t n = sendfile(request->sockfd, fd, &offset, content_length - offset);
if (n <= 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) continue;
break; break;
} }
bytes_sent += sent;
} }
free(response); close(fd);
free(file_data);
return keepalive; return keepalive;
} }

View File

@@ -4,6 +4,7 @@
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <signal.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <sys/epoll.h> #include <sys/epoll.h>
@@ -32,6 +33,9 @@ static void cws_server_setup_hints(struct addrinfo *hints, const char *hostname)
} }
cws_server_ret cws_server_start(cws_config *config) { cws_server_ret cws_server_start(cws_config *config) {
/* Ignore SIGPIPE so a write/sendfile to a closed peer doesn't terminate the process */
signal(SIGPIPE, SIG_IGN);
if (!config || !config->hostname || !config->port) { if (!config || !config->hostname || !config->port) {
return CWS_SERVER_CONFIG; return CWS_SERVER_CONFIG;
} }