Implement ranged requests for static files
This commit is contained in:
parent
24a34c3633
commit
dc05e13c97
|
@ -0,0 +1,31 @@
|
|||
#ifndef _LIGHTTPD_HTTP_RANGE_PARSER_H_
|
||||
#define _LIGHTTPD_HTTP_RANGE_PARSER_H_
|
||||
|
||||
#include <lighttpd/base.h>
|
||||
|
||||
typedef struct {
|
||||
/* public */
|
||||
gboolean last_range;
|
||||
goffset range_start, range_length, range_end; /* length = end - start + 1; */
|
||||
|
||||
/* private */
|
||||
GString *data;
|
||||
goffset limit; /* "file size" */
|
||||
gboolean found_valid_range;
|
||||
|
||||
int cs;
|
||||
gchar *data_pos;
|
||||
} liParseHttpRangeState;
|
||||
|
||||
typedef enum {
|
||||
LI_PARSE_HTTP_RANGE_OK,
|
||||
LI_PARSE_HTTP_RANGE_DONE,
|
||||
LI_PARSE_HTTP_RANGE_INVALID,
|
||||
LI_PARSE_HTTP_RANGE_NOT_SATISFIABLE
|
||||
} liParseHttpRangeResult;
|
||||
|
||||
LI_API void li_parse_http_range_init(liParseHttpRangeState* s, GString *range_str, goffset limit);
|
||||
LI_API liParseHttpRangeResult li_parse_http_range_next(liParseHttpRangeState* s);
|
||||
LI_API void li_parse_http_range_clear(liParseHttpRangeState* s);
|
||||
|
||||
#endif
|
|
@ -12,6 +12,7 @@ enum liCoreOptions {
|
|||
LI_CORE_OPTION_LOG,
|
||||
|
||||
LI_CORE_OPTION_STATIC_FILE_EXCLUDE,
|
||||
LI_CORE_OPTION_STATIC_RANGE_REQUESTS,
|
||||
|
||||
LI_CORE_OPTION_SERVER_NAME,
|
||||
LI_CORE_OPTION_SERVER_TAG,
|
||||
|
|
|
@ -177,6 +177,7 @@ SET(LIGHTTPD_SHARED_SRC
|
|||
etag.c
|
||||
filter_chunked.c
|
||||
http_headers.c
|
||||
http_range_parser.c
|
||||
http_request_parser.c
|
||||
http_response_parser.c
|
||||
lighttpd-glue.c
|
||||
|
@ -235,6 +236,7 @@ ADD_PREFIX(ANGEL_SHARED_SRC angel/)
|
|||
|
||||
## Build parsers by using ragel...
|
||||
RAGEL_PARSER(main/config_parser.rl -T0)
|
||||
RAGEL_PARSER(main/http_range_parser.rl)
|
||||
RAGEL_PARSER(main/http_request_parser.rl)
|
||||
RAGEL_PARSER(main/http_response_parser.rl)
|
||||
RAGEL_PARSER(common/ip_parsers.rl)
|
||||
|
|
|
@ -16,6 +16,7 @@ lighttpd_shared_src= \
|
|||
etag.c \
|
||||
filter_chunked.c \
|
||||
http_headers.c \
|
||||
http_range_parser.c \
|
||||
http_request_parser.c \
|
||||
http_response_parser.c \
|
||||
lighttpd-glue.c \
|
||||
|
@ -61,7 +62,7 @@ lighttpd_shared_src+=$(lua_src)
|
|||
endif
|
||||
EXTRA_lighttpd_SOURCES=$(lua_src)
|
||||
|
||||
BUILT_SOURCES=config_parser.c http_request_parser.c http_response_parser.c url_parser.c
|
||||
BUILT_SOURCES=config_parser.c http_range_parser.rl http_request_parser.c http_response_parser.c url_parser.c
|
||||
|
||||
config_parser.c: config_parser.rl
|
||||
ragel -C -T0 -o $@ $<
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
|
||||
#include <lighttpd/base.h>
|
||||
#include <lighttpd/http_range_parser.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
%%{
|
||||
machine http_range_parser;
|
||||
variable cs s->cs;
|
||||
variable p s->data_pos;
|
||||
|
||||
SP = ' ';
|
||||
HT = '\t';
|
||||
|
||||
ws = SP | HT;
|
||||
|
||||
action int_start {
|
||||
tmp = 0;
|
||||
}
|
||||
action int_step {
|
||||
int d = fc - '0';
|
||||
if (tmp > (G_MAXOFFSET-10)/10) {
|
||||
s->cs = http_range_parser_error;
|
||||
return LI_PARSE_HTTP_RANGE_INVALID;
|
||||
}
|
||||
tmp = 10*tmp + d;
|
||||
}
|
||||
|
||||
int = (digit digit**) >int_start $int_step ;
|
||||
|
||||
action first_byte {
|
||||
s->range_start = tmp;
|
||||
}
|
||||
action last_byte {
|
||||
s->range_end = tmp;
|
||||
}
|
||||
action last_byte_empty {
|
||||
s->range_end = s->limit;
|
||||
}
|
||||
action suffix_range {
|
||||
s->range_end = s->limit - 1;
|
||||
s->range_start = s->limit - tmp;
|
||||
}
|
||||
|
||||
range = (int %first_byte "-" (int %last_byte | "" %last_byte_empty) | "-" int %suffix_range);
|
||||
|
||||
main := ws* "bytes" "=" range ("," >{fbreak;} range)**;
|
||||
|
||||
write data;
|
||||
}%%
|
||||
|
||||
liParseHttpRangeResult li_parse_http_range_next(liParseHttpRangeState* s) {
|
||||
const char *pe, *eof;
|
||||
goffset tmp = 0;
|
||||
|
||||
eof = pe = s->data->str + s->data->len;
|
||||
|
||||
for ( ;; ) {
|
||||
if (s->cs >= http_range_parser_first_final) {
|
||||
return s->found_valid_range ? LI_PARSE_HTTP_RANGE_DONE : LI_PARSE_HTTP_RANGE_NOT_SATISFIABLE;
|
||||
}
|
||||
|
||||
%% write exec;
|
||||
|
||||
if (s->cs >= http_range_parser_first_final) {
|
||||
s->last_range = TRUE;
|
||||
} else if (s->cs == http_range_parser_error) {
|
||||
return LI_PARSE_HTTP_RANGE_INVALID;
|
||||
}
|
||||
|
||||
if (s->range_end >= s->limit) {
|
||||
s->range_end = s->limit - 1;
|
||||
}
|
||||
if (s->range_start < 0) {
|
||||
s->range_start = 0;
|
||||
}
|
||||
|
||||
if (s->range_start <= s->range_end) {
|
||||
s->found_valid_range = TRUE;
|
||||
s->range_length = s->range_end - s->range_start + 1;
|
||||
return LI_PARSE_HTTP_RANGE_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void li_parse_http_range_init(liParseHttpRangeState* s, GString *range_str, goffset limit) {
|
||||
s->data = g_string_new_len(GSTR_LEN(range_str));
|
||||
s->data_pos = s->data->str;
|
||||
s->limit = limit;
|
||||
s->last_range = FALSE;
|
||||
s->found_valid_range = FALSE;
|
||||
|
||||
%% write init;
|
||||
}
|
||||
|
||||
void li_parse_http_range_clear(liParseHttpRangeState* s) {
|
||||
if (s->data) {
|
||||
g_string_free(s->data, TRUE);
|
||||
s->data = NULL;
|
||||
}
|
||||
}
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
#include <lighttpd/version.h>
|
||||
|
||||
#include <lighttpd/http_range_parser.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
|
@ -312,6 +314,7 @@ static liHandlerResult core_handle_static(liVRequest *vr, gpointer param, gpoint
|
|||
struct stat st;
|
||||
int err;
|
||||
liHandlerResult res;
|
||||
static const gchar boundary[] = "fkj49sn38dcn3";
|
||||
|
||||
UNUSED(param);
|
||||
UNUSED(context);
|
||||
|
@ -375,8 +378,13 @@ static liHandlerResult core_handle_static(liVRequest *vr, gpointer param, gpoint
|
|||
}
|
||||
vr->response.http_status = 403;
|
||||
} else {
|
||||
GString *mime_str;
|
||||
const GString *mime_str;
|
||||
gboolean cachable;
|
||||
gboolean ranged_response = FALSE;
|
||||
liHttpHeader *hh_range;
|
||||
liChunkFile *cf;
|
||||
static const GString default_mime_str = { CONST_STR_LEN("application/octet-stream"), 0 };
|
||||
|
||||
#ifdef FD_CLOEXEC
|
||||
fcntl(fd, F_SETFD, FD_CLOEXEC);
|
||||
#endif
|
||||
|
@ -393,13 +401,79 @@ static liHandlerResult core_handle_static(liVRequest *vr, gpointer param, gpoint
|
|||
return LI_HANDLER_GO_ON;
|
||||
}
|
||||
|
||||
cf = li_chunkfile_new(NULL, fd, FALSE);
|
||||
|
||||
mime_str = li_mimetype_get(vr, vr->physical.path);
|
||||
vr->response.http_status = 200;
|
||||
if (mime_str)
|
||||
if (!mime_str) mime_str = &default_mime_str;
|
||||
|
||||
if (CORE_OPTION(LI_CORE_OPTION_STATIC_RANGE_REQUESTS).boolean) {
|
||||
li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Accept-Ranges"), CONST_STR_LEN("bytes"));
|
||||
|
||||
hh_range = li_http_header_lookup(vr->request.headers, CONST_STR_LEN("range"));
|
||||
if (hh_range) {
|
||||
/* TODO: Check If-Range: header */
|
||||
GString range_str = { HEADER_VALUE_LEN(hh_range), 0 };
|
||||
liParseHttpRangeState rs;
|
||||
gboolean is_multipart = FALSE, done = FALSE;
|
||||
|
||||
li_parse_http_range_init(&rs, &range_str, st.st_size);
|
||||
do {
|
||||
switch (li_parse_http_range_next(&rs)) {
|
||||
case LI_PARSE_HTTP_RANGE_OK:
|
||||
if (!is_multipart && !rs.last_range) {
|
||||
is_multipart = TRUE;
|
||||
}
|
||||
g_string_printf(vr->wrk->tmp_str, "bytes %"G_GOFFSET_FORMAT"-%"G_GOFFSET_FORMAT"/%"G_GOFFSET_FORMAT, rs.range_start, rs.range_end, (goffset) st.st_size);
|
||||
if (is_multipart) {
|
||||
GString *subheader = g_string_sized_new(1023);
|
||||
g_string_append_printf(subheader, "\r\n--%s\r\nContent-Type: %s\r\nContent-Range: %s\r\n\r\n", boundary, mime_str->str, vr->wrk->tmp_str->str);
|
||||
li_chunkqueue_append_string(vr->out, subheader);
|
||||
li_chunkqueue_append_chunkfile(vr->out, cf, rs.range_start, rs.range_length);
|
||||
} else {
|
||||
li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Range"), GSTR_LEN(vr->wrk->tmp_str));
|
||||
li_chunkqueue_append_chunkfile(vr->out, cf, rs.range_start, rs.range_length);
|
||||
}
|
||||
break;
|
||||
case LI_PARSE_HTTP_RANGE_DONE:
|
||||
ranged_response = TRUE;
|
||||
done = TRUE;
|
||||
vr->response.http_status = 206;
|
||||
if (is_multipart) {
|
||||
GString *subheader = g_string_sized_new(1023);
|
||||
g_string_append_printf(subheader, "\r\n--%s--\r\n", boundary);
|
||||
li_chunkqueue_append_string(vr->out, subheader);
|
||||
|
||||
g_string_printf(vr->wrk->tmp_str, "multipart/byteranges; boundary=%s", boundary);
|
||||
li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), GSTR_LEN(vr->wrk->tmp_str));
|
||||
} else {
|
||||
li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), GSTR_LEN(mime_str));
|
||||
}
|
||||
break;
|
||||
case LI_PARSE_HTTP_RANGE_INVALID:
|
||||
done = TRUE;
|
||||
li_chunkqueue_reset(vr->out);
|
||||
break;
|
||||
case LI_PARSE_HTTP_RANGE_NOT_SATISFIABLE:
|
||||
ranged_response = TRUE;
|
||||
done = TRUE;
|
||||
li_chunkqueue_reset(vr->out); vr->out->is_closed = TRUE;
|
||||
g_string_printf(vr->wrk->tmp_str, "bytes */%"G_GOFFSET_FORMAT, (goffset) st.st_size);
|
||||
li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Range"), GSTR_LEN(vr->wrk->tmp_str));
|
||||
vr->response.http_status = 416;
|
||||
break;
|
||||
}
|
||||
} while (!done);
|
||||
li_parse_http_range_clear(&rs);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ranged_response) {
|
||||
vr->response.http_status = 200;
|
||||
li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), GSTR_LEN(mime_str));
|
||||
else
|
||||
li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream"));
|
||||
li_chunkqueue_append_file_fd(vr->out, NULL, 0, st.st_size, fd);
|
||||
li_chunkqueue_append_chunkfile(vr->out, cf, st.st_size, fd);
|
||||
}
|
||||
|
||||
li_chunkfile_release(cf);
|
||||
}
|
||||
|
||||
return LI_HANDLER_GO_ON;
|
||||
|
@ -1195,6 +1269,7 @@ static const liPluginOption options[] = {
|
|||
{ "log", LI_VALUE_HASH, NULL, core_option_log_parse, core_option_log_free },
|
||||
|
||||
{ "static.exclude", LI_VALUE_LIST, NULL, NULL, NULL }, /* TODO: not used right now */
|
||||
{ "static.range-requests", LI_VALUE_BOOLEAN, GINT_TO_POINTER(TRUE), NULL, NULL },
|
||||
|
||||
{ "server.name", LI_VALUE_STRING, NULL, NULL, NULL },
|
||||
{ "server.tag", LI_VALUE_STRING, PACKAGE_DESC, NULL, NULL },
|
||||
|
|
|
@ -31,6 +31,7 @@ def build(bld):
|
|||
etag.c
|
||||
filter_chunked.c
|
||||
http_headers.c
|
||||
http_range_parser.rl
|
||||
http_request_parser.rl
|
||||
http_response_parser.rl
|
||||
lighttpd-glue.c
|
||||
|
|
Loading…
Reference in New Issue