Files
GacUI/Import/VlppOS.cpp
T
2026-07-26 04:16:55 -07:00

16301 lines
432 KiB
C++

/***********************************************************************
THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY
DEVELOPER: Zihan Chen(vczh)
***********************************************************************/
#include "VlppOS.h"
/***********************************************************************
.\FILESYSTEM.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
namespace vl
{
namespace filesystem
{
using namespace collections;
using namespace stream;
extern IFileSystemImpl* GetFileSystemImpl();
static wchar_t GetInjectedDelimiter()
{
return GetFileSystemImpl()->GetPathDelimiter();
}
static WString GetInjectedDelimiterString()
{
return WString::FromChar(GetInjectedDelimiter());
}
// ReadDirectoryChangesW
/***********************************************************************
FilePath
***********************************************************************/
void FilePath::NormalizeDelimiters(collections::Array<wchar_t>& buffer)
{
auto delimiter = GetInjectedDelimiter();
auto compatibleDelimiters = GetFileSystemImpl()->GetCompatibleDelimiters();
for (vint i = 0; i < buffer.Count(); i++)
{
for (auto reading = compatibleDelimiters; *reading; reading++)
{
if (*reading == buffer[i])
{
buffer[i] = delimiter;
break;
}
}
}
}
void FilePath::TrimLastDelimiter(WString& fullPath)
{
auto delimiter = GetInjectedDelimiter();
if (!GetFileSystemImpl()->IsRoot(fullPath) && fullPath.Length() > 0 && fullPath[fullPath.Length() - 1] == delimiter)
{
fullPath = fullPath.Left(fullPath.Length() - 1);
}
}
FilePath::FilePath()
{
Initialize();
}
FilePath::FilePath(const WString& _filePath)
:fullPath(_filePath)
{
Initialize();
}
FilePath::FilePath(const wchar_t* _filePath)
:fullPath(_filePath)
{
Initialize();
}
FilePath::FilePath(const FilePath& _filePath)
:fullPath(_filePath.fullPath)
{
}
WString FilePath::GetName()const
{
auto delimiter = GetInjectedDelimiterString();
auto index = INVLOC.FindLast(fullPath, delimiter, Locale::None);
if (index.key == -1) return fullPath;
return fullPath.Right(fullPath.Length() - index.key - 1);
}
FilePath FilePath::GetFolder()const
{
auto delimiter = GetInjectedDelimiterString();
auto index = INVLOC.FindLast(fullPath, delimiter, Locale::None);
if (index.key == -1) return FilePath();
return fullPath.Left(index.key);
}
WString FilePath::GetFullPath()const
{
return fullPath;
}
void FilePath::GetPathComponents(WString path, collections::List<WString>& components)
{
WString pathRemaining = path;
auto delimiter = GetInjectedDelimiter();
auto delimiterString = WString::FromChar(delimiter);
auto doubleDelimiter = delimiterString + delimiterString;
components.Clear();
while (true)
{
auto index = INVLOC.FindFirst(pathRemaining, delimiterString, Locale::None);
if (index.key == -1)
break;
if (index.key != 0)
components.Add(pathRemaining.Left(index.key));
else
{
if (pathRemaining.Length() >= 2 && pathRemaining[1] == delimiter)
{
components.Add(doubleDelimiter);
index.value++;
}
else if (GetFileSystemImpl()->IsRoot(delimiterString))
{
components.Add(delimiterString);
}
}
pathRemaining = pathRemaining.Right(pathRemaining.Length() - (index.key + index.value));
}
if (pathRemaining.Length() != 0)
{
components.Add(pathRemaining);
}
}
WString FilePath::ComponentsToPath(const collections::List<WString>& components)
{
WString result;
auto delimiter = GetInjectedDelimiterString();
auto doubleDelimiter = delimiter + delimiter;
vint i = 0;
if (components.Count() > 0)
{
if (components[0] == doubleDelimiter)
{
result += doubleDelimiter;
i++;
}
else if (components[0] == delimiter)
{
result += delimiter;
i++;
}
}
for (; i < components.Count(); i++)
{
if (result.Length() > 0 && result[result.Length() - 1] != GetInjectedDelimiter())
{
result += delimiter;
}
result += components[i];
}
return result;
}
/***********************************************************************
File
***********************************************************************/
File::File(const FilePath& _filePath)
:filePath(_filePath)
{
}
const FilePath& File::GetFilePath()const
{
return filePath;
}
bool File::ReadAllTextWithEncodingTesting(WString& text, stream::BomEncoder::Encoding& encoding, bool& containsBom)
{
Array<unsigned char> buffer;
{
FileStream fileStream(filePath.GetFullPath(), FileStream::ReadOnly);
if (!fileStream.IsAvailable()) return false;
if (fileStream.Size() == 0)
{
text = L"";
encoding = BomEncoder::Mbcs;
containsBom = false;
return true;
}
buffer.Resize((vint)fileStream.Size());
vint count = fileStream.Read(&buffer[0], buffer.Count());
CHECK_ERROR(count == buffer.Count(), L"vl::filesystem::File::ReadAllTextWithEncodingTesting(WString&, BomEncoder::Encoding&, bool&)#Failed to read the whole file.");
}
TestEncoding(&buffer[0], buffer.Count(), encoding, containsBom);
MemoryWrapperStream memoryStream(&buffer[0], buffer.Count());
if (containsBom)
{
BomDecoder decoder;
DecoderStream decoderStream(memoryStream, decoder);
StreamReader reader(decoderStream);
text = reader.ReadToEnd();
}
else
{
switch (encoding)
{
case BomEncoder::Utf8:
{
Utf8Decoder decoder;
DecoderStream decoderStream(memoryStream, decoder);
StreamReader reader(decoderStream);
text = reader.ReadToEnd();
}
break;
case BomEncoder::Utf16:
{
Utf16Decoder decoder;
DecoderStream decoderStream(memoryStream, decoder);
StreamReader reader(decoderStream);
text = reader.ReadToEnd();
}
break;
case BomEncoder::Utf16BE:
{
Utf16BEDecoder decoder;
DecoderStream decoderStream(memoryStream, decoder);
StreamReader reader(decoderStream);
text = reader.ReadToEnd();
}
break;
default:
{
MbcsDecoder decoder;
DecoderStream decoderStream(memoryStream, decoder);
StreamReader reader(decoderStream);
text = reader.ReadToEnd();
}
}
}
return true;
}
WString File::ReadAllTextByBom()const
{
WString text;
ReadAllTextByBom(text);
return text;
}
bool File::ReadAllTextByBom(WString& text)const
{
FileStream fileStream(filePath.GetFullPath(), FileStream::ReadOnly);
if (!fileStream.IsAvailable()) return false;
BomDecoder decoder;
DecoderStream decoderStream(fileStream, decoder);
StreamReader reader(decoderStream);
text = reader.ReadToEnd();
return true;
}
bool File::ReadAllLinesByBom(collections::List<WString>& lines)const
{
FileStream fileStream(filePath.GetFullPath(), FileStream::ReadOnly);
if (!fileStream.IsAvailable()) return false;
BomDecoder decoder;
DecoderStream decoderStream(fileStream, decoder);
StreamReader reader(decoderStream);
while (!reader.IsEnd())
{
lines.Add(reader.ReadLine());
}
return true;
}
bool File::WriteAllText(const WString& text, bool bom, stream::BomEncoder::Encoding encoding)
{
FileStream fileStream(filePath.GetFullPath(), FileStream::WriteOnly);
if (!fileStream.IsAvailable()) return false;
IEncoder* encoder = nullptr;
if (bom)
{
encoder = new BomEncoder(encoding);
}
else switch (encoding)
{
case BomEncoder::Utf8:
encoder = new Utf8Encoder;
break;
case BomEncoder::Utf16:
encoder = new Utf16Encoder;
break;
case BomEncoder::Utf16BE:
encoder = new Utf16BEEncoder;
break;
default:
encoder = new MbcsEncoder;
break;
}
{
EncoderStream encoderStream(fileStream, *encoder);
StreamWriter writer(encoderStream);
writer.WriteString(text);
}
delete encoder;
return true;
}
bool File::WriteAllLines(collections::List<WString>& lines, bool bom, stream::BomEncoder::Encoding encoding)
{
FileStream fileStream(filePath.GetFullPath(), FileStream::WriteOnly);
if (!fileStream.IsAvailable()) return false;
IEncoder* encoder = nullptr;
if (bom)
{
encoder = new BomEncoder(encoding);
}
else switch (encoding)
{
case BomEncoder::Utf8:
encoder = new Utf8Encoder;
break;
case BomEncoder::Utf16:
encoder = new Utf16Encoder;
break;
case BomEncoder::Utf16BE:
encoder = new Utf16BEEncoder;
break;
default:
encoder = new MbcsEncoder;
break;
}
{
EncoderStream encoderStream(fileStream, *encoder);
StreamWriter writer(encoderStream);
for (auto line : lines)
{
writer.WriteLine(line);
}
}
delete encoder;
return true;
}
bool File::Exists()const
{
return filePath.IsFile();
}
/***********************************************************************
Folder
***********************************************************************/
Folder::Folder(const FilePath& _filePath)
:filePath(_filePath)
{
}
const FilePath& Folder::GetFilePath()const
{
return filePath;
}
bool Folder::Exists()const
{
return filePath.IsFolder();
}
bool Folder::Create(bool recursively)const
{
if (recursively)
{
auto folder = filePath.GetFolder();
if (folder.IsFile()) return false;
if (folder.IsFolder()) return Create(false);
return Folder(folder).Create(true) && Create(false);
}
else
{
return CreateNonRecursively();
}
}
bool Folder::Delete(bool recursively)const
{
if (!Exists()) return false;
if (recursively)
{
List<Folder> folders;
GetFolders(folders);
for (auto folder : folders)
{
if (!folder.Delete(true)) return false;
}
List<File> files;
GetFiles(files);
for (auto file : files)
{
if (!file.Delete()) return false;
}
return Delete(false);
}
return DeleteNonRecursively();
}
}
}
/***********************************************************************
.\FILESYSTEM.INJECTABLE.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
namespace vl
{
namespace filesystem
{
extern IFileSystemImpl* GetOSFileSystemImpl();
feature_injection::FeatureInjection<IFileSystemImpl>& GetFileSystemInjection()
{
static feature_injection::FeatureInjection<IFileSystemImpl> injection(GetOSFileSystemImpl());
return injection;
}
void InjectFileSystemImpl(IFileSystemImpl* impl)
{
GetFileSystemInjection().Inject(impl);
}
IFileSystemImpl* GetFileSystemImpl()
{
return GetFileSystemInjection().Get();
}
void EjectFileSystemImpl(IFileSystemImpl* impl)
{
if (impl == nullptr)
{
GetFileSystemInjection().EjectAll();
}
else
{
GetFileSystemInjection().Eject(impl);
}
}
/***********************************************************************
FilePath
***********************************************************************/
wchar_t FilePath::GetPathDelimiter()
{
return GetFileSystemImpl()->GetPathDelimiter();
}
void FilePath::Initialize()
{
GetFileSystemImpl()->Initialize(fullPath);
}
FilePath FilePath::operator/(const WString& relativePath) const
{
return GetFileSystemImpl()->ConcatPath(fullPath, relativePath);
}
bool FilePath::IsFile() const
{
return GetFileSystemImpl()->IsFile(fullPath);
}
bool FilePath::IsFolder() const
{
return GetFileSystemImpl()->IsFolder(fullPath);
}
bool FilePath::IsRoot() const
{
return GetFileSystemImpl()->IsRoot(fullPath);
}
WString FilePath::GetRelativePathFor(const FilePath& _filePath) const
{
return GetFileSystemImpl()->GetRelativePathFor(fullPath, _filePath.GetFullPath());
}
/***********************************************************************
File
***********************************************************************/
bool File::Delete() const
{
return GetFileSystemImpl()->FileDelete(filePath);
}
bool File::Rename(const WString& newName) const
{
return GetFileSystemImpl()->FileRename(filePath, newName);
}
/***********************************************************************
Folder
***********************************************************************/
bool Folder::GetFolders(collections::List<Folder>& folders) const
{
return GetFileSystemImpl()->GetFolders(filePath, folders);
}
bool Folder::GetFiles(collections::List<File>& files) const
{
return GetFileSystemImpl()->GetFiles(filePath, files);
}
bool Folder::CreateNonRecursively() const
{
return GetFileSystemImpl()->CreateFolder(filePath);
}
bool Folder::DeleteNonRecursively() const
{
return GetFileSystemImpl()->DeleteFolder(filePath);
}
bool Folder::Rename(const WString& newName) const
{
return GetFileSystemImpl()->FolderRename(filePath, newName);
}
}
}
/***********************************************************************
.\LOCALE.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
namespace vl
{
using namespace collections;
/***********************************************************************
EnUsLocaleImpl
***********************************************************************/
#ifdef VCZH_GCC
#define _wcsicmp wcscasecmp
#define _wcsnicmp wcsncasecmp
#endif
Locale EnUsLocaleImpl::Invariant() const
{
return Locale(L"");
}
Locale EnUsLocaleImpl::SystemDefault() const
{
return Locale(L"en-US");
}
Locale EnUsLocaleImpl::UserDefault() const
{
return Locale(L"en-US");
}
void EnUsLocaleImpl::Enumerate(List<Locale>& locales) const
{
locales.Add(Locale(L"en-US"));
}
void EnUsLocaleImpl::GetShortDateFormats(const WString&, List<WString>& formats) const
{
formats.Add(L"MM/dd/yyyy");
formats.Add(L"yyyy-MM-dd");
}
void EnUsLocaleImpl::GetLongDateFormats(const WString&, List<WString>& formats) const
{
formats.Add(L"dddd, dd MMMM yyyy");
}
void EnUsLocaleImpl::GetYearMonthDateFormats(const WString&, List<WString>& formats) const
{
formats.Add(L"yyyy MMMM");
}
void EnUsLocaleImpl::GetLongTimeFormats(const WString&, List<WString>& formats) const
{
formats.Add(L"HH:mm:ss");
}
void EnUsLocaleImpl::GetShortTimeFormats(const WString&, List<WString>& formats) const
{
formats.Add(L"HH:mm");
formats.Add(L"hh:mm tt");
}
WString EnUsLocaleImpl::FormatDate(const WString& localeName, const WString& format, DateTime date) const
{
/*
auto df = L"yyyy,MM,MMM,MMMM,dd,ddd,dddd";
auto ds = L"2000,01,Jan,January,02,Sun,Sunday";
auto tf = L"hh,HH,mm,ss,tt";
auto ts = L"01,13,02,03,PM";
*/
WString result;
const wchar_t* reading = format.Buffer();
while (*reading)
{
if (wcsncmp(reading, L"yyyy", 4) == 0)
{
WString fragment = itow(date.year);
while (fragment.Length() < 4) fragment = L"0" + fragment;
result += fragment;
reading += 4;
}
else if (wcsncmp(reading, L"MMMM", 4) == 0)
{
result += GetLongMonthName(localeName, date.month);
reading += 4;
}
else if (wcsncmp(reading, L"MMM", 3) == 0)
{
result += GetShortMonthName(localeName, date.month);
reading += 3;
}
else if (wcsncmp(reading, L"MM", 2) == 0)
{
WString fragment = itow(date.month);
while (fragment.Length() < 2) fragment = L"0" + fragment;
result += fragment;
reading += 2;
}
else if (wcsncmp(reading, L"dddd", 4) == 0)
{
result += GetLongDayOfWeekName(localeName, date.dayOfWeek);
reading += 4;
}
else if (wcsncmp(reading, L"ddd", 3) == 0)
{
result += GetShortDayOfWeekName(localeName, date.dayOfWeek);
reading += 3;
}
else if (wcsncmp(reading, L"dd", 2) == 0)
{
WString fragment = itow(date.day);
while (fragment.Length() < 2) fragment = L"0" + fragment;
result += fragment;
reading += 2;
}
else if (wcsncmp(reading, L"hh", 2) == 0)
{
WString fragment = itow(date.hour > 12 ? date.hour - 12 : date.hour);
while (fragment.Length() < 2) fragment = L"0" + fragment;
result += fragment;
reading += 2;
}
else if (wcsncmp(reading, L"HH", 2) == 0)
{
WString fragment = itow(date.hour);
while (fragment.Length() < 2) fragment = L"0" + fragment;
result += fragment;
reading += 2;
}
else if (wcsncmp(reading, L"mm", 2) == 0)
{
WString fragment = itow(date.minute);
while (fragment.Length() < 2) fragment = L"0" + fragment;
result += fragment;
reading += 2;
}
else if (wcsncmp(reading, L"ss", 2) == 0)
{
WString fragment = itow(date.second);
while (fragment.Length() < 2) fragment = L"0" + fragment;
result += fragment;
reading += 2;
}
else if (wcsncmp(reading, L"tt", 2) == 0)
{
result += date.hour > 12 ? L"PM" : L"AM";
reading += 2;
}
else
{
result += WString::FromChar(*reading);
reading++;
}
}
return result;
}
WString EnUsLocaleImpl::FormatTime(const WString& localeName, const WString& format, DateTime time) const
{
return FormatDate(localeName, format, time);
}
WString EnUsLocaleImpl::FormatNumber(const WString&, const WString& number) const
{
return number;
}
WString EnUsLocaleImpl::FormatCurrency(const WString&, const WString& currency) const
{
return currency;
}
WString EnUsLocaleImpl::GetShortDayOfWeekName(const WString&, vint dayOfWeek) const
{
switch (dayOfWeek)
{
case 0: return L"Sun";
case 1: return L"Mon";
case 2: return L"Tue";
case 3: return L"Wed";
case 4: return L"Thu";
case 5: return L"Fri";
case 6: return L"Sat";
}
return L"";
}
WString EnUsLocaleImpl::GetLongDayOfWeekName(const WString&, vint dayOfWeek) const
{
switch (dayOfWeek)
{
case 0: return L"Sunday";
case 1: return L"Monday";
case 2: return L"Tuesday";
case 3: return L"Wednesday";
case 4: return L"Thursday";
case 5: return L"Friday";
case 6: return L"Saturday";
}
return L"";
}
WString EnUsLocaleImpl::GetShortMonthName(const WString&, vint month) const
{
switch (month)
{
case 1: return L"Jan";
case 2: return L"Feb";
case 3: return L"Mar";
case 4: return L"Apr";
case 5: return L"May";
case 6: return L"Jun";
case 7: return L"Jul";
case 8: return L"Aug";
case 9: return L"Sep";
case 10: return L"Oct";
case 11: return L"Nov";
case 12: return L"Dec";
}
return L"";
}
WString EnUsLocaleImpl::GetLongMonthName(const WString&, vint month) const
{
switch (month)
{
case 1: return L"January";
case 2: return L"February";
case 3: return L"March";
case 4: return L"April";
case 5: return L"May";
case 6: return L"June";
case 7: return L"July";
case 8: return L"August";
case 9: return L"September";
case 10: return L"October";
case 11: return L"November";
case 12: return L"December";
}
return L"";
}
WString EnUsLocaleImpl::ToLower(const WString&, const WString& str) const
{
return wlower(str);
}
WString EnUsLocaleImpl::ToUpper(const WString&, const WString& str) const
{
return wupper(str);
}
WString EnUsLocaleImpl::ToLinguisticLower(const WString&, const WString& str) const
{
return wlower(str);
}
WString EnUsLocaleImpl::ToLinguisticUpper(const WString&, const WString& str) const
{
return wupper(str);
}
vint EnUsLocaleImpl::Compare(const WString&, const WString& s1, const WString& s2, Locale::Normalization normalization) const
{
switch (normalization)
{
case Locale::Normalization::None:
return wcscmp(s1.Buffer(), s2.Buffer());
case Locale::Normalization::IgnoreCase:
return _wcsicmp(s1.Buffer(), s2.Buffer());
default:
return 0;
}
}
vint EnUsLocaleImpl::CompareOrdinal(const WString& s1, const WString& s2) const
{
return wcscmp(s1.Buffer(), s2.Buffer());
}
vint EnUsLocaleImpl::CompareOrdinalIgnoreCase(const WString& s1, const WString& s2) const
{
return _wcsicmp(s1.Buffer(), s2.Buffer());
}
Pair<vint, vint> EnUsLocaleImpl::FindFirst(const WString&, const WString& text, const WString& find, Locale::Normalization normalization) const
{
if (text.Length() < find.Length() || find.Length() == 0)
{
return Pair<vint, vint>(-1, 0);
}
const wchar_t* result = nullptr;
switch (normalization)
{
case Locale::Normalization::None:
{
const wchar_t* reading = text.Buffer();
while (*reading)
{
if (wcsncmp(reading, find.Buffer(), find.Length()) == 0)
{
result = reading;
break;
}
reading++;
}
}
break;
case Locale::Normalization::IgnoreCase:
{
const wchar_t* reading = text.Buffer();
while (*reading)
{
if (_wcsnicmp(reading, find.Buffer(), find.Length()) == 0)
{
result = reading;
break;
}
reading++;
}
}
break;
}
return result == nullptr ? Pair<vint, vint>(-1, 0) : Pair<vint, vint>(result - text.Buffer(), find.Length());
}
Pair<vint, vint> EnUsLocaleImpl::FindLast(const WString&, const WString& text, const WString& find, Locale::Normalization normalization) const
{
if (text.Length() < find.Length() || find.Length() == 0)
{
return Pair<vint, vint>(-1, 0);
}
const wchar_t* result = nullptr;
switch (normalization)
{
case Locale::Normalization::None:
{
const wchar_t* reading = text.Buffer();
while (*reading)
{
if (wcsncmp(reading, find.Buffer(), find.Length()) == 0)
{
result = reading;
}
reading++;
}
}
break;
case Locale::Normalization::IgnoreCase:
{
const wchar_t* reading = text.Buffer();
while (*reading)
{
if (_wcsnicmp(reading, find.Buffer(), find.Length()) == 0)
{
result = reading;
}
reading++;
}
}
break;
}
return result == nullptr ? Pair<vint, vint>(-1, 0) : Pair<vint, vint>(result - text.Buffer(), find.Length());
}
bool EnUsLocaleImpl::StartsWith(const WString&, const WString& text, const WString& find, Locale::Normalization normalization) const
{
if (text.Length() < find.Length() || find.Length() == 0)
{
return false;
}
switch (normalization)
{
case Locale::Normalization::None:
return wcsncmp(text.Buffer(), find.Buffer(), find.Length()) == 0;
case Locale::Normalization::IgnoreCase:
return _wcsnicmp(text.Buffer(), find.Buffer(), find.Length()) == 0;
}
return false;
}
bool EnUsLocaleImpl::EndsWith(const WString&, const WString& text, const WString& find, Locale::Normalization normalization) const
{
if (text.Length() < find.Length() || find.Length() == 0)
{
return false;
}
switch (normalization)
{
case Locale::Normalization::None:
return wcsncmp(text.Buffer() + text.Length() - find.Length(), find.Buffer(), find.Length()) == 0;
case Locale::Normalization::IgnoreCase:
return _wcsnicmp(text.Buffer() + text.Length() - find.Length(), find.Buffer(), find.Length()) == 0;
}
return false;
}
#ifdef VCZH_GCC
#undef _wcsicmp
#undef _wcsnicmp
#endif
/***********************************************************************
InjectLocaleImpl
***********************************************************************/
extern ILocaleImpl* GetOSLocaleImpl();
feature_injection::FeatureInjection<ILocaleImpl>& GetLocaleInjection()
{
static feature_injection::FeatureInjection<ILocaleImpl> injection(GetOSLocaleImpl());
return injection;
}
void InjectLocaleImpl(ILocaleImpl* impl)
{
GetLocaleInjection().Inject(impl);
}
void EjectLocaleImpl(ILocaleImpl* impl)
{
if (impl == nullptr)
{
GetLocaleInjection().EjectAll();
}
else
{
GetLocaleInjection().Eject(impl);
}
}
/***********************************************************************
Locale
***********************************************************************/
Locale::Locale(const WString& _localeName)
:localeName(_localeName)
{
}
const WString& Locale::GetName()const
{
return localeName;
}
/***********************************************************************
Locale (static)
***********************************************************************/
Locale Locale::Invariant()
{
return GetLocaleInjection().Get()->Invariant();
}
Locale Locale::SystemDefault()
{
return GetLocaleInjection().Get()->SystemDefault();
}
Locale Locale::UserDefault()
{
return GetLocaleInjection().Get()->UserDefault();
}
void Locale::Enumerate(collections::List<Locale>& locales)
{
GetLocaleInjection().Get()->Enumerate(locales);
}
/***********************************************************************
Locale (ILocaleImpl redirections)
***********************************************************************/
void Locale::GetShortDateFormats(collections::List<WString>& formats)const
{
GetLocaleInjection().Get()->GetShortDateFormats(localeName, formats);
}
void Locale::GetLongDateFormats(collections::List<WString>& formats)const
{
GetLocaleInjection().Get()->GetLongDateFormats(localeName, formats);
}
void Locale::GetYearMonthDateFormats(collections::List<WString>& formats)const
{
GetLocaleInjection().Get()->GetYearMonthDateFormats(localeName, formats);
}
void Locale::GetLongTimeFormats(collections::List<WString>& formats)const
{
GetLocaleInjection().Get()->GetLongTimeFormats(localeName, formats);
}
void Locale::GetShortTimeFormats(collections::List<WString>& formats)const
{
GetLocaleInjection().Get()->GetShortTimeFormats(localeName, formats);
}
WString Locale::FormatDate(const WString& format, DateTime date)const
{
return GetLocaleInjection().Get()->FormatDate(localeName, format, date);
}
WString Locale::FormatTime(const WString& format, DateTime time)const
{
return GetLocaleInjection().Get()->FormatTime(localeName, format, time);
}
WString Locale::FormatNumber(const WString& number)const
{
return GetLocaleInjection().Get()->FormatNumber(localeName, number);
}
WString Locale::FormatCurrency(const WString& currency)const
{
return GetLocaleInjection().Get()->FormatCurrency(localeName, currency);
}
WString Locale::GetShortDayOfWeekName(vint dayOfWeek)const
{
return GetLocaleInjection().Get()->GetShortDayOfWeekName(localeName, dayOfWeek);
}
WString Locale::GetLongDayOfWeekName(vint dayOfWeek)const
{
return GetLocaleInjection().Get()->GetLongDayOfWeekName(localeName, dayOfWeek);
}
WString Locale::GetShortMonthName(vint month)const
{
return GetLocaleInjection().Get()->GetShortMonthName(localeName, month);
}
WString Locale::GetLongMonthName(vint month)const
{
return GetLocaleInjection().Get()->GetLongMonthName(localeName, month);
}
WString Locale::ToLower(const WString& str)const
{
return GetLocaleInjection().Get()->ToLower(localeName, str);
}
WString Locale::ToUpper(const WString& str)const
{
return GetLocaleInjection().Get()->ToUpper(localeName, str);
}
WString Locale::ToLinguisticLower(const WString& str)const
{
return GetLocaleInjection().Get()->ToLinguisticLower(localeName, str);
}
WString Locale::ToLinguisticUpper(const WString& str)const
{
return GetLocaleInjection().Get()->ToLinguisticUpper(localeName, str);
}
vint Locale::Compare(const WString& s1, const WString& s2, Normalization normalization)const
{
return GetLocaleInjection().Get()->Compare(localeName, s1, s2, normalization);
}
vint Locale::CompareOrdinal(const WString& s1, const WString& s2)const
{
return GetLocaleInjection().Get()->CompareOrdinal(s1, s2);
}
vint Locale::CompareOrdinalIgnoreCase(const WString& s1, const WString& s2)const
{
return GetLocaleInjection().Get()->CompareOrdinalIgnoreCase(s1, s2);
}
collections::Pair<vint, vint> Locale::FindFirst(const WString& text, const WString& find, Normalization normalization)const
{
return GetLocaleInjection().Get()->FindFirst(localeName, text, find, normalization);
}
collections::Pair<vint, vint> Locale::FindLast(const WString& text, const WString& find, Normalization normalization)const
{
return GetLocaleInjection().Get()->FindLast(localeName, text, find, normalization);
}
bool Locale::StartsWith(const WString& text, const WString& find, Normalization normalization)const
{
return GetLocaleInjection().Get()->StartsWith(localeName, text, find, normalization);
}
bool Locale::EndsWith(const WString& text, const WString& find, Normalization normalization)const
{
return GetLocaleInjection().Get()->EndsWith(localeName, text, find, normalization);
}
}
/***********************************************************************
.\THREADING.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#if defined VCZH_ARM
#include <arm_acle.h>
#elif defined VCZH_MSVC || defined VCZH_GCC
#include <emmintrin.h>
#endif
namespace vl
{
/***********************************************************************
SpinLock
***********************************************************************/
SpinLock::Scope::Scope(SpinLock& _spinLock)
:spinLock(&_spinLock)
{
spinLock->Enter();
}
SpinLock::Scope::~Scope()
{
spinLock->Leave();
}
bool SpinLock::TryEnter()
{
return token.exchange(1) == 0;
}
void SpinLock::Enter()
{
while (true)
{
vint expected = 0;
if (token.compare_exchange_strong(expected, 1))
{
return;
}
while (token != 0)
{
#ifdef VCZH_ARM
__yield();
#else
_mm_pause();
#endif
}
}
}
void SpinLock::Leave()
{
token.exchange(0);
}
/***********************************************************************
ThreadLocalStorage
***********************************************************************/
void ThreadLocalStorage::Clear()
{
CHECK_ERROR(!disposed, L"vl::ThreadLocalStorage::Clear()#Cannot access a disposed ThreadLocalStorage.");
if(destructor)
{
if (auto data = Get())
{
destructor(data);
}
}
Set(nullptr);
}
void ThreadLocalStorage::Dispose()
{
CHECK_ERROR(!disposed, L"vl::ThreadLocalStorage::Dispose()#Cannot access a disposed ThreadLocalStorage.");
Clear();
disposed = true;
}
struct TlsStorageLink
{
ThreadLocalStorage* storage = nullptr;
TlsStorageLink* next = nullptr;
};
volatile bool tlsFixed = false;
TlsStorageLink* tlsHead = nullptr;
TlsStorageLink** tlsTail = &tlsHead;
void ThreadLocalStorage::PushStorage(ThreadLocalStorage* storage)
{
CHECK_ERROR(!tlsFixed, L"vl::ThreadLocalStorage::PushStorage(ThreadLocalStorage*)#Cannot create new ThreadLocalStorage instance after calling ThreadLocalStorage::FixStorages().");
auto link = new TlsStorageLink;
link->storage = storage;
*tlsTail = link;
tlsTail = &link->next;
}
void ThreadLocalStorage::FixStorages()
{
tlsFixed = true;
}
void ThreadLocalStorage::ClearStorages()
{
FixStorages();
auto current = tlsHead;
while (current)
{
current->storage->Clear();
current = current->next;
}
}
void ThreadLocalStorage::DisposeStorages()
{
FixStorages();
auto current = tlsHead;
tlsHead = nullptr;
tlsTail = nullptr;
while (current)
{
current->storage->Dispose();
auto temp = current;
current = current->next;
delete temp;
}
}
/***********************************************************************
TaskQueue
***********************************************************************/
TaskQueue::TaskQueue()
{
CHECK_ERROR(semaphoreTasks.Create(0, 65536), L"vl::TaskQueue::TaskQueue()#Failed to create the task semaphore.");
}
TaskQueue::~TaskQueue()
{
}
void TaskQueue::QueueTask(Func<void()> task)
{
SPIN_LOCK(lockTasks)
{
tasks.Add(task);
}
semaphoreTasks.Release();
}
void TaskQueue::QueueExitTask()
{
SPIN_LOCK(lockTasks)
{
exitTaskQueued = true;
}
semaphoreTasks.Release();
}
void TaskQueue::RunTaskQueue()
{
while (true)
{
Func<void()> task;
bool hasTask = false;
bool shouldExit = false;
SPIN_LOCK(lockTasks)
{
if (tasks.Count() > 0)
{
task = tasks[0];
tasks.RemoveAt(0);
hasTask = true;
}
else
{
shouldExit = exitTaskQueued;
}
}
if (shouldExit)
{
break;
}
if (!hasTask)
{
semaphoreTasks.Wait();
continue;
}
task();
}
}
}
/***********************************************************************
.\ENCODING\BASE64ENCODING.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
namespace vl
{
namespace stream
{
const char8_t Utf8Base64Codes[] = u8"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/***********************************************************************
Utf8Base64Encoder
***********************************************************************/
void Utf8Base64Encoder::WriteBytesToCharArray(uint8_t* fromBytes, char8_t(&toChars)[Base64CycleChars], vint bytes)
{
switch (bytes)
{
case 1:
{
toChars[0] = Utf8Base64Codes[fromBytes[0] >> 2];
toChars[1] = Utf8Base64Codes[(fromBytes[0] % (1 << 2)) << 4];
toChars[2] = u8'=';
toChars[3] = u8'=';
}
break;
case 2:
{
toChars[0] = Utf8Base64Codes[fromBytes[0] >> 2];
toChars[1] = Utf8Base64Codes[((fromBytes[0] % (1 << 2)) << 4) | (fromBytes[1] >> 4)];
toChars[2] = Utf8Base64Codes[(fromBytes[1] % (1 << 4)) << 2];
toChars[3] = u8'=';
}
break;
case 3:
{
toChars[0] = Utf8Base64Codes[fromBytes[0] >> 2];
toChars[1] = Utf8Base64Codes[((fromBytes[0] % (1 << 2)) << 4) | (fromBytes[1] >> 4)];
toChars[2] = Utf8Base64Codes[((fromBytes[1] % (1 << 4)) << 2) | (fromBytes[2] >> 6)];
toChars[3] = Utf8Base64Codes[fromBytes[2] % (1 << 6)];
}
break;
default:
CHECK_FAIL(L"vl::stream::Utf8Base64Encoder::WriteBytesToCharArray(uint8_t*, char8_t(&)[Base64CycleChars], vint)#Parameter bytes should be 1, 2 or 3.");
}
}
bool Utf8Base64Encoder::WriteCycle(uint8_t*& reading, vint& _size)
{
if (_size <= 0) return false;
vint bytes = _size < Base64CycleBytes ? _size : Base64CycleBytes;
char8_t chars[Base64CycleChars];
WriteBytesToCharArray(reading, chars, bytes);
vint writtenBytes = stream->Write(chars, Base64CycleChars);
CHECK_ERROR(writtenBytes == Base64CycleChars, L"vl::stream::Utf8Base64Encoder::WriteCycle(uint8_t*&, vint&)#The underlying stream failed to accept enough base64 characters.");
reading += bytes;
_size -= bytes;
return true;
}
bool Utf8Base64Encoder::WriteCache(uint8_t*& reading, vint& _size)
{
if (cacheSize > 0 || _size < Base64CycleBytes)
{
vint copiedBytes = Base64CycleBytes - cacheSize;
if (copiedBytes > _size) copiedBytes = _size;
if (copiedBytes > 0)
{
memcpy(cache + cacheSize, reading, copiedBytes);
reading += copiedBytes;
_size -= copiedBytes;
cacheSize += copiedBytes;
}
}
if (cacheSize == 0) return _size > 0;
if (cacheSize == Base64CycleBytes)
{
uint8_t* cacheReading = cache;
return WriteCycle(cacheReading, cacheSize);
}
return true;
}
vint Utf8Base64Encoder::Write(void* _buffer, vint _size)
{
uint8_t* reading = (uint8_t*)_buffer;
// flush cache if any
if (!WriteCache(reading, _size)) goto FINISHED_WRITING;
// run Base64 encoding
while (_size >= Base64CycleBytes)
{
if (!WriteCycle(reading, _size)) goto FINISHED_WRITING;
}
// run the last Base64 encoding cycle and wrote a postfix to cache
WriteCache(reading, _size);
FINISHED_WRITING:
return reading - (uint8_t*)_buffer;
}
void Utf8Base64Encoder::Close()
{
if (cacheSize > 0)
{
char8_t chars[Base64CycleChars];
WriteBytesToCharArray(cache, chars, cacheSize);
vint writtenBytes = stream->Write(chars, Base64CycleChars);
CHECK_ERROR(writtenBytes == Base64CycleChars, L"vl::stream::Utf8Base64Encoder::Close()#The underlying stream failed to accept enough base64 characters.");
cacheSize = 0;
}
EncoderBase::Close();
}
/***********************************************************************
Utf8Base64Decoder
***********************************************************************/
vint Utf8Base64Decoder::ReadBytesFromCharArray(char8_t(&fromChars)[Base64CycleChars], uint8_t* toBytes)
{
uint8_t nums[Base64CycleChars];
for (vint i = 0; i < Base64CycleChars; i++)
{
char8_t c = fromChars[i];
if (u8'A' <= c && c <= u8'Z') nums[i] = c - u8'A';
else if (u8'a' <= c && c <= u8'z') nums[i] = c - u8'a' + 26;
else if (u8'0' <= c && c <= u8'9') nums[i] = c - u8'0' + 52;
else switch (c)
{
case '+':nums[i] = 62; break;
case '/':nums[i] = 63; break;
case '=':nums[i] = 0; break;
default:
CHECK_FAIL(L"vl::stream::Utf8Base64Decoder::ReadBytesFromCharArray(char(&)[Base64CycleChars], uint8_t*)#Illegal Base64 character.");
}
}
toBytes[0] = (nums[0] << 2) | (nums[1] >> 4);
if (fromChars[2] == u8'=') return 1;
toBytes[1] = ((nums[1] % (1 << 4)) << 4) | (nums[2] >> 2);
if (fromChars[3] == u8'=') return 2;
toBytes[2] = ((nums[2] % (1 << 2)) << 6) | nums[3];
return 3;
}
vint Utf8Base64Decoder::ReadCycle(uint8_t*& writing, vint& _size)
{
char8_t chars[Base64CycleChars];
vint readChars = stream->Read((void*)chars, Base64CycleChars);
if (readChars == 0) return 0;
CHECK_ERROR(readChars == Base64CycleChars, L"vl::stream::Utf8Base64Decoder::ReadCycle(uint8_t*&, vint&)#The underlying stream failed to provide enough base64 characters.");
vint readBytes = ReadBytesFromCharArray(chars, writing);
writing += readBytes;
_size -= readBytes;
return readBytes;
}
void Utf8Base64Decoder::ReadCache(uint8_t*& writing, vint& _size)
{
if (cacheSize > 0)
{
vint copiedBytes = cacheSize;
if (copiedBytes > _size) copiedBytes = _size;
if (copiedBytes > 0)
{
memcpy(writing, cache, copiedBytes);
writing += copiedBytes;
_size -= copiedBytes;
cacheSize -= copiedBytes;
if (cacheSize > 0)
{
memmove(cache, cache + copiedBytes, cacheSize);
}
}
}
}
vint Utf8Base64Decoder::Read(void* _buffer, vint _size)
{
uint8_t* writing = (uint8_t*)_buffer;
// write cache to buffer if any
ReadCache(writing, _size);
// run Base64 decoding
while (_size >= Base64CycleBytes)
{
if (ReadCycle(writing, _size) == 0) goto FINISHED_READING;
}
// run the last Base64 decoding cycle and write a prefix to buffer
if (_size > 0)
{
uint8_t* cacheWriting = cache;
vint temp = 0;
if ((cacheSize = ReadCycle(cacheWriting, temp)) == 0) goto FINISHED_READING;
ReadCache(writing, _size);
}
FINISHED_READING:
return writing - (uint8_t*)_buffer;
}
}
}
/***********************************************************************
.\ENCODING\ENCODING.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
namespace vl
{
namespace stream
{
/***********************************************************************
EncoderBase
***********************************************************************/
void EncoderBase::Setup(IStream* _stream)
{
stream = _stream;
}
void EncoderBase::Close()
{
}
/***********************************************************************
DecoderBase
***********************************************************************/
void DecoderBase::Setup(IStream* _stream)
{
stream = _stream;
}
void DecoderBase::Close()
{
}
}
}
/***********************************************************************
.\ENCODING\LZWENCODING.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
namespace vl
{
namespace stream
{
using namespace collections;
using namespace lzw;
/***********************************************************************
LzwBase
***********************************************************************/
void LzwBase::UpdateIndexBits()
{
if (nextIndex >=2 && (nextIndex & (nextIndex - 1)) == 0)
{
indexBits++;
}
}
lzw::Code* LzwBase::CreateCode(lzw::Code* prefix, vuint8_t byte)
{
if (nextIndex < MaxDictionarySize)
{
Code* code = codeAllocator.Create();
code->byte = byte;
code->code = nextIndex;
code->parent = prefix;
code->size = prefix->size + 1;
prefix->children.Set(byte, code, mapAllocator);
nextIndex++;
return code;
}
else
{
return 0;
}
}
LzwBase::LzwBase()
:codeAllocator(65536)
, mapAllocator(1048576)
{
root = codeAllocator.Create();
for (vint i = 0; i < 256; i++)
{
UpdateIndexBits();
CreateCode(root, (vuint8_t)i);
}
}
LzwBase::LzwBase(bool (&existingBytes)[256])
{
root = codeAllocator.Create();
for (vint i = 0; i < 256; i++)
{
if (existingBytes[i])
{
UpdateIndexBits();
CreateCode(root, (vuint8_t)i);
}
}
if (indexBits < 8)
{
eofIndex = nextIndex++;
}
}
LzwBase::~LzwBase()
{
}
/***********************************************************************
LzwEncoder
***********************************************************************/
void LzwEncoder::Flush()
{
vint written = 0;
vint bufferUsedSize = bufferUsedBits / 8;
if (bufferUsedBits % 8 != 0)
{
bufferUsedSize++;
}
while (written < bufferUsedSize)
{
vint size = stream->Write(buffer + written, bufferUsedSize - written);
CHECK_ERROR(size != 0, L"LzwEncoder::Flush()#Failed to flush the lzw buffer.");
written += size;
}
bufferUsedBits = 0;
}
vuint8_t highMarks[9] = { 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF };
vuint8_t lowMarks[9] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF };
void LzwEncoder::WriteNumber(vint number, vint bitSize)
{
vint bitStart = 0;
vint bitStep = 8 - bufferUsedBits % 8;
if (bitStep > bitSize)
{
bitStep = bitSize;
}
while (bitStart < bitSize)
{
if(bufferUsedBits == BufferSize * 8)
{
Flush();
}
vint writeStart = bufferUsedBits % 8;
vint byteIndex = bufferUsedBits / 8;
vuint8_t byte = buffer[byteIndex];
byte &= highMarks[writeStart];
vuint8_t content = (vuint8_t)((number >> bitStart)&lowMarks[bitStep]) << (8 - writeStart - bitStep);
byte |= content;
buffer[byteIndex] = byte;
bufferUsedBits += bitStep;
bitStart += bitStep;
vint remain = bitSize - bitStart;
bitStep = remain < 8 ? remain : 8;
}
}
LzwEncoder::LzwEncoder()
{
prefix = root;
}
LzwEncoder::LzwEncoder(bool (&existingBytes)[256])
:LzwBase(existingBytes)
{
prefix = root;
}
LzwEncoder::~LzwEncoder()
{
}
void LzwEncoder::Close()
{
if (prefix != root)
{
WriteNumber(prefix->code, indexBits);
prefix = root;
}
vint remain = 8 - bufferUsedBits % 8;
if (remain != 8 && remain >= indexBits)
{
CHECK_ERROR(eofIndex != -1, L"LzwEncoder::Close()#Internal error.");
WriteNumber(eofIndex, indexBits);
}
Flush();
EncoderBase::Close();
}
vint LzwEncoder::Write(void* _buffer, vint _size)
{
vuint8_t* bytes = (vuint8_t*)_buffer;
for (vint i = 0; i < _size; i++)
{
vuint8_t byte = bytes[i];
Code* next = prefix->children.Get(byte);
if (next)
{
prefix = next;
}
else
{
WriteNumber(prefix->code, indexBits);
if (nextIndex < MaxDictionarySize)
{
UpdateIndexBits();
CreateCode(prefix, byte);
}
prefix = root->children.Get(byte);
}
}
return _size;
}
/***********************************************************************
LzwDecoder
***********************************************************************/
bool LzwDecoder::ReadNumber(vint& number, vint bitSize)
{
number = 0;
if (inputBufferSize == -1)
{
return false;
}
vint remainBits = inputBufferSize * 8 - inputBufferUsedBits;
vint writtenBits = 0;
vint bitStep = 8 - inputBufferUsedBits % 8;
if (bitStep > bitSize)
{
bitStep = bitSize;
}
while (writtenBits < bitSize)
{
if (remainBits == 0)
{
inputBufferSize = stream->Read(inputBuffer, BufferSize);
if (inputBufferSize == 0)
{
inputBufferSize = -1;
return false;
}
remainBits = inputBufferSize * 8;
inputBufferUsedBits = 0;
}
vuint8_t byte = inputBuffer[inputBufferUsedBits / 8];
byte >>= (8 - inputBufferUsedBits % 8 - bitStep);
byte &= lowMarks[bitStep];
number |= byte << writtenBits;
inputBufferUsedBits += bitStep;
remainBits -= bitStep;
writtenBits += bitStep;
vint remain = bitSize - writtenBits;
bitStep = remain < 8 ? remain : 8;
}
return true;
}
void LzwDecoder::PrepareOutputBuffer(vint size)
{
if (outputBuffer.Count() < size)
{
outputBuffer.Resize(size);
}
outputBufferSize = size;
}
void LzwDecoder::ExpandCodeToOutputBuffer(lzw::Code* code)
{
vuint8_t* outputByte = &outputBuffer[0] + code->size;
Code* current = code;
while (current != root)
{
*(--outputByte) = current->byte;
current = current->parent;
}
outputBufferUsedBytes = 0;
}
LzwDecoder::LzwDecoder()
{
for (vint i = 0; i < 256; i++)
{
dictionary.Add(root->children.Get((vuint8_t)i));
}
}
LzwDecoder::LzwDecoder(bool (&existingBytes)[256])
:LzwBase(existingBytes)
{
for (vint i = 0; i < 256; i++)
{
if (existingBytes[i])
{
dictionary.Add(root->children.Get((vuint8_t)i));
}
}
if (eofIndex != -1)
{
dictionary.Add(nullptr);
}
}
LzwDecoder::~LzwDecoder()
{
}
vint LzwDecoder::Read(void* _buffer, vint _size)
{
vint written = 0;
vuint8_t* bytes = (vuint8_t*)_buffer;
while (written < _size)
{
vint expect = _size - written;
vint remain = outputBufferSize - outputBufferUsedBytes;
if (remain == 0)
{
vint index = 0;
if (!ReadNumber(index, indexBits) || index == eofIndex)
{
break;
}
Code* prefix = 0;
if (index == dictionary.Count())
{
prefix = lastCode;
PrepareOutputBuffer(prefix->size + 1);
ExpandCodeToOutputBuffer(prefix);
outputBuffer[outputBufferSize - 1] = outputBuffer[0];
}
else
{
prefix = dictionary[index];
PrepareOutputBuffer(prefix->size);
ExpandCodeToOutputBuffer(prefix);
}
if (nextIndex < MaxDictionarySize)
{
if (lastCode)
{
dictionary.Add(CreateCode(lastCode, outputBuffer[0]));
}
UpdateIndexBits();
}
lastCode = dictionary[index];
}
else
{
if (remain > expect)
{
remain = expect;
}
memcpy(bytes + written, &outputBuffer[outputBufferUsedBytes], remain);
outputBufferUsedBytes += remain;
written += remain;
}
}
return written;
}
/***********************************************************************
Helper Functions
***********************************************************************/
vint CopyStream(stream::IStream& inputStream, stream::IStream& outputStream)
{
vint totalSize = 0;
while (true)
{
char buffer[1024];
vint copied = inputStream.Read(buffer, (vint)sizeof(buffer));
if (copied == 0)
{
break;
}
totalSize += outputStream.Write(buffer, copied);
}
return totalSize;
}
const vint CompressionFragmentSize = 1048576;
void CompressStream(stream::IStream& inputStream, stream::IStream& outputStream)
{
Array<char> buffer(CompressionFragmentSize);
while (true)
{
vint size = inputStream.Read(&buffer[0], buffer.Count());
if (size == 0) break;
MemoryStream compressedStream;
{
LzwEncoder encoder;
EncoderStream encoderStream(compressedStream, encoder);
encoderStream.Write(&buffer[0], size);
}
compressedStream.SeekFromBegin(0);
{
{
vint32_t bufferSize = (vint32_t)size;
outputStream.Write(&bufferSize, (vint)sizeof(bufferSize));
}
{
vint32_t compressedSize = (vint32_t)compressedStream.Size();
outputStream.Write(&compressedSize, (vint)sizeof(compressedSize));
}
CopyStream(compressedStream, outputStream);
}
}
}
void DecompressStream(stream::IStream& inputStream, stream::IStream& outputStream)
{
vint totalSize = 0;
vint totalWritten = 0;
while (true)
{
vint32_t bufferSize = 0;
if (inputStream.Read(&bufferSize, (vint)sizeof(bufferSize)) != sizeof(bufferSize))
{
break;
}
vint32_t compressedSize = 0;
CHECK_ERROR(inputStream.Read(&compressedSize, (vint)sizeof(compressedSize)) == sizeof(compressedSize), L"vl::stream::DecompressStream(MemoryStream&, MemoryStream&)#Incomplete input");
Array<char> buffer(compressedSize);
CHECK_ERROR(inputStream.Read(&buffer[0], compressedSize) == compressedSize, L"vl::stream::DecompressStream(MemoryStream&, MemoryStream&)#Incomplete input");
MemoryWrapperStream compressedStream(&buffer[0], compressedSize);
LzwDecoder decoder;
DecoderStream decoderStream(compressedStream, decoder);
totalWritten += CopyStream(decoderStream, outputStream);
totalSize += bufferSize;
}
CHECK_ERROR(outputStream.Size() == totalSize, L"vl::stream::DecompressStream(MemoryStream&, MemoryStream&)#Incomplete input");
}
}
}
/***********************************************************************
.\ENCODING\CHARFORMAT\BOMENCODING.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
namespace vl
{
namespace stream
{
/***********************************************************************
BomEncoder
***********************************************************************/
BomEncoder::BomEncoder(Encoding _encoding)
:encoding(_encoding)
,encoder(0)
{
switch(encoding)
{
case Mbcs:
encoder=new MbcsEncoder;
break;
case Utf8:
encoder=new Utf8Encoder;
break;
case Utf16:
encoder=new Utf16Encoder;
break;
case Utf16BE:
encoder=new Utf16BEEncoder;
break;
}
}
BomEncoder::~BomEncoder()
{
Close();
}
void BomEncoder::Setup(IStream* _stream)
{
switch(encoding)
{
case Mbcs:
break;
case Utf8:
_stream->Write((void*)"\xEF\xBB\xBF", 3);
break;
case Utf16:
_stream->Write((void*)"\xFF\xFE", 2);
break;
case Utf16BE:
_stream->Write((void*)"\xFE\xFF", 2);
break;
}
encoder->Setup(_stream);
}
void BomEncoder::Close()
{
if(encoder)
{
encoder->Close();
delete encoder;
encoder=0;
}
}
vint BomEncoder::Write(void* _buffer, vint _size)
{
return encoder->Write(_buffer, _size);
}
/***********************************************************************
BomDecoder
***********************************************************************/
BomDecoder::BomStream::BomStream(IStream* _stream, char* _bom, vint _bomLength)
:stream(_stream)
,bomPosition(0)
,bomLength(_bomLength)
{
memcpy(bom, _bom, bomLength);
}
bool BomDecoder::BomStream::CanRead()const
{
return IsAvailable();
}
bool BomDecoder::BomStream::CanWrite()const
{
return false;
}
bool BomDecoder::BomStream::CanSeek()const
{
return false;
}
bool BomDecoder::BomStream::CanPeek()const
{
return false;
}
bool BomDecoder::BomStream::IsLimited()const
{
return stream!=0 && stream->IsLimited();
}
bool BomDecoder::BomStream::IsAvailable()const
{
return stream!=0 && stream->IsAvailable();
}
void BomDecoder::BomStream::Close()
{
stream=0;
}
pos_t BomDecoder::BomStream::Position()const
{
return IsAvailable()?bomPosition+stream->Position():-1;
}
pos_t BomDecoder::BomStream::Size()const
{
return -1;
}
void BomDecoder::BomStream::Seek(pos_t _size)
{
CHECK_FAIL(L"BomDecoder::BomStream::Seek(pos_t)#Operation not supported.");
}
void BomDecoder::BomStream::SeekFromBegin(pos_t _size)
{
CHECK_FAIL(L"BomDecoder::BomStream::SeekFromBegin(pos_t)#Operation not supported.");
}
void BomDecoder::BomStream::SeekFromEnd(pos_t _size)
{
CHECK_FAIL(L"BomDecoder::BomStream::SeekFromEnd(pos_t)#Operation not supported.");
}
vint BomDecoder::BomStream::Read(void* _buffer, vint _size)
{
vint result=0;
unsigned char* buffer=(unsigned char*)_buffer;
if(bomPosition<bomLength)
{
vint remain=bomLength-bomPosition;
result=remain<_size?remain:_size;
memcpy(buffer, bom+bomPosition, result);
buffer+=result;
bomPosition+=result;
_size-=result;
}
if(_size)
{
result+=stream->Read(buffer, _size);
}
return result;
}
vint BomDecoder::BomStream::Write(void* _buffer, vint _size)
{
CHECK_FAIL(L"BomDecoder::BomStream::Write(void*, vint)#Operation not supported.");
}
vint BomDecoder::BomStream::Peek(void* _buffer, vint _size)
{
CHECK_FAIL(L"BomDecoder::BomStream::Peek(void*, vint)#Operation not supported.");
}
BomDecoder::BomDecoder()
:decoder(0)
{
}
BomDecoder::~BomDecoder()
{
Close();
}
void BomDecoder::Setup(IStream* _stream)
{
char bom[3]={0};
vint length=_stream->Read(bom, sizeof(bom));
if(strncmp(bom, "\xEF\xBB\xBF", 3)==0)
{
decoder=new Utf8Decoder;
stream=new BomStream(_stream, bom+3, 0);
}
else if(strncmp(bom, "\xFF\xFE", 2)==0)
{
decoder=new Utf16Decoder;
stream=new BomStream(_stream, bom+2, 1);
}
else if(strncmp(bom, "\xFE\xFF", 2)==0)
{
decoder=new Utf16BEDecoder;
stream=new BomStream(_stream, bom+2, 1);
}
else
{
decoder=new MbcsDecoder;
stream=new BomStream(_stream, bom, 3);
}
decoder->Setup(stream);
}
void BomDecoder::Close()
{
if(decoder)
{
decoder->Close();
delete decoder;
decoder=0;
stream->Close();
delete stream;
stream=0;
}
}
vint BomDecoder::Read(void* _buffer, vint _size)
{
return decoder->Read(_buffer, _size);
}
}
}
/***********************************************************************
.\ENCODING\CHARFORMAT\CHARFORMAT.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
namespace vl
{
namespace stream
{
/***********************************************************************
Helper Functions
***********************************************************************/
bool CanBeMbcs(unsigned char* buffer, vint size)
{
for(vint i=0;i<size;i++)
{
if(buffer[i]==0) return false;
}
return true;
}
bool CanBeUtf8(unsigned char* buffer, vint size)
{
for(vint i=0;i<size;i++)
{
unsigned char c=(unsigned char)buffer[i];
if(c==0)
{
return false;
}
else
{
vint count10xxxxxx=0;
if((c&0x80)==0x00) /* 0x0xxxxxxx */ count10xxxxxx=0;
else if((c&0xE0)==0xC0) /* 0x110xxxxx */ count10xxxxxx=1;
else if((c&0xF0)==0xE0) /* 0x1110xxxx */ count10xxxxxx=2;
else if((c&0xF8)==0xF0) /* 0x11110xxx */ count10xxxxxx=3;
else if((c&0xFC)==0xF8) /* 0x111110xx */ count10xxxxxx=4;
else if((c&0xFE)==0xFC) /* 0x1111110x */ count10xxxxxx=5;
if(size<=i+count10xxxxxx)
{
return false;
}
else
{
for(vint j=0;j<count10xxxxxx;j++)
{
c=(unsigned char)buffer[i+j+1];
if((c&0xC0)!=0x80) /* 0x10xxxxxx */ return false;
}
}
i+=count10xxxxxx;
}
}
return true;
}
bool CanBeUtf16(unsigned char* buffer, vint size, bool& hitSurrogatePairs)
{
hitSurrogatePairs = false;
if (size % 2 != 0) return false;
bool needTrail = false;
for (vint i = 0; i < size; i += 2)
{
vuint16_t c = buffer[i] + (buffer[i + 1] << 8);
if (c == 0) return false;
vint type = 0;
if (0xD800 <= c && c <= 0xDBFF) type = 1;
else if (0xDC00 <= c && c <= 0xDFFF) type = 2;
if (needTrail)
{
if (type == 2)
{
needTrail = false;
}
else
{
return false;
}
}
else
{
if (type == 1)
{
needTrail = true;
hitSurrogatePairs = true;
}
else if (type != 0)
{
return false;
}
}
}
return !needTrail;
}
bool CanBeUtf16BE(unsigned char* buffer, vint size, bool& hitSurrogatePairs)
{
hitSurrogatePairs = false;
if (size % 2 != 0) return false;
bool needTrail = false;
for (vint i = 0; i < size; i += 2)
{
vuint16_t c = buffer[i + 1] + (buffer[i] << 8);
if (c == 0) return false;
vint type = 0;
if (0xD800 <= c && c <= 0xDBFF) type = 1;
else if (0xDC00 <= c && c <= 0xDFFF) type = 2;
if (needTrail)
{
if (type == 2)
{
needTrail = false;
}
else
{
return false;
}
}
else
{
if (type == 1)
{
needTrail = true;
hitSurrogatePairs = true;
}
else if (type != 0)
{
return false;
}
}
}
return !needTrail;
}
/***********************************************************************
TestEncoding
***********************************************************************/
extern void TestEncodingInternal(
unsigned char* buffer,
vint size,
BomEncoder::Encoding& encoding,
bool containsBom,
bool utf16HitSurrogatePairs,
bool utf16BEHitSurrogatePairs,
bool roughMbcs,
bool roughUtf8,
bool roughUtf16,
bool roughUtf16BE
);
void TestEncoding(unsigned char* buffer, vint size, BomEncoder::Encoding& encoding, bool& containsBom)
{
if (size >= 3 && strncmp((char*)buffer, "\xEF\xBB\xBF", 3) == 0)
{
encoding = BomEncoder::Utf8;
containsBom = true;
}
else if (size >= 2 && strncmp((char*)buffer, "\xFF\xFE", 2) == 0)
{
encoding = BomEncoder::Utf16;
containsBom = true;
}
else if (size >= 2 && strncmp((char*)buffer, "\xFE\xFF", 2) == 0)
{
encoding = BomEncoder::Utf16BE;
containsBom = true;
}
else
{
encoding = BomEncoder::Mbcs;
containsBom = false;
bool utf16HitSurrogatePairs = false;
bool utf16BEHitSurrogatePairs = false;
bool roughMbcs = CanBeMbcs(buffer, size);
bool roughUtf8 = CanBeUtf8(buffer, size);
bool roughUtf16 = CanBeUtf16(buffer, size, utf16HitSurrogatePairs);
bool roughUtf16BE = CanBeUtf16BE(buffer, size, utf16BEHitSurrogatePairs);
vint roughCount = (roughMbcs ? 1 : 0) + (roughUtf8 ? 1 : 0) + (roughUtf16 ? 1 : 0) + (roughUtf16BE ? 1 : 0);
if (roughCount == 1)
{
if (roughUtf8) encoding = BomEncoder::Utf8;
else if (roughUtf16) encoding = BomEncoder::Utf16;
else if (roughUtf16BE) encoding = BomEncoder::Utf16BE;
}
else if (roughCount > 1)
{
TestEncodingInternal(buffer, size, encoding, containsBom, utf16HitSurrogatePairs, utf16BEHitSurrogatePairs, roughMbcs, roughUtf8, roughUtf16, roughUtf16BE);
}
}
}
}
}
/***********************************************************************
.\ENCODING\CHARFORMAT\MBCSENCODING.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
namespace vl
{
namespace stream
{
/***********************************************************************
MbcsDecoder::ReadString
***********************************************************************/
extern bool IsMbcsLeadByte(char c);
extern void MbcsToWChar(wchar_t* wideBuffer, vint wideChars, vint wideReaded, char* mbcsBuffer, vint mbcsChars);
vint MbcsDecoder::ReadString(wchar_t* _buffer, vint chars)
{
char* source = new char[chars * 2];
char* reading = source;
vint readed = 0;
while (readed < chars)
{
if (stream->Read(reading, 1) != 1)
{
break;
}
if (IsMbcsLeadByte(*reading))
{
if (stream->Read(reading + 1, 1) != 1)
{
break;
}
reading += 2;
}
else
{
reading++;
}
readed++;
}
MbcsToWChar(_buffer, chars, readed, source, (vint)(reading - source));
delete[] source;
return readed;
}
/***********************************************************************
MbcsEncoder
***********************************************************************/
vint MbcsEncoder::Write(void* _buffer, vint _size)
{
// prepare a buffer for input
vint availableChars = (cacheSize + _size) / sizeof(wchar_t);
vint availableBytes = availableChars * sizeof(wchar_t);
bool needToFree = false;
vuint8_t* unicode = nullptr;
if (cacheSize > 0)
{
unicode = new vuint8_t[cacheSize + _size];
memcpy(unicode, cacheBuffer, cacheSize);
memcpy((vuint8_t*)unicode + cacheSize, _buffer, _size);
needToFree = true;
}
else
{
unicode = (vuint8_t*)_buffer;
}
#if defined VCZH_WCHAR_UTF16
if (availableChars > 0)
{
// a surrogate pair must be written as a whole thing
vuint16_t c = (vuint16_t)((wchar_t*)unicode)[availableChars - 1];
if ((c & 0xFC00U) == 0xD800U)
{
availableChars -= 1;
availableBytes -= sizeof(wchar_t);
}
}
#endif
// write the buffer
if (availableChars > 0)
{
vint written = WriteString((wchar_t*)unicode, availableChars) * sizeof(wchar_t);
CHECK_ERROR(written == availableBytes, L"MbcsEncoder::Write(void*, vint)#Failed to write a complete string.");
}
// cache the remaining
cacheSize = cacheSize + _size - availableBytes;
if (cacheSize > 0)
{
CHECK_ERROR(cacheSize <= sizeof(char32_t), L"MbcsEncoder::Write(void*, vint)#Unwritten text is too large to cache.");
memcpy(cacheBuffer, unicode + availableBytes, cacheSize);
}
if (needToFree) delete[] unicode;
return _size;
}
/***********************************************************************
MbcsDecoder::WriteString
***********************************************************************/
// implemented in platform dependent files
/***********************************************************************
MbcsDecoder
***********************************************************************/
vint MbcsDecoder::Read(void* _buffer, vint _size)
{
vuint8_t* writing = (vuint8_t*)_buffer;
vint filledBytes = 0;
// feed the cache first
if (cacheSize > 0)
{
filledBytes = cacheSize < _size ? cacheSize : _size;
memcpy(writing, cacheBuffer, cacheSize);
_size -= filledBytes;
writing += filledBytes;
// adjust the cache if it is not fully consumed
cacheSize -= filledBytes;
if (cacheSize > 0)
{
memcpy(cacheBuffer, cacheBuffer + filledBytes, cacheSize);
}
if (_size == 0)
{
return filledBytes;
}
}
// fill the buffer as many as possible
while (_size >= sizeof(wchar_t))
{
vint availableChars = _size / sizeof(wchar_t);
vint readBytes = ReadString((wchar_t*)writing, availableChars) * sizeof(wchar_t);
if (readBytes == 0) break;
filledBytes += readBytes;
_size -= readBytes;
writing += readBytes;
}
// cache the remaining wchar_t
if (_size < sizeof(wchar_t))
{
wchar_t c;
vint readChars = ReadString(&c, 1) * sizeof(wchar_t);
if (readChars == sizeof(wchar_t))
{
vuint8_t* reading = (vuint8_t*)&c;
memcpy(writing, reading, _size);
filledBytes += _size;
cacheSize = sizeof(wchar_t) - _size;
memcpy(cacheBuffer, reading + _size, cacheSize);
}
}
return filledBytes;
}
}
}
/***********************************************************************
.\ENCODING\CHARFORMAT\UTFENCODING.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
namespace vl
{
namespace stream
{
/***********************************************************************
UtfGeneralEncoder
***********************************************************************/
template<typename TNative, typename TExpect>
vint UtfGeneralEncoder<TNative, TExpect>::Write(void* _buffer, vint _size)
{
// prepare a buffer for input
vint availableChars = (cacheSize + _size) / sizeof(TExpect);
vint availableBytes = availableChars * sizeof(TExpect);
bool needToFree = false;
vuint8_t* unicode = nullptr;
if (cacheSize > 0)
{
unicode = new vuint8_t[cacheSize + _size];
memcpy(unicode, cacheBuffer, cacheSize);
memcpy((vuint8_t*)unicode + cacheSize, _buffer, _size);
needToFree = true;
}
else
{
unicode = (vuint8_t*)_buffer;
}
// write the buffer
if (availableChars > 0)
{
TStringRangeReader reader((TExpect*)unicode, availableChars);
while (TNative c = reader.Read())
{
vint written = stream->Write(&c, sizeof(c));
if (written != sizeof(c))
{
Close();
CHECK_FAIL(L"UtfGeneralEncoder<T>::Write(void*, vint)#Failed to write a complete string.");
}
}
auto cluster = reader.SourceCluster();
availableChars = cluster.index + cluster.size;
availableBytes = availableChars * sizeof(TExpect);
}
// cache the remaining
cacheSize = cacheSize + _size - availableBytes;
if (cacheSize > 0)
{
CHECK_ERROR(cacheSize <= sizeof(cacheBuffer), L"UtfGeneralEncoder<T>::Write(void*, vint)#Unwritten text is too large to cache.");
memcpy(cacheBuffer, unicode + availableBytes, cacheSize);
}
if (needToFree) delete[] unicode;
return _size;
}
/***********************************************************************
UtfGeneralDecoder
***********************************************************************/
template<typename TNative, typename TExpect>
void UtfGeneralDecoder<TNative, TExpect>::Setup(IStream* _stream)
{
DecoderBase::Setup(_stream);
reader.Setup(_stream);
}
template<typename TNative, typename TExpect>
vint UtfGeneralDecoder<TNative, TExpect>::Read(void* _buffer, vint _size)
{
vuint8_t* writing = (vuint8_t*)_buffer;
vint filledBytes = 0;
// feed the cache first
if (cacheSize > 0)
{
filledBytes = cacheSize < _size ? cacheSize : _size;
memcpy(writing, cacheBuffer, cacheSize);
_size -= filledBytes;
writing += filledBytes;
// adjust the cache if it is not fully consumed
cacheSize -= filledBytes;
if (cacheSize > 0)
{
memcpy(cacheBuffer, cacheBuffer + filledBytes, cacheSize);
}
if (_size == 0)
{
return filledBytes;
}
}
// fill the buffer as many as possible
while (_size >= sizeof(TExpect))
{
vint availableChars = _size / sizeof(TExpect);
vint readBytes = 0;
for (vint i = 0; i < availableChars; i++)
{
TExpect c = reader.Read();
if (!c) break;
*((TExpect*)writing) = c;
writing += sizeof(TExpect);
readBytes += sizeof(TExpect);
}
if (readBytes == 0) break;
filledBytes += readBytes;
_size -= readBytes;
}
// cache the remaining TExpect
if (_size < sizeof(TExpect))
{
if (TExpect c = reader.Read())
{
vuint8_t* reading = (vuint8_t*)&c;
memcpy(writing, reading, _size);
filledBytes += _size;
cacheSize = sizeof(TExpect) - _size;
memcpy(cacheBuffer, reading + _size, cacheSize);
}
}
return filledBytes;
}
/***********************************************************************
UtfGeneralEncoder<T, T>
***********************************************************************/
template<typename T>
vint UtfGeneralEncoder<T, T>::Write(void* _buffer, vint _size)
{
return stream->Write(_buffer, _size);
}
/***********************************************************************
UtfGeneralDecoder<T, T>
***********************************************************************/
template<typename T>
vint UtfGeneralDecoder<T, T>::Read(void* _buffer, vint _size)
{
return stream->Read(_buffer, _size);
}
/***********************************************************************
Unicode General (extern templates)
***********************************************************************/
template class UtfGeneralEncoder<wchar_t, wchar_t>;
template class UtfGeneralEncoder<wchar_t, char8_t>;
template class UtfGeneralEncoder<wchar_t, char16_t>;
template class UtfGeneralEncoder<wchar_t, char16be_t>;
template class UtfGeneralEncoder<wchar_t, char32_t>;
template class UtfGeneralEncoder<char8_t, wchar_t>;
template class UtfGeneralEncoder<char8_t, char8_t>;
template class UtfGeneralEncoder<char8_t, char16_t>;
template class UtfGeneralEncoder<char8_t, char16be_t>;
template class UtfGeneralEncoder<char8_t, char32_t>;
template class UtfGeneralEncoder<char16_t, wchar_t>;
template class UtfGeneralEncoder<char16_t, char8_t>;
template class UtfGeneralEncoder<char16_t, char16_t>;
template class UtfGeneralEncoder<char16_t, char16be_t>;
template class UtfGeneralEncoder<char16_t, char32_t>;
template class UtfGeneralEncoder<char16be_t, wchar_t>;
template class UtfGeneralEncoder<char16be_t, char8_t>;
template class UtfGeneralEncoder<char16be_t, char16_t>;
template class UtfGeneralEncoder<char16be_t, char16be_t>;
template class UtfGeneralEncoder<char16be_t, char32_t>;
template class UtfGeneralEncoder<char32_t, wchar_t>;
template class UtfGeneralEncoder<char32_t, char8_t>;
template class UtfGeneralEncoder<char32_t, char16_t>;
template class UtfGeneralEncoder<char32_t, char16be_t>;
template class UtfGeneralEncoder<char32_t, char32_t>;
template class UtfGeneralDecoder<wchar_t, wchar_t>;
template class UtfGeneralDecoder<wchar_t, char8_t>;
template class UtfGeneralDecoder<wchar_t, char16_t>;
template class UtfGeneralDecoder<wchar_t, char16be_t>;
template class UtfGeneralDecoder<wchar_t, char32_t>;
template class UtfGeneralDecoder<char8_t, wchar_t>;
template class UtfGeneralDecoder<char8_t, char8_t>;
template class UtfGeneralDecoder<char8_t, char16_t>;
template class UtfGeneralDecoder<char8_t, char16be_t>;
template class UtfGeneralDecoder<char8_t, char32_t>;
template class UtfGeneralDecoder<char16_t, wchar_t>;
template class UtfGeneralDecoder<char16_t, char8_t>;
template class UtfGeneralDecoder<char16_t, char16_t>;
template class UtfGeneralDecoder<char16_t, char16be_t>;
template class UtfGeneralDecoder<char16_t, char32_t>;
template class UtfGeneralDecoder<char16be_t, wchar_t>;
template class UtfGeneralDecoder<char16be_t, char8_t>;
template class UtfGeneralDecoder<char16be_t, char16_t>;
template class UtfGeneralDecoder<char16be_t, char16be_t>;
template class UtfGeneralDecoder<char16be_t, char32_t>;
template class UtfGeneralDecoder<char32_t, wchar_t>;
template class UtfGeneralDecoder<char32_t, char8_t>;
template class UtfGeneralDecoder<char32_t, char16_t>;
template class UtfGeneralDecoder<char32_t, char16be_t>;
template class UtfGeneralDecoder<char32_t, char32_t>;
}
}
/***********************************************************************
.\INTERPROCESS\CHANNELIMPLS\CHANNELPACKAGE.CPP
***********************************************************************/
namespace vl::inter_process
{
NetworkPackage NetworkPackage::Create(Nullable<vint> _clientId, const WString& _channelName, const WString& _messageBody)
{
NetworkPackage package;
package.clientId = std::move(_clientId);
package.channelName = _channelName;
package.messageBody = _messageBody;
return package;
}
NetworkPackage NetworkPackage::Create(Nullable<vint> _clientId, const ClientIdList& _extraClientIds, const WString& _channelName, const WString& _messageBody)
{
auto package = Create(std::move(_clientId), _channelName, _messageBody);
if (_extraClientIds.Count() > 0)
{
ClientIdList extraClientIds;
for (auto clientId : _extraClientIds)
{
extraClientIds.Add(clientId);
}
package.extraClientIds = std::move(extraClientIds);
}
return package;
}
WString NetworkPackage::ToString(const NetworkPackage& package)
{
auto clientIds = package.clientId ? itow(package.clientId.Value()) : WString::Empty;
if (package.extraClientIds)
{
for (auto clientId : package.extraClientIds.Value())
{
clientIds += L",";
clientIds += itow(clientId);
}
}
return clientIds
+ L";" + package.channelName
+ L";" + package.messageBody
;
}
void NetworkPackage::Parse(const WString& str, NetworkPackage& package)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::NetworkPackage::Parse(const WString&, NetworkPackage&)#"
const wchar_t* reading = str.Buffer();
const wchar_t* afterClientId = wcschr(reading, L';');
CHECK_ERROR(afterClientId != nullptr, ERROR_MESSAGE_PREFIX L"Invalid package format.");
package.clientId.Reset();
package.extraClientIds.Reset();
const wchar_t* firstExtraClientId = wcschr(reading, L',');
if (firstExtraClientId && firstExtraClientId > afterClientId)
{
firstExtraClientId = nullptr;
}
auto clientIdLength = firstExtraClientId ? (vint)(firstExtraClientId - reading) : (vint)(afterClientId - reading);
if (clientIdLength > 0)
{
package.clientId = wtoi(str.Left(clientIdLength));
}
if (firstExtraClientId)
{
ClientIdList extraClientIds;
auto readingExtraClientId = firstExtraClientId + 1;
while (readingExtraClientId < afterClientId)
{
auto delimiter = wcschr(readingExtraClientId, L',');
if (delimiter && delimiter > afterClientId)
{
delimiter = nullptr;
}
auto endingExtraClientId = delimiter ? delimiter : afterClientId;
CHECK_ERROR(endingExtraClientId > readingExtraClientId, ERROR_MESSAGE_PREFIX L"Invalid extra client id format.");
extraClientIds.Add(wtoi(WString::CopyFrom(readingExtraClientId, (vint)(endingExtraClientId - readingExtraClientId))));
readingExtraClientId = endingExtraClientId + (delimiter ? 1 : 0);
}
if (extraClientIds.Count() > 0)
{
package.extraClientIds = std::move(extraClientIds);
}
}
const wchar_t* afterChannelName = wcschr(afterClientId + 1, L';');
CHECK_ERROR(afterChannelName != nullptr, ERROR_MESSAGE_PREFIX L"Invalid package format.");
package.channelName = str.Sub((vint)(afterClientId - reading + 1), (vint)(afterChannelName - afterClientId - 1));
package.messageBody = str.Right(str.Length() - (vint)(afterChannelName - reading + 1));
#undef ERROR_MESSAGE_PREFIX
}
}
/***********************************************************************
.\STREAM\ACCESSOR.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#include <string.h>
namespace vl
{
namespace stream
{
using namespace collections;
template<typename T>
struct VCRLF_ {};
template<> struct VCRLF_<wchar_t> { static constexpr const wchar_t* Value = L"\r\n"; };
template<> struct VCRLF_<char8_t> { static constexpr const char8_t* Value = u8"\r\n"; };
template<> struct VCRLF_<char16_t> { static constexpr const char16_t* Value = u"\r\n"; };
template<> struct VCRLF_<char32_t> { static constexpr const char32_t* Value = U"\r\n"; };
template<typename T>
constexpr const T* VCRLF = VCRLF_<T>::Value;
template<typename T>
struct VEMPTYSTR_ {};
template<> struct VEMPTYSTR_<wchar_t> { static constexpr const wchar_t* Value = L""; };
template<> struct VEMPTYSTR_<char8_t> { static constexpr const char8_t* Value = u8""; };
template<> struct VEMPTYSTR_<char16_t> { static constexpr const char16_t* Value = u""; };
template<> struct VEMPTYSTR_<char32_t> { static constexpr const char32_t* Value = U""; };
template<typename T>
constexpr const T* VEMPTYSTR = VEMPTYSTR_<T>::Value;
/***********************************************************************
TextReader_<T>
***********************************************************************/
template<typename T>
ObjectString<T> TextReader_<T>::ReadString(vint length)
{
T* buffer = new T[length + 1];
vint i = 0;
for (; i < length; i++)
{
if ((buffer[i] = ReadChar()) == 0)
{
break;
}
}
buffer[i] = 0;
ObjectString<T> result(buffer);
delete[] buffer;
return result;
}
template<typename T>
ObjectString<T> TextReader_<T>::ReadLine()
{
ObjectString<T> result;
auto buffer = new T[65537];
buffer[0] = 0;
vint i = 0;
while (true)
{
T c = ReadChar();
if (c == L'\n' || c == 0)
{
buffer[i] = 0;
result += buffer;
buffer[0] = 0;
i = 0;
break;
}
else
{
if (i == 65536)
{
buffer[i] = 0;
result += buffer;
buffer[0] = 0;
i = 0;
}
buffer[i++] = c;
}
}
result += buffer;
delete[] buffer;
if (result.Length() > 0 && result[result.Length() - 1] == L'\r')
{
return result.Left(result.Length() - 1);
}
else
{
return result;
}
}
template<typename T>
ObjectString<T> TextReader_<T>::ReadToEnd()
{
ObjectString<T> result;
auto buffer = new T[65537];
buffer[0] = 0;
vint i = 0;
while (true)
{
T c = ReadChar();
if (c == 0)
{
buffer[i] = 0;
result += buffer;
buffer[0] = 0;
i = 0;
break;
}
else
{
if (i == 65536)
{
buffer[i] = 0;
result += buffer;
buffer[0] = 0;
i = 0;
}
buffer[i++] = c;
}
}
result += buffer;
delete[] buffer;
return result;
}
/***********************************************************************
TextWriter_<T>
***********************************************************************/
template<typename T>
void TextWriter_<T>::WriteString(const T* string, vint charCount)
{
while (*string)
{
WriteChar(*string++);
}
}
template<typename T>
void TextWriter_<T>::WriteString(const T* string)
{
vint len = 0;
if constexpr (std::is_same_v<T, char>)
{
len = strlen(string);
}
else if constexpr (std::is_same_v<T, char8_t>)
{
len = strlen((const char*)string);
}
else if constexpr (std::is_same_v<T, wchar_t>)
{
len = wcslen(string);
}
#if defined VCZH_WCHAR_UTF16
else if constexpr (std::is_same_v<T, char16_t>)
{
len = wcslen((const wchar_t*)string);
}
#elif defined VCZH_WCHAR_UTF32
else if constexpr (std::is_same_v<T, char32_t>)
{
len = wcslen((const wchar_t*)string);
}
#endif
else
{
len = ObjectString<T>::Unmanaged(string).Length();
}
WriteString(string, len);
}
template<typename T>
void TextWriter_<T>::WriteString(const ObjectString<T>& string)
{
if (string.Length())
{
WriteString(string.Buffer(), string.Length());
}
}
template<typename T>
void TextWriter_<T>::WriteLine(const T* string, vint charCount)
{
WriteString(string, charCount);
WriteString(VCRLF<T>, 2);
}
template<typename T>
void TextWriter_<T>::WriteLine(const T* string)
{
WriteString(string);
WriteString(VCRLF<T>, 2);
}
template<typename T>
void TextWriter_<T>::WriteLine(const ObjectString<T>& string)
{
WriteString(string);
WriteString(VCRLF<T>, 2);
}
/***********************************************************************
StringReader_<T>
***********************************************************************/
template<typename T>
void StringReader_<T>::PrepareIfLastCallIsReadLine()
{
if (lastCallIsReadLine)
{
lastCallIsReadLine = false;
if (current < string.Length() && string[current] == L'\r') current++;
if (current < string.Length() && string[current] == L'\n') current++;
}
}
template<typename T>
StringReader_<T>::StringReader_(const ObjectString<T>& _string)
: string(_string)
, current(0)
, lastCallIsReadLine(false)
{
}
template<typename T>
bool StringReader_<T>::IsEnd()
{
return current == string.Length();
}
template<typename T>
T StringReader_<T>::ReadChar()
{
PrepareIfLastCallIsReadLine();
if (IsEnd())
{
return 0;
}
else
{
return string[current++];
}
}
template<typename T>
ObjectString<T> StringReader_<T>::ReadString(vint length)
{
PrepareIfLastCallIsReadLine();
if (IsEnd())
{
return VEMPTYSTR<T>;
}
else
{
vint remain = string.Length() - current;
if (length > remain) length = remain;
ObjectString<T> result = string.Sub(current, length);
current += length;
return result;
}
}
template<typename T>
ObjectString<T> StringReader_<T>::ReadLine()
{
PrepareIfLastCallIsReadLine();
if (IsEnd())
{
return VEMPTYSTR<T>;
}
else
{
vint lineEnd = current;
while (lineEnd < string.Length())
{
T c = string[lineEnd];
if (c == L'\r' || c == L'\n') break;
lineEnd++;
}
ObjectString<T> result = string.Sub(current, lineEnd - current);
current = lineEnd;
lastCallIsReadLine = true;
return result;
}
}
template<typename T>
ObjectString<T> StringReader_<T>::ReadToEnd()
{
return ReadString(string.Length() - current);
}
/***********************************************************************
StreamReader_<T>
***********************************************************************/
template<typename T>
StreamReader_<T>::StreamReader_(IStream& _stream)
: stream(&_stream)
{
}
template<typename T>
bool StreamReader_<T>::IsEnd()
{
return stream == nullptr;
}
template<typename T>
T StreamReader_<T>::ReadChar()
{
if (stream)
{
T buffer = 0;
if (stream->Read(&buffer, sizeof(buffer)) == 0)
{
stream = nullptr;
return 0;
}
else
{
return buffer;
}
}
else
{
return 0;
}
}
/***********************************************************************
StreamWriter_<T>
***********************************************************************/
template<typename T>
StreamWriter_<T>::StreamWriter_(IStream& _stream)
:stream(&_stream)
{
}
template<typename T>
void StreamWriter_<T>::WriteChar(T c)
{
stream->Write(&c, sizeof(c));
}
template<typename T>
void StreamWriter_<T>::WriteString(const T* string, vint charCount)
{
stream->Write((void*)string, charCount * sizeof(*string));
}
/***********************************************************************
Extern Templates
***********************************************************************/
template class TextReader_<wchar_t>;
template class TextReader_<char8_t>;
template class TextReader_<char16_t>;
template class TextReader_<char32_t>;
template class TextWriter_<wchar_t>;
template class TextWriter_<char8_t>;
template class TextWriter_<char16_t>;
template class TextWriter_<char32_t>;
template class StringReader_<wchar_t>;
template class StringReader_<char8_t>;
template class StringReader_<char16_t>;
template class StringReader_<char32_t>;
template class StreamReader_<wchar_t>;
template class StreamReader_<char8_t>;
template class StreamReader_<char16_t>;
template class StreamReader_<char32_t>;
template class StreamWriter_<wchar_t>;
template class StreamWriter_<char8_t>;
template class StreamWriter_<char16_t>;
template class StreamWriter_<char32_t>;
/***********************************************************************
Extern Templates
***********************************************************************/
namespace monospace_tabling
{
void WriteBorderLine(TextWriter& writer, Array<vint>& columnWidths, vint columns)
{
writer.WriteChar(L'+');
for (vint i = 0; i < columns; i++)
{
vint c = columnWidths[i];
for (vint j = 0; j < c; j++)
{
writer.WriteChar(L'-');
}
writer.WriteChar(L'+');
}
writer.WriteLine(L"");
}
void WriteContentLine(TextWriter& writer, Array<vint>& columnWidths, vint rowHeight, vint columns, Array<WString>& tableByRow, vint startRow)
{
vint cellStart = startRow * columns;
for (vint r = 0; r < rowHeight; r++)
{
writer.WriteChar(L'|');
for (vint c = 0; c < columns; c++)
{
const wchar_t* cell = tableByRow[cellStart + c].Buffer();
for (vint i = 0; i < r; i++)
{
if (cell) cell = ::wcsstr(cell, L"\r\n");
if (cell) cell += 2;
}
writer.WriteChar(L' ');
vint length = 0;
if (cell)
{
const wchar_t* end = ::wcsstr(cell, L"\r\n");
length = end ? end - cell : (vint)wcslen(cell);
writer.WriteString(cell, length);
}
for (vint i = columnWidths[c] - 2; i >= length; i--)
{
writer.WriteChar(L' ');
}
writer.WriteChar(L'|');
}
writer.WriteLine(L"");
}
}
}
void WriteMonospacedEnglishTable(TextWriter& writer, collections::Array<WString>& tableByRow, vint rows, vint columns)
{
Array<vint> rowHeights(rows);
Array<vint> columnWidths(columns);
for (vint i = 0; i < rows; i++) rowHeights[i] = 0;
for (vint j = 0; j < columns; j++) columnWidths[j] = 0;
for (vint i = 0; i < rows; i++)
{
for (vint j = 0; j < columns; j++)
{
WString text = tableByRow[i * columns + j];
const wchar_t* reading = text.Buffer();
vint width = 0;
vint height = 0;
while (reading)
{
height++;
const wchar_t* crlf = ::wcsstr(reading, L"\r\n");
if (crlf)
{
vint length = crlf - reading + 2;
if (width < length) width = length;
reading = crlf + 2;
}
else
{
vint length = ::wcslen(reading) + 2;
if (width < length) width = length;
reading = 0;
}
}
if (rowHeights[i] < height) rowHeights[i] = height;
if (columnWidths[j] < width) columnWidths[j] = width;
}
}
monospace_tabling::WriteBorderLine(writer, columnWidths, columns);
for (vint i = 0; i < rows; i++)
{
monospace_tabling::WriteContentLine(writer, columnWidths, rowHeights[i], columns, tableByRow, i);
monospace_tabling::WriteBorderLine(writer, columnWidths, columns);
}
}
}
}
/***********************************************************************
.\STREAM\BROADCASTSTREAM.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
namespace vl
{
namespace stream
{
/***********************************************************************
BroadcastStream
***********************************************************************/
BroadcastStream::BroadcastStream()
:closed(false)
,position(0)
{
}
BroadcastStream::~BroadcastStream()
{
}
BroadcastStream::StreamList& BroadcastStream::Targets()
{
return streams;
}
bool BroadcastStream::CanRead()const
{
return false;
}
bool BroadcastStream::CanWrite()const
{
return !closed;
}
bool BroadcastStream::CanSeek()const
{
return false;
}
bool BroadcastStream::CanPeek()const
{
return false;
}
bool BroadcastStream::IsLimited()const
{
return false;
}
bool BroadcastStream::IsAvailable()const
{
return !closed;
}
void BroadcastStream::Close()
{
closed=true;
position=-1;
}
pos_t BroadcastStream::Position()const
{
return position;
}
pos_t BroadcastStream::Size()const
{
return position;
}
void BroadcastStream::Seek(pos_t _size)
{
CHECK_FAIL(L"BroadcastStream::Seek(pos_t)#Operation not supported.");
}
void BroadcastStream::SeekFromBegin(pos_t _size)
{
CHECK_FAIL(L"BroadcastStream::SeekFromBegin(pos_t)#Operation not supported.");
}
void BroadcastStream::SeekFromEnd(pos_t _size)
{
CHECK_FAIL(L"BroadcastStream::SeekFromEnd(pos_t)#Operation not supported.");
}
vint BroadcastStream::Read(void* _buffer, vint _size)
{
CHECK_FAIL(L"BroadcastStream::Read(void*, vint)#Operation not supported.");
}
vint BroadcastStream::Write(void* _buffer, vint _size)
{
// TODO: (enumerable) foreach
for(vint i=0;i<streams.Count();i++)
{
vint written = streams[i]->Write(_buffer, _size);
CHECK_ERROR(written == _size, L"BroadcastStream::Write(void*, vint)#Failed to copy data to the output stream.");
}
position+=_size;
return _size;
}
vint BroadcastStream::Peek(void* _buffer, vint _size)
{
CHECK_FAIL(L"BroadcastStream::Peek(void*, vint)#Operation not supported.");
}
}
}
/***********************************************************************
.\STREAM\CACHESTREAM.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
namespace vl
{
namespace stream
{
/***********************************************************************
CacheStream
***********************************************************************/
void CacheStream::Flush()
{
if(dirtyLength>0)
{
if(target->Position()!=start+dirtyStart)
{
target->SeekFromBegin(start+dirtyStart);
}
target->Write(buffer+dirtyStart, dirtyLength);
}
dirtyStart=0;
dirtyLength=0;
availableLength=0;
}
void CacheStream::Load(pos_t _position)
{
if(target->Position()!=_position)
{
target->SeekFromBegin(_position);
}
start=_position;
if(target->CanRead())
{
availableLength=target->Read(buffer, block);
}
}
vint CacheStream::InternalRead(void* _buffer, vint _size)
{
vint readed=0;
if(position>=start && position<start+availableLength)
{
vint bufferMax=(vint)(start+availableLength-position);
vint min=bufferMax<_size?bufferMax:_size;
memcpy(_buffer, buffer+(position-start), min);
readed+=min;
_buffer=(char*)_buffer+min;
}
if(_size>readed)
{
Flush();
if(_size-readed>=block)
{
if(CanSeek())
{
target->SeekFromBegin(position+readed);
}
vint additional=target->Read(_buffer, _size-readed);
if(additional!=-1)
{
readed+=additional;
}
}
else
{
Load(position+readed);
vint remain=_size-readed;
vint min=availableLength<remain?availableLength:remain;
memcpy(_buffer, buffer, min);
readed+=min;
}
}
return readed;
}
vint CacheStream::InternalWrite(void* _buffer, vint _size)
{
vint written=0;
if(position>=start && position<start+block)
{
vint bufferMax=(vint)(start+block-position);
vint writeLength=bufferMax<_size?bufferMax:_size;
vint writeStart=(vint)(position-start);
memcpy(buffer+writeStart, _buffer, writeLength);
written+=writeLength;
_buffer=(char*)_buffer+writeLength;
if(dirtyLength==0)
{
dirtyStart=writeStart;
dirtyLength=writeLength;
}
else
{
dirtyLength=writeStart+writeLength-dirtyStart;
}
vint availableOffset=writeStart+writeLength-availableLength;
if(availableOffset>0)
{
availableLength+=availableOffset;
}
}
if(_size>written)
{
Flush();
if(_size-written>=block)
{
if(CanSeek())
{
target->SeekFromBegin(position+written);
}
vint additional=target->Write(_buffer, _size-written);
if(additional!=-1)
{
written+=additional;
}
}
else
{
Load(position+written);
dirtyLength=_size-written;
memcpy(buffer, _buffer, dirtyLength);
written+=dirtyLength;
}
}
return written;
}
CacheStream::CacheStream(IStream& _target, vint _block)
:target(&_target)
,block(_block)
,start(0)
,position(0)
,dirtyStart(0)
,dirtyLength(0)
,availableLength(0)
,operatedSize(0)
{
if(block<=0)
{
block=65536;
}
buffer=new char[block];
}
CacheStream::~CacheStream()
{
Close();
}
bool CacheStream::CanRead()const
{
return target!=0 && target->CanRead();
}
bool CacheStream::CanWrite()const
{
return target!=0 && target->CanWrite();
}
bool CacheStream::CanSeek()const
{
return target!=0 && target->CanSeek();
}
bool CacheStream::CanPeek()const
{
return target!=0 && target->CanPeek();
}
bool CacheStream::IsLimited()const
{
return target!=0 && target->IsLimited();
}
bool CacheStream::IsAvailable()const
{
return target!=0 && target->IsAvailable();
}
void CacheStream::Close()
{
Flush();
target=0;
delete[] buffer;
buffer=0;
position=-1;
dirtyStart=0;
dirtyLength=0;
availableLength=0;
operatedSize=-1;
}
pos_t CacheStream::Position()const
{
return position;
}
pos_t CacheStream::Size()const
{
if(target!=0)
{
if(IsLimited())
{
return target->Size();
}
else
{
return operatedSize;
}
}
else
{
return -1;
}
}
void CacheStream::Seek(pos_t _size)
{
SeekFromBegin(position+_size);
}
void CacheStream::SeekFromBegin(pos_t _size)
{
if(CanSeek())
{
if(_size<0)
{
position=0;
}
else if(_size>Size())
{
position=Size();
}
else
{
position=_size;
}
}
}
void CacheStream::SeekFromEnd(pos_t _size)
{
SeekFromBegin(Size()-_size);
}
vint CacheStream::Read(void* _buffer, vint _size)
{
CHECK_ERROR(CanRead(), L"CacheStream::Read(void*, vint)#Stream is closed or operation not supported.");
CHECK_ERROR(_size>=0, L"CacheStream::Read(void*, vint)#Argument size cannot be negative.");
_size=InternalRead(_buffer, _size);
position+=_size;
if(operatedSize<position)
{
operatedSize=position;
}
return _size;
}
vint CacheStream::Write(void* _buffer, vint _size)
{
CHECK_ERROR(CanWrite(), L"CacheStream::Write(void*, vint)#Stream is closed or operation not supported.");
CHECK_ERROR(_size>=0, L"CacheStream::Read(void*, vint)#Argument size cannot be negative.");
if(IsLimited())
{
pos_t size=Size();
if(size!=-1)
{
vint remain=(vint)(size-(position+_size));
if(remain<0)
{
_size-=remain;
}
}
}
_size=InternalWrite(_buffer, _size);
position+=_size;
if(operatedSize<position)
{
operatedSize=position;
}
return _size;
}
vint CacheStream::Peek(void* _buffer, vint _size)
{
CHECK_ERROR(CanPeek(), L"CacheStream::Peek(void*, vint)#Stream is closed or operation not supported.");
CHECK_ERROR(_size>=0, L"CacheStream::Read(void*, vint)#Argument size cannot be negative.");
return InternalRead(_buffer, _size);
}
}
}
/***********************************************************************
.\STREAM\ENCODINGSTREAM.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
namespace vl
{
namespace stream
{
/***********************************************************************
EncoderStream
***********************************************************************/
EncoderStream::EncoderStream(IStream& _stream, IEncoder& _encoder)
:stream(&_stream)
,encoder(&_encoder)
,position(0)
{
encoder->Setup(stream);
}
EncoderStream::~EncoderStream()
{
Close();
}
bool EncoderStream::CanRead()const
{
return false;
}
bool EncoderStream::CanWrite()const
{
return IsAvailable();
}
bool EncoderStream::CanSeek()const
{
return false;
}
bool EncoderStream::CanPeek()const
{
return false;
}
bool EncoderStream::IsLimited()const
{
return stream!=0 && stream->IsLimited();
}
bool EncoderStream::IsAvailable()const
{
return stream!=0 && stream->IsAvailable();
}
void EncoderStream::Close()
{
encoder->Close();
stream=0;
}
pos_t EncoderStream::Position()const
{
return IsAvailable()?position:-1;
}
pos_t EncoderStream::Size()const
{
return -1;
}
void EncoderStream::Seek(pos_t _size)
{
CHECK_FAIL(L"EncoderStream::Seek(pos_t)#Operation not supported.");
}
void EncoderStream::SeekFromBegin(pos_t _size)
{
CHECK_FAIL(L"EncoderStream::SeekFromBegin(pos_t)#Operation not supported.");
}
void EncoderStream::SeekFromEnd(pos_t _size)
{
CHECK_FAIL(L"EncoderStream::SeekFromEnd(pos_t)#Operation not supported.");
}
vint EncoderStream::Read(void* _buffer, vint _size)
{
CHECK_FAIL(L"EncoderStream::Read(void*, vint)#Operation not supported.");
}
vint EncoderStream::Write(void* _buffer, vint _size)
{
vint result=encoder->Write(_buffer, _size);
if(result>=0)
{
position+=result;
}
return result;
}
vint EncoderStream::Peek(void* _buffer, vint _size)
{
CHECK_FAIL(L"EncoderStream::Peek(void*, vint)#Operation not supported.");
}
/***********************************************************************
DecoderStream
***********************************************************************/
DecoderStream::DecoderStream(IStream& _stream, IDecoder& _decoder)
:stream(&_stream)
,decoder(&_decoder)
,position(0)
{
decoder->Setup(stream);
}
DecoderStream::~DecoderStream()
{
Close();
}
bool DecoderStream::CanRead()const
{
return IsAvailable();
}
bool DecoderStream::CanWrite()const
{
return false;
}
bool DecoderStream::CanSeek()const
{
return false;
}
bool DecoderStream::CanPeek()const
{
return false;
}
bool DecoderStream::IsLimited()const
{
return stream!=0 && stream->IsLimited();
}
bool DecoderStream::IsAvailable()const
{
return stream!=0 && stream->IsAvailable();
}
void DecoderStream::Close()
{
decoder->Close();
stream=0;
}
pos_t DecoderStream::Position()const
{
return IsAvailable()?position:-1;
}
pos_t DecoderStream::Size()const
{
return -1;
}
void DecoderStream::Seek(pos_t _size)
{
CHECK_FAIL(L"DecoderStream::Seek(pos_t)#Operation not supported.");
}
void DecoderStream::SeekFromBegin(pos_t _size)
{
CHECK_FAIL(L"DecoderStream::SeekFromBegin(pos_t)#Operation not supported.");
}
void DecoderStream::SeekFromEnd(pos_t _size)
{
CHECK_FAIL(L"DecoderStream::SeekFromEnd(pos_t)#Operation not supported.");
}
vint DecoderStream::Read(void* _buffer, vint _size)
{
vint result=decoder->Read(_buffer, _size);
if(result>=0)
{
position+=result;
}
return result;
}
vint DecoderStream::Write(void* _buffer, vint _size)
{
CHECK_FAIL(L"DecoderStream::Write(void*, vint)#Operation not supported.");
}
vint DecoderStream::Peek(void* _buffer, vint _size)
{
CHECK_FAIL(L"DecoderStream::Peek(void*, vint)#Operation not supported.");
}
}
}
/***********************************************************************
.\STREAM\FILESTREAM.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#if defined VCZH_GCC
#endif
namespace vl
{
namespace filesystem
{
extern IFileSystemImpl* GetFileSystemImpl();
}
namespace stream
{
#if defined VCZH_GCC
void _fseeki64(FILE* file, pos_t offset, int origin)
{
fseek(file, (long)offset, origin);
}
#endif
/***********************************************************************
OSFileStreamImpl
***********************************************************************/
class OSFileStreamImpl : public Object, public virtual IFileStreamImpl
{
private:
WString fileName;
FileStream::AccessRight accessRight;
FILE* file;
public:
OSFileStreamImpl(const WString& _fileName, FileStream::AccessRight _accessRight)
: fileName(_fileName), accessRight(_accessRight), file(nullptr)
{
}
~OSFileStreamImpl()
{
Close();
}
bool Open() override
{
const wchar_t* mode = L"rb";
switch(accessRight)
{
case FileStream::ReadOnly:
mode = L"rb";
break;
case FileStream::WriteOnly:
mode = L"wb";
break;
case FileStream::ReadWrite:
mode = L"w+b";
break;
}
#if defined VCZH_MSVC
if(_wfopen_s(&file, fileName.Buffer(), mode) != 0)
{
file = nullptr;
return false;
}
#elif defined VCZH_GCC
AString fileNameA = wtoa(fileName);
AString modeA = wtoa(mode);
file = fopen(fileNameA.Buffer(), modeA.Buffer());
if(file == nullptr)
{
return false;
}
#endif
return true;
}
void Close() override
{
if(file != nullptr)
{
fclose(file);
file = nullptr;
}
}
pos_t Position() const override
{
if(file != nullptr)
{
#if defined VCZH_MSVC
fpos_t position = 0;
if(fgetpos(file, &position) == 0)
{
return position;
}
#elif defined VCZH_GCC
return (pos_t)ftell(file);
#endif
}
return -1;
}
pos_t Size() const override
{
if(file != nullptr)
{
#if defined VCZH_MSVC
fpos_t position = 0;
if(fgetpos(file, &position) == 0)
{
if(fseek(file, 0, SEEK_END) == 0)
{
pos_t size = Position();
if(fsetpos(file, &position) == 0)
{
return size;
}
}
}
#elif defined VCZH_GCC
long position = ftell(file);
fseek(file, 0, SEEK_END);
long size = ftell(file);
fseek(file, position, SEEK_SET);
return (pos_t)size;
#endif
}
return -1;
}
void Seek(pos_t _size) override
{
if(Position() + _size > Size())
{
_fseeki64(file, 0, SEEK_END);
}
else if(Position() + _size < 0)
{
_fseeki64(file, 0, SEEK_SET);
}
else
{
_fseeki64(file, _size, SEEK_CUR);
}
}
void SeekFromBegin(pos_t _size) override
{
if(_size > Size())
{
_fseeki64(file, 0, SEEK_END);
}
else if(_size < 0)
{
_fseeki64(file, 0, SEEK_SET);
}
else
{
_fseeki64(file, _size, SEEK_SET);
}
}
void SeekFromEnd(pos_t _size) override
{
if(_size < 0)
{
_fseeki64(file, 0, SEEK_END);
}
else if(_size > Size())
{
_fseeki64(file, 0, SEEK_SET);
}
else
{
_fseeki64(file, -_size, SEEK_END);
}
}
vint Read(void* _buffer, vint _size) override
{
CHECK_ERROR(file != nullptr, L"FileStream::Read(pos_t)#Stream is closed, cannot perform this operation.");
CHECK_ERROR(_size >= 0, L"FileStream::Read(void*, vint)#Argument size cannot be negative.");
return fread(_buffer, 1, _size, file);
}
vint Write(void* _buffer, vint _size) override
{
CHECK_ERROR(file != nullptr, L"FileStream::Write(pos_t)#Stream is closed, cannot perform this operation.");
CHECK_ERROR(_size >= 0, L"FileStream::Write(void*, vint)#Argument size cannot be negative.");
return fwrite(_buffer, 1, _size, file);
}
vint Peek(void* _buffer, vint _size) override
{
CHECK_ERROR(file != nullptr, L"FileStream::Peek(pos_t)#Stream is closed, cannot perform this operation.");
CHECK_ERROR(_size >= 0, L"FileStream::Peek(void*, vint)#Argument size cannot be negative.");
#if defined VCZH_MSVC
fpos_t position = 0;
if(fgetpos(file, &position) == 0)
{
size_t count = fread(_buffer, 1, _size, file);
if(fsetpos(file, &position) == 0)
{
return count;
}
}
return -1;
#elif defined VCZH_GCC
long position = ftell(file);
size_t count = fread(_buffer, 1, _size, file);
fseek(file, position, SEEK_SET);
return count;
#endif
}
};
/***********************************************************************
CreateOSFileStreamImpl
***********************************************************************/
Ptr<IFileStreamImpl> CreateOSFileStreamImpl(const WString& fileName, FileStream::AccessRight accessRight)
{
return Ptr(new OSFileStreamImpl(fileName, accessRight));
}
/***********************************************************************
FileStream
***********************************************************************/
FileStream::FileStream(const WString& fileName, AccessRight _accessRight)
: accessRight(_accessRight)
{
impl = filesystem::GetFileSystemImpl()->GetFileStreamImpl(fileName, _accessRight);
if(!impl->Open())
{
impl = nullptr;
}
}
FileStream::~FileStream()
{
Close();
}
bool FileStream::CanRead() const
{
return impl != nullptr && (accessRight == ReadOnly || accessRight == ReadWrite);
}
bool FileStream::CanWrite() const
{
return impl != nullptr && (accessRight == WriteOnly || accessRight == ReadWrite);
}
bool FileStream::CanSeek() const
{
return impl != nullptr;
}
bool FileStream::CanPeek() const
{
return impl != nullptr && (accessRight == ReadOnly || accessRight == ReadWrite);
}
bool FileStream::IsLimited() const
{
return impl != nullptr && accessRight == ReadOnly;
}
bool FileStream::IsAvailable() const
{
return impl != nullptr;
}
void FileStream::Close()
{
if(impl != nullptr)
{
impl->Close();
impl = nullptr;
}
}
pos_t FileStream::Position() const
{
return impl != nullptr ? impl->Position() : -1;
}
pos_t FileStream::Size() const
{
return impl != nullptr ? impl->Size() : -1;
}
void FileStream::Seek(pos_t _size)
{
CHECK_ERROR(impl != nullptr, L"FileStream::Seek(pos_t)#Stream is closed, cannot perform this operation.");
impl->Seek(_size);
}
void FileStream::SeekFromBegin(pos_t _size)
{
CHECK_ERROR(impl != nullptr, L"FileStream::SeekFromBegin(pos_t)#Stream is closed, cannot perform this operation.");
impl->SeekFromBegin(_size);
}
void FileStream::SeekFromEnd(pos_t _size)
{
CHECK_ERROR(impl != nullptr, L"FileStream::SeekFromEnd(pos_t)#Stream is closed, cannot perform this operation.");
impl->SeekFromEnd(_size);
}
vint FileStream::Read(void* _buffer, vint _size)
{
CHECK_ERROR(impl != nullptr, L"FileStream::Read(pos_t)#Stream is closed, cannot perform this operation.");
return impl->Read(_buffer, _size);
}
vint FileStream::Write(void* _buffer, vint _size)
{
CHECK_ERROR(impl != nullptr, L"FileStream::Write(pos_t)#Stream is closed, cannot perform this operation.");
return impl->Write(_buffer, _size);
}
vint FileStream::Peek(void* _buffer, vint _size)
{
CHECK_ERROR(impl != nullptr, L"FileStream::Peek(pos_t)#Stream is closed, cannot perform this operation.");
return impl->Peek(_buffer, _size);
}
}
}
/***********************************************************************
.\STREAM\MEMORYSTREAM.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
namespace vl
{
namespace stream
{
/***********************************************************************
MemoryStream
***********************************************************************/
void MemoryStream::PrepareSpace(vint totalSpace)
{
if(totalSpace>capacity)
{
totalSpace=(totalSpace/block+1)*block;
char* newBuffer=new char[totalSpace];
if(buffer)
{
memcpy(newBuffer, buffer, size);
delete[] buffer;
}
buffer=newBuffer;
capacity=totalSpace;
}
}
MemoryStream::MemoryStream(vint _block)
:block(_block)
,buffer(0)
,size(0)
,position(0)
,capacity(0)
{
if(block<=0)
{
block=65536;
}
}
MemoryStream::~MemoryStream()
{
Close();
}
bool MemoryStream::CanRead()const
{
return block!=0;
}
bool MemoryStream::CanWrite()const
{
return block!=0;
}
bool MemoryStream::CanSeek()const
{
return block!=0;
}
bool MemoryStream::CanPeek()const
{
return block!=0;
}
bool MemoryStream::IsLimited()const
{
return false;
}
bool MemoryStream::IsAvailable()const
{
return block!=0;
}
void MemoryStream::Close()
{
if(buffer)
{
delete[] buffer;
}
block=0;
buffer=0;
size=-1;
position=-1;
capacity=0;
}
pos_t MemoryStream::Position()const
{
return position;
}
pos_t MemoryStream::Size()const
{
return size;
}
void MemoryStream::Seek(pos_t _size)
{
SeekFromBegin(position+_size);
}
void MemoryStream::SeekFromBegin(pos_t _size)
{
CHECK_ERROR(block!=0, L"MemoryStream::SeekFromBegin(pos_t)#Stream is closed, cannot perform this operation.");
vint expected=(vint)_size;
if(expected<0)
{
position=0;
}
else if(expected>=size)
{
position=size;
}
else
{
position=expected;
}
}
void MemoryStream::SeekFromEnd(pos_t _size)
{
SeekFromBegin(size-_size);
}
vint MemoryStream::Read(void* _buffer, vint _size)
{
CHECK_ERROR(block!=0, L"MemoryStream::Read(pos_t)#Stream is closed, cannot perform this operation.");
CHECK_ERROR(_size>=0, L"MemoryStream::Read(void*, vint)#Argument size cannot be negative.");
vint max=size-position;
if(_size>max)
{
_size=max;
}
memmove(_buffer, buffer+position, _size);
position+=_size;
return _size;
}
vint MemoryStream::Write(void* _buffer, vint _size)
{
CHECK_ERROR(block!=0, L"MemoryStream::Write(pos_t)#Stream is closed, cannot perform this operation.");
CHECK_ERROR(_size>=0, L"MemoryStream::Write(void*, vint)#Argument size cannot be negative.");
PrepareSpace(size+_size);
memmove(buffer+position, _buffer, _size);
position+=_size;
if(size<position)
{
size=position;
}
return _size;
}
vint MemoryStream::Peek(void* _buffer, vint _size)
{
CHECK_ERROR(block!=0, L"MemoryStream::Peek(pos_t)#Stream is closed, cannot perform this operation.");
CHECK_ERROR(_size>=0, L"MemoryStream::Peek(void*, vint)#Argument size cannot be negative.");
vint max=size-position;
if(_size>max)
{
_size=max;
}
memmove(_buffer, buffer+position, _size);
return _size;
}
void* MemoryStream::GetInternalBuffer()
{
return buffer;
}
}
}
/***********************************************************************
.\STREAM\MEMORYWRAPPERSTREAM.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
namespace vl
{
namespace stream
{
/***********************************************************************
MemoryWrapperStream
***********************************************************************/
MemoryWrapperStream::MemoryWrapperStream(void* _buffer, vint _size)
:buffer((char*)_buffer)
,size(_size)
,position(0)
{
if(size<=0)
{
buffer=0;
size=0;
}
}
MemoryWrapperStream::~MemoryWrapperStream()
{
}
bool MemoryWrapperStream::CanRead()const
{
return buffer!=0;
}
bool MemoryWrapperStream::CanWrite()const
{
return buffer!=0;
}
bool MemoryWrapperStream::CanSeek()const
{
return buffer!=0;
}
bool MemoryWrapperStream::CanPeek()const
{
return buffer!=0;
}
bool MemoryWrapperStream::IsLimited()const
{
return buffer!=0;
}
bool MemoryWrapperStream::IsAvailable()const
{
return buffer!=0;
}
void MemoryWrapperStream::Close()
{
buffer=0;
size=-1;
position=-1;
}
pos_t MemoryWrapperStream::Position()const
{
return position;
}
pos_t MemoryWrapperStream::Size()const
{
return size;
}
void MemoryWrapperStream::Seek(pos_t _size)
{
SeekFromBegin(position+_size);
}
void MemoryWrapperStream::SeekFromBegin(pos_t _size)
{
CHECK_ERROR(buffer!=0, L"MemoryWrapperStream::SeekFromBegin(pos_t)#Stream is closed, cannot perform this operation.");
vint expected=(vint)_size;
if(expected<0)
{
position=0;
}
else if(expected>=size)
{
position=size;
}
else
{
position=expected;
}
}
void MemoryWrapperStream::SeekFromEnd(pos_t _size)
{
SeekFromBegin(size-_size);
}
vint MemoryWrapperStream::Read(void* _buffer, vint _size)
{
CHECK_ERROR(buffer!=0, L"MemoryWrapperStream::Read(pos_t)#Stream is closed, cannot perform this operation.");
CHECK_ERROR(_size>=0, L"MemoryWrapperStream::Read(void*, vint)#Argument size cannot be negative.");
vint max=size-position;
if(_size>max)
{
_size=max;
}
memmove(_buffer, buffer+position, _size);
position+=_size;
return _size;
}
vint MemoryWrapperStream::Write(void* _buffer, vint _size)
{
CHECK_ERROR(buffer!=0, L"MemoryWrapperStream::Write(pos_t)#Stream is closed, cannot perform this operation.");
CHECK_ERROR(_size>=0, L"MemoryWrapperStream::Write(void*, vint)#Argument size cannot be negative.");
vint max=size-position;
if(_size>max)
{
_size=max;
}
memmove(buffer+position, _buffer, _size);
position+=_size;
return _size;
}
vint MemoryWrapperStream::Peek(void* _buffer, vint _size)
{
CHECK_ERROR(buffer!=0, L"MemoryWrapperStream::Peek(pos_t)#Stream is closed, cannot perform this operation.");
CHECK_ERROR(_size>=0, L"MemoryWrapperStream::Peek(void*, vint)#Argument size cannot be negative.");
vint max=size-position;
if(_size>max)
{
_size=max;
}
memmove(_buffer, buffer+position, _size);
return _size;
}
}
}
/***********************************************************************
.\STREAM\RECORDERSTREAM.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
namespace vl
{
namespace stream
{
/***********************************************************************
RecorderStream
***********************************************************************/
RecorderStream::RecorderStream(IStream& _in, IStream& _out)
:in(&_in)
, out(&_out)
{
}
RecorderStream::~RecorderStream()
{
}
bool RecorderStream::CanRead()const
{
return IsAvailable() && in->CanRead();
}
bool RecorderStream::CanWrite()const
{
return false;
}
bool RecorderStream::CanSeek()const
{
return false;
}
bool RecorderStream::CanPeek()const
{
return false;
}
bool RecorderStream::IsLimited()const
{
return IsAvailable() && in->IsLimited();
}
bool RecorderStream::IsAvailable()const
{
return in != 0 && out != 0 && in->IsAvailable() && out->IsAvailable();
}
void RecorderStream::Close()
{
in = nullptr;
out = nullptr;
}
pos_t RecorderStream::Position()const
{
return IsAvailable() ? in->Position() : -1;
}
pos_t RecorderStream::Size()const
{
return IsAvailable() ? in->Size() : -1;
}
void RecorderStream::Seek(pos_t _size)
{
CHECK_FAIL(L"RecorderStream::Seek(pos_t)#Operation not supported.");
}
void RecorderStream::SeekFromBegin(pos_t _size)
{
CHECK_FAIL(L"RecorderStream::SeekFromBegin(pos_t)#Operation not supported.");
}
void RecorderStream::SeekFromEnd(pos_t _size)
{
CHECK_FAIL(L"RecorderStream::SeekFromEnd(pos_t)#Operation not supported.");
}
vint RecorderStream::Read(void* _buffer, vint _size)
{
_size = in->Read(_buffer, _size);
vint written = out->Write(_buffer, _size);
CHECK_ERROR(written == _size, L"RecorderStream::Read(void*, vint)#Failed to copy data to the output stream.");
return _size;
}
vint RecorderStream::Write(void* _buffer, vint _size)
{
CHECK_FAIL(L"RecorderStream::Write(void*, vint)#Operation not supported.");
}
vint RecorderStream::Peek(void* _buffer, vint _size)
{
CHECK_FAIL(L"RecorderStream::Peek(void*, vint)#Operation not supported.");
}
}
}
/***********************************************************************
.\INTERPROCESS\ASYNCSOCKET\ASYNCSOCKET.CPP
***********************************************************************/
#include <chrono>
#include <cstring>
#include <limits>
namespace vl::inter_process::async_tcp_socket
{
thread_local NetworkProtocolCallbackDomain::CallbackFrame* NetworkProtocolCallbackDomain::currentCallbackFrame = nullptr;
thread_local NetworkProtocolConnection::CallbackFrame* NetworkProtocolConnection::currentCallbackFrame = nullptr;
thread_local NetworkProtocolConnection::SocketCallbackFrame*
NetworkProtocolConnection::currentSocketCallbackFrame = nullptr;
/***********************************************************************
IAsyncSocketCallback
***********************************************************************/
void IAsyncSocketCallback::OnWriteCompleted(Ptr<AsyncSocketBuffer>)
{
}
void IAsyncSocketCallback::OnError(const WString&, bool)
{
}
void IAsyncSocketCallback::OnConnected()
{
}
void IAsyncSocketCallback::OnDisconnected()
{
}
AsyncSocketServerStartException::AsyncSocketServerStartException(AsyncSocketServerStartFailure _failure, const WString& message)
: Exception(message)
, failure(_failure)
{
}
AsyncSocketServerStartFailure AsyncSocketServerStartException::GetFailure()const
{
return failure;
}
void IAsyncSocketServerCallback::OnServerStopped()
{
}
/***********************************************************************
NetworkProtocolCallbackDomain::CallbackFrame
***********************************************************************/
NetworkProtocolCallbackDomain::CallbackFrame::CallbackFrame(Ptr<NetworkProtocolCallbackDomain> _domain)
: domain(_domain)
{
if (domain)
{
previous = currentCallbackFrame;
currentCallbackFrame = this;
CS_LOCK(domain->lockState)
{
domain->activeCallbacks++;
}
}
}
NetworkProtocolCallbackDomain::CallbackFrame::~CallbackFrame()
{
if (domain)
{
currentCallbackFrame = previous;
CS_LOCK(domain->lockState)
{
domain->activeCallbacks--;
domain->cvState.WakeAllPendings();
}
}
}
/***********************************************************************
NetworkProtocolCallbackDomain
***********************************************************************/
vint NetworkProtocolCallbackDomain::CurrentCallbackDepth()
{
vint depth = 0;
for (auto frame = currentCallbackFrame; frame; frame = frame->previous)
{
if (frame->domain.Obj() == this)
{
depth++;
}
}
return depth;
}
void NetworkProtocolCallbackDomain::WaitForCallbacks(vint callbackDepth)
{
CS_LOCK(lockState)
{
while (activeCallbacks > callbackDepth)
{
cvState.SleepWith(lockState);
}
}
}
/***********************************************************************
NetworkProtocolConnectionLifecycle
***********************************************************************/
void NetworkProtocolConnectionLifecycle::TakeRetainedAdapterIfDrained(Ptr<Object>& releasing)
{
if (stopFinished && disconnectFinished && activeCallbacks == 0 && activeSocketCallbacks == 0 && activeSocketCalls == 0)
{
releasing = std::move(retainedAdapter);
}
}
/***********************************************************************
NetworkProtocolConnection::CallbackFrame
***********************************************************************/
NetworkProtocolConnection::CallbackFrame::CallbackFrame(Ptr<Lifecycle> _state)
: state(_state)
, previous(currentCallbackFrame)
, domainFrame(state->callbackDomain)
{
currentCallbackFrame = this;
}
NetworkProtocolConnection::CallbackFrame::~CallbackFrame()
{
currentCallbackFrame = previous;
Ptr<Object> releasing;
CS_LOCK(state->lockState)
{
state->activeCallbacks--;
state->TakeRetainedAdapterIfDrained(releasing);
state->cvState.WakeAllPendings();
}
}
/***********************************************************************
NetworkProtocolConnection::SocketCallbackFrame
***********************************************************************/
NetworkProtocolConnection::SocketCallbackFrame::SocketCallbackFrame(Ptr<Lifecycle> _state)
: state(_state)
, previous(currentSocketCallbackFrame)
{
currentSocketCallbackFrame = this;
CS_LOCK(state->lockState)
{
state->activeSocketCallbacks++;
}
}
NetworkProtocolConnection::SocketCallbackFrame::~SocketCallbackFrame()
{
currentSocketCallbackFrame = previous;
Ptr<Object> releasing;
CS_LOCK(state->lockState)
{
state->activeSocketCallbacks--;
state->TakeRetainedAdapterIfDrained(releasing);
state->cvState.WakeAllPendings();
}
}
/***********************************************************************
NetworkProtocolConnection
***********************************************************************/
vint NetworkProtocolConnection::CurrentCallbackDepth(Ptr<Lifecycle> state)
{
vint depth = 0;
for (auto frame = currentCallbackFrame; frame; frame = frame->previous)
{
if (frame->state.Obj() == state.Obj())
{
depth++;
}
}
return depth;
}
vint NetworkProtocolConnection::CurrentSocketCallbackDepth(Ptr<Lifecycle> state)
{
vint depth = 0;
for (auto frame = currentSocketCallbackFrame; frame; frame = frame->previous)
{
if (frame->state.Obj() == state.Obj())
{
depth++;
}
}
return depth;
}
void NetworkProtocolConnection::FinishSocketCall(Ptr<Lifecycle> state)
{
CS_LOCK(state->lockState)
{
state->activeSocketCalls--;
state->cvState.WakeAllPendings();
}
}
template<typename TCallback>
void NetworkProtocolConnection::InvokeProtocolCallback(Ptr<Lifecycle> state, bool allowTerminal, TCallback&& invoke)
{
INetworkProtocolCallback* installed = nullptr;
auto callbackDepth = CurrentCallbackDepth(state);
state->lockState.Enter();
while (state->callbackInstalling && callbackDepth == 0 && state->callback)
{
state->cvState.SleepWith(state->lockState);
}
if (state->callback && (allowTerminal || (!state->stopStarted && !state->terminal)))
{
installed = state->callback;
state->activeCallbacks++;
}
state->lockState.Leave();
if (installed)
{
CallbackFrame frame(state);
invoke(installed);
}
}
void NetworkProtocolConnection::SubmitWrite(Ptr<Lifecycle> state, IAsyncSocketConnection* connection, Ptr<AsyncSocketBuffer> buffer)
{
try
{
connection->WriteAsync(buffer);
}
catch (...)
{
CS_LOCK(state->lockState)
{
state->queuedWrites.Clear();
state->writePending = false;
state->cvState.WakeAllPendings();
}
FinishSocketCall(state);
throw;
}
FinishSocketCall(state);
}
void NetworkProtocolConnection::NotifyProtocolDisconnected(Ptr<Lifecycle> state)
{
auto callbackDepth = CurrentCallbackDepth(state);
state->lockState.Enter();
if (!state->disconnectedNotified)
{
state->disconnectedNotified = true;
state->terminal = true;
state->queuedWrites.Clear();
state->writePending = false;
state->cvState.WakeAllPendings();
}
if (state->disconnectFinished)
{
state->lockState.Leave();
return;
}
if (state->disconnectDelivering)
{
if (callbackDepth == 0)
{
while (!state->disconnectFinished)
{
state->cvState.SleepWith(state->lockState);
}
}
state->lockState.Leave();
return;
}
if (callbackDepth == 0)
{
while (state->activeCallbacks > 0 && !state->disconnectDelivering && !state->disconnectFinished)
{
state->cvState.SleepWith(state->lockState);
}
if (state->disconnectFinished)
{
state->lockState.Leave();
return;
}
if (state->disconnectDelivering)
{
while (!state->disconnectFinished)
{
state->cvState.SleepWith(state->lockState);
}
state->lockState.Leave();
return;
}
}
state->disconnectDelivering = true;
while (state->activeCallbacks > callbackDepth)
{
state->cvState.SleepWith(state->lockState);
}
state->lockState.Leave();
try
{
InvokeProtocolCallback(state, true, [](INetworkProtocolCallback* installed)
{
installed->OnDisconnected();
});
}
catch (...)
{
Ptr<Object> releasing;
CS_LOCK(state->lockState)
{
state->callback = nullptr;
while (state->activeCallbacks > callbackDepth)
{
state->cvState.SleepWith(state->lockState);
}
state->disconnectFinished = true;
state->TakeRetainedAdapterIfDrained(releasing);
state->cvState.WakeAllPendings();
}
throw;
}
Ptr<Object> releasing;
CS_LOCK(state->lockState)
{
state->callback = nullptr;
while (state->activeCallbacks > callbackDepth)
{
state->cvState.SleepWith(state->lockState);
}
state->disconnectFinished = true;
state->TakeRetainedAdapterIfDrained(releasing);
state->cvState.WakeAllPendings();
}
}
void NetworkProtocolConnection::DetachSocketCallback(Ptr<Lifecycle> state, IAsyncSocketConnection* connection)
{
if (connection)
{
connection->InstallCallback(nullptr);
}
CS_LOCK(state->lockState)
{
if (state->socketConnection == connection)
{
state->socketConnection = nullptr;
}
state->cvState.WakeAllPendings();
}
}
void NetworkProtocolConnection::StopConnection(Ptr<Lifecycle> state, Ptr<Object> retainedAdapter)
{
auto callbackDepth = CurrentCallbackDepth(state);
auto socketCallbackDepth = CurrentSocketCallbackDepth(state);
auto nestedCallback = callbackDepth > 0 || socketCallbackDepth > 0;
IAsyncSocketConnection* connection = nullptr;
bool executeStop = false;
bool nestedFollower = false;
Ptr<Object> releasing;
state->lockState.Enter();
if (retainedAdapter)
{
state->retainedAdapter = retainedAdapter;
}
if (!state->stopStarted)
{
state->stopStarted = true;
if (!nestedCallback && !state->terminal && state->queuedWrites.Count() > 0)
{
state->drainWrites = true;
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(WriteDrainTimeout);
while (state->queuedWrites.Count() > 0 && !state->terminal)
{
auto now = std::chrono::steady_clock::now();
if (now >= deadline)
{
break;
}
auto remaining = std::chrono::ceil<std::chrono::milliseconds>(deadline - now).count();
state->cvState.SleepWithForTime(state->lockState, (vint)remaining);
}
state->drainWrites = false;
}
state->queuedWrites.Clear();
state->writePending = false;
while (state->activeSocketCalls > 0)
{
state->cvState.SleepWith(state->lockState);
}
connection = state->socketConnection;
executeStop = true;
}
else if (nestedCallback)
{
connection = state->socketConnection;
nestedFollower = true;
}
else
{
while (!state->stopFinished)
{
state->cvState.SleepWith(state->lockState);
}
while (state->activeCallbacks > 0 || state->activeSocketCallbacks > 0 || state->activeSocketCalls > 0)
{
state->cvState.SleepWith(state->lockState);
}
state->TakeRetainedAdapterIfDrained(releasing);
state->lockState.Leave();
return;
}
state->lockState.Leave();
if (nestedFollower)
{
if (connection && socketCallbackDepth > 0)
{
connection->Stop();
}
NotifyProtocolDisconnected(state);
return;
}
if (executeStop && connection)
{
connection->Stop();
}
NotifyProtocolDisconnected(state);
CS_LOCK(state->lockState)
{
while (state->activeCallbacks > callbackDepth || state->activeSocketCallbacks > socketCallbackDepth)
{
state->cvState.SleepWith(state->lockState);
}
state->stopFinished = true;
state->TakeRetainedAdapterIfDrained(releasing);
state->cvState.WakeAllPendings();
}
}
void NetworkProtocolConnection::ReportFatalError(Ptr<Lifecycle> state, const WString& error)
{
bool report = false;
CS_LOCK(state->lockState)
{
if (!state->terminal && !state->stopStarted)
{
state->terminal = true;
state->queuedWrites.Clear();
state->writePending = false;
state->cvState.WakeAllPendings();
report = true;
}
}
if (report)
{
try
{
InvokeProtocolCallback(state, true, [&](INetworkProtocolCallback* installed)
{
installed->OnLocalError(error, true);
});
}
catch (...)
{
StopConnection(state);
throw;
}
StopConnection(state);
}
}
void NetworkProtocolConnection::StopWithRetainedAdapter(Ptr<NetworkProtocolConnection> retainedAdapter)
{
StopConnection(lifecycle, retainedAdapter);
}
NetworkProtocolConnection::NetworkProtocolConnection(IAsyncSocketConnection* connection, Ptr<NetworkProtocolCallbackDomain> callbackDomain)
: lifecycle(Ptr(new Lifecycle))
{
CHECK_ERROR(connection, L"NetworkProtocolConnection requires a valid async socket connection.");
lifecycle->socketConnection = connection;
lifecycle->callbackDomain = callbackDomain;
connection->InstallCallback(this);
}
NetworkProtocolConnection::~NetworkProtocolConnection()
{
StopConnection(lifecycle);
}
void NetworkProtocolConnection::InstallCallback(INetworkProtocolCallback* value)
{
auto state = lifecycle;
if (!value)
{
auto callbackDepth = CurrentCallbackDepth(state);
bool uninstallOwner = false;
CS_LOCK(state->lockState)
{
uninstallOwner = state->callback != nullptr;
state->callback = nullptr;
while ((callbackDepth == 0 || uninstallOwner) && state->activeCallbacks > callbackDepth)
{
state->cvState.SleepWith(state->lockState);
}
}
return;
}
bool canInstall = false;
CS_LOCK(state->lockState)
{
if (!state->callback && !state->callbackInstalling && !state->stopStarted && !state->terminal)
{
state->callback = value;
state->callbackInstalling = true;
state->activeCallbacks++;
canInstall = true;
}
}
CHECK_ERROR(canInstall, L"NetworkProtocolConnection::InstallCallback cannot replace a callback or install one on a stopped connection.");
CallbackFrame frame(state);
try
{
value->OnInstalled(this);
}
catch (...)
{
CS_LOCK(state->lockState)
{
if (state->callback == value)
{
state->callback = nullptr;
}
state->callbackInstalling = false;
state->cvState.WakeAllPendings();
}
throw;
}
CS_LOCK(state->lockState)
{
state->callbackInstalling = false;
state->cvState.WakeAllPendings();
}
}
void NetworkProtocolConnection::BeginReadingLoopUnsafe()
{
auto state = lifecycle;
IAsyncSocketConnection* connection = nullptr;
CS_LOCK(state->lockState)
{
if (!state->stopStarted && !state->terminal && state->socketConnection)
{
connection = state->socketConnection;
state->activeSocketCalls++;
}
}
if (!connection)
{
return;
}
try
{
connection->BeginReadingLoopUnsafe();
}
catch (...)
{
FinishSocketCall(state);
throw;
}
FinishSocketCall(state);
}
void NetworkProtocolConnection::SendString(const WString& str)
{
auto state = lifecycle;
auto length = str.Length();
CHECK_ERROR(length >= 0 && length <= (std::numeric_limits<vint32_t>::max)(), L"NetworkProtocolConnection::SendString cannot encode a string longer than vint32_t.");
CHECK_ERROR((size_t)length <= ((size_t)(std::numeric_limits<vint>::max)() - sizeof(vint32_t)) / sizeof(wchar_t), L"NetworkProtocolConnection::SendString frame size overflow.");
auto buffer = Ptr(new AsyncSocketBuffer);
auto characterBytes = (size_t)length * sizeof(wchar_t);
auto frameBytes = sizeof(vint32_t) + characterBytes;
buffer->data.Resize((vint)frameBytes);
auto encodedLength = (vint32_t)length;
std::memcpy(&buffer->data[0], &encodedLength, sizeof(encodedLength));
if (characterBytes > 0)
{
std::memcpy(&buffer->data[sizeof(encodedLength)], str.Buffer(), characterBytes);
}
IAsyncSocketConnection* connection = nullptr;
Ptr<AsyncSocketBuffer> submitting;
CS_LOCK(state->lockState)
{
if (!state->stopStarted && !state->terminal && state->socketConnection)
{
state->queuedWrites.Add(buffer);
if (!state->writePending)
{
state->writePending = true;
connection = state->socketConnection;
submitting = state->queuedWrites[0];
state->activeSocketCalls++;
}
}
}
if (submitting)
{
SubmitWrite(state, connection, submitting);
}
}
void NetworkProtocolConnection::Stop()
{
StopConnection(lifecycle);
}
void NetworkProtocolConnection::OnRead(const vuint8_t* buffer, vint size)
{
auto state = lifecycle;
SocketCallbackFrame socketCallbackFrame(state);
if (!buffer || size <= 0)
{
return;
}
collections::List<WString> completedStrings;
bool malformed = false;
CS_LOCK(state->lockParser)
{
if (state->parserFailed)
{
return;
}
auto reading = buffer;
auto available = size;
while (available > 0)
{
if (state->expectedCharacters == -1)
{
auto required = (vint)sizeof(vint32_t) - state->lengthBytesReceived;
auto copied = required < available ? required : available;
std::memcpy(state->lengthBytes + state->lengthBytesReceived, reading, (size_t)copied);
state->lengthBytesReceived += copied;
reading += copied;
available -= copied;
if (state->lengthBytesReceived < (vint)sizeof(vint32_t))
{
continue;
}
std::memcpy(&state->expectedCharacters, state->lengthBytes, sizeof(state->expectedCharacters));
state->lengthBytesReceived = 0;
if (state->expectedCharacters < 0 || (size_t)state->expectedCharacters > ((size_t)(std::numeric_limits<vint>::max)() - sizeof(vint32_t)) / sizeof(wchar_t))
{
state->parserFailed = true;
malformed = true;
break;
}
state->characterBuffer.Resize((vint)state->expectedCharacters);
state->characterBytesReceived = 0;
if (state->expectedCharacters == 0)
{
completedStrings.Add(WString());
state->expectedCharacters = -1;
}
}
if (state->expectedCharacters >= 0)
{
auto characterBytes = (vint)((size_t)state->expectedCharacters * sizeof(wchar_t));
auto required = characterBytes - state->characterBytesReceived;
auto copied = required < available ? required : available;
std::memcpy((vuint8_t*)&state->characterBuffer[0] + state->characterBytesReceived, reading, (size_t)copied);
state->characterBytesReceived += copied;
reading += copied;
available -= copied;
if (state->characterBytesReceived == characterBytes)
{
completedStrings.Add(WString::CopyFrom(&state->characterBuffer[0], state->expectedCharacters));
state->characterBuffer.Resize(0);
state->characterBytesReceived = 0;
state->expectedCharacters = -1;
}
}
}
}
for (auto&& str : completedStrings)
{
InvokeProtocolCallback(state, false, [&](INetworkProtocolCallback* installed)
{
installed->OnReadString(str);
});
}
if (malformed)
{
ReportFatalError(state, L"The async socket protocol received an invalid string length.");
}
}
void NetworkProtocolConnection::OnWriteCompleted(Ptr<AsyncSocketBuffer> buffer)
{
auto state = lifecycle;
SocketCallbackFrame socketCallbackFrame(state);
IAsyncSocketConnection* connection = nullptr;
Ptr<AsyncSocketBuffer> submitting;
bool mismatched = false;
CS_LOCK(state->lockState)
{
if (state->queuedWrites.Count() == 0)
{
return;
}
if (state->queuedWrites[0].Obj() != buffer.Obj())
{
state->queuedWrites.Clear();
state->writePending = false;
mismatched = true;
state->cvState.WakeAllPendings();
}
else
{
state->queuedWrites.RemoveAt(0);
if ((!state->stopStarted || state->drainWrites) && !state->terminal && state->socketConnection && state->queuedWrites.Count() > 0)
{
connection = state->socketConnection;
submitting = state->queuedWrites[0];
state->activeSocketCalls++;
}
else
{
state->writePending = false;
}
state->cvState.WakeAllPendings();
}
}
CHECK_ERROR(!mismatched, L"NetworkProtocolConnection received a completion for an unexpected async socket buffer.");
if (submitting)
{
SubmitWrite(state, connection, submitting);
}
}
void NetworkProtocolConnection::OnError(const WString& error, bool fatal)
{
auto state = lifecycle;
SocketCallbackFrame socketCallbackFrame(state);
if (fatal)
{
ReportFatalError(state, error);
}
else
{
InvokeProtocolCallback(state, false, [&](INetworkProtocolCallback* installed)
{
installed->OnLocalError(error, false);
});
}
}
void NetworkProtocolConnection::OnConnected()
{
auto state = lifecycle;
SocketCallbackFrame socketCallbackFrame(state);
InvokeProtocolCallback(state, false, [](INetworkProtocolCallback* installed)
{
installed->OnConnected();
});
}
void NetworkProtocolConnection::OnDisconnected()
{
auto state = lifecycle;
SocketCallbackFrame socketCallbackFrame(state);
IAsyncSocketConnection* connection = nullptr;
CS_LOCK(state->lockState)
{
while (state->activeSocketCalls > 0)
{
state->cvState.SleepWith(state->lockState);
}
connection = state->socketConnection;
}
try
{
DetachSocketCallback(state, connection);
}
catch (...)
{
CS_LOCK(state->lockState)
{
if (state->socketConnection == connection)
{
state->socketConnection = nullptr;
}
state->cvState.WakeAllPendings();
}
NotifyProtocolDisconnected(state);
throw;
}
NotifyProtocolDisconnected(state);
}
void NetworkProtocolConnection::OnInstalled(IAsyncSocketConnection* connection)
{
auto state = lifecycle;
SocketCallbackFrame socketCallbackFrame(state);
CHECK_ERROR(connection == state->socketConnection, L"NetworkProtocolConnection was installed on an unexpected async socket connection.");
}
}
/***********************************************************************
.\INTERPROCESS\ASYNCSOCKET\ASYNCSOCKET_HTTPCLIENT.CPP
***********************************************************************/
namespace vl::inter_process::async_tcp_socket
{
using namespace vl::collections;
namespace
{
constexpr vint HttpRequestMaxAttempts = 3;
constexpr vint SendDrainTimeout = 1000;
class SameEndpointClientContractException : public Exception
{
public:
SameEndpointClientContractException(const WString& message)
: Exception(message)
{
}
};
wchar_t FoldServerCharacter(wchar_t c)
{
return L'A' <= c && c <= L'Z' ? c - L'A' + L'a' : c;
}
bool ValidateLoopbackServer(const WString& server)
{
if (server == L"127.0.0.1") return true;
const wchar_t localhost[] = L"localhost";
if (server.Length() != 9) return false;
for (vint i = 0; i < 9; i++)
{
if (FoldServerCharacter(server[i]) != localhost[i]) return false;
}
return true;
}
WString NormalizeUrlPrefix(const WString& urlPrefix)
{
auto normalizedUrlPrefix = urlPrefix;
while (normalizedUrlPrefix.Length() > 0 && normalizedUrlPrefix[normalizedUrlPrefix.Length() - 1] == L'/')
{
normalizedUrlPrefix = normalizedUrlPrefix.Left(normalizedUrlPrefix.Length() - 1);
}
return normalizedUrlPrefix;
}
WString DescribeHttpError(const wchar_t* operation, const windows_http::HttpError& error)
{
return WString::Unmanaged(operation) + L" failed: " + error.message;
}
bool IsResponseNotFoundError(const windows_http::HttpError& error)
{
return error.errorCode == (vuint32_t)SocketHttpClientErrorCode::ResponseNotFound;
}
bool DecodeSuccessfulResponse(
const windows_http::HttpResponse& response,
const wchar_t* operation,
WString& body,
WString& error
)
{
if (response.statusCode != 200)
{
error = WString::Unmanaged(operation) + L" returned status code " + itow(response.statusCode) + L".";
return false;
}
if (response.contentType != HttpNetworkProtocolContentType)
{
error = WString::Unmanaged(operation) + L" did not return the required content type.";
return false;
}
if (!response.TryGetBodyUtf8(body) || (body.Length() > 0 && !IsValidHttpNetworkProtocolMessage(body)))
{
error = WString::Unmanaged(operation) + L" returned malformed UTF-8 or an embedded NUL.";
return false;
}
return true;
}
BEGIN_GLOBAL_STORAGE_CLASS(SocketHttpClientTestHooks)
SpinLock lock;
Func<void()> receiveSubmitted;
Func<void()> fatalReserved;
Func<void()> stopStarted;
INITIALIZE_GLOBAL_STORAGE_CLASS
FINALIZE_GLOBAL_STORAGE_CLASS
SPIN_LOCK(lock)
{
receiveSubmitted = {};
fatalReserved = {};
stopStarted = {};
}
END_GLOBAL_STORAGE_CLASS(SocketHttpClientTestHooks)
void InvokeReceiveSubmittedForTesting()
{
Func<void()> callback;
auto& hooks = GetSocketHttpClientTestHooks();
SPIN_LOCK(hooks.lock)
{
callback = hooks.receiveSubmitted;
}
if (callback)
{
try { callback(); }
catch (...) {}
}
}
void InvokeFatalReservedForTesting()
{
Func<void()> callback;
auto& hooks = GetSocketHttpClientTestHooks();
SPIN_LOCK(hooks.lock)
{
callback = hooks.fatalReserved;
}
if (callback)
{
try { callback(); }
catch (...) {}
}
}
void InvokeStopStartedForTesting()
{
Func<void()> callback;
auto& hooks = GetSocketHttpClientTestHooks();
SPIN_LOCK(hooks.lock)
{
callback = hooks.stopStarted;
}
if (callback)
{
try { callback(); }
catch (...) {}
}
}
}
void SetSocketHttpClientReceiveSubmittedCallbackForTesting(const Func<void()>& callback)
{
auto& hooks = GetSocketHttpClientTestHooks();
SPIN_LOCK(hooks.lock)
{
hooks.receiveSubmitted = callback;
}
}
void ResetSocketHttpClientReceiveSubmittedCallbackForTesting()
{
SetSocketHttpClientReceiveSubmittedCallbackForTesting({});
}
void SetSocketHttpClientFatalStopCallbacksForTesting(
const Func<void()>& fatalReserved,
const Func<void()>& stopStarted
)
{
auto& hooks = GetSocketHttpClientTestHooks();
SPIN_LOCK(hooks.lock)
{
hooks.fatalReserved = fatalReserved;
hooks.stopStarted = stopStarted;
}
}
void ResetSocketHttpClientFatalStopCallbacksForTesting()
{
SetSocketHttpClientFatalStopCallbacksForTesting({}, {});
}
/***********************************************************************
SocketHttpClient::Impl
***********************************************************************/
class SocketHttpClient::Impl : public Object
{
using QueryResult = Variant<windows_http::HttpResponse, windows_http::HttpError>;
enum class State
{
Ready,
WaitingForServer,
Connected,
Stopping,
};
class SendItem : public Object
{
public:
Array<char> body;
vint attempt = 1;
};
class QueryWaiter : public Object
{
private:
CriticalSection lock;
ConditionVariable cv;
Ptr<QueryResult> result;
public:
void Complete(QueryResult value)
{
CS_LOCK(lock)
{
if (!result)
{
result = Ptr(new QueryResult(std::move(value)));
cv.WakeAllPendings();
}
}
}
Ptr<QueryResult> Wait()
{
CS_LOCK(lock)
{
while (!result) cv.SleepWith(lock);
return result;
}
return nullptr;
}
};
struct CallbackFrame
{
Ptr<Impl> state;
CallbackFrame* previous = nullptr;
NetworkProtocolCallbackDomain::CallbackFrame
domainFrame;
CallbackFrame(Ptr<Impl> _state)
: state(_state)
, previous(currentCallbackFrame)
, domainFrame(state->callbackDomain)
{
currentCallbackFrame = this;
}
~CallbackFrame()
{
currentCallbackFrame = previous;
CS_LOCK(state->lockState)
{
state->activeCallbacks--;
state->cvState.WakeAllPendings();
}
}
};
struct WorkerFrame
{
Ptr<Impl> state;
WorkerFrame* previous = nullptr;
WorkerFrame(Ptr<Impl> _state)
: state(_state)
, previous(currentWorkerFrame)
{
currentWorkerFrame = this;
}
~WorkerFrame()
{
currentWorkerFrame = previous;
CS_LOCK(state->lockState)
{
state->activeWorkers--;
if (state->hardStopping)
{
state->sendReconnecting = false;
state->receiveReconnecting = false;
}
state->cvState.WakeAllPendings();
}
}
};
struct WaitFrame
{
Ptr<Impl> state;
WaitFrame* previous = nullptr;
WaitFrame(Ptr<Impl> _state)
: state(_state)
, previous(currentWaitFrame)
{
currentWaitFrame = this;
CS_LOCK(state->lockState)
{
state->activeWaits++;
}
}
~WaitFrame()
{
currentWaitFrame = previous;
CS_LOCK(state->lockState)
{
state->activeWaits--;
state->cvState.WakeAllPendings();
}
}
};
static thread_local CallbackFrame* currentCallbackFrame;
static thread_local WorkerFrame* currentWorkerFrame;
static thread_local WaitFrame* currentWaitFrame;
SocketHttpClient* owner = nullptr;
WString server;
vint port = 0;
Ptr<IAsyncSocketClient> clientSource;
Ptr<IAsyncSocketClient> initialClient;
WString urlPrefix;
WString urlConnect;
WString urlRequest;
WString urlResponse;
Ptr<Impl> selfReference;
Ptr<NetworkProtocolCallbackDomain> callbackDomain = Ptr(new NetworkProtocolCallbackDomain);
CriticalSection lockState;
CriticalSection lockClientCreation;
ConditionVariable cvState;
State state = State::Ready;
INetworkProtocolCallback* callback = nullptr;
bool callbackInstalling = false;
vint activeCallbacks = 0;
vint activeWorkers = 0;
vint activeWaits = 0;
bool readingStarted = false;
bool receivePollActive = false;
bool receiveReconnecting = false;
bool sendActive = false;
bool sendReconnecting = false;
bool stopStarted = false;
bool drainSends = false;
bool hardStopping = false;
bool stopFinished = false;
bool fatalStarted = false;
bool disconnectedNotified = false;
bool disconnectDelivering = false;
bool disconnectFinished = false;
Ptr<SocketHttpClientApi> sendApi;
Ptr<SocketHttpClientApi> receiveApi;
List<Ptr<SendItem>> sendQueue;
Ptr<Impl> RetainSelf()
{
CS_LOCK(lockState)
{
return selfReference;
}
return nullptr;
}
vint CurrentCallbackDepth()
{
vint depth = 0;
for (auto frame = currentCallbackFrame; frame; frame = frame->previous)
{
if (frame->state.Obj() == this) depth++;
}
return depth;
}
vint CurrentWorkerDepth()
{
vint depth = 0;
for (auto frame = currentWorkerFrame; frame; frame = frame->previous)
{
if (frame->state.Obj() == this) depth++;
}
return depth;
}
vint CurrentWaitDepth()
{
vint depth = 0;
for (auto frame = currentWaitFrame; frame; frame = frame->previous)
{
if (frame->state.Obj() == this) depth++;
}
return depth;
}
template<typename TCallback>
void InvokeProtocolCallback(bool allowStopping, TCallback&& invoke)
{
INetworkProtocolCallback* installed = nullptr;
auto callbackDepth = CurrentCallbackDepth();
lockState.Enter();
while (callbackInstalling && callbackDepth == 0 && callback)
{
cvState.SleepWith(lockState);
}
if (callback && (allowStopping || !stopStarted))
{
installed = callback;
activeCallbacks++;
}
lockState.Leave();
if (installed)
{
CallbackFrame frame(RetainSelf());
invoke(installed);
}
}
bool IsStopped()
{
CS_LOCK(lockState)
{
return stopStarted;
}
return true;
}
bool CanReceiveUnsafe()
{
return state == State::Connected && readingStarted && !stopStarted && !hardStopping;
}
bool CanSendUnsafe()
{
return !hardStopping && (state == State::Connected || drainSends);
}
void StopApiNoThrow(Ptr<SocketHttpClientApi> api)
{
if (!api) return;
try
{
api->Stop();
}
catch (...)
{
}
}
Ptr<SocketHttpClientApi> CreateApi()
{
Ptr<IAsyncSocketClient> nativeClient;
bool sameEndpointClient = false;
try
{
CS_LOCK(lockClientCreation)
{
nativeClient = initialClient;
initialClient = nullptr;
if (!nativeClient)
{
sameEndpointClient = true;
nativeClient = clientSource->CreateSameEndpointClient();
}
}
if (!nativeClient)
{
throw SameEndpointClientContractException(L"IAsyncSocketClient::CreateSameEndpointClient returned null.");
}
if (sameEndpointClient && nativeClient.Obj() == clientSource.Obj())
{
throw SameEndpointClientContractException(L"IAsyncSocketClient::CreateSameEndpointClient returned the source client instead of a fresh client.");
}
if (nativeClient->GetPort() != port)
{
throw SameEndpointClientContractException(L"IAsyncSocketClient::CreateSameEndpointClient returned a client for a different port.");
}
if (nativeClient->GetStatus() != ClientStatus::Ready)
{
throw SameEndpointClientContractException(L"IAsyncSocketClient::CreateSameEndpointClient returned a client that is not ready.");
}
return Ptr(new SocketHttpClientApi(nativeClient, server));
}
catch (const SameEndpointClientContractException&)
{
throw;
}
catch (const Exception& exception)
{
throw SameEndpointClientContractException(L"IAsyncSocketClient::CreateSameEndpointClient failed: " + exception.Message());
}
catch (...)
{
throw SameEndpointClientContractException(L"IAsyncSocketClient::CreateSameEndpointClient failed.");
}
}
bool WaitApiForServer(Ptr<SocketHttpClientApi> api)
{
api->WaitForServer();
return api->GetStatus() == ClientStatus::Connected;
}
void ReportLocalError(const WString& error, bool fatal)
{
InvokeProtocolCallback(fatal, [&](INetworkProtocolCallback* installed)
{
installed->OnLocalError(error, fatal);
});
}
void NotifyDisconnected()
{
auto callbackDepth = CurrentCallbackDepth();
lockState.Enter();
if (!disconnectedNotified)
{
disconnectedNotified = true;
cvState.WakeAllPendings();
}
if (disconnectFinished)
{
lockState.Leave();
return;
}
if (disconnectDelivering)
{
if (callbackDepth == 0)
{
while (!disconnectFinished) cvState.SleepWith(lockState);
}
lockState.Leave();
return;
}
if (callbackDepth == 0)
{
while (activeCallbacks > 0 && !disconnectDelivering && !disconnectFinished)
{
cvState.SleepWith(lockState);
}
if (disconnectFinished)
{
lockState.Leave();
return;
}
}
disconnectDelivering = true;
while (activeCallbacks > callbackDepth) cvState.SleepWith(lockState);
lockState.Leave();
try
{
InvokeProtocolCallback(true, [](INetworkProtocolCallback* installed)
{
installed->OnDisconnected();
});
}
catch (...)
{
CS_LOCK(lockState)
{
callback = nullptr;
disconnectFinished = true;
cvState.WakeAllPendings();
}
throw;
}
CS_LOCK(lockState)
{
callback = nullptr;
disconnectFinished = true;
cvState.WakeAllPendings();
}
}
void ReportFatalError(const WString& error)
{
INetworkProtocolCallback* installed = nullptr;
auto callbackDepth = CurrentCallbackDepth();
bool report = false;
lockState.Enter();
while (callbackInstalling && callbackDepth == 0 && callback && !stopStarted)
{
cvState.SleepWith(lockState);
}
if (!fatalStarted && !stopStarted)
{
fatalStarted = true;
report = true;
if (callback)
{
installed = callback;
activeCallbacks++;
}
}
lockState.Leave();
if (!report) return;
InvokeFatalReservedForTesting();
try
{
if (installed)
{
CallbackFrame frame(RetainSelf());
installed->OnLocalError(error, true);
}
}
catch (...)
{
Stop(true);
throw;
}
Stop(true);
}
bool HandleConnectFailure(WString error, vint& attempt)
{
if (IsStopped()) return false;
if (attempt >= HttpRequestMaxAttempts)
{
ReportFatalError(error);
return false;
}
ReportLocalError(error, false);
attempt++;
return !IsStopped();
}
Ptr<QueryResult> QueryConnect(Ptr<SocketHttpClientApi> api)
{
auto request = CreateHttpNetworkProtocolConnectRequest(urlConnect);
auto waiter = Ptr(new QueryWaiter);
api->HttpQuery(request, [waiter](QueryResult result)
{
waiter->Complete(std::move(result));
});
return waiter->Wait();
}
bool ValidateConnectResponse(
const windows_http::HttpResponse& response,
WString& requestUrl,
WString& responseUrl,
WString& error
)
{
WString body;
if (!DecodeSuccessfulResponse(response, L"/Connect", body, error)) return false;
if (body.Length() == 0)
{
error = L"/Connect returned an empty body.";
return false;
}
WString requestPath;
WString responsePath;
if (!ParseHttpNetworkProtocolConnectBody(body, requestPath, responsePath))
{
error = L"/Connect did not return exactly two paths.";
return false;
}
if (!ValidateHttpNetworkProtocolEndpointPath(requestPath) || !ValidateHttpNetworkProtocolEndpointPath(responsePath))
{
error = L"/Connect returned an illegal path.";
return false;
}
auto requestTarget = urlPrefix + requestPath;
auto responseTarget = urlPrefix + responsePath;
if (
ValidateHttpRequestLine(L"POST", requestTarget) != HttpRequestLineValidationResult::Succeeded ||
ValidateHttpRequestLine(L"POST", responseTarget) != HttpRequestLineValidationResult::Succeeded
)
{
error = L"/Connect returned a path exceeding the HTTP request-target contract.";
return false;
}
requestUrl = std::move(requestTarget);
responseUrl = std::move(responseTarget);
return true;
}
bool PublishApi(Ptr<SocketHttpClientApi> api, bool receive)
{
CS_LOCK(lockState)
{
if (stopStarted || hardStopping) return false;
if (receive)
{
receiveApi = api;
}
else
{
sendApi = api;
}
return true;
}
return false;
}
void ClearApi(Ptr<SocketHttpClientApi> api, bool receive)
{
CS_LOCK(lockState)
{
if (receive)
{
if (receiveApi == api) receiveApi = nullptr;
}
else
{
if (sendApi == api) sendApi = nullptr;
}
cvState.WakeAllPendings();
}
}
void SubmitReceivePoll(Ptr<SocketHttpClientApi> api)
{
auto self = RetainSelf();
if (!self) return;
auto request = CreateHttpNetworkProtocolReceiveRequest(urlRequest);
request.receiveTimeout = 0;
try
{
api->HttpQuery(request, [self, api](QueryResult result)
{
self->OnReceiveCompleted(api, std::move(result));
});
InvokeReceiveSubmittedForTesting();
}
catch (...)
{
HandleReceiveTransportFailure(api);
}
}
bool ReserveReceivePoll(Ptr<SocketHttpClientApi> api)
{
CS_LOCK(lockState)
{
if (!CanReceiveUnsafe() || receiveReconnecting || receivePollActive || receiveApi != api) return false;
receivePollActive = true;
return true;
}
return false;
}
void HandleReceiveTransportFailure(Ptr<SocketHttpClientApi> api)
{
bool schedule = false;
CS_LOCK(lockState)
{
if (receiveApi == api && CanReceiveUnsafe() && !receiveReconnecting)
{
receivePollActive = false;
receiveReconnecting = true;
activeWorkers++;
schedule = true;
}
}
if (!schedule) return;
auto self = RetainSelf();
bool queued = false;
try
{
auto worker = Func<void()>([self, api]()
{
WorkerFrame frame(self);
try
{
self->RunReceiveReplacement(api);
}
catch (...)
{
try
{
self->ReportFatalError(L"/Request replacement worker failed unexpectedly.");
}
catch (...)
{
self->Stop();
}
}
});
if (self)
{
try
{
queued = ThreadPoolLite::Queue(worker);
}
catch (...)
{
}
if (!queued)
{
try
{
queued = Thread::CreateAndStart(worker) != nullptr;
}
catch (...)
{
}
}
}
}
catch (...)
{
}
if (!queued)
{
CS_LOCK(lockState)
{
activeWorkers--;
receiveReconnecting = false;
cvState.WakeAllPendings();
}
ReportFatalError(L"/Request could not queue its replacement worker.");
}
}
void RunReceiveReplacement(Ptr<SocketHttpClientApi> deadApi)
{
StopApiNoThrow(deadApi);
ClearApi(deadApi, true);
while (true)
{
CS_LOCK(lockState)
{
if (!CanReceiveUnsafe() || !receiveReconnecting) return;
}
Ptr<SocketHttpClientApi> api;
try
{
api = CreateApi();
}
catch (const SameEndpointClientContractException& exception)
{
ReportFatalError(exception.Message());
return;
}
catch (...)
{
ReportFatalError(L"IAsyncSocketClient::CreateSameEndpointClient failed.");
return;
}
if (!PublishApi(api, true))
{
StopApiNoThrow(api);
return;
}
bool connected = false;
try
{
connected = WaitApiForServer(api);
}
catch (...)
{
}
if (!connected)
{
StopApiNoThrow(api);
ClearApi(api, true);
continue;
}
bool submit = false;
CS_LOCK(lockState)
{
if (receiveApi == api && CanReceiveUnsafe() && receiveReconnecting)
{
receiveReconnecting = false;
receivePollActive = true;
submit = true;
}
}
if (!submit)
{
StopApiNoThrow(api);
return;
}
SubmitReceivePoll(api);
return;
}
}
void OnReceiveCompleted(Ptr<SocketHttpClientApi> api, QueryResult result)
{
bool current = false;
CS_LOCK(lockState)
{
if (receiveApi == api && receivePollActive)
{
receivePollActive = false;
current = true;
}
}
if (!current) return;
if (auto httpError = result.TryGet<windows_http::HttpError>())
{
if (IsResponseNotFoundError(*httpError))
{
ReportFatalError(DescribeHttpError(L"/Request", *httpError));
return;
}
HandleReceiveTransportFailure(api);
return;
}
WString body;
WString error;
auto valid = DecodeSuccessfulResponse(result.Get<windows_http::HttpResponse>(), L"/Request", body, error);
if (ReserveReceivePoll(api))
{
// SocketHttpClientApi starts this replacement from inside its response
// callback before any user callback below can create a receive gap.
SubmitReceivePoll(api);
}
if (valid && body.Length() > 0)
{
InvokeProtocolCallback(false, [&](INetworkProtocolCallback* installed)
{
installed->OnReadString(body);
});
}
}
bool IsCurrentSendUnsafe(Ptr<SocketHttpClientApi> api, Ptr<SendItem> item)
{
return sendQueue.Count() > 0 && sendQueue[0] == item && sendApi == api;
}
void SubmitSend(Ptr<SocketHttpClientApi> api, Ptr<SendItem> item)
{
bool submit = false;
CS_LOCK(lockState)
{
submit = CanSendUnsafe() && sendActive && IsCurrentSendUnsafe(api, item);
}
if (!submit) return;
auto request = CreateHttpNetworkProtocolSendRequest(urlResponse, item->body);
auto self = RetainSelf();
try
{
api->HttpQuery(request, [self, api, item](QueryResult result)
{
self->OnSendCompleted(api, item, std::move(result));
});
}
catch (...)
{
HandleSendFailure(api, item, L"/Response could not submit the HTTP exchange.", true);
}
}
void QueueSendReplacement(Ptr<SocketHttpClientApi> deadApi, Ptr<SendItem> item)
{
bool schedule = false;
CS_LOCK(lockState)
{
if (CanSendUnsafe() && sendReconnecting && IsCurrentSendUnsafe(deadApi, item))
{
activeWorkers++;
schedule = true;
}
else
{
sendReconnecting = false;
}
}
if (!schedule) return;
auto self = RetainSelf();
bool queued = false;
try
{
auto worker = Func<void()>([self, deadApi, item]()
{
WorkerFrame frame(self);
try
{
self->RunSendReplacement(deadApi, item);
}
catch (...)
{
try
{
self->ReportFatalError(L"/Response replacement worker failed unexpectedly.");
}
catch (...)
{
self->Stop();
}
}
});
if (self)
{
try
{
queued = ThreadPoolLite::Queue(worker);
}
catch (...)
{
}
if (!queued)
{
try
{
queued = Thread::CreateAndStart(worker) != nullptr;
}
catch (...)
{
}
}
}
}
catch (...)
{
}
if (!queued)
{
CS_LOCK(lockState)
{
activeWorkers--;
sendReconnecting = false;
cvState.WakeAllPendings();
}
ReportFatalError(L"/Response could not queue its replacement worker.");
}
}
bool HandleSendPreparationFailure(Ptr<SendItem> item, const WString& error)
{
bool fatal = false;
CS_LOCK(lockState)
{
if (!CanSendUnsafe() || !sendReconnecting || sendQueue.Count() == 0 || sendQueue[0] != item) return false;
fatal = item->attempt >= HttpRequestMaxAttempts;
if (!fatal) item->attempt++;
}
if (fatal)
{
ReportFatalError(error);
return false;
}
ReportLocalError(error, false);
CS_LOCK(lockState)
{
return CanSendUnsafe() && sendReconnecting && sendQueue.Count() > 0 && sendQueue[0] == item;
}
return false;
}
void RunSendReplacement(Ptr<SocketHttpClientApi> deadApi, Ptr<SendItem> item)
{
StopApiNoThrow(deadApi);
ClearApi(deadApi, false);
while (true)
{
CS_LOCK(lockState)
{
if (!CanSendUnsafe() || !sendReconnecting || sendQueue.Count() == 0 || sendQueue[0] != item) return;
}
Ptr<SocketHttpClientApi> api;
try
{
api = CreateApi();
}
catch (const SameEndpointClientContractException& exception)
{
ReportFatalError(exception.Message());
return;
}
catch (...)
{
ReportFatalError(L"IAsyncSocketClient::CreateSameEndpointClient failed.");
return;
}
if (!PublishApi(api, false))
{
StopApiNoThrow(api);
return;
}
bool connected = false;
try
{
connected = WaitApiForServer(api);
}
catch (...)
{
}
if (!connected)
{
StopApiNoThrow(api);
ClearApi(api, false);
if (!HandleSendPreparationFailure(item, L"/Response replacement failed to connect.")) return;
continue;
}
bool submit = false;
CS_LOCK(lockState)
{
if (sendApi == api && CanSendUnsafe() && sendReconnecting && sendQueue.Count() > 0 && sendQueue[0] == item)
{
sendReconnecting = false;
sendActive = true;
submit = true;
}
}
if (!submit)
{
StopApiNoThrow(api);
return;
}
SubmitSend(api, item);
return;
}
}
void HandleSendFailure(Ptr<SocketHttpClientApi> api, Ptr<SendItem> item, WString error, bool transportFailure)
{
bool fatal = false;
bool retryHealthy = false;
bool replace = false;
CS_LOCK(lockState)
{
if (!sendActive || !CanSendUnsafe() || !IsCurrentSendUnsafe(api, item)) return;
sendActive = false;
fatal = item->attempt >= HttpRequestMaxAttempts;
if (!fatal)
{
item->attempt++;
if (transportFailure)
{
sendReconnecting = true;
replace = true;
}
else
{
sendActive = true;
retryHealthy = true;
}
}
cvState.WakeAllPendings();
}
if (fatal)
{
ReportFatalError(error);
return;
}
try
{
ReportLocalError(error, false);
}
catch (...)
{
if (replace) QueueSendReplacement(api, item);
if (retryHealthy) SubmitSend(api, item);
throw;
}
if (replace) QueueSendReplacement(api, item);
if (retryHealthy) SubmitSend(api, item);
}
void OnSendCompleted(Ptr<SocketHttpClientApi> api, Ptr<SendItem> item, QueryResult result)
{
if (auto httpError = result.TryGet<windows_http::HttpError>())
{
if (IsResponseNotFoundError(*httpError))
{
ReportFatalError(DescribeHttpError(L"/Response", *httpError));
return;
}
HandleSendFailure(api, item, DescribeHttpError(L"/Response", *httpError), true);
return;
}
WString body;
WString error;
if (!DecodeSuccessfulResponse(result.Get<windows_http::HttpResponse>(), L"/Response", body, error))
{
HandleSendFailure(api, item, error, false);
return;
}
Ptr<SendItem> next;
CS_LOCK(lockState)
{
if (!sendActive || !IsCurrentSendUnsafe(api, item)) return;
sendActive = false;
sendQueue.RemoveAt(0);
if (CanSendUnsafe() && sendQueue.Count() > 0)
{
next = sendQueue[0];
sendActive = true;
}
cvState.WakeAllPendings();
}
// Preserve FIFO ownership by submitting the next accepted send before
// delivering a piggybacked message to callback-reentrant application code.
if (next) SubmitSend(api, next);
if (body.Length() > 0)
{
InvokeProtocolCallback(false, [&](INetworkProtocolCallback* installed)
{
installed->OnReadString(body);
});
}
}
public:
Impl(
SocketHttpClient* _owner,
Ptr<IAsyncSocketClient> _initialClient,
const WString& _server,
const WString& _urlPrefix
)
: owner(_owner)
, server(_server)
, clientSource(_initialClient)
, initialClient(_initialClient)
, urlPrefix(NormalizeUrlPrefix(_urlPrefix))
, urlConnect(urlPrefix + HttpServerUrl_Connect)
{
CHECK_ERROR(owner, L"SocketHttpClient requires an owning adapter.");
CHECK_ERROR(initialClient, L"SocketHttpClient requires an initial native client.");
CHECK_ERROR(ValidateLoopbackServer(server), L"SocketHttpClient requires an explicit loopback server.");
port = initialClient->GetPort();
CHECK_ERROR(1 <= port && port <= 65535, L"SocketHttpClient requires the initial client port to be in 1..65535.");
CHECK_ERROR(initialClient->GetStatus() == ClientStatus::Ready, L"SocketHttpClient requires an initial client in the ready state.");
CHECK_ERROR(ValidateHttpNetworkProtocolBaseUrl(urlPrefix), L"SocketHttpClient requires an empty or legal origin-form URL prefix.");
CHECK_ERROR(ValidateHttpRequestLine(L"GET", urlConnect) == HttpRequestLineValidationResult::Succeeded, L"SocketHttpClient URL prefix makes /Connect exceed the HTTP request-line limit.");
}
void Initialize(Ptr<Impl> self)
{
CS_LOCK(lockState)
{
selfReference = self;
}
}
void ReleaseSelf()
{
CS_LOCK(lockState)
{
owner = nullptr;
selfReference = nullptr;
}
}
INetworkProtocolConnection* GetConnection()
{
return owner;
}
void WaitForServer()
{
auto self = RetainSelf();
CS_LOCK(lockState)
{
CHECK_ERROR(state == State::Ready && !stopStarted, L"SocketHttpClient::WaitForServer can only be called once.");
state = State::WaitingForServer;
}
WaitFrame waitFrame(self);
vint attempt = 1;
Ptr<SocketHttpClientApi> api;
while (!IsStopped())
{
if (!api)
{
try
{
api = CreateApi();
}
catch (const SameEndpointClientContractException& exception)
{
ReportFatalError(exception.Message());
return;
}
catch (...)
{
ReportFatalError(L"IAsyncSocketClient::CreateSameEndpointClient failed.");
return;
}
if (!PublishApi(api, false))
{
StopApiNoThrow(api);
return;
}
bool connected = false;
try
{
connected = WaitApiForServer(api);
}
catch (...)
{
}
if (!connected)
{
StopApiNoThrow(api);
ClearApi(api, false);
api = nullptr;
if (!HandleConnectFailure(L"/Connect native connection failed.", attempt)) return;
continue;
}
}
Ptr<QueryResult> result;
try
{
result = QueryConnect(api);
}
catch (...)
{
StopApiNoThrow(api);
ClearApi(api, false);
api = nullptr;
if (!HandleConnectFailure(L"/Connect could not submit its HTTP exchange.", attempt)) return;
continue;
}
if (IsStopped()) return;
if (auto httpError = result->TryGet<windows_http::HttpError>())
{
auto error = DescribeHttpError(L"/Connect", *httpError);
if (IsResponseNotFoundError(*httpError))
{
ReportFatalError(error);
return;
}
StopApiNoThrow(api);
ClearApi(api, false);
api = nullptr;
if (!HandleConnectFailure(error, attempt)) return;
continue;
}
WString requestUrl;
WString responseUrl;
WString error;
if (!ValidateConnectResponse(result->Get<windows_http::HttpResponse>(), requestUrl, responseUrl, error))
{
if (!HandleConnectFailure(error, attempt)) return;
continue;
}
CS_LOCK(lockState)
{
if (stopStarted) return;
urlRequest = requestUrl;
urlResponse = responseUrl;
}
break;
}
if (IsStopped()) return;
// The logical token is already fixed. Failures in this second physical
// bootstrap are silent and never repeat /Connect.
while (!IsStopped())
{
Ptr<SocketHttpClientApi> apiReceive;
try
{
apiReceive = CreateApi();
}
catch (const SameEndpointClientContractException& exception)
{
ReportFatalError(exception.Message());
return;
}
catch (...)
{
ReportFatalError(L"IAsyncSocketClient::CreateSameEndpointClient failed.");
return;
}
if (!PublishApi(apiReceive, true))
{
StopApiNoThrow(apiReceive);
return;
}
bool connected = false;
try
{
connected = WaitApiForServer(apiReceive);
}
catch (...)
{
}
if (!connected)
{
StopApiNoThrow(apiReceive);
ClearApi(apiReceive, true);
continue;
}
bool publishConnected = false;
CS_LOCK(lockState)
{
if (!stopStarted && receiveApi == apiReceive)
{
state = State::Connected;
publishConnected = true;
}
}
if (!publishConnected)
{
StopApiNoThrow(apiReceive);
return;
}
InvokeProtocolCallback(false, [](INetworkProtocolCallback* installed)
{
installed->OnConnected();
});
return;
}
}
ClientStatus GetStatus()
{
CS_LOCK(lockState)
{
switch (state)
{
case State::Ready:
return ClientStatus::Ready;
case State::WaitingForServer:
return ClientStatus::WaitingForServer;
case State::Connected:
return ClientStatus::Connected;
default:
return ClientStatus::Disconnected;
}
}
return ClientStatus::Disconnected;
}
void InstallCallback(INetworkProtocolCallback* value)
{
if (!value)
{
auto callbackDepth = CurrentCallbackDepth();
bool uninstallOwner = false;
CS_LOCK(lockState)
{
uninstallOwner = callback != nullptr;
callback = nullptr;
while ((callbackDepth == 0 || uninstallOwner) && activeCallbacks > callbackDepth)
{
cvState.SleepWith(lockState);
}
}
return;
}
bool canInstall = false;
CS_LOCK(lockState)
{
if (!callback && !callbackInstalling && !stopStarted)
{
callback = value;
callbackInstalling = true;
activeCallbacks++;
canInstall = true;
}
}
CHECK_ERROR(canInstall, L"SocketHttpClient::InstallCallback cannot replace a callback or install one on a stopped client.");
CallbackFrame frame(RetainSelf());
try
{
value->OnInstalled(owner);
}
catch (...)
{
CS_LOCK(lockState)
{
if (callback == value) callback = nullptr;
callbackInstalling = false;
cvState.WakeAllPendings();
}
throw;
}
CS_LOCK(lockState)
{
callbackInstalling = false;
cvState.WakeAllPendings();
}
}
void BeginReadingLoopUnsafe()
{
Ptr<SocketHttpClientApi> api;
CS_LOCK(lockState)
{
CHECK_ERROR(state == State::Connected && !stopStarted, L"SocketHttpClient::BeginReadingLoopUnsafe requires a connected client.");
CHECK_ERROR(!readingStarted, L"SocketHttpClient::BeginReadingLoopUnsafe can only be called once.");
readingStarted = true;
api = receiveApi;
CHECK_ERROR(api, L"SocketHttpClient has no receive API after connecting.");
receivePollActive = true;
}
SubmitReceivePoll(api);
}
void SendString(const WString& str)
{
CHECK_ERROR(str.Length() > 0, L"SocketHttpClient::SendString does not accept an empty string.");
CHECK_ERROR(IsValidHttpNetworkProtocolMessage(str), L"SocketHttpClient::SendString requires valid Unicode without embedded NUL characters.");
Array<vuint8_t> validated;
CHECK_ERROR(EncodeStrictUtf8(str, validated), L"SocketHttpClient::SendString requires valid Unicode without embedded NUL characters.");
CHECK_ERROR(validated.Count() <= HttpBodySizeLimit, L"SocketHttpClient::SendString exceeds the HTTP body size limit.");
auto item = Ptr(new SendItem);
item->body.Resize(validated.Count());
for (vint i = 0; i < validated.Count(); i++)
{
item->body[i] = (char)validated[i];
}
Ptr<SocketHttpClientApi> api;
bool submit = false;
CS_LOCK(lockState)
{
CHECK_ERROR(state == State::Connected && !stopStarted, L"SocketHttpClient::SendString requires a connected client.");
sendQueue.Add(item);
if (!sendActive && !sendReconnecting)
{
api = sendApi;
CHECK_ERROR(api, L"SocketHttpClient has no send API after connecting.");
sendActive = true;
submit = true;
}
}
if (submit) SubmitSend(api, item);
}
void Stop(bool internalFollower = false)
{
auto self = RetainSelf();
auto callbackDepth = CurrentCallbackDepth();
auto workerDepth = CurrentWorkerDepth();
auto waitDepth = CurrentWaitDepth();
auto nested = internalFollower || callbackDepth > 0 || workerDepth > 0 || waitDepth > 0;
bool executeStop = false;
Ptr<SocketHttpClientApi> cancellingReceive;
lockState.Enter();
if (stopFinished)
{
while (activeCallbacks > callbackDepth || activeWorkers > workerDepth || activeWaits > waitDepth)
{
cvState.SleepWith(lockState);
}
lockState.Leave();
return;
}
if (!stopStarted)
{
stopStarted = true;
state = State::Stopping;
drainSends = !fatalStarted && sendQueue.Count() > 0;
cancellingReceive = receiveApi;
executeStop = true;
}
else if (nested)
{
lockState.Leave();
return;
}
else
{
while (!stopFinished) cvState.SleepWith(lockState);
while (activeCallbacks > 0 || activeWorkers > 0 || activeWaits > 0) cvState.SleepWith(lockState);
lockState.Leave();
return;
}
lockState.Leave();
if (!executeStop) return;
InvokeStopStartedForTesting();
// The infinite receive exchange is always cancelled before the bounded
// opportunity given to already accepted send-lane messages.
StopApiNoThrow(cancellingReceive);
lockState.Enter();
if (drainSends)
{
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(SendDrainTimeout);
while (sendQueue.Count() > 0)
{
auto now = std::chrono::steady_clock::now();
if (now >= deadline) break;
auto remaining = std::chrono::ceil<std::chrono::milliseconds>(deadline - now).count();
cvState.SleepWithForTime(lockState, (vint)remaining);
}
}
drainSends = false;
hardStopping = true;
sendQueue.Clear();
sendActive = false;
receivePollActive = false;
auto stoppingSend = sendApi;
auto stoppingReceive = receiveApi;
lockState.Leave();
StopApiNoThrow(stoppingReceive);
StopApiNoThrow(stoppingSend);
lockState.Enter();
while (activeWorkers > workerDepth || activeWaits > waitDepth)
{
cvState.SleepWith(lockState);
}
lockState.Leave();
try
{
NotifyDisconnected();
}
catch (...)
{
CS_LOCK(lockState)
{
while (activeCallbacks > callbackDepth) cvState.SleepWith(lockState);
stopFinished = true;
cvState.WakeAllPendings();
}
throw;
}
CS_LOCK(lockState)
{
while (activeCallbacks > callbackDepth) cvState.SleepWith(lockState);
stopFinished = true;
cvState.WakeAllPendings();
}
}
};
thread_local SocketHttpClient::Impl::CallbackFrame* SocketHttpClient::Impl::currentCallbackFrame = nullptr;
thread_local SocketHttpClient::Impl::WorkerFrame* SocketHttpClient::Impl::currentWorkerFrame = nullptr;
thread_local SocketHttpClient::Impl::WaitFrame* SocketHttpClient::Impl::currentWaitFrame = nullptr;
/***********************************************************************
SocketHttpClient
***********************************************************************/
SocketHttpClient::SocketHttpClient(
Ptr<IAsyncSocketClient> client,
const WString& server,
const WString& urlPrefix
)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::SocketHttpClient::SocketHttpClient(Ptr<IAsyncSocketClient>, const WString&, const WString&)#"
CHECK_ERROR(client, ERROR_MESSAGE_PREFIX L"An initial native client is required.");
auto created = Ptr(new Impl(this, client, server, urlPrefix));
created->Initialize(created);
impl = created;
#undef ERROR_MESSAGE_PREFIX
}
SocketHttpClient::~SocketHttpClient()
{
try
{
impl->Stop();
}
catch (...)
{
}
impl->ReleaseSelf();
}
INetworkProtocolConnection* SocketHttpClient::GetConnection()
{
return impl->GetConnection();
}
void SocketHttpClient::WaitForServer()
{
impl->WaitForServer();
}
ClientStatus SocketHttpClient::GetStatus()
{
return impl->GetStatus();
}
void SocketHttpClient::InstallCallback(INetworkProtocolCallback* callback)
{
impl->InstallCallback(callback);
}
void SocketHttpClient::BeginReadingLoopUnsafe()
{
impl->BeginReadingLoopUnsafe();
}
void SocketHttpClient::SendString(const WString& str)
{
impl->SendString(str);
}
void SocketHttpClient::Stop()
{
impl->Stop();
}
}
/***********************************************************************
.\INTERPROCESS\ASYNCSOCKET\ASYNCSOCKET_HTTPCLIENTAPI.CPP
***********************************************************************/
/***********************************************************************
Vczh Library++ 3.0
Developer: Zihan Chen(vczh)
Interfaces:
SocketHttpClientApi
***********************************************************************/
namespace vl::inter_process::async_tcp_socket
{
using namespace vl::collections;
namespace
{
wchar_t FoldAscii(wchar_t c)
{
return L'A' <= c && c <= L'Z' ? c - L'A' + L'a' : c;
}
bool AsciiEqualsIgnoreCase(const WString& a, const WString& b)
{
if (a.Length() != b.Length()) return false;
for (vint i = 0; i < a.Length(); i++)
{
if (FoldAscii(a[i]) != FoldAscii(b[i])) return false;
}
return true;
}
WString FoldAsciiFieldName(const WString& name)
{
Array<wchar_t> characters(name.Length());
for (vint i = 0; i < name.Length(); i++)
{
characters[i] = FoldAscii(name[i]);
}
return characters.Count() == 0 ? WString() : WString::CopyFrom(&characters[0], characters.Count());
}
WString TrimHttpWhitespace(const WString& value)
{
vint begin = 0;
vint end = value.Length();
while (begin < end && (value[begin] == L' ' || value[begin] == L'\t')) begin++;
while (begin < end && (value[end - 1] == L' ' || value[end - 1] == L'\t')) end--;
return value.Sub(begin, end - begin);
}
bool ContainsOnlyIdentityCoding(const WString& value)
{
vint begin = 0;
bool found = false;
while (begin <= value.Length())
{
vint end = begin;
while (end < value.Length() && value[end] != L',') end++;
auto coding = TrimHttpWhitespace(value.Sub(begin, end - begin));
if (!AsciiEqualsIgnoreCase(coding, L"identity")) return false;
found = true;
if (end == value.Length()) break;
begin = end + 1;
}
return found;
}
bool ValidateServer(const WString& server)
{
return
AsciiEqualsIgnoreCase(server, L"localhost") ||
server == L"127.0.0.1";
}
HttpField CreateField(const WString& name, const WString& value)
{
HttpField field;
field.name = FoldAsciiFieldName(name);
auto utf8 = wtou8(value);
field.value.Resize(utf8.Length());
if (utf8.Length() > 0)
{
memcpy(&field.value[0], utf8.Buffer(), utf8.Length());
}
return field;
}
WString DecodeFieldValue(const Array<vuint8_t>& value)
{
if (value.Count() == 0) return WString::Empty;
Array<char8_t> utf8(value.Count());
for (vint i = 0; i < value.Count(); i++)
{
utf8[i] = (char8_t)value[i];
}
return u8tow(U8String::CopyFrom(&utf8[0], utf8.Count()));
}
windows_http::HttpError MakeError(const WString& operation, const WString& message, SocketHttpClientErrorCode code)
{
windows_http::HttpError error;
error.operation = operation;
error.errorCode = (vuint32_t)code;
error.message = message;
return error;
}
}
/***********************************************************************
SocketHttpClientApi::Impl
***********************************************************************/
class SocketHttpClientApi::Impl : public Object, public virtual IHttpRequestCallback
{
using QueryResult = Variant<windows_http::HttpResponse, windows_http::HttpError>;
using QueryCallback = Func<void(QueryResult)>;
class Query : public Object
{
public:
Ptr<HttpRequest> request;
vint responseTimeout = 0;
QueryCallback callback;
bool completed = false;
};
struct CallbackFrame
{
Impl* owner = nullptr;
Ptr<Impl> self;
CallbackFrame* previous = nullptr;
CallbackFrame(Impl* _owner, Ptr<Impl> _self)
: owner(_owner)
, self(_self)
, previous(currentCallbackFrame)
{
currentCallbackFrame = this;
}
~CallbackFrame()
{
currentCallbackFrame = previous;
CS_LOCK(owner->lockState)
{
owner->activeCallbacks--;
owner->cvState.WakeAllPendings();
}
}
};
struct ResponseFrame
{
Impl* owner = nullptr;
Ptr<Impl> self;
ResponseFrame* previous = nullptr;
ResponseFrame(Impl* _owner, Ptr<Impl> _self)
: owner(_owner)
, self(_self)
, previous(currentResponseFrame)
{
currentResponseFrame = this;
}
~ResponseFrame()
{
currentResponseFrame = previous;
}
};
static thread_local CallbackFrame* currentCallbackFrame;
static thread_local ResponseFrame* currentResponseFrame;
Ptr<HttpRequestClient> client;
IHttpRequestConnection* connection = nullptr;
WString authority;
Ptr<Impl> selfReference;
CriticalSection lockState;
ConditionVariable cvState;
Ptr<Query> activeQuery;
List<Ptr<Query>> queuedQueries;
List<Ptr<Query>> pendingQueries;
vint activeCallbacks = 0;
bool waitStarted = false;
bool readingStarted = false;
bool responseDispatching = false;
bool terminal = false;
bool stopStarted = false;
bool stopFinished = false;
Ptr<Impl> RetainSelf()
{
Ptr<Impl> self;
CS_LOCK(lockState)
{
self = selfReference;
}
return self;
}
vint CurrentCallbackDepth()
{
vint depth = 0;
for (auto frame = currentCallbackFrame; frame; frame = frame->previous)
{
if (frame->owner == this) depth++;
}
return depth;
}
bool IsInsideResponseCallback()
{
for (auto frame = currentResponseFrame; frame; frame = frame->previous)
{
if (frame->owner == this) return true;
}
return false;
}
Ptr<Query> TakeFirstQueuedUnsafe()
{
auto query = queuedQueries[0];
queuedQueries.RemoveAt(0);
return query;
}
void TakeAllQueriesUnsafe(List<Ptr<Query>>& queries)
{
if (activeQuery)
{
queries.Add(activeQuery);
activeQuery = nullptr;
}
for (auto query : queuedQueries)
{
queries.Add(query);
}
queuedQueries.Clear();
for (auto query : pendingQueries)
{
queries.Add(query);
}
pendingQueries.Clear();
}
void MoveAllQueriesToPendingUnsafe()
{
if (activeQuery)
{
pendingQueries.Add(activeQuery);
activeQuery = nullptr;
}
for (auto query : queuedQueries)
{
pendingQueries.Add(query);
}
queuedQueries.Clear();
}
bool ReserveCallbackUnsafe(Ptr<Query> query, QueryCallback& callback)
{
if (!query || query->completed) return false;
query->completed = true;
callback = query->callback;
if (callback) activeCallbacks++;
return true;
}
void InvokeReserved(QueryCallback callback, QueryResult result)
{
if (!callback) return;
CallbackFrame frame(this, RetainSelf());
try
{
callback(std::move(result));
}
catch (...)
{
}
}
void CompleteWithError(Ptr<Query> query, const windows_http::HttpError& error)
{
QueryCallback callback;
bool reserved = false;
CS_LOCK(lockState)
{
reserved = ReserveCallbackUnsafe(query, callback);
}
if (reserved)
{
InvokeReserved(callback, QueryResult(error));
}
}
void CompleteAllWithError(List<Ptr<Query>>& queries, const windows_http::HttpError& error)
{
for (auto query : queries)
{
CompleteWithError(query, error);
}
}
void CompletePendingWithError(const windows_http::HttpError& error)
{
while (true)
{
QueryCallback callback;
bool reserved = false;
CS_LOCK(lockState)
{
if (!stopStarted && pendingQueries.Count() > 0)
{
auto query = pendingQueries[0];
pendingQueries.RemoveAt(0);
reserved = ReserveCallbackUnsafe(query, callback);
}
}
if (!reserved) return;
InvokeReserved(callback, QueryResult(error));
}
}
void InvokeDirect(QueryCallback callback, const windows_http::HttpError& error)
{
if (!callback) return;
CS_LOCK(lockState)
{
activeCallbacks++;
}
InvokeReserved(callback, QueryResult(error));
}
Ptr<Query> CreateQuery(const windows_http::HttpRequest& request, windows_http::HttpError& error)
{
if (request.secure)
{
error = MakeError(L"SocketHttpClientApi::HttpQuery", L"TLS is not supported by SocketHttpClientApi.", SocketHttpClientErrorCode::InvalidRequest);
return nullptr;
}
if (request.username != WString::Empty || request.password != WString::Empty)
{
error = MakeError(L"SocketHttpClientApi::HttpQuery", L"Credentials are not supported by SocketHttpClientApi.", SocketHttpClientErrorCode::InvalidRequest);
return nullptr;
}
if (request.keepAliveOnStop)
{
error = MakeError(L"SocketHttpClientApi::HttpQuery", L"keepAliveOnStop is not supported by SocketHttpClientApi.", SocketHttpClientErrorCode::InvalidRequest);
return nullptr;
}
auto query = Ptr(new Query);
query->request = Ptr(new HttpRequest);
query->responseTimeout = request.receiveTimeout;
query->callback = {};
query->request->method = request.method == WString::Empty ? WString::Unmanaged(L"GET") : request.method;
query->request->requestTarget = request.query == WString::Empty ? WString::Unmanaged(L"/") : request.query;
query->request->headers.Add(CreateAsciiHttpField(L"Host", authority));
query->request->headers.Add(CreateAsciiHttpField(L"Accept-Encoding", L"identity"));
for (vint i = 0; i < request.acceptTypes.Count(); i++)
{
query->request->headers.Add(CreateField(L"Accept", request.acceptTypes.Get(i)));
}
if (request.contentType != WString::Empty)
{
query->request->headers.Add(CreateField(L"Content-Type", request.contentType));
}
if (request.cookie != WString::Empty)
{
query->request->headers.Add(CreateField(L"Cookie", request.cookie));
}
for (vint i = 0; i < request.extraHeaders.Count(); i++)
{
auto name = request.extraHeaders.Keys()[i];
auto value = request.extraHeaders.Values()[i];
if (AsciiEqualsIgnoreCase(name, L"Host"))
{
if (!AsciiEqualsIgnoreCase(TrimHttpWhitespace(value), authority))
{
error = MakeError(L"SocketHttpClientApi::HttpQuery", L"A caller-supplied Host field conflicts with the configured server and injected client port.", SocketHttpClientErrorCode::InvalidRequest);
return nullptr;
}
continue;
}
if (AsciiEqualsIgnoreCase(name, L"Accept-Encoding"))
{
if (!ContainsOnlyIdentityCoding(value))
{
error = MakeError(L"SocketHttpClientApi::HttpQuery", L"SocketHttpClientApi only supports Accept-Encoding: identity.", SocketHttpClientErrorCode::InvalidRequest);
return nullptr;
}
continue;
}
query->request->headers.Add(CreateField(name, value));
}
if (request.body.Count() > 0)
{
HttpBodyChunk chunk;
chunk.data.Resize(request.body.Count());
memcpy(&chunk.data[0], &request.body.Get(0), request.body.Count());
query->request->body.chunks.Add(std::move(chunk));
}
return query;
}
bool ConvertResponse(Ptr<HttpResponse> response, windows_http::HttpResponse& output, windows_http::HttpError& error)
{
if (!response)
{
error = MakeError(L"SocketHttpClientApi::OnReadResponse", L"The HTTP request layer returned an empty response.", SocketHttpClientErrorCode::Transport);
return false;
}
output.statusCode = response->statusCode;
bool contentTypeAssigned = false;
bool cookieAssigned = false;
auto processField = [&](const HttpField& field)
{
if (AsciiEqualsIgnoreCase(field.name, L"Content-Encoding"))
{
if (!ContainsOnlyIdentityCoding(DecodeFieldValue(field.value)))
{
error = MakeError(L"SocketHttpClientApi::OnReadResponse", L"The server returned an unsupported Content-Encoding.", SocketHttpClientErrorCode::UnsupportedCoding);
return false;
}
}
else if (!contentTypeAssigned && AsciiEqualsIgnoreCase(field.name, L"Content-Type"))
{
output.contentType = DecodeFieldValue(field.value);
contentTypeAssigned = true;
}
else if (!cookieAssigned && AsciiEqualsIgnoreCase(field.name, L"Set-Cookie"))
{
output.cookie = DecodeFieldValue(field.value);
cookieAssigned = true;
}
return true;
};
for (auto&& field : response->headers)
{
if (!processField(field)) return false;
}
for (auto&& field : response->body.trailers)
{
if (!processField(field)) return false;
}
Array<vuint8_t> body;
if (!FlattenHttpBody(response->body, body))
{
error = MakeError(L"SocketHttpClientApi::OnReadResponse", L"The response body is too large to flatten.", SocketHttpClientErrorCode::Transport);
return false;
}
output.body.Resize(body.Count());
for (vint i = 0; i < body.Count(); i++)
{
output.body[i] = (char)body[i];
}
return true;
}
bool TrySend(Ptr<Query> query, windows_http::HttpError& error)
{
try
{
connection->SendRequest(query->request, query->responseTimeout);
return true;
}
catch (...)
{
error = MakeError(L"SocketHttpClientApi::HttpQuery", L"The HTTP request layer rejected the exchange.", SocketHttpClientErrorCode::Transport);
return false;
}
}
void StopConnectionNoThrow()
{
try
{
connection->Stop();
}
catch (...)
{
}
}
void HandleSendFailure(Ptr<Query> query, const windows_http::HttpError& error)
{
bool stopConnection = false;
CS_LOCK(lockState)
{
if (!query->completed && !stopStarted && !terminal)
{
terminal = true;
responseDispatching = false;
MoveAllQueriesToPendingUnsafe();
stopConnection = true;
}
}
if (stopConnection)
{
StopConnectionNoThrow();
CompletePendingWithError(error);
}
}
void HandleTerminalError(const windows_http::HttpError& error, bool stopConnectionImmediately)
{
bool shouldStop = false;
CS_LOCK(lockState)
{
if (!stopStarted && !terminal)
{
terminal = true;
responseDispatching = false;
MoveAllQueriesToPendingUnsafe();
shouldStop = stopConnectionImmediately;
}
}
if (shouldStop)
{
StopConnectionNoThrow();
}
CompletePendingWithError(error);
}
public:
Impl(Ptr<IAsyncSocketClient> socketClient, const WString& _authority)
: client(new HttpRequestClient(socketClient))
, authority(_authority)
{
connection = client->GetConnection();
}
void Initialize(Ptr<Impl> self)
{
CS_LOCK(lockState)
{
selfReference = self;
}
try
{
connection->InstallCallback(this);
}
catch (...)
{
CS_LOCK(lockState)
{
selfReference = nullptr;
}
throw;
}
}
void ReleaseSelf()
{
CS_LOCK(lockState)
{
selfReference = nullptr;
}
}
void WaitForServer()
{
CS_LOCK(lockState)
{
CHECK_ERROR(!waitStarted && !stopStarted && !terminal, L"SocketHttpClientApi::WaitForServer can only be called once on an active client.");
waitStarted = true;
}
try
{
client->WaitForServer();
}
catch (...)
{
CS_LOCK(lockState)
{
terminal = true;
}
throw;
}
bool beginReading = false;
CS_LOCK(lockState)
{
beginReading = !stopStarted && !terminal;
}
if (!beginReading) return;
try
{
connection->BeginReadingLoopUnsafe();
}
catch (...)
{
CS_LOCK(lockState)
{
terminal = true;
}
throw;
}
CS_LOCK(lockState)
{
readingStarted = !stopStarted && !terminal;
}
}
ClientStatus GetStatus()
{
return client->GetStatus();
}
void HttpQuery(const windows_http::HttpRequest& request, QueryCallback callback)
{
auto self = RetainSelf();
windows_http::HttpError error;
Ptr<Query> query;
try
{
query = CreateQuery(request, error);
}
catch (...)
{
error = MakeError(L"SocketHttpClientApi::HttpQuery", L"The request could not be translated to the socket HTTP representation.", SocketHttpClientErrorCode::InvalidRequest);
}
if (!query)
{
InvokeDirect(callback, error);
return;
}
query->callback = callback;
bool start = false;
bool reject = false;
bool notReady = false;
CS_LOCK(lockState)
{
if (stopStarted || terminal)
{
reject = true;
}
else if (!readingStarted)
{
notReady = true;
}
else if (!activeQuery && queuedQueries.Count() == 0 && (!responseDispatching || IsInsideResponseCallback()))
{
activeQuery = query;
start = true;
}
else
{
queuedQueries.Add(query);
}
}
if (reject)
{
CompleteWithError(query, MakeError(L"SocketHttpClientApi::HttpQuery", L"SocketHttpClientApi has stopped accepting work.", SocketHttpClientErrorCode::Stopped));
}
else if (notReady)
{
CompleteWithError(query, MakeError(L"SocketHttpClientApi::HttpQuery", L"WaitForServer must complete before sending an HTTP query.", SocketHttpClientErrorCode::InvalidRequest));
}
else if (start)
{
if (!TrySend(query, error))
{
HandleSendFailure(query, error);
}
}
}
void Stop()
{
auto self = RetainSelf();
List<Ptr<Query>> cancelledQueries;
auto callbackDepth = CurrentCallbackDepth();
bool executeStop = false;
lockState.Enter();
if (stopFinished)
{
while (activeCallbacks > callbackDepth) cvState.SleepWith(lockState);
lockState.Leave();
return;
}
if (!stopStarted)
{
stopStarted = true;
terminal = true;
responseDispatching = false;
TakeAllQueriesUnsafe(cancelledQueries);
executeStop = true;
}
else if (callbackDepth > 0)
{
lockState.Leave();
return;
}
else
{
while (!stopFinished) cvState.SleepWith(lockState);
while (activeCallbacks > 0) cvState.SleepWith(lockState);
lockState.Leave();
return;
}
lockState.Leave();
if (executeStop)
{
StopConnectionNoThrow();
CompleteAllWithError(
cancelledQueries,
MakeError(L"SocketHttpClientApi::Stop", L"The HTTP query was cancelled because the client stopped.", SocketHttpClientErrorCode::Stopped)
);
}
CS_LOCK(lockState)
{
while (activeCallbacks > callbackDepth) cvState.SleepWith(lockState);
stopFinished = true;
cvState.WakeAllPendings();
}
}
void OnReadRequest(Ptr<HttpRequest>) override
{
auto self = RetainSelf();
if (!self) return;
HandleTerminalError(
MakeError(L"SocketHttpClientApi::OnReadRequest", L"The client connection received a request instead of a response.", SocketHttpClientErrorCode::Transport),
true
);
}
void OnReadRequestFailure(HttpRequestFailure) override
{
auto self = RetainSelf();
if (!self) return;
HandleTerminalError(
MakeError(L"SocketHttpClientApi::OnReadRequestFailure", L"The client connection reported a request parsing failure.", SocketHttpClientErrorCode::Transport),
true
);
}
void OnReadResponse(Ptr<HttpResponse> response) override
{
auto self = RetainSelf();
if (!self) return;
ResponseFrame responseFrame(this, self);
windows_http::HttpResponse convertedResponse;
windows_http::HttpError responseError;
auto converted = ConvertResponse(response, convertedResponse, responseError);
Ptr<Query> completedQuery;
Ptr<Query> nextQuery;
QueryCallback completedCallback;
bool reserved = false;
bool stopForResponse = false;
windows_http::HttpError terminalError;
CS_LOCK(lockState)
{
if (activeQuery && !activeQuery->completed)
{
responseDispatching = true;
completedQuery = activeQuery;
activeQuery = nullptr;
reserved = ReserveCallbackUnsafe(completedQuery, completedCallback);
if (!converted)
{
terminal = true;
MoveAllQueriesToPendingUnsafe();
stopForResponse = true;
terminalError = responseError;
}
else if (!stopStarted && !terminal && queuedQueries.Count() > 0)
{
nextQuery = TakeFirstQueuedUnsafe();
activeQuery = nextQuery;
}
}
}
if (!reserved) return;
windows_http::HttpError sendError;
bool sendFailed = nextQuery && !TrySend(nextQuery, sendError);
if (sendFailed)
{
CS_LOCK(lockState)
{
if (!nextQuery->completed && !stopStarted && !terminal)
{
terminal = true;
MoveAllQueriesToPendingUnsafe();
stopForResponse = true;
terminalError = sendError;
}
}
}
if (converted)
{
InvokeReserved(completedCallback, QueryResult(std::move(convertedResponse)));
}
else
{
InvokeReserved(completedCallback, QueryResult(responseError));
}
Ptr<Query> lateQuery;
if (!stopForResponse)
{
CS_LOCK(lockState)
{
if (!stopStarted && !terminal && !activeQuery && queuedQueries.Count() > 0)
{
lateQuery = TakeFirstQueuedUnsafe();
activeQuery = lateQuery;
}
responseDispatching = false;
}
if (lateQuery && !TrySend(lateQuery, sendError))
{
CS_LOCK(lockState)
{
if (!lateQuery->completed && !stopStarted && !terminal)
{
terminal = true;
MoveAllQueriesToPendingUnsafe();
stopForResponse = true;
terminalError = sendError;
}
}
}
}
else
{
CS_LOCK(lockState)
{
responseDispatching = false;
}
}
if (stopForResponse)
{
StopConnectionNoThrow();
CompletePendingWithError(terminalError);
}
}
void OnReadResponseFailure(HttpResponseFailure failure) override
{
auto self = RetainSelf();
if (!self) return;
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::SocketHttpClientApi::Impl::OnReadResponseFailure(HttpResponseFailure)#"
CHECK_ERROR(failure == HttpResponseFailure::NotFound, ERROR_MESSAGE_PREFIX L"Received an unsupported response failure.");
HandleTerminalError(
MakeError(L"SocketHttpClientApi::OnReadResponseFailure", L"The server returned 404 Not Found.", SocketHttpClientErrorCode::ResponseNotFound),
false
);
#undef ERROR_MESSAGE_PREFIX
}
void OnWriteCompleted() override
{
}
void OnError(const WString& error, bool fatal) override
{
auto self = RetainSelf();
if (!self) return;
// The raw layer stops after delivering a fatal error. A nonfatal error
// needs an explicit stop after this wrapper terminalizes its queue.
auto stopConnectionImmediately = !fatal;
HandleTerminalError(
MakeError(L"SocketHttpClientApi::OnError", error, SocketHttpClientErrorCode::Transport),
stopConnectionImmediately
);
}
void OnConnected() override
{
}
void OnDisconnected() override
{
auto self = RetainSelf();
if (!self) return;
HandleTerminalError(
MakeError(L"SocketHttpClientApi::OnDisconnected", L"The HTTP connection was disconnected.", SocketHttpClientErrorCode::Transport),
false
);
}
void OnInstalled(IHttpRequestConnection* installedConnection) override
{
CHECK_ERROR(installedConnection == connection, L"SocketHttpClientApi was installed on an unexpected HTTP connection.");
}
};
thread_local SocketHttpClientApi::Impl::CallbackFrame* SocketHttpClientApi::Impl::currentCallbackFrame = nullptr;
thread_local SocketHttpClientApi::Impl::ResponseFrame* SocketHttpClientApi::Impl::currentResponseFrame = nullptr;
/***********************************************************************
SocketHttpClientApi
***********************************************************************/
SocketHttpClientApi::SocketHttpClientApi(Ptr<IAsyncSocketClient> client, const WString& server)
{
CHECK_ERROR(client, L"SocketHttpClientApi requires an asynchronous socket client.");
CHECK_ERROR(ValidateServer(server), L"SocketHttpClientApi requires an explicit loopback server.");
auto port = client->GetPort();
CHECK_ERROR(1 <= port && port <= 65535, L"SocketHttpClientApi requires the injected client port to be in 1..65535.");
auto authority = server + WString::Unmanaged(L":") + itow(port);
auto created = Ptr(new Impl(client, authority));
created->Initialize(created);
impl = created;
}
SocketHttpClientApi::~SocketHttpClientApi()
{
impl->Stop();
impl->ReleaseSelf();
}
void SocketHttpClientApi::WaitForServer()
{
impl->WaitForServer();
}
ClientStatus SocketHttpClientApi::GetStatus()
{
return impl->GetStatus();
}
void SocketHttpClientApi::HttpQuery(
const windows_http::HttpRequest& request,
Func<void(Variant<windows_http::HttpResponse, windows_http::HttpError>)> callback
)
{
impl->HttpQuery(request, callback);
}
void SocketHttpClientApi::Stop()
{
impl->Stop();
}
WString SocketHttpClientApi::UrlEncodeQuery(const WString& query)
{
return HttpUrlEncodeQuery(query);
}
WString SocketHttpClientApi::UrlDecodeQuery(const WString& query)
{
return HttpUrlDecodeQuery(query);
}
}
/***********************************************************************
.\INTERPROCESS\ASYNCSOCKET\ASYNCSOCKET_HTTPREQUEST.CPP
***********************************************************************/
/***********************************************************************
Vczh Library++ 3.0
Developer: Zihan Chen(vczh)
Async Socket HTTP/1.1 Connection
***********************************************************************/
namespace vl::inter_process::async_tcp_socket
{
using namespace collections;
namespace
{
constexpr vint HttpWireMessageSizeLimit = 128 * 1024 * 1024;
constexpr vint HttpChunkCountLimit = 64 * 1024;
bool IsOws(vuint8_t c)
{
return c == ' ' || c == '\t';
}
bool IsDigit(vuint8_t c)
{
return c >= '0' && c <= '9';
}
bool IsHexDigit(vuint8_t c)
{
return IsDigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
}
vuint8_t HexDigitValue(vuint8_t c)
{
if (IsDigit(c)) return c - '0';
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return c - 'a' + 10;
}
bool IsTokenCharacter(vuint8_t c)
{
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
{
return true;
}
switch (c)
{
case '!': case '#': case '$': case '%': case '&': case '\'': case '*': case '+':
case '-': case '.': case '^': case '_': case '`': case '|': case '~':
return true;
default:
return false;
}
}
bool IsFieldValueCharacter(vuint8_t c)
{
return c == '\t' || (c >= 0x20 && c != 0x7F);
}
bool IsQuotedTextCharacter(vuint8_t c)
{
return c == '\t' || c == ' ' || c == '!' || (c >= '#' && c <= '[') || (c >= ']' && c != 0x7F);
}
vint FindCrlf(const vuint8_t* buffer, vint begin, vint end)
{
for (vint i = begin; i + 1 < end; i++)
{
if (buffer[i] == '\r' && buffer[i + 1] == '\n')
{
return i;
}
}
return -1;
}
WString CopyAscii(const vuint8_t* buffer, vint begin, vint end, bool lowercase)
{
if (begin == end)
{
return L"";
}
Array<wchar_t> text(end - begin);
for (vint i = begin; i < end; i++)
{
auto c = buffer[i];
if (lowercase && c >= 'A' && c <= 'Z')
{
c += 'a' - 'A';
}
text[i - begin] = (wchar_t)c;
}
return WString::CopyFrom(&text[0], text.Count());
}
bool IsAsciiEqual(const WString& text, const wchar_t* expected)
{
return text == expected;
}
bool ParseQuotedString(const vuint8_t* buffer, vint& reading, vint end)
{
if (reading >= end || buffer[reading] != '"')
{
return false;
}
reading++;
while (reading < end)
{
auto c = buffer[reading++];
if (c == '"')
{
return true;
}
if (c == '\\')
{
if (reading >= end || !IsFieldValueCharacter(buffer[reading]))
{
return false;
}
reading++;
}
else if (!IsQuotedTextCharacter(c))
{
return false;
}
}
return false;
}
bool ParseToken(const vuint8_t* buffer, vint& reading, vint end, WString* output = nullptr)
{
vint begin = reading;
while (reading < end && IsTokenCharacter(buffer[reading]))
{
reading++;
}
if (reading == begin)
{
return false;
}
if (output)
{
*output = CopyAscii(buffer, begin, reading, true);
}
return true;
}
bool ParseSemicolonParameters(const vuint8_t* buffer, vint& reading, vint end)
{
while (true)
{
while (reading < end && IsOws(buffer[reading])) reading++;
if (reading == end)
{
return true;
}
if (buffer[reading++] != ';')
{
return false;
}
while (reading < end && IsOws(buffer[reading])) reading++;
if (!ParseToken(buffer, reading, end))
{
return false;
}
while (reading < end && IsOws(buffer[reading])) reading++;
if (reading < end && buffer[reading] == '=')
{
reading++;
while (reading < end && IsOws(buffer[reading])) reading++;
if (reading < end && buffer[reading] == '"')
{
if (!ParseQuotedString(buffer, reading, end)) return false;
}
else if (!ParseToken(buffer, reading, end))
{
return false;
}
}
}
}
bool ValidateChunkSizeLinePrefix(const vuint8_t* buffer, vint begin, vint end)
{
vint reading = begin;
if (reading == end) return true;
if (!IsHexDigit(buffer[reading])) return false;
vuint64_t chunkSize = 0;
while (reading < end && IsHexDigit(buffer[reading]))
{
auto digit = (vuint64_t)HexDigitValue(buffer[reading++]);
if (chunkSize > ((std::numeric_limits<vuint64_t>::max)() - digit) / 16) return false;
chunkSize = chunkSize * 16 + digit;
if (chunkSize > (vuint64_t)HttpBodySizeLimit) return false;
}
while (true)
{
while (reading < end && IsOws(buffer[reading])) reading++;
if (reading == end) return true;
if (buffer[reading++] != ';') return false;
while (reading < end && IsOws(buffer[reading])) reading++;
if (reading == end) return true;
vint nameBegin = reading;
while (reading < end && IsTokenCharacter(buffer[reading])) reading++;
if (reading == nameBegin) return false;
while (reading < end && IsOws(buffer[reading])) reading++;
if (reading == end) return true;
if (buffer[reading] != '=') continue;
reading++;
while (reading < end && IsOws(buffer[reading])) reading++;
if (reading == end) return true;
if (buffer[reading] == '"')
{
reading++;
bool closed = false;
while (reading < end)
{
auto c = buffer[reading++];
if (c == '"')
{
closed = true;
break;
}
if (c == '\\')
{
if (reading == end) return true;
if (!IsFieldValueCharacter(buffer[reading++])) return false;
}
else if (!IsQuotedTextCharacter(c))
{
return false;
}
}
if (!closed) return true;
}
else
{
vint valueBegin = reading;
while (reading < end && IsTokenCharacter(buffer[reading])) reading++;
if (reading == valueBegin) return false;
}
}
}
bool ValidateCompleteChunkSizeLine(const vuint8_t* buffer, vint begin, vint end)
{
vint reading = begin;
vuint64_t chunkSize = 0;
while (reading < end && IsHexDigit(buffer[reading]))
{
auto digit = (vuint64_t)HexDigitValue(buffer[reading++]);
if (chunkSize > ((std::numeric_limits<vuint64_t>::max)() - digit) / 16) return false;
chunkSize = chunkSize * 16 + digit;
}
if (reading == begin || chunkSize > (vuint64_t)HttpBodySizeLimit) return false;
if (reading < end)
{
vint firstExtension = reading;
while (firstExtension < end && IsOws(buffer[firstExtension])) firstExtension++;
if (firstExtension == end || buffer[firstExtension] != ';') return false;
}
return ParseSemicolonParameters(buffer, reading, end);
}
bool ParseFieldLine(const vuint8_t* buffer, vint begin, vint end, HttpField& field)
{
vint colon = -1;
for (vint i = begin; i < end; i++)
{
if (buffer[i] == ':')
{
colon = i;
break;
}
if (!IsTokenCharacter(buffer[i]))
{
return false;
}
}
if (colon == begin || colon == -1)
{
return false;
}
for (vint i = begin; i < colon; i++)
{
if (!IsTokenCharacter(buffer[i])) return false;
}
vint valueBegin = colon + 1;
vint valueEnd = end;
while (valueBegin < valueEnd && IsOws(buffer[valueBegin])) valueBegin++;
while (valueBegin < valueEnd && IsOws(buffer[valueEnd - 1])) valueEnd--;
for (vint i = valueBegin; i < valueEnd; i++)
{
if (!IsFieldValueCharacter(buffer[i])) return false;
}
field.name = CopyAscii(buffer, begin, colon, true);
field.value.Resize(valueEnd - valueBegin);
if (valueEnd > valueBegin)
{
std::memcpy(&field.value[0], buffer + valueBegin, (size_t)(valueEnd - valueBegin));
}
return true;
}
bool ParseUnsignedDecimal(const Array<vuint8_t>& value, vint begin, vint end, vuint64_t& number)
{
if (begin == end) return false;
number = 0;
for (vint i = begin; i < end; i++)
{
if (!IsDigit(value[i])) return false;
auto digit = (vuint64_t)(value[i] - '0');
if (number > ((std::numeric_limits<vuint64_t>::max)() - digit) / 10)
{
return false;
}
number = number * 10 + digit;
}
return true;
}
bool ParseContentLength(const Array<vuint8_t>& value, bool& initialized, vuint64_t& contentLength, vint& valueCount)
{
vint reading = 0;
while (true)
{
while (reading < value.Count() && IsOws(value[reading])) reading++;
vint begin = reading;
while (reading < value.Count() && IsDigit(value[reading])) reading++;
vint end = reading;
while (reading < value.Count() && IsOws(value[reading])) reading++;
vuint64_t number = 0;
if (!ParseUnsignedDecimal(value, begin, end, number))
{
return false;
}
valueCount++;
if (!initialized)
{
initialized = true;
contentLength = number;
}
else if (contentLength != number)
{
return false;
}
if (reading == value.Count()) return true;
if (value[reading++] != ',') return false;
if (reading == value.Count()) return false;
}
}
bool ParseTransferCodings(const Array<vuint8_t>& value, List<WString>& codings, bool& hasParameters)
{
vint reading = 0;
while (true)
{
while (reading < value.Count() && IsOws(value[reading])) reading++;
WString coding;
if (!ParseToken(value.Count() == 0 ? nullptr : &value[0], reading, value.Count(), &coding))
{
return false;
}
codings.Add(coding);
while (reading < value.Count() && IsOws(value[reading])) reading++;
while (reading < value.Count() && value[reading] == ';')
{
hasParameters = true;
reading++;
while (reading < value.Count() && IsOws(value[reading])) reading++;
if (!ParseToken(&value[0], reading, value.Count())) return false;
while (reading < value.Count() && IsOws(value[reading])) reading++;
if (reading >= value.Count() || value[reading] != '=')
{
return false;
}
else
{
reading++;
while (reading < value.Count() && IsOws(value[reading])) reading++;
if (reading < value.Count() && value[reading] == '"')
{
if (!ParseQuotedString(&value[0], reading, value.Count())) return false;
}
else if (!ParseToken(&value[0], reading, value.Count()))
{
return false;
}
}
while (reading < value.Count() && IsOws(value[reading])) reading++;
}
if (reading == value.Count()) return true;
if (value[reading++] != ',' || reading == value.Count()) return false;
}
}
bool HasConnectionClose(const Array<vuint8_t>& value)
{
vint reading = 0;
while (reading < value.Count())
{
while (reading < value.Count() && IsOws(value[reading])) reading++;
vint begin = reading;
while (reading < value.Count() && value[reading] != ',') reading++;
vint end = reading;
while (begin < end && IsOws(value[begin])) begin++;
while (begin < end && IsOws(value[end - 1])) end--;
if (end - begin == 5)
{
const wchar_t* close = L"close";
bool matched = true;
for (vint i = 0; i < 5; i++)
{
auto c = value[begin + i];
if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
if (c != close[i]) matched = false;
}
if (matched) return true;
}
if (reading < value.Count()) reading++;
}
return false;
}
bool ParseHttpVersion(const vuint8_t* buffer, vint begin, vint end, HttpVersion& version)
{
const wchar_t* prefix = L"HTTP/";
if (end - begin < 8) return false;
for (vint i = 0; i < 5; i++)
{
if (buffer[begin + i] != prefix[i]) return false;
}
vint reading = begin + 5;
vuint64_t major = 0;
vuint64_t minor = 0;
vint majorBegin = reading;
while (reading < end && IsDigit(buffer[reading]))
{
auto digit = (vuint64_t)(buffer[reading++] - '0');
if (major > ((std::numeric_limits<vuint64_t>::max)() - digit) / 10) return false;
major = major * 10 + digit;
}
if (reading == majorBegin || reading >= end || buffer[reading++] != '.') return false;
vint minorBegin = reading;
while (reading < end && IsDigit(buffer[reading]))
{
auto digit = (vuint64_t)(buffer[reading++] - '0');
if (minor > ((std::numeric_limits<vuint64_t>::max)() - digit) / 10) return false;
minor = minor * 10 + digit;
}
if (reading != end || reading == minorBegin || major > (vuint64_t)(std::numeric_limits<vint>::max)() || minor > (vuint64_t)(std::numeric_limits<vint>::max)())
{
return false;
}
version.major = (vint)major;
version.minor = (vint)minor;
return true;
}
bool ParseRequestLine(const vuint8_t* buffer, vint end, HttpVersion& version, WString& method, WString& target)
{
vint firstSpace = -1;
vint secondSpace = -1;
for (vint i = 0; i < end; i++)
{
if (buffer[i] == ' ')
{
if (firstSpace == -1) firstSpace = i;
else if (secondSpace == -1) secondSpace = i;
else return false;
}
}
if (firstSpace <= 0 || secondSpace <= firstSpace + 1 || secondSpace + 1 >= end) return false;
for (vint i = 0; i < firstSpace; i++) if (!IsTokenCharacter(buffer[i])) return false;
for (vint i = firstSpace + 1; i < secondSpace; i++)
{
if (buffer[i] < 0x21 || buffer[i] > 0x7E) return false;
}
method = CopyAscii(buffer, 0, firstSpace, false);
target = CopyAscii(buffer, firstSpace + 1, secondSpace, false);
return ParseHttpVersion(buffer, secondSpace + 1, end, version);
}
bool ParseStatusLine(const vuint8_t* buffer, vint end, HttpVersion& version, vint& statusCode, WString& reason)
{
vint firstSpace = -1;
for (vint i = 0; i < end; i++)
{
if (buffer[i] == ' ')
{
firstSpace = i;
break;
}
}
if (firstSpace == -1 || !ParseHttpVersion(buffer, 0, firstSpace, version)) return false;
if (firstSpace + 4 >= end || !IsDigit(buffer[firstSpace + 1]) || !IsDigit(buffer[firstSpace + 2]) || !IsDigit(buffer[firstSpace + 3])) return false;
if (buffer[firstSpace + 4] != ' ') return false;
statusCode = (buffer[firstSpace + 1] - '0') * 100 + (buffer[firstSpace + 2] - '0') * 10 + buffer[firstSpace + 3] - '0';
if (statusCode < 200 || statusCode > 599) return false;
vint reasonBegin = firstSpace + 5;
for (vint i = reasonBegin; i < end; i++)
{
if (buffer[i] < 0x20 || buffer[i] > 0x7E) return false;
}
reason = CopyAscii(buffer, reasonBegin, end, false);
return true;
}
enum class HttpBodyDetailedParsingResult
{
Succeeded,
Incomplete,
BadRequest,
PayloadTooLarge,
TrailerFieldsTooLarge,
};
HttpBodyDetailedParsingResult ParseHttpRequestBodyToChunksDetailed(
const vuint8_t* buffer,
vint availableBytes,
HttpBody& output,
vint& consumedBytes
);
enum class HttpMessageParsingResult
{
Succeeded,
Incomplete,
BadRequest,
PayloadTooLarge,
UriTooLong,
ExpectationFailed,
RequestHeaderFieldsTooLarge,
NotImplemented,
HttpVersionNotSupported,
};
HttpRequestFailure GetHttpRequestFailure(HttpMessageParsingResult result)
{
switch (result)
{
case HttpMessageParsingResult::PayloadTooLarge:
return HttpRequestFailure::PayloadTooLarge;
case HttpMessageParsingResult::UriTooLong:
return HttpRequestFailure::UriTooLong;
case HttpMessageParsingResult::ExpectationFailed:
return HttpRequestFailure::ExpectationFailed;
case HttpMessageParsingResult::RequestHeaderFieldsTooLarge:
return HttpRequestFailure::RequestHeaderFieldsTooLarge;
case HttpMessageParsingResult::NotImplemented:
return HttpRequestFailure::NotImplemented;
case HttpMessageParsingResult::HttpVersionNotSupported:
return HttpRequestFailure::HttpVersionNotSupported;
default:
return HttpRequestFailure::BadRequest;
}
}
bool HasHeader(const List<HttpField>& fields, const wchar_t* name)
{
for (auto&& field : fields)
{
if (IsAsciiEqual(field.name, name)) return true;
}
return false;
}
bool TryGetHttpRequestLineSize(const WString& method, const WString& requestTarget, vint& size)
{
if (
method.Length() > HttpRequestLineSizeLimit - 10 ||
requestTarget.Length() > HttpRequestLineSizeLimit - 10 - method.Length()
)
{
return false;
}
size = 10 + method.Length() + requestTarget.Length();
return true;
}
HttpMessageParsingResult ParseHttpMessage(
const vuint8_t* buffer,
vint availableBytes,
bool requestMessage,
const WString& responseToMethod,
WString& parsedRequestMethod,
Ptr<HttpRequest>& request,
Ptr<HttpResponse>& response,
vint& consumedBytes,
bool& connectionClose
)
{
consumedBytes = 0;
connectionClose = false;
parsedRequestMethod = L"";
vint startLineEnd = FindCrlf(buffer, 0, availableBytes);
if (startLineEnd == -1)
{
auto possibleLineBytes = availableBytes > 0 && buffer[availableBytes - 1] == '\r' ? availableBytes - 1 : availableBytes;
return possibleLineBytes > HttpRequestLineSizeLimit
? (requestMessage ? HttpMessageParsingResult::UriTooLong : HttpMessageParsingResult::BadRequest)
: HttpMessageParsingResult::Incomplete;
}
if (startLineEnd > HttpRequestLineSizeLimit)
{
return requestMessage ? HttpMessageParsingResult::UriTooLong : HttpMessageParsingResult::BadRequest;
}
HttpVersion version;
WString method;
WString target;
vint statusCode = 0;
WString reason;
if (requestMessage)
{
if (!ParseRequestLine(buffer, startLineEnd, version, method, target)) return HttpMessageParsingResult::BadRequest;
parsedRequestMethod = method;
}
else
{
if (!ParseStatusLine(buffer, startLineEnd, version, statusCode, reason)) return HttpMessageParsingResult::BadRequest;
}
if (version.major != 1 || version.minor != 1)
{
return HttpMessageParsingResult::HttpVersionNotSupported;
}
List<HttpField> headers;
vint headersBegin = startLineEnd + 2;
vint reading = headersBegin;
vint bodyBegin = -1;
while (true)
{
vint lineEnd = FindCrlf(buffer, reading, availableBytes);
if (lineEnd == -1)
{
return availableBytes - headersBegin >= HttpHeaderBlockSizeLimit
? (requestMessage ? HttpMessageParsingResult::RequestHeaderFieldsTooLarge : HttpMessageParsingResult::BadRequest)
: HttpMessageParsingResult::Incomplete;
}
if (lineEnd + 2 - headersBegin > HttpHeaderBlockSizeLimit)
{
return requestMessage ? HttpMessageParsingResult::RequestHeaderFieldsTooLarge : HttpMessageParsingResult::BadRequest;
}
if (lineEnd == reading)
{
bodyBegin = lineEnd + 2;
break;
}
HttpField field;
if (!ParseFieldLine(buffer, reading, lineEnd, field)) return HttpMessageParsingResult::BadRequest;
headers.Add(std::move(field));
reading = lineEnd + 2;
}
HttpFraming framing;
auto framingResult = AnalyzeHttpFraming(headers, framing);
if (framingResult == HttpFramingAnalysisResult::Invalid) return HttpMessageParsingResult::BadRequest;
if (framingResult == HttpFramingAnalysisResult::UnsupportedTransferCoding) return HttpMessageParsingResult::NotImplemented;
if (requestMessage && HasHeader(headers, L"expect")) return HttpMessageParsingResult::ExpectationFailed;
auto hasContentLength = framing.kind == HttpFramingKind::ContentLength;
auto hasTransferEncoding = framing.kind == HttpFramingKind::Chunked;
auto headResponse = !requestMessage && responseToMethod == L"HEAD";
auto noContentResponse = !requestMessage && statusCode == 204;
auto notModifiedResponse = !requestMessage && statusCode == 304;
auto responseWithoutBody = headResponse || noContentResponse || notModifiedResponse;
if (noContentResponse && (hasContentLength || hasTransferEncoding)) return HttpMessageParsingResult::BadRequest;
if (notModifiedResponse && hasTransferEncoding) return HttpMessageParsingResult::BadRequest;
if (!requestMessage && !responseWithoutBody && !hasContentLength && !hasTransferEncoding) return HttpMessageParsingResult::BadRequest;
HttpBody body;
vint bodyBytes = 0;
if (!responseWithoutBody && hasTransferEncoding)
{
auto result = ParseHttpRequestBodyToChunksDetailed(buffer + bodyBegin, availableBytes - bodyBegin, body, bodyBytes);
if (result == HttpBodyDetailedParsingResult::Incomplete) return HttpMessageParsingResult::Incomplete;
if (result == HttpBodyDetailedParsingResult::PayloadTooLarge) return HttpMessageParsingResult::PayloadTooLarge;
if (result == HttpBodyDetailedParsingResult::TrailerFieldsTooLarge) return HttpMessageParsingResult::RequestHeaderFieldsTooLarge;
if (result == HttpBodyDetailedParsingResult::BadRequest) return HttpMessageParsingResult::BadRequest;
}
else if (!responseWithoutBody && hasContentLength)
{
if (framing.contentLength > (vuint64_t)HttpBodySizeLimit) return HttpMessageParsingResult::PayloadTooLarge;
bodyBytes = (vint)framing.contentLength;
if (availableBytes - bodyBegin < bodyBytes) return HttpMessageParsingResult::Incomplete;
if (bodyBytes > 0)
{
HttpBodyChunk chunk;
chunk.data.Resize(bodyBytes);
std::memcpy(&chunk.data[0], buffer + bodyBegin, (size_t)bodyBytes);
body.chunks.Add(std::move(chunk));
}
}
consumedBytes = bodyBegin + bodyBytes;
connectionClose = framing.connectionClose;
if (requestMessage)
{
request = Ptr(new HttpRequest);
request->version = version;
request->method = method;
request->requestTarget = target;
request->headers = std::move(headers);
request->body = std::move(body);
}
else
{
response = Ptr(new HttpResponse);
response->version = version;
response->statusCode = statusCode;
response->reason = reason;
response->headers = std::move(headers);
response->body = std::move(body);
}
return HttpMessageParsingResult::Succeeded;
}
bool ValidateField(const HttpField& field, bool trailer)
{
if (field.name.Length() == 0) return false;
for (vint i = 0; i < field.name.Length(); i++)
{
auto c = field.name[i];
if ((vuint32_t)c > 0x7F || !IsTokenCharacter((vuint8_t)c) || (c >= L'A' && c <= L'Z')) return false;
}
for (auto c : field.value)
{
if (!IsFieldValueCharacter(c)) return false;
}
if (trailer && (field.name == L"content-length" || field.name == L"transfer-encoding")) return false;
return true;
}
void AppendAscii(List<vuint8_t>& bytes, const wchar_t* text)
{
while (*text)
{
CHECK_ERROR(*text <= 0x7F, L"HTTP serialization requires ASCII protocol text.");
bytes.Add((vuint8_t)*text++);
}
}
void AppendAscii(List<vuint8_t>& bytes, const WString& text)
{
for (vint i = 0; i < text.Length(); i++)
{
CHECK_ERROR(text[i] <= 0x7F, L"HTTP serialization requires ASCII protocol text.");
bytes.Add((vuint8_t)text[i]);
}
}
void AppendDecimal(List<vuint8_t>& bytes, vint number)
{
CHECK_ERROR(number >= 0, L"HTTP serialization requires a non-negative decimal number.");
vuint8_t digits[32];
auto count = 0;
auto value = (vuint64_t)number;
do
{
digits[count++] = (vuint8_t)('0' + value % 10);
value /= 10;
} while (value > 0);
for (vint i = count - 1; i >= 0; i--) bytes.Add(digits[i]);
}
void AppendHexadecimal(List<vuint8_t>& bytes, vint number)
{
CHECK_ERROR(number > 0, L"HTTP chunk serialization requires a positive chunk size.");
vuint8_t digits[32];
auto count = 0;
auto value = (vuint64_t)number;
const wchar_t* hex = L"0123456789abcdef";
while (value > 0)
{
digits[count++] = (vuint8_t)hex[value % 16];
value /= 16;
}
for (vint i = count - 1; i >= 0; i--) bytes.Add(digits[i]);
}
void AppendCrlf(List<vuint8_t>& bytes)
{
bytes.Add('\r');
bytes.Add('\n');
}
void AppendField(List<vuint8_t>& bytes, const HttpField& field)
{
AppendAscii(bytes, field.name);
bytes.Add(':');
bytes.Add(' ');
for (auto c : field.value) bytes.Add(c);
AppendCrlf(bytes);
}
vint DecimalDigitCount(vint number)
{
vint count = 1;
while (number >= 10)
{
number /= 10;
count++;
}
return count;
}
vint HexadecimalDigitCount(vint number)
{
vint count = 1;
while (number >= 16)
{
number /= 16;
count++;
}
return count;
}
vint AsciiLength(const wchar_t* text)
{
vint count = 0;
while (text[count]) count++;
return count;
}
void AddBoundedSize(vint& total, vint adding, vint limit, const wchar_t* error)
{
CHECK_ERROR(adding >= 0 && total <= limit - adding, error);
total += adding;
}
vint ValidateBody(const HttpBody& body)
{
CHECK_ERROR(body.chunks.Count() <= HttpChunkCountLimit, L"HTTP body contains too many chunks.");
vint total = 0;
for (auto&& chunk : body.chunks)
{
CHECK_ERROR(chunk.data.Count() > 0, L"HTTP body chunks must contain at least one octet.");
CHECK_ERROR(total <= HttpBodySizeLimit - chunk.data.Count(), L"HTTP body exceeds the configured size limit.");
total += chunk.data.Count();
}
for (auto&& trailer : body.trailers)
{
CHECK_ERROR(ValidateField(trailer, true), L"HTTP body contains an invalid trailer field.");
}
return total;
}
Ptr<AsyncSocketBuffer> SerializeHttpMessage(HttpRequest* request, HttpResponse* response, const WString& responseToMethod, bool& connectionClose)
{
auto requestMessage = request != nullptr;
CHECK_ERROR(requestMessage != (response != nullptr), L"HTTP serialization requires exactly one message.");
auto&& version = requestMessage ? request->version : response->version;
auto&& headers = requestMessage ? request->headers : response->headers;
auto&& body = requestMessage ? request->body : response->body;
CHECK_ERROR(version.major == 1 && version.minor == 1, L"HTTP serialization only supports HTTP/1.1.");
vint startLineSize = 0;
if (requestMessage)
{
switch (ValidateHttpRequestLine(request->method, request->requestTarget))
{
case HttpRequestLineValidationResult::InvalidMethod:
CHECK_FAIL(L"HTTP request serialization received an invalid method.");
case HttpRequestLineValidationResult::InvalidRequestTarget:
CHECK_FAIL(L"HTTP request serialization received an invalid request target.");
case HttpRequestLineValidationResult::TooLong:
CHECK_FAIL(L"HTTP start line exceeds the configured size limit.");
default:
break;
}
CHECK_ERROR(TryGetHttpRequestLineSize(request->method, request->requestTarget, startLineSize), L"HTTP start line exceeds the configured size limit.");
}
else
{
CHECK_ERROR(response->statusCode >= 200 && response->statusCode <= 599, L"HTTP response serialization only supports final status codes from 200 through 599.");
for (vint i = 0; i < response->reason.Length(); i++)
{
CHECK_ERROR(response->reason[i] >= 0x20 && response->reason[i] <= 0x7E, L"HTTP response serialization received an invalid reason phrase.");
}
startLineSize = 13;
AddBoundedSize(startLineSize, response->reason.Length(), HttpRequestLineSizeLimit, L"HTTP start line exceeds the configured size limit.");
}
vint headerBlockSize = 2;
for (auto&& field : headers)
{
CHECK_ERROR(ValidateField(field, false), L"HTTP serialization received an invalid header field.");
AddBoundedSize(headerBlockSize, field.name.Length(), HttpHeaderBlockSizeLimit, L"HTTP header block exceeds the configured size limit.");
AddBoundedSize(headerBlockSize, 2, HttpHeaderBlockSizeLimit, L"HTTP header block exceeds the configured size limit.");
AddBoundedSize(headerBlockSize, field.value.Count(), HttpHeaderBlockSizeLimit, L"HTTP header block exceeds the configured size limit.");
AddBoundedSize(headerBlockSize, 2, HttpHeaderBlockSizeLimit, L"HTTP header block exceeds the configured size limit.");
}
HttpFraming framing;
CHECK_ERROR(AnalyzeHttpFraming(headers, framing) == HttpFramingAnalysisResult::Succeeded, L"HTTP serialization received invalid, ambiguous, or unsupported body framing.");
auto bodySize = ValidateBody(body);
auto headResponse = !requestMessage && responseToMethod == L"HEAD";
auto noContentResponse = !requestMessage && response->statusCode == 204;
auto notModifiedResponse = !requestMessage && response->statusCode == 304;
auto suppressBody = headResponse || noContentResponse || notModifiedResponse;
auto hasContentLength = framing.kind == HttpFramingKind::ContentLength;
auto hasTransferEncoding = framing.kind == HttpFramingKind::Chunked;
auto chunked = hasTransferEncoding;
auto generateContentLength = false;
auto generateTransferEncoding = false;
if (noContentResponse)
{
CHECK_ERROR(body.chunks.Count() == 0 && body.trailers.Count() == 0, L"An HTTP 204 response cannot contain a body.");
CHECK_ERROR(!hasContentLength && !hasTransferEncoding, L"An HTTP 204 response cannot contain body framing.");
chunked = false;
}
else if (notModifiedResponse)
{
CHECK_ERROR(body.chunks.Count() == 0 && body.trailers.Count() == 0, L"An HTTP 304 response cannot contain a body.");
CHECK_ERROR(!hasTransferEncoding, L"An HTTP 304 response cannot contain Transfer-Encoding.");
chunked = false;
}
else if (!hasContentLength && !hasTransferEncoding)
{
chunked = body.chunks.Count() > 1 || body.trailers.Count() > 0;
generateTransferEncoding = chunked;
generateContentLength = !chunked && (!requestMessage || body.chunks.Count() > 0);
}
if (!noContentResponse && !notModifiedResponse && !chunked)
{
CHECK_ERROR(body.trailers.Count() == 0 && body.chunks.Count() <= 1, L"A fixed HTTP body cannot contain multiple chunks or trailers.");
if (hasContentLength)
{
if (!headResponse || body.chunks.Count() > 0)
{
CHECK_ERROR(framing.contentLength == (vuint64_t)bodySize, L"HTTP Content-Length does not match the supplied body.");
}
}
else if (!generateContentLength && !headResponse)
{
CHECK_ERROR(requestMessage && bodySize == 0, L"An HTTP response requires explicit body framing.");
}
}
if (generateContentLength)
{
AddBoundedSize(headerBlockSize, AsciiLength(L"content-length: ") + DecimalDigitCount(bodySize) + 2, HttpHeaderBlockSizeLimit, L"HTTP header block exceeds the configured size limit.");
}
if (generateTransferEncoding)
{
AddBoundedSize(headerBlockSize, AsciiLength(L"transfer-encoding: chunked\r\n"), HttpHeaderBlockSizeLimit, L"HTTP header block exceeds the configured size limit.");
}
vint trailerBlockSize = 2;
for (auto&& trailer : body.trailers)
{
AddBoundedSize(trailerBlockSize, trailer.name.Length(), HttpTrailerBlockSizeLimit, L"HTTP trailer block exceeds the configured size limit.");
AddBoundedSize(trailerBlockSize, 2, HttpTrailerBlockSizeLimit, L"HTTP trailer block exceeds the configured size limit.");
AddBoundedSize(trailerBlockSize, trailer.value.Count(), HttpTrailerBlockSizeLimit, L"HTTP trailer block exceeds the configured size limit.");
AddBoundedSize(trailerBlockSize, 2, HttpTrailerBlockSizeLimit, L"HTTP trailer block exceeds the configured size limit.");
}
vint wireSize = startLineSize;
AddBoundedSize(wireSize, 2, HttpWireMessageSizeLimit, L"HTTP wire message exceeds the configured size limit.");
AddBoundedSize(wireSize, headerBlockSize, HttpWireMessageSizeLimit, L"HTTP wire message exceeds the configured size limit.");
if (!suppressBody && chunked)
{
for (auto&& chunk : body.chunks)
{
AddBoundedSize(wireSize, HexadecimalDigitCount(chunk.data.Count()) + 4, HttpWireMessageSizeLimit, L"HTTP wire message exceeds the configured size limit.");
AddBoundedSize(wireSize, chunk.data.Count(), HttpWireMessageSizeLimit, L"HTTP wire message exceeds the configured size limit.");
}
AddBoundedSize(wireSize, 3, HttpWireMessageSizeLimit, L"HTTP wire message exceeds the configured size limit.");
AddBoundedSize(wireSize, trailerBlockSize, HttpWireMessageSizeLimit, L"HTTP wire message exceeds the configured size limit.");
}
else if (!suppressBody)
{
AddBoundedSize(wireSize, bodySize, HttpWireMessageSizeLimit, L"HTTP wire message exceeds the configured size limit.");
}
List<vuint8_t> bytes;
if (requestMessage)
{
AppendAscii(bytes, request->method);
bytes.Add(' ');
AppendAscii(bytes, request->requestTarget);
bytes.Add(' ');
}
AppendAscii(bytes, L"HTTP/");
AppendDecimal(bytes, version.major);
bytes.Add('.');
AppendDecimal(bytes, version.minor);
if (!requestMessage)
{
bytes.Add(' ');
AppendDecimal(bytes, response->statusCode);
bytes.Add(' ');
AppendAscii(bytes, response->reason);
}
CHECK_ERROR(bytes.Count() <= HttpRequestLineSizeLimit, L"HTTP start line exceeds the configured size limit.");
AppendCrlf(bytes);
for (auto&& field : headers) AppendField(bytes, field);
if (generateContentLength)
{
AppendAscii(bytes, L"content-length: ");
AppendDecimal(bytes, bodySize);
AppendCrlf(bytes);
}
if (generateTransferEncoding)
{
AppendAscii(bytes, L"transfer-encoding: chunked\r\n");
}
AppendCrlf(bytes);
if (!suppressBody && chunked)
{
for (auto&& chunk : body.chunks)
{
AppendHexadecimal(bytes, chunk.data.Count());
AppendCrlf(bytes);
for (auto c : chunk.data) bytes.Add(c);
AppendCrlf(bytes);
}
AppendAscii(bytes, L"0\r\n");
for (auto&& trailer : body.trailers) AppendField(bytes, trailer);
AppendCrlf(bytes);
}
else if (!suppressBody && body.chunks.Count() == 1)
{
for (auto c : body.chunks[0].data) bytes.Add(c);
}
CHECK_ERROR(bytes.Count() <= HttpWireMessageSizeLimit, L"HTTP wire message exceeds the configured size limit.");
auto buffer = Ptr(new AsyncSocketBuffer);
buffer->data.Resize(bytes.Count());
if (bytes.Count() > 0)
{
std::memcpy(&buffer->data[0], &bytes[0], (size_t)bytes.Count());
}
connectionClose = framing.connectionClose;
return buffer;
}
}
HttpFramingAnalysisResult AnalyzeHttpFraming(const List<HttpField>& fields, HttpFraming& framing)
{
framing = HttpFraming();
bool contentLengthInitialized = false;
bool hasTransferEncoding = false;
List<WString> transferCodings;
bool transferCodingParameters = false;
for (auto&& field : fields)
{
if (field.name == L"content-length")
{
framing.contentLengthFieldCount++;
if (field.value.Count() == 0)
{
framing.contentLengthValuesPlainDecimal = false;
}
else
{
for (auto c : field.value)
{
if (!IsDigit(c))
{
framing.contentLengthValuesPlainDecimal = false;
break;
}
}
}
if (!ParseContentLength(field.value, contentLengthInitialized, framing.contentLength, framing.contentLengthValueCount))
{
return HttpFramingAnalysisResult::Invalid;
}
}
else if (field.name == L"transfer-encoding")
{
hasTransferEncoding = true;
if (!ParseTransferCodings(field.value, transferCodings, transferCodingParameters))
{
return HttpFramingAnalysisResult::Invalid;
}
}
else if (field.name == L"connection")
{
framing.connectionClose |= HasConnectionClose(field.value);
}
}
if (hasTransferEncoding && contentLengthInitialized)
{
return HttpFramingAnalysisResult::Invalid;
}
if (hasTransferEncoding)
{
if (
transferCodings.Count() != 1 ||
transferCodings[0] != L"chunked" ||
transferCodingParameters
)
{
return HttpFramingAnalysisResult::UnsupportedTransferCoding;
}
framing.kind = HttpFramingKind::Chunked;
}
else if (contentLengthInitialized)
{
framing.kind = HttpFramingKind::ContentLength;
}
return HttpFramingAnalysisResult::Succeeded;
}
const HttpField* FindHttpField(const List<HttpField>& fields, const WString& normalizedName)
{
for (auto&& field : fields)
{
if (field.name == normalizedName)
{
return &field;
}
}
return nullptr;
}
vint CountHttpFields(const List<HttpField>& fields, const WString& normalizedName)
{
vint count = 0;
for (auto&& field : fields)
{
if (field.name == normalizedName)
{
count++;
}
}
return count;
}
HttpField CreateAsciiHttpField(const WString& name, const WString& value)
{
CHECK_ERROR(name.Length() > 0, L"An HTTP field name cannot be empty.");
HttpField field;
Array<wchar_t> normalizedName(name.Length());
for (vint i = 0; i < name.Length(); i++)
{
auto c = name[i];
CHECK_ERROR((vuint32_t)c <= 0x7F && IsTokenCharacter((vuint8_t)c), L"An HTTP field name must contain only ASCII token characters.");
if (L'A' <= c && c <= L'Z') c += L'a' - L'A';
normalizedName[i] = c;
}
field.name = WString::CopyFrom(&normalizedName[0], normalizedName.Count());
field.value.Resize(value.Length());
for (vint i = 0; i < value.Length(); i++)
{
auto c = value[i];
CHECK_ERROR((vuint32_t)c <= 0x7F && IsFieldValueCharacter((vuint8_t)c), L"An HTTP field value must contain only valid ASCII field characters.");
field.value[i] = (vuint8_t)c;
}
return field;
}
bool DecodeAsciiHttpFieldValue(const Array<vuint8_t>& value, WString& text)
{
for (auto c : value)
{
if (c > 0x7F) return false;
}
if (value.Count() == 0)
{
text = WString::Empty;
return true;
}
Array<wchar_t> characters(value.Count());
for (vint i = 0; i < value.Count(); i++)
{
characters[i] = (wchar_t)value[i];
}
text = WString::CopyFrom(&characters[0], characters.Count());
return true;
}
bool HttpFieldValueEqualsAscii(const Array<vuint8_t>& value, const WString& expected)
{
if (value.Count() != expected.Length()) return false;
for (vint i = 0; i < value.Count(); i++)
{
if ((vuint32_t)expected[i] > 0x7F || value[i] != (vuint8_t)expected[i]) return false;
}
return true;
}
bool TryGetHttpBodySize(const HttpBody& body, vint& size)
{
vint total = 0;
for (auto&& chunk : body.chunks)
{
if (chunk.data.Count() > HttpBodySizeLimit - total) return false;
total += chunk.data.Count();
}
size = total;
return true;
}
bool FlattenHttpBody(const HttpBody& body, Array<vuint8_t>& bytes)
{
vint size = 0;
if (!TryGetHttpBodySize(body, size)) return false;
Array<vuint8_t> flattened(size);
vint offset = 0;
for (auto&& chunk : body.chunks)
{
if (chunk.data.Count() > 0)
{
std::memcpy(&flattened[offset], &chunk.data[0], (size_t)chunk.data.Count());
offset += chunk.data.Count();
}
}
bytes = std::move(flattened);
return true;
}
void SetHttpBodyBytes(HttpBody& body, Array<vuint8_t>&& bytes)
{
CHECK_ERROR(bytes.Count() <= HttpBodySizeLimit, L"HTTP body exceeds the configured size limit.");
Array<vuint8_t> replacement = std::move(bytes);
body.chunks.Clear();
body.trailers.Clear();
if (replacement.Count() > 0)
{
HttpBodyChunk chunk;
chunk.data = std::move(replacement);
body.chunks.Add(std::move(chunk));
}
}
bool EncodeStrictUtf8(const WString& text, Array<vuint8_t>& bytes)
{
List<vuint8_t> encoded;
for (vint i = 0; i < text.Length(); i++)
{
auto code = (vuint32_t)text[i];
if constexpr (sizeof(wchar_t) == 2)
{
if (0xD800 <= code && code <= 0xDBFF)
{
if (++i == text.Length()) return false;
auto low = (vuint32_t)text[i];
if (low < 0xDC00 || low > 0xDFFF) return false;
code = 0x10000 + ((code - 0xD800) << 10) + (low - 0xDC00);
}
else if (0xDC00 <= code && code <= 0xDFFF)
{
return false;
}
}
else if (code > 0x10FFFF || (0xD800 <= code && code <= 0xDFFF))
{
return false;
}
vint adding = code < 0x80 ? 1 : code < 0x800 ? 2 : code < 0x10000 ? 3 : 4;
if (encoded.Count() > (std::numeric_limits<vint>::max)() - adding) return false;
if (adding == 1)
{
encoded.Add((vuint8_t)code);
}
else
{
static const vuint8_t prefixes[] = { 0, 0xC0, 0xE0, 0xF0 };
vuint8_t output[4];
for (vint j = adding - 1; j > 0; j--)
{
output[j] = 0x80 | (code & 0x3F);
code >>= 6;
}
output[0] = prefixes[adding - 1] | (vuint8_t)code;
for (vint j = 0; j < adding; j++) encoded.Add(output[j]);
}
}
Array<vuint8_t> result(encoded.Count());
for (vint i = 0; i < encoded.Count(); i++) result[i] = encoded[i];
bytes = std::move(result);
return true;
}
bool DecodeStrictUtf8(const vuint8_t* bytes, vint count, WString& text)
{
if (count < 0 || (!bytes && count > 0)) return false;
List<wchar_t> characters;
for (vint i = 0; i < count;)
{
auto first = bytes[i++];
vuint32_t code = 0;
vint following = 0;
if (first < 0x80)
{
code = first;
}
else if (0xC2 <= first && first <= 0xDF)
{
code = first & 0x1F;
following = 1;
}
else if (0xE0 <= first && first <= 0xEF)
{
code = first & 0x0F;
following = 2;
}
else if (0xF0 <= first && first <= 0xF4)
{
code = first & 0x07;
following = 3;
}
else
{
return false;
}
if (following > count - i) return false;
for (vint j = 0; j < following; j++)
{
auto next = bytes[i++];
if ((next & 0xC0) != 0x80) return false;
code = (code << 6) | (next & 0x3F);
}
if (
(following == 1 && code < 0x80) ||
(following == 2 && code < 0x800) ||
(following == 3 && code < 0x10000) ||
code > 0x10FFFF ||
(0xD800 <= code && code <= 0xDFFF)
)
{
return false;
}
if constexpr (sizeof(wchar_t) == 2)
{
if (code <= 0xFFFF)
{
characters.Add((wchar_t)code);
}
else
{
if (characters.Count() > (std::numeric_limits<vint>::max)() - 2) return false;
code -= 0x10000;
characters.Add((wchar_t)(0xD800 + (code >> 10)));
characters.Add((wchar_t)(0xDC00 + (code & 0x3FF)));
}
}
else
{
characters.Add((wchar_t)code);
}
}
text = characters.Count() == 0 ? WString::Empty : WString::CopyFrom(&characters[0], characters.Count());
return true;
}
HttpRequestLineValidationResult ValidateHttpRequestLine(const WString& method, const WString& requestTarget)
{
if (method.Length() == 0) return HttpRequestLineValidationResult::InvalidMethod;
for (vint i = 0; i < method.Length(); i++)
{
auto c = method[i];
if ((vuint32_t)c > 0x7F || !IsTokenCharacter((vuint8_t)c)) return HttpRequestLineValidationResult::InvalidMethod;
}
if (requestTarget.Length() == 0) return HttpRequestLineValidationResult::InvalidRequestTarget;
for (vint i = 0; i < requestTarget.Length(); i++)
{
auto c = requestTarget[i];
if (c < 0x21 || c > 0x7E) return HttpRequestLineValidationResult::InvalidRequestTarget;
}
vint requestLineSize = 0;
if (!TryGetHttpRequestLineSize(method, requestTarget, requestLineSize)) return HttpRequestLineValidationResult::TooLong;
return HttpRequestLineValidationResult::Succeeded;
}
/***********************************************************************
HttpRequestBody
***********************************************************************/
namespace
{
HttpBodyDetailedParsingResult ParseHttpRequestBodyToChunksDetailed(
const vuint8_t* buffer,
vint availableBytes,
HttpBody& output,
vint& consumedBytes
)
{
consumedBytes = 0;
if (availableBytes < 0 || (!buffer && availableBytes > 0)) return HttpBodyDetailedParsingResult::BadRequest;
HttpBody parsed;
vint reading = 0;
vint decodedBytes = 0;
while (true)
{
vint lineEnd = FindCrlf(buffer, reading, availableBytes);
if (lineEnd == -1)
{
auto trailingCrlfPrefix = availableBytes > reading && buffer[availableBytes - 1] == '\r';
vint prefixEnd = trailingCrlfPrefix ? availableBytes - 1 : availableBytes;
auto validPrefix = trailingCrlfPrefix
? ValidateCompleteChunkSizeLine(buffer, reading, prefixEnd)
: ValidateChunkSizeLinePrefix(buffer, reading, prefixEnd);
if (prefixEnd - reading > HttpChunkSizeLineLimit || !validPrefix)
{
return HttpBodyDetailedParsingResult::BadRequest;
}
return HttpBodyDetailedParsingResult::Incomplete;
}
if (lineEnd - reading > HttpChunkSizeLineLimit) return HttpBodyDetailedParsingResult::BadRequest;
if (lineEnd + 2 > HttpWireMessageSizeLimit) return HttpBodyDetailedParsingResult::PayloadTooLarge;
vint numberEnd = reading;
vuint64_t chunkSize = 0;
while (numberEnd < lineEnd && IsHexDigit(buffer[numberEnd]))
{
auto digit = (vuint64_t)HexDigitValue(buffer[numberEnd++]);
if (chunkSize > ((std::numeric_limits<vuint64_t>::max)() - digit) / 16) return HttpBodyDetailedParsingResult::PayloadTooLarge;
chunkSize = chunkSize * 16 + digit;
}
if (numberEnd == reading) return HttpBodyDetailedParsingResult::BadRequest;
if (chunkSize > (vuint64_t)HttpBodySizeLimit) return HttpBodyDetailedParsingResult::PayloadTooLarge;
if (numberEnd < lineEnd)
{
vint firstExtension = numberEnd;
while (firstExtension < lineEnd && IsOws(buffer[firstExtension])) firstExtension++;
if (firstExtension == lineEnd || buffer[firstExtension] != ';') return HttpBodyDetailedParsingResult::BadRequest;
}
vint extensionReading = numberEnd;
if (!ParseSemicolonParameters(buffer, extensionReading, lineEnd)) return HttpBodyDetailedParsingResult::BadRequest;
reading = lineEnd + 2;
if (chunkSize == 0)
{
vint trailerBegin = reading;
while (true)
{
vint trailerEnd = FindCrlf(buffer, reading, availableBytes);
if (trailerEnd == -1)
{
return availableBytes - trailerBegin >= HttpTrailerBlockSizeLimit
? HttpBodyDetailedParsingResult::TrailerFieldsTooLarge
: HttpBodyDetailedParsingResult::Incomplete;
}
if (trailerEnd + 2 - trailerBegin > HttpTrailerBlockSizeLimit) return HttpBodyDetailedParsingResult::TrailerFieldsTooLarge;
if (trailerEnd + 2 > HttpWireMessageSizeLimit) return HttpBodyDetailedParsingResult::PayloadTooLarge;
if (trailerEnd == reading)
{
consumedBytes = trailerEnd + 2;
output = std::move(parsed);
return HttpBodyDetailedParsingResult::Succeeded;
}
HttpField trailer;
if (!ParseFieldLine(buffer, reading, trailerEnd, trailer) || trailer.name == L"content-length" || trailer.name == L"transfer-encoding")
{
return HttpBodyDetailedParsingResult::BadRequest;
}
parsed.trailers.Add(std::move(trailer));
reading = trailerEnd + 2;
}
}
if (chunkSize > (vuint64_t)(HttpBodySizeLimit - decodedBytes)) return HttpBodyDetailedParsingResult::PayloadTooLarge;
vint chunkBytes = (vint)chunkSize;
if (availableBytes - reading < chunkBytes) return HttpBodyDetailedParsingResult::Incomplete;
auto terminatorBytes = availableBytes - reading - chunkBytes;
if (terminatorBytes == 0) return HttpBodyDetailedParsingResult::Incomplete;
if (buffer[reading + chunkBytes] != '\r') return HttpBodyDetailedParsingResult::BadRequest;
if (terminatorBytes == 1) return HttpBodyDetailedParsingResult::Incomplete;
if (buffer[reading + chunkBytes + 1] != '\n') return HttpBodyDetailedParsingResult::BadRequest;
if (reading > HttpWireMessageSizeLimit - chunkBytes - 2) return HttpBodyDetailedParsingResult::PayloadTooLarge;
if (parsed.chunks.Count() >= HttpChunkCountLimit) return HttpBodyDetailedParsingResult::PayloadTooLarge;
HttpBodyChunk chunk;
chunk.data.Resize(chunkBytes);
std::memcpy(&chunk.data[0], buffer + reading, (size_t)chunkBytes);
parsed.chunks.Add(std::move(chunk));
decodedBytes += chunkBytes;
reading += chunkBytes + 2;
}
}
}
HttpRequestBodyParsingResult ParseHttpRequestBodyToChunks(
const vuint8_t* buffer,
vint availableBytes,
HttpBody& output,
vint& consumedBytes
)
{
auto result = ParseHttpRequestBodyToChunksDetailed(buffer, availableBytes, output, consumedBytes);
switch (result)
{
case HttpBodyDetailedParsingResult::Succeeded:
return HttpRequestBodyParsingResult::Succeeded;
case HttpBodyDetailedParsingResult::Incomplete:
return HttpRequestBodyParsingResult::Incomplete;
default:
return HttpRequestBodyParsingResult::Invalid;
}
}
/***********************************************************************
IHttpRequestCallback
***********************************************************************/
void IHttpRequestCallback::OnReadRequest(Ptr<HttpRequest>)
{
}
void IHttpRequestCallback::OnReadRequestFailure(HttpRequestFailure)
{
}
void IHttpRequestCallback::OnReadResponse(Ptr<HttpResponse>)
{
}
void IHttpRequestCallback::OnReadResponseFailure(HttpResponseFailure)
{
OnError(L"The HTTP client received a 404 Not Found response.", true);
}
void IHttpRequestCallback::OnWriteCompleted()
{
}
void IHttpRequestCallback::OnError(const WString&, bool)
{
}
void IHttpRequestCallback::OnConnected()
{
}
void IHttpRequestCallback::OnDisconnected()
{
}
/***********************************************************************
HttpRequestTimeoutController
***********************************************************************/
namespace
{
class HttpRequestTimeoutController : public Object, public virtual IHttpRequestTimeoutController
{
private:
class State : public Object
{
public:
CriticalSection lock;
ConditionVariable cv;
Func<void()> callback;
std::chrono::steady_clock::time_point
deadline;
vint duration = 0;
bool armed = false;
bool workerRunning = false;
vint activeCallbacks = 0;
};
static thread_local State* currentCallbackState;
Ptr<State> state = Ptr(new State);
static void Run(Ptr<State> state)
{
while (true)
{
Func<void()> callback;
state->lock.Enter();
while (state->armed)
{
auto now = std::chrono::steady_clock::now();
if (now >= state->deadline)
{
callback = state->callback;
state->callback = {};
state->armed = false;
state->activeCallbacks++;
break;
}
auto remaining = std::chrono::ceil<std::chrono::milliseconds>(state->deadline - now).count();
auto wait = remaining > (std::numeric_limits<vint>::max)()
? (std::numeric_limits<vint>::max)()
: (vint)remaining;
state->cv.SleepWithForTime(state->lock, wait);
}
if (!callback)
{
state->workerRunning = false;
state->cv.WakeAllPendings();
state->lock.Leave();
return;
}
state->lock.Leave();
auto previous = currentCallbackState;
currentCallbackState = state.Obj();
try
{
callback();
}
catch (...)
{
}
currentCallbackState = previous;
CS_LOCK(state->lock)
{
state->activeCallbacks--;
state->cv.WakeAllPendings();
}
}
}
public:
~HttpRequestTimeoutController()
{
CancelAndWait();
}
void Arm(vint milliseconds, const Func<void()>& callback) override
{
CHECK_ERROR(milliseconds > 0, L"The HTTP timeout controller requires a positive duration.");
bool queueWorker = false;
CS_LOCK(state->lock)
{
CHECK_ERROR(!state->armed, L"The HTTP timeout controller is already armed.");
state->callback = callback;
state->duration = milliseconds;
state->deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(milliseconds);
state->armed = true;
if (!state->workerRunning)
{
state->workerRunning = true;
queueWorker = true;
}
state->cv.WakeAllPendings();
}
if (queueWorker)
{
auto captured = state;
auto queued = ThreadPoolLite::Queue(Func<void()>([captured]()
{
Run(captured);
}));
if (!queued)
{
CS_LOCK(state->lock)
{
state->callback = {};
state->armed = false;
state->workerRunning = false;
state->cv.WakeAllPendings();
}
CHECK_ERROR(false, L"The HTTP timeout controller could not queue its deadline worker.");
}
}
}
void Refresh() override
{
CS_LOCK(state->lock)
{
if (state->armed)
{
state->deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(state->duration);
state->cv.WakeAllPendings();
}
}
}
void CancelAndWait() override
{
auto nestedCallback = currentCallbackState == state.Obj();
state->lock.Enter();
state->armed = false;
state->callback = {};
state->cv.WakeAllPendings();
if (!nestedCallback)
{
while (state->workerRunning || state->activeCallbacks > 0)
{
state->cv.SleepWith(state->lock);
}
}
state->lock.Leave();
}
};
thread_local HttpRequestTimeoutController::State* HttpRequestTimeoutController::currentCallbackState = nullptr;
}
Ptr<IHttpRequestTimeoutController> CreateHttpRequestTimeoutController()
{
return Ptr(new HttpRequestTimeoutController);
}
/***********************************************************************
HttpRequestCallbackDomain
***********************************************************************/
thread_local HttpRequestCallbackDomain::CallbackFrame* HttpRequestCallbackDomain::currentCallbackFrame = nullptr;
HttpRequestCallbackDomain::CallbackFrame::CallbackFrame(Ptr<HttpRequestCallbackDomain> _domain)
: domain(_domain)
, previous(currentCallbackFrame)
{
currentCallbackFrame = this;
CS_LOCK(domain->lockState)
{
domain->activeCallbacks++;
}
}
HttpRequestCallbackDomain::CallbackFrame::~CallbackFrame()
{
currentCallbackFrame = previous;
CS_LOCK(domain->lockState)
{
domain->activeCallbacks--;
domain->cvState.WakeAllPendings();
}
}
vint HttpRequestCallbackDomain::CurrentCallbackDepth()
{
vint depth = 0;
for (auto frame = currentCallbackFrame; frame; frame = frame->previous)
{
if (frame->domain.Obj() == this) depth++;
}
return depth;
}
void HttpRequestCallbackDomain::WaitForCallbacks(vint callbackDepth)
{
CS_LOCK(lockState)
{
while (activeCallbacks > callbackDepth)
{
cvState.SleepWith(lockState);
}
}
}
/***********************************************************************
HttpRequestConnectionLifecycle
***********************************************************************/
class HttpRequestConnectionLifecycle : public Object
{
public:
class RetainedAdapterRelease
{
public:
Ptr<Object> adapter;
Func<void()> drainedCallback;
~RetainedAdapterRelease()
{
if (drainedCallback)
{
try
{
drainedCallback();
}
catch (...)
{
}
}
}
};
IAsyncSocketConnection* socketConnection = nullptr;
HttpRequestConnectionDirection direction = HttpRequestConnectionDirection::Server;
bool responseNotFoundIsFatal = false;
Ptr<HttpRequestCallbackDomain> callbackDomain;
Ptr<IHttpRequestTimeoutController> timeoutController;
Ptr<Object> retainedAdapter;
Func<void()> drainedCallback;
CriticalSection lockState;
ConditionVariable cvState;
IHttpRequestCallback* callback = nullptr;
bool callbackInstalling = false;
vint activeCallbacks = 0;
vint activeSocketCallbacks = 0;
vint activeSocketCalls = 0;
bool stopStarted = false;
bool stopFinished = false;
bool terminal = false;
bool disconnectedNotified = false;
bool disconnectDelivering = false;
bool disconnectFinished = false;
bool readingStarted = false;
bool parserFailed = false;
bool peerDisconnected = false;
List<vuint8_t> receiveBuffer;
bool timeoutArmed = false;
bool awaitingResponse = false;
bool exchangeActive = false;
bool closeAfterExchange = false;
WString activeRequestMethod;
vint activeResponseTimeout = HttpIncompleteMessageTimeout;
Ptr<HttpResponse> heldResponse;
bool fatalAfterResponse = false;
bool responseDelivering = false;
bool responseFinalizing = false;
Ptr<AsyncSocketBuffer> deferredRequestWrite;
bool deferredRequestClose = false;
WString deferredRequestMethod;
vint deferredResponseTimeout = HttpIncompleteMessageTimeout;
Ptr<AsyncSocketBuffer> pendingWrite;
bool writePending = false;
void TakeRetainedAdapterIfDrained(RetainedAdapterRelease& releasing)
{
if (stopFinished && disconnectFinished && activeCallbacks == 0 && activeSocketCallbacks == 0 && activeSocketCalls == 0)
{
releasing.adapter = std::move(retainedAdapter);
releasing.drainedCallback = drainedCallback;
drainedCallback = {};
}
}
};
/***********************************************************************
HttpRequestConnection callback frames
***********************************************************************/
struct HttpRequestConnection::CallbackFrame
{
Ptr<Lifecycle> state;
CallbackFrame* previous = nullptr;
HttpRequestCallbackDomain::CallbackFrame domainFrame;
CallbackFrame(Ptr<Lifecycle> _state)
: state(_state)
, previous(currentCallbackFrame)
, domainFrame(state->callbackDomain)
{
currentCallbackFrame = this;
}
~CallbackFrame()
{
currentCallbackFrame = previous;
Lifecycle::RetainedAdapterRelease releasing;
CS_LOCK(state->lockState)
{
state->activeCallbacks--;
state->TakeRetainedAdapterIfDrained(releasing);
state->cvState.WakeAllPendings();
}
}
};
struct HttpRequestConnection::SocketCallbackFrame
{
Ptr<Lifecycle> state;
SocketCallbackFrame* previous = nullptr;
SocketCallbackFrame(Ptr<Lifecycle> _state)
: state(_state)
, previous(currentSocketCallbackFrame)
{
currentSocketCallbackFrame = this;
CS_LOCK(state->lockState)
{
state->activeSocketCallbacks++;
}
}
~SocketCallbackFrame()
{
currentSocketCallbackFrame = previous;
Lifecycle::RetainedAdapterRelease releasing;
CS_LOCK(state->lockState)
{
state->activeSocketCallbacks--;
state->TakeRetainedAdapterIfDrained(releasing);
state->cvState.WakeAllPendings();
}
}
};
struct HttpRequestConnection::TimeoutCallbackFrame
{
Ptr<Lifecycle> state;
TimeoutCallbackFrame* previous = nullptr;
TimeoutCallbackFrame(Ptr<Lifecycle> _state)
: state(_state)
, previous(currentTimeoutCallbackFrame)
{
currentTimeoutCallbackFrame = this;
}
~TimeoutCallbackFrame()
{
currentTimeoutCallbackFrame = previous;
}
};
thread_local HttpRequestConnection::CallbackFrame* HttpRequestConnection::currentCallbackFrame = nullptr;
thread_local HttpRequestConnection::SocketCallbackFrame* HttpRequestConnection::currentSocketCallbackFrame = nullptr;
thread_local HttpRequestConnection::TimeoutCallbackFrame* HttpRequestConnection::currentTimeoutCallbackFrame = nullptr;
/***********************************************************************
HttpRequestConnection helpers
***********************************************************************/
vint HttpRequestConnection::CurrentCallbackDepth(Ptr<Lifecycle> state)
{
vint depth = 0;
for (auto frame = currentCallbackFrame; frame; frame = frame->previous)
{
if (frame->state.Obj() == state.Obj()) depth++;
}
return depth;
}
vint HttpRequestConnection::CurrentSocketCallbackDepth(Ptr<Lifecycle> state)
{
vint depth = 0;
for (auto frame = currentSocketCallbackFrame; frame; frame = frame->previous)
{
if (frame->state.Obj() == state.Obj()) depth++;
}
return depth;
}
vint HttpRequestConnection::CurrentTimeoutCallbackDepth(Ptr<Lifecycle> state)
{
vint depth = 0;
for (auto frame = currentTimeoutCallbackFrame; frame; frame = frame->previous)
{
if (frame->state.Obj() == state.Obj()) depth++;
}
return depth;
}
void HttpRequestConnection::FinishSocketCall(Ptr<Lifecycle> state)
{
bool completePeerStop = false;
CS_LOCK(state->lockState)
{
state->activeSocketCalls--;
completePeerStop = state->activeSocketCalls == 0 && state->peerDisconnected && !state->stopFinished;
state->cvState.WakeAllPendings();
}
if (completePeerStop)
{
StopConnection(state);
}
Lifecycle::RetainedAdapterRelease releasing;
CS_LOCK(state->lockState)
{
state->TakeRetainedAdapterIfDrained(releasing);
}
}
template<typename TCallback>
void HttpRequestConnection::InvokeHttpCallback(Ptr<Lifecycle> state, bool allowTerminal, TCallback&& invoke)
{
IHttpRequestCallback* installed = nullptr;
auto callbackDepth = CurrentCallbackDepth(state);
state->lockState.Enter();
while (state->callbackInstalling && callbackDepth == 0 && state->callback)
{
state->cvState.SleepWith(state->lockState);
}
if (state->callback && (allowTerminal || (!state->stopStarted && !state->terminal)))
{
installed = state->callback;
state->activeCallbacks++;
}
state->lockState.Leave();
if (installed)
{
CallbackFrame frame(state);
invoke(installed);
}
}
void HttpRequestConnection::SubmitWrite(Ptr<Lifecycle> state, IAsyncSocketConnection* connection, Ptr<AsyncSocketBuffer> buffer)
{
try
{
connection->WriteAsync(buffer);
}
catch (...)
{
CS_LOCK(state->lockState)
{
if (state->pendingWrite.Obj() == buffer.Obj())
{
state->pendingWrite = nullptr;
state->writePending = false;
if (state->direction == HttpRequestConnectionDirection::Client)
{
state->exchangeActive = false;
state->closeAfterExchange = false;
}
}
state->cvState.WakeAllPendings();
}
FinishSocketCall(state);
throw;
}
FinishSocketCall(state);
}
void HttpRequestConnection::ReportRequestFailure(Ptr<Lifecycle> state, HttpRequestFailure failure, bool timeoutOnly, bool reserved)
{
bool report = false;
bool cancelTimeout = false;
CS_LOCK(state->lockState)
{
if (
reserved &&
state->direction == HttpRequestConnectionDirection::Server &&
!state->stopStarted &&
!state->terminal &&
state->parserFailed &&
state->awaitingResponse
)
{
cancelTimeout = state->timeoutArmed;
state->timeoutArmed = false;
report = true;
}
else if (
state->direction == HttpRequestConnectionDirection::Server &&
!state->stopStarted &&
!state->terminal &&
!state->parserFailed &&
!state->awaitingResponse &&
(!timeoutOnly || state->timeoutArmed)
)
{
state->parserFailed = true;
state->awaitingResponse = true;
state->closeAfterExchange = true;
state->receiveBuffer.Clear();
cancelTimeout = state->timeoutArmed;
state->timeoutArmed = false;
report = true;
state->cvState.WakeAllPendings();
}
}
if (!report)
{
return;
}
if (cancelTimeout)
{
state->timeoutController->CancelAndWait();
}
try
{
InvokeHttpCallback(state, false, [&](IHttpRequestCallback* installed)
{
installed->OnReadRequestFailure(failure);
});
}
catch (...)
{
StopConnection(state);
throw;
}
bool closeWithoutResponse = false;
CS_LOCK(state->lockState)
{
closeWithoutResponse = !state->stopStarted && !state->terminal && !state->peerDisconnected && !state->writePending;
}
if (closeWithoutResponse)
{
StopConnection(state);
}
}
void HttpRequestConnection::InstallTimeout(Ptr<Lifecycle> state, vint milliseconds, const WString& error)
{
CHECK_ERROR(milliseconds > 0, L"HttpRequestConnection requires a positive timeout when arming a deadline.");
auto captured = state;
auto capturedError = error;
try
{
state->timeoutController->Arm(milliseconds, Func<void()>([captured, capturedError]()
{
TimeoutCallbackFrame timeoutCallbackFrame(captured);
if (captured->direction == HttpRequestConnectionDirection::Server)
{
ReportRequestFailure(captured, HttpRequestFailure::RequestTimeout, true);
return;
}
bool expired = false;
CS_LOCK(captured->lockState)
{
if (captured->timeoutArmed && !captured->stopStarted && !captured->terminal && !captured->parserFailed)
{
captured->timeoutArmed = false;
captured->parserFailed = true;
expired = true;
}
}
if (expired)
{
ReportFatalError(captured, capturedError);
}
}));
bool cancelStaleTimeout = false;
CS_LOCK(state->lockState)
{
cancelStaleTimeout = !state->timeoutArmed || state->stopStarted || state->terminal;
}
if (cancelStaleTimeout)
{
state->timeoutController->CancelAndWait();
}
}
catch (...)
{
CS_LOCK(state->lockState)
{
state->timeoutArmed = false;
}
ReportFatalError(state, L"The HTTP timeout controller failed while arming a message timeout.");
}
}
void HttpRequestConnection::RefreshTimeout(Ptr<Lifecycle> state)
{
try
{
state->timeoutController->Refresh();
}
catch (...)
{
CS_LOCK(state->lockState)
{
state->timeoutArmed = false;
}
ReportFatalError(state, L"The HTTP timeout controller failed while refreshing a message timeout.");
}
}
void HttpRequestConnection::ReportResponseFailure(Ptr<Lifecycle> state, HttpResponseFailure failure)
{
bool report = false;
CS_LOCK(state->lockState)
{
if (!state->terminal && !state->stopStarted)
{
state->terminal = true;
state->parserFailed = true;
state->timeoutArmed = false;
state->pendingWrite = nullptr;
state->writePending = false;
state->heldResponse = nullptr;
state->fatalAfterResponse = false;
state->responseDelivering = false;
state->deferredRequestWrite = nullptr;
state->deferredRequestClose = false;
state->deferredRequestMethod = L"";
report = true;
state->cvState.WakeAllPendings();
}
}
if (report)
{
state->timeoutController->CancelAndWait();
try
{
InvokeHttpCallback(state, true, [&](IHttpRequestCallback* installed)
{
installed->OnReadResponseFailure(failure);
});
}
catch (...)
{
StopConnection(state);
throw;
}
StopConnection(state);
}
}
void HttpRequestConnection::DeliverResponse(Ptr<Lifecycle> state, Ptr<HttpResponse> response, bool closeAfterDelivery)
{
if (state->responseNotFoundIsFatal && response->statusCode == (vint)HttpResponseFailure::NotFound)
{
ReportResponseFailure(state, HttpResponseFailure::NotFound);
return;
}
try
{
InvokeHttpCallback(state, false, [&](IHttpRequestCallback* installed)
{
installed->OnReadResponse(response);
});
}
catch (...)
{
bool notifyDisconnected = false;
CS_LOCK(state->lockState)
{
state->responseDelivering = false;
state->deferredRequestWrite = nullptr;
state->deferredRequestClose = false;
state->deferredRequestMethod = L"";
notifyDisconnected = state->peerDisconnected;
state->responseFinalizing = notifyDisconnected;
state->cvState.WakeAllPendings();
}
if (notifyDisconnected)
{
StopConnection(state);
CS_LOCK(state->lockState)
{
state->responseFinalizing = false;
state->cvState.WakeAllPendings();
}
}
throw;
}
bool fatalAfterDelivery = false;
bool notifyDisconnected = false;
Ptr<AsyncSocketBuffer> deferredRequestWrite;
IAsyncSocketConnection* deferredRequestConnection = nullptr;
CS_LOCK(state->lockState)
{
fatalAfterDelivery = state->fatalAfterResponse;
state->fatalAfterResponse = false;
notifyDisconnected = state->peerDisconnected;
state->responseDelivering = false;
state->responseFinalizing = fatalAfterDelivery || closeAfterDelivery || notifyDisconnected;
if (state->responseFinalizing)
{
state->deferredRequestWrite = nullptr;
state->deferredRequestClose = false;
state->deferredRequestMethod = L"";
}
else if (state->deferredRequestWrite && !state->stopStarted && !state->terminal && state->socketConnection)
{
deferredRequestWrite = std::move(state->deferredRequestWrite);
state->exchangeActive = true;
state->closeAfterExchange = state->deferredRequestClose;
state->deferredRequestClose = false;
state->activeRequestMethod = std::move(state->deferredRequestMethod);
state->activeResponseTimeout = state->deferredResponseTimeout;
state->pendingWrite = deferredRequestWrite;
state->writePending = true;
deferredRequestConnection = state->socketConnection;
state->activeSocketCalls++;
}
state->cvState.WakeAllPendings();
}
try
{
if (fatalAfterDelivery)
{
ReportFatalError(state, L"The HTTP client received an unsolicited response after its exchange completed.");
}
else if (notifyDisconnected)
{
StopConnection(state);
}
else if (closeAfterDelivery)
{
StopConnection(state);
}
}
catch (...)
{
CS_LOCK(state->lockState)
{
state->responseFinalizing = false;
state->cvState.WakeAllPendings();
}
throw;
}
CS_LOCK(state->lockState)
{
state->responseFinalizing = false;
state->cvState.WakeAllPendings();
}
if (!fatalAfterDelivery && !closeAfterDelivery && !notifyDisconnected)
{
if (deferredRequestWrite)
{
try
{
SubmitWrite(state, deferredRequestConnection, deferredRequestWrite);
}
catch (...)
{
ReportFatalError(state, L"The HTTP client failed to submit a deferred request write.");
return;
}
}
ProcessBufferedInput(state);
}
}
void HttpRequestConnection::ProcessBufferedInput(Ptr<Lifecycle> state)
{
Ptr<HttpRequest> request;
Ptr<HttpResponse> response;
bool cancelTimeout = false;
bool deliverRequest = false;
bool deliverResponse = false;
bool closeAfterDelivery = false;
bool requestFailure = false;
bool clientFailure = false;
HttpRequestFailure failure = HttpRequestFailure::BadRequest;
WString parsedRequestMethod;
state->lockState.Enter();
if (state->stopStarted || state->terminal || state->parserFailed || state->peerDisconnected)
{
state->lockState.Leave();
return;
}
auto parseEnabled = state->direction == HttpRequestConnectionDirection::Server
? !state->awaitingResponse
: state->exchangeActive && !state->heldResponse && !state->responseDelivering && !state->responseFinalizing;
if (!parseEnabled)
{
state->lockState.Leave();
return;
}
if (state->receiveBuffer.Count() == 0)
{
state->lockState.Leave();
return;
}
vint consumedBytes = 0;
bool messageClose = false;
auto result = ParseHttpMessage(
&state->receiveBuffer[0],
state->receiveBuffer.Count(),
state->direction == HttpRequestConnectionDirection::Server,
state->activeRequestMethod,
parsedRequestMethod,
request,
response,
consumedBytes,
messageClose
);
if (result == HttpMessageParsingResult::Incomplete)
{
bool armTimeout = false;
bool refreshTimeout = false;
auto serverSide = state->direction == HttpRequestConnectionDirection::Server;
if (serverSide && parsedRequestMethod.Length() > 0)
{
state->activeRequestMethod = parsedRequestMethod;
}
auto timeout = serverSide ? HttpIncompleteMessageTimeout : state->activeResponseTimeout;
auto canArm = timeout > 0 && (serverSide || !state->writePending);
if (canArm && !state->timeoutArmed)
{
state->timeoutArmed = true;
armTimeout = true;
}
else if (canArm && serverSide)
{
refreshTimeout = true;
}
state->lockState.Leave();
if (armTimeout)
{
InstallTimeout(state, timeout, L"The HTTP peer timed out while sending an incomplete message.");
}
else if (refreshTimeout)
{
RefreshTimeout(state);
}
return;
}
if (result != HttpMessageParsingResult::Succeeded)
{
if (state->direction == HttpRequestConnectionDirection::Server)
{
state->activeRequestMethod = parsedRequestMethod;
state->parserFailed = true;
state->awaitingResponse = true;
state->closeAfterExchange = true;
state->receiveBuffer.Clear();
failure = GetHttpRequestFailure(result);
requestFailure = true;
}
else
{
clientFailure = true;
}
state->lockState.Leave();
}
else
{
state->receiveBuffer.RemoveRange(0, consumedBytes);
if (state->timeoutArmed)
{
state->timeoutArmed = false;
cancelTimeout = true;
}
state->closeAfterExchange |= messageClose;
if (state->direction == HttpRequestConnectionDirection::Server)
{
state->activeRequestMethod = request->method;
state->awaitingResponse = true;
deliverRequest = true;
}
else if (state->writePending)
{
state->heldResponse = response;
if (state->receiveBuffer.Count() > 0)
{
state->fatalAfterResponse = true;
state->receiveBuffer.Clear();
}
}
else
{
state->exchangeActive = false;
state->responseDelivering = true;
deliverResponse = true;
closeAfterDelivery = state->closeAfterExchange;
if (state->receiveBuffer.Count() > 0)
{
state->fatalAfterResponse = true;
state->receiveBuffer.Clear();
}
}
state->lockState.Leave();
}
if (requestFailure)
{
ReportRequestFailure(state, failure, false, true);
return;
}
if (clientFailure)
{
ReportFatalError(state, L"The HTTP peer sent malformed or unsafe HTTP/1.1 framing.");
return;
}
if (cancelTimeout)
{
state->timeoutController->CancelAndWait();
}
if (deliverRequest)
{
InvokeHttpCallback(state, false, [&](IHttpRequestCallback* installed)
{
installed->OnReadRequest(request);
});
}
else if (deliverResponse)
{
DeliverResponse(state, response, closeAfterDelivery);
}
}
void HttpRequestConnection::NotifyDisconnected(Ptr<Lifecycle> state)
{
auto callbackDepth = CurrentCallbackDepth(state);
state->lockState.Enter();
if (!state->disconnectedNotified)
{
state->disconnectedNotified = true;
state->terminal = true;
state->pendingWrite = nullptr;
state->writePending = false;
state->heldResponse = nullptr;
state->fatalAfterResponse = false;
state->deferredRequestWrite = nullptr;
state->deferredRequestClose = false;
state->deferredRequestMethod = L"";
state->cvState.WakeAllPendings();
}
if (state->disconnectFinished)
{
state->lockState.Leave();
return;
}
if (state->disconnectDelivering)
{
if (callbackDepth == 0)
{
while (!state->disconnectFinished) state->cvState.SleepWith(state->lockState);
}
state->lockState.Leave();
return;
}
if (callbackDepth == 0)
{
while (state->activeCallbacks > 0 && !state->disconnectDelivering && !state->disconnectFinished)
{
state->cvState.SleepWith(state->lockState);
}
if (state->disconnectFinished)
{
state->lockState.Leave();
return;
}
}
state->disconnectDelivering = true;
while (state->activeCallbacks > callbackDepth)
{
state->cvState.SleepWith(state->lockState);
}
state->lockState.Leave();
try
{
InvokeHttpCallback(state, true, [](IHttpRequestCallback* installed)
{
installed->OnDisconnected();
});
}
catch (...)
{
Lifecycle::RetainedAdapterRelease releasing;
CS_LOCK(state->lockState)
{
state->callback = nullptr;
state->disconnectFinished = true;
state->TakeRetainedAdapterIfDrained(releasing);
state->cvState.WakeAllPendings();
}
throw;
}
Lifecycle::RetainedAdapterRelease releasing;
CS_LOCK(state->lockState)
{
state->callback = nullptr;
while (state->activeCallbacks > callbackDepth)
{
state->cvState.SleepWith(state->lockState);
}
state->disconnectFinished = true;
state->TakeRetainedAdapterIfDrained(releasing);
state->cvState.WakeAllPendings();
}
}
void HttpRequestConnection::StopConnection(Ptr<Lifecycle> state, Ptr<Object> retainedAdapter)
{
auto callbackDepth = CurrentCallbackDepth(state);
auto socketCallbackDepth = CurrentSocketCallbackDepth(state);
auto timeoutCallbackDepth = CurrentTimeoutCallbackDepth(state);
auto nestedCallback = callbackDepth > 0 || socketCallbackDepth > 0;
IAsyncSocketConnection* connection = nullptr;
bool executeStop = false;
bool nestedFollower = false;
bool timeoutFollower = false;
Lifecycle::RetainedAdapterRelease releasing;
state->lockState.Enter();
if (retainedAdapter) state->retainedAdapter = retainedAdapter;
if (!state->stopStarted)
{
state->stopStarted = true;
state->timeoutArmed = false;
state->pendingWrite = nullptr;
state->writePending = false;
state->heldResponse = nullptr;
state->fatalAfterResponse = false;
state->deferredRequestWrite = nullptr;
state->deferredRequestClose = false;
state->deferredRequestMethod = L"";
if (!nestedCallback)
{
while (state->activeSocketCalls > 0) state->cvState.SleepWith(state->lockState);
}
connection = state->peerDisconnected ? nullptr : state->socketConnection;
executeStop = true;
}
else if (nestedCallback)
{
connection = state->socketConnection;
nestedFollower = true;
}
else if (timeoutCallbackDepth > 0)
{
timeoutFollower = true;
}
else
{
while (!state->stopFinished) state->cvState.SleepWith(state->lockState);
while (state->activeCallbacks > 0 || state->activeSocketCallbacks > 0 || state->activeSocketCalls > 0)
{
state->cvState.SleepWith(state->lockState);
}
state->TakeRetainedAdapterIfDrained(releasing);
state->lockState.Leave();
return;
}
state->lockState.Leave();
if (nestedFollower)
{
if (connection && socketCallbackDepth > 0) connection->Stop();
NotifyDisconnected(state);
return;
}
if (timeoutFollower)
{
return;
}
state->timeoutController->CancelAndWait();
if (executeStop && connection) connection->Stop();
NotifyDisconnected(state);
CS_LOCK(state->lockState)
{
while (state->activeCallbacks > callbackDepth || state->activeSocketCallbacks > socketCallbackDepth)
{
state->cvState.SleepWith(state->lockState);
}
state->stopFinished = true;
state->TakeRetainedAdapterIfDrained(releasing);
state->cvState.WakeAllPendings();
}
}
void HttpRequestConnection::ReportFatalError(Ptr<Lifecycle> state, const WString& error)
{
bool report = false;
CS_LOCK(state->lockState)
{
if (!state->terminal && !state->stopStarted)
{
state->terminal = true;
state->parserFailed = true;
state->timeoutArmed = false;
state->pendingWrite = nullptr;
state->writePending = false;
state->heldResponse = nullptr;
state->fatalAfterResponse = false;
state->deferredRequestWrite = nullptr;
state->deferredRequestClose = false;
state->deferredRequestMethod = L"";
report = true;
state->cvState.WakeAllPendings();
}
}
if (report)
{
state->timeoutController->CancelAndWait();
try
{
InvokeHttpCallback(state, true, [&](IHttpRequestCallback* installed)
{
installed->OnError(error, true);
});
}
catch (...)
{
StopConnection(state);
throw;
}
StopConnection(state);
}
}
/***********************************************************************
HttpRequestConnection
***********************************************************************/
HttpRequestConnection::HttpRequestConnection(
IAsyncSocketConnection* connection,
HttpRequestConnectionDirection direction,
Ptr<HttpRequestCallbackDomain> callbackDomain,
Ptr<IHttpRequestTimeoutController> timeoutController,
bool responseNotFoundIsFatal
)
: lifecycle(Ptr(new Lifecycle))
{
CHECK_ERROR(connection, L"HttpRequestConnection requires a valid async socket connection.");
lifecycle->socketConnection = connection;
lifecycle->direction = direction;
lifecycle->responseNotFoundIsFatal = responseNotFoundIsFatal;
lifecycle->callbackDomain = callbackDomain ? callbackDomain : Ptr(new HttpRequestCallbackDomain);
lifecycle->timeoutController = timeoutController ? timeoutController : CreateHttpRequestTimeoutController();
connection->InstallCallback(this);
}
HttpRequestConnection::~HttpRequestConnection()
{
StopConnection(lifecycle);
}
void HttpRequestConnection::RetainUntilStopped(Ptr<HttpRequestConnection> retainedAdapter, const Func<void()>& drainedCallback)
{
CHECK_ERROR(retainedAdapter.Obj() == this, L"HttpRequestConnection::RetainUntilStopped requires this connection as its retained adapter.");
CHECK_ERROR(drainedCallback, L"HttpRequestConnection::RetainUntilStopped requires a drained callback.");
bool canRetain = false;
CS_LOCK(lifecycle->lockState)
{
if (!lifecycle->retainedAdapter && !lifecycle->drainedCallback && !lifecycle->stopStarted)
{
lifecycle->retainedAdapter = retainedAdapter;
lifecycle->drainedCallback = drainedCallback;
canRetain = true;
}
}
CHECK_ERROR(canRetain, L"HttpRequestConnection::RetainUntilStopped can only be called once before stopping.");
}
void HttpRequestConnection::StopWithRetainedAdapter(Ptr<HttpRequestConnection> retainedAdapter)
{
StopConnection(lifecycle, retainedAdapter);
}
bool HttpRequestConnection::IsInsideCallback()
{
return CurrentCallbackDepth(lifecycle) > 0 || CurrentSocketCallbackDepth(lifecycle) > 0;
}
void HttpRequestConnection::InstallCallback(IHttpRequestCallback* value)
{
auto state = lifecycle;
if (!value)
{
auto callbackDepth = CurrentCallbackDepth(state);
bool uninstallOwner = false;
CS_LOCK(state->lockState)
{
uninstallOwner = state->callback != nullptr;
state->callback = nullptr;
while ((callbackDepth == 0 || uninstallOwner) && state->activeCallbacks > callbackDepth)
{
state->cvState.SleepWith(state->lockState);
}
}
return;
}
bool canInstall = false;
CS_LOCK(state->lockState)
{
if (!state->callback && !state->callbackInstalling && !state->stopStarted && !state->terminal)
{
state->callback = value;
state->callbackInstalling = true;
state->activeCallbacks++;
canInstall = true;
}
}
CHECK_ERROR(canInstall, L"HttpRequestConnection::InstallCallback cannot replace a callback or install one on a stopped connection.");
CallbackFrame frame(state);
try
{
value->OnInstalled(this);
}
catch (...)
{
CS_LOCK(state->lockState)
{
if (state->callback == value) state->callback = nullptr;
state->callbackInstalling = false;
state->cvState.WakeAllPendings();
}
throw;
}
CS_LOCK(state->lockState)
{
state->callbackInstalling = false;
state->cvState.WakeAllPendings();
}
}
void HttpRequestConnection::BeginReadingLoopUnsafe()
{
auto state = lifecycle;
IAsyncSocketConnection* connection = nullptr;
Ptr<Object> callRetainer;
bool canBegin = false;
CS_LOCK(state->lockState)
{
if (!state->readingStarted && !state->stopStarted && !state->terminal && state->socketConnection)
{
state->readingStarted = true;
connection = state->socketConnection;
callRetainer = state->retainedAdapter;
state->activeSocketCalls++;
canBegin = true;
}
}
CHECK_ERROR(canBegin, L"HttpRequestConnection::BeginReadingLoopUnsafe can only be called once on an active connection.");
try
{
connection->BeginReadingLoopUnsafe();
}
catch (...)
{
FinishSocketCall(state);
throw;
}
FinishSocketCall(state);
}
void HttpRequestConnection::SendRequest(Ptr<HttpRequest> request, vint responseTimeout)
{
auto state = lifecycle;
CHECK_ERROR(state->direction == HttpRequestConnectionDirection::Client, L"HttpRequestConnection::SendRequest is only available on a client connection.");
CHECK_ERROR(request, L"HttpRequestConnection::SendRequest requires a request.");
bool requestClose = false;
auto buffer = SerializeHttpMessage(request.Obj(), nullptr, L"", requestClose);
IAsyncSocketConnection* connection = nullptr;
Ptr<Object> callRetainer;
bool canSend = false;
bool deferSend = false;
CS_LOCK(state->lockState)
{
auto commonStateAvailable = !state->stopStarted && !state->terminal && !state->peerDisconnected
&& !state->exchangeActive && !state->writePending && !state->deferredRequestWrite
&& !state->responseFinalizing && !state->fatalAfterResponse && !state->closeAfterExchange && state->socketConnection;
if (commonStateAvailable && state->responseDelivering)
{
state->deferredRequestWrite = buffer;
state->deferredRequestClose = requestClose;
state->deferredRequestMethod = request->method;
state->deferredResponseTimeout = responseTimeout;
deferSend = true;
canSend = true;
}
else if (commonStateAvailable && !state->responseDelivering)
{
state->exchangeActive = true;
state->closeAfterExchange = requestClose;
state->activeRequestMethod = request->method;
state->activeResponseTimeout = responseTimeout;
state->pendingWrite = buffer;
state->writePending = true;
connection = state->socketConnection;
callRetainer = state->retainedAdapter;
state->activeSocketCalls++;
canSend = true;
}
}
CHECK_ERROR(canSend, L"HttpRequestConnection::SendRequest requires an idle active client connection.");
if (deferSend) return;
SubmitWrite(state, connection, buffer);
ProcessBufferedInput(state);
}
void HttpRequestConnection::SendResponse(Ptr<HttpResponse> response)
{
auto state = lifecycle;
CHECK_ERROR(state->direction == HttpRequestConnectionDirection::Server, L"HttpRequestConnection::SendResponse is only available on a server connection.");
CHECK_ERROR(response, L"HttpRequestConnection::SendResponse requires a response.");
WString responseToMethod;
CS_LOCK(state->lockState)
{
responseToMethod = state->activeRequestMethod;
}
bool responseClose = false;
auto buffer = SerializeHttpMessage(nullptr, response.Obj(), responseToMethod, responseClose);
IAsyncSocketConnection* connection = nullptr;
Ptr<Object> callRetainer;
bool canSend = false;
CS_LOCK(state->lockState)
{
if (!state->stopStarted && !state->terminal && state->awaitingResponse && !state->writePending && state->socketConnection)
{
state->closeAfterExchange |= responseClose;
state->pendingWrite = buffer;
state->writePending = true;
connection = state->socketConnection;
callRetainer = state->retainedAdapter;
state->activeSocketCalls++;
canSend = true;
}
}
CHECK_ERROR(canSend, L"HttpRequestConnection::SendResponse requires one delivered request without a response in progress.");
SubmitWrite(state, connection, buffer);
}
void HttpRequestConnection::Stop()
{
StopConnection(lifecycle);
}
void HttpRequestConnection::OnRead(const vuint8_t* buffer, vint size)
{
auto state = lifecycle;
SocketCallbackFrame frame(state);
if (!buffer || size <= 0) return;
bool tooLarge = false;
bool requestTooLarge = false;
bool unsolicited = false;
CS_LOCK(state->lockState)
{
if (state->stopStarted || state->terminal || state->parserFailed || state->peerDisconnected) return;
if (state->direction == HttpRequestConnectionDirection::Client && state->heldResponse)
{
state->fatalAfterResponse = true;
}
else if (state->direction == HttpRequestConnectionDirection::Client && (state->responseDelivering || state->responseFinalizing) && !state->exchangeActive)
{
state->fatalAfterResponse = true;
}
else if (state->direction == HttpRequestConnectionDirection::Client && !state->exchangeActive)
{
state->parserFailed = true;
unsolicited = true;
}
else if (size > HttpWireMessageSizeLimit - state->receiveBuffer.Count())
{
if (state->direction == HttpRequestConnectionDirection::Server)
{
state->parserFailed = true;
state->awaitingResponse = true;
state->closeAfterExchange = true;
state->receiveBuffer.Clear();
requestTooLarge = true;
}
else
{
state->parserFailed = true;
tooLarge = true;
}
}
else
{
for (vint i = 0; i < size; i++) state->receiveBuffer.Add(buffer[i]);
}
}
if (unsolicited)
{
ReportFatalError(state, L"The HTTP client received bytes without an active request exchange.");
return;
}
if (tooLarge)
{
ReportFatalError(state, L"The HTTP peer exceeded the configured wire-message size limit.");
return;
}
if (requestTooLarge)
{
ReportRequestFailure(state, HttpRequestFailure::PayloadTooLarge, false, true);
return;
}
ProcessBufferedInput(state);
}
void HttpRequestConnection::OnWriteCompleted(Ptr<AsyncSocketBuffer> buffer)
{
auto state = lifecycle;
SocketCallbackFrame frame(state);
bool mismatched = false;
bool ignored = false;
CS_LOCK(state->lockState)
{
if (!state->writePending || state->pendingWrite.Obj() != buffer.Obj())
{
if (state->stopStarted || state->terminal || state->peerDisconnected)
{
ignored = true;
}
else
{
mismatched = true;
}
}
else
{
state->pendingWrite = nullptr;
}
}
if (ignored) return;
CHECK_ERROR(!mismatched, L"HttpRequestConnection received a completion for an unexpected async socket buffer.");
InvokeHttpCallback(state, false, [](IHttpRequestCallback* installed)
{
installed->OnWriteCompleted();
});
Ptr<HttpResponse> response;
bool serverSide = false;
bool closeAfterDelivery = false;
bool armTimeout = false;
vint responseTimeout = 0;
CS_LOCK(state->lockState)
{
if (state->stopStarted || state->terminal) return;
state->writePending = false;
serverSide = state->direction == HttpRequestConnectionDirection::Server;
if (serverSide)
{
state->awaitingResponse = false;
state->activeRequestMethod = L"";
closeAfterDelivery = state->closeAfterExchange;
}
else if (state->heldResponse)
{
response = std::move(state->heldResponse);
state->exchangeActive = false;
state->responseDelivering = true;
closeAfterDelivery = state->closeAfterExchange;
}
else if (!state->peerDisconnected && !state->timeoutArmed && state->activeResponseTimeout > 0)
{
state->timeoutArmed = true;
responseTimeout = state->activeResponseTimeout;
armTimeout = true;
}
}
if (response)
{
DeliverResponse(state, response, closeAfterDelivery);
}
else if (closeAfterDelivery)
{
StopConnection(state);
}
else if (serverSide)
{
ProcessBufferedInput(state);
}
else if (armTimeout)
{
InstallTimeout(state, responseTimeout, L"The HTTP peer timed out before sending a response header.");
}
}
void HttpRequestConnection::OnError(const WString& error, bool fatal)
{
auto state = lifecycle;
SocketCallbackFrame frame(state);
if (fatal)
{
ReportFatalError(state, error);
}
else
{
InvokeHttpCallback(state, false, [&](IHttpRequestCallback* installed)
{
installed->OnError(error, false);
});
}
}
void HttpRequestConnection::OnConnected()
{
auto state = lifecycle;
SocketCallbackFrame frame(state);
InvokeHttpCallback(state, false, [](IHttpRequestCallback* installed)
{
installed->OnConnected();
});
}
void HttpRequestConnection::OnDisconnected()
{
auto state = lifecycle;
SocketCallbackFrame frame(state);
IAsyncSocketConnection* connection = nullptr;
bool incomplete = false;
bool surplusAfterHeldResponse = false;
bool deferDisconnected = false;
bool cancelTimeout = false;
CS_LOCK(state->lockState)
{
state->peerDisconnected = true;
state->timeoutArmed = false;
cancelTimeout = !state->stopStarted;
connection = state->socketConnection;
if (!state->terminal && !state->stopStarted && state->direction == HttpRequestConnectionDirection::Client && state->heldResponse)
{
surplusAfterHeldResponse = state->fatalAfterResponse;
incomplete = !surplusAfterHeldResponse;
state->terminal = true;
state->pendingWrite = nullptr;
state->writePending = false;
}
else if (state->direction == HttpRequestConnectionDirection::Client && (state->responseDelivering || state->responseFinalizing))
{
deferDisconnected = true;
}
else
{
incomplete = !state->terminal && !state->stopStarted && (
state->direction == HttpRequestConnectionDirection::Client
? state->exchangeActive
: !state->awaitingResponse && state->receiveBuffer.Count() > 0
);
state->terminal = true;
}
}
if (connection)
{
connection->InstallCallback(nullptr);
}
CS_LOCK(state->lockState)
{
if (state->socketConnection == connection) state->socketConnection = nullptr;
state->cvState.WakeAllPendings();
}
if (cancelTimeout)
{
state->timeoutController->CancelAndWait();
}
if (deferDisconnected)
{
return;
}
if (surplusAfterHeldResponse)
{
InvokeHttpCallback(state, true, [](IHttpRequestCallback* installed)
{
installed->OnError(L"The HTTP client received an unsolicited response after its exchange completed.", true);
});
}
else if (incomplete)
{
InvokeHttpCallback(state, true, [](IHttpRequestCallback* installed)
{
installed->OnError(L"The HTTP peer disconnected while a message was incomplete.", true);
});
}
NotifyDisconnected(state);
StopConnection(state);
}
void HttpRequestConnection::OnInstalled(IAsyncSocketConnection* connection)
{
auto state = lifecycle;
SocketCallbackFrame frame(state);
CHECK_ERROR(connection == state->socketConnection, L"HttpRequestConnection was installed on an unexpected async socket connection.");
}
}
/***********************************************************************
.\INTERPROCESS\ASYNCSOCKET\ASYNCSOCKET_HTTPREQUESTCLIENT.CPP
***********************************************************************/
namespace vl::inter_process::async_tcp_socket
{
/***********************************************************************
HttpRequestClient::Impl
***********************************************************************/
class HttpRequestClient::Impl : public Object
{
private:
class DeferredClientRelease : public Object
{
private:
CriticalSection lockState;
Ptr<DeferredClientRelease> selfReference;
Ptr<IAsyncSocketClient> retainedClient;
public:
DeferredClientRelease(Ptr<IAsyncSocketClient> client)
: retainedClient(client)
{
}
void InitializeSelf(Ptr<DeferredClientRelease> self)
{
CS_LOCK(lockState)
{
selfReference = self;
}
}
void Run()
{
Ptr<IAsyncSocketClient> client;
CS_LOCK(lockState)
{
client = retainedClient;
}
try
{
client->GetConnection()->Stop();
}
catch (...)
{
}
CS_LOCK(lockState)
{
retainedClient = nullptr;
selfReference = nullptr;
}
}
};
Ptr<IAsyncSocketClient> client;
Ptr<HttpRequestConnection> connection;
static void QueueDeferredRelease(Ptr<DeferredClientRelease> deferredRelease)
{
auto finalize = Func<void()>([deferredRelease]()
{
deferredRelease->Run();
});
if (!ThreadPoolLite::Queue(finalize))
{
// The holder self-reference keeps the native client alive if the
// operating system cannot start either asynchronous cleanup path.
if (!Thread::CreateAndStart(finalize)) return;
}
}
public:
Impl(Ptr<IAsyncSocketClient> _client)
: client(_client)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::HttpRequestClient::HttpRequestClient(Ptr<IAsyncSocketClient>)#"
CHECK_ERROR(client, ERROR_MESSAGE_PREFIX L"Requires a client.");
connection = Ptr(new HttpRequestConnection(
client->GetConnection(),
HttpRequestConnectionDirection::Client,
nullptr,
nullptr,
true
));
#undef ERROR_MESSAGE_PREFIX
}
~Impl()
{
auto deferFinalization = connection->IsInsideCallback();
Ptr<DeferredClientRelease> deferredRelease;
if (deferFinalization)
{
deferredRelease = Ptr(new DeferredClientRelease(client));
deferredRelease->InitializeSelf(deferredRelease);
}
connection->StopWithRetainedAdapter(connection);
if (deferFinalization)
{
QueueDeferredRelease(deferredRelease);
}
}
IHttpRequestConnection* GetConnection()
{
return connection.Obj();
}
void WaitForServer()
{
client->WaitForServer();
}
ClientStatus GetStatus()
{
return client->GetStatus();
}
};
/***********************************************************************
HttpRequestClient
***********************************************************************/
HttpRequestClient::HttpRequestClient(Ptr<IAsyncSocketClient> client)
: impl(Ptr(new Impl(client)))
{
}
HttpRequestClient::~HttpRequestClient()
{
}
IHttpRequestConnection* HttpRequestClient::GetConnection()
{
return impl->GetConnection();
}
void HttpRequestClient::WaitForServer()
{
impl->WaitForServer();
}
ClientStatus HttpRequestClient::GetStatus()
{
return impl->GetStatus();
}
}
/***********************************************************************
.\INTERPROCESS\ASYNCSOCKET\ASYNCSOCKET_HTTPREQUESTSERVER.CPP
***********************************************************************/
namespace vl::inter_process::async_tcp_socket
{
/***********************************************************************
HttpRequestServer::Impl
***********************************************************************/
class HttpRequestServer::Impl : public Object
{
private:
class Lifecycle : public Object
{
public:
HttpRequestServer* owner = nullptr;
Ptr<IAsyncSocketServer> server;
Ptr<HttpRequestCallbackDomain> callbackDomain = Ptr(new HttpRequestCallbackDomain);
Func<Ptr<IHttpRequestTimeoutController>()>
timeoutControllerFactory;
// covers owner, connections, and all lifecycle flags below
CriticalSection lockState;
ConditionVariable cvState;
collections::List<Ptr<HttpRequestConnection>>
connections;
bool startCalled = false;
bool stopStarted = false;
bool unexpectedStopNotified = false;
bool stopFinished = false;
bool nativeStopCalling = false;
bool destroyStarted = false;
bool destroyAdaptersRetained = false;
Lifecycle(
HttpRequestServer* _owner,
Ptr<IAsyncSocketServer> _server,
const Func<Ptr<IHttpRequestTimeoutController>()>& _timeoutControllerFactory
)
: owner(_owner)
, server(_server)
, timeoutControllerFactory(_timeoutControllerFactory)
{
}
};
class SocketServerCallback
: public Object
, public virtual IAsyncSocketServerCallback
{
private:
Ptr<Lifecycle> lifecycle;
CriticalSection lockSelf;
Ptr<SocketServerCallback> selfReference;
public:
SocketServerCallback(Ptr<Lifecycle> _lifecycle)
: lifecycle(_lifecycle)
{
}
void InitializeSelf(Ptr<SocketServerCallback> self)
{
CS_LOCK(lockSelf)
{
selfReference = self;
}
}
void ReleaseSelfReference()
{
CS_LOCK(lockSelf)
{
selfReference = nullptr;
}
}
WaitForClientResult OnClientConnected(IAsyncSocketConnection* connection) override
{
Ptr<SocketServerCallback> self;
CS_LOCK(lockSelf)
{
self = selfReference;
}
return self
? Impl::OnSocketClientConnected(lifecycle, connection)
: WaitForClientResult::Reject;
}
void OnServerStopped() override
{
Ptr<SocketServerCallback> self;
CS_LOCK(lockSelf)
{
self = selfReference;
}
if (self)
{
Impl::OnSocketServerStopped(lifecycle);
}
}
};
Ptr<Lifecycle> lifecycle;
Ptr<SocketServerCallback> callback;
static void StopConnections(Ptr<Lifecycle> state, bool retainAdapters = false)
{
collections::List<Ptr<HttpRequestConnection>> stoppingConnections;
CS_LOCK(state->lockState)
{
for (auto connection : state->connections)
{
stoppingConnections.Add(connection);
}
}
for (auto connection : stoppingConnections)
{
if (retainAdapters)
{
connection->StopWithRetainedAdapter(connection);
}
else
{
connection->Stop();
}
}
}
static void FinalizeDestroyedOwner(Ptr<Lifecycle> state)
{
CS_LOCK(state->lockState)
{
if (state->destroyStarted && state->destroyAdaptersRetained)
{
state->owner = nullptr;
state->connections.Clear();
}
}
}
static void QueueDeferredStop(Ptr<Lifecycle> state, Ptr<SocketServerCallback> retainedCallback)
{
auto finalize = Func<void()>([state, retainedCallback]()
{
state->lockState.Enter();
while (!state->stopFinished || state->nativeStopCalling)
{
state->cvState.SleepWith(state->lockState);
}
state->nativeStopCalling = true;
state->lockState.Leave();
try
{
state->server->Stop();
}
catch (...)
{
}
try
{
StopConnections(state);
}
catch (...)
{
}
state->callbackDomain->WaitForCallbacks(0);
FinalizeDestroyedOwner(state);
CS_LOCK(state->lockState)
{
state->nativeStopCalling = false;
state->cvState.WakeAllPendings();
}
retainedCallback->ReleaseSelfReference();
});
if (!ThreadPoolLite::Queue(finalize))
{
// The callback self-reference keeps all deferred state alive if the
// operating system cannot start either asynchronous cleanup path.
if (!Thread::CreateAndStart(finalize)) return;
}
}
static WaitForClientResult OnSocketClientConnected(Ptr<Lifecycle> state, IAsyncSocketConnection* connection)
{
HttpRequestCallbackDomain::CallbackFrame callbackFrame(state->callbackDomain);
HttpRequestServer* owner = nullptr;
CS_LOCK(state->lockState)
{
if (!state->stopStarted)
{
owner = state->owner;
}
}
if (!owner)
{
return WaitForClientResult::Reject;
}
Ptr<IHttpRequestTimeoutController> timeoutController;
try
{
if (state->timeoutControllerFactory)
{
timeoutController = state->timeoutControllerFactory();
CHECK_ERROR(timeoutController, L"The HTTP request timeout controller factory returned null.");
}
}
catch (...)
{
return WaitForClientResult::Reject;
}
auto httpConnection = Ptr(new HttpRequestConnection(
connection,
HttpRequestConnectionDirection::Server,
state->callbackDomain,
timeoutController
));
auto connectionObject = httpConnection.Obj();
httpConnection->RetainUntilStopped(httpConnection, Func<void()>([state, connectionObject]()
{
CS_LOCK(state->lockState)
{
state->connections.Remove(connectionObject);
state->cvState.WakeAllPendings();
}
}));
bool invoke = false;
CS_LOCK(state->lockState)
{
if (!state->stopStarted && state->owner == owner)
{
state->connections.Add(httpConnection);
invoke = true;
}
}
if (!invoke)
{
httpConnection->StopWithRetainedAdapter(httpConnection);
return WaitForClientResult::Reject;
}
auto result = WaitForClientResult::Reject;
try
{
result = owner->OnClientConnected(httpConnection.Obj());
}
catch (...)
{
result = WaitForClientResult::Reject;
}
bool accepted = false;
CS_LOCK(state->lockState)
{
accepted = result == WaitForClientResult::Accept && !state->stopStarted;
if (!accepted)
{
state->connections.Remove(httpConnection.Obj());
}
}
if (!accepted)
{
httpConnection->StopWithRetainedAdapter(httpConnection);
}
return accepted ? WaitForClientResult::Accept : WaitForClientResult::Reject;
}
static void OnSocketServerStopped(Ptr<Lifecycle> state)
{
HttpRequestCallbackDomain::CallbackFrame callbackFrame(state->callbackDomain);
HttpRequestServer* owner = nullptr;
CS_LOCK(state->lockState)
{
if (!state->stopStarted && !state->unexpectedStopNotified)
{
state->unexpectedStopNotified = true;
owner = state->owner;
}
}
if (owner)
{
try
{
owner->OnServerStopped();
}
catch (...)
{
}
}
}
public:
Impl(
HttpRequestServer* owner,
Ptr<IAsyncSocketServer> server,
const Func<Ptr<IHttpRequestTimeoutController>()>& timeoutControllerFactory
)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::HttpRequestServer::HttpRequestServer(Ptr<IAsyncSocketServer>)#"
CHECK_ERROR(server, ERROR_MESSAGE_PREFIX L"Requires a server.");
lifecycle = Ptr(new Lifecycle(owner, server, timeoutControllerFactory));
callback = Ptr(new SocketServerCallback(lifecycle));
#undef ERROR_MESSAGE_PREFIX
}
~Impl()
{
Destroy();
}
void Start()
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::HttpRequestServer::Start()#"
auto state = lifecycle;
bool canStart = false;
CS_LOCK(state->lockState)
{
if (!state->startCalled && !state->stopStarted)
{
state->startCalled = true;
canStart = true;
}
}
CHECK_ERROR(canStart, ERROR_MESSAGE_PREFIX L"Can only be called once before stopping.");
callback->InitializeSelf(callback);
try
{
state->server->Start(callback.Obj());
}
catch (...)
{
try
{
Stop();
}
catch (...)
{
}
throw;
}
#undef ERROR_MESSAGE_PREFIX
}
void Stop()
{
auto state = lifecycle;
auto retainedCallback = callback;
auto callbackDepth = state->callbackDomain->CurrentCallbackDepth();
bool firstStop = false;
bool nestedFollower = false;
bool deferFinalization = false;
state->lockState.Enter();
if (!state->stopStarted)
{
state->stopStarted = true;
state->nativeStopCalling = true;
firstStop = true;
}
else if (callbackDepth > 0)
{
nestedFollower = true;
}
else
{
while (!state->stopFinished || state->nativeStopCalling)
{
state->cvState.SleepWith(state->lockState);
}
state->nativeStopCalling = true;
}
state->lockState.Leave();
if (nestedFollower)
{
StopConnections(state);
return;
}
try
{
state->server->Stop();
StopConnections(state);
state->callbackDomain->WaitForCallbacks(firstStop ? callbackDepth : 0);
deferFinalization = firstStop && callbackDepth > 0;
if (!deferFinalization)
{
FinalizeDestroyedOwner(state);
retainedCallback->ReleaseSelfReference();
}
}
catch (...)
{
if (!firstStop || callbackDepth == 0)
{
retainedCallback->ReleaseSelfReference();
}
CS_LOCK(state->lockState)
{
state->nativeStopCalling = false;
if (firstStop)
{
state->stopFinished = true;
}
state->cvState.WakeAllPendings();
}
throw;
}
CS_LOCK(state->lockState)
{
state->nativeStopCalling = false;
if (firstStop)
{
state->stopFinished = true;
}
state->cvState.WakeAllPendings();
}
if (deferFinalization)
{
QueueDeferredStop(state, retainedCallback);
}
}
void Destroy()
{
auto state = lifecycle;
bool execute = false;
CS_LOCK(state->lockState)
{
if (!state->destroyStarted)
{
state->destroyStarted = true;
execute = true;
}
}
if (!execute)
{
return;
}
Stop();
StopConnections(state, true);
CS_LOCK(state->lockState)
{
state->destroyAdaptersRetained = true;
}
if (state->callbackDomain->CurrentCallbackDepth() == 0)
{
state->callbackDomain->WaitForCallbacks(0);
FinalizeDestroyedOwner(state);
}
}
bool IsStopped()
{
return lifecycle->server->IsStopped();
}
};
/***********************************************************************
HttpRequestServer
***********************************************************************/
HttpRequestServer::HttpRequestServer(Ptr<IAsyncSocketServer> server)
: HttpRequestServer(server, {})
{
}
HttpRequestServer::HttpRequestServer(
Ptr<IAsyncSocketServer> server,
const Func<Ptr<IHttpRequestTimeoutController>()>& timeoutControllerFactory
)
: impl(Ptr(new Impl(this, server, timeoutControllerFactory)))
{
}
HttpRequestServer::~HttpRequestServer()
{
impl->Destroy();
}
WaitForClientResult HttpRequestServer::OnClientConnected(IHttpRequestConnection*)
{
return WaitForClientResult::Accept;
}
void HttpRequestServer::OnServerStopped()
{
Stop();
}
void HttpRequestServer::Start()
{
impl->Start();
}
void HttpRequestServer::Stop()
{
impl->Stop();
}
bool HttpRequestServer::IsStopped()
{
return impl->IsStopped();
}
}
/***********************************************************************
.\INTERPROCESS\ASYNCSOCKET\ASYNCSOCKET_HTTPSERVER.CPP
***********************************************************************/
#include <random>
namespace vl::inter_process::async_tcp_socket
{
using namespace collections;
namespace
{
constexpr vint GeneratedTokenLength = 36;
WString ValidateServerUrlPrefix(const WString& urlPrefix)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::SocketHttpServer::SocketHttpServer(Ptr<IAsyncSocketServer>, const WString&)#"
auto normalizedUrlPrefix = urlPrefix;
while (normalizedUrlPrefix.Length() > 0 && normalizedUrlPrefix[normalizedUrlPrefix.Length() - 1] == L'/')
{
normalizedUrlPrefix = normalizedUrlPrefix.Left(normalizedUrlPrefix.Length() - 1);
}
CHECK_ERROR(ValidateHttpNetworkProtocolBaseUrl(normalizedUrlPrefix), ERROR_MESSAGE_PREFIX L"urlPrefix must be empty or a legal ASCII origin-form path prefix.");
CHECK_ERROR(ValidateHttpRequestLine(L"GET", normalizedUrlPrefix + HttpServerUrl_Connect) == HttpRequestLineValidationResult::Succeeded, ERROR_MESSAGE_PREFIX L"The /Connect target exceeds the HTTP request-line limit.");
const WString tokenPlaceholder = L"000000000000000000000000000000000000";
CHECK_ERROR(ValidateHttpRequestLine(L"POST", normalizedUrlPrefix + HttpServerUrl_Request + L"/" + tokenPlaceholder) == HttpRequestLineValidationResult::Succeeded, ERROR_MESSAGE_PREFIX L"The /Request target plus a generated token exceeds the HTTP request-line limit.");
CHECK_ERROR(ValidateHttpRequestLine(L"POST", normalizedUrlPrefix + HttpServerUrl_Response + L"/" + tokenPlaceholder) == HttpRequestLineValidationResult::Succeeded, ERROR_MESSAGE_PREFIX L"The /Response target plus a generated token exceeds the HTTP request-line limit.");
return normalizedUrlPrefix;
#undef ERROR_MESSAGE_PREFIX
}
bool HasEmptyBody(Ptr<HttpRequest> request)
{
if (!request || request->body.chunks.Count() != 0 || request->body.trailers.Count() != 0) return false;
HttpFraming framing;
if (AnalyzeHttpFraming(request->headers, framing) != HttpFramingAnalysisResult::Succeeded) return false;
if (framing.kind == HttpFramingKind::None) return true;
return
framing.kind == HttpFramingKind::ContentLength &&
framing.contentLength == 0 &&
framing.contentLengthFieldCount == 1 &&
framing.contentLengthValueCount == 1 &&
framing.contentLengthValuesPlainDecimal;
}
bool DecodeSubmittedMessage(Ptr<SocketHttpRequestContext> context, Ptr<HttpRequest> request, WString& message)
{
if (!request || request->body.trailers.Count() != 0) return false;
HttpFraming framing;
if (AnalyzeHttpFraming(request->headers, framing) != HttpFramingAnalysisResult::Succeeded) return false;
if (
framing.kind != HttpFramingKind::ContentLength ||
framing.contentLength == 0 ||
framing.contentLengthFieldCount != 1 ||
framing.contentLengthValueCount != 1 ||
!framing.contentLengthValuesPlainDecimal ||
CountHttpFields(request->headers, L"content-type") != 1
)
{
return false;
}
auto contentType = FindHttpField(request->headers, L"content-type");
return
HttpFieldValueEqualsAscii(contentType->value, HttpNetworkProtocolContentType) &&
context->TryGetBodyUtf8(message) &&
IsValidHttpNetworkProtocolMessage(message);
}
bool ExtractToken(const WString& path, const wchar_t* route, WString& token)
{
auto prefix = WString::Unmanaged(route) + L"/";
if (path.Length() <= prefix.Length() || path.Left(prefix.Length()) != prefix) return false;
token = path.Right(path.Length() - prefix.Length());
return token.Length() > 0;
}
WString GenerateToken()
{
vuint8_t bytes[16];
std::random_device random;
for (vint i = 0; i < 16; i++) bytes[i] = (vuint8_t)random();
bytes[6] = (bytes[6] & 0x0F) | 0x40;
bytes[8] = (bytes[8] & 0x3F) | 0x80;
const wchar_t* hex = L"0123456789abcdef";
wchar_t text[GeneratedTokenLength];
vint writing = 0;
for (vint i = 0; i < 16; i++)
{
if (i == 4 || i == 6 || i == 8 || i == 10) text[writing++] = L'-';
text[writing++] = hex[bytes[i] >> 4];
text[writing++] = hex[bytes[i] & 0x0F];
}
return WString::CopyFrom(text, GeneratedTokenLength);
}
BEGIN_GLOBAL_STORAGE_CLASS(SocketHttpServerTestHooks)
SpinLock lock;
Func<void(const WString&)> claimed;
Func<void(const WString&, bool)> completed;
Func<void(const WString&)> registered;
Func<bool(const WString&)> cancelBeforeResponse;
INITIALIZE_GLOBAL_STORAGE_CLASS
FINALIZE_GLOBAL_STORAGE_CLASS
SPIN_LOCK(lock)
{
claimed = {};
completed = {};
registered = {};
cancelBeforeResponse = {};
}
END_GLOBAL_STORAGE_CLASS(SocketHttpServerTestHooks)
void InvokePollClaimed(const WString& token)
{
Func<void(const WString&)> callback;
auto& hooks = GetSocketHttpServerTestHooks();
SPIN_LOCK(hooks.lock) { callback = hooks.claimed; }
if (callback) try { callback(token); } catch (...) {}
}
void InvokePollCompleted(const WString& token, bool succeeded)
{
Func<void(const WString&, bool)> callback;
auto& hooks = GetSocketHttpServerTestHooks();
SPIN_LOCK(hooks.lock) { callback = hooks.completed; }
if (callback) try { callback(token, succeeded); } catch (...) {}
}
void InvokePollRegistered(const WString& token)
{
Func<void(const WString&)> callback;
auto& hooks = GetSocketHttpServerTestHooks();
SPIN_LOCK(hooks.lock) { callback = hooks.registered; }
if (callback) try { callback(token); } catch (...) {}
}
bool ShouldCancelPollBeforeResponse(const WString& token)
{
Func<bool(const WString&)> callback;
auto& hooks = GetSocketHttpServerTestHooks();
SPIN_LOCK(hooks.lock) { callback = hooks.cancelBeforeResponse; }
if (callback) try { return callback(token); } catch (...) {}
return false;
}
class SocketHttpServerConnection;
class SocketHttpServerLifecycle;
class SocketHttpServerOutboundMessage : public Object
{
public:
Array<vuint8_t> body;
};
class SocketHttpServerConnectionLifecycle : public Object
{
public:
CriticalSection lockState;
ConditionVariable cvState;
SocketHttpServerConnection* owner = nullptr;
Ptr<SocketHttpServerLifecycle> server;
WString token;
INetworkProtocolCallback* callback = nullptr;
List<WString> queuedInbound;
List<Ptr<SocketHttpServerOutboundMessage>>
queuedOutbound;
List<Ptr<SocketHttpRequestContext>>
queuedPollRegistrations;
Ptr<SocketHttpRequestContext> pendingPoll;
Ptr<SocketHttpRequestContext> inFlightPoll;
Ptr<SocketHttpServerOutboundMessage>
inFlightMessage;
vint activeCallbacks = 0;
bool callbackInstalling = false;
bool pollRegistrationProcessing = false;
bool accepted = false;
bool stopStarted = false;
bool stopCancellationFinished = false;
bool stopFinished = false;
bool stopAssistProcessing = false;
bool disconnectDelivering = false;
bool disconnectFinished = false;
};
class SocketHttpServerConnection
: public Object
, public virtual INetworkProtocolConnection
{
struct CallbackFrame
{
Ptr<SocketHttpServerConnectionLifecycle> state;
CallbackFrame* previous = nullptr;
CallbackFrame(Ptr<SocketHttpServerConnectionLifecycle> _state);
~CallbackFrame();
};
struct InboundFrame
{
Ptr<SocketHttpServerConnectionLifecycle> state;
InboundFrame* previous = nullptr;
List<Ptr<SocketHttpServerOutboundMessage>>
generated;
InboundFrame(Ptr<SocketHttpServerConnectionLifecycle> _state);
~InboundFrame();
};
struct PollWork
{
Ptr<SocketHttpRequestContext> context;
Ptr<SocketHttpServerOutboundMessage>
message;
operator bool() const { return context != nullptr; }
};
static thread_local CallbackFrame* currentCallbackFrame;
static thread_local InboundFrame* currentInboundFrame;
Ptr<SocketHttpServerConnectionLifecycle>
lifecycle;
static vint CurrentCallbackDepth(Ptr<SocketHttpServerConnectionLifecycle> state);
static bool ClaimPollUnsafe(Ptr<SocketHttpServerConnectionLifecycle> state, PollWork& work);
static void StartPollResponse(Ptr<SocketHttpServerConnectionLifecycle> state, PollWork work);
static void FinishPollResponse(Ptr<SocketHttpServerConnectionLifecycle> state, Ptr<SocketHttpRequestContext> context, bool succeeded);
static void ProcessPollRegistrations(Ptr<SocketHttpServerConnectionLifecycle> state);
void StopCore(bool removeFromServer, bool waitForPoll);
public:
SocketHttpServerConnection(Ptr<SocketHttpServerLifecycle> server, const WString& token);
void DetachServer(SocketHttpServerLifecycle* server);
bool MarkAccepted();
bool IsAccepted();
bool HasCurrentCallback();
WString GetToken();
WaitForClientResult InvokeClientConnected(SocketHttpServer* server);
bool RegisterPoll(Ptr<SocketHttpRequestContext> context);
bool DispatchInbound(const WString& message, Ptr<SocketHttpServerOutboundMessage>& response);
void StopFromServer();
void WaitForPollCompletion();
void InstallCallback(INetworkProtocolCallback* callback) override;
void BeginReadingLoopUnsafe() override;
void SendString(const WString& str) override;
void Stop() override;
};
class SocketHttpServerLifecycle : public Object
{
public:
SocketHttpServer* owner = nullptr;
CriticalSection lockState;
ConditionVariable cvState;
Dictionary<WString, Ptr<SocketHttpServerConnection>>
connections;
List<Ptr<SocketHttpServerConnection>>
stoppingConnections;
bool startCalled = false;
bool started = false;
bool stopStarted = false;
bool stopProcessing = false;
bool stopProcessed = false;
bool stopAssistProcessing = false;
SocketHttpServerLifecycle(SocketHttpServer* _owner)
: owner(_owner)
{
}
void PrepareStart()
{
CS_LOCK(lockState)
{
CHECK_ERROR(!startCalled && !stopStarted, L"SocketHttpServer::Start can only be called once before stopping.");
startCalled = true;
started = true;
}
}
Ptr<SocketHttpServerConnection> CreateConnection(Ptr<SocketHttpServerLifecycle> retainedSelf)
{
while (true)
{
auto token = GenerateToken();
auto connection = Ptr(new SocketHttpServerConnection(retainedSelf, token));
CS_LOCK(lockState)
{
if (!started || stopStarted) return nullptr;
if (!connections.Keys().Contains(token))
{
connections.Add(token, connection);
return connection;
}
}
}
}
Ptr<SocketHttpServerConnection> FindConnection(const WString& token)
{
CS_LOCK(lockState)
{
if (stopStarted) return nullptr;
auto index = connections.Keys().IndexOf(token);
return index == -1 ? nullptr : connections.Values()[index];
}
return nullptr;
}
bool TryAccept(const WString& token, SocketHttpServerConnection* connection)
{
CS_LOCK(lockState)
{
if (stopStarted) return false;
auto index = connections.Keys().IndexOf(token);
if (index == -1 || connections.Values()[index].Obj() != connection) return false;
return connection->MarkAccepted();
}
return false;
}
bool IsStopped()
{
CS_LOCK(lockState) { return stopStarted; }
return true;
}
void RemoveConnection(const WString& token, SocketHttpServerConnection* connection)
{
bool removed = false;
bool retained = false;
CS_LOCK(lockState)
{
auto index = connections.Keys().IndexOf(token);
if (index != -1 && connections.Values()[index].Obj() == connection)
{
if (connection->IsAccepted())
{
stoppingConnections.Add(connections.Values()[index]);
retained = true;
}
connections.Remove(token);
removed = true;
}
}
if (removed && !retained) connection->DetachServer(this);
}
void PrepareStop(List<Ptr<SocketHttpServerConnection>>& stopping)
{
CS_LOCK(lockState)
{
if (!stopStarted)
{
stopStarted = true;
started = false;
for (auto connection : connections.Values()) stoppingConnections.Add(connection);
connections.Clear();
}
for (auto connection : stoppingConnections) stopping.Add(connection);
}
}
void PrepareStopProcessing(bool callbackNested, bool inheritsAssist, bool& execute, bool& assist)
{
CS_LOCK(lockState)
{
if (!stopProcessing && !stopProcessed)
{
stopProcessing = true;
execute = true;
if (callbackNested)
{
stopAssistProcessing = true;
assist = true;
}
}
else if (callbackNested && !inheritsAssist && !stopProcessed && !stopAssistProcessing)
{
stopAssistProcessing = true;
assist = true;
}
}
}
void FinishStop(bool ownsAssist)
{
lockState.Enter();
if (ownsAssist)
{
stopAssistProcessing = false;
}
else
{
while (stopAssistProcessing) cvState.SleepWith(lockState);
}
stopProcessing = false;
stopProcessed = true;
cvState.WakeAllPendings();
lockState.Leave();
}
void FinishStopAssist()
{
CS_LOCK(lockState)
{
stopAssistProcessing = false;
cvState.WakeAllPendings();
}
}
void WaitForStop()
{
CS_LOCK(lockState)
{
while (!stopProcessed) cvState.SleepWith(lockState);
}
}
Ptr<SocketHttpServerConnection> ReleaseStoppedConnection(SocketHttpServerConnection* connection)
{
Ptr<SocketHttpServerConnection> releasing;
CS_LOCK(lockState)
{
for (vint i = 0; i < stoppingConnections.Count(); i++)
{
if (stoppingConnections[i].Obj() == connection)
{
releasing = stoppingConnections[i];
stoppingConnections.RemoveAt(i);
break;
}
}
}
if (releasing) connection->DetachServer(this);
return releasing;
}
};
struct SocketHttpServerStopFrame
{
SocketHttpServerLifecycle* lifecycle = nullptr;
SocketHttpServerStopFrame* previous = nullptr;
bool ownsAssist = false;
};
thread_local SocketHttpServerStopFrame* currentSocketHttpServerStopFrame = nullptr;
SocketHttpServerStopFrame* FindSocketHttpServerStopFrame(SocketHttpServerLifecycle* lifecycle)
{
for (auto frame = currentSocketHttpServerStopFrame; frame; frame = frame->previous)
{
if (frame->lifecycle == lifecycle) return frame;
}
return nullptr;
}
struct SocketHttpServerStopScope
{
SocketHttpServerStopFrame frame;
SocketHttpServerStopScope(SocketHttpServerLifecycle* lifecycle, bool ownsAssist)
{
frame.lifecycle = lifecycle;
frame.previous = currentSocketHttpServerStopFrame;
frame.ownsAssist = ownsAssist;
currentSocketHttpServerStopFrame = &frame;
}
~SocketHttpServerStopScope()
{
currentSocketHttpServerStopFrame = frame.previous;
}
};
thread_local SocketHttpServerConnection::CallbackFrame* SocketHttpServerConnection::currentCallbackFrame = nullptr;
thread_local SocketHttpServerConnection::InboundFrame* SocketHttpServerConnection::currentInboundFrame = nullptr;
SocketHttpServerConnection::CallbackFrame::CallbackFrame(Ptr<SocketHttpServerConnectionLifecycle> _state)
: state(_state)
, previous(currentCallbackFrame)
{
currentCallbackFrame = this;
}
SocketHttpServerConnection::CallbackFrame::~CallbackFrame()
{
currentCallbackFrame = previous;
Ptr<SocketHttpServerLifecycle> server;
SocketHttpServerConnection* owner = nullptr;
CS_LOCK(state->lockState)
{
state->activeCallbacks--;
if (state->activeCallbacks == 0 && state->stopFinished && state->server)
{
server = state->server;
owner = state->owner;
}
state->cvState.WakeAllPendings();
}
Ptr<SocketHttpServerConnection> releasing;
if (server && owner) releasing = server->ReleaseStoppedConnection(owner);
}
SocketHttpServerConnection::InboundFrame::InboundFrame(Ptr<SocketHttpServerConnectionLifecycle> _state)
: state(_state)
, previous(currentInboundFrame)
{
currentInboundFrame = this;
}
SocketHttpServerConnection::InboundFrame::~InboundFrame()
{
currentInboundFrame = previous;
}
vint SocketHttpServerConnection::CurrentCallbackDepth(Ptr<SocketHttpServerConnectionLifecycle> state)
{
vint depth = 0;
for (auto frame = currentCallbackFrame; frame; frame = frame->previous)
{
if (frame->state == state) depth++;
}
return depth;
}
bool SocketHttpServerConnection::ClaimPollUnsafe(Ptr<SocketHttpServerConnectionLifecycle> state, PollWork& work)
{
if (
state->stopStarted ||
!state->accepted ||
state->inFlightPoll ||
!state->pendingPoll ||
state->queuedOutbound.Count() == 0
)
{
return false;
}
state->inFlightPoll = state->pendingPoll;
state->pendingPoll = nullptr;
state->inFlightMessage = state->queuedOutbound[0];
state->queuedOutbound.RemoveAt(0);
work.context = state->inFlightPoll;
work.message = state->inFlightMessage;
return true;
}
void SocketHttpServerConnection::StartPollResponse(Ptr<SocketHttpServerConnectionLifecycle> state, PollWork work)
{
if (!work) return;
InvokePollClaimed(state->token);
if (ShouldCancelPollBeforeResponse(state->token)) work.context->Cancel();
bool submitted = false;
try
{
submitted = work.context->RespondBytes(
200,
L"OK",
HttpNetworkProtocolContentType,
work.message->body,
Func<void(bool)>([state, context = work.context](bool succeeded)
{
FinishPollResponse(state, context, succeeded);
})
);
}
catch (...)
{
}
if (!submitted) FinishPollResponse(state, work.context, false);
}
void SocketHttpServerConnection::FinishPollResponse(Ptr<SocketHttpServerConnectionLifecycle> state, Ptr<SocketHttpRequestContext> context, bool succeeded)
{
PollWork next;
bool completed = false;
CS_LOCK(state->lockState)
{
if (state->inFlightPoll == context)
{
if (!succeeded && !state->stopStarted)
{
state->queuedOutbound.Insert(0, state->inFlightMessage);
}
state->inFlightPoll = nullptr;
state->inFlightMessage = nullptr;
ClaimPollUnsafe(state, next);
state->cvState.WakeAllPendings();
completed = true;
}
}
if (!completed) return;
InvokePollCompleted(state->token, succeeded);
StartPollResponse(state, next);
}
void SocketHttpServerConnection::ProcessPollRegistrations(Ptr<SocketHttpServerConnectionLifecycle> state)
{
while (true)
{
Ptr<SocketHttpRequestContext> context;
Ptr<SocketHttpRequestContext> replaced;
PollWork work;
bool cancel = false;
state->lockState.Enter();
if (state->queuedPollRegistrations.Count() == 0)
{
auto registered = state->pendingPoll && !state->stopStarted && state->accepted;
state->pollRegistrationProcessing = false;
state->cvState.WakeAllPendings();
state->lockState.Leave();
if (registered) InvokePollRegistered(state->token);
return;
}
context = state->queuedPollRegistrations[0];
state->queuedPollRegistrations.RemoveAt(0);
replaced = state->pendingPoll;
state->pendingPoll = nullptr;
state->lockState.Leave();
if (replaced) replaced->Cancel();
CS_LOCK(state->lockState)
{
if (state->stopStarted || !state->accepted)
{
cancel = true;
}
else
{
state->pendingPoll = context;
ClaimPollUnsafe(state, work);
}
}
if (cancel) context->Cancel();
StartPollResponse(state, work);
}
}
SocketHttpServerConnection::SocketHttpServerConnection(Ptr<SocketHttpServerLifecycle> server, const WString& token)
: lifecycle(Ptr(new SocketHttpServerConnectionLifecycle))
{
lifecycle->owner = this;
lifecycle->server = server;
lifecycle->token = token;
}
void SocketHttpServerConnection::DetachServer(SocketHttpServerLifecycle* server)
{
CS_LOCK(lifecycle->lockState)
{
if (lifecycle->server.Obj() == server) lifecycle->server = nullptr;
}
}
bool SocketHttpServerConnection::MarkAccepted()
{
CS_LOCK(lifecycle->lockState)
{
if (!lifecycle->stopStarted)
{
lifecycle->accepted = true;
return true;
}
}
return false;
}
bool SocketHttpServerConnection::IsAccepted()
{
CS_LOCK(lifecycle->lockState) { return lifecycle->accepted; }
return false;
}
bool SocketHttpServerConnection::HasCurrentCallback()
{
return CurrentCallbackDepth(lifecycle) > 0;
}
WString SocketHttpServerConnection::GetToken()
{
return lifecycle->token;
}
WaitForClientResult SocketHttpServerConnection::InvokeClientConnected(SocketHttpServer* server)
{
auto state = lifecycle;
bool invoke = false;
CS_LOCK(state->lockState)
{
if (!state->stopStarted)
{
state->activeCallbacks++;
invoke = true;
}
}
if (!invoke) return WaitForClientResult::Reject;
WaitForClientResult result;
{
CallbackFrame frame(state);
result = server->OnClientConnected(this);
}
if (result != WaitForClientResult::Accept) return WaitForClientResult::Reject;
Ptr<SocketHttpServerLifecycle> retainedServer;
CS_LOCK(state->lockState) { retainedServer = state->server; }
return retainedServer && retainedServer->TryAccept(state->token, this)
? WaitForClientResult::Accept
: WaitForClientResult::Reject;
}
bool SocketHttpServerConnection::RegisterPoll(Ptr<SocketHttpRequestContext> context)
{
auto state = lifecycle;
bool process = false;
CS_LOCK(state->lockState)
{
if (state->stopStarted || !state->accepted) return false;
state->queuedPollRegistrations.Add(context);
if (!state->pollRegistrationProcessing)
{
state->pollRegistrationProcessing = true;
process = true;
}
}
if (process) ProcessPollRegistrations(state);
return true;
}
bool SocketHttpServerConnection::DispatchInbound(const WString& message, Ptr<SocketHttpServerOutboundMessage>& response)
{
auto state = lifecycle;
INetworkProtocolCallback* installed = nullptr;
PollWork work;
CS_LOCK(state->lockState)
{
if (state->stopStarted || !state->accepted) return false;
if (state->callback && !state->callbackInstalling)
{
installed = state->callback;
state->activeCallbacks++;
}
else
{
state->queuedInbound.Add(message);
if (state->queuedOutbound.Count() > 0)
{
response = state->queuedOutbound[0];
state->queuedOutbound.RemoveAt(0);
}
ClaimPollUnsafe(state, work);
}
}
if (!installed)
{
StartPollResponse(state, work);
return true;
}
List<Ptr<SocketHttpServerOutboundMessage>> generated;
{
CallbackFrame callbackFrame(state);
InboundFrame inboundFrame(state);
installed->OnReadString(message);
generated = std::move(inboundFrame.generated);
}
CS_LOCK(state->lockState)
{
if (state->stopStarted) return false;
if (generated.Count() > 0)
{
response = generated[0];
for (vint i = 1; i < generated.Count(); i++) state->queuedOutbound.Add(generated[i]);
}
else if (state->queuedOutbound.Count() > 0)
{
response = state->queuedOutbound[0];
state->queuedOutbound.RemoveAt(0);
}
ClaimPollUnsafe(state, work);
}
StartPollResponse(state, work);
return true;
}
void SocketHttpServerConnection::StopCore(bool removeFromServer, bool waitForPoll)
{
auto state = lifecycle;
if (removeFromServer)
{
Ptr<SocketHttpServerLifecycle> server;
CS_LOCK(state->lockState) { server = state->server; }
if (server) server->RemoveConnection(state->token, this);
}
auto callbackDepth = CurrentCallbackDepth(state);
List<Ptr<SocketHttpRequestContext>> cancelling;
bool first = false;
bool ownsAssist = false;
state->lockState.Enter();
if (!state->stopStarted)
{
first = true;
state->stopStarted = true;
if (callbackDepth > 0)
{
state->stopAssistProcessing = true;
ownsAssist = true;
}
if (state->pendingPoll) cancelling.Add(state->pendingPoll);
state->pendingPoll = nullptr;
for (auto context : state->queuedPollRegistrations) cancelling.Add(context);
state->queuedPollRegistrations.Clear();
state->queuedInbound.Clear();
state->queuedOutbound.Clear();
}
else if (callbackDepth > 0)
{
if (
state->stopFinished ||
state->disconnectDelivering ||
state->disconnectFinished ||
state->stopAssistProcessing
)
{
state->lockState.Leave();
return;
}
state->stopAssistProcessing = true;
ownsAssist = true;
state->lockState.Leave();
}
else
{
while (!state->stopFinished) state->cvState.SleepWith(state->lockState);
while (state->activeCallbacks > 0 || (waitForPoll && (state->inFlightPoll || state->pollRegistrationProcessing)))
{
state->cvState.SleepWith(state->lockState);
}
state->lockState.Leave();
return;
}
if (first) state->lockState.Leave();
if (first)
{
for (auto context : cancelling)
{
try { context->Cancel(); } catch (...) {}
}
CS_LOCK(state->lockState)
{
state->stopCancellationFinished = true;
state->cvState.WakeAllPendings();
}
}
INetworkProtocolCallback* disconnected = nullptr;
state->lockState.Enter();
while (!state->stopCancellationFinished || state->pollRegistrationProcessing)
{
state->cvState.SleepWith(state->lockState);
}
if (first && !ownsAssist)
{
while (state->stopAssistProcessing) state->cvState.SleepWith(state->lockState);
}
while (state->activeCallbacks > callbackDepth)
{
state->cvState.SleepWith(state->lockState);
}
if (state->accepted && state->callback && !state->disconnectDelivering && !state->disconnectFinished)
{
disconnected = state->callback;
state->disconnectDelivering = true;
state->activeCallbacks++;
}
state->lockState.Leave();
if (disconnected)
{
try
{
CallbackFrame frame(state);
disconnected->OnDisconnected();
}
catch (...)
{
}
}
Ptr<SocketHttpServerLifecycle> releasingServer;
CS_LOCK(state->lockState)
{
state->callback = nullptr;
state->callbackInstalling = false;
state->disconnectDelivering = false;
state->disconnectFinished = true;
if (ownsAssist) state->stopAssistProcessing = false;
if (first)
{
state->stopFinished = true;
if (state->activeCallbacks == 0) releasingServer = state->server;
}
state->cvState.WakeAllPendings();
}
Ptr<SocketHttpServerConnection> releasing;
if (releasingServer) releasing = releasingServer->ReleaseStoppedConnection(this);
if (first && waitForPoll)
{
CS_LOCK(state->lockState)
{
while (state->inFlightPoll) state->cvState.SleepWith(state->lockState);
}
}
}
void SocketHttpServerConnection::StopFromServer()
{
StopCore(false, false);
}
void SocketHttpServerConnection::WaitForPollCompletion()
{
CS_LOCK(lifecycle->lockState)
{
while (lifecycle->inFlightPoll || lifecycle->pollRegistrationProcessing)
{
lifecycle->cvState.SleepWith(lifecycle->lockState);
}
}
}
void SocketHttpServerConnection::InstallCallback(INetworkProtocolCallback* callback)
{
auto state = lifecycle;
if (!callback)
{
auto callbackDepth = CurrentCallbackDepth(state);
CS_LOCK(state->lockState)
{
state->callback = nullptr;
while (state->activeCallbacks > callbackDepth)
{
state->cvState.SleepWith(state->lockState);
}
}
return;
}
bool canInstall = false;
CS_LOCK(state->lockState)
{
if (!state->callback && !state->callbackInstalling && !state->stopStarted)
{
state->callback = callback;
state->callbackInstalling = true;
state->activeCallbacks++;
canInstall = true;
}
}
CHECK_ERROR(canInstall, L"SocketHttpServerConnection::InstallCallback cannot replace a callback or install one on a stopped connection.");
try
{
CallbackFrame frame(state);
callback->OnInstalled(this);
while (true)
{
WString message;
bool replay = false;
CS_LOCK(state->lockState)
{
if (!state->stopStarted && state->callback == callback && state->queuedInbound.Count() > 0)
{
message = state->queuedInbound[0];
state->queuedInbound.RemoveAt(0);
replay = true;
}
else
{
state->callbackInstalling = false;
state->cvState.WakeAllPendings();
}
}
if (!replay) break;
callback->OnReadString(message);
}
}
catch (...)
{
CS_LOCK(state->lockState)
{
if (state->callback == callback) state->callback = nullptr;
state->callbackInstalling = false;
state->cvState.WakeAllPendings();
}
throw;
}
}
void SocketHttpServerConnection::BeginReadingLoopUnsafe()
{
}
void SocketHttpServerConnection::SendString(const WString& str)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::async_tcp_socket::SocketHttpServerConnection::SendString(const WString&)#"
Array<vuint8_t> validated;
CHECK_ERROR(str.Length() > 0, ERROR_MESSAGE_PREFIX L"A logical HTTP message cannot be empty.");
CHECK_ERROR(IsValidHttpNetworkProtocolMessage(str), ERROR_MESSAGE_PREFIX L"A logical HTTP message must contain valid Unicode without NUL.");
CHECK_ERROR(EncodeStrictUtf8(str, validated), ERROR_MESSAGE_PREFIX L"A logical HTTP message must contain valid Unicode without NUL.");
CHECK_ERROR(validated.Count() <= HttpBodySizeLimit, ERROR_MESSAGE_PREFIX L"The UTF-8 message exceeds HttpBodySizeLimit.");
#undef ERROR_MESSAGE_PREFIX
auto message = Ptr(new SocketHttpServerOutboundMessage);
message->body = std::move(validated);
auto state = lifecycle;
PollWork work;
CS_LOCK(state->lockState)
{
CHECK_ERROR(!state->stopStarted, L"SocketHttpServerConnection::SendString cannot send on a stopped connection.");
if (currentInboundFrame && currentInboundFrame->state == state)
{
currentInboundFrame->generated.Add(message);
return;
}
state->queuedOutbound.Add(message);
ClaimPollUnsafe(state, work);
}
StartPollResponse(state, work);
}
void SocketHttpServerConnection::Stop()
{
StopCore(true, true);
}
}
class SocketHttpServer::Impl : public Object
{
public:
enum class BeginStopResult
{
Continue,
ReturnFollower,
};
Ptr<SocketHttpServerLifecycle> lifecycle;
Impl(SocketHttpServer* owner)
: lifecycle(Ptr(new SocketHttpServerLifecycle(owner)))
{
}
static bool HasCurrentCallback(List<Ptr<SocketHttpServerConnection>>& stopping)
{
for (auto connection : stopping)
{
if (connection->HasCurrentCallback()) return true;
}
return false;
}
static void DrainConnections(List<Ptr<SocketHttpServerConnection>>& stopping)
{
for (auto connection : stopping)
{
if (!connection->HasCurrentCallback()) connection->StopFromServer();
}
for (auto connection : stopping)
{
if (connection->HasCurrentCallback()) connection->StopFromServer();
}
}
void ExecuteAssistant(List<Ptr<SocketHttpServerConnection>>& stopping)
{
try
{
SocketHttpServerStopScope scope(lifecycle.Obj(), true);
DrainConnections(stopping);
}
catch (...)
{
lifecycle->FinishStopAssist();
throw;
}
lifecycle->FinishStopAssist();
}
BeginStopResult BeginStop(List<Ptr<SocketHttpServerConnection>>& stopping)
{
lifecycle->PrepareStop(stopping);
auto existingFrame = FindSocketHttpServerStopFrame(lifecycle.Obj());
auto callbackNested = HasCurrentCallback(stopping);
bool execute = false;
bool assist = false;
lifecycle->PrepareStopProcessing(
callbackNested,
existingFrame && existingFrame->ownsAssist,
execute,
assist
);
if (execute)
{
try
{
SocketHttpServerStopScope scope(lifecycle.Obj(), assist);
DrainConnections(stopping);
}
catch (...)
{
lifecycle->FinishStop(assist);
throw;
}
lifecycle->FinishStop(assist);
return BeginStopResult::Continue;
}
if (existingFrame)
{
if (existingFrame->ownsAssist)
{
DrainConnections(stopping);
}
else if (assist)
{
ExecuteAssistant(stopping);
}
return BeginStopResult::ReturnFollower;
}
if (callbackNested)
{
if (assist) ExecuteAssistant(stopping);
return BeginStopResult::ReturnFollower;
}
lifecycle->WaitForStop();
DrainConnections(stopping);
return BeginStopResult::Continue;
}
void OnRequest(SocketHttpServer* owner, Ptr<SocketHttpRequestContext> context)
{
auto request = context->GetRequest();
auto path = context->GetRelativePath();
if (!request || context->GetQuery() != WString::Empty)
{
context->RespondStatus(404, L"Route not found");
return;
}
if (request->method == L"GET" && path == HttpServerUrl_Connect && HasEmptyBody(request))
{
auto connection = lifecycle->CreateConnection(lifecycle);
if (!connection)
{
context->RespondStatus(404, L"Connection rejected");
return;
}
WaitForClientResult result = WaitForClientResult::Reject;
try { result = connection->InvokeClientConnected(owner); }
catch (...) { result = WaitForClientResult::Reject; }
if (result != WaitForClientResult::Accept)
{
connection->Stop();
context->RespondStatus(404, L"Connection rejected");
return;
}
auto token = connection->GetToken();
auto body = CreateHttpNetworkProtocolConnectBody(
WString::Unmanaged(HttpServerUrl_Request) + L"/" + token,
WString::Unmanaged(HttpServerUrl_Response) + L"/" + token
);
context->RespondUtf8(200, L"OK", HttpNetworkProtocolContentType, body);
return;
}
WString token;
if (request->method == L"POST" && ExtractToken(path, HttpServerUrl_Request, token) && HasEmptyBody(request))
{
auto connection = lifecycle->FindConnection(token);
if (connection && connection->RegisterPoll(context)) return;
context->RespondStatus(404, L"Connection not found");
return;
}
WString message;
if (request->method == L"POST" && ExtractToken(path, HttpServerUrl_Response, token) && DecodeSubmittedMessage(context, request, message))
{
auto connection = lifecycle->FindConnection(token);
Ptr<SocketHttpServerOutboundMessage> response;
if (connection && connection->DispatchInbound(message, response))
{
Array<vuint8_t> empty;
context->RespondBytes(200, L"OK", HttpNetworkProtocolContentType, response ? response->body : empty);
return;
}
}
context->RespondStatus(404, L"Route not found");
}
};
void SetSocketHttpServerPollCallbacksForTesting(
const Func<void(const WString&)>& claimed,
const Func<void(const WString&, bool)>& completed,
const Func<void(const WString&)>& registered,
const Func<bool(const WString&)>& cancelBeforeResponse
)
{
auto& hooks = GetSocketHttpServerTestHooks();
SPIN_LOCK(hooks.lock)
{
hooks.claimed = claimed;
hooks.completed = completed;
hooks.registered = registered;
hooks.cancelBeforeResponse = cancelBeforeResponse;
}
}
void ResetSocketHttpServerPollCallbacksForTesting()
{
SetSocketHttpServerPollCallbacksForTesting({}, {}, {}, {});
}
SocketHttpServer::SocketHttpServer(Ptr<IAsyncSocketServer> server, const WString& urlPrefix)
: SocketHttpServerApi(server, ValidateServerUrlPrefix(urlPrefix))
, impl(Ptr(new Impl(this)))
{
}
SocketHttpServer::~SocketHttpServer()
{
try { Stop(); } catch (...) {}
CS_LOCK(impl->lifecycle->lockState) { impl->lifecycle->owner = nullptr; }
}
WaitForClientResult SocketHttpServer::OnClientConnected(INetworkProtocolConnection*)
{
return WaitForClientResult::Accept;
}
void SocketHttpServer::OnHttpRequestReceived(Ptr<SocketHttpRequestContext> context)
{
impl->OnRequest(this, context);
}
void SocketHttpServer::OnHttpServerStopping()
{
List<Ptr<SocketHttpServerConnection>> stopping;
impl->BeginStop(stopping);
}
void SocketHttpServer::Start()
{
impl->lifecycle->PrepareStart();
try
{
SocketHttpServerApi::Start();
}
catch (...)
{
List<Ptr<SocketHttpServerConnection>> stopping;
impl->BeginStop(stopping);
throw;
}
}
void SocketHttpServer::Stop()
{
List<Ptr<SocketHttpServerConnection>> stopping;
if (impl->BeginStop(stopping) == Impl::BeginStopResult::ReturnFollower) return;
SocketHttpServerApi::Stop();
for (auto connection : stopping) connection->WaitForPollCompletion();
}
bool SocketHttpServer::IsStopped()
{
return impl->lifecycle->IsStopped() || SocketHttpServerApi::IsStopped();
}
}
/***********************************************************************
.\INTERPROCESS\ASYNCSOCKET\ASYNCSOCKET_HTTPSERVERAPI.CPP
***********************************************************************/
namespace vl::inter_process::async_tcp_socket
{
using namespace collections;
namespace
{
class ApiRegistration;
class RegistryEntry;
class SharedServer;
class ConnectionState;
struct PrefixInfo
{
WString normalizedUrl;
WString decodedPath;
};
wchar_t Lower(wchar_t c)
{
return c >= L'A' && c <= L'Z' ? c - L'A' + L'a' : c;
}
WString Lower(const WString& text)
{
if (text.Length() == 0) return {};
auto buffer = new wchar_t[text.Length() + 1];
for (vint i = 0; i < text.Length(); i++) buffer[i] = Lower(text[i]);
buffer[text.Length()] = 0;
return WString::TakeOver(buffer, text.Length());
}
bool Token(wchar_t c)
{
if ((c >= L'a' && c <= L'z') || (c >= L'A' && c <= L'Z') || (c >= L'0' && c <= L'9')) return true;
switch (c)
{
case L'!': case L'#': case L'$': case L'%': case L'&': case L'\'': case L'*':
case L'+': case L'-': case L'.': case L'^': case L'_': case L'`': case L'|': case L'~':
return true;
default:
return false;
}
}
vint Hex(wchar_t c)
{
if (c >= L'0' && c <= L'9') return c - L'0';
if (c >= L'a' && c <= L'f') return c - L'a' + 10;
if (c >= L'A' && c <= L'F') return c - L'A' + 10;
return -1;
}
bool DecodePath(const WString& raw, WString& decoded)
{
List<vuint8_t> bytes;
for (vint i = 0; i < raw.Length(); i++)
{
auto c = raw[i];
if (c == L'%')
{
if (i + 2 >= raw.Length()) return false;
auto h1 = Hex(raw[i + 1]);
auto h2 = Hex(raw[i + 2]);
if (h1 < 0 || h2 < 0) return false;
auto b = (vuint8_t)(h1 * 16 + h2);
if (b == 0 || b == '/' || b == '\\') return false;
bytes.Add(b);
i += 2;
}
else
{
if (c == 0 || c == L'\\' || c > 0x7F) return false;
bytes.Add((vuint8_t)c);
}
}
Array<vuint8_t> encoded(bytes.Count());
for (vint i = 0; i < bytes.Count(); i++) encoded[i] = bytes[i];
return ::vl::inter_process::async_tcp_socket::DecodeStrictUtf8(
encoded.Count() == 0 ? nullptr : &encoded[0],
encoded.Count(),
decoded
);
}
bool ParseAuthority(const WString& text, vint& port)
{
vint colon = -1;
for (vint i = 0; i < text.Length(); i++)
{
if (text[i] == L':')
{
if (colon != -1) return false;
colon = i;
}
else if (text[i] == L'@' || text[i] == L'/' || text[i] == L'?' || text[i] == L'#') return false;
}
if (colon <= 0 || colon + 1 >= text.Length()) return false;
auto host = Lower(text.Left(colon));
if (host != L"localhost" && host != L"127.0.0.1") return false;
vuint64_t number = 0;
for (vint i = colon + 1; i < text.Length(); i++)
{
if (text[i] < L'0' || text[i] > L'9') return false;
number = number * 10 + text[i] - L'0';
if (number > 65535) return false;
}
if (number == 0) return false;
port = (vint)number;
return true;
}
PrefixInfo ParsePrefix(vint port, const WString& urlPrefix)
{
#define ERROR_PREFIX L"vl::inter_process::async_tcp_socket::SocketHttpServerApi::SocketHttpServerApi(Ptr<IAsyncSocketServer>, const WString&, bool)#"
CHECK_ERROR(1 <= port && port <= 65535, ERROR_PREFIX L"The injected server port must be in 1..65535.");
CHECK_ERROR(urlPrefix.Length() == 0 || urlPrefix[0] == L'/', ERROR_PREFIX L"The URL prefix must be empty or begin with /.");
CHECK_ERROR(urlPrefix.IndexOf(L'?') == -1 && urlPrefix.IndexOf(L'#') == -1, ERROR_PREFIX L"Query and fragment components are not supported.");
auto rawPath = urlPrefix;
PrefixInfo info;
while (rawPath.Length() > 0 && rawPath[rawPath.Length() - 1] == L'/') rawPath = rawPath.Left(rawPath.Length() - 1);
CHECK_ERROR(DecodePath(rawPath, info.decodedPath), ERROR_PREFIX L"The path contains invalid escaping, UTF-8, NUL, or an encoded separator.");
info.normalizedUrl = L"http://localhost:" + itow(port) + rawPath;
return info;
#undef ERROR_PREFIX
}
WString TrimOws(const WString& text)
{
vint begin = 0, end = text.Length();
while (begin < end && (text[begin] == L' ' || text[begin] == L'\t')) begin++;
while (begin < end && (text[end - 1] == L' ' || text[end - 1] == L'\t')) end--;
return text.Sub(begin, end - begin);
}
bool AnalyzeCacheControl(const Array<vuint8_t>& bytes, bool& noStore)
{
WString text;
if (!DecodeAsciiHttpFieldValue(bytes, text)) return false;
noStore = false;
vint begin = 0;
bool quoted = false, escaped = false;
for (vint i = 0; i <= text.Length(); i++)
{
if (i < text.Length())
{
auto c = text[i];
if (escaped) { escaped = false; continue; }
if (quoted)
{
if (c == L'\\') escaped = true;
else if (c == L'"') quoted = false;
continue;
}
if (c == L'"') { quoted = true; continue; }
if (c != L',') continue;
}
else if (quoted || escaped) return false;
auto item = TrimOws(text.Sub(begin, i - begin));
if (item.Length() == 0) return false;
auto equals = item.IndexOf(L'=');
auto name = TrimOws(equals == -1 ? item : item.Left(equals));
if (name.Length() == 0) return false;
for (vint j = 0; j < name.Length(); j++) if (!Token(name[j])) return false;
if (equals != -1 && TrimOws(item.Right(item.Length() - equals - 1)).Length() == 0) return false;
if (Lower(name) == L"no-store")
{
if (equals != -1) return false;
noStore = true;
}
begin = i + 1;
}
return true;
}
bool ValidHttpDate(const Array<vuint8_t>& bytes)
{
WString text;
if (!DecodeAsciiHttpFieldValue(bytes, text)) return false;
text = TrimOws(text);
if (text.Length() != 29
|| text[3] != L',' || text[4] != L' ' || text[7] != L' ' || text[11] != L' '
|| text[16] != L' ' || text[19] != L':' || text[22] != L':' || text[25] != L' '
|| text.Sub(26, 3) != L"GMT") return false;
auto dayName = text.Sub(0, 3);
if (dayName != L"Sun" && dayName != L"Mon" && dayName != L"Tue" && dayName != L"Wed" && dayName != L"Thu" && dayName != L"Fri" && dayName != L"Sat") return false;
vint digitIndexes[] = { 5, 6, 12, 13, 14, 15, 17, 18, 20, 21, 23, 24 };
for (auto index : digitIndexes)
{
if (text[index] < L'0' || text[index] > L'9') return false;
}
auto monthName = text.Sub(8, 3);
vint month =
monthName == L"Jan" ? 1 : monthName == L"Feb" ? 2 : monthName == L"Mar" ? 3 :
monthName == L"Apr" ? 4 : monthName == L"May" ? 5 : monthName == L"Jun" ? 6 :
monthName == L"Jul" ? 7 : monthName == L"Aug" ? 8 : monthName == L"Sep" ? 9 :
monthName == L"Oct" ? 10 : monthName == L"Nov" ? 11 : monthName == L"Dec" ? 12 : 0;
if (month == 0) return false;
auto day = (text[5] - L'0') * 10 + text[6] - L'0';
auto year = (text[12] - L'0') * 1000 + (text[13] - L'0') * 100 + (text[14] - L'0') * 10 + text[15] - L'0';
auto hour = (text[17] - L'0') * 10 + text[18] - L'0';
auto minute = (text[20] - L'0') * 10 + text[21] - L'0';
auto second = (text[23] - L'0') * 10 + text[24] - L'0';
if (year == 0 || hour > 23 || minute > 59 || second > 60) return false;
vint monthDays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (month == 2 && (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0))) monthDays[1] = 29;
return day >= 1 && day <= monthDays[month - 1];
}
const wchar_t* Reason(vint code)
{
switch (code)
{
case 200: return L"OK"; case 204: return L"No Content"; case 304: return L"Not Modified";
case 400: return L"Bad Request"; case 404: return L"Not Found"; case 405: return L"Method Not Allowed";
case 408: return L"Request Timeout"; case 413: return L"Payload Too Large"; case 414: return L"URI Too Long";
case 415: return L"Unsupported Media Type"; case 417: return L"Expectation Failed";
case 431: return L"Request Header Fields Too Large"; case 500: return L"Internal Server Error";
case 501: return L"Not Implemented"; case 505: return L"HTTP Version Not Supported";
default: return L"Response";
}
}
void ValidateConvenienceResponseArguments(vint statusCode, const WString& reason, const WString& contentType, vint bodySize)
{
CHECK_ERROR(statusCode >= 200 && statusCode <= 599, L"Invalid HTTP response status.");
for (vint i = 0; i < reason.Length(); i++)
{
CHECK_ERROR((vuint32_t)reason[i] >= 0x20 && (vuint32_t)reason[i] <= 0x7E, L"Invalid HTTP response reason phrase.");
}
if (contentType != WString::Empty)
{
CreateAsciiHttpField(L"content-type", contentType);
}
CHECK_ERROR(bodySize <= HttpBodySizeLimit, L"The response body is too large.");
}
Ptr<HttpResponse> CreateConvenienceResponse(vint statusCode, const WString& reason, const WString& contentType, Array<vuint8_t>&& body)
{
auto response = Ptr(new HttpResponse);
response->statusCode = statusCode;
response->reason = reason;
if (contentType != WString::Empty)
{
response->headers.Add(CreateAsciiHttpField(L"content-type", contentType));
}
SetHttpBodyBytes(response->body, std::move(body));
return response;
}
WString Two(vint value) { return value < 10 ? L"0" + itow(value) : itow(value); }
WString DateHeader()
{
const wchar_t* days[] = { L"Sun", L"Mon", L"Tue", L"Wed", L"Thu", L"Fri", L"Sat" };
const wchar_t* months[] = { L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec" };
auto now = DateTime::UtcTime();
return WString::Unmanaged(days[now.dayOfWeek]) + L", " + Two(now.day) + L" " + months[now.month - 1] + L" " + itow(now.year) + L" " + Two(now.hour) + L":" + Two(now.minute) + L":" + Two(now.second) + L" GMT";
}
Ptr<HttpResponse> Normalize(Ptr<HttpResponse> input, const WString& method)
{
CHECK_ERROR(input, L"SocketHttpRequestContext::Respond requires a response.");
CHECK_ERROR(input->statusCode >= 200 && input->statusCode <= 599, L"Invalid HTTP response status.");
CHECK_ERROR(input->body.trailers.Count() == 0, L"Response trailers are not supported.");
Array<vuint8_t> body;
CHECK_ERROR(FlattenHttpBody(input->body, body), L"The response body is too large.");
auto bodySize = body.Count();
auto bodyAllowed = method != L"HEAD" && input->statusCode != 204 && input->statusCode != 304;
auto length = bodyAllowed || method == L"HEAD" ? bodySize : 0;
auto output = Ptr(new HttpResponse);
output->statusCode = input->statusCode;
output->reason = input->reason == WString::Empty ? Reason(input->statusCode) : input->reason;
for (vint i = 0; i < output->reason.Length(); i++)
{
CHECK_ERROR(output->reason[i] >= 0x20 && output->reason[i] <= 0x7E, L"Invalid HTTP response reason phrase.");
}
List<HttpField> normalizedHeaders;
for (auto&& source : input->headers)
{
auto copy = CreateAsciiHttpField(source.name, WString::Empty);
for (auto c : source.value) CHECK_ERROR(c == '\t' || (c >= 0x20 && c != 0x7F), L"Invalid response field value.");
copy.value.Resize(source.value.Count());
for (vint i = 0; i < source.value.Count(); i++) copy.value[i] = source.value[i];
normalizedHeaders.Add(std::move(copy));
}
HttpFraming framing;
CHECK_ERROR(AnalyzeHttpFraming(normalizedHeaders, framing) == HttpFramingAnalysisResult::Succeeded, L"Invalid or unsupported HTTP response framing.");
CHECK_ERROR(framing.kind != HttpFramingKind::Chunked, L"Response Transfer-Encoding is not supported.");
CHECK_ERROR(framing.contentLengthValuesPlainDecimal, L"Application Content-Length values must be plain decimal numbers.");
CHECK_ERROR(framing.contentLength <= (vuint64_t)std::numeric_limits<vint>::max(), L"The application Content-Length is too large.");
bool date = false, noStore = false, cors = false;
for (auto&& source : normalizedHeaders)
{
if (source.name == L"content-length") continue;
if (source.name == L"date")
{
CHECK_ERROR(!date && ValidHttpDate(source.value), L"A response requires at most one valid IMF-fixdate Date field.");
date = true;
}
else if (source.name == L"cache-control")
{
bool sourceNoStore = false;
CHECK_ERROR(AnalyzeCacheControl(source.value, sourceNoStore), L"Invalid Cache-Control response field.");
noStore |= sourceNoStore;
}
else if (source.name == L"access-control-allow-origin")
{
WString value;
CHECK_ERROR(!cors && DecodeAsciiHttpFieldValue(source.value, value) && TrimOws(value) == L"*", L"The loopback CORS policy requires exactly one Access-Control-Allow-Origin: * field.");
cors = true;
}
HttpField copy;
copy.name = source.name;
copy.value.Resize(source.value.Count());
if (source.value.Count() > 0)
{
memcpy(&copy.value[0], &source.value[0], source.value.Count());
}
output->headers.Add(std::move(copy));
}
auto contentLength = framing.kind == HttpFramingKind::ContentLength;
auto suppliedLength = (vint)framing.contentLength;
auto outputLength = length;
if (contentLength && input->statusCode == 304 && method != L"HEAD") outputLength = suppliedLength;
else CHECK_ERROR(!contentLength || suppliedLength == length, L"Contradictory response Content-Length.");
if (!date) output->headers.Add(CreateAsciiHttpField(L"date", DateHeader()));
if (!noStore) output->headers.Add(CreateAsciiHttpField(L"cache-control", L"no-store"));
if (!cors) output->headers.Add(CreateAsciiHttpField(L"access-control-allow-origin", L"*"));
if (input->statusCode != 204) output->headers.Add(CreateAsciiHttpField(L"content-length", itow(outputLength)));
if (bodyAllowed && bodySize > 0)
{
SetHttpBodyBytes(output->body, std::move(body));
}
return output;
}
Ptr<HttpResponse> Automatic(vint code, bool close, bool preflight = false)
{
auto response = Ptr(new HttpResponse);
response->statusCode = code;
response->reason = Reason(code);
if (close) response->headers.Add(CreateAsciiHttpField(L"connection", L"close"));
if (preflight)
{
response->headers.Add(CreateAsciiHttpField(L"access-control-allow-methods", L"GET, HEAD, POST, OPTIONS"));
response->headers.Add(CreateAsciiHttpField(L"access-control-allow-headers", L"Accept, Content-Type"));
response->headers.Add(CreateAsciiHttpField(L"allow", L"GET, HEAD, POST, OPTIONS"));
}
return response;
}
}
}
namespace vl::inter_process::async_tcp_socket
{
using namespace collections;
namespace
{
class ApiRegistration : public Object
{
public:
struct Frame
{
ApiRegistration* registration;
Frame* previous;
Frame(ApiRegistration* value);
~Frame();
};
static thread_local Frame* currentFrame;
PrefixInfo prefix;
bool respondToOptions;
Ptr<RegistryEntry> entry;
CriticalSection lock;
ConditionVariable cv;
List<Ptr<SocketHttpRequestContext>> contexts;
List<Func<void()>> contextCancellations;
Func<void(Ptr<SocketHttpRequestContext>)> requestCallback;
Func<void()> stoppingCallback;
vint activeCallbacks = 0;
bool stopping = false;
bool stopped = false;
ApiRegistration(const PrefixInfo& _prefix, bool _respondToOptions)
: prefix(_prefix), respondToOptions(_respondToOptions)
{
}
static vint Depth(ApiRegistration* registration)
{
vint depth = 0;
for (auto frame = currentFrame; frame; frame = frame->previous) if (frame->registration == registration) depth++;
return depth;
}
bool AddContext(Ptr<SocketHttpRequestContext> context, const Func<void()>& cancellation)
{
bool added = false;
CS_LOCK(lock)
{
if (!stopping)
{
contexts.Add(context);
contextCancellations.Add(cancellation);
added = true;
}
}
return added;
}
void RemoveContext(SocketHttpRequestContext* context)
{
CS_LOCK(lock)
{
vint index = -1;
for (vint i = 0; i < contexts.Count(); i++)
{
if (contexts[i].Obj() == context) { index = i; break; }
}
if (index != -1)
{
contexts.RemoveAt(index);
contextCancellations.RemoveAt(index);
}
cv.WakeAllPendings();
}
}
bool InvokeRequest(Ptr<SocketHttpRequestContext> context)
{
Func<void(Ptr<SocketHttpRequestContext>)> callback;
CS_LOCK(lock)
{
if (!stopping && requestCallback)
{
callback = requestCallback;
activeCallbacks++;
}
}
if (!callback) return false;
Frame frame(this);
try
{
callback(context);
}
catch (...)
{
CS_LOCK(lock) { activeCallbacks--; cv.WakeAllPendings(); }
throw;
}
CS_LOCK(lock) { activeCallbacks--; cv.WakeAllPendings(); }
return true;
}
bool ReserveCompletion(const Func<void(bool)>& callback)
{
if (!callback) return false;
bool reserved = false;
CS_LOCK(lock)
{
if (!stopped) { activeCallbacks++; reserved = true; }
}
return reserved;
}
void InvokeReservedCompletion(const Func<void(bool)>& callback, bool succeeded)
{
Frame frame(this);
try { callback(succeeded); } catch (...) {}
CS_LOCK(lock) { activeCallbacks--; cv.WakeAllPendings(); }
}
void Stop(bool notify)
{
List<Func<void()>> cancelling;
Func<void()> callback;
auto depth = Depth(this);
lock.Enter();
if (stopped) { lock.Leave(); return; }
if (stopping)
{
if (depth > 0) { lock.Leave(); return; }
while (!stopped) cv.SleepWith(lock);
lock.Leave();
return;
}
stopping = true;
if (notify) callback = stoppingCallback;
for (auto cancellation : contextCancellations) cancelling.Add(cancellation);
lock.Leave();
if (callback)
{
CS_LOCK(lock) { activeCallbacks++; }
Frame frame(this);
try { callback(); } catch (...) {}
CS_LOCK(lock) { activeCallbacks--; cv.WakeAllPendings(); }
}
for (auto cancellation : cancelling) cancellation();
lock.Enter();
while (contexts.Count() > 0 || activeCallbacks > depth) cv.SleepWith(lock);
requestCallback = {};
stoppingCallback = {};
stopped = true;
cv.WakeAllPendings();
lock.Leave();
}
bool IsStopped()
{
bool result = false;
CS_LOCK(lock) { result = stopping; }
return result;
}
};
thread_local ApiRegistration::Frame* ApiRegistration::currentFrame = nullptr;
ApiRegistration::Frame::Frame(ApiRegistration* value) : registration(value), previous(currentFrame) { currentFrame = this; }
ApiRegistration::Frame::~Frame() { currentFrame = previous; }
class RegistryEntry : public Object
{
public:
Ptr<IAsyncSocketServer> socketServer;
EventObject readyEvent;
List<Ptr<ApiRegistration>> registrations;
Ptr<HttpRequestServer> server;
WString failureMessage;
AsyncSocketServerStartFailure failure = AsyncSocketServerStartFailure::Other;
bool succeeded = false, terminal = false;
RegistryEntry(Ptr<IAsyncSocketServer> _socketServer)
: socketServer(_socketServer)
{
CHECK_ERROR(readyEvent.CreateManualUnsignal(false), L"Failed to create a socket HTTP registry event.");
}
};
class ConnectionState : public Object
{
public:
CriticalSection lock;
Ptr<RegistryEntry> entry;
IHttpRequestConnection* connection = nullptr;
Ptr<SocketHttpRequestContext> context;
bool automaticWrite = false;
bool closeAfterWrite = false;
bool terminal = false;
ConnectionState(Ptr<RegistryEntry> _entry) : entry(_entry) {}
};
}
class SocketHttpRequestContext::Impl : public Object
{
public:
enum class State { Pending, Sending, Completed, Cancelled };
CriticalSection lock;
Ptr<ConnectionState> connectionState;
Ptr<ApiRegistration> registration;
Ptr<HttpRequest> request;
WString relativePath, query;
SocketHttpRequestContext* owner = nullptr;
Func<void(bool)> completion;
State state = State::Pending;
Impl(Ptr<ConnectionState> cs, Ptr<ApiRegistration> reg, Ptr<HttpRequest> req, const WString& relative, const WString& _query)
: connectionState(cs), registration(reg), request(req), relativePath(relative), query(_query)
{
}
bool Finish(bool succeeded, bool includePending, bool includeSending, bool stopConnection)
{
Func<void(bool)> callback;
bool won = false;
CS_LOCK(lock)
{
if ((includeSending && state == State::Sending) || (includePending && state == State::Pending))
{
if (state == State::Sending)
{
callback = completion;
completion = {};
}
state = succeeded ? State::Completed : State::Cancelled;
won = true;
}
}
if (!won) return false;
auto completionReserved = registration->ReserveCompletion(callback);
IHttpRequestConnection* connection = nullptr;
CS_LOCK(connectionState->lock)
{
if (connectionState->context.Obj() == owner) connectionState->context = nullptr;
if (stopConnection) { connectionState->terminal = true; connection = connectionState->connection; }
}
registration->RemoveContext(owner);
if (connection) try { connection->Stop(); } catch (...) {}
if (completionReserved) registration->InvokeReservedCompletion(callback, succeeded);
return true;
}
};
class SocketHttpServerApiDispatcher : public Object, public virtual IHttpRequestCallback
{
private:
CriticalSection lockSelf;
Ptr<SocketHttpServerApiDispatcher> selfReference;
Ptr<ConnectionState> state;
Ptr<SocketHttpServerApiDispatcher> Retain();
void SendAutomatic(vint code, bool close, bool preflight = false);
void Process(Ptr<HttpRequest> request);
public:
SocketHttpServerApiDispatcher(Ptr<RegistryEntry> entry) : state(Ptr(new ConnectionState(entry))) {}
void InitializeSelf(Ptr<SocketHttpServerApiDispatcher> self) { CS_LOCK(lockSelf) { selfReference = self; } }
void OnReadRequest(Ptr<HttpRequest> request) override;
void OnReadRequestFailure(HttpRequestFailure failure) override;
void OnWriteCompleted() override;
void OnError(const WString& error, bool fatal) override;
void OnDisconnected() override;
void OnInstalled(IHttpRequestConnection* connection) override;
};
namespace
{
void UnexpectedStop(Ptr<RegistryEntry> entry);
class SharedServer : public HttpRequestServer
{
private:
CriticalSection lock;
Ptr<SharedServer> selfReference;
Ptr<RegistryEntry> entry;
protected:
void OnServerStopped() override;
public:
SharedServer(
Ptr<IAsyncSocketServer> server,
Ptr<RegistryEntry> _entry,
const Func<Ptr<IHttpRequestTimeoutController>()>& timeoutControllerFactory
)
: HttpRequestServer(server, timeoutControllerFactory)
, entry(_entry)
{
}
void InitializeSelf(Ptr<SharedServer> self) { CS_LOCK(lock) { selfReference = self; } }
WaitForClientResult OnClientConnected(IHttpRequestConnection* connection) override;
void StopAndRelease();
};
BEGIN_GLOBAL_STORAGE_CLASS(SocketHttpRegistry)
SpinLock lock;
Dictionary<IAsyncSocketServer*, Ptr<RegistryEntry>> entries;
Func<Ptr<IHttpRequestTimeoutController>()>
timeoutControllerFactory;
INITIALIZE_GLOBAL_STORAGE_CLASS
FINALIZE_GLOBAL_STORAGE_CLASS
List<Ptr<HttpRequestServer>> servers;
List<Ptr<RegistryEntry>> retainedEntries;
SPIN_LOCK(lock)
{
for (auto entry : entries.Values())
{
retainedEntries.Add(entry);
if (entry->server) servers.Add(entry->server);
}
entries.Clear();
timeoutControllerFactory = {};
}
for (auto server : servers)
{
auto shared = server.Cast<SharedServer>();
if (shared) shared->StopAndRelease(); else server->Stop();
}
END_GLOBAL_STORAGE_CLASS(SocketHttpRegistry)
}
}
namespace vl::inter_process::async_tcp_socket
{
using namespace collections;
namespace
{
Func<Ptr<IHttpRequestTimeoutController>()> GetTimeoutControllerFactory()
{
Func<Ptr<IHttpRequestTimeoutController>()> factory;
auto& registry = GetSocketHttpRegistry();
SPIN_LOCK(registry.lock) { factory = registry.timeoutControllerFactory; }
return factory;
}
bool CurrentEntry(SocketHttpRegistry& registry, Ptr<RegistryEntry> entry)
{
auto index = registry.entries.Keys().IndexOf(entry->socketServer.Obj());
return index != -1 && registry.entries.Values()[index] == entry;
}
bool Duplicate(Ptr<RegistryEntry> entry, Ptr<ApiRegistration> registration)
{
for (auto existing : entry->registrations)
{
if (existing != registration && existing->prefix.decodedPath == registration->prefix.decodedPath) return true;
}
return false;
}
void RegisterApi(Ptr<IAsyncSocketServer> socketServer, Ptr<ApiRegistration> registration)
{
auto& registry = GetSocketHttpRegistry();
Ptr<RegistryEntry> entry;
bool creator = false;
SPIN_LOCK(registry.lock)
{
auto index = registry.entries.Keys().IndexOf(socketServer.Obj());
if (index != -1) entry = registry.entries.Values()[index];
}
if (!entry)
{
auto candidate = Ptr(new RegistryEntry(socketServer));
SPIN_LOCK(registry.lock)
{
auto index = registry.entries.Keys().IndexOf(socketServer.Obj());
if (index == -1)
{
entry = candidate;
entry->registrations.Add(registration);
registration->entry = entry;
registry.entries.Add(socketServer.Obj(), entry);
creator = true;
}
else entry = registry.entries.Values()[index];
}
}
if (!creator)
{
CHECK_ERROR(entry->readyEvent.Wait(), L"Failed to wait for a shared socket HTTP listener.");
bool joined = false, duplicate = false;
AsyncSocketServerStartFailure failure = AsyncSocketServerStartFailure::Other;
WString message;
SPIN_LOCK(registry.lock)
{
if (entry->succeeded && !entry->terminal && CurrentEntry(registry, entry))
{
duplicate = Duplicate(entry, registration);
if (!duplicate)
{
entry->registrations.Add(registration);
registration->entry = entry;
joined = true;
}
}
else
{
failure = entry->failure;
message = entry->failureMessage;
}
}
CHECK_ERROR(!duplicate, L"A SocketHttpServerApi with the same normalized prefix has already started.");
if (joined) return;
if (message == WString::Empty) message = L"The injected async socket server stopped before the API could join it.";
throw AsyncSocketServerStartException(failure, message);
}
Ptr<SharedServer> server;
AsyncSocketServerStartFailure failure = AsyncSocketServerStartFailure::Other;
WString message;
bool started = false;
try
{
server = Ptr(new SharedServer(socketServer, entry, GetTimeoutControllerFactory()));
server->InitializeSelf(server);
server->Start();
started = true;
}
catch (const AsyncSocketServerStartException& exception) { failure = exception.GetFailure(); message = exception.Message(); }
catch (const Exception& exception) { message = exception.Message(); }
catch (...) { message = L"The socket HTTP listener failed to start."; }
bool published = false;
SPIN_LOCK(registry.lock)
{
if (started && !entry->terminal && CurrentEntry(registry, entry))
{
entry->server = server;
entry->succeeded = true;
published = true;
}
else
{
if (started)
{
failure = AsyncSocketServerStartFailure::Other;
message = L"The listener stopped while starting.";
}
else if (message == WString::Empty)
{
message = L"The socket HTTP listener failed to start.";
}
if (CurrentEntry(registry, entry)) registry.entries.Remove(entry->socketServer.Obj());
entry->registrations.Remove(registration.Obj());
registration->entry = nullptr;
entry->failure = failure;
entry->failureMessage = message;
entry->terminal = true;
}
}
entry->readyEvent.Signal();
if (published) return;
if (server) server->StopAndRelease();
throw AsyncSocketServerStartException(failure, message);
}
Ptr<HttpRequestServer> UnregisterApi(Ptr<ApiRegistration> registration)
{
Ptr<HttpRequestServer> server;
Ptr<RegistryEntry> entry;
auto& registry = GetSocketHttpRegistry();
SPIN_LOCK(registry.lock)
{
entry = registration->entry;
if (entry)
{
entry->registrations.Remove(registration.Obj());
registration->entry = nullptr;
if (entry->registrations.Count() == 0)
{
if (CurrentEntry(registry, entry)) registry.entries.Remove(entry->socketServer.Obj());
entry->terminal = true;
server = entry->server;
entry->server = nullptr;
}
}
}
return server;
}
List<Ptr<ApiRegistration>> Registrations(Ptr<RegistryEntry> entry)
{
List<Ptr<ApiRegistration>> result;
auto& registry = GetSocketHttpRegistry();
SPIN_LOCK(registry.lock)
{
if (!entry->terminal) for (auto item : entry->registrations) result.Add(item);
}
return result;
}
void UnexpectedStop(Ptr<RegistryEntry> entry)
{
List<Ptr<ApiRegistration>> stopping;
auto& registry = GetSocketHttpRegistry();
SPIN_LOCK(registry.lock)
{
if (!entry->terminal)
{
entry->terminal = true;
if (CurrentEntry(registry, entry)) registry.entries.Remove(entry->socketServer.Obj());
for (auto registration : entry->registrations) { registration->entry = nullptr; stopping.Add(registration); }
entry->registrations.Clear();
entry->server = nullptr;
}
}
for (auto registration : stopping) registration->Stop(true);
}
bool Match(const WString& prefix, const WString& path)
{
return prefix.Length() == 0 || path == prefix || (path.Length() > prefix.Length() && path.Left(prefix.Length()) == prefix && path[prefix.Length()] == L'/');
}
bool SingleHeader(Ptr<HttpRequest> request, const WString& name, WString& value, bool& exists)
{
auto count = CountHttpFields(request->headers, name);
if (count > 1) return false;
exists = count == 1;
if (!exists) return true;
return DecodeAsciiHttpFieldValue(FindHttpField(request->headers, name)->value, value);
}
WString Trim(const WString& text)
{
vint begin = 0, end = text.Length();
while (begin < end && (text[begin] == L' ' || text[begin] == L'\t')) begin++;
while (begin < end && (text[end - 1] == L' ' || text[end - 1] == L'\t')) end--;
return text.Sub(begin, end - begin);
}
bool Preflight(Ptr<HttpRequest> request, bool& present, bool& supportedMethod)
{
present = false;
supportedMethod = false;
WString method;
bool exists = false;
if (!SingleHeader(request, L"access-control-request-method", method, exists)) return false;
if (!exists) return true;
present = true;
method = Trim(method);
supportedMethod = method == L"GET" || method == L"HEAD" || method == L"POST" || method == L"OPTIONS";
if (method.Length() == 0) return false;
for (auto&& field : request->headers)
{
if (field.name != L"access-control-request-headers") continue;
WString headers;
if (!DecodeAsciiHttpFieldValue(field.value, headers)) return false;
vint reading = 0;
while (reading <= headers.Length())
{
vint comma = reading;
while (comma < headers.Length() && headers[comma] != L',') comma++;
auto item = Lower(Trim(headers.Sub(reading, comma - reading)));
if (item.Length() == 0 || (item != L"accept" && item != L"content-type")) return false;
if (comma == headers.Length()) break;
reading = comma + 1;
}
}
return true;
}
}
void SetSocketHttpServerTimeoutControllerFactoryForTesting(const Func<Ptr<IHttpRequestTimeoutController>()>& factory)
{
auto& registry = GetSocketHttpRegistry();
SPIN_LOCK(registry.lock)
{
CHECK_ERROR(registry.entries.Count() == 0, L"The test timeout controller factory can only change while no API is started.");
registry.timeoutControllerFactory = factory;
}
}
void ResetSocketHttpServerTimeoutControllerFactoryForTesting() { SetSocketHttpServerTimeoutControllerFactoryForTesting({}); }
WaitForClientResult SharedServer::OnClientConnected(IHttpRequestConnection* connection)
{
Ptr<RegistryEntry> retained;
CS_LOCK(lock) { retained = entry; }
if (!retained) return WaitForClientResult::Reject;
try
{
auto dispatcher = Ptr(new SocketHttpServerApiDispatcher(retained));
dispatcher->InitializeSelf(dispatcher);
connection->InstallCallback(dispatcher.Obj());
return WaitForClientResult::Accept;
}
catch (...) { return WaitForClientResult::Reject; }
}
void SharedServer::StopAndRelease()
{
Ptr<SharedServer> retained;
CS_LOCK(lock) { retained = selfReference; }
try { HttpRequestServer::Stop(); } catch (...) {}
CS_LOCK(lock) { entry = nullptr; selfReference = nullptr; }
}
void SharedServer::OnServerStopped()
{
Ptr<SharedServer> retained;
Ptr<RegistryEntry> retainedEntry;
CS_LOCK(lock) { retained = selfReference; retainedEntry = entry; }
if (retainedEntry) UnexpectedStop(retainedEntry);
try { HttpRequestServer::OnServerStopped(); } catch (...) {}
CS_LOCK(lock) { entry = nullptr; selfReference = nullptr; }
}
}
namespace vl::inter_process::async_tcp_socket
{
using namespace collections;
Ptr<SocketHttpServerApiDispatcher> SocketHttpServerApiDispatcher::Retain()
{
Ptr<SocketHttpServerApiDispatcher> retained;
CS_LOCK(lockSelf) { retained = selfReference; }
return retained;
}
void SocketHttpServerApiDispatcher::SendAutomatic(vint code, bool close, bool preflight)
{
auto response = Normalize(Automatic(code, close, preflight), L"GET");
IHttpRequestConnection* connection = nullptr;
CS_LOCK(state->lock)
{
if (state->terminal || state->automaticWrite || state->context) return;
state->automaticWrite = true;
state->closeAfterWrite = close;
connection = state->connection;
}
if (!connection) return;
try
{
connection->SendResponse(response);
}
catch (...)
{
CS_LOCK(state->lock)
{
state->automaticWrite = false;
state->terminal = true;
}
try { connection->Stop(); } catch (...) {}
}
}
void SocketHttpServerApiDispatcher::Process(Ptr<HttpRequest> request)
{
if (!request || request->version.major != 1 || request->version.minor != 1)
{
SendAutomatic(505, true);
return;
}
WString host;
bool hostExists = false;
if (!SingleHeader(request, L"host", host, hostExists) || !hostExists)
{
SendAutomatic(400, true);
return;
}
vint port = 0;
if (!ParseAuthority(host, port))
{
SendAutomatic(400, true);
return;
}
auto supportedMethod = request->method == L"GET" || request->method == L"HEAD" || request->method == L"POST" || request->method == L"OPTIONS";
if (!supportedMethod)
{
SendAutomatic(501, false);
return;
}
auto registrations = Registrations(state->entry);
auto matchesPort = state->entry->socketServer->GetPort() == port;
if (request->requestTarget == L"*")
{
if (request->method != L"OPTIONS")
{
SendAutomatic(400, true);
return;
}
bool automaticOptions = false;
for (auto registration : registrations)
{
if (matchesPort && registration->respondToOptions)
{
automaticOptions = true;
break;
}
}
if (!automaticOptions)
{
SendAutomatic(501, false);
return;
}
bool present = false, methodSupported = false;
if (!Preflight(request, present, methodSupported))
{
SendAutomatic(400, false, true);
return;
}
SendAutomatic(present && !methodSupported ? 405 : 200, false, true);
return;
}
auto target = request->requestTarget;
if (target.Length() == 0 || target[0] != L'/' || target.IndexOf(L'#') != -1)
{
SendAutomatic(400, true);
return;
}
vint question = target.IndexOf(L'?');
auto rawPath = question == -1 ? target : target.Left(question);
auto query = question == -1 ? WString::Empty : target.Right(target.Length() - question - 1);
WString path;
if (!DecodePath(rawPath, path))
{
SendAutomatic(400, true);
return;
}
Ptr<ApiRegistration> selected;
for (auto registration : registrations)
{
if (matchesPort && Match(registration->prefix.decodedPath, path))
{
if (!selected || registration->prefix.decodedPath.Length() > selected->prefix.decodedPath.Length()) selected = registration;
}
}
if (!selected)
{
SendAutomatic(404, false);
return;
}
if (request->method == L"OPTIONS" && selected->respondToOptions)
{
bool present = false, methodSupported = false;
if (!Preflight(request, present, methodSupported))
{
SendAutomatic(400, false, true);
return;
}
if (present)
{
SendAutomatic(methodSupported ? 200 : 405, false, true);
return;
}
}
WString relative;
if (selected->prefix.decodedPath.Length() == 0) relative = path;
else if (path == selected->prefix.decodedPath) relative = L"/";
else relative = path.Right(path.Length() - selected->prefix.decodedPath.Length());
if (relative.Length() == 0) relative = L"/";
auto contextImpl = Ptr(new SocketHttpRequestContext::Impl(state, selected, request, relative, query));
auto context = Ptr(new SocketHttpRequestContext(contextImpl));
contextImpl->owner = context.Obj();
auto cancelForStop = Func<void()>([contextImpl]()
{
contextImpl->Finish(false, true, true, true);
});
bool installed = false;
CS_LOCK(state->lock)
{
if (!state->terminal && !state->context && !state->automaticWrite)
{
state->context = context;
installed = true;
}
}
if (!installed || !selected->AddContext(context, cancelForStop))
{
CS_LOCK(state->lock) { if (state->context == context) state->context = nullptr; }
SendAutomatic(404, false);
return;
}
try
{
if (!selected->InvokeRequest(context)) context->Cancel();
}
catch (...)
{
context->Respond(Automatic(500, false));
}
}
void SocketHttpServerApiDispatcher::OnReadRequest(Ptr<HttpRequest> request)
{
auto retained = Retain();
if (!retained) return;
try { Process(request); } catch (...) { SendAutomatic(500, true); }
}
void SocketHttpServerApiDispatcher::OnReadRequestFailure(HttpRequestFailure failure)
{
auto retained = Retain();
if (!retained) return;
SendAutomatic((vint)failure, true);
}
void SocketHttpServerApiDispatcher::OnWriteCompleted()
{
auto retained = Retain();
if (!retained) return;
Ptr<SocketHttpRequestContext> context;
bool automatic = false, close = false;
CS_LOCK(state->lock)
{
context = state->context;
if (!context && state->automaticWrite)
{
automatic = true;
close = state->closeAfterWrite;
state->automaticWrite = false;
state->closeAfterWrite = false;
}
}
if (context) context->impl->Finish(true, false, true, false);
if (!context && !automatic) return;
if (close)
{
IHttpRequestConnection* stopping = nullptr;
CS_LOCK(state->lock)
{
state->terminal = true;
stopping = state->connection;
}
if (stopping) try { stopping->Stop(); } catch (...) {}
}
}
void SocketHttpServerApiDispatcher::OnError(const WString&, bool fatal)
{
auto retained = Retain();
if (!retained) return;
Ptr<SocketHttpRequestContext> context;
IHttpRequestConnection* connection = nullptr;
CS_LOCK(state->lock)
{
context = state->context;
if (fatal || context || state->automaticWrite)
{
state->terminal = true;
connection = state->connection;
}
}
if (context) context->impl->Finish(false, true, true, true);
else if (connection) try { connection->Stop(); } catch (...) {}
}
void SocketHttpServerApiDispatcher::OnDisconnected()
{
auto retained = Retain();
if (!retained) return;
Ptr<SocketHttpRequestContext> context;
CS_LOCK(state->lock)
{
state->terminal = true;
state->connection = nullptr;
state->automaticWrite = false;
context = state->context;
}
if (context) context->impl->Finish(false, true, true, false);
CS_LOCK(lockSelf) { selfReference = nullptr; }
}
void SocketHttpServerApiDispatcher::OnInstalled(IHttpRequestConnection* connection)
{
CS_LOCK(state->lock) { state->connection = connection; }
connection->BeginReadingLoopUnsafe();
}
SocketHttpRequestContext::SocketHttpRequestContext(Ptr<Impl> _impl) : impl(_impl) {}
SocketHttpRequestContext::~SocketHttpRequestContext() {}
Ptr<HttpRequest> SocketHttpRequestContext::GetRequest() { return impl->request; }
WString SocketHttpRequestContext::GetRelativePath() { return impl->relativePath; }
WString SocketHttpRequestContext::GetQuery() { return impl->query; }
bool SocketHttpRequestContext::TryGetBodyUtf8(WString& body)
{
Array<vuint8_t> bytes;
if (!FlattenHttpBody(impl->request->body, bytes)) return false;
return ::vl::inter_process::async_tcp_socket::DecodeStrictUtf8(
bytes.Count() == 0 ? nullptr : &bytes[0],
bytes.Count(),
body
);
}
bool SocketHttpRequestContext::Respond(Ptr<HttpResponse> response, Func<void(bool)> completion)
{
Ptr<HttpResponse> normalized;
IHttpRequestConnection* connection = nullptr;
CS_LOCK(impl->lock)
{
if (impl->state != Impl::State::Pending) return false;
normalized = Normalize(response, impl->request->method);
impl->state = Impl::State::Sending;
impl->completion = completion;
}
CS_LOCK(impl->connectionState->lock)
{
if (!impl->connectionState->terminal && impl->connectionState->context.Obj() == this) connection = impl->connectionState->connection;
}
if (!connection)
{
impl->Finish(false, false, true, false);
return true;
}
try { connection->SendResponse(normalized); }
catch (...) { impl->Finish(false, false, true, true); }
return true;
}
bool SocketHttpRequestContext::RespondStatus(vint statusCode, const WString& reason, Func<void(bool)> completion)
{
ValidateConvenienceResponseArguments(statusCode, reason, WString::Empty, 0);
Array<vuint8_t> body;
return Respond(CreateConvenienceResponse(statusCode, reason, WString::Empty, std::move(body)), completion);
}
bool SocketHttpRequestContext::RespondBytes(vint statusCode, const WString& reason, const WString& contentType, const Array<vuint8_t>& body, Func<void(bool)> completion)
{
ValidateConvenienceResponseArguments(statusCode, reason, contentType, body.Count());
Array<vuint8_t> copy(body.Count());
if (body.Count() > 0) memcpy(&copy[0], &body[0], body.Count());
return Respond(CreateConvenienceResponse(statusCode, reason, contentType, std::move(copy)), completion);
}
bool SocketHttpRequestContext::RespondUtf8(vint statusCode, const WString& reason, const WString& contentType, const WString& body, Func<void(bool)> completion)
{
ValidateConvenienceResponseArguments(statusCode, reason, contentType, 0);
Array<vuint8_t> encoded;
CHECK_ERROR(EncodeStrictUtf8(body, encoded), L"The response body contains invalid Unicode.");
CHECK_ERROR(encoded.Count() <= HttpBodySizeLimit, L"The response body is too large.");
return Respond(CreateConvenienceResponse(statusCode, reason, contentType, std::move(encoded)), completion);
}
bool SocketHttpRequestContext::Cancel()
{
return impl->Finish(false, true, false, true);
}
}
namespace vl::inter_process::async_tcp_socket
{
class SocketHttpServerApi::Impl : public Object
{
private:
SocketHttpServerApi* owner;
Ptr<IAsyncSocketServer> socketServer;
Ptr<ApiRegistration> registration;
CriticalSection lock;
ConditionVariable cv;
bool startCalled = false;
bool startInProgress = false;
bool startSucceeded = false;
bool stopRequested = false;
bool stopRequestedNotify = false;
bool stopStarted = false;
bool stopFinished = false;
void Request(Ptr<SocketHttpRequestContext> context)
{
SocketHttpServerApi* target = nullptr;
CS_LOCK(lock) { target = owner; }
if (target) target->OnHttpRequestReceived(context);
else context->Cancel();
}
void Stopping()
{
SocketHttpServerApi* target = nullptr;
CS_LOCK(lock) { target = owner; }
if (target) target->OnHttpServerStopping();
}
void StopInternal(bool notify)
{
bool first = false;
bool notifyRegistration = false;
auto callbackDepth = ApiRegistration::Depth(registration.Obj());
lock.Enter();
if (startInProgress && callbackDepth > 0)
{
stopRequested = true;
stopRequestedNotify |= notify;
lock.Leave();
return;
}
while (startInProgress) cv.SleepWith(lock);
if (!stopStarted)
{
stopStarted = true;
first = true;
notifyRegistration = notify && startSucceeded;
}
else if (callbackDepth > 0)
{
lock.Leave();
return;
}
else
{
while (!stopFinished) cv.SleepWith(lock);
}
lock.Leave();
if (!first) return;
auto server = UnregisterApi(registration);
registration->Stop(notifyRegistration);
if (server)
{
auto shared = server.Cast<SharedServer>();
if (shared) shared->StopAndRelease();
else server->Stop();
}
CS_LOCK(lock)
{
stopFinished = true;
cv.WakeAllPendings();
}
}
public:
Impl(SocketHttpServerApi* _owner, Ptr<IAsyncSocketServer> _socketServer, const WString& urlPrefix, bool respondToOptions)
: owner(_owner)
, socketServer(_socketServer)
{
#define ERROR_PREFIX L"vl::inter_process::async_tcp_socket::SocketHttpServerApi::SocketHttpServerApi(Ptr<IAsyncSocketServer>, const WString&, bool)#"
CHECK_ERROR(socketServer, ERROR_PREFIX L"Requires a server.");
registration = Ptr(new ApiRegistration(ParsePrefix(socketServer->GetPort(), urlPrefix), respondToOptions));
registration->requestCallback = Func<void(Ptr<SocketHttpRequestContext>)>([this](Ptr<SocketHttpRequestContext> context) { Request(context); });
registration->stoppingCallback = Func<void()>([this]() { Stopping(); });
#undef ERROR_PREFIX
}
void Start()
{
#define ERROR_PREFIX L"vl::inter_process::async_tcp_socket::SocketHttpServerApi::Start()#"
CS_LOCK(lock)
{
CHECK_ERROR(!startCalled && !stopStarted, ERROR_PREFIX L"Can only be called once before stopping.");
startCalled = true;
startInProgress = true;
}
try
{
RegisterApi(socketServer, registration);
}
catch (...)
{
bool stopAfterStart = false, notifyAfterStart = false;
CS_LOCK(lock)
{
startInProgress = false;
stopAfterStart = stopRequested;
notifyAfterStart = stopRequestedNotify;
stopRequested = false;
stopRequestedNotify = false;
cv.WakeAllPendings();
}
if (stopAfterStart)
{
try { StopInternal(notifyAfterStart); } catch (...) {}
}
throw;
}
bool stopAfterStart = false, notifyAfterStart = false;
CS_LOCK(lock)
{
startSucceeded = true;
startInProgress = false;
stopAfterStart = stopRequested;
notifyAfterStart = stopRequestedNotify;
stopRequested = false;
stopRequestedNotify = false;
cv.WakeAllPendings();
}
if (stopAfterStart) StopInternal(notifyAfterStart);
#undef ERROR_PREFIX
}
void Stop() { StopInternal(true); }
void Destroy()
{
CS_LOCK(lock) { owner = nullptr; }
StopInternal(false);
}
bool IsStopped()
{
CS_LOCK(lock)
{
if (stopStarted) return true;
}
return registration->IsStopped();
}
WString GetUrlPrefix() { return registration->prefix.normalizedUrl; }
};
SocketHttpServerApi::SocketHttpServerApi(Ptr<IAsyncSocketServer> server, const WString& urlPrefix, bool respondToOptions)
: impl(Ptr(new Impl(this, server, urlPrefix, respondToOptions)))
{
}
SocketHttpServerApi::~SocketHttpServerApi() { impl->Destroy(); }
void SocketHttpServerApi::OnHttpServerStopping() {}
void SocketHttpServerApi::Start() { impl->Start(); }
void SocketHttpServerApi::Stop() { impl->Stop(); }
bool SocketHttpServerApi::IsStopped() { return impl->IsStopped(); }
WString SocketHttpServerApi::GetUrlPrefix() { return impl->GetUrlPrefix(); }
}
/***********************************************************************
.\INTERPROCESS\NETWORKPROTOCOLHTTP.CPP
***********************************************************************/
namespace vl::inter_process
{
using namespace vl::collections;
namespace
{
void EncodeUtf8(const WString& text, Array<char>& utf8)
{
stream::MemoryStream memoryStream;
{
stream::Utf8Encoder encoder;
stream::EncoderStream encoderStream(memoryStream, encoder);
stream::StreamWriter writer(encoderStream);
writer.WriteString(text);
}
utf8.Resize((vint)memoryStream.Size());
if (utf8.Count() > 0)
{
memoryStream.SeekFromBegin(0);
memoryStream.Read(&utf8[0], utf8.Count());
}
}
WString DecodeUtf8(const void* buffer, vint size)
{
if (size == 0)
{
return WString::Empty;
}
stream::MemoryWrapperStream memoryStream((void*)buffer, size);
stream::Utf8Decoder decoder;
stream::DecoderStream decoderStream(memoryStream, decoder);
stream::StreamReader reader(decoderStream);
return reader.ReadToEnd();
}
vint HexValue(char c)
{
if ('0' <= c && c <= '9') return c - '0';
if ('a' <= c && c <= 'f') return c - 'a' + 10;
if ('A' <= c && c <= 'F') return c - 'A' + 10;
return -1;
}
bool IsHttpNetworkProtocolPathCharacter(wchar_t c)
{
if (L'a' <= c && c <= L'z') return true;
if (L'A' <= c && c <= L'Z') return true;
if (L'0' <= c && c <= L'9') return true;
switch (c)
{
case L'-': case L'.': case L'_': case L'~':
case L'!': case L'$': case L'&': case L'\'': case L'(':
case L')': case L'*': case L'+': case L',': case L';': case L'=':
case L':': case L'@': case L'/':
return true;
default:
return false;
}
}
bool ValidateHttpNetworkProtocolPath(const WString& path, bool allowEmpty, bool rejectTrailingSlash)
{
if (path.Length() == 0) return allowEmpty;
if (path[0] != L'/') return false;
if (rejectTrailingSlash && path[path.Length() - 1] == L'/') return false;
List<vuint8_t> decoded;
for (vint i = 0; i < path.Length(); i++)
{
wchar_t c = path[i];
if (c == L'%')
{
if (i + 2 >= path.Length()) return false;
wchar_t highChar = path[i + 1];
wchar_t lowChar = path[i + 2];
if (highChar > 0x7F || lowChar > 0x7F) return false;
vint high = HexValue((char)highChar);
vint low = HexValue((char)lowChar);
if (high == -1 || low == -1) return false;
vuint8_t byte = (vuint8_t)(high * 16 + low);
if (byte == 0 || byte == '/' || byte == '\\') return false;
decoded.Add(byte);
i += 2;
}
else
{
if (c > 0x7F || !IsHttpNetworkProtocolPathCharacter(c)) return false;
decoded.Add((vuint8_t)c);
}
}
WString ignored;
return async_tcp_socket::DecodeStrictUtf8(
decoded.Count() == 0 ? nullptr : &decoded[0],
decoded.Count(),
ignored
);
}
}
WString CreateHttpNetworkProtocolConnectBody(const WString& requestPath, const WString& responsePath)
{
CHECK_ERROR(requestPath.Length() > 0, L"CreateHttpNetworkProtocolConnectBody(const WString&, const WString&): The request path cannot be empty.");
CHECK_ERROR(responsePath.Length() > 0, L"CreateHttpNetworkProtocolConnectBody(const WString&, const WString&): The response path cannot be empty.");
CHECK_ERROR(requestPath.IndexOf(L';') == -1, L"CreateHttpNetworkProtocolConnectBody(const WString&, const WString&): The request path cannot contain a semicolon.");
CHECK_ERROR(responsePath.IndexOf(L';') == -1, L"CreateHttpNetworkProtocolConnectBody(const WString&, const WString&): The response path cannot contain a semicolon.");
return requestPath + L";" + responsePath;
}
bool ParseHttpNetworkProtocolConnectBody(const WString& body, WString& requestPath, WString& responsePath)
{
vint delimiter = body.IndexOf(L';');
if (delimiter <= 0 || delimiter == body.Length() - 1) return false;
if (body.Right(body.Length() - delimiter - 1).IndexOf(L';') != -1) return false;
WString parsedRequestPath = body.Left(delimiter);
WString parsedResponsePath = body.Right(body.Length() - delimiter - 1);
requestPath = parsedRequestPath;
responsePath = parsedResponsePath;
return true;
}
bool ValidateHttpNetworkProtocolBaseUrl(const WString& baseUrl)
{
return ValidateHttpNetworkProtocolPath(baseUrl, true, true);
}
bool ValidateHttpNetworkProtocolEndpointPath(const WString& path)
{
return ValidateHttpNetworkProtocolPath(path, false, false);
}
bool IsValidHttpNetworkProtocolMessage(const WString& message)
{
if (message.Length() == 0) return false;
for (vint i = 0; i < message.Length(); i++)
{
if (message[i] == 0) return false;
}
return true;
}
WString HttpUrlEncodeQuery(const WString& query)
{
Array<char> utf8;
EncodeUtf8(query, utf8);
Array<wchar_t> encoded(utf8.Count() * 3 + 1);
wchar_t* writing = &encoded[0];
for (vint i = 0; i < utf8.Count(); i++)
{
vuint8_t x = (vuint8_t)utf8[i];
if ((L'a' <= x && x <= L'z') || (L'A' <= x && x <= L'Z') || (L'0' <= x && x <= L'9'))
{
*writing++ = (wchar_t)x;
}
else
{
*writing++ = L'%';
*writing++ = L"0123456789ABCDEF"[x / 16];
*writing++ = L"0123456789ABCDEF"[x % 16];
}
}
*writing = 0;
return &encoded[0];
}
WString HttpUrlDecodeQuery(const WString& query)
{
Array<char> encoded;
EncodeUtf8(query, encoded);
List<char> utf8;
for (vint i = 0; i < encoded.Count(); i++)
{
char c = encoded[i];
if (c == '%' && i + 2 < encoded.Count())
{
vint high = HexValue(encoded[i + 1]);
vint low = HexValue(encoded[i + 2]);
if (high != -1 && low != -1)
{
utf8.Add((char)(high * 16 + low));
i += 2;
continue;
}
}
utf8.Add(c == '+' ? ' ' : c);
}
return utf8.Count() == 0
? WString::Empty
: DecodeUtf8(&utf8[0], utf8.Count());
}
}
namespace vl::inter_process::windows_http
{
void HttpRequest::SetBodyUtf8(const WString& bodyString)
{
EncodeUtf8(bodyString, body);
}
bool HttpResponse::TryGetBodyUtf8(WString& bodyString) const
{
return ::vl::inter_process::async_tcp_socket::DecodeStrictUtf8(
body.Count() == 0 ? nullptr : reinterpret_cast<const vuint8_t*>(&body[0]),
body.Count(),
bodyString
);
}
WString HttpResponse::GetBodyUtf8() const
{
return body.Count() == 0
? WString::Empty
: DecodeUtf8(&body[0], body.Count());
}
}
namespace vl::inter_process
{
windows_http::HttpRequest CreateHttpNetworkProtocolConnectRequest(const WString& target)
{
windows_http::HttpRequest request;
request.method = L"GET";
request.query = target;
request.acceptTypes.Add(HttpNetworkProtocolContentType);
return request;
}
windows_http::HttpRequest CreateHttpNetworkProtocolReceiveRequest(const WString& target)
{
windows_http::HttpRequest request;
request.method = L"POST";
request.query = target;
request.acceptTypes.Add(HttpNetworkProtocolContentType);
request.extraHeaders.Add(L"Content-Length", L"0");
return request;
}
windows_http::HttpRequest CreateHttpNetworkProtocolSendRequest(const WString& target, const Array<char>& body)
{
windows_http::HttpRequest request;
request.method = L"POST";
request.query = target;
request.acceptTypes.Add(HttpNetworkProtocolContentType);
request.contentType = HttpNetworkProtocolContentType;
request.body.Resize(body.Count());
for (vint i = 0; i < body.Count(); i++)
{
request.body[i] = body[i];
}
return request;
}
}
/***********************************************************************
.\TUI\TUI.CPP
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#include <exception>
using namespace vl;
using namespace vl::collections;
namespace vl
{
namespace console
{
namespace tui_internal
{
struct ListenerEntry
{
ITuiCallback* listener = nullptr;
vuint64_t generation = 0;
};
}
class TUI::Impl
{
public:
bool active = false;
bool stopRequested = false;
bool shuttingDown = false;
bool backendStarted = false;
bool consoleDisabled = false;
vint ownerThreadId = -1;
vint width = 0;
vint height = 0;
vint timerPeriod = 0;
vuint64_t nextTimer = 0;
TuiColorMode colorMode = TuiColorMode::Auto;
Array<TuiPixel> buffer;
List<unittest::TuiBackendEvent> eventQueue;
Ptr<unittest::ITuiBackend> backend;
std::exception_ptr callbackException;
std::exception_ptr* cleanupException = nullptr;
~Impl()
{
if (backendStarted)
{
try
{
backend->Stop();
}
catch (...)
{
if (cleanupException && !*cleanupException)
{
*cleanupException = std::current_exception();
}
}
}
if (consoleDisabled)
{
Console::Enable();
}
}
};
class TUI::ListenerStorage
{
public:
vuint64_t nextGeneration = 0;
List<tui_internal::ListenerEntry> listeners;
};
TUI::Impl* TUI::impl = nullptr;
TUI::ListenerStorage* TUI::listenerStorage = nullptr;
Ptr<unittest::ITuiBackend>* TUI::injectedBackend = nullptr;
namespace tui_internal
{
constexpr vuint8_t PackState(vuint8_t up, vuint8_t down, vuint8_t left, vuint8_t right)
{
return (vuint8_t)((up << 6) | (down << 4) | (left << 2) | right);
}
char32_t GetMergeableChar(const TuiMergeablePixel& pixel)
{
auto up = (vuint8_t)pixel.up;
auto down = (vuint8_t)pixel.down;
auto left = (vuint8_t)pixel.left;
auto right = (vuint8_t)pixel.right;
if (up > 3 || down > 3 || left > 3 || right > 3) return 0;
switch (PackState(up, down, left, right))
{
case PackState(0, 0, 0, 0): return 0;
case PackState(0, 0, 1, 1): return U'\u2500';
case PackState(0, 0, 2, 2): return U'\u2501';
case PackState(1, 1, 0, 0): return U'\u2502';
case PackState(2, 2, 0, 0): return U'\u2503';
case PackState(0, 1, 0, 1): return U'\u250C';
case PackState(0, 1, 0, 2): return U'\u250D';
case PackState(0, 2, 0, 1): return U'\u250E';
case PackState(0, 2, 0, 2): return U'\u250F';
case PackState(0, 1, 1, 0): return U'\u2510';
case PackState(0, 1, 2, 0): return U'\u2511';
case PackState(0, 2, 1, 0): return U'\u2512';
case PackState(0, 2, 2, 0): return U'\u2513';
case PackState(1, 0, 0, 1): return U'\u2514';
case PackState(1, 0, 0, 2): return U'\u2515';
case PackState(2, 0, 0, 1): return U'\u2516';
case PackState(2, 0, 0, 2): return U'\u2517';
case PackState(1, 0, 1, 0): return U'\u2518';
case PackState(1, 0, 2, 0): return U'\u2519';
case PackState(2, 0, 1, 0): return U'\u251A';
case PackState(2, 0, 2, 0): return U'\u251B';
case PackState(1, 1, 0, 1): return U'\u251C';
case PackState(1, 1, 0, 2): return U'\u251D';
case PackState(2, 1, 0, 1): return U'\u251E';
case PackState(1, 2, 0, 1): return U'\u251F';
case PackState(2, 2, 0, 1): return U'\u2520';
case PackState(2, 1, 0, 2): return U'\u2521';
case PackState(1, 2, 0, 2): return U'\u2522';
case PackState(2, 2, 0, 2): return U'\u2523';
case PackState(1, 1, 1, 0): return U'\u2524';
case PackState(1, 1, 2, 0): return U'\u2525';
case PackState(2, 1, 1, 0): return U'\u2526';
case PackState(1, 2, 1, 0): return U'\u2527';
case PackState(2, 2, 1, 0): return U'\u2528';
case PackState(2, 1, 2, 0): return U'\u2529';
case PackState(1, 2, 2, 0): return U'\u252A';
case PackState(2, 2, 2, 0): return U'\u252B';
case PackState(0, 1, 1, 1): return U'\u252C';
case PackState(0, 1, 2, 1): return U'\u252D';
case PackState(0, 1, 1, 2): return U'\u252E';
case PackState(0, 1, 2, 2): return U'\u252F';
case PackState(0, 2, 1, 1): return U'\u2530';
case PackState(0, 2, 2, 1): return U'\u2531';
case PackState(0, 2, 1, 2): return U'\u2532';
case PackState(0, 2, 2, 2): return U'\u2533';
case PackState(1, 0, 1, 1): return U'\u2534';
case PackState(1, 0, 2, 1): return U'\u2535';
case PackState(1, 0, 1, 2): return U'\u2536';
case PackState(1, 0, 2, 2): return U'\u2537';
case PackState(2, 0, 1, 1): return U'\u2538';
case PackState(2, 0, 2, 1): return U'\u2539';
case PackState(2, 0, 1, 2): return U'\u253A';
case PackState(2, 0, 2, 2): return U'\u253B';
case PackState(1, 1, 1, 1): return U'\u253C';
case PackState(1, 1, 2, 1): return U'\u253D';
case PackState(1, 1, 1, 2): return U'\u253E';
case PackState(1, 1, 2, 2): return U'\u253F';
case PackState(2, 1, 1, 1): return U'\u2540';
case PackState(1, 2, 1, 1): return U'\u2541';
case PackState(2, 2, 1, 1): return U'\u2542';
case PackState(2, 1, 2, 1): return U'\u2543';
case PackState(2, 1, 1, 2): return U'\u2544';
case PackState(1, 2, 2, 1): return U'\u2545';
case PackState(1, 2, 1, 2): return U'\u2546';
case PackState(2, 1, 2, 2): return U'\u2547';
case PackState(1, 2, 2, 2): return U'\u2548';
case PackState(2, 2, 2, 1): return U'\u2549';
case PackState(2, 2, 1, 2): return U'\u254A';
case PackState(2, 2, 2, 2): return U'\u254B';
case PackState(0, 0, 3, 3): return U'\u2550';
case PackState(3, 3, 0, 0): return U'\u2551';
case PackState(0, 1, 0, 3): return U'\u2552';
case PackState(0, 3, 0, 1): return U'\u2553';
case PackState(0, 3, 0, 3): return U'\u2554';
case PackState(0, 1, 3, 0): return U'\u2555';
case PackState(0, 3, 1, 0): return U'\u2556';
case PackState(0, 3, 3, 0): return U'\u2557';
case PackState(1, 0, 0, 3): return U'\u2558';
case PackState(3, 0, 0, 1): return U'\u2559';
case PackState(3, 0, 0, 3): return U'\u255A';
case PackState(1, 0, 3, 0): return U'\u255B';
case PackState(3, 0, 1, 0): return U'\u255C';
case PackState(3, 0, 3, 0): return U'\u255D';
case PackState(1, 1, 0, 3): return U'\u255E';
case PackState(3, 3, 0, 1): return U'\u255F';
case PackState(3, 3, 0, 3): return U'\u2560';
case PackState(1, 1, 3, 0): return U'\u2561';
case PackState(3, 3, 1, 0): return U'\u2562';
case PackState(3, 3, 3, 0): return U'\u2563';
case PackState(0, 1, 3, 3): return U'\u2564';
case PackState(0, 3, 1, 1): return U'\u2565';
case PackState(0, 3, 3, 3): return U'\u2566';
case PackState(1, 0, 3, 3): return U'\u2567';
case PackState(3, 0, 1, 1): return U'\u2568';
case PackState(3, 0, 3, 3): return U'\u2569';
case PackState(1, 1, 3, 3): return U'\u256A';
case PackState(3, 3, 1, 1): return U'\u256B';
case PackState(3, 3, 3, 3): return U'\u256C';
case PackState(0, 0, 1, 0): return U'\u2574';
case PackState(1, 0, 0, 0): return U'\u2575';
case PackState(0, 0, 0, 1): return U'\u2576';
case PackState(0, 1, 0, 0): return U'\u2577';
case PackState(0, 0, 2, 0): return U'\u2578';
case PackState(2, 0, 0, 0): return U'\u2579';
case PackState(0, 0, 0, 2): return U'\u257A';
case PackState(0, 2, 0, 0): return U'\u257B';
case PackState(0, 0, 1, 2): return U'\u257C';
case PackState(1, 2, 0, 0): return U'\u257D';
case PackState(0, 0, 2, 1): return U'\u257E';
case PackState(2, 1, 0, 0): return U'\u257F';
default: return 0;
}
}
bool IsEmptyMergeable(const TuiMergeablePixel& pixel)
{
return
pixel.up == TuiMergeableGlyph::None &&
pixel.down == TuiMergeableGlyph::None &&
pixel.left == TuiMergeableGlyph::None &&
pixel.right == TuiMergeableGlyph::None;
}
bool IsLineGlyph(TuiMergeableGlyph glyph)
{
return
glyph == TuiMergeableGlyph::ThinLine ||
glyph == TuiMergeableGlyph::ThickLine ||
glyph == TuiMergeableGlyph::DoubleLine;
}
bool IsColorMode(TuiColorMode colorMode, bool allowAuto)
{
return
(allowAuto && colorMode == TuiColorMode::Auto) ||
colorMode == TuiColorMode::TrueColor ||
colorMode == TuiColorMode::Color256 ||
colorMode == TuiColorMode::Color16;
}
char32_t GetUnmergeableChar(const TuiUnmergeablePixel& pixel)
{
if (pixel.glyph != TuiUnmergeableGlyph::RoundCorner) return 0;
switch (pixel.direction)
{
case TuiUnmergeableDirection::LeftTop: return U'\u256D';
case TuiUnmergeableDirection::RightTop: return U'\u256E';
case TuiUnmergeableDirection::LeftBottom: return U'\u2570';
case TuiUnmergeableDirection::RightBottom: return U'\u256F';
default: return 0;
}
}
bool IsScalar(char32_t code)
{
return code <= 0x10FFFF && !(code >= 0xD800 && code <= 0xDFFF);
}
TuiPixel EmptyPixel(TuiColor background = { 0, 0, 0 })
{
TuiPixel pixel;
pixel.backgroundColor = background;
return pixel;
}
void CheckBuffer(TuiPixel* buffer, vint width, vint height)
{
CHECK_ERROR(buffer != nullptr, L"vl::console::TUI drawing helper requires a non-null buffer.");
CHECK_ERROR(width >= 0 && height >= 0, L"vl::console::TUI drawing helper requires non-negative dimensions.");
}
void RepairWide(TuiPixel* buffer, vint width, vint height, vint x, vint y)
{
if (x < 0 || x >= width || y < 0 || y >= height) return;
auto index = y * width + x;
auto& pixel = buffer[index];
if (pixel.glyph == TuiPixelGlyph::WideCharContinuation)
{
pixel = EmptyPixel(pixel.backgroundColor);
if (x > 0)
{
auto& leading = buffer[index - 1];
if (leading.glyph == TuiPixelGlyph::Char && TUI::MeasureChar(leading.c) == 2)
{
leading = EmptyPixel(leading.backgroundColor);
}
}
}
else if (pixel.glyph == TuiPixelGlyph::Char && TUI::MeasureChar(pixel.c) == 2)
{
pixel = EmptyPixel(pixel.backgroundColor);
if (x + 1 < width && buffer[index + 1].glyph == TuiPixelGlyph::WideCharContinuation)
{
auto background = buffer[index + 1].backgroundColor;
buffer[index + 1] = EmptyPixel(background);
}
}
}
void PlaceMergeable(TuiPixel* buffer, vint width, vint height, vint x, vint y, const TuiMergeablePixel& drawing, TuiColor foreground, Nullable<TuiColor> background)
{
if (x < 0 || x >= width || y < 0 || y >= height) return;
RepairWide(buffer, width, height, x, y);
auto& pixel = buffer[y * width + x];
auto candidate = drawing;
if (pixel.glyph == TuiPixelGlyph::Mergeable)
{
candidate = pixel.mergeable;
if (drawing.up != TuiMergeableGlyph::None) candidate.up = drawing.up;
if (drawing.down != TuiMergeableGlyph::None) candidate.down = drawing.down;
if (drawing.left != TuiMergeableGlyph::None) candidate.left = drawing.left;
if (drawing.right != TuiMergeableGlyph::None) candidate.right = drawing.right;
if (GetMergeableChar(candidate) == 0)
{
candidate = drawing;
}
}
pixel.glyph = TuiPixelGlyph::Mergeable;
pixel.mergeable = candidate;
pixel.foregroundColor = foreground;
if (background) pixel.backgroundColor = background.Value();
}
void PlaceUnmergeable(TuiPixel* buffer, vint width, vint height, vint x, vint y, TuiUnmergeableDirection direction, TuiColor foreground, Nullable<TuiColor> background)
{
if (x < 0 || x >= width || y < 0 || y >= height) return;
RepairWide(buffer, width, height, x, y);
auto& pixel = buffer[y * width + x];
pixel.glyph = TuiPixelGlyph::Unmergeable;
pixel.unmergeable = { TuiUnmergeableGlyph::RoundCorner, direction };
pixel.foregroundColor = foreground;
if (background) pixel.backgroundColor = background.Value();
}
void CheckOwner(auto& storage)
{
CHECK_ERROR(storage.active, L"vl::console::TUI operation requires an active TUI.");
CHECK_ERROR(storage.ownerThreadId == Thread::GetCurrentThreadId(), L"vl::console::TUI operation must run on the owner thread.");
}
vint FindListener(auto* storage, ITuiCallback* listener, vuint64_t generation = 0)
{
if (!storage) return -1;
for (vint i = 0; i < storage->listeners.Count(); i++)
{
auto entry = storage->listeners[i];
if (entry.listener == listener && (generation == 0 || entry.generation == generation)) return i;
}
return -1;
}
template<typename TCallback>
void InvokeListeners(auto& storage, auto* listenerStorage, TCallback&& callback, bool stopping = false)
{
if (!listenerStorage) return;
List<ListenerEntry> snapshot;
for (auto entry : listenerStorage->listeners) snapshot.Add(entry);
for (auto entry : snapshot)
{
if (!stopping && storage.stopRequested) break;
if (FindListener(listenerStorage, entry.listener, entry.generation) == -1) continue;
try
{
callback(entry.listener);
}
catch (...)
{
if (!storage.callbackException) storage.callbackException = std::current_exception();
storage.stopRequested = true;
throw;
}
if (storage.callbackException) std::rethrow_exception(storage.callbackException);
}
}
void ResizeBuffer(auto& storage, vint width, vint height)
{
CHECK_ERROR(width > 0 && height > 0, L"vl::console::TUI backend returned an invalid terminal size.");
Array<TuiPixel> newBuffer(width * height);
auto copyWidth = width < storage.width ? width : storage.width;
auto copyHeight = height < storage.height ? height : storage.height;
for (vint y = 0; y < copyHeight; y++)
{
for (vint x = 0; x < copyWidth; x++)
{
newBuffer[y * width + x] = storage.buffer[y * storage.width + x];
}
}
for (vint y = 0; y < height; y++)
{
for (vint x = 0; x < width; x++)
{
auto index = y * width + x;
auto& pixel = newBuffer[index];
if (pixel.glyph == TuiPixelGlyph::WideCharContinuation)
{
if (x == 0 || newBuffer[index - 1].glyph != TuiPixelGlyph::Char || TUI::MeasureChar(newBuffer[index - 1].c) != 2)
{
pixel = EmptyPixel(pixel.backgroundColor);
}
}
else if (pixel.glyph == TuiPixelGlyph::Char && TUI::MeasureChar(pixel.c) == 2)
{
if (x + 1 >= width || newBuffer[index + 1].glyph != TuiPixelGlyph::WideCharContinuation)
{
pixel = EmptyPixel(pixel.backgroundColor);
if (x + 1 < width && newBuffer[index + 1].glyph == TuiPixelGlyph::WideCharContinuation)
{
auto background = newBuffer[index + 1].backgroundColor;
newBuffer[index + 1] = EmptyPixel(background);
}
}
}
}
}
storage.buffer = std::move(newBuffer);
storage.width = width;
storage.height = height;
}
void DispatchEvent(auto& storage, auto* listenerStorage, const unittest::TuiBackendEvent& event)
{
if (storage.stopRequested) return;
switch (event.type)
{
case unittest::TuiBackendEventType::Resize:
if (event.width != storage.width || event.height != storage.height)
{
ResizeBuffer(storage, event.width, event.height);
InvokeListeners(storage, listenerStorage, [](ITuiCallback* listener) { listener->BufferSizeChanged(); });
}
break;
case unittest::TuiBackendEventType::MouseMove:
InvokeListeners(storage, listenerStorage, [&](ITuiCallback* listener) { listener->MouseMove(event.mouseInfo); });
break;
case unittest::TuiBackendEventType::MouseDown:
InvokeListeners(storage, listenerStorage, [&](ITuiCallback* listener) { listener->MouseDown(event.mouseButton, event.mouseInfo); });
break;
case unittest::TuiBackendEventType::MouseUp:
InvokeListeners(storage, listenerStorage, [&](ITuiCallback* listener) { listener->MouseUp(event.mouseButton, event.mouseInfo); });
break;
case unittest::TuiBackendEventType::MouseDoubleClick:
InvokeListeners(storage, listenerStorage, [&](ITuiCallback* listener) { listener->MouseDoubleClick(event.mouseButton, event.mouseInfo); });
break;
case unittest::TuiBackendEventType::MouseVerticalWheel:
InvokeListeners(storage, listenerStorage, [&](ITuiCallback* listener) { listener->MouseVerticalWheel(event.mouseInfo); });
break;
case unittest::TuiBackendEventType::MouseHorizontalWheel:
InvokeListeners(storage, listenerStorage, [&](ITuiCallback* listener) { listener->MouseHorizontalWheel(event.mouseInfo); });
break;
case unittest::TuiBackendEventType::KeyDown:
InvokeListeners(storage, listenerStorage, [&](ITuiCallback* listener) { listener->KeyDown(event.keyInfo); });
break;
case unittest::TuiBackendEventType::KeyUp:
InvokeListeners(storage, listenerStorage, [&](ITuiCallback* listener) { listener->KeyUp(event.keyInfo); });
break;
case unittest::TuiBackendEventType::Char:
InvokeListeners(storage, listenerStorage, [&](ITuiCallback* listener) { listener->Char(event.charInfo); });
break;
default:
break;
}
}
const TuiColor canonicalColor16[] =
{
{ 0x00, 0x00, 0x00 }, { 0x80, 0x00, 0x00 }, { 0x00, 0x80, 0x00 }, { 0x80, 0x80, 0x00 },
{ 0x00, 0x00, 0x80 }, { 0x80, 0x00, 0x80 }, { 0x00, 0x80, 0x80 }, { 0xC0, 0xC0, 0xC0 },
{ 0x80, 0x80, 0x80 }, { 0xFF, 0x00, 0x00 }, { 0x00, 0xFF, 0x00 }, { 0xFF, 0xFF, 0x00 },
{ 0x00, 0x00, 0xFF }, { 0xFF, 0x00, 0xFF }, { 0x00, 0xFF, 0xFF }, { 0xFF, 0xFF, 0xFF },
};
TuiColor GetCanonicalColor(vint index)
{
CHECK_ERROR(index >= 0 && index < 256, L"vl::console::tui_internal::GetCanonicalColor(vint)#Index out of range.");
if (index < 16) return canonicalColor16[index];
if (index < 232)
{
auto value = index - 16;
vuint8_t levels[] = { 0, 95, 135, 175, 215, 255 };
return { levels[value / 36], levels[(value / 6) % 6], levels[value % 6] };
}
auto level = (vuint8_t)(8 + 10 * (index - 232));
return { level, level, level };
}
vint QuantizeColor(TuiColor color, TuiColorMode colorMode, const TuiColor* customColor16)
{
auto count = colorMode == TuiColorMode::Color16 ? 16 : 256;
vint best = 0;
vint bestDistance = -1;
for (vint i = 0; i < count; i++)
{
auto candidate = customColor16 && i < 16 ? customColor16[i] : GetCanonicalColor(i);
auto dr = (vint)color.r - candidate.r;
auto dg = (vint)color.g - candidate.g;
auto db = (vint)color.b - candidate.b;
auto distance = dr * dr + dg * dg + db * db;
if (bestDistance == -1 || distance < bestDistance)
{
best = i;
bestDistance = distance;
}
}
return best;
}
}
using namespace tui_internal;
/***********************************************************************
TuiPixel
***********************************************************************/
char32_t TuiPixel::GetChar32() const
{
switch (glyph)
{
case TuiPixelGlyph::Char:
return c;
case TuiPixelGlyph::Mergeable:
return GetMergeableChar(mergeable);
case TuiPixelGlyph::Unmergeable:
return GetUnmergeableChar(unmergeable);
default:
return 0;
}
}
wchar_t TuiPixel::GetWChar() const
{
auto code = GetChar32();
if (!IsScalar(code) || code == 0) return 0;
if constexpr (sizeof(wchar_t) == 2)
{
return code <= 0xFFFF ? (wchar_t)code : 0;
}
else
{
return (wchar_t)code;
}
}
/***********************************************************************
ITuiCallback
***********************************************************************/
void ITuiCallback::Starting() {}
void ITuiCallback::Stopping() {}
void ITuiCallback::BufferSizeChanged() {}
void ITuiCallback::MouseMove(const TuiMouseInfo&) {}
void ITuiCallback::MouseDown(TuiMouseButton, const TuiMouseInfo&) {}
void ITuiCallback::MouseUp(TuiMouseButton, const TuiMouseInfo&) {}
void ITuiCallback::MouseDoubleClick(TuiMouseButton, const TuiMouseInfo&) {}
void ITuiCallback::MouseVerticalWheel(const TuiMouseInfo&) {}
void ITuiCallback::MouseHorizontalWheel(const TuiMouseInfo&) {}
void ITuiCallback::KeyDown(const TuiKeyInfo&) {}
void ITuiCallback::KeyUp(const TuiKeyInfo&) {}
void ITuiCallback::Char(const TuiCharInfo&) {}
void ITuiCallback::Timer() {}
/***********************************************************************
TUI
***********************************************************************/
TUI::Impl& TUI::GetImpl()
{
CHECK_ERROR(impl, L"vl::console::TUI operation requires an active TUI.");
CheckOwner(*impl);
return *impl;
}
bool TUI::TryGetConsoleSize(vint& width, vint& height)
{
if (impl && impl->active)
{
CheckOwner(*impl);
return impl->backend->TryGetConsoleSize(width, height);
}
if (injectedBackend)
{
return (*injectedBackend)->TryGetConsoleSize(width, height);
}
auto backend = CreateTuiBackend();
return backend->TryGetConsoleSize(width, height);
}
void TUI::Start(const TuiStartOptions& options)
{
if (impl)
{
if (impl->active) CheckOwner(*impl);
return;
}
CHECK_ERROR(IsColorMode(options.colorMode, true), L"vl::console::TUI::Start(const TuiStartOptions&)#The requested color mode is invalid.");
CHECK_ERROR(Console::IsEnabled(), L"vl::console::TUI::Start(const TuiStartOptions&)#Console must be enabled before TUI starts.");
auto storage = new Impl;
impl = storage;
std::exception_ptr thrown;
try
{
storage->backend = injectedBackend ? *injectedBackend : CreateTuiBackend();
storage->colorMode = storage->backend->Start(options);
storage->backendStarted = true;
CHECK_ERROR(IsColorMode(storage->colorMode, false), L"vl::console::TUI::Start(const TuiStartOptions&)#The backend selected an invalid color mode.");
vint width = 0;
vint height = 0;
CHECK_ERROR(storage->backend->TryGetConsoleSize(width, height), L"vl::console::TUI::Start(const TuiStartOptions&)#Failed to query the terminal size.");
Console::Disable();
storage->consoleDisabled = true;
ResizeBuffer(*storage, width, height);
storage->ownerThreadId = Thread::GetCurrentThreadId();
storage->active = true;
InvokeListeners(*storage, listenerStorage, [](ITuiCallback* listener) { listener->Starting(); });
if (!storage->stopRequested)
{
InvokeListeners(*storage, listenerStorage, [](ITuiCallback* listener) { listener->BufferSizeChanged(); });
}
while (!storage->stopRequested)
{
if (!RunOneCycle()) break;
}
if (!storage->callbackException)
{
storage->shuttingDown = true;
InvokeListeners(*storage, listenerStorage, [](ITuiCallback* listener) { listener->Stopping(); }, true);
}
}
catch (...)
{
thrown = std::current_exception();
}
storage->cleanupException = &thrown;
impl = nullptr;
delete storage;
if (listenerStorage && listenerStorage->listeners.Count() == 0)
{
delete listenerStorage;
listenerStorage = nullptr;
}
if (thrown) std::rethrow_exception(thrown);
}
bool TUI::RunOneCycle()
{
auto& storage = GetImpl();
if (storage.callbackException) std::rethrow_exception(storage.callbackException);
if (storage.stopRequested) return false;
if (storage.eventQueue.Count() == 0)
{
auto now = storage.backend->GetMonotonicTime();
vint timeout = -1;
if (storage.timerPeriod > 0)
{
if (now >= storage.nextTimer)
{
storage.nextTimer += storage.timerPeriod;
InvokeListeners(storage, listenerStorage, [](ITuiCallback* listener) { listener->Timer(); });
return !storage.stopRequested;
}
auto remaining = storage.nextTimer - now;
timeout = remaining > (vuint64_t)0x7FFFFFFF ? 0x7FFFFFFF : (vint)remaining;
}
unittest::TuiBackendEvent event;
if (storage.backend->ReadEvent(timeout, event))
{
storage.eventQueue.Add(event);
}
else if (storage.timerPeriod > 0 && storage.backend->GetMonotonicTime() >= storage.nextTimer)
{
storage.nextTimer += storage.timerPeriod;
InvokeListeners(storage, listenerStorage, [](ITuiCallback* listener) { listener->Timer(); });
return !storage.stopRequested;
}
}
if (storage.eventQueue.Count() > 0)
{
auto event = storage.eventQueue[0];
storage.eventQueue.RemoveAt(0);
DispatchEvent(storage, listenerStorage, event);
}
if (storage.callbackException) std::rethrow_exception(storage.callbackException);
return !storage.stopRequested;
}
void TUI::Stop()
{
if (!impl || !impl->active) return;
auto& storage = *impl;
CheckOwner(storage);
if (storage.shuttingDown) return;
storage.stopRequested = true;
}
bool TUI::IsInUse()
{
if (!impl || !impl->active) return false;
CheckOwner(*impl);
return true;
}
bool TUI::IsStopRequested()
{
if (!impl || !impl->active) return false;
auto& storage = *impl;
CheckOwner(storage);
return storage.stopRequested;
}
TuiColorMode TUI::GetColorMode()
{
auto& storage = GetImpl();
return storage.colorMode;
}
bool TUI::InstallListener(ITuiCallback* listener)
{
if (impl && impl->active) CheckOwner(*impl);
if (!listener || FindListener(listenerStorage, listener) != -1) return false;
if (!listenerStorage) listenerStorage = new ListenerStorage;
listenerStorage->listeners.Add({ listener, ++listenerStorage->nextGeneration });
return true;
}
bool TUI::UninstallListener(ITuiCallback* listener)
{
if (impl && impl->active) CheckOwner(*impl);
if (!listener) return false;
auto index = FindListener(listenerStorage, listener);
if (index == -1) return false;
listenerStorage->listeners.RemoveAt(index);
if (!impl && listenerStorage->listeners.Count() == 0)
{
delete listenerStorage;
listenerStorage = nullptr;
}
return true;
}
void TUI::StartTimer(vint milliseconds)
{
auto& storage = GetImpl();
CHECK_ERROR(milliseconds > 0, L"vl::console::TUI::StartTimer(vint)#The timer period must be positive.");
storage.timerPeriod = milliseconds;
storage.nextTimer = storage.backend->GetMonotonicTime() + milliseconds;
}
void TUI::StopTimer()
{
auto& storage = GetImpl();
storage.timerPeriod = 0;
storage.nextTimer = 0;
}
TuiPixel* TUI::GetBuffer()
{
auto& storage = GetImpl();
return storage.buffer.Count() == 0 ? nullptr : &storage.buffer[0];
}
vint TUI::GetBufferWidth()
{
auto& storage = GetImpl();
return storage.width;
}
vint TUI::GetBufferHeight()
{
auto& storage = GetImpl();
return storage.height;
}
void TUI::RenderBuffer()
{
auto& storage = GetImpl();
for (vint y = 0; y < storage.height; y++)
{
for (vint x = 0; x < storage.width; x++)
{
auto index = y * storage.width + x;
auto& pixel = storage.buffer[index];
switch (pixel.glyph)
{
case TuiPixelGlyph::Char:
if (pixel.c != 0)
{
CHECK_ERROR(IsScalar(pixel.c), L"vl::console::TUI::RenderBuffer()#A Char cell contains an invalid Unicode scalar.");
auto width = MeasureChar(pixel.c);
CHECK_ERROR(width == 1 || width == 2, L"vl::console::TUI::RenderBuffer()#A Char cell contains a zero-width or non-printable scalar.");
if (width == 2)
{
CHECK_ERROR(x + 1 < storage.width && storage.buffer[index + 1].glyph == TuiPixelGlyph::WideCharContinuation, L"vl::console::TUI::RenderBuffer()#A width-two Char cell has no continuation.");
CHECK_ERROR(storage.buffer[index + 1].foregroundColor == pixel.foregroundColor && storage.buffer[index + 1].backgroundColor == pixel.backgroundColor, L"vl::console::TUI::RenderBuffer()#A width-two continuation has different colors.");
}
}
break;
case TuiPixelGlyph::Mergeable:
CHECK_ERROR(GetMergeableChar(pixel.mergeable) != 0 || IsEmptyMergeable(pixel.mergeable), L"vl::console::TUI::RenderBuffer()#A Mergeable cell contains an unsupported arm state.");
break;
case TuiPixelGlyph::Unmergeable:
CHECK_ERROR(GetUnmergeableChar(pixel.unmergeable) != 0, L"vl::console::TUI::RenderBuffer()#An Unmergeable cell contains an invalid glyph.");
break;
case TuiPixelGlyph::WideCharContinuation:
CHECK_ERROR(x > 0 && storage.buffer[index - 1].glyph == TuiPixelGlyph::Char && MeasureChar(storage.buffer[index - 1].c) == 2, L"vl::console::TUI::RenderBuffer()#A continuation cell has no width-two leading cell.");
break;
default:
CHECK_FAIL(L"vl::console::TUI::RenderBuffer()#A cell contains an invalid glyph type.");
}
}
}
storage.backend->Render(&storage.buffer[0], storage.width, storage.height, storage.colorMode);
}
void TUI::PrintChar(const TuiPrintOptions& options, char32_t code, vint x, vint y)
{
PrintChar(GetBuffer(), GetBufferWidth(), GetBufferHeight(), options, code, x, y);
}
void TUI::DrawLineV(const TuiLineOptions& options, vint x, vint y1, vint y2)
{
DrawLineV(GetBuffer(), GetBufferWidth(), GetBufferHeight(), options, x, y1, y2);
}
void TUI::DrawLineH(const TuiLineOptions& options, vint x1, vint x2, vint y)
{
DrawLineH(GetBuffer(), GetBufferWidth(), GetBufferHeight(), options, x1, x2, y);
}
void TUI::DrawRect(const TuiRectOptions& options, vint x1, vint y1, vint x2, vint y2)
{
DrawRect(GetBuffer(), GetBufferWidth(), GetBufferHeight(), options, x1, y1, x2, y2);
}
void TUI::Clear(TuiColor backgroundColor, vint x1, vint y1, vint x2, vint y2)
{
Clear(GetBuffer(), GetBufferWidth(), GetBufferHeight(), backgroundColor, x1, y1, x2, y2);
}
void TUI::PrintChar(TuiPixel* buffer, vint width, vint height, const TuiPrintOptions& options, char32_t code, vint x, vint y)
{
CheckBuffer(buffer, width, height);
CHECK_ERROR(IsScalar(code), L"vl::console::TUI::PrintChar(...)#The character must be a Unicode scalar.");
auto charWidth = MeasureChar(code);
if (charWidth == 0 || x < 0 || x >= width || y < 0 || y >= height) return;
if (charWidth == 2 && x + 1 >= width) return;
RepairWide(buffer, width, height, x, y);
if (charWidth == 2) RepairWide(buffer, width, height, x + 1, y);
auto& leading = buffer[y * width + x];
leading.glyph = TuiPixelGlyph::Char;
leading.c = code;
leading.foregroundColor = options.foregroundColor;
leading.backgroundColor = options.backgroundColor;
if (charWidth == 2)
{
auto& continuation = buffer[y * width + x + 1];
continuation.glyph = TuiPixelGlyph::WideCharContinuation;
continuation.c = 0;
continuation.foregroundColor = options.foregroundColor;
continuation.backgroundColor = options.backgroundColor;
}
}
void TUI::DrawLineV(TuiPixel* buffer, vint width, vint height, const TuiLineOptions& options, vint x, vint y1, vint y2)
{
CheckBuffer(buffer, width, height);
CHECK_ERROR(y1 <= y2, L"vl::console::TUI::DrawLineV(...)#The ordered range is invalid.");
CHECK_ERROR(IsLineGlyph(options.glyph), L"vl::console::TUI::DrawLineV(...)#The line style is invalid.");
if (x < 0 || x >= width || y2 < 0 || y1 >= height) return;
auto begin = y1 < 0 ? 0 : y1;
auto end = y2 >= height ? height - 1 : y2;
TuiMergeablePixel drawing = { options.glyph, options.glyph, TuiMergeableGlyph::None, TuiMergeableGlyph::None };
for (vint y = begin; y <= end; y++) PlaceMergeable(buffer, width, height, x, y, drawing, options.foregroundColor, options.backgroundColor);
}
void TUI::DrawLineH(TuiPixel* buffer, vint width, vint height, const TuiLineOptions& options, vint x1, vint x2, vint y)
{
CheckBuffer(buffer, width, height);
CHECK_ERROR(x1 <= x2, L"vl::console::TUI::DrawLineH(...)#The ordered range is invalid.");
CHECK_ERROR(IsLineGlyph(options.glyph), L"vl::console::TUI::DrawLineH(...)#The line style is invalid.");
if (y < 0 || y >= height || x2 < 0 || x1 >= width) return;
auto begin = x1 < 0 ? 0 : x1;
auto end = x2 >= width ? width - 1 : x2;
TuiMergeablePixel drawing = { TuiMergeableGlyph::None, TuiMergeableGlyph::None, options.glyph, options.glyph };
for (vint x = begin; x <= end; x++) PlaceMergeable(buffer, width, height, x, y, drawing, options.foregroundColor, options.backgroundColor);
}
void TUI::DrawRect(TuiPixel* buffer, vint width, vint height, const TuiRectOptions& options, vint x1, vint y1, vint x2, vint y2)
{
CheckBuffer(buffer, width, height);
CHECK_ERROR(x1 < x2 && y1 < y2, L"vl::console::TUI::DrawRect(...)#A rectangle must have distinct corners.");
CHECK_ERROR(IsLineGlyph(options.glyph), L"vl::console::TUI::DrawRect(...)#The line style is invalid.");
CHECK_ERROR(options.corner == TuiRectCorner::Sharp || options.corner == TuiRectCorner::Round, L"vl::console::TUI::DrawRect(...)#The corner style is invalid.");
CHECK_ERROR(options.corner == TuiRectCorner::Sharp || options.glyph == TuiMergeableGlyph::ThinLine, L"vl::console::TUI::DrawRect(...)#Rounded corners require a thin line.");
if (x2 < 0 || y2 < 0 || x1 >= width || y1 >= height) return;
TuiMergeablePixel horizontal = { TuiMergeableGlyph::None, TuiMergeableGlyph::None, options.glyph, options.glyph };
TuiMergeablePixel vertical = { options.glyph, options.glyph, TuiMergeableGlyph::None, TuiMergeableGlyph::None };
auto left = x1 + 1 < 0 ? 0 : x1 + 1;
auto right = x2 - 1 >= width ? width - 1 : x2 - 1;
for (vint x = left; x <= right; x++)
{
PlaceMergeable(buffer, width, height, x, y1, horizontal, options.foregroundColor, options.backgroundColor);
PlaceMergeable(buffer, width, height, x, y2, horizontal, options.foregroundColor, options.backgroundColor);
}
auto top = y1 + 1 < 0 ? 0 : y1 + 1;
auto bottom = y2 - 1 >= height ? height - 1 : y2 - 1;
for (vint y = top; y <= bottom; y++)
{
PlaceMergeable(buffer, width, height, x1, y, vertical, options.foregroundColor, options.backgroundColor);
PlaceMergeable(buffer, width, height, x2, y, vertical, options.foregroundColor, options.backgroundColor);
}
if (options.corner == TuiRectCorner::Round)
{
PlaceUnmergeable(buffer, width, height, x1, y1, TuiUnmergeableDirection::LeftTop, options.foregroundColor, options.backgroundColor);
PlaceUnmergeable(buffer, width, height, x2, y1, TuiUnmergeableDirection::RightTop, options.foregroundColor, options.backgroundColor);
PlaceUnmergeable(buffer, width, height, x1, y2, TuiUnmergeableDirection::LeftBottom, options.foregroundColor, options.backgroundColor);
PlaceUnmergeable(buffer, width, height, x2, y2, TuiUnmergeableDirection::RightBottom, options.foregroundColor, options.backgroundColor);
}
else
{
PlaceMergeable(buffer, width, height, x1, y1, { TuiMergeableGlyph::None, options.glyph, TuiMergeableGlyph::None, options.glyph }, options.foregroundColor, options.backgroundColor);
PlaceMergeable(buffer, width, height, x2, y1, { TuiMergeableGlyph::None, options.glyph, options.glyph, TuiMergeableGlyph::None }, options.foregroundColor, options.backgroundColor);
PlaceMergeable(buffer, width, height, x1, y2, { options.glyph, TuiMergeableGlyph::None, TuiMergeableGlyph::None, options.glyph }, options.foregroundColor, options.backgroundColor);
PlaceMergeable(buffer, width, height, x2, y2, { options.glyph, TuiMergeableGlyph::None, options.glyph, TuiMergeableGlyph::None }, options.foregroundColor, options.backgroundColor);
}
}
void TUI::Clear(TuiPixel* buffer, vint width, vint height, TuiColor backgroundColor, vint x1, vint y1, vint x2, vint y2)
{
CheckBuffer(buffer, width, height);
CHECK_ERROR(x1 <= x2 && y1 <= y2, L"vl::console::TUI::Clear(...)#The ordered rectangle is invalid.");
if (x2 < 0 || y2 < 0 || x1 >= width || y1 >= height) return;
auto left = x1 < 0 ? 0 : x1;
auto top = y1 < 0 ? 0 : y1;
auto right = x2 >= width ? width - 1 : x2;
auto bottom = y2 >= height ? height - 1 : y2;
for (vint y = top; y <= bottom; y++)
{
for (vint x = left; x <= right; x++)
{
RepairWide(buffer, width, height, x, y);
buffer[y * width + x] = EmptyPixel(backgroundColor);
}
}
}
/***********************************************************************
ScopedTuiBackend
***********************************************************************/
namespace unittest
{
ScopedTuiBackend::ScopedTuiBackend(Ptr<ITuiBackend> backend)
{
CHECK_ERROR(!TUI::impl, L"vl::console::unittest::ScopedTuiBackend::ScopedTuiBackend(...)#Cannot replace the backend while TUI is active.");
CHECK_ERROR(backend, L"vl::console::unittest::ScopedTuiBackend::ScopedTuiBackend(...)#The backend cannot be null.");
previous = TUI::injectedBackend;
current = backend;
TUI::injectedBackend = &current;
}
ScopedTuiBackend::~ScopedTuiBackend() noexcept(false)
{
CHECK_ERROR(!TUI::impl, L"vl::console::unittest::ScopedTuiBackend::~ScopedTuiBackend()#Cannot restore the backend while TUI is active.");
TUI::injectedBackend = previous;
}
}
}
}