Skip to content

Instantly share code, notes, and snippets.

@x-yuri
Created October 26, 2024 18:57
Show Gist options
  • Save x-yuri/98f2f63a7c7af700c0f2535082738a09 to your computer and use it in GitHub Desktop.
Save x-yuri/98f2f63a7c7af700c0f2535082738a09 to your computer and use it in GitHub Desktop.
Reading an HTTP request without blocking

Reading an HTTP request without blocking

It's assumed that only valid requests are received. And that read() doesn't block until \r\n\r\n is received.

a.c:

#include <unistd.h>
#include <err.h>
#include <string.h>

#include "a.h"

#define BUF_SIZE 10

/*
#include <stdio.h>
void print_buf(char *buf, size_t len)
{
    fputs("buf: (", stdout);
    for (size_t i = 0; i < len; i++) {
        if (buf[i] < 32 || buf[i] == 127) {
            printf("\\%02x", buf[i]);
        } else putchar(buf[i]);
    }
    puts(")");
}
*/

size_t read_request(int client) {
    char buf[BUF_SIZE], *bufp = buf;
    size_t n_read = 0;
    while (1) {
        ssize_t r;
        size_t i;
        r = read(client, bufp, BUF_SIZE - (bufp - buf));
        if (r == -1) err(1, "read()");
        n_read += r;
        /* print_buf(buf, bufp - buf + r); */

        /* find the beginning of the last \r\n sequence */
        i = r;
        while (i > 0 && r - i < 4 && (bufp[i - 1] == '\r' || bufp[i - 1] == '\n'))
            i--;

        /* copy the sequence to the beginning of the buffer */
        if (i > 0) {
            i += bufp - buf;
            r += bufp - buf;
            bufp = buf;
        }
        if (i < (size_t)r) {
            size_t j;
            for (j = 0; i < (size_t)r; j++, i++)
                bufp[j] = bufp[i];
            bufp = bufp + j;
        }

        /* break if reached \r\n\r\n */
        if (bufp - buf == 4 && memcmp(buf, "\r\n\r\n", 4) == 0)
            break;
    }
    return n_read;
}

a.h:

size_t read_request(int);

a.test.c:

#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <setjmp.h>
#include <cmocka.h>

#include <string.h>
#include <stdio.h>
#include <sys/types.h>

#include "d.h"

size_t n_executed = 0;

ssize_t __wrap_read(int, void *, size_t);
ssize_t __wrap_read_impl(int, void *, size_t, size_t, ...);

ssize_t __wrap_read(int fd, void *buf, size_t count) {
    switch (fd) {
        case 1: return __wrap_read_impl(fd, buf, count, 4, "\r", "\n", "\r", "\n");
        case 2: return __wrap_read_impl(fd, buf, count, 2, "\r\n", "a\r\n\r\n");
        case 3: return __wrap_read_impl(fd, buf, count, 2, "a", "\r\n\r\n");
        default: return -1;
    }
}

ssize_t __wrap_read_impl(int fd, void *buf, size_t count, size_t n, ...) {
    (void) fd; /* unused */
    (void) count; /* unused */
    va_list args;
    size_t i;
    char *r;
    if (n == n_executed) {
        puts("blocking...");
        return -1;
    }
    va_start(args, n);
    for (i = 0; i < n_executed + 1; i++)
        r = va_arg(args, char *);
    va_end(args);
    memcpy(buf, r, strlen(r));
    n_executed++;
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
    return strlen(r);
    #pragma GCC diagnostic pop
}

static void test_read_mock1(void **state) {
    (void) state; /* unused */
    size_t r;
    n_executed = 0;
    r = read_request(1);
    assert_int_equal(r, 4);
}

static void test_read_mock2(void **state) {
    (void) state; /* unused */
    size_t r;
    n_executed = 0;
    r = read_request(2);
    assert_int_equal(r, 7);
}

static void test_read_mock3(void **state) {
    (void) state; /* unused */
    size_t r;
    n_executed = 0;
    r = read_request(3);
    assert_int_equal(r, 5);
}

int main(void) {
    const struct CMUnitTest tests[] = {
        cmocka_unit_test(test_read_mock1),
        cmocka_unit_test(test_read_mock2),
        cmocka_unit_test(test_read_mock3),
    };
    return cmocka_run_group_tests(tests, NULL, NULL);
}
$ gcc -Wall -Wextra -pedantic -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -std=c99 -O \
    d.c d.test.c -lcmocka -Wl,--wrap=read
$ ./a.out
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment