skip to content
==> 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}