Skip to content

Commit

Permalink
parse Range header
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikaoto committed Jul 26, 2022
1 parent 73c9a29 commit d8c3248
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 3 deletions.
130 changes: 129 additions & 1 deletion http.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,99 @@ is_http_end(char *buf, size_t size)
buf[i-1] == '\r' && buf[i] == '\n';
}

// Return a ^ b.
// Expects b >= 0.
long long
ll_power(long long a, long long b) {
if (b == 0) return 1;

long long ret = a;
while (b > 1) {
ret *= a;
b--;
}

return ret;
}

// Parses next number, returns its value and advances 'str' to stand on the
// char after number.
// Assumes 'str' starts with a digit.
// 'end' is he outer bound of the string.
long long
consume_next_num(char **str, char *end)
{
long long num = 0;
int num_len = 0;
while (is_digit(*(*str + num_len)) && (*str + num_len) != end) {
num_len++;
}

for (int i = 0; i < num_len; i++) {
n = *(*str + i) - '0';
long long increment = ((long long) n) * ll_power(10, num_len - i - 1);
num += increment;
}

*str += num_len;

return num;
}

int
parse_range_header(
char *str,
size_t len,
int* range_start_given,
off_t* range_start,
int* range_end_given,
off_t* range_end)
{
char *end = str + len;

// Advance str by n bytes; Return if running over buffer
#define SAFE_ADVANCE(str, n) { \
if (((str) + (n)) >= end) { \
return 0; \
} \
(str) += (n); \
}

if (strncmp(str, "bytes=", 6)) {
return 0;
}
SAFE_ADVANCE(str, 6);

// Range start
if (is_digit(*str)) {
*range_start_given = 1;
*range_start = (off_t) consume_next_num(&str, end);
}

// The dash between the ranges
if (*str != '-') {
*range_start_given = 0;
return 0;
}
SAFE_ADVANCE(str, 1);

// Range end
if (is_digit(*str)) {
*range_end_given = 1;
*range_end = (off_t) consume_next_num(&str, end);
}

// Neither of the ranges were given
if (!range_start_given && !range_end_given) {
*range_start = 0;
*range_end = 0;
return 0;
}

return 1;
#undef SAFE_ADVANCE
}

// Parses buffer and fills out Http_Request fields
Http_Request*
parse_http_request(Http_Request *req)
Expand All @@ -74,7 +167,7 @@ parse_http_request(Http_Request *req)
(str) += (n); \
}

// Left and right boundaries of a token
// Left (l) and right (r) boundaries of a token
char *l = req->buf->data;
char *r = req->buf->data;

Expand Down Expand Up @@ -209,10 +302,17 @@ parse_http_request(Http_Request *req)
req->accept = xstrndup(hv, hv_len);
} else if (!strncasecmp("Connection:", hn, hn_len + 1)) {
req->connection = xstrndup(hv, hv_len);
} else if (!strncasecmp("Range:", hn, hn_len + 1)) {
parse_range_header(hv, hv_len,
&req->range_start_given,
&req->range_start,
&req->range_end_given,
&req->range_end);
}
}

return req;
#undef SAFE_ADVANCE
}

void
Expand Down Expand Up @@ -249,6 +349,10 @@ print_http_request(FILE *f, Http_Request *req)
fprintf(f, " .user_agent = \"%s\",\n", req->user_agent);
fprintf(f, " .accept = \"%s\",\n", req->accept);
fprintf(f, " .error = \"%s\",\n", req->error);
fprintf(f, " .range_start_given = %d,\n", req->range_start_given);
fprintf(f, " .range_start = %zu,\n", req->range_start);
fprintf(f, " .range_end_given = %d,\n", req->range_end_given);
fprintf(f, " .range_end = %zu,\n", req->range_end);
fprintf(f, "}\n");
}

Expand Down Expand Up @@ -363,6 +467,11 @@ make_http_response(Server *serv, Http_Request *req)
res->file_nbytes_sent = 0;
res->file_path = NULL;

res->range_start = req->range_start_given ?
req->range_start : 0;
res->range_end = req->range_end_given ?
req->range_end : (off_t) res->body.n_items;

res->error = NULL;

