Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,13 @@ ifeq ($(WITH_HTTP), yes)
$(CXX) -g -Wall -O0 -std=c++11 -I. -Ibase -Ihttp -o bin/websocket_parser_test unittest/websocket_parser_test.cpp -Llib -lhv -pthread
endif
ifeq ($(WITH_EVPP), yes)
ifeq ($(WITH_HTTP_SERVER), yes)
ifeq ($(WITH_HTTP_CLIENT), yes)
$(CXX) -g -Wall -O0 -std=c++11 -I. -Ibase -Issl -Ievent -Icpputil -Ievpp -Ihttp -Ihttp/server -Ihttp/client -o bin/http_static_range_test unittest/http_static_range_test.cpp -Llib -lhv -pthread
endif
endif
endif
ifeq ($(WITH_EVPP), yes)
ifeq ($(WITH_REDIS), yes)
$(CXX) -g -Wall -O0 -std=c++11 -I. -Ibase -Ievent -Icpputil -Iredis -o bin/redis_protocol_test unittest/redis_protocol_test.cpp redis/RedisMessage.cpp
$(CXX) -g -Wall -O0 -std=c++11 -I. -Ibase -Issl -Ievent -Icpputil -Iredis -Ievpp -o bin/redis_async_client_test unittest/redis_async_client_test.cpp unittest/redis_test_server.cpp -Llib -lhv -pthread
Expand Down
60 changes: 53 additions & 7 deletions http/server/HttpHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,42 @@ int HttpHandler::defaultRequestHandler() {
return status_code;
}

// Parses the first range of a "bytes=" Range header for a file of `total`
// bytes: "first-last", "first-" or a suffix "-length" (RFC 9110 section 14.1.2).
// Returns 0 with [from, to] set, -1 if the range cannot be satisfied, and 1 if
// the header is to be ignored (another range unit or an invalid range).
static int parse_byte_range(const char* value, long total, long& from, long& to) {
if (strncasecmp(value, "bytes=", 6) != 0) return 1;
const char* p = value + 6;
while (*p == ' ' || *p == '\t') ++p;
char* end = NULL;
if (*p == '-') {
if (!isdigit((unsigned char)p[1])) return 1;
long length = strtol(p + 1, &end, 10);
while (*end == ' ' || *end == '\t') ++end;
if (*end != '\0' && *end != ',') return 1;
if (length == 0 || total == 0) return -1;
from = length < total ? total - length : 0;
to = total - 1;
return 0;
}
if (!isdigit((unsigned char)*p)) return 1;
from = strtol(p, &end, 10);
if (*end != '-') return 1;
p = end + 1;
while (*p == ' ' || *p == '\t') ++p;
if (*p == '\0' || *p == ',') {
to = total - 1;
} else {
if (!isdigit((unsigned char)*p)) return 1;
to = strtol(p, &end, 10);
while (*end == ' ' || *end == '\t') ++end;
if ((*end != '\0' && *end != ',') || to < from) return 1;
if (to >= total) to = total - 1;
}
return from < total ? 0 : -1;
}

