==> tests/http.c <==1/* http.rs's view_routes, and the port over TCP: the views (text, JSON, a bad format), a request relayed to
2 * the http program (a stand-in driver), the 503 when it cannot answer, render without a blob thread. */
3#define _XOPEN_SOURCE 700 /* sexc compiles tests without the package's cflags (reported) */
4#include "mesh/abi.h"
5#include "mesh/check.h"
6#include "mesh/http_relay.h"
7#include "mesh/log.h"
8
9#include <arpa/inet.h>
10#include <netinet/in.h>
11#include <pthread.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <sys/socket.h>
16#include <unistd.h>
17
18static mesh_shared *SH;
19static int answer = 1;
20
21static void *driver(void *arg) {
22 (void)arg;
23 mesh_cmd c;
24 while (mesh_chan_recv(SH->cmd, &c, -1) == MESH_CHAN_OK) {
25 if (c.k == MESH_CMD_REQ && answer) mesh_reply_bytes(c.reply, "HTTP/1.0 200 OK\r\nContent-Length: 2\r\n\r\nhi", 40);
26 else mesh_reply_errf(c.reply, "no program \"%s\" (`programs` lists them)", c.name);
27 c.reply = NULL;
28 mesh_cmd_free(&c);
29 }
30 return NULL;
31}
32
33static int lfd, port;
34static void *serve(void *arg) {
35 (void)arg;
36 mesh_http_serve(SH, lfd);
37 return NULL;
38}
39
40/* One request; the whole response. */
41static char *get(const char *head) {
42 int fd = socket(AF_INET, SOCK_STREAM, 0);
43 struct sockaddr_in sa = {0};
44 sa.sin_family = AF_INET;
45 sa.sin_port = htons((uint16_t)port);
46 sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
47 connect(fd, (struct sockaddr *)&sa, sizeof sa);
48 if (write(fd, head, strlen(head)) < 0) abort();
49 mesh_buf b = MESH_BUF_INIT;
50 char buf[4096];
51 ssize_t k;
52 while ((k = read(fd, buf, sizeof buf)) > 0) mesh_buf_put(&b, buf, (size_t)k);
53 close(fd);
54 return mesh_buf_take(&b);
55}
56
57int main(void) {
58 const char *v, *q;
59 size_t ql;
60 CHECK(mesh_http_view_route("GET /fleet HTTP/1.0", &v, &q, &ql) && strcmp(v, "fleet") == 0 && ql == 0);
61 CHECK(mesh_http_view_route("GET /jobs?format=json HTTP/1.1", &v, &q, &ql) && strcmp(v, "jobs") == 0 && ql == 11 && strncmp(q, "format=json", 11) == 0);
62 CHECK(mesh_http_view_route("GET /pipes", &v, &q, &ql) && strcmp(v, "pipes") == 0);
63 CHECK(!mesh_http_view_route("GET /fleetx HTTP/1.0", &v, &q, &ql));
64 CHECK(!mesh_http_view_route("POST /fleet HTTP/1.0", &v, &q, &ql));
65 CHECK(!mesh_http_view_route("GET /status HTTP/1.0", &v, &q, &ql));
66 const char *w;
67 char *job;
68 CHECK(mesh_http_render_route("GET /render?job=4.1 HTTP/1.0", &w, &job) && strcmp(w, "status") == 0 && strcmp(job, "4.1") == 0);
69 free(job);
70 CHECK(mesh_http_render_route("GET /render.bmp?x=1&job= HTTP/1.1", &w, &job) && strcmp(w, "bmp") == 0 && strcmp(job, "latest") == 0);
71 free(job);
72 CHECK(!mesh_http_render_route("GET /render.ppm HTTP/1.0", &w, &job));
73 CHECK(!mesh_http_render_route("GET /render HTTP/1.0", &w, &job));
74
75 mesh_log_set_fd(-1);
76 static _Atomic bool quit;
77 mesh_mailbox mb = mesh_mailbox_test();
78 SH = mesh_shared_new(mb, "/tmp", false, 0, 1, &quit);
79 pthread_t dt, st;
80 pthread_create(&dt, NULL, driver, NULL);
81 lfd = socket(AF_INET, SOCK_STREAM, 0);
82 struct sockaddr_in sa = {0};
83 sa.sin_family = AF_INET;
84 sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
85 bind(lfd, (struct sockaddr *)&sa, sizeof sa);
86 socklen_t sl = sizeof sa;
87 getsockname(lfd, (struct sockaddr *)&sa, &sl);
88 listen(lfd, 8);
89 port = ntohs(sa.sin_port);
90 pthread_create(&st, NULL, serve, NULL);
91
92 char *r = get("GET /fleet HTTP/1.0\r\n\r\n");
93 const char *body = "fleet system=0 n=1 role=single -- not in a fleet (single meshd: --cluster / --peer)\n";
94 char want[512];
95 snprintf(want, sizeof want, "HTTP/1.0 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: %zu\r\nCache-Control: no-store\r\n\r\n%s", strlen(body), body);
96 CHECK_S(r, want);
97 free(r);
98 r = get("GET /pipes?format=json HTTP/1.0\r\n\r\n");
99 CHECK(strstr(r, "Content-Type: application/json\r\n") && strstr(r, "\r\n\r\n{\"pipes\":[{\"system\":0,\"inboxes\":0}]}\n"));
100 free(r);
101 r = get("GET /jobs?format=xml HTTP/1.0\r\n\r\n");
102 CHECK(strncmp(r, "HTTP/1.0 400 Bad Request\r\n", 26) == 0 && strstr(r, "\r\n\r\nmeshd: format=xml: json or text\n"));
103 free(r);
104 r = get("GET /status HTTP/1.0\r\n\r\n");
105 CHECK_S(r, "HTTP/1.0 200 OK\r\nContent-Length: 2\r\n\r\nhi");
106 free(r);
107 CHECK_U(mesh_host_block_get_u32(&SH->host, MESH_S_HTTP_REQUESTS), 1);
108 answer = 0;
109 r = get("GET /status HTTP/1.0\r\n\r\n");
110 body = "meshd: no program \"http\" (`programs` lists them)\n";
111 snprintf(want, sizeof want, "HTTP/1.0 503 Service Unavailable\r\nContent-Type: text/plain\r\nContent-Length: %zu\r\n\r\n%s", strlen(body), body);
112 CHECK_S(r, want);
113 free(r);
114 r = get("GET /render?job=latest HTTP/1.0\r\n\r\n");
115 CHECK(strncmp(r, "HTTP/1.0 404 Not Found\r\n", 24) == 0 && strstr(r, "meshd: the blob thread is gone\n"));
116 free(r);
117 r = get("GET /infer?max=4 HTTP/1.0\r\n\r\n");
118 CHECK(strncmp(r, "HTTP/1.0 400 Bad Request\r\n", 26) == 0 && strstr(r, "meshd: infer: no prompt= parameter\n"));
119 free(r);
120 CHECK(atomic_load(&SH->host.net.http[2]) == 7);
121 mesh_chan_drop_sender(SH->cmd);
122 pthread_join(dt, NULL);
123 free(mb.base);
124 return mesh_check_done();
125}