commit 3d7bcf8

uint  ·  2026-01-25 22:16:45 +0000 UTC
parent 857194b
add streaming
3 files changed,  +190, -43
+183, -43
  1@@ -1,7 +1,11 @@
  2 #include <errno.h>
  3+#include <fcntl.h>
  4 #include <stddef.h>
  5+#include <stdint.h>
  6 #include <stdio.h>
  7+#include <stdlib.h>
  8 #include <string.h>
  9+#include <sys/stat.h>
 10 #include <unistd.h>
 11 
 12 #include "config.h"
 13@@ -10,64 +14,54 @@
 14 #include "log.h"
 15 #include "scan.h"
 16 
 17+static const struct item* find_item(uint64_t id);
 18+static int join_path(char* out, size_t outsz, const char* a, const char* b);
 19+static int parse_hex64(const char* s, uint64_t* out);
 20 static int read_reqline(int c, char* method, size_t msz, char* path, size_t psz);
 21 static void reply_json(int c, const char* status, const char* body, size_t len);
 22 static void reply_text(int c, const char* status, const char* body);
 23+static int stream_file(int c, const struct item* it);
 24 static int write_all(int fd, const void* buf, size_t n);
 25 
 26-int http_handle(int c)
 27-{
 28-	char method[16];
 29-	char path[1024];
 30 
 31-	if (read_reqline(c, method, sizeof(method), path, sizeof(path)) < 0) {
 32-		reply_text(c, HTTP_400, "bad request\n");
 33-		return -1;
 34-	}
 35-	LOG(verbose_log, "HTTP", "request: %s %s", method, path);
 36+extern struct library lib;
 37 
 38-	if (strcmp(method, "GET") != 0) {
 39-		LOG(verbose_log, "HTTP", "method not allowed: %s", method);
 40-		reply_text(c, HTTP_405, "method not allowed\n");
 41-		return 0;
 42-	}
 43+static const struct item* find_item(uint64_t id)
 44+{
 45+	for (size_t i = 0; i < lib.len; i++)
 46+		if (lib.items[i].id == id)
 47+			return &lib.items[i];
 48 
 49-	if (strcmp(path, "/ping") == 0) {
 50-		LOG(verbose_log, "HTTP", "route /ping");
 51-		reply_text(c, HTTP_200, "ok\n");
 52-		return 0;
 53-	}
 54+	return NULL;
 55+}
 56 
 57-	if (strcmp(path, "/library") == 0) {
 58-		LOG(verbose_log, "HTTP", "route /library");
 59-		struct library l;
 60-		struct json j;
 61+static int join_path(char* out, size_t outsz, const char* a, const char* b)
 62+{
 63+	int n = snprintf(out, outsz, "%s/%s", a, b);
 64+	if (n < 0)
 65+		return -1;
 66 
 67-		if (scan_library(&l, media_dir) < 0) {
 68-			LOG(verbose_log, "SCAN", "scan failed");
 69-			reply_text(c, HTTP_500, "scan failed\n");
 70-			return -1;
 71-		}
 72-		LOG(verbose_log, "SCAN", "found %zu items", l.len);
 73+	if ((size_t)n >= outsz)
 74+		return -1;
 75 
 76-		if (json_library(&j, &l) < 0) {
 77-			LOG(verbose_log, "JSON", "encode failed");
 78-			scan_library_free(&l);
 79-			reply_text(c, HTTP_500, "json failed\n");
 80-			return -1;
 81-		}
 82-		LOG(verbose_log, "JSON", "encoded %zu bytes", j.len);
 83+	return 0;
 84+}
 85 
 86-		scan_library_free(&l);
 87-		reply_json(c, HTTP_200, j.buf, j.len);
 88-		json_free(&j);
 89+static int parse_hex64(const char* s, uint64_t* out)
 90+{
 91+	char* end = NULL;
 92 
 93-		return 0;
 94-	}
 95+	if (!s || *s == '\0')
 96+		return -1;
 97 
 98-	LOG(verbose_log, "HTTP", "route not found: %s", path);
 99-	reply_text(c, HTTP_404, "not found\n");
100+	errno = 0;
101+	unsigned long long v = strtoull(s, &end, 16);
102+	if (errno != 0)
103+		return -1;
104+	if (!end || *end != '\0')
105+		return -1;
106 
107+	*out = (uint64_t)v;
108 	return 0;
109 }
110 
111@@ -159,6 +153,88 @@ static void reply_text(int c, const char* status, const char* body)
112 	(void)write_all(c, resp, (size_t)n);
113 }
114 
115+static int stream_file(int c, const struct item* it)
116+{
117+	char full[4096];
118+
119+	/* build absolute path */
120+	if (join_path(full, sizeof(full), media_dir, it->path) < 0) {
121+		LOG(verbose_log, "HTTP", "path too long");
122+		reply_text(c, HTTP_500, "server error\n");
123+		return -1;
124+	}
125+
126+	/* open file */
127+	int fd = open(full, O_RDONLY);
128+	if (fd < 0) {
129+		LOG(verbose_log, "HTTP", "open failed: %s", it->path);
130+		reply_text(c, HTTP_404, "not found\n");
131+		return 0;
132+	}
133+
134+	/* stat file */
135+	struct stat st;
136+	if (fstat(fd, &st) < 0) {
137+		close(fd);
138+		reply_text(c, HTTP_500, "server error\n");
139+		return -1;
140+	}
141+
142+	/* reject non-regular files */
143+	if (!S_ISREG(st.st_mode)) {
144+		close(fd);
145+		reply_text(c, HTTP_404, "not found\n");
146+		return 0;
147+	}
148+
149+	size_t len = (size_t)st.st_size;
150+
151+	/* build response header */
152+	char resp[HTTP_RESP_MAX];
153+	int n = snprintf(
154+		resp, sizeof(resp),
155+		"%s"
156+		HTTP_BIN
157+		HTTP_LENGTH
158+		HTTP_CLOSE
159+		"\r\n",
160+		HTTP_200, len
161+	);
162+
163+	if (n < 0) {
164+		close(fd);
165+		return -1;
166+	}
167+
168+	if ((size_t)n >= sizeof(resp))
169+		n = (int)(sizeof(resp) - 1);
170+
171+	/* send headers */
172+	(void)write_all(c, resp, (size_t)n);
173+
174+	/* stream file */
175+	char buf[8192];
176+	for (;;) {
177+		ssize_t r = read(fd, buf, sizeof(buf));
178+		if (r < 0) {
179+			if (errno == EINTR)
180+				continue;
181+			break;
182+		}
183+
184+		if (r == 0)
185+			break;
186+
187+		if (write_all(c, buf, (size_t)r) < 0)
188+			break;
189+	}
190+
191+	close(fd);
192+	LOG(verbose_log, "HTTP", "streamed %s (%zu bytes)", it->path, len);
193+
194+	return 0;
195+}
196+
197 static int write_all(int fd, const void* buf, size_t n)
198 {
199 	const char* p = buf;
200@@ -178,3 +254,67 @@ static int write_all(int fd, const void* buf, size_t n)
201 	return 0;
202 }
203 
204+int http_handle(int c)
205+{
206+	char method[16];
207+	char path[1024];
208+
209+	if (read_reqline(c, method, sizeof(method), path, sizeof(path)) < 0) {
210+		reply_text(c, HTTP_400, "bad request\n");
211+		return -1;
212+	}
213+	LOG(verbose_log, "HTTP", "request: %s %s", method, path);
214+
215+	if (strcmp(method, "GET") != 0) {
216+		LOG(verbose_log, "HTTP", "method not allowed: %s", method);
217+		reply_text(c, HTTP_405, "method not allowed\n");
218+		return 0;
219+	}
220+
221+	if (strcmp(path, "/ping") == 0) {
222+		LOG(verbose_log, "HTTP", "route /ping");
223+		reply_text(c, HTTP_200, "ok\n");
224+		return 0;
225+	}
226+
227+	if (strcmp(path, "/library") == 0) {
228+		LOG(verbose_log, "HTTP", "route /library");
229+		struct json j;
230+
231+		if (json_library(&j, &lib) < 0) {
232+			LOG(verbose_log, "JSON", "encode failed");
233+			reply_text(c, HTTP_500, "json failed\n");
234+			return -1;
235+		}
236+		LOG(verbose_log, "JSON", "encoded %zu bytes", j.len);
237+
238+		reply_json(c, HTTP_200, j.buf, j.len);
239+		json_free(&j);
240+
241+		return 0;
242+	}
243+
244+	if (strncmp(path, "/stream/", 8) == 0) {
245+		LOG(verbose_log, "HTTP", "route /stream");
246+
247+		uint64_t id;
248+		if (parse_hex64(path + 8, &id) < 0) {
249+			reply_text(c, HTTP_400, "bad request\n");
250+			return 0;
251+		}
252+
253+		const struct item* it = find_item(id);
254+		if (!it) {
255+			reply_text(c, HTTP_404, "not found\n");
256+			return 0;
257+		}
258+
259+		return stream_file(c, it);
260+	}
261+
262+	LOG(verbose_log, "HTTP", "route not found: %s", path);
263+	reply_text(c, HTTP_404, "not found\n");
264+
265+	return 0;
266+}
267+
+1, -0
1@@ -9,6 +9,7 @@
2 
3 #define HTTP_TEXT   "Content-Type: text/plain\r\n"
4 #define HTTP_JSON   "Content-Type: application/json\r\n"
5+#define HTTP_BIN    "Content-Type: application/octet-stream\r\n"
6 #define HTTP_CLOSE  "Connection: close\r\n"
7 #define HTTP_LENGTH "Content-Length: %zu\r\n"
8 
+6, -0
 1@@ -22,6 +22,7 @@
 2 #include "config.h"
 3 #include "http.h"
 4 #include "log.h"
 5+#include "scan.h"
 6 
 7 void die(const char* s, int e);
 8 void run(void);
 9@@ -29,6 +30,7 @@ void setup(void);
10 int write_all(int fd, const void* buf, size_t n);
11 
12 int sock;
13+struct library lib;
14 
15 void die(const char* s, int e)
16 {
17@@ -77,6 +79,10 @@ void setup(void)
18 	if (ret < 0)
19 		die("bind", EXIT_FAILURE);
20 
21+	if (scan_library(&lib, media_dir) < 0)
22+		die("scan_library", EXIT_FAILURE);
23+	LOG(verbose_log, "SCAN", "cached %zu items", lib.len);
24+
25 	ret = listen(sock, LISTEN_BACKLOG);
26 	if (ret < 0)
27 		die("listen", EXIT_FAILURE);