file: support range requests

Signed-off-by: Jianhui Zhao <zhaojh329@gmail.com>
main^2
Jianhui Zhao 2021-01-02 23:30:39 +08:00
parent f767b6cd5f
commit 0755e549ff
3 changed files with 134 additions and 12 deletions

View File

@ -62,9 +62,10 @@ static void conn_send(struct uh_connection *conn, const void *data, ssize_t len)
ev_io_start(conni->srv->loop, &conni->iow);
}
static void conn_send_file(struct uh_connection *conn, const char *path)
static void conn_send_file(struct uh_connection *conn, const char *path, size_t offset, size_t len)
{
struct uh_connection_internal *conni = (struct uh_connection_internal *)conn;
size_t min = 8192;
struct stat st;
int fd;
@ -76,13 +77,24 @@ static void conn_send_file(struct uh_connection *conn, const char *path)
fstat(fd, &st);
/* If the file is not greater than 8K, then append it to the HTTP head, send once */
st.st_size -= buffer_put_fd(&conni->wb, fd, 8192, NULL);
if (st.st_size > 0) {
conni->file.size = st.st_size;
conni->file.fd = fd;
} else {
if (offset >= st.st_size) {
close(fd);
return;
}
lseek(fd, offset, SEEK_SET);
st.st_size -= offset;
if (len == 0 || len > st.st_size)
len = st.st_size;
/* If the file is not greater than 8K, then append it to the HTTP head, send once */
if (len <= min) {
buffer_put_fd(&conni->wb, fd, len, NULL);
close(fd);
} else {
conni->file.size = len;
conni->file.fd = fd;
}
ev_io_start(conni->srv->loop, &conni->iow);

View File

@ -34,6 +34,7 @@
#include <time.h>
#include <fcntl.h>
#include <inttypes.h>
#include <ctype.h>
#include "uhttpd_internal.h"
#include "mimetypes.h"
@ -152,6 +153,102 @@ static void file_if_gzip(struct uh_connection *conn, const char *path, const cha
conn->printf(conn, "Content-Encoding: gzip\r\n");
}
static bool file_range(struct uh_connection *conn, size_t size, size_t *start, size_t *end, bool *ranged)
{
struct uh_connection_internal *conni = (struct uh_connection_internal *)conn;
const struct uh_str hdr = conn->get_header(conn, "Range");
int content_length;
const char *reason;
const char *p, *e;
char buf[32];
int i;
*start = 0;
*end = size - 1;
if (!hdr.p) {
*ranged = false;
return true;
}
if (hdr.len < 8)
goto err;
p = hdr.p;
e = hdr.p + hdr.len;
if (strncmp(p, "bytes=", 6))
goto err;
p += 6;
i = 0;
while (p < e) {
if (i >= sizeof(buf) - 1)
goto err;
if (isdigit(*p)) {
buf[i++] = *p++;
continue;
}
if (*p != '-')
goto err;
p++;
buf[i] = '\0';
break;
}
*start = strtoul(buf, NULL, 0);
i = 0;
while (p < e) {
if (i >= (sizeof(buf) - 1) || !isdigit(*p))
goto err;
buf[i++] = *p++;
}
buf[i] = '\0';
*end = strtoul(buf, NULL, 0);
if (*start >= size)
goto err;
if (*end == 0)
*end = size - 1;
if (*end < *start)
goto err;
if (*end > size - 1)
*end = size - 1;
*ranged = true;
return true;
err:
reason = http_status_str(HTTP_STATUS_RANGE_NOT_SATISFIABLE);
content_length = strlen(reason);
conn->send_status_line(conn, HTTP_STATUS_RANGE_NOT_SATISFIABLE, "Content-Type: text/plain\r\nConnection: close\r\n");
conn->printf(conn, "Content-Length: %d\r\n", content_length);
conn->printf(conn, "Content-Range: bytes */%" PRIu64 "\r\n", size);
conn->send(conn, "\r\n", 2);
conn->send(conn, reason, content_length);
conni->flags |= CONN_F_SEND_AND_CLOSE;
conn->done(conn);
return false;
}
void serve_file(struct uh_connection *conn)
{
struct uh_connection_internal *conni = (struct uh_connection_internal *)conn;
@ -160,8 +257,10 @@ void serve_file(struct uh_connection *conn)
const char *docroot = srv->docroot;
const char *index_page = srv->index_page;
static char fullpath[512];
size_t start, end;
const char *mime;
struct stat st;
bool ranged;
if (!docroot || !docroot[0])
docroot = ".";
@ -210,6 +309,9 @@ void serve_file(struct uh_connection *conn)
return;
}
if (!file_range(conn, st.st_size, &start, &end, &ranged))
return;
if (!file_if_modified_since(conn, &st) ||
!file_if_range(conn, &st) ||
!file_if_unmodified_since(conn, &st)) {
@ -217,21 +319,29 @@ void serve_file(struct uh_connection *conn)
return;
}
conn->send_status_line(conn, HTTP_STATUS_OK, NULL);
if (ranged)
conn->send_status_line(conn, HTTP_STATUS_PARTIAL_CONTENT, NULL);
else
conn->send_status_line(conn, HTTP_STATUS_OK, NULL);
file_response_ok_hdrs(conn, &st);
mime = file_mime_lookup(fullpath);
conn->printf(conn, "Content-Type: %s\r\n", mime);
conn->printf(conn, "Content-Length: %" PRIu64 "\r\n", st.st_size);
conn->printf(conn, "Content-Length: %" PRIu64 "\r\n", end - start + 1);
file_if_gzip(conn, fullpath, mime);
if (ranged)
conn->printf(conn, "Content-Range: bytes %" PRIu64 "-%" PRIu64 "/%" PRIu64 "\r\n", start, end, st.st_size);
else
file_if_gzip(conn, fullpath, mime);
conn->printf(conn, "\r\n");
if (conn->get_method(conn) == HTTP_HEAD)
return;
conn->send_file(conn, fullpath);
conn->send_file(conn, fullpath, start, end - start + 1);
conn->done(conn);
}

View File

@ -52,7 +52,7 @@ struct uh_connection {
*/
void (*done)(struct uh_connection *conn);
void (*send)(struct uh_connection *conn, const void *data, ssize_t len);
void (*send_file)(struct uh_connection *conn, const char *path);
void (*send_file)(struct uh_connection *conn, const char *path, size_t offset, size_t len);
void (*printf)(struct uh_connection *conn, const char *format, ...);
void (*vprintf)(struct uh_connection *conn, const char *format, va_list arg);
void (*send_status_line)(struct uh_connection *conn, int code, const char *extra_headers);