aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--zenhttp/httpasio.cpp17
-rw-r--r--zenhttp/httpserver.cpp64
-rw-r--r--zenhttp/httpsys.cpp10
-rw-r--r--zenhttp/include/zenhttp/httpcommon.h9
-rw-r--r--zenhttp/include/zenhttp/httpserver.h2
-rw-r--r--zenserver/objectstore/objectstore.cpp53
6 files changed, 147 insertions, 8 deletions
diff --git a/zenhttp/httpasio.cpp b/zenhttp/httpasio.cpp
index 450b5a1fc..f270c9d2b 100644
--- a/zenhttp/httpasio.cpp
+++ b/zenhttp/httpasio.cpp
@@ -7,6 +7,7 @@
#include <deque>
#include <memory>
+#include <string_view>
#include <vector>
ZEN_THIRD_PARTY_INCLUDES_START
@@ -41,6 +42,7 @@ static constinit uint32_t HashAccept = HashStringAsLowerDjb2("Accept"sv);
static constinit uint32_t HashExpect = HashStringAsLowerDjb2("Expect"sv);
static constinit uint32_t HashSession = HashStringAsLowerDjb2("UE-Session"sv);
static constinit uint32_t HashRequest = HashStringAsLowerDjb2("UE-Request"sv);
+static constinit uint32_t HashRange = HashStringAsLowerDjb2("Range"sv);
inline spdlog::logger&
InitLogger()
@@ -103,6 +105,7 @@ public:
virtual void WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::span<IoBuffer> Blobs) override;
virtual void WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::u8string_view ResponseString) override;
virtual void WriteResponseAsync(std::function<void(HttpServerRequest&)>&& ContinuationHandler) override;
+ virtual bool TryGetRanges(HttpRanges& Ranges) override;
using HttpServerRequest::WriteResponse;
@@ -151,6 +154,8 @@ struct HttpRequest
Oid SessionId() const { return m_SessionId; }
int RequestId() const { return m_RequestId; }
+ std::string_view RangeHeader() const { return m_RangeHeaderIndex != -1 ? m_Headers[m_RangeHeaderIndex].Value : std::string_view(); }
+
private:
struct HeaderEntry
{
@@ -176,6 +181,7 @@ private:
int8_t m_ContentLengthHeaderIndex;
int8_t m_AcceptHeaderIndex;
int8_t m_ContentTypeHeaderIndex;
+ int8_t m_RangeHeaderIndex;
HttpVerb m_RequestVerb;
bool m_KeepAlive = false;
bool m_Expect100Continue = false;
@@ -740,6 +746,10 @@ HttpRequest::AppendCurrentHeader()
ZEN_INFO("Unexpected expect - Expect: {}", HeaderValue);
}
}
+ else if (HeaderHash == HashRange)
+ {
+ m_RangeHeaderIndex = (int8_t)m_Headers.size();
+ }
m_Headers.emplace_back(HeaderName, HeaderValue);
}
@@ -912,6 +922,7 @@ HttpRequest::ResetState()
m_ContentLengthHeaderIndex = -1;
m_AcceptHeaderIndex = -1;
m_ContentTypeHeaderIndex = -1;
+ m_RangeHeaderIndex = -1;
m_Expect100Continue = false;
m_BodyBuffer = {};
m_BodyPosition = 0;
@@ -1178,6 +1189,12 @@ HttpAsioServerRequest::WriteResponseAsync(std::function<void(HttpServerRequest&)
ContinuationHandler(*this);
}
+bool
+HttpAsioServerRequest::TryGetRanges(HttpRanges& Ranges)
+{
+ return TryParseHttpRangeHeader(m_Request.RangeHeader(), Ranges);
+}
+
//////////////////////////////////////////////////////////////////////////
HttpAsioServerImpl::HttpAsioServerImpl()
diff --git a/zenhttp/httpserver.cpp b/zenhttp/httpserver.cpp
index 840b90931..952857f2c 100644
--- a/zenhttp/httpserver.cpp
+++ b/zenhttp/httpserver.cpp
@@ -185,6 +185,70 @@ ParseContentTypeInit(const std::string_view& ContentTypeString)
HttpContentType (*ParseContentType)(const std::string_view& ContentTypeString) = &ParseContentTypeInit;
+bool
+TryParseHttpRangeHeader(std::string_view RangeHeader, HttpRanges& Ranges)
+{
+ if (RangeHeader.empty())
+ {
+ return false;
+ }
+
+ const size_t Count = Ranges.size();
+
+ std::size_t UnitDelim = RangeHeader.find_first_of('=');
+ if (UnitDelim == std::string_view::npos)
+ {
+ return false;
+ }
+
+ // only bytes for now
+ std::string_view Unit = RangeHeader.substr(0, UnitDelim);
+ if (Unit != "bytes"sv)
+ {
+ return false;
+ }
+
+ std::string_view Tokens = RangeHeader.substr(UnitDelim);
+ while (!Tokens.empty())
+ {
+ // Skip =,
+ Tokens = Tokens.substr(1);
+
+ size_t Delim = Tokens.find_first_of(',');
+ if (Delim == std::string_view::npos)
+ {
+ Delim = Tokens.length();
+ }
+
+ std::string_view Token = Tokens.substr(0, Delim);
+ Tokens = Tokens.substr(Delim);
+
+ Delim = Token.find_first_of('-');
+ if (Delim == std::string_view::npos)
+ {
+ return false;
+ }
+
+ const auto Start = ParseInt<uint32_t>(Token.substr(0, Delim));
+ const auto End = ParseInt<uint32_t>(Token.substr(Delim + 1));
+
+ if (Start.has_value() && End.has_value() && End.value() > Start.value())
+ {
+ Ranges.push_back({.Start = Start.value(), .End = End.value()});
+ }
+ else if (Start)
+ {
+ Ranges.push_back({.Start = Start.value()});
+ }
+ else if (End)
+ {
+ Ranges.push_back({.End = End.value()});
+ }
+ }
+
+ return Count != Ranges.size();
+}
+
//////////////////////////////////////////////////////////////////////////
const std::string_view
diff --git a/zenhttp/httpsys.cpp b/zenhttp/httpsys.cpp
index f6f8024ca..4b566905e 100644
--- a/zenhttp/httpsys.cpp
+++ b/zenhttp/httpsys.cpp
@@ -188,6 +188,7 @@ public:
virtual void WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::span<IoBuffer> Blobs) override;
virtual void WriteResponse(HttpResponseCode ResponseCode, HttpContentType ContentType, std::u8string_view ResponseString) override;
virtual void WriteResponseAsync(std::function<void(HttpServerRequest&)>&& ContinuationHandler) override;
+ virtual bool TryGetRanges(HttpRanges& Ranges) override;
using HttpServerRequest::WriteResponse;
@@ -1408,6 +1409,15 @@ HttpSysServerRequest::WriteResponseAsync(std::function<void(HttpServerRequest&)>
}
}
+bool
+HttpSysServerRequest::TryGetRanges(HttpRanges& Ranges)
+{
+ HTTP_REQUEST* Req = m_HttpTx.HttpRequest();
+ const HTTP_KNOWN_HEADER& RangeHeader = Req->Headers.KnownHeaders[HttpHeaderRange];
+
+ return TryParseHttpRangeHeader({RangeHeader.pRawValue, RangeHeader.RawValueLength}, Ranges);
+}
+
//////////////////////////////////////////////////////////////////////////
InitialRequestHandler::InitialRequestHandler(HttpSysTransaction& InRequest) : HttpSysRequestHandler(InRequest)
diff --git a/zenhttp/include/zenhttp/httpcommon.h b/zenhttp/include/zenhttp/httpcommon.h
index 3e213ece4..19fda8db4 100644
--- a/zenhttp/include/zenhttp/httpcommon.h
+++ b/zenhttp/include/zenhttp/httpcommon.h
@@ -17,9 +17,18 @@ class CbObject;
class CbPackage;
class StringBuilderBase;
+struct HttpRange
+{
+ uint32_t Start = ~uint32_t(0);
+ uint32_t End = ~uint32_t(0);
+};
+
+using HttpRanges = std::vector<HttpRange>;
+
std::string_view MapContentTypeToString(HttpContentType ContentType);
extern HttpContentType (*ParseContentType)(const std::string_view& ContentTypeString);
std::string_view ReasonStringForHttpResultCode(int HttpCode);
+bool TryParseHttpRangeHeader(std::string_view RangeHeader, HttpRanges& Ranges);
[[nodiscard]] inline bool
IsHttpSuccessCode(int HttpCode)
diff --git a/zenhttp/include/zenhttp/httpserver.h b/zenhttp/include/zenhttp/httpserver.h
index 5bd51740a..451a47b4a 100644
--- a/zenhttp/include/zenhttp/httpserver.h
+++ b/zenhttp/include/zenhttp/httpserver.h
@@ -59,6 +59,8 @@ public:
}
};
+ virtual bool TryGetRanges(HttpRanges&) { return false; }
+
QueryParams GetQueryParams();
inline HttpVerb RequestVerb() const { return m_Verb; }
diff --git a/zenserver/objectstore/objectstore.cpp b/zenserver/objectstore/objectstore.cpp
index 950505bcb..e5739418e 100644
--- a/zenserver/objectstore/objectstore.cpp
+++ b/zenserver/objectstore/objectstore.cpp
@@ -8,6 +8,7 @@
#include <zencore/string.h>
#include "zencore/compactbinarybuilder.h"
#include "zenhttp/httpcommon.h"
+#include "zenhttp/httpserver.h"
#include <thread>
@@ -130,6 +131,13 @@ HttpObjectStoreService::GetBlob(zen::HttpRouterRequest& Request)
return Request.ServerRequest().WriteResponse(HttpResponseCode::NotFound);
}
+ zen::HttpRanges Ranges;
+ if (Request.ServerRequest().TryGetRanges(Ranges); Ranges.size() > 1)
+ {
+ // Only a single range is supported
+ return Request.ServerRequest().WriteResponse(HttpResponseCode::BadRequest);
+ }
+
FileContents File = ReadFile(FilePath);
if (File.ErrorCode)
{
@@ -144,16 +152,45 @@ HttpObjectStoreService::GetBlob(zen::HttpRouterRequest& Request)
}
const IoBuffer& FileBuf = File.Data[0];
- const uint64_t Total = TotalBytesServed.fetch_add(FileBuf.Size()) + FileBuf.Size();
- ZEN_LOG_DEBUG(LogObj,
- "GET - '{}/{}' ({}) [OK] (Total: {})",
- BucketName,
- RelativeBucketPath,
- NiceBytes(FileBuf.Size()),
- NiceBytes(Total));
+ if (Ranges.empty())
+ {
+ const uint64_t TotalServed = TotalBytesServed.fetch_add(FileBuf.Size()) + FileBuf.Size();
- Request.ServerRequest().WriteResponse(HttpResponseCode::OK, HttpContentType::kUnknownContentType, FileBuf);
+ ZEN_LOG_DEBUG(LogObj,
+ "GET - '{}/{}' ({}) [OK] (Served: {})",
+ BucketName,
+ RelativeBucketPath,
+ NiceBytes(FileBuf.Size()),
+ NiceBytes(TotalServed));
+
+ Request.ServerRequest().WriteResponse(HttpResponseCode::OK, HttpContentType::kBinary, FileBuf);
+ }
+ else
+ {
+ const auto Range = Ranges[0];
+ const uint64_t RangeSize = Range.End - Range.Start;
+ const uint64_t TotalServed = TotalBytesServed.fetch_add(RangeSize) + RangeSize;
+
+ ZEN_LOG_DEBUG(LogObj,
+ "GET - '{}/{}' (Range: {}-{}) ({}/{}) [OK] (Served: {})",
+ BucketName,
+ RelativeBucketPath,
+ Range.Start,
+ Range.End,
+ NiceBytes(RangeSize),
+ NiceBytes(FileBuf.Size()),
+ NiceBytes(TotalServed));
+
+ MemoryView RangeView = FileBuf.GetView().Mid(Range.Start, RangeSize);
+ if (RangeView.GetSize() != RangeSize)
+ {
+ return Request.ServerRequest().WriteResponse(HttpResponseCode::BadRequest);
+ }
+
+ IoBuffer RangeBuf = IoBuffer(IoBuffer::Wrap, RangeView.GetData(), RangeView.GetSize());
+ Request.ServerRequest().WriteResponse(HttpResponseCode::PartialContent, HttpContentType::kBinary, RangeBuf);
+ }
}
void