int HttpHandler::defaultStaticHandler() {
// file service
std::string path = req->Path();
Expand All @@ -629,28 +665,38 @@ int HttpHandler::defaultStaticHandler() {
int status_code = HTTP_STATUS_OK;
// Range:
bool has_range = false;
long from, to = 0;
if (req->GetRange(from, to)) {
has_range = true;
long from = 0, to = 0;
auto range = req->headers.find("Range");
if (range != req->headers.end()) {
if (openFile(filepath.c_str()) != 0) {
return HTTP_STATUS_NOT_FOUND;
}
long total = file->size();
if (to == 0 || to >= total) to = total - 1;
if (from < 0 || from >= total || to < from) {
int ret = parse_byte_range(range->second.c_str(), total, from, to);
if (ret < 0) {
closeFile();
resp->SetHeader("Content-Range", hv::asprintf("bytes */%ld", total));
return HTTP_STATUS_RANGE_NOT_SATISFIABLE;
}
if (ret > 0) {
// Ignore the Range header and send the whole file
closeFile();
} else {
has_range = true;
}
}
if (has_range) {
long total = file->size();
file->seek(from);
status_code = HTTP_STATUS_PARTIAL_CONTENT;
resp->status_code = HTTP_STATUS_PARTIAL_CONTENT;
resp->content_length = to - from + 1;
resp->SetContentTypeByFilename(filepath.c_str());
resp->SetRange(from, to, total);
if(resp->content_length < service->max_file_cache_size) {
// read into body directly
int nread = file->readrange(resp->body, from, to);
// read into body directly (readrange would read to the end of the file for to = 0)
resp->body.resize(resp->content_length);
int nread = file->read((void*)resp->body.data(), resp->content_length);
closeFile();
if (nread != resp->content_length) {
resp->content_length = 0;
Expand Down
3 changes: 3 additions & 0 deletions scripts/unittest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ bin/http_router_test
if [ -x bin/websocket_parser_test ]; then
bin/websocket_parser_test || exit $?
fi
if [ -x bin/http_static_range_test ]; then
bin/http_static_range_test || exit $?
fi
if [ -x bin/tlv_test ]; then
bin/tlv_test
fi
Expand Down
7 changes: 7 additions & 0 deletions unittest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ target_link_libraries(websocket_parser_test ${HV_LIBRARIES})
set(HTTP_UNITTEST_TARGETS websocket_parser_test)
endif()

if(WITH_EVPP AND WITH_HTTP AND WITH_HTTP_SERVER AND WITH_HTTP_CLIENT)
add_executable(http_static_range_test http_static_range_test.cpp)
target_include_directories(http_static_range_test PRIVATE .. ../base ../ssl ../event ../cpputil ../evpp ../http ../http/server ../http/client)
target_link_libraries(http_static_range_test ${HV_LIBRARIES})
set(HTTP_UNITTEST_TARGETS ${HTTP_UNITTEST_TARGETS} http_static_range_test)
endif()

if(WITH_LUA)
add_executable(lua_binding_test lua_binding_test.cpp)
target_include_directories(lua_binding_test PRIVATE .. ../base ../ssl ../event ../cpputil ../evpp ../lua)
Expand Down
54 changes: 54 additions & 0 deletions unittest/http_static_range_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* http_static_range_test: Range requests served by the default static file
* handler (RFC 9110 section 14).
*/

#include <assert.h>
#include <stdio.h>

#include "hbase.h"
#include "hfile.h"
#include "htime.h"
#include "HttpServer.h"
#include "HttpService.h"
#include "requests.h"

static void check(const char* range, int status, const char* content_range, const char* body) {
auto req = std::make_shared<HttpRequest>();
req->url = "http://127.0.0.1:18082/a.txt";
req->headers["Range"] = range;
auto resp = requests::request(req);
assert(resp != NULL);
printf("Range: %s => %d %s\n", range, (int)resp->status_code, resp->GetHeader("Content-Range").c_str());
assert(resp->status_code == status);
assert(resp->GetHeader("Content-Range") == content_range);
if (body) assert(resp->body == body);
}

int main() {
hv_mkdir_p("tmp/http_static_range_test");
HFile file;
int ret = file.open("tmp/http_static_range_test/a.txt", "wb");
assert(ret == 0);
file.write("0123456789", 10);
file.close();

HttpService service;
service.document_root = "tmp/http_static_range_test";

hv::HttpServer server(&service);
server.setPort(18082);
server.start();
hv_msleep(200);

check("bytes=0-0", 206, "bytes 0-0/10", "0");
check("bytes=5-", 206, "bytes 5-9/10", "56789");
check("bytes=-3", 206, "bytes 7-9/10", "789");
check("bytes=-20", 206, "bytes 0-9/10", "0123456789");
check("bytes=10-", 416, "bytes */10", NULL);
check("items=0-1", 200, "", "0123456789");

server.stop();
printf("http_static_range_test passed\n");
return 0;
}