/* * A minimal HTTP upstream for the test suite, built on hio. * * It answers any request with a fixed marker plus the request line it * received, so a test driving it through the proxy can tell both that the * response came from here and that the method and path arrived intact. * A request for a path containing "/missing" is answered with 404 so the * status-propagation path can be checked too. * * Usage: httpecho [ready-file] */ #include #include #include #include #include #include #define INBUF_CAPA 8192 typedef struct conn_xtn_t conn_xtn_t; struct conn_xtn_t { hio_bch_t buf[INBUF_CAPA]; hio_oow_t len; int answered; }; /* large enough to push a proxy's queued bytes past its backpressure * threshold on loopback, where the client drains fast. */ #define BIGBODY_LEN (8 * 1024 * 1024) static hio_t* g_hio = HIO_NULL; static void on_sigint (int sig) { if (g_hio) hio_stop (g_hio, HIO_STOPREQ_TERMINATION); } static int answer (hio_dev_sck_t* sck, conn_xtn_t* cx) { hio_bch_t body[INBUF_CAPA + 64]; hio_bch_t head[256]; hio_bch_t reqline[512]; hio_oow_t i, n; int status = 200; const hio_bch_t* reason = "OK"; /* the request line is everything up to the first CR or LF */ for (i = 0; i < cx->len && i < HIO_COUNTOF(reqline) - 1; i++) { if (cx->buf[i] == '\r' || cx->buf[i] == '\n') break; reqline[i] = cx->buf[i]; } reqline[i] = '\0'; if (hio_find_bchars_in_bchars(reqline, i, "/missing", 8, 0)) { status = 404; reason = "Not Found"; } /* a body big enough to back up a proxy's write queue. the pattern is * position-dependent so a truncated or shuffled relay is visible in the * checksum rather than only in the length. */ if (hio_find_bchars_in_bchars(reqline, i, "/big", 4, 0)) { hio_oow_t sent = 0; snprintf (head, HIO_COUNTOF(head), "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nContent-Length: %d\r\nConnection: close\r\n\r\n", (int)BIGBODY_LEN); if (hio_dev_sck_write(sck, head, hio_count_bcstr(head), HIO_NULL, HIO_NULL) <= -1) return -1; while (sent < BIGBODY_LEN) { hio_bch_t chunk[4096]; hio_oow_t k, want = BIGBODY_LEN - sent; if (want > HIO_COUNTOF(chunk)) want = HIO_COUNTOF(chunk); for (k = 0; k < want; k++) chunk[k] = (hio_bch_t)('a' + ((sent + k) % 26)); if (hio_dev_sck_write(sck, chunk, want, HIO_NULL, HIO_NULL) <= -1) return -1; sent += want; } return 0; } n = snprintf(body, HIO_COUNTOF(body), "httpecho\r\n%s\r\n", reqline); snprintf (head, HIO_COUNTOF(head), "HTTP/1.1 %d %s\r\nContent-Type: text/plain\r\nContent-Length: %d\r\nConnection: close\r\n\r\n", status, reason, (int)n); if (hio_dev_sck_write(sck, head, hio_count_bcstr(head), HIO_NULL, HIO_NULL) <= -1) return -1; if (hio_dev_sck_write(sck, body, n, HIO_NULL, HIO_NULL) <= -1) return -1; return 0; } static int on_read (hio_dev_sck_t* sck, const void* buf, hio_iolen_t len, const hio_skad_t* srcaddr) { conn_xtn_t* cx = hio_dev_sck_getxtn(sck); if (len <= 0) return 0; if (cx->answered) return 0; if (cx->len + len > HIO_SIZEOF(cx->buf)) { hio_dev_sck_halt (sck); return 0; } memcpy (&cx->buf[cx->len], buf, len); cx->len += len; /* answer as soon as the header block is complete. a request body is * not needed for what this fixture is for. */ if (cx->len >= 4 && hio_find_bchars_in_bchars(cx->buf, cx->len, "\r\n\r\n", 4, 0)) { cx->answered = 1; if (answer(sck, cx) <= -1) hio_dev_sck_halt (sck); } return 0; } static int on_write (hio_dev_sck_t* sck, hio_iolen_t wrlen, void* wrctx, const hio_skad_t* dstaddr) { return 0; } static void on_connect (hio_dev_sck_t* sck) { if (sck->state & HIO_DEV_SCK_ACCEPTED) { conn_xtn_t* cx = hio_dev_sck_getxtn(sck); cx->len = 0; cx->answered = 0; } } static void on_disconnect (hio_dev_sck_t* sck) { } int main (int argc, char* argv[]) { hio_dev_sck_t* svr; hio_dev_sck_make_t m; hio_dev_sck_bind_t b; hio_dev_sck_listen_t l; hio_skad_t addr; int ret = -1; if (argc < 2 || argc > 3) { fprintf (stderr, "Usage: %s [ready-file]\n", argv[0]); return -1; } g_hio = hio_open(HIO_NULL, 0, HIO_NULL, HIO_FEATURE_ALL, 256, HIO_NULL); if (!g_hio) { fprintf (stderr, "unable to open hio\n"); return -1; } signal (SIGINT, on_sigint); signal (SIGTERM, on_sigint); signal (SIGPIPE, SIG_IGN); if (hio_bcstrtoskad(g_hio, argv[1], &addr) <= -1) { fprintf (stderr, "invalid address - %s\n", argv[1]); goto oops; } memset (&m, 0, HIO_SIZEOF(m)); m.type = (hio_skad_get_family(&addr) == HIO_AF_INET)? HIO_DEV_SCK_TCP4: HIO_DEV_SCK_TCP6; m.on_read = on_read; m.on_write = on_write; m.on_connect = on_connect; m.on_disconnect = on_disconnect; svr = hio_dev_sck_make(g_hio, HIO_SIZEOF(conn_xtn_t), &m); if (!svr) { fprintf (stderr, "unable to make a socket - %s\n", hio_geterrbmsg(g_hio)); goto oops; } memset (&b, 0, HIO_SIZEOF(b)); b.localaddr = addr; b.options = HIO_DEV_SCK_BIND_REUSEADDR; if (hio_dev_sck_bind(svr, &b) <= -1) { fprintf (stderr, "unable to bind - %s\n", hio_geterrbmsg(g_hio)); goto oops; } memset (&l, 0, HIO_SIZEOF(l)); l.backlogs = 64; if (hio_dev_sck_listen(svr, &l) <= -1) { fprintf (stderr, "unable to listen - %s\n", hio_geterrbmsg(g_hio)); goto oops; } if (argc >= 3) { FILE* fp = fopen(argv[2], "w"); if (fp) { fputs ("ready\n", fp); fclose (fp); } } hio_loop (g_hio); ret = 0; oops: if (g_hio) hio_close (g_hio); return ret; }