Serve a range of a file payload via HTTP

This commit is contained in:
Jeremy Lakeman 2013-08-15 15:47:39 +09:30
parent e322114850
commit 21132466f1
4 changed files with 82 additions and 17 deletions

View File

@ -456,6 +456,7 @@ typedef struct rhizome_http_request {
/* The HTTP request as currently received */
int request_length;
int header_length;
char request[1024];
/* Nature of the request */
@ -535,6 +536,8 @@ typedef struct rhizome_http_request {
struct http_response {
unsigned int result_code;
const char * content_type;
uint64_t content_start;
uint64_t content_end;
uint64_t content_length;
const char * body;
};

View File

@ -140,6 +140,7 @@ int rhizome_direct_form_received(rhizome_http_request *r)
/* Write HTTP response header */
struct http_response hr;
bzero(&hr, sizeof hr);
hr.result_code=200;
hr.content_type="binary/octet-stream";
hr.content_length=bytes;
@ -836,7 +837,11 @@ void rhizome_direct_http_dispatch(rhizome_direct_sync_request *r)
/* Allocate a buffer to receive the entire action list */
content_length = parts.content_length;
unsigned char *actionlist=malloc(content_length);
unsigned char *actionlist=emalloc(content_length);
if (!actionlist){
close(sock);
goto end;
}
bcopy(parts.content_start, actionlist, len);
if (fill_buffer(sock, actionlist, len, content_length)==-1){
free(actionlist);

View File

@ -228,9 +228,11 @@ void rhizome_client_poll(struct sched_ent *alarm)
unschedule(&r->alarm);
schedule(&r->alarm);
r->request_length += bytes;
if (http_header_complete(r->request, r->request_length, bytes)) {
r->header_length = http_header_complete(r->request, r->request_length, bytes);
if (r->header_length){
/* We have the request. Now parse it to see if we can respond to it */
if (rhizome_http_parse_func!=NULL) rhizome_http_parse_func(r);
if (rhizome_http_parse_func!=NULL)
rhizome_http_parse_func(r);
}
} else {
if (config.debug.rhizome_tx)
@ -482,19 +484,25 @@ int http_header_complete(const char *buf, size_t len, size_t read_since_last_cal
{
IN();
const char *bufend = buf + len;
const char *p = buf;
size_t tail = read_since_last_call + 4;
if (tail < len)
buf = bufend - tail;
p = bufend - tail;
int count = 0;
for (; count < 2 && buf != bufend; ++buf) {
switch (*buf) {
case '\n': ++count; break;
case '\r': break;
case '\0': break; // ignore NUL (telnet inserts them)
default: count = 0; break;
for (; p != bufend; ++p) {
switch (*p) {
case '\n':
if (++count==2)
RETURN(p - buf);
case '\r': // ignore CR
case '\0': // ignore NUL (telnet inserts them)
break;
default:
count = 0;
break;
}
}
RETURN(count == 2);
RETURN(0);
OUT();
}
@ -508,6 +516,8 @@ int rhizome_server_parse_http_request(rhizome_http_request *r)
r->request_type = 0;
// Parse the HTTP "GET" line.
char *path = NULL;
const char *headers = NULL;
int header_length = 0;
size_t pathlen = 0;
if (str_startswith(r->request, "POST ", (const char **)&path)) {
return rhizome_direct_parse_http_request(r);
@ -520,10 +530,11 @@ int rhizome_server_parse_http_request(rhizome_http_request *r)
pathlen = p - path;
if ( str_startswith(p, " HTTP/1.", &p)
&& (str_startswith(p, "0", &p) || str_startswith(p, "1", &p))
&& (str_startswith(p, "\r\n", &p) || str_startswith(p, "\n", &p))
)
&& (str_startswith(p, "\r\n", &headers) || str_startswith(p, "\n", &headers))
){
path[pathlen] = '\0';
else
header_length = r->header_length - (headers - r->request);
}else
path = NULL;
}
if (path) {
@ -607,7 +618,6 @@ int rhizome_server_parse_http_request(rhizome_http_request *r)
if (!rhizome_str_is_file_hash(id)) {
rhizome_server_simple_http_response(r, 400, "<html><h1>Invalid payload ID</h1></html>\r\n");
} else {
// TODO: Check for Range: header and return 206 if returning partial content
str_toupper_inplace(id);
bzero(&r->read_state, sizeof(r->read_state));
if (rhizome_open_read(&r->read_state, id, 1))
@ -618,9 +628,27 @@ int rhizome_server_parse_http_request(rhizome_http_request *r)
rhizome_server_simple_http_response(r, 404, "<html><h1>Unknown length</h1></html>\r\n");
}
}
const char *range=str_str((char*)headers,"Range: bytes=",header_length);
r->read_state.offset = r->source_index = 0;
if (range){
sscanf(range, "Range: bytes=%"PRId64"-", &r->read_state.offset);
DEBUGF("Found range header %"PRId64,r->read_state.offset);
if (r->read_state.offset)
r->read_state.hash=0;
}
if (r->read_state.length - r->read_state.offset>0){
rhizome_server_http_response_header(r, 200, "application/binary", r->read_state.length - r->read_state.offset);
struct http_response hr;
bzero(&hr, sizeof hr);
hr.result_code = 200;
hr.content_type = "application/binary";
hr.content_start = r->read_state.offset;
hr.content_end = r->read_state.length;
hr.content_length = r->read_state.length;
hr.body = NULL;
rhizome_server_set_response(r, &hr);
r->request_type |= RHIZOME_HTTP_REQUEST_STORE;
}
}
@ -707,7 +735,13 @@ static strbuf strbuf_build_http_response(strbuf sb, const struct http_response *
{
strbuf_sprintf(sb, "HTTP/1.0 %03u %s\r\n", h->result_code, httpResultString(h->result_code));
strbuf_sprintf(sb, "Content-type: %s\r\n", h->content_type);
strbuf_sprintf(sb, "Content-length: %llu\r\n", h->content_length);
if (h->content_end && h->content_length)
strbuf_sprintf(sb,
"Content-range: %"PRIu64"-%"PRIu64"/%"PRIu64"\r\n"
"Content-length: %"PRIu64"\r\n",
h->content_start, h->content_end, h->content_length, h->content_end - h->content_start);
else if (h->content_length)
strbuf_sprintf(sb, "Content-length: %"PRIu64"\r\n", h->content_length);
strbuf_puts(sb, "\r\n");
if (h->body)
strbuf_puts(sb, h->body);

View File

@ -409,6 +409,29 @@ test_FileTransferDelete() {
assert_rhizome_received file1_2
}
doc_HttpFetchRange="Fetch a file range using HTTP GET."
setup_HttpFetchRange() {
setup_curl_7
setup_common
set_instance +A
rhizome_add_file file1
start_servald_instances +A
wait_until rhizome_http_server_started +A
get_rhizome_server_port PORTA +A
}
test_HttpFetchRange() {
executeOk curl \
--silent --fail --show-error \
--output http.output \
--dump-header http.headers \
--write-out '%{http_code}\n' \
--continue-at 32 \
"http://$addr_localhost:$PORTA/rhizome/file/$FILEHASH"
tfw_cat http.headers http.output
assertGrep http.headers "Content-range:"
assertGrep http.headers "Content-length:"
}
doc_HttpImport="Import bundle using HTTP POST multi-part form."
setup_HttpImport() {
setup_curl_7