305 lines
8.7 KiB
C++
305 lines
8.7 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
namespace UE::IoStore::HTTP
|
|
{
|
|
|
|
// {{{1 request ................................................................
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FRequest::~FRequest()
|
|
{
|
|
if (Ptr == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Activity_Free(Ptr);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FRequest::FRequest(FRequest&& Rhs)
|
|
{
|
|
this->~FRequest();
|
|
Swap(Ptr, Rhs.Ptr);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FRequest&& FRequest::Accept(EMimeType MimeType)
|
|
{
|
|
switch (MimeType)
|
|
{
|
|
case EMimeType::Text: return Header("Accept", "text/html");
|
|
case EMimeType::Binary: return Header("Accept", "application/octet-stream");
|
|
case EMimeType::Json: return Header("Accept", "application/json");
|
|
case EMimeType::Xml: return Header("Accept", "application/xml");
|
|
case EMimeType::CbObject: return Header("Accept", "application/x-ue-cb");
|
|
case EMimeType::CbPackage: return Header("Accept", "application/x-ue-pkg");
|
|
case EMimeType::CompressedBuffer: return Header("Accept", "application/x-ue-comp");
|
|
}
|
|
|
|
return MoveTemp(*this);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FRequest&& FRequest::Accept(FAnsiStringView MimeType)
|
|
{
|
|
return Header("Accept", MimeType);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FRequest&& FRequest::Header(FAnsiStringView Key, FAnsiStringView Value)
|
|
{
|
|
checkSlow(Ptr->State == FActivity::EState::Build);
|
|
Ptr->Buffer.AddHeader(Key, Value);
|
|
return MoveTemp(*this);
|
|
}
|
|
|
|
|
|
|
|
// {{{1 response ...............................................................
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
EStatusCodeClass FResponse::GetStatus() const
|
|
{
|
|
uint32 Code = GetStatusCode();
|
|
if (Code <= 199) return EStatusCodeClass::Informational;
|
|
if (Code <= 299) return EStatusCodeClass::Successful;
|
|
if (Code <= 399) return EStatusCodeClass::Redirection;
|
|
if (Code <= 499) return EStatusCodeClass::ClientError;
|
|
if (Code <= 599) return EStatusCodeClass::ServerError;
|
|
return EStatusCodeClass::Unknown;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
uint32 FResponse::GetStatusCode() const
|
|
{
|
|
const auto* Activity = (const FActivity*)this;
|
|
const FResponseInternal& Internal = Activity->Response;
|
|
|
|
const char* MessageData = Activity->Buffer.GetData() + Activity->StateParam;
|
|
if (Internal.Code < 0)
|
|
{
|
|
const char* CodePtr = MessageData + Internal.Offsets.StatusCode;
|
|
Internal.Code = uint16(CrudeToInt(FAnsiStringView(CodePtr, 3)));
|
|
}
|
|
|
|
return Internal.Code;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FAnsiStringView FResponse::GetStatusMessage() const
|
|
{
|
|
const auto* Activity = (const FActivity*)this;
|
|
const FResponseInternal& Internal = Activity->Response;
|
|
|
|
const char* MessageData = Activity->Buffer.GetData() + Activity->StateParam;
|
|
return FAnsiStringView(
|
|
MessageData + Internal.Offsets.Message,
|
|
Internal.Offsets.Headers - Internal.Offsets.Message
|
|
);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
int64 FResponse::GetContentLength() const
|
|
{
|
|
const auto* Activity = (const FActivity*)this;
|
|
const FResponseInternal& Internal = Activity->Response;
|
|
return Internal.ContentLength;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
EMimeType FResponse::GetContentType() const
|
|
{
|
|
FAnsiStringView Value;
|
|
GetContentType(Value);
|
|
|
|
if (Value == "text/html") return EMimeType::Text;
|
|
if (Value == "application/octet-stream") return EMimeType::Binary;
|
|
if (Value == "application/json") return EMimeType::Json;
|
|
if (Value == "application/xml") return EMimeType::Xml;
|
|
if (Value == "application/x-ue-cb") return EMimeType::CbObject;
|
|
if (Value == "application/x-ue-pkg") return EMimeType::CbPackage;
|
|
if (Value == "application/x-ue-comp") return EMimeType::CompressedBuffer;
|
|
|
|
return EMimeType::Unknown;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void FResponse::GetContentType(FAnsiStringView& Out) const
|
|
{
|
|
Out = GetHeader("Content-Type");
|
|
|
|
int32 SemiColon;
|
|
if (Out.FindChar(';', SemiColon))
|
|
{
|
|
Out = Out.Mid(SemiColon).TrimEnd();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FAnsiStringView FResponse::GetHeader(FAnsiStringView Name) const
|
|
{
|
|
FAnsiStringView Result;
|
|
ReadHeaders([&Result, Name] (FAnsiStringView Candidate, FAnsiStringView Value)
|
|
{
|
|
if (Candidate != Name)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
Result = Value;
|
|
return false;
|
|
});
|
|
return Result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void FResponse::ReadHeaders(FHeaderSink Sink) const
|
|
{
|
|
const auto* Activity = (const FActivity*)this;
|
|
const FResponseInternal& Internal = Activity->Response;
|
|
|
|
const char* MessageData = Activity->Buffer.GetData() + Activity->StateParam;
|
|
FAnsiStringView Result, Headers(
|
|
MessageData + Internal.Offsets.Headers,
|
|
Internal.MessageLength - Internal.Offsets.Headers
|
|
);
|
|
|
|
EnumerateHeaders(Headers, Sink);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
void FResponse::SetDestination(FIoBuffer* Buffer)
|
|
{
|
|
auto* Activity = (FActivity*)this;
|
|
Activity->Dest = Buffer;
|
|
}
|
|
|
|
|
|
|
|
// {{{1 ticket-status ..........................................................
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FTicketStatus::EId FTicketStatus::GetId() const
|
|
{
|
|
const auto* Activity = (FActivity*)this;
|
|
switch (Activity->State)
|
|
{
|
|
case FActivity::EState::RecvMessage: return EId::Response;
|
|
case FActivity::EState::RecvStream:
|
|
case FActivity::EState::RecvDone: return EId::Content;
|
|
case FActivity::EState::Cancelled: return EId::Cancelled;
|
|
case FActivity::EState::Failed: return EId::Error;
|
|
default: check(false);
|
|
}
|
|
return EId::Error;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
UPTRINT FTicketStatus::GetParam() const
|
|
{
|
|
const auto* Activity = (FActivity*)this;
|
|
return Activity->SinkParam;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FTicket FTicketStatus::GetTicket() const
|
|
{
|
|
const auto* Activity = (FActivity*)this;
|
|
return 1ull << Activity->Slot;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
uint32 FTicketStatus::GetIndex() const
|
|
{
|
|
const auto* Activity = (FActivity*)this;
|
|
return Activity->Slot;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FResponse& FTicketStatus::GetResponse() const
|
|
{
|
|
check(GetId() < EId::Content);
|
|
const auto* Activity = (FActivity*)this;
|
|
return *(FResponse*)Activity;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
uint32 FTicketStatus::GetContentLength() const
|
|
{
|
|
check(GetId() <= EId::Content);
|
|
const auto* Activity = (FActivity*)this;
|
|
return Activity->Response.ContentLength;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
const FTicketPerf& FTicketStatus::GetPerf() const
|
|
{
|
|
check(GetId() == EId::Content);
|
|
const auto* Activity = (FActivity*)this;
|
|
return *(FTicketPerf*)Activity;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
const FIoBuffer& FTicketStatus::GetContent() const
|
|
{
|
|
check(GetId() == EId::Content);
|
|
const auto* Activity = (FActivity*)this;
|
|
return *(Activity->Dest);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FTicketStatus::FError FTicketStatus::GetError() const
|
|
{
|
|
check(GetId() == EId::Error);
|
|
const auto* Activity = (FActivity*)this;
|
|
return FError({Activity->ErrorReason, Activity->StateParam});
|
|
}
|
|
|
|
|
|
|
|
// {{{1 perf ...................................................................
|
|
|
|
#if IAS_HTTP_WITH_PERF
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
FTicketPerf::FSample FTicketPerf::GetSample() const
|
|
{
|
|
const auto& Activity = *(FActivity*)this;
|
|
|
|
static uint64 Freq;
|
|
if (Freq == 0)
|
|
{
|
|
Freq = uint64(1.0 / FPlatformTime::GetSecondsPerCycle64());
|
|
}
|
|
|
|
auto Clamp = [] (auto Value) { return uint16(FMath::Min(uint32(Value), 0xffffu)); };
|
|
auto ToMs = [&] (uint64 Value) { return Clamp((Value * 1000ull) / Freq); };
|
|
|
|
const FStopwatch& Stopwatch = Activity.Stopwatch;
|
|
FSample Sample = {
|
|
ToMs(Stopwatch.GetInterval(0)),
|
|
ToMs(Stopwatch.GetInterval(1)),
|
|
ToMs(Stopwatch.GetInterval(2)),
|
|
};
|
|
|
|
uint32 Bps = ~0u;
|
|
if (Sample.RecvMs)
|
|
{
|
|
Bps = uint32((Activity.Response.ContentLength * 1000ull) / Sample.RecvMs);
|
|
}
|
|
Sample.RecvKiBps = Clamp(Bps >> 10);
|
|
|
|
return Sample;
|
|
}
|
|
|
|
#endif // IAS_HTTP_WITH_PERF
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
} // namespace UE::IoStore::HTTP
|