char *clean_http_path = cleanup_path(req->path);
Expand Down Expand Up @@ -390,6 +499,7 @@ make_http_response(Server *serv, Http_Request *req)
/* TODO: have a function like
`write_standard_headers(buf, serv)` that puts
Connection, Keep-Alive, Date headers into buf */
buf_append_str(&res->head, "Accept-Ranges: bytes\r\n");
buf_append_str(&res->head, "Content-Type: text/plain\r\n");
buf_sprintf(&res->head, "Content-Length: %zu\r\n", strlen(body));
/*buf_sprintf(res->buf, "Keep-Alive: timeout=%d\r\n",
Expand Down Expand Up @@ -419,6 +529,7 @@ make_http_response(Server *serv, Http_Request *req)
"HTTP/1.1 301\r\n"
"Location: %s/\r\n",
decoded_http_path);
buf_append_str(&res->head, "Accept-Ranges: bytes\r\n");
/*buf_sprintf(
&res->head,
"Keep-Alive: timeout=%d\r\n\r\n",
Expand Down Expand Up @@ -455,12 +566,29 @@ make_http_response(Server *serv, Http_Request *req)
&res->body,
real_path,
decoded_http_path);

// Check if the ranges given are invalid
if ((req->range_start_given &&
req->range_start > (off_t) res->body.n_items) ||
(req->range_end_given &&
req->range_end > (off_t) res->body.n_items)) {
// TODO: return range cannot be satisfied error
}
return fulfill(&dq, res);
}
}

// We're serving a single file
// Check if the ranges given are invalid
if ((req->range_start_given &&
req->range_start > res->file.size) ||
(req->range_end_given &&
req->range_end > res->file.size)) {
// TODO: return range cannot be satisfied error
}

buf_append_str(&res->head, "HTTP/1.1 200\r\n");
buf_append_str(&res->head, "Accept-Ranges: bytes\r\n");

// Keep-Alive
/*buf_sprintf(
Expand Down
38 changes: 37 additions & 1 deletion mimino.c
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,10 @@ write_body(Connection *conn)

int sent = send_buf(
conn->fd,
//conn->res->range_start +
conn->res->body.data + conn->res->body_nbytes_sent,
//conn->res->range_end - conn->res->body_nbytes_sent
// use this ^ instead of the thing below V
conn->res->body.n_items - conn->res->body_nbytes_sent);

// Fatal error, no use retrying
Expand All @@ -307,6 +310,17 @@ write_body(Connection *conn)
conn->res->body_nbytes_sent += sent;

// Retry later if not sent fully
/*
TODO: the actual check for if the requested data was
fully sent or not should look like something below:
res->range_start = req->range_start_given ?
req->range_start : 0;
res->range_end = req->range_end_given ?
req->range_end : res->body.n_items;
if (res->range_start + conn->res->body_nbytes_sent < res->range_end) {
*/
if (conn->res->body_nbytes_sent < conn->res->body.n_items) {
if (conn->write_tries_left == 0) {
conn->res->error = "write_body(): Max write tries reached";
Expand Down Expand Up @@ -508,6 +522,8 @@ do_conn_state(Server *serv, nfds_t idx)
// TODO: turn this into a separate state
parse_http_request(conn->req);

print_http_request(stdout, conn->req);

// Parse error
if (conn->req->error) {
fprintf(stdout,
Expand Down Expand Up @@ -901,11 +917,30 @@ send_buf(int sock, char *buf, size_t len)
size_t nbytes_sent;
int try = 5;

// TODO: This retry loop might be unnecessary.
//
// * Retrying immediately on EINTR/EAGAIN will most likely
// do nothing as not enough time will have passed for the
// socket to become writable.
//
// * Run a load test on the server using this retry
// mechanism and then run one where try=1.
//
// My theory is that the latter variant will be slightly
// faster as it won't unsuccessfully call send() 4 more
// times every time we have a temporarily unavailable
// socket and instead will serve other requests and thus
// pass time until the blocked sockets become available
// again.
//
// A retry mechanism in general is useful for
// slow/intermittent connections, so even if I end up
// removing the one below, it's still wise to have one
// outside this function.
for (nbytes_sent = 0; nbytes_sent < len;) {
int sent = send(sock, buf, len, 0);
int saved_errno = errno;
if (sent == -1) {
perror("send()");
switch (saved_errno) {
case EINTR:
case EAGAIN:
Expand All @@ -915,6 +950,7 @@ send_buf(int sock, char *buf, size_t len)
continue;
case ECONNRESET:
default:
perror("send()");
return -1;
break;
}
Expand Down
9 changes: 9 additions & 0 deletions mimino.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ typedef struct {
char *accept;
char *connection;
char *error;

int range_start_given;
off_t range_start;

int range_end_given;
off_t range_end;
} Http_Request;

typedef struct {
Expand All @@ -49,6 +55,9 @@ typedef struct {
size_t file_nbytes_sent;
char *file_path;

off_t range_start;
off_t range_end;

char *error;
} Http_Response;

Expand Down
2 changes: 1 addition & 1 deletion todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
- optimizaiton
- use hashmap instead of lookup table for mime types
- when serving a single non-directory file, don't resolve any paths
- caching with infinite (?) TTL
- implement caching (with infinite (?) TTL)
- use readdir() instead of scandir() for faster dir scanning
(~/src/darkhttpd/darkhttpd.c:1830:0)
- Try on improvements from GoodSocket by jart:
Expand Down

0 comments on commit d8c3248

Please sign in to comment.