Browse Source

Implement ranged requests for static files

personal/stbuehler/wip
Stefan Bühler 12 years ago
parent
commit
dc05e13c97
  1. 31
      include/lighttpd/http_range_parser.h
  2. 1
      include/lighttpd/plugin_core.h
  3. 2
      src/CMakeLists.txt
  4. 3
      src/main/Makefile.am
  5. 101
      src/main/http_range_parser.rl
  6. 87
      src/main/plugin_core.c
  7. 1
      src/main/wscript

31
include/lighttpd/http_range_parser.h

@ -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

1
include/lighttpd/plugin_core.h

@ -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,

2
src/CMakeLists.txt

@ -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)

3
src/main/Makefile.am

@ -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 $@ $<

101
src/main/http_range_parser.rl

@ -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;
}
}

87
src/main/plugin_core.c

@ -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 },

1
src/main/wscript

@ -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…
Cancel
Save