Repository: LiteLDev/LeviLamina (cpp)

Issue:
JsonConfigFile类读取array中大于int32范围的number会得到错误结果
### 异常模块

ScriptEngine(脚本引擎)

### 操作系统

Windows Server 2019

### LiteLoader版本

2.3.0

### BDS版本

1.19.1.01

### 发生了什么?

请看复现步骤

### 复现此问题的步骤

```js
// ./plugins/test.js
const config = new JsonConfigFile('plugins/test/config.json');
config.init('testList', []);

log(config.get('testList'))
```
```jsonc
// ./plugins/test/config.json
{
    "testList": [3076823485]
}
```

### 有关的日志/输出

```shell
xx:xx:xx INFO [test] [-1218143811]
```


### 插件列表

_No response_


Discussion:

Patch:
diff --git a/.clang-format b/.clang-format
--- a/.clang-format
+++ b/.clang-format
@@ -17,7 +17,7 @@ AllowShortLoopsOnASingleLine: true
 AllowShortFunctionsOnASingleLine: false
 AllowShortLambdasOnASingleLine: true
 AlwaysBreakAfterDefinitionReturnType: None
-ColumnLimit: 0
+ColumnLimit: 120
 CommentPragmas: '^ IWYU pragma:'
 PointerAlignment: Left
 IndentWidth: 4
@@ -38,27 +38,27 @@ UseTab: Never
 BreakBeforeBraces: Custom
 BraceWrapping:
   # case 语句后面
-  AfterCaseLabel: true
+  AfterCaseLabel: false
   # class定义后面
-  AfterClass: true
+  AfterClass: false
   # 控制语句后面
-  AfterControlStatement: true
+  AfterControlStatement: false
   # enum定义后面
-  AfterEnum: true
+  AfterEnum: false
   # 函数定义后面
-  AfterFunction: true
+  AfterFunction: false
   # 命名空间定义后面
-  AfterNamespace: true
+  AfterNamespace: false
   # struct定义后面
-  AfterStruct: true
+  AfterStruct: false
   # union定义后面
-  AfterUnion: true
+  AfterUnion: false
   # extern导出块后面
   AfterExternBlock: false
   # catch之前
-  BeforeCatch: true
+  BeforeCatch: false
   # else之前
-  BeforeElse: true
+  BeforeElse: false
   # 缩进大括号(整个大括号框起来的部分都缩进)
   IndentBraces: false
   # 空函数的大括号是否可以在一行
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -12,7 +12,9 @@ jobs:
     runs-on: windows-2022
 
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
+        with:
+          fetch-depth: 0
 
       - name: Download Server
         working-directory: ${{env.GITHUB_WORKSPACE}}
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,20 +1,26 @@
 ## [LiteLoader Release Note]
-LiteLoaderBDS-2.3.0 update has been released, adapted to BDS-1.19.1, ProtocolVersion 527
+LiteLoaderBDS-2.3.1 update has been released, adapted to BDS-1.19.1, ProtocolVersion 527
 
 ## [New features]
-- Support BDS 1.19.1
-- Add EconomyCore switch options, #323
-  - `plugins\LiteLoader\LiteLoader.json`
-- LLMoney Remote Call Support
-- Add LLSE sneak related APIs
+- Add initial support for HttpRequest and HttpResponse
+- Add biome APIs
+- Add Experience related APIs
+- Add sendToast API
 
 ## [Change]
-- Update RemoteCall API
-- Change LLSE
-  - bl.setNbt
+- Improve Dynamic command error output
 
 ## [Bug Fixes]
 - Fix LL bugs:
-  - Actor::isPlayer()
+  - Teleport bug
+  - Sqlite bug,#452
+  - Vec3::toBlockPos error
+  - Xuid of PlayerLeftEvent
+  - Config loading error
+  - LLSE error when config load fails
 - Fix LLSE bugs:
-  - pl.getRespawnPosition
+  - Plugin onPostInit Error reason output
+  - NbtListClass bugs
+  - JsonConfigFile Bugs,#456
+- Fix BDS bugs:
+  - Wine console input
diff --git a/LiteLoader/Header/MC/Biome.hpp b/LiteLoader/Header/MC/Biome.hpp
--- a/LiteLoader/Header/MC/Biome.hpp
+++ b/LiteLoader/Header/MC/Biome.hpp
@@ -5,7 +5,8 @@
 
 #define BEFORE_EXTRA
 // Include Headers or Declare Types Here
-
+enum VanillaBiomeTypes;
+    
 #undef BEFORE_EXTRA
 
 class Biome {
@@ -13,7 +14,12 @@ class Biome {
 #define AFTER_EXTRA
 // Add Member There
 public:
-enum BiomeTempCategory;
+    enum BiomeTempCategory;
+    LIAPI int getId() const;
+    LIAPI std::string const& getName() const;
+    LIAPI static Biome* fromId(int id);
+    LIAPI static Biome* fromName(std::string const& name);
+    LIAPI static std::vector<Biome*> getBiomesByType(VanillaBiomeTypes type);
 
 #undef AFTER_EXTRA
 
diff --git a/LiteLoader/Header/MC/BlockPos.hpp b/LiteLoader/Header/MC/BlockPos.hpp
--- a/LiteLoader/Header/MC/BlockPos.hpp
+++ b/LiteLoader/Header/MC/BlockPos.hpp
@@ -1,8 +1,7 @@
 #pragma once
 #include "../Global.h"
 class Vec3;
-class BlockPos
-{
+class BlockPos {
 public:
     int x, y, z;
     inline BlockPos()
@@ -11,26 +10,31 @@ class BlockPos
         : x(mx)
         , y(my)
         , z(mz){};
+
     MCAPI BlockPos(class ChunkPos const&, class ChunkBlockPos const&, short);
     MCAPI BlockPos(class ChunkPos const&, int);
     MCAPI BlockPos(class SubChunkPos const&);
     MCAPI BlockPos(class Vec3 const&);
     MCAPI BlockPos(float, float, float);
     MCAPI BlockPos(double, double, double);
+
     MCAPI bool cerealize(class BasicSaver&, struct SerializerTraits const&) const;
     MCAPI bool decerealize(class BasicLoader&, struct SerializerTraits const&, class BedrockLoadContext const&);
+
     MCAPI class BlockPos neighbor(unsigned char) const;
-    MCAPI int randomSeed() const;
     MCAPI class BlockPos relative(unsigned char, int) const;
     MCAPI class BlockPos transform(enum Rotation, enum Mirror, class Vec3 const&) const;
+
+    MCAPI int randomSeed() const;
+
     MCAPI static class BlockPos const MAX;
     MCAPI static class BlockPos const MIN;
     MCAPI static class BlockPos const ONE;
     MCAPI static class BlockPos const ZERO;
+
     MCAPI static void bindType();
 
-    std::vector<BlockPos> getNeighbors() const
-    {
+    std::vector<BlockPos> getNeighbors() const {
         std::vector<BlockPos> res;
         res.push_back({x, y - 1, z});
         res.push_back({x, y + 1, z});
@@ -41,173 +45,141 @@ class BlockPos
         return res;
     }
 
-    inline std::string toString() const
-    {
+    inline std::string toString() const {
         return std::to_string(x) + "," + std::to_string(y) + "," + std::to_string(z);
     }
 
-    inline BlockPos add(int dx) const
-    {
+    inline BlockPos add(int dx) const {
         return {x + dx, y, z};
     }
 
-    inline BlockPos add(int dx, int dy) const
-    {
+    inline BlockPos add(int dx, int dy) const {
         return {x + dx, y + dy, z};
     }
 
-    inline BlockPos add(int dx, int dy, int dz) const
-    {
+    inline BlockPos add(int dx, int dy, int dz) const {
         return {x + dx, y + dy, z + dz};
     }
 
 
-    int& operator[](int index)
-    {
-        if (index < 0 || index > 2)
-        {
-            return (&x)[0];
-        }
+    int& operator[](int index) {
+            if (index < 0 || index > 2) { return (&x)[0]; }
         return (&x)[index];
     }
 
-    constexpr inline  bool operator==(BlockPos const& b) const
-    {
+    constexpr inline bool operator==(BlockPos const& b) const {
         return x == b.x && y == b.y && z == b.z;
     }
 
-    constexpr bool operator!=(BlockPos const& b) const
-    {
+    constexpr bool operator!=(BlockPos const& b) const {
         return x != b.x || y != b.y || z != b.z;
     }
 
-    constexpr BlockPos& operator+=(BlockPos const& b)
-    {
+    constexpr BlockPos& operator+=(BlockPos const& b) {
         x += b.x;
         y += b.y;
         z += b.z;
         return *this;
     }
 
-    constexpr BlockPos& operator-=(BlockPos const& b)
-    {
+    constexpr BlockPos& operator-=(BlockPos const& b) {
         x -= b.x;
         y -= b.y;
         z -= b.z;
         return *this;
     }
 
-    constexpr BlockPos& operator*=(BlockPos const& b)
-    {
+    constexpr BlockPos& operator*=(BlockPos const& b) {
         x *= b.x;
         y *= b.y;
         z *= b.z;
         return *this;
     }
 
-    constexpr BlockPos& operator/=(BlockPos const& b)
-    {
+    constexpr BlockPos& operator/=(BlockPos const& b) {
         x /= b.x;
         y /= b.y;
         z /= b.z;
         return *this;
     }
 
-    inline BlockPos operator+(BlockPos const& b) const
-    {
+    inline BlockPos operator+(BlockPos const& b) const {
         return {x + b.x, y + b.y, z + b.z};
     }
 
-    inline BlockPos operator*(BlockPos const& b) const
-    {
+    inline BlockPos operator*(BlockPos const& b) const {
         return {x * b.x, y * b.y, z * b.z};
     }
 
-    inline BlockPos operator/(BlockPos const& b) const
-    {
+    inline BlockPos operator/(BlockPos const& b) const {
         return {x / b.x, y / b.y, z / b.z};
     }
 
-    inline BlockPos operator-(BlockPos const& b) const
-    {
+    inline BlockPos operator-(BlockPos const& b) const {
         return {x - b.x, y - b.y, z - b.z};
     }
 
-    inline BlockPos operator*(int b) const
-    {
+    inline BlockPos operator*(int b) const {
         return {x * b, y * b, z * b};
     }
 
-    inline BlockPos operator/(int b) const
-    {
+    inline BlockPos operator/(int b) const {
         return {x / b, y / b, z / b};
     }
 
-    inline BlockPos operator+(int b) const
-    {
+    inline BlockPos operator+(int b) const {
         return {x + b, y + b, z + b};
     }
 
-    inline BlockPos operator-(int b) const
-    {
+    inline BlockPos operator-(int b) const {
         return {x - b, y - b, z - b};
     }
 
-    constexpr BlockPos& operator+=(int b)
-    {
+    constexpr BlockPos& operator+=(int b) {
         x += b;
         y += b;
         z += b;
         return *this;
     }
 
-    constexpr BlockPos& operator-=(int b)
-    {
+    constexpr BlockPos& operator-=(int b) {
         x -= b;
         y -= b;
         z -= b;
         return *this;
     }
 
-    constexpr BlockPos& operator*=(int b)
-    {
+    constexpr BlockPos& operator*=(int b) {
         x *= b;
         y *= b;
         z *= b;
         return *this;
     }
 
-    constexpr BlockPos& operator/=(int b)
-    {
+    constexpr BlockPos& operator/=(int b) {
         x /= b;
         y /= b;
         z /= b;
         return *this;
     }
 
-    inline bool containedWithin(BlockPos const& a, BlockPos const& b) const
-    {
-        return x >= a.x && y >= a.y && z >= a.z && x <= b.x && y <= b.y &&
-               z <= b.z;
+    inline bool containedWithin(BlockPos const& a, BlockPos const& b) const {
+        return x >= a.x && y >= a.y && z >= a.z && x <= b.x && y <= b.y && z <= b.z;
     }
 
-    inline double length() const
-    {
+    inline double length() const {
         return sqrt(x * x + y * y + z * z);
     }
 
-    inline double distanceTo(BlockPos const& a) const
-    {
+    inline double distanceTo(BlockPos const& a) const {
         return (*this - a).length();
     }
 
-    inline static BlockPos max(const BlockPos& a, const BlockPos& b)
-    {
+    inline static BlockPos max(const BlockPos& a, const BlockPos& b) {
         return {std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z)};
     }
 
-    inline static BlockPos min(const BlockPos& a, const BlockPos& b)
-    {
+    inline static BlockPos min(const BlockPos& a, const BlockPos& b) {
         return {std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z)};
     }
 
@@ -217,21 +189,36 @@ class BlockPos
     LIAPI bool containedWithin(class BoundingBox const&) const;
 };
 
-namespace std
-{
+namespace std {
 
-template <>
-struct hash<BlockPos>
-{
-    std::size_t operator()(BlockPos const& pos) const noexcept
-    {
+template <> struct hash<BlockPos> {
+    std::size_t operator()(BlockPos const& pos) const noexcept {
         //??$hash3@HHH@Math@mce@@SA_KAEBH00@Z
         unsigned __int64 t1; // r8
         unsigned __int64 t2; // r8
 
-        t1 = *((unsigned __int8*)&pos.x + 3) ^ (0x100000001B3i64 * (*((unsigned __int8*)&pos.x + 2) ^ (0x100000001B3i64 * (*((unsigned __int8*)&pos.x + 1) ^ (0x100000001B3i64 * (*(unsigned __int8*)&pos.x ^ 0xCBF29CE484222325ui64))))));
-        t2 = (((0x100000001B3i64 * t1 + 2654435769u) >> 2) + 2654435769u + ((0x100000001B3i64 * t1 + 2654435769u) << 6) + 0x100000001B3i64 * (*((unsigned __int8*)&pos.y + 3) ^ (0x100000001B3i64 * (*((unsigned __int8*)&pos.y + 2) ^ (0x100000001B3i64 * (*((unsigned __int8*)&pos.y + 1) ^ (0x100000001B3i64 * (*(unsigned __int8*)&pos.y ^ 0xCBF29CE484222325ui64)))))))) ^ (0x100000001B3i64 * t1 + 2654435769u);
-        return t2 ^ ((t2 << 6) + 0x100000001B3i64 * (*((unsigned __int8*)&pos.z + 3) ^ (0x100000001B3i64 * (*((unsigned __int8*)&pos.z + 2) ^ (0x100000001B3i64 * (*((unsigned __int8*)&pos.z + 1) ^ (0x100000001B3i64 * (*(unsigned __int8*)&pos.z ^ 0xCBF29CE484222325ui64))))))) + (t2 >> 2) + 2654435769u);
+        t1 = *((unsigned __int8*)&pos.x + 3) ^
+             (0x100000001B3i64 *
+              (*((unsigned __int8*)&pos.x + 2) ^
+               (0x100000001B3i64 * (*((unsigned __int8*)&pos.x + 1) ^
+                                    (0x100000001B3i64 * (*(unsigned __int8*)&pos.x ^ 0xCBF29CE484222325ui64))))));
+        t2 =
+            (((0x100000001B3i64 * t1 + 2654435769u) >> 2) + 2654435769u + ((0x100000001B3i64 * t1 + 2654435769u) << 6) +
+             0x100000001B3i64 * (*((unsigned __int8*)&pos.y + 3) ^
+                                 (0x100000001B3i64 *
+                                  (*((unsigned __int8*)&pos.y + 2) ^
+                                   (0x100000001B3i64 *
+                                    (*((unsigned __int8*)&pos.y + 1) ^
+                                     (0x100000001B3i64 * (*(unsigned __int8*)&pos.y ^ 0xCBF29CE484222325ui64)))))))) ^
+            (0x100000001B3i64 * t1 + 2654435769u);
+        return t2 ^ ((t2 << 6) +
+                     0x100000001B3i64 *
+                         (*((unsigned __int8*)&pos.z + 3) ^
+                          (0x100000001B3i64 * (*((unsigned __int8*)&pos.z + 2) ^
+                                               (0x100000001B3i64 * (*((unsigned __int8*)&pos.z + 1) ^
+                                                                    (0x100000001B3i64 * (*(unsigned __int8*)&pos.z ^
+                                                                                         0xCBF29CE484222325ui64))))))) +
+                     (t2 >> 2) + 2654435769u);
     }
 };
 
diff --git a/LiteLoader/Header/MC/BoundingBox.hpp b/LiteLoader/Header/MC/BoundingBox.hpp
--- a/LiteLoader/Header/MC/BoundingBox.hpp
+++ b/LiteLoader/Header/MC/BoundingBox.hpp
@@ -10,16 +10,14 @@
 #define NOMINMAX
 #endif
 
-class BoundingBox
-{
+class BoundingBox {
 
 public:
     BlockPos min;
     BlockPos max;
 
 
-    inline BlockPos getCenter() const
-    {
+    inline BlockPos getCenter() const {
         return {(min.x + max.x) / 2, (min.y + max.y) / 2, (min.z + max.z) / 2};
     }
 
@@ -36,46 +34,74 @@ class BoundingBox
         : min(BlockPos::MIN)
         , max(BlockPos::MIN){};
 
-    inline BlockPos& operator[](int index)
-    {
-        if (index < 0 || index > 1)
-        {
-            return (&min)[0];
-        }
+    inline BlockPos& operator[](int index) {
+            if (index < 0 || index > 1) { return (&min)[0]; }
         return (&min)[index];
     }
 
-    inline void forEachBlockInBox(const std::function<void(const BlockPos&)>& todo)
-    {
+    constexpr BoundingBox& operator+=(int& b) {
+        min += b;
+        max += b;
+        return *this;
+    }
+
+    constexpr BoundingBox& operator-=(int& b) {
+        min -= b;
+        max -= b;
+        return *this;
+    }
+
+    constexpr BoundingBox& operator+=(BlockPos const& b) {
+        min += b;
+        max += b;
+        return *this;
+    }
+
+    constexpr BoundingBox& operator-=(BlockPos const& b) {
+        min -= b;
+        max -= b;
+        return *this;
+    }
+
+    inline BoundingBox operator+(BlockPos const& b) const {
+        return BoundingBox(min + b, max + b);
+    }
+
+    inline BoundingBox operator+(int& b) const {
+        return BoundingBox(min + b, max + b);
+    }
+
+    inline BoundingBox operator-(BlockPos const& b) const {
+        return BoundingBox(min - b, max - b);
+    }
+
+    inline BoundingBox operator-(int& b) const {
+        return BoundingBox(min - b, max - b);
+    }
+
+    inline void forEachBlockInBox(const std::function<void(const BlockPos&)>& todo) {
         for (int y = min.y; y <= max.y; y++)
             for (int x = min.x; x <= max.x; x++)
-                for (int z = min.z; z <= max.z; z++)
-                {
-                    todo({x, y, z});
-                }
+                    for (int z = min.z; z <= max.z; z++) { todo({x, y, z}); }
     }
-    inline static BoundingBox mergeBoundingBox(BoundingBox const& a,
-                                               BoundingBox const& b)
-    {
+    inline static BoundingBox mergeBoundingBox(BoundingBox const& a, BoundingBox const& b) {
         return BoundingBox(BlockPos::min(a.min, b.min), BlockPos::max(a.max, b.max));
     }
 
 public:
+
     MCAPI BoundingBox(class BlockPos const&, class BlockPos const&, enum Rotation);
+
     MCAPI bool isValid() const;
+
     MCAPI static class BoundingBox orientBox(int, int, int, int, int, int, int, int, int, int);
 };
 
-namespace std
-{
+namespace std {
 
-template <>
-struct hash<BoundingBox>
-{
-    std::size_t operator()(BoundingBox const& box) const noexcept
-    {
-        return (std::hash<BlockPos>()(box.min) ^
-                std::hash<BlockPos>()(box.max));
+template <> struct hash<BoundingBox> {
+    std::size_t operator()(BoundingBox const& box) const noexcept {
+        return (std::hash<BlockPos>()(box.min) ^ std::hash<BlockPos>()(box.max));
     }
 };
 
diff --git a/LiteLoader/Header/MC/ContentIdentity.hpp b/LiteLoader/Header/MC/ContentIdentity.hpp
--- a/LiteLoader/Header/MC/ContentIdentity.hpp
+++ b/LiteLoader/Header/MC/ContentIdentity.hpp
@@ -12,7 +12,9 @@ class ContentIdentity {
 
 #define AFTER_EXTRA
 // Add Member There
-
+public:
+    mce::UUID uuid;
+    bool valid{};
 #undef AFTER_EXTRA
 
 
diff --git a/LiteLoader/Header/MC/Core.hpp b/LiteLoader/Header/MC/Core.hpp
--- a/LiteLoader/Header/MC/Core.hpp
+++ b/LiteLoader/Header/MC/Core.hpp
@@ -48,13 +48,7 @@ class FileOpenMode {
     FileOpenMode(FileOpenMode const&) = delete;
     FileOpenMode(FileOpenMode const&&) = delete;
 };
-template <typename T0>
-class PathBuffer {
-public:
-    PathBuffer() = delete;
-    PathBuffer(PathBuffer const&) = delete;
-    PathBuffer(PathBuffer const&&) = delete;
-};
+
 struct LevelStorageResult {
     LevelStorageResult() = delete;
     LevelStorageResult(LevelStorageResult const&) = delete;
@@ -78,12 +72,40 @@ class SingleThreadedLock {
     SingleThreadedLock(SingleThreadedLock const&) = delete;
     SingleThreadedLock(SingleThreadedLock const&&) = delete;
 };
-class Path {
+class PathPart
+{
+public:
+    std::string data;
+};
+
+class Path : public PathPart
+{
 public:
     Path() = delete;
     Path(Path const&) = delete;
     Path(Path const&&) = delete;
 };
+
+template <typename T>
+class PathBuffer
+{
+public:
+    PathBuffer() = delete;
+    PathBuffer(PathBuffer const&) = delete;
+    PathBuffer(PathBuffer const&&) = delete;
+	
+    T value;
+    operator T&() noexcept
+    {
+        return value;
+    }
+    operator T const&() const noexcept
+    {
+        return value;
+    }
+};
+
+
 class FilePathManager {
 public:
     FilePathManager() = delete;
diff --git a/LiteLoader/Header/MC/IFeature.hpp b/LiteLoader/Header/MC/IFeature.hpp
--- a/LiteLoader/Header/MC/IFeature.hpp
+++ b/LiteLoader/Header/MC/IFeature.hpp
@@ -1,33 +1,17 @@
 // This Header is auto generated by BDSLiteLoader Toolchain
 #pragma once
-#define AUTO_GENERATED
 #include "../Global.h"
 
-#define BEFORE_EXTRA
-// Include Headers or Declare Types Here
-
-#undef BEFORE_EXTRA
-
 class IFeature {
-
-#define AFTER_EXTRA
-// Add Member There
-
-#undef AFTER_EXTRA
-
-#ifndef DISABLE_CONSTRUCTOR_PREVENTION_IFEATURE
 public:
-    class IFeature& operator=(class IFeature const &) = delete;
-    IFeature(class IFeature const &) = delete;
-    IFeature() = delete;
-#endif
-
+    /*0*/ virtual ~IFeature() = 0;
+    /*1*/ virtual std::optional<BlockPos> place(class IBlockWorldGenAPI&, class BlockPos const&, class Random&,
+                                                class RenderParams&) const = 0;
+    /*2*/ virtual void __unk_vfn_2();
+    /*2*/ //virtual void isValidPlacement(std::string const&);
 
 public:
 #ifdef ENABLE_VIRTUAL_FAKESYMBOL_IFEATURE
     MCVAPI bool isValidPlacement(std::string const &);
 #endif
-
-
-
 };
\ No newline at end of file
diff --git a/LiteLoader/Header/MC/ItemStackRequestAction.hpp b/LiteLoader/Header/MC/ItemStackRequestAction.hpp
--- a/LiteLoader/Header/MC/ItemStackRequestAction.hpp
+++ b/LiteLoader/Header/MC/ItemStackRequestAction.hpp
@@ -13,10 +13,13 @@ class ItemStackRequestAction {
 #define AFTER_EXTRA
 // Add Member There
 public:
+    ItemStackRequestActionType mType;
+    
     static inline std::unordered_map<enum ItemStackRequestActionType, std::string> const& getActionTypeMap()
     {
         return actionTypeMap.mMap1;
     }
+    
 #undef AFTER_EXTRA
 
 #ifndef DISABLE_CONSTRUCTOR_PREVENTION_ITEMSTACKREQUESTACTION
diff --git a/LiteLoader/Header/MC/PackIdVersion.hpp b/LiteLoader/Header/MC/PackIdVersion.hpp
--- a/LiteLoader/Header/MC/PackIdVersion.hpp
+++ b/LiteLoader/Header/MC/PackIdVersion.hpp
@@ -5,14 +5,31 @@
 
 #define BEFORE_EXTRA
 // Include Headers or Declare Types Here
-
+#include "SemVersion.hpp"
+enum class PackType : uint8_t
+{
+    Invalid = 0,
+    Addon = 1,
+    Cached = 2,
+    Protected = 3,
+    Behaviour = 4,
+    PersonaPiece=5,
+    Resources=6,
+    Skins=7,
+    WorldTemplate=8,
+    PackTypeCount = 9
+};
 #undef BEFORE_EXTRA
 
 struct PackIdVersion {
-
+public:
+    mce::UUID mUUID;
+    SemVersion mVersion;
+    PackType mType;
 #define AFTER_EXTRA
 // Add Member There
-
+public:
+    
 #undef AFTER_EXTRA
 
 #ifndef DISABLE_CONSTRUCTOR_PREVENTION_PACKIDVERSION
diff --git a/LiteLoader/Header/MC/PackInfoData.hpp b/LiteLoader/Header/MC/PackInfoData.hpp
--- a/LiteLoader/Header/MC/PackInfoData.hpp
+++ b/LiteLoader/Header/MC/PackInfoData.hpp
@@ -4,18 +4,33 @@
 #include "../Global.h"
 
 #define BEFORE_EXTRA
+#include "ContentIdentity.hpp"
+#include "PackIdVersion.hpp"
+
+static_assert(offsetof(PackIdVersion, mUUID) == 0x0);
+static_assert(offsetof(PackIdVersion, mVersion) == 0x10);
+static_assert(offsetof(PackIdVersion, mType) == 0x80);
 
 #undef BEFORE_EXTRA
 
 struct PackInfoData {
 
 #define AFTER_EXTRA
-
+    PackIdVersion mPackIdVersion;//0
+    uint64_t mPackSize;//0x88
+    std::string mContentKey;//0x90
+    std::string mSubpackName;
+    ContentIdentity mContentIdentity;
+    bool mHasScripts;
+    bool mForceServerPacks; 
+    bool isRtxCapable;
+	
 #undef AFTER_EXTRA
 
 #ifndef DISABLE_CONSTRUCTOR_PREVENTION_PACKINFODATA
 public:
-    struct PackInfoData& operator=(struct PackInfoData const &) = delete;
+	
+    struct PackInfoData& operator=(struct PackInfoData const&) = default;
     PackInfoData(struct PackInfoData const &) = delete;
     PackInfoData() = delete;
 #endif
@@ -28,4 +43,4 @@ struct PackInfoData {
 
 private:
 
-};
\ No newline at end of file
+};
diff --git a/LiteLoader/Header/MC/Path.hpp b/LiteLoader/Header/MC/Path.hpp
--- a/LiteLoader/Header/MC/Path.hpp
+++ b/LiteLoader/Header/MC/Path.hpp
@@ -6,6 +6,9 @@
 #define BEFORE_EXTRA
 // Include Headers or Declare Types Here
 
+enum NodeType;
+enum PathCompletionType;
+
 #undef BEFORE_EXTRA
 
 class Path {
@@ -13,12 +16,15 @@ class Path {
 #define AFTER_EXTRA
 // Add Member There
 public:
-class Node {
-public:
-    Node() = delete;
-    Node(Node const&) = delete;
-    Node(Node const&&) = delete;
-};
+    class Node
+    {
+        BlockPos mPos;
+        enum NodeType mType;
+    };
+
+    std::vector<Node> mNodes;
+    size_t mIndex;
+    enum PathCompletionType mCompletionType;
 
 #undef AFTER_EXTRA
 
diff --git a/LiteLoader/Header/MC/Player.hpp b/LiteLoader/Header/MC/Player.hpp
--- a/LiteLoader/Header/MC/Player.hpp
+++ b/LiteLoader/Header/MC/Player.hpp
@@ -50,6 +50,17 @@ class Player : public Mob {
     LIAPI bool isOperator();
     LIAPI bool isOP();
 
+    LIAPI int getCurrentExperience();
+    LIAPI bool setCurrentExperience(int exp);
+    LIAPI size_t getTotalExperience();
+    LIAPI bool setTotalExperience(size_t exp);
+    LIAPI bool reduceExperience(size_t exp);
+
+    // Experience required to upgrade to level from (level-1)
+    LIAPI static int getXpNeededForLevel(int level);
+
+    // Experience required to upgrade to level from level 0
+    LIAPI static size_t getTotalXpNeededForLevel(int level);
 
     LIAPI bool sendText(string text, TextType type = TextType::RAW);
     template <typename... Args>
@@ -111,6 +122,7 @@ class Player : public Mob {
     LIAPI bool sendTextTalkPacket(const string& msg, Player* target/* = nullptr*/);
 
     LIAPI bool sendRawFormPacket(unsigned formId, const string& data) const;
+    LIAPI bool sendToastPacket(string title, string msg);
 
     LIAPI static bool isValid(Player* player);
 
diff --git a/LiteLoader/Header/MC/ResourcePackDataInfoPacket.hpp b/LiteLoader/Header/MC/ResourcePackDataInfoPacket.hpp
--- a/LiteLoader/Header/MC/ResourcePackDataInfoPacket.hpp
+++ b/LiteLoader/Header/MC/ResourcePackDataInfoPacket.hpp
@@ -13,7 +13,14 @@ class ResourcePackDataInfoPacket : public Packet {
 
 #define AFTER_EXTRA
 // Add Member There
-
+public:
+    string mPackId;
+    unsigned int maxChunkSize;
+    int chunkCount;
+    unsigned __int64 compressedPackSize;
+    string sha256;
+    bool isPremium;
+    int packType;
 #undef AFTER_EXTRA
 
 #ifndef DISABLE_CONSTRUCTOR_PREVENTION_RESOURCEPACKDATAINFOPACKET
diff --git a/LiteLoader/Header/MC/ResourcePacksInfoPacket.hpp b/LiteLoader/Header/MC/ResourcePacksInfoPacket.hpp
--- a/LiteLoader/Header/MC/ResourcePacksInfoPacket.hpp
+++ b/LiteLoader/Header/MC/ResourcePacksInfoPacket.hpp
@@ -6,6 +6,8 @@
 
 #define BEFORE_EXTRA
 // Include Headers or Declare Types Here
+#include "PackInfoData.hpp"
+#include "PackIdVersion.hpp"
 
 #undef BEFORE_EXTRA
 
@@ -13,7 +15,12 @@ class ResourcePacksInfoPacket : public Packet {
 
 #define AFTER_EXTRA
 // Add Member There
-
+public:
+    bool mTexturePackRequired;
+    bool mHasScripts;
+    bool mForceServerPacks;
+    std::vector<struct PackInfoData> mAddOnPacks;
+    std::vector<struct PackInfoData> mTexturePacks;
 #undef AFTER_EXTRA
 
 #ifndef DISABLE_CONSTRUCTOR_PREVENTION_RESOURCEPACKSINFOPACKET
diff --git a/LiteLoader/Header/MC/SemVersion.hpp b/LiteLoader/Header/MC/SemVersion.hpp
--- a/LiteLoader/Header/MC/SemVersion.hpp
+++ b/LiteLoader/Header/MC/SemVersion.hpp
@@ -21,7 +21,9 @@ struct any_version_constructor {
     any_version_constructor(any_version_constructor const&) = delete;
     any_version_constructor(any_version_constructor const&&) = delete;
 };
-
+unsigned short major{}, minor{}, patch{};                 // 0, 2, 4
+std::string pre_release, build_meta, full_version_string; // 8, 40, 72
+bool valid_version{}, any_version{};                      // 104, 105
 #undef AFTER_EXTRA
 
 
diff --git a/LiteLoader/Header/MC/ToastRequestPacket.hpp b/LiteLoader/Header/MC/ToastRequestPacket.hpp
--- a/LiteLoader/Header/MC/ToastRequestPacket.hpp
+++ b/LiteLoader/Header/MC/ToastRequestPacket.hpp
@@ -11,7 +11,9 @@
 class ToastRequestPacket : public Packet {
 
 #define AFTER_EXTRA
-
+public:
+    string mTitle;
+    string mMessage;
 #undef AFTER_EXTRA
 
 #ifndef DISABLE_CONSTRUCTOR_PREVENTION_TOASTREQUESTPACKET
diff --git a/LiteLoader/Header/MC/Types.hpp b/LiteLoader/Header/MC/Types.hpp
--- a/LiteLoader/Header/MC/Types.hpp
+++ b/LiteLoader/Header/MC/Types.hpp
@@ -9,29 +9,25 @@
 #include "ChunkPos.hpp"
 #include "ChunkBlockPos.hpp"
 
-typedef std::string xuid_t;
+typedef std::string        xuid_t;
 typedef unsigned long long QWORD;
 
-namespace mce
-{
-class UUID
-{
+namespace mce {
+class UUID {
     uint64_t a, b;
 
 public:
-    MCAPI std::string asString() const;
-    MCAPI static UUID fromString(std::string const&);
-    MCAPI bool isEmpty() const;
-    MCAPI static UUID seedFromString(std::string const&);
+    MCAPI std::string            asString() const;
+    MCAPI static UUID            fromString(std::string const&);
+    MCAPI bool                   isEmpty() const;
+    MCAPI static UUID            seedFromString(std::string const&);
     MCAPI static class mce::UUID EMPTY;
 
-    inline operator bool() const
-    {
+    inline operator bool() const {
         return !isEmpty();
     }
 };
-class Color
-{
+class Color {
 public:
     float r;
     float g;
@@ -42,105 +38,252 @@ class Color
         , g(g)
         , b(b)
         , a(a){};
+    Color(int ir, int ig, int ib, int ia = 255)
+        : r(ir / 255.0f)
+        , g(ig / 255.0f)
+        , b(ib / 255.0f)
+        , a(ia / 255.0f){};
 
-    inline operator bool() const
-    {
+    inline operator bool() const {
         return !(*this == NIL);
     }
 
-    LIAPI double distanceTo(mce::Color const dst) const;
+    LIAPI double distanceTo(mce::Color const& dst) const;
     LIAPI std::string toConsoleCode(bool foreground = true) const;
-    LIAPI std::string toNearestColorCode() const;
+    LIAPI std::string             toNearestColorCode() const;
     LIAPI static class mce::Color fromConsoleCode(std::string const&);
     LIAPI static class mce::Color fromColorCode(std::string const&);
+    LIAPI class mce::Color        sRGBToLinear() const;
+    LIAPI class mce::Color        LinearTosRGB() const;
+    LIAPI class mce::Color        LinearToXYZ() const;
+    LIAPI class mce::Color        XYZToLinear() const;
+    LIAPI class mce::Color        XYZToLab() const;
+    LIAPI class mce::Color        LabToXYZ() const;
+    LIAPI double                  deltaE76(mce::Color const& dst) const; // 2.3 for JND
+    LIAPI double                  deltaE94(mce::Color const& dst) const; // 1.0 for JND
+    LIAPI double                  deltaE00(mce::Color const& dst) const; // 1.0 for JND
 
     MCAPI static mce::Color const NIL;
     MCAPI static class mce::Color fromHexString(std::string const&);
-    MCAPI bool operator==(class mce::Color const&) const;
-    MCAPI int toABGR(void) const;
-    MCAPI int toARGB(void) const;
+    MCAPI bool                    operator==(class mce::Color const&) const;
+    MCAPI int                     toABGR(void) const;
+    MCAPI int                     toARGB(void) const;
     MCAPI std::string toHexString(void) const;
 
+    inline Vec3 toVec3() const {
+        return {r, g, b};
+    }
+
+    inline static Color fromVec3(const Vec3& k) {
+        return {k.x, k.y, k.z, 1.0f};
+    }
+
+    inline BlockPos toBlockPos() const {
+        return {r * 255.0f, g * 255.0f, b * 255.0f};
+    }
+
+    inline static Color fromBlockPos(const BlockPos& k) {
+        return {k.x / 255.0f, k.y / 255.0f, k.z / 255.0f, 1.0f};
+    }
+
+    inline bool operator!=(const Color& c) const {
+        return !(c == *this);
+    }
+
+    inline Color operator*(float c) const {
+        return {r * c, g * c, b * c, a * c};
+    }
+
+    inline Color operator/(float c) const {
+        return {r / c, g / c, b / c, a / c};
+    }
+
+    inline Color operator+(float c) const {
+        return {r + c, g + c, b + c, a + c};
+    }
+
+    inline Color operator-(float c) const {
+        return {r - c, g - c, b - c, a - c};
+    }
+
+    constexpr Color& operator+=(float c) {
+        r += c;
+        g += c;
+        b += c;
+        a += c;
+        return *this;
+    }
+
+    constexpr Color& operator-=(float c) {
+        r -= c;
+        g -= c;
+        b -= c;
+        a -= c;
+        return *this;
+    }
+
+    constexpr Color& operator*=(float c) {
+        r *= c;
+        g *= c;
+        b *= c;
+        a *= c;
+        return *this;
+    }
+
+    constexpr Color& operator/=(float c) {
+        r /= c;
+        g /= c;
+        b /= c;
+        a /= c;
+        return *this;
+    }
+
+    constexpr Color& operator+=(Color const& c) {
+        r += c.r;
+        g += c.g;
+        b += c.b;
+        a += c.a;
+        return *this;
+    }
+
+    constexpr Color& operator-=(Color const& c) {
+        r -= c.r;
+        g -= c.g;
+        b -= c.b;
+        a -= c.a;
+        return *this;
+    }
+
+    constexpr Color& operator*=(Color const& c) {
+        r *= c.r;
+        g *= c.g;
+        b *= c.b;
+        a *= c.a;
+        return *this;
+    }
+
+    constexpr Color& operator/=(Color const& c) {
+        r /= c.r;
+        g /= c.g;
+        b /= c.b;
+        a /= c.a;
+        return *this;
+    }
+
+    inline Color operator+(Color const& c) const {
+        return {r + c.r, g + c.g, b + c.b, a + c.a};
+    }
+
+    inline Color operator*(Color const& c) const {
+        return {r * c.r, g * c.g, b * c.b, a * c.a};
+    }
+
+    inline Color operator/(Color const& c) const {
+        return {r / c.r, g / c.g, b / c.b, a / c.a};
+    }
+
+    inline Color operator-(Color const& c) const {
+        return {r - c.r, g - c.g, b - c.b, a - c.a};
+    }
+
+    inline static Color max(const Color& k, const Color& l) {
+        return {std::max(k.r, l.r), std::max(k.g, l.g), std::max(k.b, l.b), std::max(k.a, l.a)};
+    }
+
+    inline static Color min(const Color& k, const Color& l) {
+        return {std::min(k.r, l.r), std::min(k.g, l.g), std::min(k.b, l.b), std::min(k.a, l.a)};
+    }
+
+    inline static Color lerp(const Color& k, const Color& l, float m) {
+        return k * (1.0f - m) + l * m;
+    }
+
+    inline static Color mix(const Color& k, const Color& l, float m) {
+        return lerp(k, l, m);
+    }
 };
 
 }; // namespace mce
 
+namespace std {
+
+template <>
+struct hash<mce::Color> {
+    std::size_t operator()(mce::Color const& c) const noexcept {
+        return (std::hash<float>()(c.r) ^ std::hash<float>()(c.g) ^
+                std::hash<float>()(c.b) ^ std::hash<float>()(c.a));
+    }
+};
+
+} // namespace std
+
 template <typename A, typename T>
-class AutomaticID
-{
+class AutomaticID {
     T id;
 
 public:
-    AutomaticID()
-    {
+    AutomaticID() {
         id = 0;
     }
 
-    AutomaticID(T x)
-    {
+    AutomaticID(T x) {
         id = x;
     }
 
-    inline operator T() const
-    {
+    inline operator T() const {
         return id;
     }
 };
 
 #include "ActorUniqueID.hpp"
 
-//static_assert(!std::is_pod_v<ActorUniqueID>);
-class NetherNet
-{
+// static_assert(!std::is_pod_v<ActorUniqueID>);
+class NetherNet {
 public:
     struct NetworkID;
 };
 
-class ActorRuntimeID
-{
+class ActorRuntimeID {
 public:
     unsigned long long id;
 
-    inline unsigned long long get() const
-    {
+    inline unsigned long long get() const {
         return id;
     }
 
-    inline operator unsigned long long() const
-    {
+    inline operator unsigned long long() const {
         return id;
     }
 };
-//static_assert(std::is_pod_v<ActorRuntimeID>);
+// static_assert(std::is_pod_v<ActorRuntimeID>);
 
 #include "RelativeFloat.hpp"
 
-//namespace Core {
+// namespace Core {
 //
-//class PathPart {
-//public:
-//    std::string data;
-//};
+// class PathPart {
+// public:
+//     std::string data;
+// };
 //
-//class Path : public PathPart {
-//public:
-//};
+// class Path : public PathPart {
+// public:
+// };
 //
-//template <typename T>
-//class PathBuffer {
-//    T value;
-//    operator T&() noexcept {
-//        return value;
-//    }
-//    operator T const &() const noexcept {
-//        return value;
-//    }
-//};
+// template <typename T>
+// class PathBuffer {
+//     T value;
+//     operator T&() noexcept {
+//         return value;
+//     }
+//     operator T const &() const noexcept {
+//         return value;
+//     }
+// };
 //
-//}; // namespace Core
+// }; // namespace Core
 
-namespace gametest
-{
+namespace gametest {
 class BaseGameTestBatchRunner;
 
 class BaseGameTestFunction;
@@ -164,32 +307,27 @@ class IGameTestHelperProvider;
 struct TestParameters;
 }; // namespace gametest
 
-namespace DBHelpers
-{
+namespace DBHelpers {
 enum Category;
 }; // namespace DBHelpers
 
-class AgentCommands
-{
+class AgentCommands {
 public:
     class Command;
 };
 
 #include "../MC/Automation.hpp"
 
-class ClientBlobCache
-{
+class ClientBlobCache {
 public:
-    struct Server
-    {
+    struct Server {
         class TransferBuilder;
 
         class ActiveTransfersManager;
     };
 };
 
-class ClientBlockPipeline
-{
+class ClientBlockPipeline {
 public:
     template <typename T>
     class VolumeOf;
@@ -197,13 +335,11 @@ class ClientBlockPipeline
     struct BlockSchematic;
 };
 
-struct ISurfaceBuilder
-{
+struct ISurfaceBuilder {
     struct BuildParameters;
 };
 
-struct FeatureLoading
-{
+struct FeatureLoading {
     template <typename T>
     struct ConcreteFeatureHolder;
     struct FeatureRootParseContext;
@@ -214,69 +350,58 @@ struct FeatureLoading
 template <typename T1, typename T2>
 class OperationNode;
 
-struct OperationNodeDetails
-{
+struct OperationNodeDetails {
     template <typename T1, typename T2>
     class WorkingData;
 };
 
-struct PositionTrackingDB
-{
+struct PositionTrackingDB {
     class TrackingRecord;
     class PositionTrackingDBClient;
     class PositionTrackingDBServer;
 };
 
-struct BlockGeometry
-{
+struct BlockGeometry {
     struct Model;
 
     class TessellatedModel;
 };
 
-struct CanyonFeatureUtils
-{
+struct CanyonFeatureUtils {
     struct CanyonConfiguration;
 };
 
-class IInPackagePacks
-{
+class IInPackagePacks {
 public:
     struct MetaData;
 };
 
-struct GameEventConfig
-{
+struct GameEventConfig {
     enum GameEvents;
 };
 
 #include "IMinecraftEventing.hpp"
 
-struct OperationNodeValues
-{
+struct OperationNodeValues {
     enum Terrain;
 };
 
-struct SubChunkStorageUnit
-{
+struct SubChunkStorageUnit {
     enum PruneType;
 };
 
-//Templates
+// Templates
 template <typename T, typename T2, int unk>
-class TypedServerNetId
-{
+class TypedServerNetId {
 public:
-    T2 netId;
-    inline operator T2()
-    {
+    T2     netId;
+    inline operator T2() {
         return netId;
     }
 };
 
 template <typename T, typename T2, int unk>
-class TypedClientNetId
-{
+class TypedClientNetId {
 public:
     T2 netId;
 
@@ -285,8 +410,7 @@ class TypedClientNetId
 };
 
 template <typename T, typename T2, int unk>
-class TypedRuntimeId
-{
+class TypedRuntimeId {
 public:
     T2 netId;
 };
@@ -294,7 +418,7 @@ class TypedRuntimeId
 template <typename T, typename T2>
 class AutomaticID;
 
-template<typename T, typename T2>
+template <typename T, typename T2>
 class BidirectionalUnorderedMap {
 public:
     std::unordered_map<T, T2> mMap1;
@@ -313,9 +437,9 @@ class Factory;
 template <typename T>
 class InheritanceTree;
 
-//enum class ItemStackRequestActionType : char;
-//template <typename T, ItemStackRequestActionType actionType>
-//class ItemStackRequestActionCraft;
+// enum class ItemStackRequestActionType : char;
+// template <typename T, ItemStackRequestActionType actionType>
+// class ItemStackRequestActionCraft;
 
 template <typename T>
 class ItemStateVariant;
@@ -338,32 +462,28 @@ class SharedPtr;
 template <typename T>
 class StackRefResultT;
 
-template<typename T>
+template <typename T>
 class WeakPtr {
     T** value;
+
 public:
-    inline T* get()
-    {
+    inline T* get() {
         if (value)
             return *value;
         return nullptr;
     }
-    inline T const* get() const
-    {
+    inline T const* get() const {
         if (value)
             return *value;
         return nullptr;
     }
-    inline T& operator*()
-    {
+    inline T& operator*() {
         return *get();
     }
-    inline T const& operator*() const
-    {
+    inline T const& operator*() const {
         return *get();
     }
-    inline operator bool() const
-    {
+    inline operator bool() const {
         return get() != nullptr;
     }
 };
@@ -377,15 +497,14 @@ struct SharePtrRefTraits;
 template <typename T>
 class SubChunkStorage;
 
-enum class ScriptFacing
-{
+enum class ScriptFacing {
     Unknown = -1,
-    Down = 0,
-    Up = 1,
-    North = 2,
-    South = 3,
-    West = 4,
-    East = 5,
+    Down    = 0,
+    Up      = 1,
+    North   = 2,
+    South   = 3,
+    West    = 4,
+    East    = 5,
 };
 
 template <typename T, typename T2>
@@ -398,8 +517,7 @@ template <typename T>
 class ToFloatFunction;
 
 template <typename T>
-class TypedScreenCapabilities
-{
+class TypedScreenCapabilities {
 };
 
 template <typename T, typename T2, typename T3, typename T4 = class UNK>
@@ -418,45 +536,39 @@ template <typename T>
 class buffer_span_mut;
 
 template <typename T>
-class optional_ref
-{
+class optional_ref {
     T* value;
 
 public:
-    inline T* get() const
-    {
+    inline T* get() const {
         if (*this)
             return value;
         return nullptr;
     }
-    //inline T* set(T const& val)
+    // inline T* set(T const& val)
     //{
-    //    *value = &val;
-    //}
-    inline T& operator*() const
-    {
+    //     *value = &val;
+    // }
+    inline T& operator*() const {
         return *value;
     }
-    inline T* operator->() const
-    {
+    inline T* operator->() const {
         return value;
     }
-    inline operator bool() const
-    {
+    inline operator bool() const {
         return value != nullptr;
     }
 };
 
-namespace cg
-{
-    class ImageBuffer;
+namespace cg {
+class ImageBuffer;
 };
 
 template <int a>
 class DividedPos2d;
 
-//template <typename T>
-//struct GameplayHandlerResult;
+// template <typename T>
+// struct GameplayHandlerResult;
 
 template <typename T>
 struct GameplayHandlerResult;
@@ -476,7 +588,7 @@ struct TaskStartInfoEx;
 template <typename T1>
 class WildcardCommandSelector;
 
-//enum
+// enum
 
 enum class ContainerType : char {
     INVENTORY              = -1,
@@ -494,7 +606,7 @@ enum class ContainerType : char {
     MINECART_CHEST         = 10,
     MINECART_HOPPER        = 11,
     HORSE                  = 12,
-    TRADE                  = 15,//NotGenerated
+    TRADE                  = 15, // NotGenerated
     MINECART_COMMAND_BLOCK = 16,
     JUKEBOX                = 17,
     COMPOUND_CREATOR       = 20,
@@ -508,332 +620,316 @@ enum class ContainerType : char {
     SMITHING_TABLE         = 33,
 };
 
-enum class UpdateBlockLayer : int
-{
+enum class UpdateBlockLayer : int {
     UpdateBlockDefault,
     UpdateBlockLiquid
 };
 
-enum class UpdateBlockFlags : int
-{
-    UpdateBlockNone = 0,
-    BlockUpdateNeighbors = 1,
-    BlockUpdateNetwork = 2,
-    BlockUpdateAll = 3,//default value in BDS
-    BlockUpdateNoGraphic = 4,
-    BlockUpdatePriority = 8,
+enum class UpdateBlockFlags : int {
+    UpdateBlockNone        = 0,
+    BlockUpdateNeighbors   = 1,
+    BlockUpdateNetwork     = 2,
+    BlockUpdateAll         = 3, // default value in BDS
+    BlockUpdateNoGraphic   = 4,
+    BlockUpdatePriority    = 8,
     BlockUpdateAllPriority = 11
-    //old one not correctly ? 
-    //BlockUpdateNeighbours = 1,
-    //BlockUpdateNetwork = 2,
-    //BlockUpdateNoGraphics = 3,
-    //BlockUpdatePriority = 4,
+    // old one not correctly ?
+    // BlockUpdateNeighbours = 1,
+    // BlockUpdateNetwork = 2,
+    // BlockUpdateNoGraphics = 3,
+    // BlockUpdatePriority = 4,
 };
 
-enum class TextType : char
-{
-    RAW = 0,
-    CHAT = 1,
-    TRANSLATION = 2,
-    POPUP = 3,
+enum class TextType : char {
+    RAW           = 0,
+    CHAT          = 1,
+    TRANSLATION   = 2,
+    POPUP         = 3,
     JUKEBOX_POPUP = 4,
-    TIP = 5,
-    SYSTEM = 6,
-    WHISPER = 7,
-    ANNOUNCEMENT = 8,
-    JSON_WHISPER = 9,
-    JSON = 10
+    TIP           = 5,
+    SYSTEM        = 6,
+    WHISPER       = 7,
+    ANNOUNCEMENT  = 8,
+    JSON_WHISPER  = 9,
+    JSON          = 10
 };
 
-enum class TitleType : int
-{
-    Clear = 0,
-    Reset = 1,
-    SetTitle = 2,
-    SetSubtitle = 3,
-    SetActionBar = 4,
-    SetDurations = 5,
-    TitleTextObject = 6,
-    SubtitleTextObject = 7,
+enum class TitleType : int {
+    Clear               = 0,
+    Reset               = 1,
+    SetTitle            = 2,
+    SetSubtitle         = 3,
+    SetActionBar        = 4,
+    SetDurations        = 5,
+    TitleTextObject     = 6,
+    SubtitleTextObject  = 7,
     ActionbarTextObject = 8
 };
-enum class BossEvent : int
-{
-    Show = 0,
-    RegisterPlayer = 1,
-    Hide = 2,
-    UnregisterPlayer = 3,
-    HealthPercentage = 4,
-    Title = 5,
-    AppearanceProperties = 6,
-    Texture = 7,
+enum class BossEvent : int {
+    Show                    = 0,
+    RegisterPlayer          = 1,
+    Hide                    = 2,
+    UnregisterPlayer        = 3,
+    HealthPercentage        = 4,
+    Title                   = 5,
+    AppearanceProperties    = 6,
+    Texture                 = 7,
     ResendRaidBossEventData = 8,
 };
 
-enum class BossEventColour : int
-{
-    Grey = 0,
-    Blue = 1,
-    Red = 2,
-    Green = 3,
+enum class BossEventColour : int {
+    Grey   = 0,
+    Blue   = 1,
+    Red    = 2,
+    Green  = 3,
     Yellow = 4,
     Purple = 5,
-    White = 6
+    White  = 6
 };
 
-enum class ContainerEnumName : int8_t
-{
-    AnvilInputContainer = 0,
-    AnvilMaterialContainer = 1,
-    AnvilResultPreviewContainer = 2,
-    SmithingTableInputContainer = 3,
-    SmithingTableMaterialContainer = 4,
+enum class ContainerEnumName : int8_t {
+    AnvilInputContainer                 = 0,
+    AnvilMaterialContainer              = 1,
+    AnvilResultPreviewContainer         = 2,
+    SmithingTableInputContainer         = 3,
+    SmithingTableMaterialContainer      = 4,
     SmithingTableResultPreviewContainer = 5,
-    ArmorContainer = 6,
-    LevelEntityContainer = 7,
-    BeaconPaymentContainer = 8,
-    BrewingStandInputContainer = 9,
-    BrewingStandResultContainer = 10,
-    BrewingStandFuelContainer = 11,
+    ArmorContainer                      = 6,
+    LevelEntityContainer                = 7,
+    BeaconPaymentContainer              = 8,
+    BrewingStandInputContainer          = 9,
+    BrewingStandResultContainer         = 10,
+    BrewingStandFuelContainer           = 11,
     CombinedHotbarAndInventoryContainer = 12,
-    CraftingInputContainer = 13,
-    CraftingOutputPreviewContainer = 14,
-    RecipeConstructionContainer = 15,
-    RecipeNatureContainer = 16,
-    RecipeItemsContainer = 17,
-    RecipeSearchContainer = 18,
-    RecipeSearchBarContainer = 19,
-    RecipeEquipmentContainer = 20,
-    EnchantingInputContainer = 21,
-    EnchantingMaterialContainer = 22,
-    FurnaceFuelContainer = 23,
-    FurnaceIngredientContainer = 24,
-    FurnaceResultContainer = 25,
-    HorseEquipContainer = 26,
-    HotbarContainer = 27,
-    InventoryContainer = 28,
-    ShulkerBoxContainer = 29,
-    TradeIngredient1Container = 30,
-    TradeIngredient2Container = 31,
-    TradeResultPreviewContainer = 32,
-    OffhandContainer = 33,
-    CompoundCreatorInput = 34,
-    CompoundCreatorOutputPreview = 35,
-    ElementConstructorOutputPreview = 36,
-    MaterialReducerInput = 37,
-    MaterialReducerOutput = 38,
-    LabTableInput = 39,
-    LoomInputContainer = 40,
-    LoomDyeContainer = 41,
-    LoomMaterialContainer = 42,
-    LoomResultPreviewContainer = 43,
-    BlastFurnaceIngredientContainer = 44,
-    SmokerIngredientContainer = 45,
-    Trade2Ingredient1Container = 46,
-    Trade2Ingredient2Container = 47,
-    Trade2ResultPreviewContainer = 48,
-    GrindstoneInputContainer = 49,
-    GrindstoneAdditionalContainer = 50,
-    GrindstoneResultPreviewContainer = 51,
-    StonecutterInputContainer = 52,
-    StonecutterResultPreviewContainer = 53,
-    CartographyInputContainer = 54,
-    CartographyAdditionalContainer = 55,
-    CartographyResultPreviewContainer = 56,
-    BarrelContainer = 57,
-    CursorContainer = 58,
-    CreatedOutputContainer = 59
+    CraftingInputContainer              = 13,
+    CraftingOutputPreviewContainer      = 14,
+    RecipeConstructionContainer         = 15,
+    RecipeNatureContainer               = 16,
+    RecipeItemsContainer                = 17,
+    RecipeSearchContainer               = 18,
+    RecipeSearchBarContainer            = 19,
+    RecipeEquipmentContainer            = 20,
+    EnchantingInputContainer            = 21,
+    EnchantingMaterialContainer         = 22,
+    FurnaceFuelContainer                = 23,
+    FurnaceIngredientContainer          = 24,
+    FurnaceResultContainer              = 25,
+    HorseEquipContainer                 = 26,
+    HotbarContainer                     = 27,
+    InventoryContainer                  = 28,
+    ShulkerBoxContainer                 = 29,
+    TradeIngredient1Container           = 30,
+    TradeIngredient2Container           = 31,
+    TradeResultPreviewContainer         = 32,
+    OffhandContainer                    = 33,
+    CompoundCreatorInput                = 34,
+    CompoundCreatorOutputPreview        = 35,
+    ElementConstructorOutputPreview     = 36,
+    MaterialReducerInput                = 37,
+    MaterialReducerOutput               = 38,
+    LabTableInput                       = 39,
+    LoomInputContainer                  = 40,
+    LoomDyeContainer                    = 41,
+    LoomMaterialContainer               = 42,
+    LoomResultPreviewContainer          = 43,
+    BlastFurnaceIngredientContainer     = 44,
+    SmokerIngredientContainer           = 45,
+    Trade2Ingredient1Container          = 46,
+    Trade2Ingredient2Container          = 47,
+    Trade2ResultPreviewContainer        = 48,
+    GrindstoneInputContainer            = 49,
+    GrindstoneAdditionalContainer       = 50,
+    GrindstoneResultPreviewContainer    = 51,
+    StonecutterInputContainer           = 52,
+    StonecutterResultPreviewContainer   = 53,
+    CartographyInputContainer           = 54,
+    CartographyAdditionalContainer      = 55,
+    CartographyResultPreviewContainer   = 56,
+    BarrelContainer                     = 57,
+    CursorContainer                     = 58,
+    CreatedOutputContainer              = 59
 };
 
-enum class ParticleType
-{
-    dragondestroyblock = 0x40,
-    none = 0x00,
-    bubble = 0x01,
-    bubblemanual = 0x02,
-    crit = 0x03,
-    blockforcefield = 0x04,
-    smoke = 0x05,
-    explode = 0x06,
-    evaporation = 0x07,
-    flame = 0x08,
-    candleflame = 0x09,
-    lava = 0x0a,
-    largesmoke = 0x0b,
-    reddust = 0x0c,
-    risingborderdust = 0x0d,
-    iconcrack = 0x0e,
-    snowballpoof = 0x0f,
-    largeexplode = 0x10,
-    hugeexplosion = 0x11,
-    mobflame = 0x12,
-    heart = 0x13,
-    terrain = 0x14,
-    townaura = 0x15,
-    portal = 0x16,
-    watersplash = 0x18,
-    watersplashmanual = 0x19,
-    waterwake = 0x1a,
-    dripwater = 0x1b,
-    driplava = 0x1c,
-    driphoney = 0x1d,
-    stalactitedripwater = 0x1e,
-    stalactitedriplava = 0x1f,
-    fallingdust = 0x20,
-    mobspell = 0x21,
-    mobspellambient = 0x22,
+enum class ParticleType {
+    dragondestroyblock    = 0x40,
+    none                  = 0x00,
+    bubble                = 0x01,
+    bubblemanual          = 0x02,
+    crit                  = 0x03,
+    blockforcefield       = 0x04,
+    smoke                 = 0x05,
+    explode               = 0x06,
+    evaporation           = 0x07,
+    flame                 = 0x08,
+    candleflame           = 0x09,
+    lava                  = 0x0a,
+    largesmoke            = 0x0b,
+    reddust               = 0x0c,
+    risingborderdust      = 0x0d,
+    iconcrack             = 0x0e,
+    snowballpoof          = 0x0f,
+    largeexplode          = 0x10,
+    hugeexplosion         = 0x11,
+    mobflame              = 0x12,
+    heart                 = 0x13,
+    terrain               = 0x14,
+    townaura              = 0x15,
+    portal                = 0x16,
+    watersplash           = 0x18,
+    watersplashmanual     = 0x19,
+    waterwake             = 0x1a,
+    dripwater             = 0x1b,
+    driplava              = 0x1c,
+    driphoney             = 0x1d,
+    stalactitedripwater   = 0x1e,
+    stalactitedriplava    = 0x1f,
+    fallingdust           = 0x20,
+    mobspell              = 0x21,
+    mobspellambient       = 0x22,
     mobspellinstantaneous = 0x23,
-    ink = 0x24,
-    slime = 0x25,
-    rainsplash = 0x26,
-    villagerangry = 0x27,
-    villagerhappy = 0x28,
-    enchantingtable = 0x29,
-    trackingemitter = 0x2a,
-    note = 0x2b,
-    witchspell = 0x2c,
-    carrotboost = 0x2d,
-    mobappearance = 0x2e,
-    endrod = 0x2f,
-    dragonbreath = 0x30,
-    spit = 0x31,
-    totem = 0x32,
-    food = 0x33,
-    fireworksstarter = 0x34,
-    fireworks = 0x35,
-    fireworksoverlay = 0x36,
-    balloongas = 0x37,
-    coloredflame = 0x38,
-    sparkler = 0x39,
-    conduit = 0x3a,
-    bubblecolumnup = 0x3b,
-    bubblecolumndown = 0x3c,
-    sneeze = 0x3d,
-    shulkerbullet = 0x3e,
-    bleach = 0x3f,
-    myceliumdust = 0x41,
-    fallingborderdust = 0x42,
-    campfiresmoke = 0x43,
-    campfiresmoketall = 0x44,
-    dragonbreathfire = 0x45,
-    dragonbreathtrail = 0x46,
-    soul = 0x48,
-    obsidiantear = 0x49,
-    portalreverse = 0x4a,
-    snowflake = 0x4b,
-    wax = 0x50,
-    electricspark = 0x51,
-    shriek = 0x52,
-    sculksoul = 0x53
+    ink                   = 0x24,
+    slime                 = 0x25,
+    rainsplash            = 0x26,
+    villagerangry         = 0x27,
+    villagerhappy         = 0x28,
+    enchantingtable       = 0x29,
+    trackingemitter       = 0x2a,
+    note                  = 0x2b,
+    witchspell            = 0x2c,
+    carrotboost           = 0x2d,
+    mobappearance         = 0x2e,
+    endrod                = 0x2f,
+    dragonbreath          = 0x30,
+    spit                  = 0x31,
+    totem                 = 0x32,
+    food                  = 0x33,
+    fireworksstarter      = 0x34,
+    fireworks             = 0x35,
+    fireworksoverlay      = 0x36,
+    balloongas            = 0x37,
+    coloredflame          = 0x38,
+    sparkler              = 0x39,
+    conduit               = 0x3a,
+    bubblecolumnup        = 0x3b,
+    bubblecolumndown      = 0x3c,
+    sneeze                = 0x3d,
+    shulkerbullet         = 0x3e,
+    bleach                = 0x3f,
+    myceliumdust          = 0x41,
+    fallingborderdust     = 0x42,
+    campfiresmoke         = 0x43,
+    campfiresmoketall     = 0x44,
+    dragonbreathfire      = 0x45,
+    dragonbreathtrail     = 0x46,
+    soul                  = 0x48,
+    obsidiantear          = 0x49,
+    portalreverse         = 0x4a,
+    snowflake             = 0x4b,
+    wax                   = 0x50,
+    electricspark         = 0x51,
+    shriek                = 0x52,
+    sculksoul             = 0x53
 };
 
-enum class transactionType
-{
-    NormalTransaction = 0x00,
-    InventoryMismatch = 0x01,
-    ItemUseTransaction = 0x02,
+enum class transactionType {
+    NormalTransaction          = 0x00,
+    InventoryMismatch          = 0x01,
+    ItemUseTransaction         = 0x02,
     ItemUseOnEntityTransaction = 0x03,
-    ItemReleaseTransaction = 0x04
+    ItemReleaseTransaction     = 0x04
 };
 
-enum class ItemStackRequestActionType: char
-{
-    Take = 0x00,
-    Place = 0x01,
-    Swap = 0x02,
-    Drop = 0x03,
-    Destroy = 0x04,
-    Consume = 0x05,
-    Create = 0x06,
-    PlaceInItemContainer = 0x07,
-    TakeFromItemContainer = 0x08,
-    ScreenLabTableCombine = 0x09,
-    ScreenBeaconPayment = 0x0a,
-    ScreenHUDMineBlock = 0x0b,
-    CraftRecipe = 0x0c,
-    CraftRecipeAuto = 0x0d,
-    CraftCreative = 0x0e,
-    CraftRecipeOptional = 0x0f,
-    CraftRepairAndDisenchant = 0x10,
-    CraftLoom = 0x11,
+enum class ItemStackRequestActionType : char {
+    Take                                     = 0x00,
+    Place                                    = 0x01,
+    Swap                                     = 0x02,
+    Drop                                     = 0x03,
+    Destroy                                  = 0x04,
+    Consume                                  = 0x05,
+    Create                                   = 0x06,
+    PlaceInItemContainer                     = 0x07,
+    TakeFromItemContainer                    = 0x08,
+    ScreenLabTableCombine                    = 0x09,
+    ScreenBeaconPayment                      = 0x0a,
+    ScreenHUDMineBlock                       = 0x0b,
+    CraftRecipe                              = 0x0c,
+    CraftRecipeAuto                          = 0x0d,
+    CraftCreative                            = 0x0e,
+    CraftRecipeOptional                      = 0x0f,
+    CraftRepairAndDisenchant                 = 0x10,
+    CraftLoom                                = 0x11,
     CraftNonImplemented_DEPRECATEDASKTYLAING = 0x12,
-    CraftResults_DEPRECATEDASKTYLAING = 0x13,
+    CraftResults_DEPRECATEDASKTYLAING        = 0x13,
 };
 
-enum class ActorDamageCause : int
-{
-    None = -0x01,
-    Override = 0x00,
-    Contact = 0x01,
-    EntityAttack = 0x02,
-    Projectile = 0x03,
-    Suffocation = 0x04,
-    Fall = 0x05,
-    Fire = 0x06,
-    FireTick = 0x07,
-    Lava = 0x08,
-    Drowning = 0x09,
-    BlockExplosion = 0x0A,
+enum class ActorDamageCause : int {
+    None            = -0x01,
+    Override        = 0x00,
+    Contact         = 0x01,
+    EntityAttack    = 0x02,
+    Projectile      = 0x03,
+    Suffocation     = 0x04,
+    Fall            = 0x05,
+    Fire            = 0x06,
+    FireTick        = 0x07,
+    Lava            = 0x08,
+    Drowning        = 0x09,
+    BlockExplosion  = 0x0A,
     EntityExplosion = 0x0B,
-    Void = 0x0C,
-    Suicide = 0x0D,
-    Magic = 0x0E,
-    Wither = 0x0F,
-    Starve = 0x10,
-    Anvil = 0x11,
-    Thorns = 0x12,
-    FallingBlock = 0x13,
-    Piston = 0x14,
-    FlyIntoWall = 0x15,
-    Magma = 0x16,
-    Fireworks = 0x17,
-    Lightning = 0x18,
-    Charging = 0x19,
-    Temperature = 0x1A,
-    Freezing = 0x1B,
-    Stalactite = 0x1C,
-    Stalagmite = 0x1D,
-    All = 0x1F,
+    Void            = 0x0C,
+    Suicide         = 0x0D,
+    Magic           = 0x0E,
+    Wither          = 0x0F,
+    Starve          = 0x10,
+    Anvil           = 0x11,
+    Thorns          = 0x12,
+    FallingBlock    = 0x13,
+    Piston          = 0x14,
+    FlyIntoWall     = 0x15,
+    Magma           = 0x16,
+    Fireworks       = 0x17,
+    Lightning       = 0x18,
+    Charging        = 0x19,
+    Temperature     = 0x1A,
+    Freezing        = 0x1B,
+    Stalactite      = 0x1C,
+    Stalagmite      = 0x1D,
+    All             = 0x1F,
 };
 
-enum class ObjectiveSortOrder : char
-{
-    Ascending = 0,
+enum class ObjectiveSortOrder : char {
+    Ascending  = 0,
     Descending = 1
 };
 
-enum class PlayerScoreSetFunction : char
-{
-    Set = 0,
-    Add = 1,
+enum class PlayerScoreSetFunction : char {
+    Set    = 0,
+    Add    = 1,
     Remove = 2
 };
 
-enum class ContainerID : uint8_t
-{
-    Invalid = 0xff,
-    Inventory = 0,
-    First = 1,
-    Last = 100,
-    Offhand = 119,
-    Armor = 120,
+enum class ContainerID : uint8_t {
+    Invalid        = 0xff,
+    Inventory      = 0,
+    First          = 1,
+    Last           = 100,
+    Offhand        = 119,
+    Armor          = 120,
     SelectionSlots = 122,
-    PlayerUIOnly = 124
+    PlayerUIOnly   = 124
 };
 
-enum class FaceID : char
-{
+enum class FaceID : char {
     Unknown = -1,
-    Down = 0,
-    Up = 1,
-    North = 2,
-    South = 3,
-    West = 4,
-    East = 5,
+    Down    = 0,
+    Up      = 1,
+    North   = 2,
+    South   = 3,
+    West    = 4,
+    East    = 5,
 };
 
-enum class MinecraftPacketIds : int
-{
+enum class MinecraftPacketIds : int {
     Login                             = 0x01,
     PlayStatus                        = 0x02,
     ServerToClientHandshake           = 0x03,
@@ -1001,7 +1097,7 @@ enum class MinecraftPacketIds : int
     EduUriResource                    = 0xAA,
     CreatePhoto                       = 0xAB,
     UpdateSubChunkBlocks              = 0xAC,
-    PhotoInfoRequest                  = 0xAD,//removed
+    PhotoInfoRequest                  = 0xAD, // removed
     SubChunk                          = 0xAE,
     SubChunkRequest                   = 0xAF,
     PlayerStartItemCooldown           = 0xB0,
@@ -1010,14 +1106,14 @@ enum class MinecraftPacketIds : int
     TickingAreasLoadStatus            = 0xB3,
     DimensionData                     = 0xB4,
     AgentActionEvent                  = 0xB5,
-    ChangeMobProperty 		          = 0xB6,
+    ChangeMobProperty                 = 0xB6,
     LessonProgress                    = 0xB7,
     RequestAbility                    = 0xB8,
     RequestPermissions                = 0xB9,
     ToastRequest                      = 0XBA
 };
 
-enum ItemStackNetResult :unsigned char {
+enum ItemStackNetResult : unsigned char {
     Success                                          = 0,
     Error                                            = 1,
     InvalidRequestActionType                         = 2,
@@ -1087,65 +1183,57 @@ enum ItemStackNetResult :unsigned char {
     ScreenStackError                                 = 67,
 };
 
-//class CommandVersion {
-//public:
-//    int Min = 1, Max = 0x7FFFFFFF;
-//};
+// class CommandVersion {
+// public:
+//     int Min = 1, Max = 0x7FFFFFFF;
+// };
 
-enum class InventoryTransactionError
-{
-    Unknown = 0,
-    NoError = 1,
-    BalanceMismatch = 2,
+enum class InventoryTransactionError {
+    Unknown            = 0,
+    NoError            = 1,
+    BalanceMismatch    = 2,
     SourceItemMismatch = 3,
-    InventoryMismatch = 4,
-    SizeMismatch = 5,
-    AuthorityMismatch = 6,
-    StateMismatch = 7,
-    ApiDenied = 8
+    InventoryMismatch  = 4,
+    SizeMismatch       = 5,
+    AuthorityMismatch  = 6,
+    StateMismatch      = 7,
+    ApiDenied          = 8
 };
 
-enum class InventorySourceType
-{
-    Invalid = -1,
-    Container = 0,
-    Global = 1,
-    World = 2,
-    Creative = 3,
+enum class InventorySourceType {
+    Invalid                = -1,
+    Container              = 0,
+    Global                 = 1,
+    World                  = 2,
+    Creative               = 3,
     UntrackedInteractionUI = 100,
-    NONIMPLEMENTEDTODO = 99999
+    NONIMPLEMENTEDTODO     = 99999
 };
 
 template <typename T>
-struct InvertableFilter
-{
-    T value;
+struct InvertableFilter {
+    T    value;
     bool inverted;
 };
 
 template <typename T>
-class CommandSelectorResults
-{
+class CommandSelectorResults {
 public:
     std::shared_ptr<std::vector<T*>> data;
 
-    auto begin()
-    {
+    auto begin() {
         return data->begin();
     }
 
-    auto end()
-    {
+    auto end() {
         return data->end();
     }
 
-    auto count() const
-    {
+    auto count() const {
         return data->size();
     }
 
-    auto empty() const
-    {
+    auto empty() const {
         return data->empty();
     }
 };
diff --git a/LiteLoader/Header/MC/Vec3.hpp b/LiteLoader/Header/MC/Vec3.hpp
--- a/LiteLoader/Header/MC/Vec3.hpp
+++ b/LiteLoader/Header/MC/Vec3.hpp
@@ -3,8 +3,7 @@
 #include "../Global.h"
 #include "BlockPos.hpp"
 
-class Vec3
-{
+class Vec3 {
 public:
     float x{}, y{}, z{};
 
@@ -86,178 +85,143 @@ class Vec3
 
     LIAPI float distanceTo(Vec3 const&) const;
 
-    inline std::string toString() const
-    {
+    inline std::string toString() const {
         return std::to_string(x) + "," + std::to_string(y) + "," + std::to_string(z);
     }
 
-    inline Vec3 add(float dx, float dy, float dz) const
-    {
+    inline Vec3 add(float dx, float dy, float dz) const {
         return {x + dx, y + dy, z + dz};
     }
 
-    inline Vec3 normalize() const
-    {
+    inline Vec3 normalize() const {
         float l = length();
         return {x / l, y / l, z / l};
     }
 
 
-    float& operator[](int index)
-    {
-        if (index < 0 || index > 2)
-        {
-            return (&x)[0];
-        }
+    float& operator[](int index) {
+            if (index < 0 || index > 2) { return (&x)[0]; }
         return (&x)[index];
     }
 
-    constexpr bool operator==(const Vec3& b) const
-    {
+    constexpr bool operator==(const Vec3& b) const {
         return x == b.x && y == b.y && z == b.z;
     }
 
-    constexpr bool operator!=(const Vec3& b) const
-    {
+    constexpr bool operator!=(const Vec3& b) const {
         return x != b.x || y != b.y || z != b.z;
     }
 
-    inline Vec3 operator*(float b) const
-    {
+    inline Vec3 operator*(float b) const {
         return {x * b, y * b, z * b};
     }
 
-    inline Vec3 operator/(float b) const
-    {
+    inline Vec3 operator/(float b) const {
         return {x / b, y / b, z / b};
     }
 
-    inline Vec3 operator+(float b) const
-    {
+    inline Vec3 operator+(float b) const {
         return {x + b, y + b, z + b};
     }
 
-    inline Vec3 operator-(float b) const
-    {
+    inline Vec3 operator-(float b) const {
         return {x - b, y - b, z - b};
     }
 
-    constexpr Vec3& operator+=(float b)
-    {
+    constexpr Vec3& operator+=(float b) {
         x += b;
         y += b;
         z += b;
         return *this;
     }
 
-    constexpr Vec3& operator-=(float b)
-    {
+    constexpr Vec3& operator-=(float b) {
         x -= b;
         y -= b;
         z -= b;
         return *this;
     }
 
-    constexpr Vec3& operator*=(float b)
-    {
+    constexpr Vec3& operator*=(float b) {
         x *= b;
         y *= b;
         z *= b;
         return *this;
     }
 
-    constexpr Vec3& operator/=(float b)
-    {
+    constexpr Vec3& operator/=(float b) {
         x /= b;
         y /= b;
         z /= b;
         return *this;
     }
 
-    constexpr Vec3& operator+=(Vec3 const& b)
-    {
+    constexpr Vec3& operator+=(Vec3 const& b) {
         x += b.x;
         y += b.y;
         z += b.z;
         return *this;
     }
 
-    constexpr Vec3& operator-=(Vec3 const& b)
-    {
+    constexpr Vec3& operator-=(Vec3 const& b) {
         x -= b.x;
         y -= b.y;
         z -= b.z;
         return *this;
     }
 
-    constexpr Vec3& operator*=(Vec3 const& b)
-    {
+    constexpr Vec3& operator*=(Vec3 const& b) {
         x *= b.x;
         y *= b.y;
         z *= b.z;
         return *this;
     }
 
-    constexpr Vec3& operator/=(Vec3 const& b)
-    {
+    constexpr Vec3& operator/=(Vec3 const& b) {
         x /= b.x;
         y /= b.y;
         z /= b.z;
         return *this;
     }
 
-    inline Vec3 operator+(Vec3 const& b) const
-    {
+    inline Vec3 operator+(Vec3 const& b) const {
         return {x + b.x, y + b.y, z + b.z};
     }
 
-    inline Vec3 operator*(Vec3 const& b) const
-    {
+    inline Vec3 operator*(Vec3 const& b) const {
         return {x * b.x, y * b.y, z * b.z};
     }
 
-    inline Vec3 operator/(Vec3 const& b) const
-    {
+    inline Vec3 operator/(Vec3 const& b) const {
         return {x / b.x, y / b.y, z / b.z};
     }
 
-    inline Vec3 operator-(Vec3 const& b) const
-    {
+    inline Vec3 operator-(Vec3 const& b) const {
         return {x - b.x, y - b.y, z - b.z};
     }
 
-    inline float dot(const Vec3& b) const
-    {
+    inline float dot(const Vec3& b) const {
         return {x * b.x + y * b.y + z * b.z};
     }
 
-    inline Vec3 cross(const Vec3& b) const
-    {
+    inline Vec3 cross(const Vec3& b) const {
         return {y * b.z - z * b.y, z * b.x - x * b.z, x * b.y - y * b.x};
     }
 
-    inline static Vec3 max(const Vec3& a, const Vec3& b)
-    {
+    inline static Vec3 max(const Vec3& a, const Vec3& b) {
         return {std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z)};
     }
 
-    inline static Vec3 min(const Vec3& a, const Vec3& b)
-    {
+    inline static Vec3 min(const Vec3& a, const Vec3& b) {
         return {std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z)};
     }
 };
 
-namespace std
-{
-
-template <>
-struct hash<Vec3>
-{
-    std::size_t operator()(Vec3 const& pos) const noexcept
-    {
-        return (std::hash<float>()(pos.x) ^
-                std::hash<float>()(pos.y) ^
-                std::hash<float>()(pos.z));
+namespace std {
+
+template <> struct hash<Vec3> {
+    std::size_t operator()(Vec3 const& pos) const noexcept {
+        return (std::hash<float>()(pos.x) ^ std::hash<float>()(pos.y) ^ std::hash<float>()(pos.z));
     }
 };
 
diff --git a/LiteLoader/Header/RemoteCallAPI.h b/LiteLoader/Header/RemoteCallAPI.h
--- a/LiteLoader/Header/RemoteCallAPI.h
+++ b/LiteLoader/Header/RemoteCallAPI.h
@@ -24,6 +24,10 @@
 // // in other plugin
 // auto strSize = RemoteCall::importAs<int(std::string const& arg)>("TestNameSpace", "strSize");
 // logger.info("Size of str: {}", strSize("12345678"));
+// 
+// // in js plugin
+// const strSize = ll.import("TestNameSpace", "strSize");
+// logger.info(`Size of str: ${strSize("12345678")}`);
 //
 /////////////////////////////////////////////////////
 namespace RemoteCall
diff --git a/LiteLoader/Header/ScheduleAPI.h b/LiteLoader/Header/ScheduleAPI.h
--- a/LiteLoader/Header/ScheduleAPI.h
+++ b/LiteLoader/Header/ScheduleAPI.h
@@ -43,6 +43,8 @@ class ScheduleTask
     {
         return taskId;
     }
+
+    LIAPI bool isFinished() const;
 };
 
 namespace Schedule
diff --git a/LiteLoader/Header/Utils/WinHelper.h b/LiteLoader/Header/Utils/WinHelper.h
--- a/LiteLoader/Header/Utils/WinHelper.h
+++ b/LiteLoader/Header/Utils/WinHelper.h
@@ -26,3 +26,5 @@ HMODULE inline GetCurrentModule()
 // Get the module path from a module handler
 LIAPI std::string GetModulePath(HMODULE handler);
 LIAPI std::string GetModuleName(HMODULE handler);
+
+LIAPI bool IsWineEnvironment();
diff --git a/LiteLoader/Header/third-party/httplib/httplib.h b/LiteLoader/Header/third-party/httplib/httplib.h
--- a/LiteLoader/Header/third-party/httplib/httplib.h
+++ b/LiteLoader/Header/third-party/httplib/httplib.h
@@ -1,90 +1,112 @@
 //
 //  httplib.h
 //
-//  Copyright (c) 2020 Yuji Hirose. All rights reserved.
+//  Copyright (c) 2022 Yuji Hirose. All rights reserved.
 //  MIT License
 //
 
 #ifndef CPPHTTPLIB_HTTPLIB_H
 #define CPPHTTPLIB_HTTPLIB_H
 
+#define CPPHTTPLIB_VERSION "0.10.7"
+
 /*
  * Configuration
  */
 
 #ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND
-#    define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
+#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
 #endif
 
 #ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
-#    define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5
+#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5
 #endif
 
 #ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND
-#    define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300
+#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300
 #endif
 
 #ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND
-#    define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0
+#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0
 #endif
 
 #ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND
-#    define CPPHTTPLIB_READ_TIMEOUT_SECOND 5
+#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5
 #endif
 
 #ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND
-#    define CPPHTTPLIB_READ_TIMEOUT_USECOND 0
+#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0
 #endif
 
 #ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND
-#    define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5
+#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5
 #endif
 
 #ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND
-#    define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0
+#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0
 #endif
 
 #ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND
-#    define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0
+#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0
 #endif
 
 #ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND
-#    ifdef _WIN32
-#        define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000
-#    else
-#        define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0
-#    endif
+#ifdef _WIN32
+#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000
+#else
+#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0
+#endif
 #endif
 
 #ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH
-#    define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192
+#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192
+#endif
+
+#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH
+#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192
 #endif
 
 #ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT
-#    define CPPHTTPLIB_REDIRECT_MAX_COUNT 20
+#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20
 #endif
 
 #ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH
-#    define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits<size_t>::max)())
+#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits<size_t>::max)())
+#endif
+
+#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH
+#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192
 #endif
 
 #ifndef CPPHTTPLIB_TCP_NODELAY
-#    define CPPHTTPLIB_TCP_NODELAY false
+#define CPPHTTPLIB_TCP_NODELAY false
 #endif
 
 #ifndef CPPHTTPLIB_RECV_BUFSIZ
-#    define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u)
+#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u)
 #endif
 
 #ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ
-#    define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u)
+#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u)
 #endif
 
 #ifndef CPPHTTPLIB_THREAD_POOL_COUNT
-#    define CPPHTTPLIB_THREAD_POOL_COUNT                              \
-        ((std::max)(8u, std::thread::hardware_concurrency() > 0       \
-                            ? std::thread::hardware_concurrency() - 1 \
-                            : 0))
+#define CPPHTTPLIB_THREAD_POOL_COUNT                                           \
+  ((std::max)(8u, std::thread::hardware_concurrency() > 0                      \
+                      ? std::thread::hardware_concurrency() - 1                \
+                      : 0))
+#endif
+
+#ifndef CPPHTTPLIB_RECV_FLAGS
+#define CPPHTTPLIB_RECV_FLAGS 0
+#endif
+
+#ifndef CPPHTTPLIB_SEND_FLAGS
+#define CPPHTTPLIB_SEND_FLAGS 0
+#endif
+
+#ifndef CPPHTTPLIB_LISTEN_BACKLOG
+#define CPPHTTPLIB_LISTEN_BACKLOG 5
 #endif
 
 /*
@@ -92,98 +114,98 @@
  */
 
 #ifdef _WIN32
-#    ifndef _CRT_SECURE_NO_WARNINGS
-#        define _CRT_SECURE_NO_WARNINGS
-#    endif  //_CRT_SECURE_NO_WARNINGS
+#ifndef _CRT_SECURE_NO_WARNINGS
+#define _CRT_SECURE_NO_WARNINGS
+#endif //_CRT_SECURE_NO_WARNINGS
 
-#    ifndef _CRT_NONSTDC_NO_DEPRECATE
-#        define _CRT_NONSTDC_NO_DEPRECATE
-#    endif  //_CRT_NONSTDC_NO_DEPRECATE
+#ifndef _CRT_NONSTDC_NO_DEPRECATE
+#define _CRT_NONSTDC_NO_DEPRECATE
+#endif //_CRT_NONSTDC_NO_DEPRECATE
 
-#    if defined(_MSC_VER)
-#        ifdef _WIN64
+#if defined(_MSC_VER)
+#ifdef _WIN64
 using ssize_t = __int64;
-#        else
+#else
 using ssize_t = int;
-#        endif
-
-#        if _MSC_VER < 1900
-#            define snprintf _snprintf_s
-#        endif
-#    endif  // _MSC_VER
+#endif
 
-#    ifndef S_ISREG
-#        define S_ISREG(m) (((m)&S_IFREG) == S_IFREG)
-#    endif  // S_ISREG
+#if _MSC_VER < 1900
+#define snprintf _snprintf_s
+#endif
+#endif // _MSC_VER
 
-#    ifndef S_ISDIR
-#        define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR)
-#    endif  // S_ISDIR
+#ifndef S_ISREG
+#define S_ISREG(m) (((m)&S_IFREG) == S_IFREG)
+#endif // S_ISREG
 
-#    ifndef NOMINMAX
-#        define NOMINMAX
-#    endif  // NOMINMAX
+#ifndef S_ISDIR
+#define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR)
+#endif // S_ISDIR
 
-#    include <io.h>
-#    include <winsock2.h>
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif // NOMINMAX
 
-#    include <wincrypt.h>
-#    include <ws2tcpip.h>
+#include <io.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
 
-#    ifndef WSA_FLAG_NO_HANDLE_INHERIT
-#        define WSA_FLAG_NO_HANDLE_INHERIT 0x80
-#    endif
+#ifndef WSA_FLAG_NO_HANDLE_INHERIT
+#define WSA_FLAG_NO_HANDLE_INHERIT 0x80
+#endif
 
-#    ifdef _MSC_VER
-#        pragma comment(lib, "ws2_32.lib")
-#        pragma comment(lib, "crypt32.lib")
-#        pragma comment(lib, "cryptui.lib")
-#    endif
+#ifdef _MSC_VER
+#pragma comment(lib, "ws2_32.lib")
+#endif
 
-#    ifndef strcasecmp
-#        define strcasecmp _stricmp
-#    endif  // strcasecmp
+#ifndef strcasecmp
+#define strcasecmp _stricmp
+#endif // strcasecmp
 
 using socket_t = SOCKET;
-#    ifdef CPPHTTPLIB_USE_POLL
-#        define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
-#    endif
-
-#else  // not _WIN32
-
-#    include <arpa/inet.h>
-#    include <ifaddrs.h>
-#    include <netdb.h>
-#    include <netinet/in.h>
-#    include <cstring>
-#    ifdef __linux__
-#        include <resolv.h>
-#    endif
-#    include <netinet/tcp.h>
-#    ifdef CPPHTTPLIB_USE_POLL
-#        include <poll.h>
-#    endif
-#    include <pthread.h>
-#    include <sys/select.h>
-#    include <sys/socket.h>
-#    include <unistd.h>
-#    include <csignal>
+#ifdef CPPHTTPLIB_USE_POLL
+#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
+#endif
+
+#else // not _WIN32
+
+#include <arpa/inet.h>
+#include <ifaddrs.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef __linux__
+#include <resolv.h>
+#endif
+#include <netinet/tcp.h>
+#ifdef CPPHTTPLIB_USE_POLL
+#include <poll.h>
+#endif
+#include <csignal>
+#include <pthread.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <unistd.h>
 
 using socket_t = int;
-#    define INVALID_SOCKET (-1)
-#endif  //_WIN32
+#ifndef INVALID_SOCKET
+#define INVALID_SOCKET (-1)
+#endif
+#endif //_WIN32
 
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/stat.h>
+#include <cstring>
+#include <algorithm>
 #include <array>
 #include <atomic>
 #include <cassert>
 #include <cctype>
 #include <climits>
 #include <condition_variable>
+#include <errno.h>
+#include <fcntl.h>
 #include <fstream>
 #include <functional>
+#include <iomanip>
 #include <iostream>
 #include <list>
 #include <map>
@@ -191,43 +213,60 @@ using socket_t = int;
 #include <mutex>
 #include <random>
 #include <regex>
+#include <set>
 #include <sstream>
 #include <string>
+#include <sys/stat.h>
 #include <thread>
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-#    include <openssl/err.h>
-#    include <openssl/md5.h>
-#    include <openssl/ssl.h>
-#    include <openssl/x509v3.h>
+#ifdef _WIN32
+#include <wincrypt.h>
+
+// these are defined in wincrypt.h and it breaks compilation if BoringSSL is
+// used
+#undef X509_NAME
+#undef X509_CERT_PAIR
+#undef X509_EXTENSIONS
+#undef PKCS7_SIGNER_INFO
+
+#ifdef _MSC_VER
+#pragma comment(lib, "crypt32.lib")
+#pragma comment(lib, "cryptui.lib")
+#endif
+#endif //_WIN32
+
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
 
-#    if defined(_WIN32) && defined(OPENSSL_USE_APPLINK)
-#        include <openssl/applink.c>
-#    endif
+#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK)
+#include <openssl/applink.c>
+#endif
 
-#    include <iomanip>
-#    include <iostream>
-#    include <sstream>
+#include <iostream>
+#include <sstream>
 
-#    if OPENSSL_VERSION_NUMBER < 0x1010100fL
-#        error Sorry, OpenSSL versions prior to 1.1.1 are not supported
-#    endif
+#if OPENSSL_VERSION_NUMBER < 0x1010100fL
+#error Sorry, OpenSSL versions prior to 1.1.1 are not supported
+#endif
 
-#    if OPENSSL_VERSION_NUMBER < 0x10100000L
-#        include <openssl/include/crypto.h>
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+#include <openssl/crypto.h>
 inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) {
-    return M_ASN1_STRING_data(asn1);
+  return M_ASN1_STRING_data(asn1);
 }
-#    endif
+#endif
 #endif
 
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
-#    include <zlib.h>
+#include <zlib.h>
 #endif
 
 #ifdef CPPHTTPLIB_BROTLI_SUPPORT
-#    include <brotli/decode.h>
-#    include <brotli/encode.h>
+#include <brotli/decode.h>
+#include <brotli/encode.h>
 #endif
 
 /*
@@ -237,20 +276,43 @@ namespace httplib {
 
 namespace detail {
 
+/*
+ * Backport std::make_unique from C++14.
+ *
+ * NOTE: This code came up with the following stackoverflow post:
+ * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique
+ *
+ */
+
+template <class T, class... Args>
+typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
+make_unique(Args &&...args) {
+  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+}
+
+template <class T>
+typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type
+make_unique(std::size_t n) {
+  typedef typename std::remove_extent<T>::type RT;
+  return std::unique_ptr<T>(new RT[n]);
+}
+
 struct ci {
-    bool operator()(const std::string &s1, const std::string &s2) const {
-        return std::lexicographical_compare(
-            s1.begin(), s1.end(), s2.begin(), s2.end(),
-            [](char c1, char c2) { return ::tolower(c1) < ::tolower(c2); });
-    }
+  bool operator()(const std::string &s1, const std::string &s2) const {
+    return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(),
+                                        s2.end(),
+                                        [](unsigned char c1, unsigned char c2) {
+                                          return ::tolower(c1) < ::tolower(c2);
+                                        });
+  }
 };
 
-}  // namespace detail
+} // namespace detail
 
 using Headers = std::multimap<std::string, std::string, detail::ci>;
 
 using Params = std::multimap<std::string, std::string>;
-using Match  = std::smatch;
+using Match = std::smatch;
 
 using Progress = std::function<bool(uint64_t current, uint64_t total)>;
 
@@ -258,3357 +320,3934 @@ struct Response;
 using ResponseHandler = std::function<bool(const Response &response)>;
 
 struct MultipartFormData {
-    std::string name;
-    std::string content;
-    std::string filename;
-    std::string content_type;
+  std::string name;
+  std::string content;
+  std::string filename;
+  std::string content_type;
 };
 using MultipartFormDataItems = std::vector<MultipartFormData>;
-using MultipartFormDataMap   = std::multimap<std::string, MultipartFormData>;
+using MultipartFormDataMap = std::multimap<std::string, MultipartFormData>;
 
 class DataSink {
-  public:
-    DataSink() : os(&sb_), sb_(*this) {}
+public:
+  DataSink() : os(&sb_), sb_(*this) {}
 
-    DataSink(const DataSink &) = delete;
-    DataSink &operator=(const DataSink &) = delete;
-    DataSink(DataSink &&)                 = delete;
-    DataSink &operator=(DataSink &&) = delete;
+  DataSink(const DataSink &) = delete;
+  DataSink &operator=(const DataSink &) = delete;
+  DataSink(DataSink &&) = delete;
+  DataSink &operator=(DataSink &&) = delete;
 
-    std::function<void(const char *data, size_t data_len)> write;
-    std::function<void()> done;
-    std::function<bool()> is_writable;
-    std::ostream os;
+  std::function<bool(const char *data, size_t data_len)> write;
+  std::function<void()> done;
+  std::function<bool()> is_writable;
+  std::ostream os;
 
-  private:
-    class data_sink_streambuf : public std::streambuf {
-      public:
-        explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {}
-
-      protected:
-        std::streamsize xsputn(const char *s, std::streamsize n) {
-            sink_.write(s, static_cast<size_t>(n));
-            return n;
-        }
-
-      private:
-        DataSink &sink_;
-    };
+private:
+  class data_sink_streambuf : public std::streambuf {
+  public:
+    explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {}
 
-    data_sink_streambuf sb_;
-};
+  protected:
+    std::streamsize xsputn(const char *s, std::streamsize n) {
+      sink_.write(s, static_cast<size_t>(n));
+      return n;
+    }
 
-using ContentProvider = std::function<bool(size_t offset, size_t length, DataSink &sink)>;
+  private:
+    DataSink &sink_;
+  };
 
-using ContentProviderWithoutLength = std::function<bool(size_t offset, DataSink &sink)>;
+  data_sink_streambuf sb_;
+};
 
-using ContentReceiver = std::function<bool(const char *data, size_t data_length)>;
+using ContentProvider =
+    std::function<bool(size_t offset, size_t length, DataSink &sink)>;
 
-using MultipartContentHeader = std::function<bool(const MultipartFormData &file)>;
+using ContentProviderWithoutLength =
+    std::function<bool(size_t offset, DataSink &sink)>;
 
-class ContentReader {
-  public:
-    using Reader = std::function<bool(ContentReceiver receiver)>;
-    using MultipartReader =
-        std::function<bool(MultipartContentHeader header, ContentReceiver receiver)>;
+using ContentProviderResourceReleaser = std::function<void(bool success)>;
 
-    ContentReader(Reader reader, MultipartReader multipart_reader)
-        : reader_(reader), multipart_reader_(multipart_reader) {}
+using ContentReceiverWithProgress =
+    std::function<bool(const char *data, size_t data_length, uint64_t offset,
+                       uint64_t total_length)>;
 
-    bool operator()(MultipartContentHeader header, ContentReceiver receiver) const {
-        return multipart_reader_(header, receiver);
-    }
+using ContentReceiver =
+    std::function<bool(const char *data, size_t data_length)>;
 
-    bool operator()(ContentReceiver receiver) const { return reader_(receiver); }
+using MultipartContentHeader =
+    std::function<bool(const MultipartFormData &file)>;
 
-    Reader reader_;
-    MultipartReader multipart_reader_;
+class ContentReader {
+public:
+  using Reader = std::function<bool(ContentReceiver receiver)>;
+  using MultipartReader = std::function<bool(MultipartContentHeader header,
+                                             ContentReceiver receiver)>;
+
+  ContentReader(Reader reader, MultipartReader multipart_reader)
+      : reader_(std::move(reader)),
+        multipart_reader_(std::move(multipart_reader)) {}
+
+  bool operator()(MultipartContentHeader header,
+                  ContentReceiver receiver) const {
+    return multipart_reader_(std::move(header), std::move(receiver));
+  }
+
+  bool operator()(ContentReceiver receiver) const {
+    return reader_(std::move(receiver));
+  }
+
+  Reader reader_;
+  MultipartReader multipart_reader_;
 };
 
-using Range  = std::pair<ssize_t, ssize_t>;
+using Range = std::pair<ssize_t, ssize_t>;
 using Ranges = std::vector<Range>;
 
 struct Request {
-    std::string method;
-    std::string path;
-    Headers headers;
-    std::string body;
-
-    std::string remote_addr;
-    int remote_port = -1;
-
-    // for server
-    std::string version;
-    std::string target;
-    Params params;
-    MultipartFormDataMap files;
-    Ranges ranges;
-    Match matches;
-
-    // for client
-    size_t redirect_count = CPPHTTPLIB_REDIRECT_MAX_COUNT;
-    ResponseHandler response_handler;
-    ContentReceiver content_receiver;
-    size_t content_length = 0;
-    ContentProvider content_provider;
-    Progress progress;
-
+  std::string method;
+  std::string path;
+  Headers headers;
+  std::string body;
+
+  std::string remote_addr;
+  int remote_port = -1;
+
+  // for server
+  std::string version;
+  std::string target;
+  Params params;
+  MultipartFormDataMap files;
+  Ranges ranges;
+  Match matches;
+
+  // for client
+  ResponseHandler response_handler;
+  ContentReceiverWithProgress content_receiver;
+  Progress progress;
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-    const SSL *ssl;
+  const SSL *ssl = nullptr;
 #endif
 
-    bool has_header(const char *key) const;
-    std::string get_header_value(const char *key, size_t id = 0) const;
-    template <typename T>
-    T get_header_value(const char *key, size_t id = 0) const;
-    size_t get_header_value_count(const char *key) const;
-    void set_header(const char *key, const char *val);
-    void set_header(const char *key, const std::string &val);
-
-    bool has_param(const char *key) const;
-    std::string get_param_value(const char *key, size_t id = 0) const;
-    size_t get_param_value_count(const char *key) const;
-
-    bool is_multipart_form_data() const;
-
-    bool has_file(const char *key) const;
-    MultipartFormData get_file_value(const char *key) const;
-
-    // private members...
-    size_t authorization_count_ = 0;
+  bool has_header(const char *key) const;
+  std::string get_header_value(const char *key, size_t id = 0) const;
+  template <typename T>
+  T get_header_value(const char *key, size_t id = 0) const;
+  size_t get_header_value_count(const char *key) const;
+  void set_header(const char *key, const char *val);
+  void set_header(const char *key, const std::string &val);
+
+  bool has_param(const char *key) const;
+  std::string get_param_value(const char *key, size_t id = 0) const;
+  size_t get_param_value_count(const char *key) const;
+
+  bool is_multipart_form_data() const;
+
+  bool has_file(const char *key) const;
+  MultipartFormData get_file_value(const char *key) const;
+
+  // private members...
+  size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT;
+  size_t content_length_ = 0;
+  ContentProvider content_provider_;
+  bool is_chunked_content_provider_ = false;
+  size_t authorization_count_ = 0;
 };
 
 struct Response {
-    std::string version;
-    int status = -1;
-    std::string reason;
-    Headers headers;
-    std::string body;
-
-    bool has_header(const char *key) const;
-    std::string get_header_value(const char *key, size_t id = 0) const;
-    template <typename T>
-    T get_header_value(const char *key, size_t id = 0) const;
-    size_t get_header_value_count(const char *key) const;
-    void set_header(const char *key, const char *val);
-    void set_header(const char *key, const std::string &val);
-
-    void set_redirect(const char *url, int status = 302);
-    void set_redirect(const std::string &url, int status = 302);
-    void set_content(const char *s, size_t n, const char *content_type);
-    void set_content(std::string s, const char *content_type);
-
-    void set_content_provider(size_t length,
-                              const char *content_type,
-                              ContentProvider provider,
-                              const std::function<void()> &resource_releaser = nullptr);
-
-    void set_content_provider(const char *content_type,
-                              ContentProviderWithoutLength provider,
-                              const std::function<void()> &resource_releaser = nullptr);
-
-    void set_chunked_content_provider(const char *content_type,
-                                      ContentProviderWithoutLength provider,
-                                      const std::function<void()> &resource_releaser = nullptr);
-
-    Response()                 = default;
-    Response(const Response &) = default;
-    Response &operator=(const Response &) = default;
-    Response(Response &&)                 = default;
-    Response &operator=(Response &&) = default;
-    ~Response() {
-        if (content_provider_resource_releaser_) {
-            content_provider_resource_releaser_();
-        }
-    }
-
-    // private members...
-    size_t content_length_ = 0;
-    ContentProvider content_provider_;
-    std::function<void()> content_provider_resource_releaser_;
-    bool is_chunked_content_provider = false;
+  std::string version;
+  int status = -1;
+  std::string reason;
+  Headers headers;
+  std::string body;
+  std::string location; // Redirect location
+
+  bool has_header(const char *key) const;
+  std::string get_header_value(const char *key, size_t id = 0) const;
+  template <typename T>
+  T get_header_value(const char *key, size_t id = 0) const;
+  size_t get_header_value_count(const char *key) const;
+  void set_header(const char *key, const char *val);
+  void set_header(const char *key, const std::string &val);
+
+  void set_redirect(const char *url, int status = 302);
+  void set_redirect(const std::string &url, int status = 302);
+  void set_content(const char *s, size_t n, const char *content_type);
+  void set_content(const std::string &s, const char *content_type);
+
+  void set_content_provider(
+      size_t length, const char *content_type, ContentProvider provider,
+      ContentProviderResourceReleaser resource_releaser = nullptr);
+
+  void set_content_provider(
+      const char *content_type, ContentProviderWithoutLength provider,
+      ContentProviderResourceReleaser resource_releaser = nullptr);
+
+  void set_chunked_content_provider(
+      const char *content_type, ContentProviderWithoutLength provider,
+      ContentProviderResourceReleaser resource_releaser = nullptr);
+
+  Response() = default;
+  Response(const Response &) = default;
+  Response &operator=(const Response &) = default;
+  Response(Response &&) = default;
+  Response &operator=(Response &&) = default;
+  ~Response() {
+    if (content_provider_resource_releaser_) {
+      content_provider_resource_releaser_(content_provider_success_);
+    }
+  }
+
+  // private members...
+  size_t content_length_ = 0;
+  ContentProvider content_provider_;
+  ContentProviderResourceReleaser content_provider_resource_releaser_;
+  bool is_chunked_content_provider_ = false;
+  bool content_provider_success_ = false;
 };
 
 class Stream {
-  public:
-    virtual ~Stream() = default;
+public:
+  virtual ~Stream() = default;
 
-    virtual bool is_readable() const = 0;
-    virtual bool is_writable() const = 0;
+  virtual bool is_readable() const = 0;
+  virtual bool is_writable() const = 0;
 
-    virtual ssize_t read(char *ptr, size_t size)                          = 0;
-    virtual ssize_t write(const char *ptr, size_t size)                   = 0;
-    virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
+  virtual ssize_t read(char *ptr, size_t size) = 0;
+  virtual ssize_t write(const char *ptr, size_t size) = 0;
+  virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
+  virtual socket_t socket() const = 0;
 
-    template <typename... Args>
-    ssize_t write_format(const char *fmt, const Args &... args);
-    ssize_t write(const char *ptr);
-    ssize_t write(const std::string &s);
+  template <typename... Args>
+  ssize_t write_format(const char *fmt, const Args &...args);
+  ssize_t write(const char *ptr);
+  ssize_t write(const std::string &s);
 };
 
 class TaskQueue {
-  public:
-    TaskQueue()          = default;
-    virtual ~TaskQueue() = default;
+public:
+  TaskQueue() = default;
+  virtual ~TaskQueue() = default;
 
-    virtual void enqueue(std::function<void()> fn) = 0;
-    virtual void shutdown()                        = 0;
+  virtual void enqueue(std::function<void()> fn) = 0;
+  virtual void shutdown() = 0;
 
-    virtual void on_idle(){};
+  virtual void on_idle() {}
 };
 
 class ThreadPool : public TaskQueue {
-  public:
-    explicit ThreadPool(size_t n) : shutdown_(false) {
-        while (n) {
-            threads_.emplace_back(worker(*this));
-            n--;
-        }
+public:
+  explicit ThreadPool(size_t n) : shutdown_(false) {
+    while (n) {
+      threads_.emplace_back(worker(*this));
+      n--;
     }
+  }
 
-    ThreadPool(const ThreadPool &) = delete;
-    ~ThreadPool() override         = default;
+  ThreadPool(const ThreadPool &) = delete;
+  ~ThreadPool() override = default;
 
-    void enqueue(std::function<void()> fn) override {
-        std::unique_lock<std::mutex> lock(mutex_);
-        jobs_.push_back(fn);
-        cond_.notify_one();
-    }
+  void enqueue(std::function<void()> fn) override {
+    std::unique_lock<std::mutex> lock(mutex_);
+    jobs_.push_back(std::move(fn));
+    cond_.notify_one();
+  }
 
-    void shutdown() override {
-        // Stop all worker threads...
-        {
-            std::unique_lock<std::mutex> lock(mutex_);
-            shutdown_ = true;
-        }
+  void shutdown() override {
+    // Stop all worker threads...
+    {
+      std::unique_lock<std::mutex> lock(mutex_);
+      shutdown_ = true;
+    }
 
-        cond_.notify_all();
+    cond_.notify_all();
 
-        // Join...
-        for (auto &t : threads_) {
-            t.join();
-        }
+    // Join...
+    for (auto &t : threads_) {
+      t.join();
     }
+  }
 
-  private:
-    struct worker {
-        explicit worker(ThreadPool &pool) : pool_(pool) {}
-
-        void operator()() {
-            for (;;) {
-                std::function<void()> fn;
-                {
-                    std::unique_lock<std::mutex> lock(pool_.mutex_);
+private:
+  struct worker {
+    explicit worker(ThreadPool &pool) : pool_(pool) {}
 
-                    pool_.cond_.wait(lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; });
+    void operator()() {
+      for (;;) {
+        std::function<void()> fn;
+        {
+          std::unique_lock<std::mutex> lock(pool_.mutex_);
 
-                    if (pool_.shutdown_ && pool_.jobs_.empty()) {
-                        break;
-                    }
+          pool_.cond_.wait(
+              lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; });
 
-                    fn = pool_.jobs_.front();
-                    pool_.jobs_.pop_front();
-                }
+          if (pool_.shutdown_ && pool_.jobs_.empty()) { break; }
 
-                assert(true == static_cast<bool>(fn));
-                fn();
-            }
+          fn = pool_.jobs_.front();
+          pool_.jobs_.pop_front();
         }
 
-        ThreadPool &pool_;
-    };
-    friend struct worker;
+        assert(true == static_cast<bool>(fn));
+        fn();
+      }
+    }
+
+    ThreadPool &pool_;
+  };
+  friend struct worker;
 
-    std::vector<std::thread> threads_;
-    std::list<std::function<void()>> jobs_;
+  std::vector<std::thread> threads_;
+  std::list<std::function<void()>> jobs_;
 
-    bool shutdown_;
+  bool shutdown_;
 
-    std::condition_variable cond_;
-    std::mutex mutex_;
+  std::condition_variable cond_;
+  std::mutex mutex_;
 };
 
 using Logger = std::function<void(const Request &, const Response &)>;
 
 using SocketOptions = std::function<void(socket_t sock)>;
 
-inline void default_socket_options(socket_t sock) {
-    int yes = 1;
-#ifdef _WIN32
-    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&yes), sizeof(yes));
-    setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, reinterpret_cast<char *>(&yes), sizeof(yes));
-#else
-#    ifdef SO_REUSEPORT
-    setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<void *>(&yes), sizeof(yes));
-#    else
-    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<void *>(&yes), sizeof(yes));
-#    endif
-#endif
-}
+void default_socket_options(socket_t sock);
 
 class Server {
-  public:
-    using Handler = std::function<void(const Request &, Response &)>;
-    using HandlerWithContentReader =
-        std::function<void(const Request &, Response &, const ContentReader &content_reader)>;
-    using Expect100ContinueHandler = std::function<int(const Request &, Response &)>;
-
-    Server();
+public:
+  using Handler = std::function<void(const Request &, Response &)>;
+
+  using ExceptionHandler =
+      std::function<void(const Request &, Response &, std::exception &e)>;
+
+  enum class HandlerResponse {
+    Handled,
+    Unhandled,
+  };
+  using HandlerWithResponse =
+      std::function<HandlerResponse(const Request &, Response &)>;
+
+  using HandlerWithContentReader = std::function<void(
+      const Request &, Response &, const ContentReader &content_reader)>;
+
+  using Expect100ContinueHandler =
+      std::function<int(const Request &, Response &)>;
+
+  Server();
+
+  virtual ~Server();
+
+  virtual bool is_valid() const;
+
+  Server &Get(const std::string &pattern, Handler handler);
+  Server &Post(const std::string &pattern, Handler handler);
+  Server &Post(const std::string &pattern, HandlerWithContentReader handler);
+  Server &Put(const std::string &pattern, Handler handler);
+  Server &Put(const std::string &pattern, HandlerWithContentReader handler);
+  Server &Patch(const std::string &pattern, Handler handler);
+  Server &Patch(const std::string &pattern, HandlerWithContentReader handler);
+  Server &Delete(const std::string &pattern, Handler handler);
+  Server &Delete(const std::string &pattern, HandlerWithContentReader handler);
+  Server &Options(const std::string &pattern, Handler handler);
+
+  bool set_base_dir(const std::string &dir,
+                    const std::string &mount_point = std::string());
+  bool set_mount_point(const std::string &mount_point, const std::string &dir,
+                       Headers headers = Headers());
+  bool remove_mount_point(const std::string &mount_point);
+  Server &set_file_extension_and_mimetype_mapping(const char *ext,
+                                                  const char *mime);
+  Server &set_file_request_handler(Handler handler);
+
+  Server &set_error_handler(HandlerWithResponse handler);
+  Server &set_error_handler(Handler handler);
+  Server &set_exception_handler(ExceptionHandler handler);
+  Server &set_pre_routing_handler(HandlerWithResponse handler);
+  Server &set_post_routing_handler(Handler handler);
+
+  Server &set_expect_100_continue_handler(Expect100ContinueHandler handler);
+  Server &set_logger(Logger logger);
+
+  Server &set_address_family(int family);
+  Server &set_tcp_nodelay(bool on);
+  Server &set_socket_options(SocketOptions socket_options);
+
+  Server &set_default_headers(Headers headers);
+
+  Server &set_keep_alive_max_count(size_t count);
+  Server &set_keep_alive_timeout(time_t sec);
+
+  Server &set_read_timeout(time_t sec, time_t usec = 0);
+  template <class Rep, class Period>
+  Server &set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
+
+  Server &set_write_timeout(time_t sec, time_t usec = 0);
+  template <class Rep, class Period>
+  Server &set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
+
+  Server &set_idle_interval(time_t sec, time_t usec = 0);
+  template <class Rep, class Period>
+  Server &set_idle_interval(const std::chrono::duration<Rep, Period> &duration);
+
+  Server &set_payload_max_length(size_t length);
+
+  bool bind_to_port(const char *host, int port, int socket_flags = 0);
+  int bind_to_any_port(const char *host, int socket_flags = 0);
+  bool listen_after_bind();
+
+  bool listen(const char *host, int port, int socket_flags = 0);
+
+  bool is_running() const;
+  void stop();
+
+  std::function<TaskQueue *(void)> new_task_queue;
+
+protected:
+  bool process_request(Stream &strm, bool close_connection,
+                       bool &connection_closed,
+                       const std::function<void(Request &)> &setup_request);
+
+  std::atomic<socket_t> svr_sock_;
+  size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
+  time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
+  time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
+  time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
+  time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
+  time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
+  time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND;
+  time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND;
+  size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
+
+private:
+  using Handlers = std::vector<std::pair<std::regex, Handler>>;
+  using HandlersForContentReader =
+      std::vector<std::pair<std::regex, HandlerWithContentReader>>;
+
+  socket_t create_server_socket(const char *host, int port, int socket_flags,
+                                SocketOptions socket_options) const;
+  int bind_internal(const char *host, int port, int socket_flags);
+  bool listen_internal();
+
+  bool routing(Request &req, Response &res, Stream &strm);
+  bool handle_file_request(const Request &req, Response &res,
+                           bool head = false);
+  bool dispatch_request(Request &req, Response &res, const Handlers &handlers);
+  bool
+  dispatch_request_for_content_reader(Request &req, Response &res,
+                                      ContentReader content_reader,
+                                      const HandlersForContentReader &handlers);
+
+  bool parse_request_line(const char *s, Request &req);
+  void apply_ranges(const Request &req, Response &res,
+                    std::string &content_type, std::string &boundary);
+  bool write_response(Stream &strm, bool close_connection, const Request &req,
+                      Response &res);
+  bool write_response_with_content(Stream &strm, bool close_connection,
+                                   const Request &req, Response &res);
+  bool write_response_core(Stream &strm, bool close_connection,
+                           const Request &req, Response &res,
+                           bool need_apply_ranges);
+  bool write_content_with_provider(Stream &strm, const Request &req,
+                                   Response &res, const std::string &boundary,
+                                   const std::string &content_type);
+  bool read_content(Stream &strm, Request &req, Response &res);
+  bool
+  read_content_with_content_receiver(Stream &strm, Request &req, Response &res,
+                                     ContentReceiver receiver,
+                                     MultipartContentHeader multipart_header,
+                                     ContentReceiver multipart_receiver);
+  bool read_content_core(Stream &strm, Request &req, Response &res,
+                         ContentReceiver receiver,
+                         MultipartContentHeader mulitpart_header,
+                         ContentReceiver multipart_receiver);
+
+  virtual bool process_and_close_socket(socket_t sock);
+
+  struct MountPointEntry {
+    std::string mount_point;
+    std::string base_dir;
+    Headers headers;
+  };
+  std::vector<MountPointEntry> base_dirs_;
+
+  std::atomic<bool> is_running_;
+  std::map<std::string, std::string> file_extension_and_mimetype_map_;
+  Handler file_request_handler_;
+  Handlers get_handlers_;
+  Handlers post_handlers_;
+  HandlersForContentReader post_handlers_for_content_reader_;
+  Handlers put_handlers_;
+  HandlersForContentReader put_handlers_for_content_reader_;
+  Handlers patch_handlers_;
+  HandlersForContentReader patch_handlers_for_content_reader_;
+  Handlers delete_handlers_;
+  HandlersForContentReader delete_handlers_for_content_reader_;
+  Handlers options_handlers_;
+  HandlerWithResponse error_handler_;
+  ExceptionHandler exception_handler_;
+  HandlerWithResponse pre_routing_handler_;
+  Handler post_routing_handler_;
+  Logger logger_;
+  Expect100ContinueHandler expect_100_continue_handler_;
+
+  int address_family_ = AF_UNSPEC;
+  bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
+  SocketOptions socket_options_ = default_socket_options;
+
+  Headers default_headers_;
+};
 
-    virtual ~Server();
+enum class Error {
+  Success = 0,
+  Unknown,
+  Connection,
+  BindIPAddress,
+  Read,
+  Write,
+  ExceedRedirectCount,
+  Canceled,
+  SSLConnection,
+  SSLLoadingCerts,
+  SSLServerVerification,
+  UnsupportedMultipartBoundaryChars,
+  Compression,
+  ConnectionTimeout,
+};
 
-    virtual bool is_valid() const;
+std::string to_string(const Error error);
 
-    Server &Get(const char *pattern, Handler handler);
-    Server &Post(const char *pattern, Handler handler);
-    Server &Post(const char *pattern, HandlerWithContentReader handler);
-    Server &Put(const char *pattern, Handler handler);
-    Server &Put(const char *pattern, HandlerWithContentReader handler);
-    Server &Patch(const char *pattern, Handler handler);
-    Server &Patch(const char *pattern, HandlerWithContentReader handler);
-    Server &Delete(const char *pattern, Handler handler);
-    Server &Delete(const char *pattern, HandlerWithContentReader handler);
-    Server &Options(const char *pattern, Handler handler);
+std::ostream &operator<<(std::ostream &os, const Error &obj);
 
-    bool set_base_dir(const char *dir, const char *mount_point = nullptr);
-    bool set_mount_point(const char *mount_point, const char *dir);
-    bool remove_mount_point(const char *mount_point);
-    void set_file_extension_and_mimetype_mapping(const char *ext, const char *mime);
-    void set_file_request_handler(Handler handler);
+class Result {
+public:
+  Result(std::unique_ptr<Response> &&res, Error err,
+         Headers &&request_headers = Headers{})
+      : res_(std::move(res)), err_(err),
+        request_headers_(std::move(request_headers)) {}
+  // Response
+  operator bool() const { return res_ != nullptr; }
+  bool operator==(std::nullptr_t) const { return res_ == nullptr; }
+  bool operator!=(std::nullptr_t) const { return res_ != nullptr; }
+  const Response &value() const { return *res_; }
+  Response &value() { return *res_; }
+  const Response &operator*() const { return *res_; }
+  Response &operator*() { return *res_; }
+  const Response *operator->() const { return res_.get(); }
+  Response *operator->() { return res_.get(); }
+
+  // Error
+  Error error() const { return err_; }
+
+  // Request Headers
+  bool has_request_header(const char *key) const;
+  std::string get_request_header_value(const char *key, size_t id = 0) const;
+  template <typename T>
+  T get_request_header_value(const char *key, size_t id = 0) const;
+  size_t get_request_header_value_count(const char *key) const;
+
+private:
+  std::unique_ptr<Response> res_;
+  Error err_;
+  Headers request_headers_;
+};
 
-    void set_error_handler(Handler handler);
-    void set_expect_100_continue_handler(Expect100ContinueHandler handler);
-    void set_logger(Logger logger);
+class ClientImpl {
+public:
+  explicit ClientImpl(const std::string &host);
 
-    void set_tcp_nodelay(bool on);
-    void set_socket_options(SocketOptions socket_options);
+  explicit ClientImpl(const std::string &host, int port);
 
-    void set_keep_alive_max_count(size_t count);
-    void set_keep_alive_timeout(time_t sec);
-    void set_read_timeout(time_t sec, time_t usec = 0);
-    void set_write_timeout(time_t sec, time_t usec = 0);
-    void set_idle_interval(time_t sec, time_t usec = 0);
+  explicit ClientImpl(const std::string &host, int port,
+                      const std::string &client_cert_path,
+                      const std::string &client_key_path);
+
+  virtual ~ClientImpl();
+
+  virtual bool is_valid() const;
+
+  Result Get(const char *path);
+  Result Get(const char *path, const Headers &headers);
+  Result Get(const char *path, Progress progress);
+  Result Get(const char *path, const Headers &headers, Progress progress);
+  Result Get(const char *path, ContentReceiver content_receiver);
+  Result Get(const char *path, const Headers &headers,
+             ContentReceiver content_receiver);
+  Result Get(const char *path, ContentReceiver content_receiver,
+             Progress progress);
+  Result Get(const char *path, const Headers &headers,
+             ContentReceiver content_receiver, Progress progress);
+  Result Get(const char *path, ResponseHandler response_handler,
+             ContentReceiver content_receiver);
+  Result Get(const char *path, const Headers &headers,
+             ResponseHandler response_handler,
+             ContentReceiver content_receiver);
+  Result Get(const char *path, ResponseHandler response_handler,
+             ContentReceiver content_receiver, Progress progress);
+  Result Get(const char *path, const Headers &headers,
+             ResponseHandler response_handler, ContentReceiver content_receiver,
+             Progress progress);
+
+  Result Get(const char *path, const Params &params, const Headers &headers,
+             Progress progress = nullptr);
+  Result Get(const char *path, const Params &params, const Headers &headers,
+             ContentReceiver content_receiver, Progress progress = nullptr);
+  Result Get(const char *path, const Params &params, const Headers &headers,
+             ResponseHandler response_handler, ContentReceiver content_receiver,
+             Progress progress = nullptr);
+
+  Result Head(const char *path);
+  Result Head(const char *path, const Headers &headers);
+
+  Result Post(const char *path);
+  Result Post(const char *path, const char *body, size_t content_length,
+              const char *content_type);
+  Result Post(const char *path, const Headers &headers, const char *body,
+              size_t content_length, const char *content_type);
+  Result Post(const char *path, const std::string &body,
+              const char *content_type);
+  Result Post(const char *path, const Headers &headers, const std::string &body,
+              const char *content_type);
+  Result Post(const char *path, size_t content_length,
+              ContentProvider content_provider, const char *content_type);
+  Result Post(const char *path, ContentProviderWithoutLength content_provider,
+              const char *content_type);
+  Result Post(const char *path, const Headers &headers, size_t content_length,
+              ContentProvider content_provider, const char *content_type);
+  Result Post(const char *path, const Headers &headers,
+              ContentProviderWithoutLength content_provider,
+              const char *content_type);
+  Result Post(const char *path, const Params &params);
+  Result Post(const char *path, const Headers &headers, const Params &params);
+  Result Post(const char *path, const MultipartFormDataItems &items);
+  Result Post(const char *path, const Headers &headers,
+              const MultipartFormDataItems &items);
+  Result Post(const char *path, const Headers &headers,
+              const MultipartFormDataItems &items, const std::string &boundary);
+
+  Result Put(const char *path);
+  Result Put(const char *path, const char *body, size_t content_length,
+             const char *content_type);
+  Result Put(const char *path, const Headers &headers, const char *body,
+             size_t content_length, const char *content_type);
+  Result Put(const char *path, const std::string &body,
+             const char *content_type);
+  Result Put(const char *path, const Headers &headers, const std::string &body,
+             const char *content_type);
+  Result Put(const char *path, size_t content_length,
+             ContentProvider content_provider, const char *content_type);
+  Result Put(const char *path, ContentProviderWithoutLength content_provider,
+             const char *content_type);
+  Result Put(const char *path, const Headers &headers, size_t content_length,
+             ContentProvider content_provider, const char *content_type);
+  Result Put(const char *path, const Headers &headers,
+             ContentProviderWithoutLength content_provider,
+             const char *content_type);
+  Result Put(const char *path, const Params &params);
+  Result Put(const char *path, const Headers &headers, const Params &params);
+
+  Result Patch(const char *path);
+  Result Patch(const char *path, const char *body, size_t content_length,
+               const char *content_type);
+  Result Patch(const char *path, const Headers &headers, const char *body,
+               size_t content_length, const char *content_type);
+  Result Patch(const char *path, const std::string &body,
+               const char *content_type);
+  Result Patch(const char *path, const Headers &headers,
+               const std::string &body, const char *content_type);
+  Result Patch(const char *path, size_t content_length,
+               ContentProvider content_provider, const char *content_type);
+  Result Patch(const char *path, ContentProviderWithoutLength content_provider,
+               const char *content_type);
+  Result Patch(const char *path, const Headers &headers, size_t content_length,
+               ContentProvider content_provider, const char *content_type);
+  Result Patch(const char *path, const Headers &headers,
+               ContentProviderWithoutLength content_provider,
+               const char *content_type);
 
-    void set_payload_max_length(size_t length);
+  Result Delete(const char *path);
+  Result Delete(const char *path, const Headers &headers);
+  Result Delete(const char *path, const char *body, size_t content_length,
+                const char *content_type);
+  Result Delete(const char *path, const Headers &headers, const char *body,
+                size_t content_length, const char *content_type);
+  Result Delete(const char *path, const std::string &body,
+                const char *content_type);
+  Result Delete(const char *path, const Headers &headers,
+                const std::string &body, const char *content_type);
 
-    bool bind_to_port(const char *host, int port, int socket_flags = 0);
-    int bind_to_any_port(const char *host, int socket_flags = 0);
-    bool listen_after_bind();
+  Result Options(const char *path);
+  Result Options(const char *path, const Headers &headers);
 
-    bool listen(const char *host, int port, int socket_flags = 0);
+  bool send(Request &req, Response &res, Error &error);
+  Result send(const Request &req);
 
-    bool is_running() const;
-    void stop();
+  size_t is_socket_open() const;
 
-    std::function<TaskQueue *(void)> new_task_queue;
+  void stop();
 
-  protected:
-    bool process_request(Stream &strm,
-                         bool close_connection,
-                         bool &connection_closed,
-                         const std::function<void(Request &)> &setup_request);
-
-    std::atomic<socket_t> svr_sock_;
-    size_t keep_alive_max_count_   = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
-    time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
-    time_t read_timeout_sec_       = CPPHTTPLIB_READ_TIMEOUT_SECOND;
-    time_t read_timeout_usec_      = CPPHTTPLIB_READ_TIMEOUT_USECOND;
-    time_t write_timeout_sec_      = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
-    time_t write_timeout_usec_     = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
-    time_t idle_interval_sec_      = CPPHTTPLIB_IDLE_INTERVAL_SECOND;
-    time_t idle_interval_usec_     = CPPHTTPLIB_IDLE_INTERVAL_USECOND;
-    size_t payload_max_length_     = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
+  void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
 
-  private:
-    using Handlers                 = std::vector<std::pair<std::regex, Handler>>;
-    using HandlersForContentReader = std::vector<std::pair<std::regex, HandlerWithContentReader>>;
-
-    socket_t create_server_socket(const char *host,
-                                  int port,
-                                  int socket_flags,
-                                  SocketOptions socket_options) const;
-    int bind_internal(const char *host, int port, int socket_flags);
-    bool listen_internal();
-
-    bool routing(Request &req, Response &res, Stream &strm);
-    bool handle_file_request(Request &req, Response &res, bool head = false);
-    bool dispatch_request(Request &req, Response &res, const Handlers &handlers);
-    bool dispatch_request_for_content_reader(Request &req,
-                                             Response &res,
-                                             ContentReader content_reader,
-                                             const HandlersForContentReader &handlers);
-
-    bool parse_request_line(const char *s, Request &req);
-    bool write_response(Stream &strm, bool close_connection, const Request &req, Response &res);
-    bool write_content_with_provider(Stream &strm,
-                                     const Request &req,
-                                     Response &res,
-                                     const std::string &boundary,
-                                     const std::string &content_type);
-    bool read_content(Stream &strm, Request &req, Response &res);
-    bool read_content_with_content_receiver(Stream &strm,
-                                            Request &req,
-                                            Response &res,
-                                            ContentReceiver receiver,
-                                            MultipartContentHeader multipart_header,
-                                            ContentReceiver multipart_receiver);
-    bool read_content_core(Stream &strm,
-                           Request &req,
-                           Response &res,
-                           ContentReceiver receiver,
-                           MultipartContentHeader mulitpart_header,
-                           ContentReceiver multipart_receiver);
-
-    virtual bool process_and_close_socket(socket_t sock);
-
-    std::atomic<bool> is_running_;
-    std::vector<std::pair<std::string, std::string>> base_dirs_;
-    std::map<std::string, std::string> file_extension_and_mimetype_map_;
-    Handler file_request_handler_;
-    Handlers get_handlers_;
-    Handlers post_handlers_;
-    HandlersForContentReader post_handlers_for_content_reader_;
-    Handlers put_handlers_;
-    HandlersForContentReader put_handlers_for_content_reader_;
-    Handlers patch_handlers_;
-    HandlersForContentReader patch_handlers_for_content_reader_;
-    Handlers delete_handlers_;
-    HandlersForContentReader delete_handlers_for_content_reader_;
-    Handlers options_handlers_;
-    Handler error_handler_;
-    Logger logger_;
-    Expect100ContinueHandler expect_100_continue_handler_;
-
-    bool tcp_nodelay_             = CPPHTTPLIB_TCP_NODELAY;
-    SocketOptions socket_options_ = default_socket_options;
-};
+  void set_default_headers(Headers headers);
 
-enum Error {
-    Success = 0,
-    Unknown,
-    Connection,
-    BindIPAddress,
-    Read,
-    Write,
-    ExceedRedirectCount,
-    Canceled,
-    SSLConnection,
-    SSLLoadingCerts,
-    SSLServerVerification
-};
+  void set_address_family(int family);
+  void set_tcp_nodelay(bool on);
+  void set_socket_options(SocketOptions socket_options);
 
-class Result {
-  public:
-    Result(const std::shared_ptr<Response> &res, Error err) : res_(res), err_(err) {}
-    operator bool() const { return res_ != nullptr; }
-    bool operator==(std::nullptr_t) const { return res_ == nullptr; }
-    bool operator!=(std::nullptr_t) const { return res_ != nullptr; }
-    const Response &value() const { return *res_; }
-    const Response &operator*() const { return *res_; }
-    const Response *operator->() const { return res_.get(); }
-    Error error() const { return err_; }
+  void set_connection_timeout(time_t sec, time_t usec = 0);
+  template <class Rep, class Period>
+  void
+  set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
 
-  private:
-    std::shared_ptr<Response> res_;
-    Error err_;
-};
+  void set_read_timeout(time_t sec, time_t usec = 0);
+  template <class Rep, class Period>
+  void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
 
-struct ClientOptions {
-    Headers headers;
-    Progress progress;
-    ResponseHandler response_handler;
-    ContentReceiver content_receiver;
-};
+  void set_write_timeout(time_t sec, time_t usec = 0);
+  template <class Rep, class Period>
+  void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
 
-class ClientImpl {
-  public:
-    explicit ClientImpl(const std::string &host);
-
-    explicit ClientImpl(const std::string &host, int port);
-
-    explicit ClientImpl(const std::string &host,
-                        int port,
-                        const std::string &client_cert_path,
-                        const std::string &client_key_path);
-
-    virtual ~ClientImpl();
-
-    virtual bool is_valid() const;
-
-    Result Get2(std::string_view path, ClientOptions options);
-
-    Result Get(const char *path);
-    Result Get(const char *path, const Headers &headers);
-    Result Get(const char *path, Progress progress);
-    Result Get(const char *path, const Headers &headers, Progress progress);
-    Result Get(const char *path, ContentReceiver content_receiver);
-    Result Get(const char *path, const Headers &headers, ContentReceiver content_receiver);
-    Result Get(const char *path, ContentReceiver content_receiver, Progress progress);
-    Result Get(const char *path,
-               const Headers &headers,
-               ContentReceiver content_receiver,
-               Progress progress);
-    Result Get(const char *path,
-               ResponseHandler response_handler,
-               ContentReceiver content_receiver);
-    Result Get(const char *path,
-               const Headers &headers,
-               ResponseHandler response_handler,
-               ContentReceiver content_receiver);
-    Result Get(const char *path,
-               ResponseHandler response_handler,
-               ContentReceiver content_receiver,
-               Progress progress);
-    Result Get(const char *path,
-               const Headers &headers,
-               ResponseHandler response_handler,
-               ContentReceiver content_receiver,
-               Progress progress);
-
-    Result Head(const char *path);
-    Result Head(const char *path, const Headers &headers);
-
-    Result Post(const char *path);
-    Result Post(const char *path, const std::string &body, const char *content_type);
-    Result Post(const char *path,
-                const Headers &headers,
-                const std::string &body,
-                const char *content_type);
-    Result Post(const char *path,
-                size_t content_length,
-                ContentProvider content_provider,
-                const char *content_type);
-    Result Post(const char *path,
-                const Headers &headers,
-                size_t content_length,
-                ContentProvider content_provider,
-                const char *content_type);
-    Result Post(const char *path, const Params &params);
-    Result Post(const char *path, const Headers &headers, const Params &params);
-    Result Post(const char *path, const MultipartFormDataItems &items);
-    Result Post(const char *path, const Headers &headers, const MultipartFormDataItems &items);
-
-    Result Put(const char *path);
-    Result Put(const char *path, const std::string &body, const char *content_type);
-    Result Put(const char *path,
-               const Headers &headers,
-               const std::string &body,
-               const char *content_type);
-    Result Put(const char *path,
-               size_t content_length,
-               ContentProvider content_provider,
-               const char *content_type);
-    Result Put(const char *path,
-               const Headers &headers,
-               size_t content_length,
-               ContentProvider content_provider,
-               const char *content_type);
-    Result Put(const char *path, const Params &params);
-    Result Put(const char *path, const Headers &headers, const Params &params);
-
-    Result Patch(const char *path, const std::string &body, const char *content_type);
-    Result Patch(const char *path,
-                 const Headers &headers,
-                 const std::string &body,
-                 const char *content_type);
-    Result Patch(const char *path,
-                 size_t content_length,
-                 ContentProvider content_provider,
-                 const char *content_type);
-    Result Patch(const char *path,
-                 const Headers &headers,
-                 size_t content_length,
-                 ContentProvider content_provider,
-                 const char *content_type);
-
-    Result Delete(const char *path);
-    Result Delete(const char *path, const std::string &body, const char *content_type);
-    Result Delete(const char *path, const Headers &headers);
-    Result Delete(const char *path,
-                  const Headers &headers,
-                  const std::string &body,
-                  const char *content_type);
-
-    Result Options(const char *path);
-    Result Options(const char *path, const Headers &headers);
-
-    bool send(const Request &req, Response &res);
-
-    size_t is_socket_open() const;
-
-    void stop();
-
-    void set_default_headers(Headers headers);
-
-    void set_tcp_nodelay(bool on);
-    void set_socket_options(SocketOptions socket_options);
-
-    void set_connection_timeout(time_t sec, time_t usec = 0);
-    void set_read_timeout(time_t sec, time_t usec = 0);
-    void set_write_timeout(time_t sec, time_t usec = 0);
-
-    void set_basic_auth(const char *username, const char *password);
-    void set_bearer_token_auth(const char *token);
+  void set_basic_auth(const char *username, const char *password);
+  void set_bearer_token_auth(const char *token);
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-    void set_digest_auth(const char *username, const char *password);
+  void set_digest_auth(const char *username, const char *password);
 #endif
 
-    void set_keep_alive(bool on);
-    void set_follow_location(bool on);
+  void set_keep_alive(bool on);
+  void set_follow_location(bool on);
 
-    void set_compress(bool on);
+  void set_url_encode(bool on);
 
-    void set_decompress(bool on);
+  void set_compress(bool on);
 
-    void set_interface(const char *intf);
+  void set_decompress(bool on);
 
-    void set_proxy(const char *host, int port);
-    void set_proxy_basic_auth(const char *username, const char *password);
-    void set_proxy_bearer_token_auth(const char *token);
+  void set_interface(const char *intf);
+
+  void set_proxy(const char *host, int port);
+  void set_proxy_basic_auth(const char *username, const char *password);
+  void set_proxy_bearer_token_auth(const char *token);
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-    void set_proxy_digest_auth(const char *username, const char *password);
+  void set_proxy_digest_auth(const char *username, const char *password);
 #endif
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-    void enable_server_certificate_verification(bool enabled);
+  void set_ca_cert_path(const char *ca_cert_file_path,
+                        const char *ca_cert_dir_path = nullptr);
+  void set_ca_cert_store(X509_STORE *ca_cert_store);
 #endif
 
-    void set_logger(Logger logger);
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+  void enable_server_certificate_verification(bool enabled);
+#endif
 
-  protected:
-    struct Socket {
-        socket_t sock = INVALID_SOCKET;
+  void set_logger(Logger logger);
+
+protected:
+  struct Socket {
+    socket_t sock = INVALID_SOCKET;
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-        SSL *ssl = nullptr;
+    SSL *ssl = nullptr;
 #endif
 
-        bool is_open() const { return sock != INVALID_SOCKET; }
-    };
+    bool is_open() const { return sock != INVALID_SOCKET; }
+  };
+
+  Result send_(Request &&req);
 
-    virtual bool create_and_connect_socket(Socket &socket);
-    virtual void close_socket(Socket &socket, bool process_socket_ret);
+  virtual bool create_and_connect_socket(Socket &socket, Error &error);
 
-    bool process_request(Stream &strm, const Request &req, Response &res, bool close_connection);
+  // All of:
+  //   shutdown_ssl
+  //   shutdown_socket
+  //   close_socket
+  // should ONLY be called when socket_mutex_ is locked.
+  // Also, shutdown_ssl and close_socket should also NOT be called concurrently
+  // with a DIFFERENT thread sending requests using that socket.
+  virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully);
+  void shutdown_socket(Socket &socket);
+  void close_socket(Socket &socket);
 
-    Error get_last_error() const;
+  bool process_request(Stream &strm, Request &req, Response &res,
+                       bool close_connection, Error &error);
 
-    void copy_settings(const ClientImpl &rhs);
+  bool write_content_with_provider(Stream &strm, const Request &req,
+                                   Error &error);
 
-    // Error state
-    mutable Error error_ = Error::Success;
+  void copy_settings(const ClientImpl &rhs);
 
-    // Socket endoint information
-    const std::string host_;
-    const int port_;
-    const std::string host_and_port_;
+  // Socket endoint information
+  const std::string host_;
+  const int port_;
+  const std::string host_and_port_;
 
-    // Current open socket
-    Socket socket_;
-    mutable std::mutex socket_mutex_;
-    std::recursive_mutex request_mutex_;
+  // Current open socket
+  Socket socket_;
+  mutable std::mutex socket_mutex_;
+  std::recursive_mutex request_mutex_;
 
-    // Default headers
-    Headers default_headers_;
+  // These are all protected under socket_mutex
+  size_t socket_requests_in_flight_ = 0;
+  std::thread::id socket_requests_are_from_thread_ = std::thread::id();
+  bool socket_should_be_closed_when_request_is_done_ = false;
 
-    // Settings
-    std::string client_cert_path_;
-    std::string client_key_path_;
+  // Hostname-IP map
+  std::map<std::string, std::string> addr_map_;
 
-    time_t connection_timeout_sec_  = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
-    time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
-    time_t read_timeout_sec_        = CPPHTTPLIB_READ_TIMEOUT_SECOND;
-    time_t read_timeout_usec_       = CPPHTTPLIB_READ_TIMEOUT_USECOND;
-    time_t write_timeout_sec_       = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
-    time_t write_timeout_usec_      = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
+  // Default headers
+  Headers default_headers_;
 
-    std::string basic_auth_username_;
-    std::string basic_auth_password_;
-    std::string bearer_token_auth_token_;
+  // Settings
+  std::string client_cert_path_;
+  std::string client_key_path_;
+
+  time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
+  time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
+  time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
+  time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
+  time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
+  time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
+
+  std::string basic_auth_username_;
+  std::string basic_auth_password_;
+  std::string bearer_token_auth_token_;
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-    std::string digest_auth_username_;
-    std::string digest_auth_password_;
+  std::string digest_auth_username_;
+  std::string digest_auth_password_;
 #endif
 
-    bool keep_alive_      = false;
-    bool follow_location_ = false;
+  bool keep_alive_ = false;
+  bool follow_location_ = false;
 
-    bool tcp_nodelay_             = CPPHTTPLIB_TCP_NODELAY;
-    SocketOptions socket_options_ = nullptr;
+  bool url_encode_ = true;
 
-    bool compress_   = false;
-    bool decompress_ = true;
+  int address_family_ = AF_UNSPEC;
+  bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
+  SocketOptions socket_options_ = nullptr;
 
-    std::string interface_;
+  bool compress_ = false;
+  bool decompress_ = true;
 
-    std::string proxy_host_;
-    int proxy_port_ = -1;
+  std::string interface_;
 
-    std::string proxy_basic_auth_username_;
-    std::string proxy_basic_auth_password_;
-    std::string proxy_bearer_token_auth_token_;
+  std::string proxy_host_;
+  int proxy_port_ = -1;
+
+  std::string proxy_basic_auth_username_;
+  std::string proxy_basic_auth_password_;
+  std::string proxy_bearer_token_auth_token_;
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-    std::string proxy_digest_auth_username_;
-    std::string proxy_digest_auth_password_;
+  std::string proxy_digest_auth_username_;
+  std::string proxy_digest_auth_password_;
 #endif
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-    bool server_certificate_verification_ = true;
+  std::string ca_cert_file_path_;
+  std::string ca_cert_dir_path_;
+
+  X509_STORE *ca_cert_store_ = nullptr;
 #endif
 
-    Logger logger_;
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+  bool server_certificate_verification_ = true;
+#endif
 
-  private:
-    socket_t create_client_socket() const;
-    bool read_response_line(Stream &strm, Response &res);
-    bool write_request(Stream &strm, const Request &req, bool close_connection);
-    bool redirect(const Request &req, Response &res);
-    bool handle_request(Stream &strm, const Request &req, Response &res, bool close_connection);
-    void stop_core();
-    std::shared_ptr<Response> send_with_content_provider(const char *method,
-                                                         const char *path,
-                                                         const Headers &headers,
-                                                         const std::string &body,
-                                                         size_t content_length,
-                                                         ContentProvider content_provider,
-                                                         const char *content_type);
-
-    virtual bool process_socket(Socket &socket, std::function<bool(Stream &strm)> callback);
-    virtual bool is_ssl() const;
+  Logger logger_;
+
+private:
+  socket_t create_client_socket(Error &error) const;
+  bool read_response_line(Stream &strm, const Request &req, Response &res);
+  bool write_request(Stream &strm, Request &req, bool close_connection,
+                     Error &error);
+  bool redirect(Request &req, Response &res, Error &error);
+  bool handle_request(Stream &strm, Request &req, Response &res,
+                      bool close_connection, Error &error);
+  std::unique_ptr<Response> send_with_content_provider(
+      Request &req,
+      // const char *method, const char *path, const Headers &headers,
+      const char *body, size_t content_length, ContentProvider content_provider,
+      ContentProviderWithoutLength content_provider_without_length,
+      const char *content_type, Error &error);
+  Result send_with_content_provider(
+      const char *method, const char *path, const Headers &headers,
+      const char *body, size_t content_length, ContentProvider content_provider,
+      ContentProviderWithoutLength content_provider_without_length,
+      const char *content_type);
+
+  std::string adjust_host_string(const std::string &host) const;
+
+  virtual bool process_socket(const Socket &socket,
+                              std::function<bool(Stream &strm)> callback);
+  virtual bool is_ssl() const;
 };
 
 class Client {
-  public:
-    // Universal interface
-    explicit Client(const char *scheme_host_port);
-
-    explicit Client(const char *scheme_host_port,
-                    const std::string &client_cert_path,
-                    const std::string &client_key_path);
-
-    // HTTP only interface
-    explicit Client(const std::string &host, int port);
-
-    explicit Client(const std::string &host,
-                    int port,
-                    const std::string &client_cert_path,
-                    const std::string &client_key_path);
-
-    ~Client();
-
-    bool is_valid() const;
-
-    Result Get2(std::string_view path, ClientOptions options);
-
-    Result Get(const char *path);
-    Result Get(const char *path, const Headers &headers);
-    Result Get(const char *path, Progress progress);
-    Result Get(const char *path, const Headers &headers, Progress progress);
-    Result Get(const char *path, ContentReceiver content_receiver);
-    Result Get(const char *path, const Headers &headers, ContentReceiver content_receiver);
-    Result Get(const char *path, ContentReceiver content_receiver, Progress progress);
-    Result Get(const char *path,
-               const Headers &headers,
-               ContentReceiver content_receiver,
-               Progress progress);
-    Result Get(const char *path,
-               ResponseHandler response_handler,
-               ContentReceiver content_receiver);
-    Result Get(const char *path,
-               const Headers &headers,
-               ResponseHandler response_handler,
-               ContentReceiver content_receiver);
-    Result Get(const char *path,
-               const Headers &headers,
-               ResponseHandler response_handler,
-               ContentReceiver content_receiver,
-               Progress progress);
-    Result Get(const char *path,
-               ResponseHandler response_handler,
-               ContentReceiver content_receiver,
-               Progress progress);
-
-    Result Head(const char *path);
-    Result Head(const char *path, const Headers &headers);
-
-    Result Post(const char *path);
-    Result Post(const char *path, const std::string &body, const char *content_type);
-    Result Post(const char *path,
-                const Headers &headers,
-                const std::string &body,
-                const char *content_type);
-    Result Post(const char *path,
-                size_t content_length,
-                ContentProvider content_provider,
-                const char *content_type);
-    Result Post(const char *path,
-                const Headers &headers,
-                size_t content_length,
-                ContentProvider content_provider,
-                const char *content_type);
-    Result Post(const char *path, const Params &params);
-    Result Post(const char *path, const Headers &headers, const Params &params);
-    Result Post(const char *path, const MultipartFormDataItems &items);
-    Result Post(const char *path, const Headers &headers, const MultipartFormDataItems &items);
-    Result Put(const char *path);
-    Result Put(const char *path, const std::string &body, const char *content_type);
-    Result Put(const char *path,
-               const Headers &headers,
-               const std::string &body,
+public:
+  // Universal interface
+  explicit Client(const std::string &scheme_host_port);
+
+  explicit Client(const std::string &scheme_host_port,
+                  const std::string &client_cert_path,
+                  const std::string &client_key_path);
+
+  // HTTP only interface
+  explicit Client(const std::string &host, int port);
+
+  explicit Client(const std::string &host, int port,
+                  const std::string &client_cert_path,
+                  const std::string &client_key_path);
+
+  Client(Client &&) = default;
+
+  ~Client();
+
+  bool is_valid() const;
+
+  Result Get(const char *path);
+  Result Get(const char *path, const Headers &headers);
+  Result Get(const char *path, Progress progress);
+  Result Get(const char *path, const Headers &headers, Progress progress);
+  Result Get(const char *path, ContentReceiver content_receiver);
+  Result Get(const char *path, const Headers &headers,
+             ContentReceiver content_receiver);
+  Result Get(const char *path, ContentReceiver content_receiver,
+             Progress progress);
+  Result Get(const char *path, const Headers &headers,
+             ContentReceiver content_receiver, Progress progress);
+  Result Get(const char *path, ResponseHandler response_handler,
+             ContentReceiver content_receiver);
+  Result Get(const char *path, const Headers &headers,
+             ResponseHandler response_handler,
+             ContentReceiver content_receiver);
+  Result Get(const char *path, const Headers &headers,
+             ResponseHandler response_handler, ContentReceiver content_receiver,
+             Progress progress);
+  Result Get(const char *path, ResponseHandler response_handler,
+             ContentReceiver content_receiver, Progress progress);
+
+  Result Get(const char *path, const Params &params, const Headers &headers,
+             Progress progress = nullptr);
+  Result Get(const char *path, const Params &params, const Headers &headers,
+             ContentReceiver content_receiver, Progress progress = nullptr);
+  Result Get(const char *path, const Params &params, const Headers &headers,
+             ResponseHandler response_handler, ContentReceiver content_receiver,
+             Progress progress = nullptr);
+
+  Result Head(const char *path);
+  Result Head(const char *path, const Headers &headers);
+
+  Result Post(const char *path);
+  Result Post(const char *path, const char *body, size_t content_length,
+              const char *content_type);
+  Result Post(const char *path, const Headers &headers, const char *body,
+              size_t content_length, const char *content_type);
+  Result Post(const char *path, const std::string &body,
+              const char *content_type);
+  Result Post(const char *path, const Headers &headers, const std::string &body,
+              const char *content_type);
+  Result Post(const char *path, size_t content_length,
+              ContentProvider content_provider, const char *content_type);
+  Result Post(const char *path, ContentProviderWithoutLength content_provider,
+              const char *content_type);
+  Result Post(const char *path, const Headers &headers, size_t content_length,
+              ContentProvider content_provider, const char *content_type);
+  Result Post(const char *path, const Headers &headers,
+              ContentProviderWithoutLength content_provider,
+              const char *content_type);
+  Result Post(const char *path, const Params &params);
+  Result Post(const char *path, const Headers &headers, const Params &params);
+  Result Post(const char *path, const MultipartFormDataItems &items);
+  Result Post(const char *path, const Headers &headers,
+              const MultipartFormDataItems &items);
+  Result Post(const char *path, const Headers &headers,
+              const MultipartFormDataItems &items, const std::string &boundary);
+  Result Put(const char *path);
+  Result Put(const char *path, const char *body, size_t content_length,
+             const char *content_type);
+  Result Put(const char *path, const Headers &headers, const char *body,
+             size_t content_length, const char *content_type);
+  Result Put(const char *path, const std::string &body,
+             const char *content_type);
+  Result Put(const char *path, const Headers &headers, const std::string &body,
+             const char *content_type);
+  Result Put(const char *path, size_t content_length,
+             ContentProvider content_provider, const char *content_type);
+  Result Put(const char *path, ContentProviderWithoutLength content_provider,
+             const char *content_type);
+  Result Put(const char *path, const Headers &headers, size_t content_length,
+             ContentProvider content_provider, const char *content_type);
+  Result Put(const char *path, const Headers &headers,
+             ContentProviderWithoutLength content_provider,
+             const char *content_type);
+  Result Put(const char *path, const Params &params);
+  Result Put(const char *path, const Headers &headers, const Params &params);
+  Result Patch(const char *path);
+  Result Patch(const char *path, const char *body, size_t content_length,
+               const char *content_type);
+  Result Patch(const char *path, const Headers &headers, const char *body,
+               size_t content_length, const char *content_type);
+  Result Patch(const char *path, const std::string &body,
                const char *content_type);
-    Result Put(const char *path,
-               size_t content_length,
-               ContentProvider content_provider,
+  Result Patch(const char *path, const Headers &headers,
+               const std::string &body, const char *content_type);
+  Result Patch(const char *path, size_t content_length,
+               ContentProvider content_provider, const char *content_type);
+  Result Patch(const char *path, ContentProviderWithoutLength content_provider,
                const char *content_type);
-    Result Put(const char *path,
-               const Headers &headers,
-               size_t content_length,
-               ContentProvider content_provider,
+  Result Patch(const char *path, const Headers &headers, size_t content_length,
+               ContentProvider content_provider, const char *content_type);
+  Result Patch(const char *path, const Headers &headers,
+               ContentProviderWithoutLength content_provider,
                const char *content_type);
-    Result Put(const char *path, const Params &params);
-    Result Put(const char *path, const Headers &headers, const Params &params);
-    Result Patch(const char *path, const std::string &body, const char *content_type);
-    Result Patch(const char *path,
-                 const Headers &headers,
-                 const std::string &body,
-                 const char *content_type);
-    Result Patch(const char *path,
-                 size_t content_length,
-                 ContentProvider content_provider,
-                 const char *content_type);
-    Result Patch(const char *path,
-                 const Headers &headers,
-                 size_t content_length,
-                 ContentProvider content_provider,
-                 const char *content_type);
-
-    Result Delete(const char *path);
-    Result Delete(const char *path, const std::string &body, const char *content_type);
-    Result Delete(const char *path, const Headers &headers);
-    Result Delete(const char *path,
-                  const Headers &headers,
-                  const std::string &body,
-                  const char *content_type);
-
-    Result Options(const char *path);
-    Result Options(const char *path, const Headers &headers);
-
-    bool send(const Request &req, Response &res);
-
-    size_t is_socket_open() const;
-
-    void stop();
-
-    void set_default_headers(Headers headers);
-
-    void set_tcp_nodelay(bool on);
-    void set_socket_options(SocketOptions socket_options);
-
-    void set_connection_timeout(time_t sec, time_t usec = 0);
-    void set_read_timeout(time_t sec, time_t usec = 0);
-    void set_write_timeout(time_t sec, time_t usec = 0);
-
-    void set_basic_auth(const char *username, const char *password);
-    void set_bearer_token_auth(const char *token);
+
+  Result Delete(const char *path);
+  Result Delete(const char *path, const Headers &headers);
+  Result Delete(const char *path, const char *body, size_t content_length,
+                const char *content_type);
+  Result Delete(const char *path, const Headers &headers, const char *body,
+                size_t content_length, const char *content_type);
+  Result Delete(const char *path, const std::string &body,
+                const char *content_type);
+  Result Delete(const char *path, const Headers &headers,
+                const std::string &body, const char *content_type);
+
+  Result Options(const char *path);
+  Result Options(const char *path, const Headers &headers);
+
+  bool send(Request &req, Response &res, Error &error);
+  Result send(const Request &req);
+
+  size_t is_socket_open() const;
+
+  void stop();
+
+  void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
+
+  void set_default_headers(Headers headers);
+
+  void set_address_family(int family);
+  void set_tcp_nodelay(bool on);
+  void set_socket_options(SocketOptions socket_options);
+
+  void set_connection_timeout(time_t sec, time_t usec = 0);
+  template <class Rep, class Period>
+  void
+  set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
+
+  void set_read_timeout(time_t sec, time_t usec = 0);
+  template <class Rep, class Period>
+  void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
+
+  void set_write_timeout(time_t sec, time_t usec = 0);
+  template <class Rep, class Period>
+  void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
+
+  void set_basic_auth(const char *username, const char *password);
+  void set_bearer_token_auth(const char *token);
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-    void set_digest_auth(const char *username, const char *password);
+  void set_digest_auth(const char *username, const char *password);
 #endif
 
-    void set_keep_alive(bool on);
-    void set_follow_location(bool on);
+  void set_keep_alive(bool on);
+  void set_follow_location(bool on);
 
-    void set_compress(bool on);
+  void set_url_encode(bool on);
 
-    void set_decompress(bool on);
+  void set_compress(bool on);
 
-    void set_interface(const char *intf);
+  void set_decompress(bool on);
 
-    void set_proxy(const char *host, int port);
-    void set_proxy_basic_auth(const char *username, const char *password);
-    void set_proxy_bearer_token_auth(const char *token);
+  void set_interface(const char *intf);
+
+  void set_proxy(const char *host, int port);
+  void set_proxy_basic_auth(const char *username, const char *password);
+  void set_proxy_bearer_token_auth(const char *token);
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-    void set_proxy_digest_auth(const char *username, const char *password);
+  void set_proxy_digest_auth(const char *username, const char *password);
 #endif
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-    void enable_server_certificate_verification(bool enabled);
+  void enable_server_certificate_verification(bool enabled);
 #endif
 
-    void set_logger(Logger logger);
+  void set_logger(Logger logger);
 
-    // SSL
+  // SSL
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-    void set_ca_cert_path(const char *ca_cert_file_path, const char *ca_cert_dir_path = nullptr);
+  void set_ca_cert_path(const char *ca_cert_file_path,
+                        const char *ca_cert_dir_path = nullptr);
 
-    void set_ca_cert_store(X509_STORE *ca_cert_store);
+  void set_ca_cert_store(X509_STORE *ca_cert_store);
 
-    long get_openssl_verify_result() const;
+  long get_openssl_verify_result() const;
 
-    SSL_CTX *ssl_context() const;
+  SSL_CTX *ssl_context() const;
 #endif
 
-  private:
-    std::shared_ptr<ClientImpl> cli_;
+private:
+  std::unique_ptr<ClientImpl> cli_;
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-    bool is_ssl_ = false;
+  bool is_ssl_ = false;
 #endif
-};  // namespace httplib
+};
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
 class SSLServer : public Server {
-  public:
-    SSLServer(const char *cert_path,
-              const char *private_key_path,
-              const char *client_ca_cert_file_path = nullptr,
-              const char *client_ca_cert_dir_path  = nullptr);
+public:
+  SSLServer(const char *cert_path, const char *private_key_path,
+            const char *client_ca_cert_file_path = nullptr,
+            const char *client_ca_cert_dir_path = nullptr,
+            const char *private_key_password = nullptr);
 
-    SSLServer(X509 *cert, EVP_PKEY *private_key, X509_STORE *client_ca_cert_store = nullptr);
+  SSLServer(X509 *cert, EVP_PKEY *private_key,
+            X509_STORE *client_ca_cert_store = nullptr);
 
-    ~SSLServer() override;
+  SSLServer(
+      const std::function<bool(SSL_CTX &ssl_ctx)> &setup_ssl_ctx_callback);
 
-    bool is_valid() const override;
+  ~SSLServer() override;
 
-  private:
-    bool process_and_close_socket(socket_t sock) override;
+  bool is_valid() const override;
+
+  SSL_CTX *ssl_context() const;
 
-    SSL_CTX *ctx_;
-    std::mutex ctx_mutex_;
+private:
+  bool process_and_close_socket(socket_t sock) override;
+
+  SSL_CTX *ctx_;
+  std::mutex ctx_mutex_;
 };
 
 class SSLClient : public ClientImpl {
-  public:
-    explicit SSLClient(const std::string &host);
+public:
+  explicit SSLClient(const std::string &host);
 
-    explicit SSLClient(const std::string &host, int port);
+  explicit SSLClient(const std::string &host, int port);
 
-    explicit SSLClient(const std::string &host,
-                       int port,
-                       const std::string &client_cert_path,
-                       const std::string &client_key_path);
+  explicit SSLClient(const std::string &host, int port,
+                     const std::string &client_cert_path,
+                     const std::string &client_key_path);
 
-    explicit SSLClient(const std::string &host, int port, X509 *client_cert, EVP_PKEY *client_key);
+  explicit SSLClient(const std::string &host, int port, X509 *client_cert,
+                     EVP_PKEY *client_key);
 
-    ~SSLClient() override;
+  ~SSLClient() override;
 
-    bool is_valid() const override;
+  bool is_valid() const override;
 
-    void set_ca_cert_path(const char *ca_cert_file_path, const char *ca_cert_dir_path = nullptr);
+  void set_ca_cert_store(X509_STORE *ca_cert_store);
 
-    void set_ca_cert_store(X509_STORE *ca_cert_store);
+  long get_openssl_verify_result() const;
 
-    long get_openssl_verify_result() const;
+  SSL_CTX *ssl_context() const;
 
-    SSL_CTX *ssl_context() const;
-
-  private:
-    bool create_and_connect_socket(Socket &socket) override;
-    void close_socket(Socket &socket, bool process_socket_ret) override;
+private:
+  bool create_and_connect_socket(Socket &socket, Error &error) override;
+  void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override;
+  void shutdown_ssl_impl(Socket &socket, bool shutdown_socket);
 
-    bool process_socket(Socket &socket, std::function<bool(Stream &strm)> callback) override;
-    bool is_ssl() const override;
+  bool process_socket(const Socket &socket,
+                      std::function<bool(Stream &strm)> callback) override;
+  bool is_ssl() const override;
 
-    bool connect_with_proxy(Socket &sock, Response &res, bool &success);
-    bool initialize_ssl(Socket &socket);
+  bool connect_with_proxy(Socket &sock, Response &res, bool &success,
+                          Error &error);
+  bool initialize_ssl(Socket &socket, Error &error);
 
-    bool load_certs();
+  bool load_certs();
 
-    bool verify_host(X509 *server_cert) const;
-    bool verify_host_with_subject_alt_name(X509 *server_cert) const;
-    bool verify_host_with_common_name(X509 *server_cert) const;
-    bool check_host_name(const char *pattern, size_t pattern_len) const;
+  bool verify_host(X509 *server_cert) const;
+  bool verify_host_with_subject_alt_name(X509 *server_cert) const;
+  bool verify_host_with_common_name(X509 *server_cert) const;
+  bool check_host_name(const char *pattern, size_t pattern_len) const;
 
-    SSL_CTX *ctx_;
-    std::mutex ctx_mutex_;
-    std::once_flag initialize_cert_;
+  SSL_CTX *ctx_;
+  std::mutex ctx_mutex_;
+  std::once_flag initialize_cert_;
 
-    std::vector<std::string> host_components_;
+  std::vector<std::string> host_components_;
 
-    std::string ca_cert_file_path_;
-    std::string ca_cert_dir_path_;
-    long verify_result_ = 0;
+  long verify_result_ = 0;
 
-    friend class ClientImpl;
+  friend class ClientImpl;
 };
 #endif
 
-// ----------------------------------------------------------------------------
-
 /*
- * Implementation
+ * Implementation of template methods.
  */
 
 namespace detail {
 
-inline bool is_hex(char c, int &v) {
-    if (0x20 <= c && isdigit(c)) {
-        v = c - '0';
-        return true;
-    } else if ('A' <= c && c <= 'F') {
-        v = c - 'A' + 10;
-        return true;
-    } else if ('a' <= c && c <= 'f') {
-        v = c - 'a' + 10;
-        return true;
-    }
-    return false;
+template <typename T, typename U>
+inline void duration_to_sec_and_usec(const T &duration, U callback) {
+  auto sec = std::chrono::duration_cast<std::chrono::seconds>(duration).count();
+  auto usec = std::chrono::duration_cast<std::chrono::microseconds>(
+                  duration - std::chrono::seconds(sec))
+                  .count();
+  callback(sec, usec);
 }
 
-inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, int &val) {
-    if (i >= s.size()) {
-        return false;
-    }
+template <typename T>
+inline T get_header_value(const Headers & /*headers*/, const char * /*key*/,
+                          size_t /*id*/ = 0, uint64_t /*def*/ = 0) {}
 
-    val = 0;
-    for (; cnt; i++, cnt--) {
-        if (!s[i]) {
-            return false;
-        }
-        int v = 0;
-        if (is_hex(s[i], v)) {
-            val = val * 16 + v;
-        } else {
-            return false;
-        }
-    }
-    return true;
+template <>
+inline uint64_t get_header_value<uint64_t>(const Headers &headers,
+                                           const char *key, size_t id,
+                                           uint64_t def) {
+  auto rng = headers.equal_range(key);
+  auto it = rng.first;
+  std::advance(it, static_cast<ssize_t>(id));
+  if (it != rng.second) {
+    return std::strtoull(it->second.data(), nullptr, 10);
+  }
+  return def;
 }
 
-inline std::string from_i_to_hex(size_t n) {
-    const char *charset = "0123456789abcdef";
-    std::string ret;
-    do {
-        ret = charset[n & 15] + ret;
-        n >>= 4;
-    } while (n > 0);
-    return ret;
-}
+} // namespace detail
 
-inline bool start_with(const std::string &a, const std::string &b) {
-    if (a.size() < b.size()) {
-        return false;
-    }
-    for (size_t i = 0; i < b.size(); i++) {
-        if (std::tolower(a[i]) != std::tolower(b[i])) {
-            return false;
-        }
-    }
-    return true;
+template <typename T>
+inline T Request::get_header_value(const char *key, size_t id) const {
+  return detail::get_header_value<T>(headers, key, id, 0);
 }
 
-inline size_t to_utf8(int code, char *buff) {
-    if (code < 0x0080) {
-        buff[0] = (code & 0x7F);
-        return 1;
-    } else if (code < 0x0800) {
-        buff[0] = static_cast<char>(0xC0 | ((code >> 6) & 0x1F));
-        buff[1] = static_cast<char>(0x80 | (code & 0x3F));
-        return 2;
-    } else if (code < 0xD800) {
-        buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
-        buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
-        buff[2] = static_cast<char>(0x80 | (code & 0x3F));
-        return 3;
-    } else if (code < 0xE000) {  // D800 - DFFF is invalid...
-        return 0;
-    } else if (code < 0x10000) {
-        buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
-        buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
-        buff[2] = static_cast<char>(0x80 | (code & 0x3F));
-        return 3;
-    } else if (code < 0x110000) {
-        buff[0] = static_cast<char>(0xF0 | ((code >> 18) & 0x7));
-        buff[1] = static_cast<char>(0x80 | ((code >> 12) & 0x3F));
-        buff[2] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
-        buff[3] = static_cast<char>(0x80 | (code & 0x3F));
-        return 4;
-    }
-
-    // NOTREACHED
-    return 0;
+template <typename T>
+inline T Response::get_header_value(const char *key, size_t id) const {
+  return detail::get_header_value<T>(headers, key, id, 0);
 }
 
-// NOTE: This code came up with the following stackoverflow post:
-// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c
-inline std::string base64_encode(const std::string &in) {
-    static const auto lookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+template <typename... Args>
+inline ssize_t Stream::write_format(const char *fmt, const Args &...args) {
+  const auto bufsiz = 2048;
+  std::array<char, bufsiz> buf{};
 
-    std::string out;
-    out.reserve(in.size());
+#if defined(_MSC_VER) && _MSC_VER < 1900
+  auto sn = _snprintf_s(buf.data(), bufsiz, _TRUNCATE, fmt, args...);
+#else
+  auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...);
+#endif
+  if (sn <= 0) { return sn; }
 
-    int val  = 0;
-    int valb = -6;
+  auto n = static_cast<size_t>(sn);
 
-    for (auto c : in) {
-        val = (val << 8) + static_cast<uint8_t>(c);
-        valb += 8;
-        while (valb >= 0) {
-            out.push_back(lookup[(val >> valb) & 0x3F]);
-            valb -= 6;
-        }
-    }
+  if (n >= buf.size() - 1) {
+    std::vector<char> glowable_buf(buf.size());
 
-    if (valb > -6) {
-        out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]);
+    while (n >= glowable_buf.size() - 1) {
+      glowable_buf.resize(glowable_buf.size() * 2);
+#if defined(_MSC_VER) && _MSC_VER < 1900
+      n = static_cast<size_t>(_snprintf_s(&glowable_buf[0], glowable_buf.size(),
+                                          glowable_buf.size() - 1, fmt,
+                                          args...));
+#else
+      n = static_cast<size_t>(
+          snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...));
+#endif
     }
+    return write(&glowable_buf[0], n);
+  } else {
+    return write(buf.data(), n);
+  }
+}
 
-    while (out.size() % 4) {
-        out.push_back('=');
-    }
+inline void default_socket_options(socket_t sock) {
+  int yes = 1;
+#ifdef _WIN32
+  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&yes),
+             sizeof(yes));
+  setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
+             reinterpret_cast<char *>(&yes), sizeof(yes));
+#else
+#ifdef SO_REUSEPORT
+  setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<void *>(&yes),
+             sizeof(yes));
+#else
+  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<void *>(&yes),
+             sizeof(yes));
+#endif
+#endif
+}
 
-    return out;
+template <class Rep, class Period>
+inline Server &
+Server::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
+  detail::duration_to_sec_and_usec(
+      duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); });
+  return *this;
 }
 
-inline bool is_file(const std::string &path) {
-    struct stat st;
-    return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode);
+template <class Rep, class Period>
+inline Server &
+Server::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
+  detail::duration_to_sec_and_usec(
+      duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
+  return *this;
 }
 
-inline bool is_dir(const std::string &path) {
-    struct stat st;
-    return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode);
+template <class Rep, class Period>
+inline Server &
+Server::set_idle_interval(const std::chrono::duration<Rep, Period> &duration) {
+  detail::duration_to_sec_and_usec(
+      duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); });
+  return *this;
 }
 
-inline bool is_valid_path(const std::string &path) {
-    size_t level = 0;
-    size_t i     = 0;
+inline std::string to_string(const Error error) {
+  switch (error) {
+  case Error::Success: return "Success";
+  case Error::Connection: return "Connection";
+  case Error::BindIPAddress: return "BindIPAddress";
+  case Error::Read: return "Read";
+  case Error::Write: return "Write";
+  case Error::ExceedRedirectCount: return "ExceedRedirectCount";
+  case Error::Canceled: return "Canceled";
+  case Error::SSLConnection: return "SSLConnection";
+  case Error::SSLLoadingCerts: return "SSLLoadingCerts";
+  case Error::SSLServerVerification: return "SSLServerVerification";
+  case Error::UnsupportedMultipartBoundaryChars:
+    return "UnsupportedMultipartBoundaryChars";
+  case Error::Compression: return "Compression";
+  case Error::ConnectionTimeout: return "ConnectionTimeout";
+  case Error::Unknown: return "Unknown";
+  default: break;
+  }
 
-    // Skip slash
-    while (i < path.size() && path[i] == '/') {
-        i++;
-    }
+  return "Invalid";
+}
 
-    while (i < path.size()) {
-        // Read component
-        auto beg = i;
-        while (i < path.size() && path[i] != '/') {
-            i++;
-        }
+inline std::ostream &operator<<(std::ostream &os, const Error &obj) {
+  os << to_string(obj);
+  os << " (" << static_cast<std::underlying_type<Error>::type>(obj) << ')';
+  return os;
+}
 
-        auto len = i - beg;
-        assert(len > 0);
+template <typename T>
+inline T Result::get_request_header_value(const char *key, size_t id) const {
+  return detail::get_header_value<T>(request_headers_, key, id, 0);
+}
 
-        if (!path.compare(beg, len, ".")) {
-            ;
-        } else if (!path.compare(beg, len, "..")) {
-            if (level == 0) {
-                return false;
-            }
-            level--;
-        } else {
-            level++;
-        }
+template <class Rep, class Period>
+inline void ClientImpl::set_connection_timeout(
+    const std::chrono::duration<Rep, Period> &duration) {
+  detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) {
+    set_connection_timeout(sec, usec);
+  });
+}
 
-        // Skip slash
-        while (i < path.size() && path[i] == '/') {
-            i++;
-        }
-    }
+template <class Rep, class Period>
+inline void ClientImpl::set_read_timeout(
+    const std::chrono::duration<Rep, Period> &duration) {
+  detail::duration_to_sec_and_usec(
+      duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); });
+}
 
-    return true;
+template <class Rep, class Period>
+inline void ClientImpl::set_write_timeout(
+    const std::chrono::duration<Rep, Period> &duration) {
+  detail::duration_to_sec_and_usec(
+      duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
 }
 
-inline std::string encode_url(const std::string &s) {
-    std::string result;
-
-    for (size_t i = 0; s[i]; i++) {
-        switch (s[i]) {
-            case ' ':
-                result += "%20";
-                break;
-            case '+':
-                result += "%2B";
-                break;
-            case '\r':
-                result += "%0D";
-                break;
-            case '\n':
-                result += "%0A";
-                break;
-            case '\'':
-                result += "%27";
-                break;
-            case ',':
-                result += "%2C";
-                break;
-            // case ':': result += "%3A"; break; // ok? probably...
-            case ';':
-                result += "%3B";
-                break;
-            default:
-                auto c = static_cast<uint8_t>(s[i]);
-                if (c >= 0x80) {
-                    result += '%';
-                    char hex[4];
-                    auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
-                    assert(len == 2);
-                    result.append(hex, static_cast<size_t>(len));
-                } else {
-                    result += s[i];
-                }
-                break;
-        }
-    }
+template <class Rep, class Period>
+inline void Client::set_connection_timeout(
+    const std::chrono::duration<Rep, Period> &duration) {
+  cli_->set_connection_timeout(duration);
+}
 
-    return result;
+template <class Rep, class Period>
+inline void
+Client::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
+  cli_->set_read_timeout(duration);
 }
 
-inline std::string decode_url(const std::string &s, bool convert_plus_to_space) {
-    std::string result;
+template <class Rep, class Period>
+inline void
+Client::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
+  cli_->set_write_timeout(duration);
+}
 
-    for (size_t i = 0; i < s.size(); i++) {
-        if (s[i] == '%' && i + 1 < s.size()) {
-            if (s[i + 1] == 'u') {
-                int val = 0;
-                if (from_hex_to_i(s, i + 2, 4, val)) {
-                    // 4 digits Unicode codes
-                    char buff[4];
-                    size_t len = to_utf8(val, buff);
-                    if (len > 0) {
-                        result.append(buff, len);
-                    }
-                    i += 5;  // 'u0000'
-                } else {
-                    result += s[i];
-                }
-            } else {
-                int val = 0;
-                if (from_hex_to_i(s, i + 1, 2, val)) {
-                    // 2 digits hex codes
-                    result += static_cast<char>(val);
-                    i += 2;  // '00'
-                } else {
-                    result += s[i];
-                }
-            }
-        } else if (convert_plus_to_space && s[i] == '+') {
-            result += ' ';
-        } else {
-            result += s[i];
-        }
-    }
+/*
+ * Forward declarations and types that will be part of the .h file if split into
+ * .h + .cc.
+ */
 
-    return result;
-}
+std::string hosted_at(const char *hostname);
 
-inline void read_file(const std::string &path, std::string &out) {
-    std::ifstream fs(path, std::ios_base::binary);
-    fs.seekg(0, std::ios_base::end);
-    auto size = fs.tellg();
-    fs.seekg(0);
-    out.resize(static_cast<size_t>(size));
-    fs.read(&out[0], static_cast<std::streamsize>(size));
+void hosted_at(const char *hostname, std::vector<std::string> &addrs);
+
+std::string append_query_params(const char *path, const Params &params);
+
+std::pair<std::string, std::string> make_range_header(Ranges ranges);
+
+std::pair<std::string, std::string>
+make_basic_authentication_header(const std::string &username,
+                                 const std::string &password,
+                                 bool is_proxy = false);
+
+namespace detail {
+
+std::string encode_query_param(const std::string &value);
+
+std::string decode_url(const std::string &s, bool convert_plus_to_space);
+
+void read_file(const std::string &path, std::string &out);
+
+std::string trim_copy(const std::string &s);
+
+void split(const char *b, const char *e, char d,
+           std::function<void(const char *, const char *)> fn);
+
+bool process_client_socket(socket_t sock, time_t read_timeout_sec,
+                           time_t read_timeout_usec, time_t write_timeout_sec,
+                           time_t write_timeout_usec,
+                           std::function<bool(Stream &)> callback);
+
+socket_t create_client_socket(
+    const char *host, const char *ip, int port, int address_family,
+    bool tcp_nodelay, SocketOptions socket_options,
+    time_t connection_timeout_sec, time_t connection_timeout_usec,
+    time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
+    time_t write_timeout_usec, const std::string &intf, Error &error);
+
+const char *get_header_value(const Headers &headers, const char *key,
+                             size_t id = 0, const char *def = nullptr);
+
+std::string params_to_query_str(const Params &params);
+
+void parse_query_text(const std::string &s, Params &params);
+
+bool parse_range_header(const std::string &s, Ranges &ranges);
+
+int close_socket(socket_t sock);
+
+ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags);
+
+ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags);
+
+enum class EncodingType { None = 0, Gzip, Brotli };
+
+EncodingType encoding_type(const Request &req, const Response &res);
+
+class BufferStream : public Stream {
+public:
+  BufferStream() = default;
+  ~BufferStream() override = default;
+
+  bool is_readable() const override;
+  bool is_writable() const override;
+  ssize_t read(char *ptr, size_t size) override;
+  ssize_t write(const char *ptr, size_t size) override;
+  void get_remote_ip_and_port(std::string &ip, int &port) const override;
+  socket_t socket() const override;
+
+  const std::string &get_buffer() const;
+
+private:
+  std::string buffer;
+  size_t position = 0;
+};
+
+class compressor {
+public:
+  virtual ~compressor() = default;
+
+  typedef std::function<bool(const char *data, size_t data_len)> Callback;
+  virtual bool compress(const char *data, size_t data_length, bool last,
+                        Callback callback) = 0;
+};
+
+class decompressor {
+public:
+  virtual ~decompressor() = default;
+
+  virtual bool is_valid() const = 0;
+
+  typedef std::function<bool(const char *data, size_t data_len)> Callback;
+  virtual bool decompress(const char *data, size_t data_length,
+                          Callback callback) = 0;
+};
+
+class nocompressor : public compressor {
+public:
+  virtual ~nocompressor() = default;
+
+  bool compress(const char *data, size_t data_length, bool /*last*/,
+                Callback callback) override;
+};
+
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+class gzip_compressor : public compressor {
+public:
+  gzip_compressor();
+  ~gzip_compressor();
+
+  bool compress(const char *data, size_t data_length, bool last,
+                Callback callback) override;
+
+private:
+  bool is_valid_ = false;
+  z_stream strm_;
+};
+
+class gzip_decompressor : public decompressor {
+public:
+  gzip_decompressor();
+  ~gzip_decompressor();
+
+  bool is_valid() const override;
+
+  bool decompress(const char *data, size_t data_length,
+                  Callback callback) override;
+
+private:
+  bool is_valid_ = false;
+  z_stream strm_;
+};
+#endif
+
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+class brotli_compressor : public compressor {
+public:
+  brotli_compressor();
+  ~brotli_compressor();
+
+  bool compress(const char *data, size_t data_length, bool last,
+                Callback callback) override;
+
+private:
+  BrotliEncoderState *state_ = nullptr;
+};
+
+class brotli_decompressor : public decompressor {
+public:
+  brotli_decompressor();
+  ~brotli_decompressor();
+
+  bool is_valid() const override;
+
+  bool decompress(const char *data, size_t data_length,
+                  Callback callback) override;
+
+private:
+  BrotliDecoderResult decoder_r;
+  BrotliDecoderState *decoder_s = nullptr;
+};
+#endif
+
+// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
+// to store data. The call can set memory on stack for performance.
+class stream_line_reader {
+public:
+  stream_line_reader(Stream &strm, char *fixed_buffer,
+                     size_t fixed_buffer_size);
+  const char *ptr() const;
+  size_t size() const;
+  bool end_with_crlf() const;
+  bool getline();
+
+private:
+  void append(char c);
+
+  Stream &strm_;
+  char *fixed_buffer_;
+  const size_t fixed_buffer_size_;
+  size_t fixed_buffer_used_size_ = 0;
+  std::string glowable_buffer_;
+};
+
+} // namespace detail
+
+// ----------------------------------------------------------------------------
+
+/*
+ * Implementation that will be part of the .cc file if split into .h + .cc.
+ */
+
+namespace detail {
+
+inline bool is_hex(char c, int &v) {
+  if (0x20 <= c && isdigit(c)) {
+    v = c - '0';
+    return true;
+  } else if ('A' <= c && c <= 'F') {
+    v = c - 'A' + 10;
+    return true;
+  } else if ('a' <= c && c <= 'f') {
+    v = c - 'a' + 10;
+    return true;
+  }
+  return false;
 }
 
-inline std::string file_extension(const std::string &path) {
-    std::smatch m;
-    static auto re = std::regex("\\.([a-zA-Z0-9]+)$");
-    if (std::regex_search(path, m, re)) {
-        return m[1].str();
+inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt,
+                          int &val) {
+  if (i >= s.size()) { return false; }
+
+  val = 0;
+  for (; cnt; i++, cnt--) {
+    if (!s[i]) { return false; }
+    int v = 0;
+    if (is_hex(s[i], v)) {
+      val = val * 16 + v;
+    } else {
+      return false;
     }
-    return std::string();
+  }
+  return true;
 }
 
-inline bool is_space_or_tab(char c) {
-    return c == ' ' || c == '\t';
+inline std::string from_i_to_hex(size_t n) {
+  const char *charset = "0123456789abcdef";
+  std::string ret;
+  do {
+    ret = charset[n & 15] + ret;
+    n >>= 4;
+  } while (n > 0);
+  return ret;
 }
 
-inline std::pair<size_t, size_t> trim(const char *b, const char *e, size_t left, size_t right) {
-    while (b + left < e && is_space_or_tab(b[left])) {
-        left++;
-    }
-    while (right > 0 && is_space_or_tab(b[right - 1])) {
-        right--;
+inline size_t to_utf8(int code, char *buff) {
+  if (code < 0x0080) {
+    buff[0] = (code & 0x7F);
+    return 1;
+  } else if (code < 0x0800) {
+    buff[0] = static_cast<char>(0xC0 | ((code >> 6) & 0x1F));
+    buff[1] = static_cast<char>(0x80 | (code & 0x3F));
+    return 2;
+  } else if (code < 0xD800) {
+    buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
+    buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
+    buff[2] = static_cast<char>(0x80 | (code & 0x3F));
+    return 3;
+  } else if (code < 0xE000) { // D800 - DFFF is invalid...
+    return 0;
+  } else if (code < 0x10000) {
+    buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
+    buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
+    buff[2] = static_cast<char>(0x80 | (code & 0x3F));
+    return 3;
+  } else if (code < 0x110000) {
+    buff[0] = static_cast<char>(0xF0 | ((code >> 18) & 0x7));
+    buff[1] = static_cast<char>(0x80 | ((code >> 12) & 0x3F));
+    buff[2] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
+    buff[3] = static_cast<char>(0x80 | (code & 0x3F));
+    return 4;
+  }
+
+  // NOTREACHED
+  return 0;
+}
+
+// NOTE: This code came up with the following stackoverflow post:
+// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c
+inline std::string base64_encode(const std::string &in) {
+  static const auto lookup =
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+  std::string out;
+  out.reserve(in.size());
+
+  int val = 0;
+  int valb = -6;
+
+  for (auto c : in) {
+    val = (val << 8) + static_cast<uint8_t>(c);
+    valb += 8;
+    while (valb >= 0) {
+      out.push_back(lookup[(val >> valb) & 0x3F]);
+      valb -= 6;
     }
-    return std::make_pair(left, right);
+  }
+
+  if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); }
+
+  while (out.size() % 4) {
+    out.push_back('=');
+  }
+
+  return out;
 }
 
-inline std::string trim_copy(const std::string &s) {
-    auto r = trim(s.data(), s.data() + s.size(), 0, s.size());
-    return s.substr(r.first, r.second - r.first);
+inline bool is_file(const std::string &path) {
+#ifdef _WIN32
+  return _access_s(path.c_str(), 0) == 0;
+#else
+  struct stat st;
+  return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode);
+#endif
+}
+
+inline bool is_dir(const std::string &path) {
+  struct stat st;
+  return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode);
 }
 
-template <class Fn>
-void split(const char *b, const char *e, char d, Fn fn) {
-    size_t i   = 0;
-    size_t beg = 0;
+inline bool is_valid_path(const std::string &path) {
+  size_t level = 0;
+  size_t i = 0;
 
-    while (e ? (b + i < e) : (b[i] != '\0')) {
-        if (b[i] == d) {
-            auto r = trim(b, e, beg, i);
-            if (r.first < r.second) {
-                fn(&b[r.first], &b[r.second]);
-            }
-            beg = i + 1;
-        }
-        i++;
+  // Skip slash
+  while (i < path.size() && path[i] == '/') {
+    i++;
+  }
+
+  while (i < path.size()) {
+    // Read component
+    auto beg = i;
+    while (i < path.size() && path[i] != '/') {
+      i++;
     }
 
-    if (i) {
-        auto r = trim(b, e, beg, i);
-        if (r.first < r.second) {
-            fn(&b[r.first], &b[r.second]);
-        }
+    auto len = i - beg;
+    assert(len > 0);
+
+    if (!path.compare(beg, len, ".")) {
+      ;
+    } else if (!path.compare(beg, len, "..")) {
+      if (level == 0) { return false; }
+      level--;
+    } else {
+      level++;
+    }
+
+    // Skip slash
+    while (i < path.size() && path[i] == '/') {
+      i++;
     }
+  }
+
+  return true;
 }
 
-// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
-// to store data. The call can set memory on stack for performance.
-class stream_line_reader {
-  public:
-    stream_line_reader(Stream &strm, char *fixed_buffer, size_t fixed_buffer_size)
-        : strm_(strm), fixed_buffer_(fixed_buffer), fixed_buffer_size_(fixed_buffer_size) {}
+inline std::string encode_query_param(const std::string &value) {
+  std::ostringstream escaped;
+  escaped.fill('0');
+  escaped << std::hex;
 
-    const char *ptr() const {
-        if (glowable_buffer_.empty()) {
-            return fixed_buffer_;
-        } else {
-            return glowable_buffer_.data();
-        }
+  for (auto c : value) {
+    if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' ||
+        c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' ||
+        c == ')') {
+      escaped << c;
+    } else {
+      escaped << std::uppercase;
+      escaped << '%' << std::setw(2)
+              << static_cast<int>(static_cast<unsigned char>(c));
+      escaped << std::nouppercase;
     }
+  }
 
-    size_t size() const {
-        if (glowable_buffer_.empty()) {
-            return fixed_buffer_used_size_;
+  return escaped.str();
+}
+
+inline std::string encode_url(const std::string &s) {
+  std::string result;
+  result.reserve(s.size());
+
+  for (size_t i = 0; s[i]; i++) {
+    switch (s[i]) {
+    case ' ': result += "%20"; break;
+    case '+': result += "%2B"; break;
+    case '\r': result += "%0D"; break;
+    case '\n': result += "%0A"; break;
+    case '\'': result += "%27"; break;
+    case ',': result += "%2C"; break;
+    // case ':': result += "%3A"; break; // ok? probably...
+    case ';': result += "%3B"; break;
+    default:
+      auto c = static_cast<uint8_t>(s[i]);
+      if (c >= 0x80) {
+        result += '%';
+        char hex[4];
+        auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
+        assert(len == 2);
+        result.append(hex, static_cast<size_t>(len));
+      } else {
+        result += s[i];
+      }
+      break;
+    }
+  }
+
+  return result;
+}
+
+inline std::string decode_url(const std::string &s,
+                              bool convert_plus_to_space) {
+  std::string result;
+
+  for (size_t i = 0; i < s.size(); i++) {
+    if (s[i] == '%' && i + 1 < s.size()) {
+      if (s[i + 1] == 'u') {
+        int val = 0;
+        if (from_hex_to_i(s, i + 2, 4, val)) {
+          // 4 digits Unicode codes
+          char buff[4];
+          size_t len = to_utf8(val, buff);
+          if (len > 0) { result.append(buff, len); }
+          i += 5; // 'u0000'
         } else {
-            return glowable_buffer_.size();
+          result += s[i];
+        }
+      } else {
+        int val = 0;
+        if (from_hex_to_i(s, i + 1, 2, val)) {
+          // 2 digits hex codes
+          result += static_cast<char>(val);
+          i += 2; // '00'
+        } else {
+          result += s[i];
         }
+      }
+    } else if (convert_plus_to_space && s[i] == '+') {
+      result += ' ';
+    } else {
+      result += s[i];
     }
+  }
 
-    bool end_with_crlf() const {
-        auto end = ptr() + size();
-        return size() >= 2 && end[-2] == '\r' && end[-1] == '\n';
-    }
+  return result;
+}
 
-    bool getline() {
-        fixed_buffer_used_size_ = 0;
-        glowable_buffer_.clear();
+inline void read_file(const std::string &path, std::string &out) {
+  std::ifstream fs(path, std::ios_base::binary);
+  fs.seekg(0, std::ios_base::end);
+  auto size = fs.tellg();
+  fs.seekg(0);
+  out.resize(static_cast<size_t>(size));
+  fs.read(&out[0], static_cast<std::streamsize>(size));
+}
 
-        for (size_t i = 0;; i++) {
-            char byte;
-            auto n = strm_.read(&byte, 1);
+inline std::string file_extension(const std::string &path) {
+  std::smatch m;
+  static auto re = std::regex("\\.([a-zA-Z0-9]+)$");
+  if (std::regex_search(path, m, re)) { return m[1].str(); }
+  return std::string();
+}
 
-            if (n < 0) {
-                return false;
-            } else if (n == 0) {
-                if (i == 0) {
-                    return false;
-                } else {
-                    break;
-                }
-            }
+inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; }
 
-            append(byte);
+inline std::pair<size_t, size_t> trim(const char *b, const char *e, size_t left,
+                                      size_t right) {
+  while (b + left < e && is_space_or_tab(b[left])) {
+    left++;
+  }
+  while (right > 0 && is_space_or_tab(b[right - 1])) {
+    right--;
+  }
+  return std::make_pair(left, right);
+}
 
-            if (byte == '\n') {
-                break;
-            }
-        }
+inline std::string trim_copy(const std::string &s) {
+  auto r = trim(s.data(), s.data() + s.size(), 0, s.size());
+  return s.substr(r.first, r.second - r.first);
+}
 
-        return true;
+inline void split(const char *b, const char *e, char d,
+                  std::function<void(const char *, const char *)> fn) {
+  size_t i = 0;
+  size_t beg = 0;
+
+  while (e ? (b + i < e) : (b[i] != '\0')) {
+    if (b[i] == d) {
+      auto r = trim(b, e, beg, i);
+      if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
+      beg = i + 1;
     }
+    i++;
+  }
 
-  private:
-    void append(char c) {
-        if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) {
-            fixed_buffer_[fixed_buffer_used_size_++] = c;
-            fixed_buffer_[fixed_buffer_used_size_]   = '\0';
-        } else {
-            if (glowable_buffer_.empty()) {
-                assert(fixed_buffer_[fixed_buffer_used_size_] == '\0');
-                glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_);
-            }
-            glowable_buffer_ += c;
-        }
+  if (i) {
+    auto r = trim(b, e, beg, i);
+    if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
+  }
+}
+
+inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer,
+                                              size_t fixed_buffer_size)
+    : strm_(strm), fixed_buffer_(fixed_buffer),
+      fixed_buffer_size_(fixed_buffer_size) {}
+
+inline const char *stream_line_reader::ptr() const {
+  if (glowable_buffer_.empty()) {
+    return fixed_buffer_;
+  } else {
+    return glowable_buffer_.data();
+  }
+}
+
+inline size_t stream_line_reader::size() const {
+  if (glowable_buffer_.empty()) {
+    return fixed_buffer_used_size_;
+  } else {
+    return glowable_buffer_.size();
+  }
+}
+
+inline bool stream_line_reader::end_with_crlf() const {
+  auto end = ptr() + size();
+  return size() >= 2 && end[-2] == '\r' && end[-1] == '\n';
+}
+
+inline bool stream_line_reader::getline() {
+  fixed_buffer_used_size_ = 0;
+  glowable_buffer_.clear();
+
+  for (size_t i = 0;; i++) {
+    char byte;
+    auto n = strm_.read(&byte, 1);
+
+    if (n < 0) {
+      return false;
+    } else if (n == 0) {
+      if (i == 0) {
+        return false;
+      } else {
+        break;
+      }
     }
 
-    Stream &strm_;
-    char *fixed_buffer_;
-    const size_t fixed_buffer_size_;
-    size_t fixed_buffer_used_size_ = 0;
-    std::string glowable_buffer_;
-};
+    append(byte);
+
+    if (byte == '\n') { break; }
+  }
+
+  return true;
+}
+
+inline void stream_line_reader::append(char c) {
+  if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) {
+    fixed_buffer_[fixed_buffer_used_size_++] = c;
+    fixed_buffer_[fixed_buffer_used_size_] = '\0';
+  } else {
+    if (glowable_buffer_.empty()) {
+      assert(fixed_buffer_[fixed_buffer_used_size_] == '\0');
+      glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_);
+    }
+    glowable_buffer_ += c;
+  }
+}
 
 inline int close_socket(socket_t sock) {
 #ifdef _WIN32
-    return closesocket(sock);
+  return closesocket(sock);
 #else
-    return close(sock);
+  return close(sock);
 #endif
 }
 
-template <typename T>
-inline ssize_t handle_EINTR(T fn) {
-    ssize_t res = false;
-    while (true) {
-        res = fn();
-        if (res < 0 && errno == EINTR) {
-            continue;
-        }
-        break;
-    }
-    return res;
+template <typename T> inline ssize_t handle_EINTR(T fn) {
+  ssize_t res = false;
+  while (true) {
+    res = fn();
+    if (res < 0 && errno == EINTR) { continue; }
+    break;
+  }
+  return res;
+}
+
+inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) {
+  return handle_EINTR([&]() {
+    return recv(sock,
+#ifdef _WIN32
+                static_cast<char *>(ptr), static_cast<int>(size),
+#else
+                ptr, size,
+#endif
+                flags);
+  });
+}
+
+inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size,
+                           int flags) {
+  return handle_EINTR([&]() {
+    return send(sock,
+#ifdef _WIN32
+                static_cast<const char *>(ptr), static_cast<int>(size),
+#else
+                ptr, size,
+#endif
+                flags);
+  });
 }
 
 inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
 #ifdef CPPHTTPLIB_USE_POLL
-    struct pollfd pfd_read;
-    pfd_read.fd     = sock;
-    pfd_read.events = POLLIN;
+  struct pollfd pfd_read;
+  pfd_read.fd = sock;
+  pfd_read.events = POLLIN;
 
-    auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
+  auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
 
-    return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
+  return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
 #else
-    fd_set fds;
-    FD_ZERO(&fds);
-    FD_SET(sock, &fds);
+#ifndef _WIN32
+  if (sock >= FD_SETSIZE) { return 1; }
+#endif
+
+  fd_set fds;
+  FD_ZERO(&fds);
+  FD_SET(sock, &fds);
 
-    timeval tv;
-    tv.tv_sec  = static_cast<long>(sec);
-    tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
+  timeval tv;
+  tv.tv_sec = static_cast<long>(sec);
+  tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
 
-    return handle_EINTR(
-        [&]() { return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv); });
+  return handle_EINTR([&]() {
+    return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
+  });
 #endif
 }
 
 inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
 #ifdef CPPHTTPLIB_USE_POLL
-    struct pollfd pfd_read;
-    pfd_read.fd     = sock;
-    pfd_read.events = POLLOUT;
+  struct pollfd pfd_read;
+  pfd_read.fd = sock;
+  pfd_read.events = POLLOUT;
 
-    auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
+  auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
 
-    return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
+  return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
 #else
-    fd_set fds;
-    FD_ZERO(&fds);
-    FD_SET(sock, &fds);
+#ifndef _WIN32
+  if (sock >= FD_SETSIZE) { return 1; }
+#endif
 
-    timeval tv;
-    tv.tv_sec  = static_cast<long>(sec);
-    tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
+  fd_set fds;
+  FD_ZERO(&fds);
+  FD_SET(sock, &fds);
 
-    return handle_EINTR(
-        [&]() { return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv); });
+  timeval tv;
+  tv.tv_sec = static_cast<long>(sec);
+  tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
+
+  return handle_EINTR([&]() {
+    return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);
+  });
 #endif
 }
 
-inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
+inline Error wait_until_socket_is_ready(socket_t sock, time_t sec,
+                                        time_t usec) {
 #ifdef CPPHTTPLIB_USE_POLL
-    struct pollfd pfd_read;
-    pfd_read.fd     = sock;
-    pfd_read.events = POLLIN | POLLOUT;
+  struct pollfd pfd_read;
+  pfd_read.fd = sock;
+  pfd_read.events = POLLIN | POLLOUT;
 
-    auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
+  auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
 
-    auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
+  auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
 
-    if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
-        int error     = 0;
-        socklen_t len = sizeof(error);
-        auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast<char *>(&error), &len);
-        return res >= 0 && !error;
-    }
-    return false;
+  if (poll_res == 0) { return Error::ConnectionTimeout; }
+
+  if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
+    int error = 0;
+    socklen_t len = sizeof(error);
+    auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
+                          reinterpret_cast<char *>(&error), &len);
+    auto successful = res >= 0 && !error;
+    return successful ? Error::Success : Error::Connection;
+  }
+
+  return Error::Connection;
 #else
-    fd_set fdsr;
-    FD_ZERO(&fdsr);
-    FD_SET(sock, &fdsr);
+#ifndef _WIN32
+  if (sock >= FD_SETSIZE) { return Error::Connection; }
+#endif
 
-    auto fdsw = fdsr;
-    auto fdse = fdsr;
+  fd_set fdsr;
+  FD_ZERO(&fdsr);
+  FD_SET(sock, &fdsr);
 
-    timeval tv;
-    tv.tv_sec  = static_cast<long>(sec);
-    tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
+  auto fdsw = fdsr;
+  auto fdse = fdsr;
 
-    auto ret = handle_EINTR(
-        [&]() { return select(static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv); });
+  timeval tv;
+  tv.tv_sec = static_cast<long>(sec);
+  tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
 
-    if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
-        int error     = 0;
-        socklen_t len = sizeof(error);
-        return getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast<char *>(&error), &len) >=
-                   0 &&
-               !error;
-    }
-    return false;
+  auto ret = handle_EINTR([&]() {
+    return select(static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv);
+  });
+
+  if (ret == 0) { return Error::ConnectionTimeout; }
+
+  if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
+    int error = 0;
+    socklen_t len = sizeof(error);
+    auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
+                          reinterpret_cast<char *>(&error), &len);
+    auto successful = res >= 0 && !error;
+    return successful ? Error::Success : Error::Connection;
+  }
+  return Error::Connection;
 #endif
 }
 
-class SocketStream : public Stream {
-  public:
-    SocketStream(socket_t sock,
-                 time_t read_timeout_sec,
-                 time_t read_timeout_usec,
-                 time_t write_timeout_sec,
-                 time_t write_timeout_usec);
-    ~SocketStream() override;
-
-    bool is_readable() const override;
-    bool is_writable() const override;
-    ssize_t read(char *ptr, size_t size) override;
-    ssize_t write(const char *ptr, size_t size) override;
-    void get_remote_ip_and_port(std::string &ip, int &port) const override;
+inline bool is_socket_alive(socket_t sock) {
+  const auto val = detail::select_read(sock, 0, 0);
+  if (val == 0) {
+    return true;
+  } else if (val < 0 && errno == EBADF) {
+    return false;
+  }
+  char buf[1];
+  return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0;
+}
 
-  private:
-    socket_t sock_;
-    time_t read_timeout_sec_;
-    time_t read_timeout_usec_;
-    time_t write_timeout_sec_;
-    time_t write_timeout_usec_;
+class SocketStream : public Stream {
+public:
+  SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
+               time_t write_timeout_sec, time_t write_timeout_usec);
+  ~SocketStream() override;
+
+  bool is_readable() const override;
+  bool is_writable() const override;
+  ssize_t read(char *ptr, size_t size) override;
+  ssize_t write(const char *ptr, size_t size) override;
+  void get_remote_ip_and_port(std::string &ip, int &port) const override;
+  socket_t socket() const override;
+
+private:
+  socket_t sock_;
+  time_t read_timeout_sec_;
+  time_t read_timeout_usec_;
+  time_t write_timeout_sec_;
+  time_t write_timeout_usec_;
+
+  std::vector<char> read_buff_;
+  size_t read_buff_off_ = 0;
+  size_t read_buff_content_size_ = 0;
+
+  static const size_t read_buff_size_ = 1024 * 4;
 };
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
 class SSLSocketStream : public Stream {
-  public:
-    SSLSocketStream(socket_t sock,
-                    SSL *ssl,
-                    time_t read_timeout_sec,
-                    time_t read_timeout_usec,
-                    time_t write_timeout_sec,
-                    time_t write_timeout_usec);
-    ~SSLSocketStream() override;
-
-    bool is_readable() const override;
-    bool is_writable() const override;
-    ssize_t read(char *ptr, size_t size) override;
-    ssize_t write(const char *ptr, size_t size) override;
-    void get_remote_ip_and_port(std::string &ip, int &port) const override;
-
-  private:
-    socket_t sock_;
-    SSL *ssl_;
-    time_t read_timeout_sec_;
-    time_t read_timeout_usec_;
-    time_t write_timeout_sec_;
-    time_t write_timeout_usec_;
+public:
+  SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec,
+                  time_t read_timeout_usec, time_t write_timeout_sec,
+                  time_t write_timeout_usec);
+  ~SSLSocketStream() override;
+
+  bool is_readable() const override;
+  bool is_writable() const override;
+  ssize_t read(char *ptr, size_t size) override;
+  ssize_t write(const char *ptr, size_t size) override;
+  void get_remote_ip_and_port(std::string &ip, int &port) const override;
+  socket_t socket() const override;
+
+private:
+  socket_t sock_;
+  SSL *ssl_;
+  time_t read_timeout_sec_;
+  time_t read_timeout_usec_;
+  time_t write_timeout_sec_;
+  time_t write_timeout_usec_;
 };
 #endif
 
-class BufferStream : public Stream {
-  public:
-    BufferStream()           = default;
-    ~BufferStream() override = default;
-
-    bool is_readable() const override;
-    bool is_writable() const override;
-    ssize_t read(char *ptr, size_t size) override;
-    ssize_t write(const char *ptr, size_t size) override;
-    void get_remote_ip_and_port(std::string &ip, int &port) const override;
-
-    const std::string &get_buffer() const;
-
-  private:
-    std::string buffer;
-    size_t position = 0;
-};
-
 inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) {
-    using namespace std::chrono;
-    auto start = steady_clock::now();
-    while (true) {
-        auto val = select_read(sock, 0, 10000);
-        if (val < 0) {
-            return false;
-        } else if (val == 0) {
-            auto current  = steady_clock::now();
-            auto duration = duration_cast<milliseconds>(current - start);
-            auto timeout  = keep_alive_timeout_sec * 1000;
-            if (duration.count() > timeout) {
-                return false;
-            }
-            std::this_thread::sleep_for(std::chrono::milliseconds(1));
-        } else {
-            return true;
-        }
-    }
-}
-
-template <typename T>
-inline bool process_server_socket_core(socket_t sock,
-                                       size_t keep_alive_max_count,
-                                       time_t keep_alive_timeout_sec,
-                                       T callback) {
-    assert(keep_alive_max_count > 0);
-    auto ret   = false;
-    auto count = keep_alive_max_count;
-    while (count > 0 && keep_alive(sock, keep_alive_timeout_sec)) {
-        auto close_connection  = count == 1;
-        auto connection_closed = false;
-        ret                    = callback(close_connection, connection_closed);
-        if (!ret || connection_closed) {
-            break;
-        }
-        count--;
+  using namespace std::chrono;
+  auto start = steady_clock::now();
+  while (true) {
+    auto val = select_read(sock, 0, 10000);
+    if (val < 0) {
+      return false;
+    } else if (val == 0) {
+      auto current = steady_clock::now();
+      auto duration = duration_cast<milliseconds>(current - start);
+      auto timeout = keep_alive_timeout_sec * 1000;
+      if (duration.count() > timeout) { return false; }
+      std::this_thread::sleep_for(std::chrono::milliseconds(1));
+    } else {
+      return true;
     }
-    return ret;
+  }
 }
 
 template <typename T>
-inline bool process_server_socket(socket_t sock,
-                                  size_t keep_alive_max_count,
-                                  time_t keep_alive_timeout_sec,
-                                  time_t read_timeout_sec,
-                                  time_t read_timeout_usec,
-                                  time_t write_timeout_sec,
-                                  time_t write_timeout_usec,
-                                  T callback) {
-    return process_server_socket_core(
-        sock, keep_alive_max_count, keep_alive_timeout_sec,
-        [&](bool close_connection, bool &connection_closed) {
-            SocketStream strm(sock, read_timeout_sec, read_timeout_usec, write_timeout_sec,
-                              write_timeout_usec);
-            return callback(strm, close_connection, connection_closed);
-        });
+inline bool
+process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
+                           size_t keep_alive_max_count,
+                           time_t keep_alive_timeout_sec, T callback) {
+  assert(keep_alive_max_count > 0);
+  auto ret = false;
+  auto count = keep_alive_max_count;
+  while (svr_sock != INVALID_SOCKET && count > 0 &&
+         keep_alive(sock, keep_alive_timeout_sec)) {
+    auto close_connection = count == 1;
+    auto connection_closed = false;
+    ret = callback(close_connection, connection_closed);
+    if (!ret || connection_closed) { break; }
+    count--;
+  }
+  return ret;
 }
 
 template <typename T>
-inline bool process_client_socket(socket_t sock,
-                                  time_t read_timeout_sec,
+inline bool
+process_server_socket(const std::atomic<socket_t> &svr_sock, socket_t sock,
+                      size_t keep_alive_max_count,
+                      time_t keep_alive_timeout_sec, time_t read_timeout_sec,
+                      time_t read_timeout_usec, time_t write_timeout_sec,
+                      time_t write_timeout_usec, T callback) {
+  return process_server_socket_core(
+      svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
+      [&](bool close_connection, bool &connection_closed) {
+        SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
+                          write_timeout_sec, write_timeout_usec);
+        return callback(strm, close_connection, connection_closed);
+      });
+}
+
+inline bool process_client_socket(socket_t sock, time_t read_timeout_sec,
                                   time_t read_timeout_usec,
                                   time_t write_timeout_sec,
                                   time_t write_timeout_usec,
-                                  T callback) {
-    SocketStream strm(sock, read_timeout_sec, read_timeout_usec, write_timeout_sec,
-                      write_timeout_usec);
-    return callback(strm);
+                                  std::function<bool(Stream &)> callback) {
+  SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
+                    write_timeout_sec, write_timeout_usec);
+  return callback(strm);
 }
 
 inline int shutdown_socket(socket_t sock) {
 #ifdef _WIN32
-    return shutdown(sock, SD_BOTH);
+  return shutdown(sock, SD_BOTH);
 #else
-    return shutdown(sock, SHUT_RDWR);
+  return shutdown(sock, SHUT_RDWR);
 #endif
 }
 
 template <typename BindOrConnect>
-socket_t create_socket(const char *host,
-                       int port,
-                       int socket_flags,
-                       bool tcp_nodelay,
+socket_t create_socket(const char *host, const char *ip, int port,
+                       int address_family, int socket_flags, bool tcp_nodelay,
                        SocketOptions socket_options,
                        BindOrConnect bind_or_connect) {
-    // Get address info
-    struct addrinfo hints;
-    struct addrinfo *result;
-
-    memset(&hints, 0, sizeof(struct addrinfo));
-    hints.ai_family   = AF_UNSPEC;
-    hints.ai_socktype = SOCK_STREAM;
-    hints.ai_flags    = socket_flags;
-    hints.ai_protocol = 0;
-
-    auto service = std::to_string(port);
-
-    if (getaddrinfo(host, service.c_str(), &hints, &result)) {
-#ifdef __linux__
-        res_init();
+  // Get address info
+  const char *node = nullptr;
+  struct addrinfo hints;
+  struct addrinfo *result;
+
+  memset(&hints, 0, sizeof(struct addrinfo));
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_protocol = 0;
+
+  if (ip[0] != '\0') {
+    node = ip;
+    // Ask getaddrinfo to convert IP in c-string to address
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_flags = AI_NUMERICHOST;
+  } else {
+    node = host;
+    hints.ai_family = address_family;
+    hints.ai_flags = socket_flags;
+  }
+
+  auto service = std::to_string(port);
+
+  if (getaddrinfo(node, service.c_str(), &hints, &result)) {
+#if defined __linux__ && !defined __ANDROID__
+    res_init();
 #endif
-        return INVALID_SOCKET;
-    }
+    return INVALID_SOCKET;
+  }
 
-    for (auto rp = result; rp; rp = rp->ai_next) {
-        // Create a socket
+  for (auto rp = result; rp; rp = rp->ai_next) {
+    // Create a socket
 #ifdef _WIN32
-        auto sock = WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0,
-                               WSA_FLAG_NO_HANDLE_INHERIT);
-        /**
-         * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1
-         * and above the socket creation fails on older Windows Systems.
-         *
-         * Let's try to create a socket the old way in this case.
-         *
-         * Reference:
-         * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa
-         *
-         * WSA_FLAG_NO_HANDLE_INHERIT:
-         * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with
-         * SP1, and later
-         *
-         */
-        if (sock == INVALID_SOCKET) {
-            sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
-        }
+    auto sock =
+        WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0,
+                   WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED);
+    /**
+     * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1
+     * and above the socket creation fails on older Windows Systems.
+     *
+     * Let's try to create a socket the old way in this case.
+     *
+     * Reference:
+     * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa
+     *
+     * WSA_FLAG_NO_HANDLE_INHERIT:
+     * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with
+     * SP1, and later
+     *
+     */
+    if (sock == INVALID_SOCKET) {
+      sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+    }
 #else
-        auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+    auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
 #endif
-        if (sock == INVALID_SOCKET) {
-            continue;
-        }
+    if (sock == INVALID_SOCKET) { continue; }
 
 #ifndef _WIN32
-        if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) {
-            continue;
-        }
+    if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { continue; }
 #endif
 
-        if (tcp_nodelay) {
-            int yes = 1;
-            setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&yes), sizeof(yes));
-        }
+    if (tcp_nodelay) {
+      int yes = 1;
+      setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&yes),
+                 sizeof(yes));
+    }
 
-        if (socket_options) {
-            socket_options(sock);
-        }
+    if (socket_options) { socket_options(sock); }
 
-        if (rp->ai_family == AF_INET6) {
-            int no = 0;
-            setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char *>(&no), sizeof(no));
-        }
-
-        // bind or connect
-        if (bind_or_connect(sock, *rp)) {
-            freeaddrinfo(result);
-            return sock;
-        }
+    if (rp->ai_family == AF_INET6) {
+      int no = 0;
+      setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char *>(&no),
+                 sizeof(no));
+    }
 
-        close_socket(sock);
+    // bind or connect
+    if (bind_or_connect(sock, *rp)) {
+      freeaddrinfo(result);
+      return sock;
     }
 
-    freeaddrinfo(result);
-    return INVALID_SOCKET;
+    close_socket(sock);
+  }
+
+  freeaddrinfo(result);
+  return INVALID_SOCKET;
 }
 
 inline void set_nonblocking(socket_t sock, bool nonblocking) {
 #ifdef _WIN32
-    auto flags = nonblocking ? 1UL : 0UL;
-    ioctlsocket(sock, FIONBIO, &flags);
+  auto flags = nonblocking ? 1UL : 0UL;
+  ioctlsocket(sock, FIONBIO, &flags);
 #else
-    auto flags = fcntl(sock, F_GETFL, 0);
-    fcntl(sock, F_SETFL, nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK)));
+  auto flags = fcntl(sock, F_GETFL, 0);
+  fcntl(sock, F_SETFL,
+        nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK)));
 #endif
 }
 
 inline bool is_connection_error() {
 #ifdef _WIN32
-    return WSAGetLastError() != WSAEWOULDBLOCK;
+  return WSAGetLastError() != WSAEWOULDBLOCK;
 #else
-    return errno != EINPROGRESS;
+  return errno != EINPROGRESS;
 #endif
 }
 
 inline bool bind_ip_address(socket_t sock, const char *host) {
-    struct addrinfo hints;
-    struct addrinfo *result;
+  struct addrinfo hints;
+  struct addrinfo *result;
 
-    memset(&hints, 0, sizeof(struct addrinfo));
-    hints.ai_family   = AF_UNSPEC;
-    hints.ai_socktype = SOCK_STREAM;
-    hints.ai_protocol = 0;
+  memset(&hints, 0, sizeof(struct addrinfo));
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_protocol = 0;
 
-    if (getaddrinfo(host, "0", &hints, &result)) {
-        return false;
-    }
+  if (getaddrinfo(host, "0", &hints, &result)) { return false; }
 
-    auto ret = false;
-    for (auto rp = result; rp; rp = rp->ai_next) {
-        const auto &ai = *rp;
-        if (!::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
-            ret = true;
-            break;
-        }
+  auto ret = false;
+  for (auto rp = result; rp; rp = rp->ai_next) {
+    const auto &ai = *rp;
+    if (!::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
+      ret = true;
+      break;
     }
+  }
 
-    freeaddrinfo(result);
-    return ret;
+  freeaddrinfo(result);
+  return ret;
 }
 
 #if !defined _WIN32 && !defined ANDROID
-#    define USE_IF2IP
+#define USE_IF2IP
 #endif
 
 #ifdef USE_IF2IP
-inline std::string if2ip(const std::string &ifn) {
-    struct ifaddrs *ifap;
-    getifaddrs(&ifap);
-    for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
-        if (ifa->ifa_addr && ifn == ifa->ifa_name) {
-            if (ifa->ifa_addr->sa_family == AF_INET) {
-                auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
-                char buf[INET_ADDRSTRLEN];
-                if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) {
-                    freeifaddrs(ifap);
-                    return std::string(buf, INET_ADDRSTRLEN);
-                }
+inline std::string if2ip(int address_family, const std::string &ifn) {
+  struct ifaddrs *ifap;
+  getifaddrs(&ifap);
+  std::string addr_candidate;
+  for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
+    if (ifa->ifa_addr && ifn == ifa->ifa_name &&
+        (AF_UNSPEC == address_family ||
+         ifa->ifa_addr->sa_family == address_family)) {
+      if (ifa->ifa_addr->sa_family == AF_INET) {
+        auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
+        char buf[INET_ADDRSTRLEN];
+        if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) {
+          freeifaddrs(ifap);
+          return std::string(buf, INET_ADDRSTRLEN);
+        }
+      } else if (ifa->ifa_addr->sa_family == AF_INET6) {
+        auto sa = reinterpret_cast<struct sockaddr_in6 *>(ifa->ifa_addr);
+        if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) {
+          char buf[INET6_ADDRSTRLEN] = {};
+          if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) {
+            // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL
+            auto s6_addr_head = sa->sin6_addr.s6_addr[0];
+            if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) {
+              addr_candidate = std::string(buf, INET6_ADDRSTRLEN);
+            } else {
+              freeifaddrs(ifap);
+              return std::string(buf, INET6_ADDRSTRLEN);
             }
+          }
         }
+      }
     }
-    freeifaddrs(ifap);
-    return std::string();
+  }
+  freeifaddrs(ifap);
+  return addr_candidate;
 }
 #endif
 
-inline socket_t create_client_socket(const char *host,
-                                     int port,
-                                     bool tcp_nodelay,
-                                     SocketOptions socket_options,
-                                     time_t timeout_sec,
-                                     time_t timeout_usec,
-                                     const std::string &intf,
-                                     Error &error) {
-    auto sock = create_socket(
-        host, port, 0, tcp_nodelay, socket_options,
-        [&](socket_t sock, struct addrinfo &ai) -> bool {
-            if (!intf.empty()) {
+inline socket_t create_client_socket(
+    const char *host, const char *ip, int port, int address_family,
+    bool tcp_nodelay, SocketOptions socket_options,
+    time_t connection_timeout_sec, time_t connection_timeout_usec,
+    time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
+    time_t write_timeout_usec, const std::string &intf, Error &error) {
+  auto sock = create_socket(
+      host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options),
+      [&](socket_t sock2, struct addrinfo &ai) -> bool {
+        if (!intf.empty()) {
 #ifdef USE_IF2IP
-                auto ip = if2ip(intf);
-                if (ip.empty()) {
-                    ip = intf;
-                }
-                if (!bind_ip_address(sock, ip.c_str())) {
-                    error = Error::BindIPAddress;
-                    return false;
-                }
+          auto ip_from_if = if2ip(address_family, intf);
+          if (ip_from_if.empty()) { ip_from_if = intf; }
+          if (!bind_ip_address(sock2, ip_from_if.c_str())) {
+            error = Error::BindIPAddress;
+            return false;
+          }
 #endif
-            }
+        }
 
-            set_nonblocking(sock, true);
+        set_nonblocking(sock2, true);
 
-            auto ret = ::connect(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
+        auto ret =
+            ::connect(sock2, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
 
-            if (ret < 0) {
-                if (is_connection_error() ||
-                    !wait_until_socket_is_ready(sock, timeout_sec, timeout_usec)) {
-                    close_socket(sock);
-                    error = Error::Connection;
-                    return false;
-                }
-            }
+        if (ret < 0) {
+          if (is_connection_error()) {
+            error = Error::Connection;
+            return false;
+          }
+          error = wait_until_socket_is_ready(sock2, connection_timeout_sec,
+                                             connection_timeout_usec);
+          if (error != Error::Success) { return false; }
+        }
 
-            set_nonblocking(sock, false);
-            error = Error::Success;
-            return true;
-        });
+        set_nonblocking(sock2, false);
+
+        {
+#ifdef _WIN32
+          auto timeout = static_cast<uint32_t>(read_timeout_sec * 1000 +
+                                               read_timeout_usec / 1000);
+          setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
+                     sizeof(timeout));
+#else
+          timeval tv;
+          tv.tv_sec = static_cast<long>(read_timeout_sec);
+          tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec);
+          setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
+#endif
+        }
+        {
+
+#ifdef _WIN32
+          auto timeout = static_cast<uint32_t>(write_timeout_sec * 1000 +
+                                               write_timeout_usec / 1000);
+          setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
+                     sizeof(timeout));
+#else
+          timeval tv;
+          tv.tv_sec = static_cast<long>(write_timeout_sec);
+          tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec);
+          setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
+#endif
+        }
 
-    if (sock != INVALID_SOCKET) {
         error = Error::Success;
-    } else {
-        if (error == Error::Success) {
-            error = Error::Connection;
-        }
-    }
+        return true;
+      });
+
+  if (sock != INVALID_SOCKET) {
+    error = Error::Success;
+  } else {
+    if (error == Error::Success) { error = Error::Connection; }
+  }
 
-    return sock;
+  return sock;
 }
 
-inline void get_remote_ip_and_port(const struct sockaddr_storage &addr,
-                                   socklen_t addr_len,
-                                   std::string &ip,
+inline bool get_remote_ip_and_port(const struct sockaddr_storage &addr,
+                                   socklen_t addr_len, std::string &ip,
                                    int &port) {
-    if (addr.ss_family == AF_INET) {
-        port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
-    } else if (addr.ss_family == AF_INET6) {
-        port = ntohs(reinterpret_cast<const struct sockaddr_in6 *>(&addr)->sin6_port);
-    }
+  if (addr.ss_family == AF_INET) {
+    port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
+  } else if (addr.ss_family == AF_INET6) {
+    port =
+        ntohs(reinterpret_cast<const struct sockaddr_in6 *>(&addr)->sin6_port);
+  } else {
+    return false;
+  }
 
-    std::array<char, NI_MAXHOST> ipstr{};
-    if (!getnameinfo(reinterpret_cast<const struct sockaddr *>(&addr), addr_len, ipstr.data(),
-                     static_cast<socklen_t>(ipstr.size()), nullptr, 0, NI_NUMERICHOST)) {
-        ip = ipstr.data();
-    }
+  std::array<char, NI_MAXHOST> ipstr{};
+  if (getnameinfo(reinterpret_cast<const struct sockaddr *>(&addr), addr_len,
+                  ipstr.data(), static_cast<socklen_t>(ipstr.size()), nullptr,
+                  0, NI_NUMERICHOST)) {
+    return false;
+  }
+
+  ip = ipstr.data();
+  return true;
 }
 
 inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
-    struct sockaddr_storage addr;
-    socklen_t addr_len = sizeof(addr);
-
-    if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr), &addr_len)) {
-        get_remote_ip_and_port(addr, addr_len, ip, port);
-    }
-}
-
-inline const char *find_content_type(const std::string &path,
-                                     const std::map<std::string, std::string> &user_data) {
-    auto ext = file_extension(path);
-
-    auto it = user_data.find(ext);
-    if (it != user_data.end()) {
-        return it->second.c_str();
-    }
-
-    if (ext == "txt") {
-        return "text/plain";
-    } else if (ext == "html" || ext == "htm") {
-        return "text/html";
-    } else if (ext == "css") {
-        return "text/css";
-    } else if (ext == "jpeg" || ext == "jpg") {
-        return "image/jpg";
-    } else if (ext == "png") {
-        return "image/png";
-    } else if (ext == "gif") {
-        return "image/gif";
-    } else if (ext == "svg") {
-        return "image/svg+xml";
-    } else if (ext == "ico") {
-        return "image/x-icon";
-    } else if (ext == "json") {
-        return "application/json";
-    } else if (ext == "pdf") {
-        return "application/pdf";
-    } else if (ext == "js") {
-        return "application/javascript";
-    } else if (ext == "wasm") {
-        return "application/wasm";
-    } else if (ext == "xml") {
-        return "application/xml";
-    } else if (ext == "xhtml") {
-        return "application/xhtml+xml";
-    }
-    return nullptr;
+  struct sockaddr_storage addr;
+  socklen_t addr_len = sizeof(addr);
+
+  if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
+                   &addr_len)) {
+    get_remote_ip_and_port(addr, addr_len, ip, port);
+  }
+}
+
+inline constexpr unsigned int str2tag_core(const char *s, size_t l,
+                                           unsigned int h) {
+  return (l == 0) ? h
+                  : str2tag_core(s + 1, l - 1,
+                                 (h * 33) ^ static_cast<unsigned char>(*s));
+}
+
+inline unsigned int str2tag(const std::string &s) {
+  return str2tag_core(s.data(), s.size(), 0);
+}
+
+namespace udl {
+
+inline constexpr unsigned int operator"" _t(const char *s, size_t l) {
+  return str2tag_core(s, l, 0);
+}
+
+} // namespace udl
+
+inline const char *
+find_content_type(const std::string &path,
+                  const std::map<std::string, std::string> &user_data) {
+  auto ext = file_extension(path);
+
+  auto it = user_data.find(ext);
+  if (it != user_data.end()) { return it->second.c_str(); }
+
+  using udl::operator""_t;
+
+  switch (str2tag(ext)) {
+  default: return nullptr;
+  case "css"_t: return "text/css";
+  case "csv"_t: return "text/csv";
+  case "htm"_t:
+  case "html"_t: return "text/html";
+  case "js"_t:
+  case "mjs"_t: return "text/javascript";
+  case "txt"_t: return "text/plain";
+  case "vtt"_t: return "text/vtt";
+
+  case "apng"_t: return "image/apng";
+  case "avif"_t: return "image/avif";
+  case "bmp"_t: return "image/bmp";
+  case "gif"_t: return "image/gif";
+  case "png"_t: return "image/png";
+  case "svg"_t: return "image/svg+xml";
+  case "webp"_t: return "image/webp";
+  case "ico"_t: return "image/x-icon";
+  case "tif"_t: return "image/tiff";
+  case "tiff"_t: return "image/tiff";
+  case "jpg"_t:
+  case "jpeg"_t: return "image/jpeg";
+
+  case "mp4"_t: return "video/mp4";
+  case "mpeg"_t: return "video/mpeg";
+  case "webm"_t: return "video/webm";
+
+  case "mp3"_t: return "audio/mp3";
+  case "mpga"_t: return "audio/mpeg";
+  case "weba"_t: return "audio/webm";
+  case "wav"_t: return "audio/wave";
+
+  case "otf"_t: return "font/otf";
+  case "ttf"_t: return "font/ttf";
+  case "woff"_t: return "font/woff";
+  case "woff2"_t: return "font/woff2";
+
+  case "7z"_t: return "application/x-7z-compressed";
+  case "atom"_t: return "application/atom+xml";
+  case "pdf"_t: return "application/pdf";
+  case "json"_t: return "application/json";
+  case "rss"_t: return "application/rss+xml";
+  case "tar"_t: return "application/x-tar";
+  case "xht"_t:
+  case "xhtml"_t: return "application/xhtml+xml";
+  case "xslt"_t: return "application/xslt+xml";
+  case "xml"_t: return "application/xml";
+  case "gz"_t: return "application/gzip";
+  case "zip"_t: return "application/zip";
+  case "wasm"_t: return "application/wasm";
+  }
 }
 
 inline const char *status_message(int status) {
-    switch (status) {
-        case 100:
-            return "Continue";
-        case 101:
-            return "Switching Protocol";
-        case 102:
-            return "Processing";
-        case 103:
-            return "Early Hints";
-        case 200:
-            return "OK";
-        case 201:
-            return "Created";
-        case 202:
-            return "Accepted";
-        case 203:
-            return "Non-Authoritative Information";
-        case 204:
-            return "No Content";
-        case 205:
-            return "Reset Content";
-        case 206:
-            return "Partial Content";
-        case 207:
-            return "Multi-Status";
-        case 208:
-            return "Already Reported";
-        case 226:
-            return "IM Used";
-        case 300:
-            return "Multiple Choice";
-        case 301:
-            return "Moved Permanently";
-        case 302:
-            return "Found";
-        case 303:
-            return "See Other";
-        case 304:
-            return "Not Modified";
-        case 305:
-            return "Use Proxy";
-        case 306:
-            return "unused";
-        case 307:
-            return "Temporary Redirect";
-        case 308:
-            return "Permanent Redirect";
-        case 400:
-            return "Bad Request";
-        case 401:
-            return "Unauthorized";
-        case 402:
-            return "Payment Required";
-        case 403:
-            return "Forbidden";
-        case 404:
-            return "Not Found";
-        case 405:
-            return "Method Not Allowed";
-        case 406:
-            return "Not Acceptable";
-        case 407:
-            return "Proxy Authentication Required";
-        case 408:
-            return "Request Timeout";
-        case 409:
-            return "Conflict";
-        case 410:
-            return "Gone";
-        case 411:
-            return "Length Required";
-        case 412:
-            return "Precondition Failed";
-        case 413:
-            return "Payload Too Large";
-        case 414:
-            return "URI Too Long";
-        case 415:
-            return "Unsupported Media Type";
-        case 416:
-            return "Range Not Satisfiable";
-        case 417:
-            return "Expectation Failed";
-        case 418:
-            return "I'm a teapot";
-        case 421:
-            return "Misdirected Request";
-        case 422:
-            return "Unprocessable Entity";
-        case 423:
-            return "Locked";
-        case 424:
-            return "Failed Dependency";
-        case 425:
-            return "Too Early";
-        case 426:
-            return "Upgrade Required";
-        case 428:
-            return "Precondition Required";
-        case 429:
-            return "Too Many Requests";
-        case 431:
-            return "Request Header Fields Too Large";
-        case 451:
-            return "Unavailable For Legal Reasons";
-        case 501:
-            return "Not Implemented";
-        case 502:
-            return "Bad Gateway";
-        case 503:
-            return "Service Unavailable";
-        case 504:
-            return "Gateway Timeout";
-        case 505:
-            return "HTTP Version Not Supported";
-        case 506:
-            return "Variant Also Negotiates";
-        case 507:
-            return "Insufficient Storage";
-        case 508:
-            return "Loop Detected";
-        case 510:
-            return "Not Extended";
-        case 511:
-            return "Network Authentication Required";
-
-        default:
-        case 500:
-            return "Internal Server Error";
-    }
+  switch (status) {
+  case 100: return "Continue";
+  case 101: return "Switching Protocol";
+  case 102: return "Processing";
+  case 103: return "Early Hints";
+  case 200: return "OK";
+  case 201: return "Created";
+  case 202: return "Accepted";
+  case 203: return "Non-Authoritative Information";
+  case 204: return "No Content";
+  case 205: return "Reset Content";
+  case 206: return "Partial Content";
+  case 207: return "Multi-Status";
+  case 208: return "Already Reported";
+  case 226: return "IM Used";
+  case 300: return "Multiple Choice";
+  case 301: return "Moved Permanently";
+  case 302: return "Found";
+  case 303: return "See Other";
+  case 304: return "Not Modified";
+  case 305: return "Use Proxy";
+  case 306: return "unused";
+  case 307: return "Temporary Redirect";
+  case 308: return "Permanent Redirect";
+  case 400: return "Bad Request";
+  case 401: return "Unauthorized";
+  case 402: return "Payment Required";
+  case 403: return "Forbidden";
+  case 404: return "Not Found";
+  case 405: return "Method Not Allowed";
+  case 406: return "Not Acceptable";
+  case 407: return "Proxy Authentication Required";
+  case 408: return "Request Timeout";
+  case 409: return "Conflict";
+  case 410: return "Gone";
+  case 411: return "Length Required";
+  case 412: return "Precondition Failed";
+  case 413: return "Payload Too Large";
+  case 414: return "URI Too Long";
+  case 415: return "Unsupported Media Type";
+  case 416: return "Range Not Satisfiable";
+  case 417: return "Expectation Failed";
+  case 418: return "I'm a teapot";
+  case 421: return "Misdirected Request";
+  case 422: return "Unprocessable Entity";
+  case 423: return "Locked";
+  case 424: return "Failed Dependency";
+  case 425: return "Too Early";
+  case 426: return "Upgrade Required";
+  case 428: return "Precondition Required";
+  case 429: return "Too Many Requests";
+  case 431: return "Request Header Fields Too Large";
+  case 451: return "Unavailable For Legal Reasons";
+  case 501: return "Not Implemented";
+  case 502: return "Bad Gateway";
+  case 503: return "Service Unavailable";
+  case 504: return "Gateway Timeout";
+  case 505: return "HTTP Version Not Supported";
+  case 506: return "Variant Also Negotiates";
+  case 507: return "Insufficient Storage";
+  case 508: return "Loop Detected";
+  case 510: return "Not Extended";
+  case 511: return "Network Authentication Required";
+
+  default:
+  case 500: return "Internal Server Error";
+  }
 }
 
 inline bool can_compress_content_type(const std::string &content_type) {
-    return (!content_type.find("text/") && content_type != "text/event-stream") ||
-           content_type == "image/svg+xml" || content_type == "application/javascript" ||
-           content_type == "application/json" || content_type == "application/xml" ||
-           content_type == "application/xhtml+xml";
-}
+  using udl::operator""_t;
 
-enum class EncodingType { None = 0, Gzip, Brotli };
+  auto tag = str2tag(content_type);
+
+  switch (tag) {
+  case "image/svg+xml"_t:
+  case "application/javascript"_t:
+  case "application/json"_t:
+  case "application/xml"_t:
+  case "application/protobuf"_t:
+  case "application/xhtml+xml"_t: return true;
+
+  default:
+    return !content_type.rfind("text/", 0) && tag != "text/event-stream"_t;
+  }
+}
 
 inline EncodingType encoding_type(const Request &req, const Response &res) {
-    auto ret = detail::can_compress_content_type(res.get_header_value("Content-Type"));
-    if (!ret) {
-        return EncodingType::None;
-    }
+  auto ret =
+      detail::can_compress_content_type(res.get_header_value("Content-Type"));
+  if (!ret) { return EncodingType::None; }
 
-    const auto &s = req.get_header_value("Accept-Encoding");
-    (void)(s);
+  const auto &s = req.get_header_value("Accept-Encoding");
+  (void)(s);
 
 #ifdef CPPHTTPLIB_BROTLI_SUPPORT
-    // TODO: 'Accept-Encoding' has br, not br;q=0
-    ret = s.find("br") != std::string::npos;
-    if (ret) {
-        return EncodingType::Brotli;
-    }
+  // TODO: 'Accept-Encoding' has br, not br;q=0
+  ret = s.find("br") != std::string::npos;
+  if (ret) { return EncodingType::Brotli; }
 #endif
 
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
-    // TODO: 'Accept-Encoding' has gzip, not gzip;q=0
-    ret = s.find("gzip") != std::string::npos;
-    if (ret) {
-        return EncodingType::Gzip;
-    }
+  // TODO: 'Accept-Encoding' has gzip, not gzip;q=0
+  ret = s.find("gzip") != std::string::npos;
+  if (ret) { return EncodingType::Gzip; }
 #endif
 
-    return EncodingType::None;
+  return EncodingType::None;
 }
 
-class compressor {
-  public:
-    virtual ~compressor(){};
-
-    typedef std::function<bool(const char *data, size_t data_len)> Callback;
-    virtual bool compress(const char *data, size_t data_length, bool last, Callback callback) = 0;
-};
+inline bool nocompressor::compress(const char *data, size_t data_length,
+                                   bool /*last*/, Callback callback) {
+  if (!data_length) { return true; }
+  return callback(data, data_length);
+}
 
-class decompressor {
-  public:
-    virtual ~decompressor() {}
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+inline gzip_compressor::gzip_compressor() {
+  std::memset(&strm_, 0, sizeof(strm_));
+  strm_.zalloc = Z_NULL;
+  strm_.zfree = Z_NULL;
+  strm_.opaque = Z_NULL;
 
-    virtual bool is_valid() const = 0;
+  is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
+                           Z_DEFAULT_STRATEGY) == Z_OK;
+}
 
-    typedef std::function<bool(const char *data, size_t data_len)> Callback;
-    virtual bool decompress(const char *data, size_t data_length, Callback callback) = 0;
-};
+inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); }
 
-class nocompressor : public compressor {
-  public:
-    ~nocompressor(){};
+inline bool gzip_compressor::compress(const char *data, size_t data_length,
+                                      bool last, Callback callback) {
+  assert(is_valid_);
 
-    bool compress(const char *data, size_t data_length, bool /*last*/, Callback callback) override {
-        if (!data_length) {
-            return true;
-        }
-        return callback(data, data_length);
-    }
-};
+  do {
+    constexpr size_t max_avail_in =
+        (std::numeric_limits<decltype(strm_.avail_in)>::max)();
 
-#ifdef CPPHTTPLIB_ZLIB_SUPPORT
-class gzip_compressor : public compressor {
-  public:
-    gzip_compressor() {
-        std::memset(&strm_, 0, sizeof(strm_));
-        strm_.zalloc = Z_NULL;
-        strm_.zfree  = Z_NULL;
-        strm_.opaque = Z_NULL;
+    strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
+        (std::min)(data_length, max_avail_in));
+    strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
 
-        is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
-                                 Z_DEFAULT_STRATEGY) == Z_OK;
-    }
+    data_length -= strm_.avail_in;
+    data += strm_.avail_in;
 
-    ~gzip_compressor() { deflateEnd(&strm_); }
+    auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH;
+    int ret = Z_OK;
 
-    bool compress(const char *data, size_t data_length, bool last, Callback callback) override {
-        assert(is_valid_);
+    std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+    do {
+      strm_.avail_out = static_cast<uInt>(buff.size());
+      strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
 
-        auto flush = last ? Z_FINISH : Z_NO_FLUSH;
+      ret = deflate(&strm_, flush);
+      if (ret == Z_STREAM_ERROR) { return false; }
 
-        strm_.avail_in = static_cast<decltype(strm_.avail_in)>(data_length);
-        strm_.next_in  = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
+      if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
+        return false;
+      }
+    } while (strm_.avail_out == 0);
 
-        int ret = Z_OK;
+    assert((flush == Z_FINISH && ret == Z_STREAM_END) ||
+           (flush == Z_NO_FLUSH && ret == Z_OK));
+    assert(strm_.avail_in == 0);
+  } while (data_length > 0);
 
-        std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
-        do {
-            strm_.avail_out = buff.size();
-            strm_.next_out  = reinterpret_cast<Bytef *>(buff.data());
+  return true;
+}
 
-            ret = deflate(&strm_, flush);
-            assert(ret != Z_STREAM_ERROR);
+inline gzip_decompressor::gzip_decompressor() {
+  std::memset(&strm_, 0, sizeof(strm_));
+  strm_.zalloc = Z_NULL;
+  strm_.zfree = Z_NULL;
+  strm_.opaque = Z_NULL;
 
-            if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
-                return false;
-            }
-        } while (strm_.avail_out == 0);
+  // 15 is the value of wbits, which should be at the maximum possible value
+  // to ensure that any gzip stream can be decoded. The offset of 32 specifies
+  // that the stream type should be automatically detected either gzip or
+  // deflate.
+  is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK;
+}
 
-        assert((last && ret == Z_STREAM_END) || (!last && ret == Z_OK));
-        assert(strm_.avail_in == 0);
-        return true;
-    }
+inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); }
 
-  private:
-    bool is_valid_ = false;
-    z_stream strm_;
-};
+inline bool gzip_decompressor::is_valid() const { return is_valid_; }
 
-class gzip_decompressor : public decompressor {
-  public:
-    gzip_decompressor() {
-        std::memset(&strm_, 0, sizeof(strm_));
-        strm_.zalloc = Z_NULL;
-        strm_.zfree  = Z_NULL;
-        strm_.opaque = Z_NULL;
+inline bool gzip_decompressor::decompress(const char *data, size_t data_length,
+                                          Callback callback) {
+  assert(is_valid_);
 
-        // 15 is the value of wbits, which should be at the maximum possible value
-        // to ensure that any gzip stream can be decoded. The offset of 32 specifies
-        // that the stream type should be automatically detected either gzip or
-        // deflate.
-        is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK;
-    }
+  int ret = Z_OK;
 
-    ~gzip_decompressor() { inflateEnd(&strm_); }
+  do {
+    constexpr size_t max_avail_in =
+        (std::numeric_limits<decltype(strm_.avail_in)>::max)();
 
-    bool is_valid() const override { return is_valid_; }
+    strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
+        (std::min)(data_length, max_avail_in));
+    strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
 
-    bool decompress(const char *data, size_t data_length, Callback callback) override {
-        assert(is_valid_);
+    data_length -= strm_.avail_in;
+    data += strm_.avail_in;
 
-        int ret = Z_OK;
+    std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+    while (strm_.avail_in > 0) {
+      strm_.avail_out = static_cast<uInt>(buff.size());
+      strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
 
-        strm_.avail_in = static_cast<decltype(strm_.avail_in)>(data_length);
-        strm_.next_in  = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
+      auto prev_avail_in = strm_.avail_in;
 
-        std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
-        while (strm_.avail_in > 0) {
-            strm_.avail_out = buff.size();
-            strm_.next_out  = reinterpret_cast<Bytef *>(buff.data());
+      ret = inflate(&strm_, Z_NO_FLUSH);
 
-            ret = inflate(&strm_, Z_NO_FLUSH);
-            assert(ret != Z_STREAM_ERROR);
-            switch (ret) {
-                case Z_NEED_DICT:
-                case Z_DATA_ERROR:
-                case Z_MEM_ERROR:
-                    inflateEnd(&strm_);
-                    return false;
-            }
+      if (prev_avail_in - strm_.avail_in == 0) { return false; }
 
-            if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
-                return false;
-            }
-        }
+      assert(ret != Z_STREAM_ERROR);
+      switch (ret) {
+      case Z_NEED_DICT:
+      case Z_DATA_ERROR:
+      case Z_MEM_ERROR: inflateEnd(&strm_); return false;
+      }
 
-        return ret == Z_OK || ret == Z_STREAM_END;
+      if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
+        return false;
+      }
     }
 
-  private:
-    bool is_valid_ = false;
-    z_stream strm_;
-};
-#endif
-
-#ifdef CPPHTTPLIB_BROTLI_SUPPORT
-class brotli_compressor : public compressor {
-  public:
-    brotli_compressor() { state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); }
-
-    ~brotli_compressor() { BrotliEncoderDestroyInstance(state_); }
+    if (ret != Z_OK && ret != Z_STREAM_END) return false;
 
-    bool compress(const char *data, size_t data_length, bool last, Callback callback) override {
-        std::array<uint8_t, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+  } while (data_length > 0);
 
-        auto operation    = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS;
-        auto available_in = data_length;
-        auto next_in      = reinterpret_cast<const uint8_t *>(data);
+  return true;
+}
+#endif
 
-        for (;;) {
-            if (last) {
-                if (BrotliEncoderIsFinished(state_)) {
-                    break;
-                }
-            } else {
-                if (!available_in) {
-                    break;
-                }
-            }
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+inline brotli_compressor::brotli_compressor() {
+  state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
+}
 
-            auto available_out = buff.size();
-            auto next_out      = buff.data();
+inline brotli_compressor::~brotli_compressor() {
+  BrotliEncoderDestroyInstance(state_);
+}
 
-            if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in,
-                                             &available_out, &next_out, nullptr)) {
-                return false;
-            }
+inline bool brotli_compressor::compress(const char *data, size_t data_length,
+                                        bool last, Callback callback) {
+  std::array<uint8_t, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
 
-            auto output_bytes = buff.size() - available_out;
-            if (output_bytes) {
-                callback(reinterpret_cast<const char *>(buff.data()), output_bytes);
-            }
-        }
+  auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS;
+  auto available_in = data_length;
+  auto next_in = reinterpret_cast<const uint8_t *>(data);
 
-        return true;
+  for (;;) {
+    if (last) {
+      if (BrotliEncoderIsFinished(state_)) { break; }
+    } else {
+      if (!available_in) { break; }
     }
 
-  private:
-    BrotliEncoderState *state_ = nullptr;
-};
+    auto available_out = buff.size();
+    auto next_out = buff.data();
 
-class brotli_decompressor : public decompressor {
-  public:
-    brotli_decompressor() {
-        decoder_s = BrotliDecoderCreateInstance(0, 0, 0);
-        decoder_r =
-            decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT : BROTLI_DECODER_RESULT_ERROR;
+    if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in,
+                                     &available_out, &next_out, nullptr)) {
+      return false;
     }
 
-    ~brotli_decompressor() {
-        if (decoder_s) {
-            BrotliDecoderDestroyInstance(decoder_s);
-        }
+    auto output_bytes = buff.size() - available_out;
+    if (output_bytes) {
+      callback(reinterpret_cast<const char *>(buff.data()), output_bytes);
     }
+  }
 
-    bool is_valid() const override { return decoder_s; }
+  return true;
+}
 
-    bool decompress(const char *data, size_t data_length, Callback callback) override {
-        if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
-            decoder_r == BROTLI_DECODER_RESULT_ERROR) {
-            return 0;
-        }
+inline brotli_decompressor::brotli_decompressor() {
+  decoder_s = BrotliDecoderCreateInstance(0, 0, 0);
+  decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
+                        : BROTLI_DECODER_RESULT_ERROR;
+}
 
-        const uint8_t *next_in = (const uint8_t *)data;
-        size_t avail_in        = data_length;
-        size_t total_out;
+inline brotli_decompressor::~brotli_decompressor() {
+  if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); }
+}
 
-        decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
+inline bool brotli_decompressor::is_valid() const { return decoder_s; }
 
-        std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
-        while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
-            char *next_out   = buff.data();
-            size_t avail_out = buff.size();
+inline bool brotli_decompressor::decompress(const char *data,
+                                            size_t data_length,
+                                            Callback callback) {
+  if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
+      decoder_r == BROTLI_DECODER_RESULT_ERROR) {
+    return 0;
+  }
 
-            decoder_r =
-                BrotliDecoderDecompressStream(decoder_s, &avail_in, &next_in, &avail_out,
-                                              reinterpret_cast<uint8_t **>(&next_out), &total_out);
+  const uint8_t *next_in = (const uint8_t *)data;
+  size_t avail_in = data_length;
+  size_t total_out;
 
-            if (decoder_r == BROTLI_DECODER_RESULT_ERROR) {
-                return false;
-            }
+  decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
 
-            if (!callback(buff.data(), buff.size() - avail_out)) {
-                return false;
-            }
-        }
+  std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
+  while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
+    char *next_out = buff.data();
+    size_t avail_out = buff.size();
 
-        return decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
-               decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
-    }
+    decoder_r = BrotliDecoderDecompressStream(
+        decoder_s, &avail_in, &next_in, &avail_out,
+        reinterpret_cast<uint8_t **>(&next_out), &total_out);
 
-  private:
-    BrotliDecoderResult decoder_r;
-    BrotliDecoderState *decoder_s = nullptr;
-};
-#endif
+    if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; }
 
-inline bool has_header(const Headers &headers, const char *key) {
-    return headers.find(key) != headers.end();
-}
+    if (!callback(buff.data(), buff.size() - avail_out)) { return false; }
+  }
 
-inline const char *get_header_value(const Headers &headers,
-                                    const char *key,
-                                    size_t id       = 0,
-                                    const char *def = nullptr) {
-    auto rng = headers.equal_range(key);
-    auto it  = rng.first;
-    std::advance(it, static_cast<ssize_t>(id));
-    if (it != rng.second) {
-        return it->second.c_str();
-    }
-    return def;
+  return decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
+         decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
 }
+#endif
 
-template <typename T>
-inline T get_header_value(const Headers & /*headers*/,
-                          const char * /*key*/,
-                          size_t /*id*/    = 0,
-                          uint64_t /*def*/ = 0) {}
+inline bool has_header(const Headers &headers, const char *key) {
+  return headers.find(key) != headers.end();
+}
 
-template <>
-inline uint64_t get_header_value<uint64_t>(const Headers &headers,
-                                           const char *key,
-                                           size_t id,
-                                           uint64_t def) {
-    auto rng = headers.equal_range(key);
-    auto it  = rng.first;
-    std::advance(it, static_cast<ssize_t>(id));
-    if (it != rng.second) {
-        return std::strtoull(it->second.data(), nullptr, 10);
-    }
-    return def;
+inline const char *get_header_value(const Headers &headers, const char *key,
+                                    size_t id, const char *def) {
+  auto rng = headers.equal_range(key);
+  auto it = rng.first;
+  std::advance(it, static_cast<ssize_t>(id));
+  if (it != rng.second) { return it->second.c_str(); }
+  return def;
 }
 
 template <typename T>
 inline bool parse_header(const char *beg, const char *end, T fn) {
-    // Skip trailing spaces and tabs.
-    while (beg < end && is_space_or_tab(end[-1])) {
-        end--;
-    }
+  // Skip trailing spaces and tabs.
+  while (beg < end && is_space_or_tab(end[-1])) {
+    end--;
+  }
 
-    auto p = beg;
-    while (p < end && *p != ':') {
-        p++;
-    }
+  auto p = beg;
+  while (p < end && *p != ':') {
+    p++;
+  }
 
-    if (p == end) {
-        return false;
-    }
+  if (p == end) { return false; }
 
-    auto key_end = p;
+  auto key_end = p;
 
-    if (*p++ != ':') {
-        return false;
-    }
+  if (*p++ != ':') { return false; }
 
-    while (p < end && is_space_or_tab(*p)) {
-        p++;
-    }
+  while (p < end && is_space_or_tab(*p)) {
+    p++;
+  }
 
-    if (p < end) {
-        fn(std::string(beg, key_end), decode_url(std::string(p, end), false));
-        return true;
-    }
+  if (p < end) {
+    fn(std::string(beg, key_end), decode_url(std::string(p, end), false));
+    return true;
+  }
 
-    return false;
+  return false;
 }
 
 inline bool read_headers(Stream &strm, Headers &headers) {
-    const auto bufsiz = 2048;
-    char buf[bufsiz];
-    stream_line_reader line_reader(strm, buf, bufsiz);
-
-    for (;;) {
-        if (!line_reader.getline()) {
-            return false;
-        }
+  const auto bufsiz = 2048;
+  char buf[bufsiz];
+  stream_line_reader line_reader(strm, buf, bufsiz);
+
+  for (;;) {
+    if (!line_reader.getline()) { return false; }
+
+    // Check if the line ends with CRLF.
+    auto line_terminator_len = 2;
+    if (line_reader.end_with_crlf()) {
+      // Blank line indicates end of headers.
+      if (line_reader.size() == 2) { break; }
+#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
+    } else {
+      // Blank line indicates end of headers.
+      if (line_reader.size() == 1) { break; }
+      line_terminator_len = 1;
+    }
+#else
+    } else {
+      continue; // Skip invalid line.
+    }
+#endif
 
-        // Check if the line ends with CRLF.
-        if (line_reader.end_with_crlf()) {
-            // Blank line indicates end of headers.
-            if (line_reader.size() == 2) {
-                break;
-            }
-        } else {
-            continue;  // Skip invalid line.
-        }
+    if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
 
-        // Exclude CRLF
-        auto end = line_reader.ptr() + line_reader.size() - 2;
+    // Exclude line terminator
+    auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
 
-        parse_header(line_reader.ptr(), end, [&](std::string &&key, std::string &&val) {
-            headers.emplace(std::move(key), std::move(val));
-        });
-    }
+    parse_header(line_reader.ptr(), end,
+                 [&](std::string &&key, std::string &&val) {
+                   headers.emplace(std::move(key), std::move(val));
+                 });
+  }
 
-    return true;
+  return true;
 }
 
-inline bool read_content_with_length(Stream &strm,
-                                     uint64_t len,
+inline bool read_content_with_length(Stream &strm, uint64_t len,
                                      Progress progress,
-                                     ContentReceiver out) {
-    char buf[CPPHTTPLIB_RECV_BUFSIZ];
-
-    uint64_t r = 0;
-    while (r < len) {
-        auto read_len = static_cast<size_t>(len - r);
-        auto n        = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ));
-        if (n <= 0) {
-            return false;
-        }
+                                     ContentReceiverWithProgress out) {
+  char buf[CPPHTTPLIB_RECV_BUFSIZ];
 
-        if (!out(buf, static_cast<size_t>(n))) {
-            return false;
-        }
+  uint64_t r = 0;
+  while (r < len) {
+    auto read_len = static_cast<size_t>(len - r);
+    auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ));
+    if (n <= 0) { return false; }
 
-        r += static_cast<uint64_t>(n);
+    if (!out(buf, static_cast<size_t>(n), r, len)) { return false; }
+    r += static_cast<uint64_t>(n);
 
-        if (progress) {
-            if (!progress(r, len)) {
-                return false;
-            }
-        }
+    if (progress) {
+      if (!progress(r, len)) { return false; }
     }
+  }
 
-    return true;
+  return true;
 }
 
 inline void skip_content_with_length(Stream &strm, uint64_t len) {
-    char buf[CPPHTTPLIB_RECV_BUFSIZ];
-    uint64_t r = 0;
-    while (r < len) {
-        auto read_len = static_cast<size_t>(len - r);
-        auto n        = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ));
-        if (n <= 0) {
-            return;
-        }
-        r += static_cast<uint64_t>(n);
-    }
+  char buf[CPPHTTPLIB_RECV_BUFSIZ];
+  uint64_t r = 0;
+  while (r < len) {
+    auto read_len = static_cast<size_t>(len - r);
+    auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ));
+    if (n <= 0) { return; }
+    r += static_cast<uint64_t>(n);
+  }
 }
 
-inline bool read_content_without_length(Stream &strm, ContentReceiver out) {
-    char buf[CPPHTTPLIB_RECV_BUFSIZ];
-    for (;;) {
-        auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
-        if (n < 0) {
-            return false;
-        } else if (n == 0) {
-            return true;
-        }
-        if (!out(buf, static_cast<size_t>(n))) {
-            return false;
-        }
+inline bool read_content_without_length(Stream &strm,
+                                        ContentReceiverWithProgress out) {
+  char buf[CPPHTTPLIB_RECV_BUFSIZ];
+  uint64_t r = 0;
+  for (;;) {
+    auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
+    if (n < 0) {
+      return false;
+    } else if (n == 0) {
+      return true;
     }
 
-    return true;
+    if (!out(buf, static_cast<size_t>(n), r, 0)) { return false; }
+    r += static_cast<uint64_t>(n);
+  }
+
+  return true;
 }
 
-inline bool read_content_chunked(Stream &strm, ContentReceiver out) {
-    const auto bufsiz = 16;
-    char buf[bufsiz];
+inline bool read_content_chunked(Stream &strm,
+                                 ContentReceiverWithProgress out) {
+  const auto bufsiz = 16;
+  char buf[bufsiz];
 
-    stream_line_reader line_reader(strm, buf, bufsiz);
+  stream_line_reader line_reader(strm, buf, bufsiz);
 
-    if (!line_reader.getline()) {
-        return false;
-    }
+  if (!line_reader.getline()) { return false; }
 
-    unsigned long chunk_len;
-    while (true) {
-        char *end_ptr;
+  unsigned long chunk_len;
+  while (true) {
+    char *end_ptr;
 
-        chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16);
+    chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16);
 
-        if (end_ptr == line_reader.ptr()) {
-            return false;
-        }
-        if (chunk_len == ULONG_MAX) {
-            return false;
-        }
+    if (end_ptr == line_reader.ptr()) { return false; }
+    if (chunk_len == ULONG_MAX) { return false; }
 
-        if (chunk_len == 0) {
-            break;
-        }
+    if (chunk_len == 0) { break; }
 
-        if (!read_content_with_length(strm, chunk_len, nullptr, out)) {
-            return false;
-        }
+    if (!read_content_with_length(strm, chunk_len, nullptr, out)) {
+      return false;
+    }
 
-        if (!line_reader.getline()) {
-            return false;
-        }
+    if (!line_reader.getline()) { return false; }
 
-        if (strcmp(line_reader.ptr(), "\r\n")) {
-            break;
-        }
+    if (strcmp(line_reader.ptr(), "\r\n")) { break; }
 
-        if (!line_reader.getline()) {
-            return false;
-        }
-    }
+    if (!line_reader.getline()) { return false; }
+  }
 
-    if (chunk_len == 0) {
-        // Reader terminator after chunks
-        if (!line_reader.getline() || strcmp(line_reader.ptr(), "\r\n"))
-            return false;
-    }
+  if (chunk_len == 0) {
+    // Reader terminator after chunks
+    if (!line_reader.getline() || strcmp(line_reader.ptr(), "\r\n"))
+      return false;
+  }
 
-    return true;
+  return true;
 }
 
 inline bool is_chunked_transfer_encoding(const Headers &headers) {
-    return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""), "chunked");
+  return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""),
+                     "chunked");
 }
 
 template <typename T, typename U>
-bool prepare_content_receiver(T &x,
-                              int &status,
-                              ContentReceiver receiver,
-                              bool decompress,
-                              U callback) {
-    if (decompress) {
-        std::string encoding = x.get_header_value("Content-Encoding");
-        std::shared_ptr<decompressor> decompressor;
-
-        if (encoding.find("gzip") != std::string::npos ||
-            encoding.find("deflate") != std::string::npos) {
+bool prepare_content_receiver(T &x, int &status,
+                              ContentReceiverWithProgress receiver,
+                              bool decompress, U callback) {
+  if (decompress) {
+    std::string encoding = x.get_header_value("Content-Encoding");
+    std::unique_ptr<decompressor> decompressor;
+
+    if (encoding == "gzip" || encoding == "deflate") {
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
-            decompressor = std::make_shared<gzip_decompressor>();
+      decompressor = detail::make_unique<gzip_decompressor>();
 #else
-            status = 415;
-            return false;
+      status = 415;
+      return false;
 #endif
-        } else if (encoding.find("br") != std::string::npos) {
+    } else if (encoding.find("br") != std::string::npos) {
 #ifdef CPPHTTPLIB_BROTLI_SUPPORT
-            decompressor = std::make_shared<brotli_decompressor>();
+      decompressor = detail::make_unique<brotli_decompressor>();
 #else
-            status = 415;
-            return false;
+      status = 415;
+      return false;
 #endif
-        }
+    }
 
-        if (decompressor) {
-            if (decompressor->is_valid()) {
-                ContentReceiver out = [&](const char *buf, size_t n) {
-                    return decompressor->decompress(
-                        buf, n, [&](const char *buf, size_t n) { return receiver(buf, n); });
-                };
-                return callback(out);
-            } else {
-                status = 500;
-                return false;
-            }
-        }
+    if (decompressor) {
+      if (decompressor->is_valid()) {
+        ContentReceiverWithProgress out = [&](const char *buf, size_t n,
+                                              uint64_t off, uint64_t len) {
+          return decompressor->decompress(buf, n,
+                                          [&](const char *buf2, size_t n2) {
+                                            return receiver(buf2, n2, off, len);
+                                          });
+        };
+        return callback(std::move(out));
+      } else {
+        status = 500;
+        return false;
+      }
     }
+  }
 
-    ContentReceiver out = [&](const char *buf, size_t n) { return receiver(buf, n); };
-    return callback(out);
+  ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off,
+                                        uint64_t len) {
+    return receiver(buf, n, off, len);
+  };
+  return callback(std::move(out));
 }
 
 template <typename T>
-bool read_content(Stream &strm,
-                  T &x,
-                  size_t payload_max_length,
-                  int &status,
-                  Progress progress,
-                  ContentReceiver receiver,
+bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
+                  Progress progress, ContentReceiverWithProgress receiver,
                   bool decompress) {
-    return prepare_content_receiver(
-        x, status, receiver, decompress, [&](const ContentReceiver &out) {
-            auto ret                       = true;
-            auto exceed_payload_max_length = false;
-
-            if (is_chunked_transfer_encoding(x.headers)) {
-                ret = read_content_chunked(strm, out);
-            } else if (!has_header(x.headers, "Content-Length")) {
-                ret = read_content_without_length(strm, out);
-            } else {
-                auto len = get_header_value<uint64_t>(x.headers, "Content-Length");
-                if (len > payload_max_length) {
-                    exceed_payload_max_length = true;
-                    skip_content_with_length(strm, len);
-                    ret = false;
-                } else if (len > 0) {
-                    ret = read_content_with_length(strm, len, progress, out);
-                }
-            }
+  return prepare_content_receiver(
+      x, status, std::move(receiver), decompress,
+      [&](const ContentReceiverWithProgress &out) {
+        auto ret = true;
+        auto exceed_payload_max_length = false;
+
+        if (is_chunked_transfer_encoding(x.headers)) {
+          ret = read_content_chunked(strm, out);
+        } else if (!has_header(x.headers, "Content-Length")) {
+          ret = read_content_without_length(strm, out);
+        } else {
+          auto len = get_header_value<uint64_t>(x.headers, "Content-Length");
+          if (len > payload_max_length) {
+            exceed_payload_max_length = true;
+            skip_content_with_length(strm, len);
+            ret = false;
+          } else if (len > 0) {
+            ret = read_content_with_length(strm, len, std::move(progress), out);
+          }
+        }
 
-            if (!ret) {
-                status = exceed_payload_max_length ? 413 : 400;
-            }
-            return ret;
-        });
+        if (!ret) { status = exceed_payload_max_length ? 413 : 400; }
+        return ret;
+      });
+} // namespace detail
+
+inline ssize_t write_headers(Stream &strm, const Headers &headers) {
+  ssize_t write_len = 0;
+  for (const auto &x : headers) {
+    auto len =
+        strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str());
+    if (len < 0) { return len; }
+    write_len += len;
+  }
+  auto len = strm.write("\r\n");
+  if (len < 0) { return len; }
+  write_len += len;
+  return write_len;
+}
+
+inline bool write_data(Stream &strm, const char *d, size_t l) {
+  size_t offset = 0;
+  while (offset < l) {
+    auto length = strm.write(d + offset, l - offset);
+    if (length < 0) { return false; }
+    offset += static_cast<size_t>(length);
+  }
+  return true;
 }
 
 template <typename T>
-inline ssize_t write_headers(Stream &strm, const T &info, const Headers &headers) {
-    ssize_t write_len = 0;
-    for (const auto &x : info.headers) {
-        if (x.first == "EXCEPTION_WHAT") {
-            continue;
-        }
-        auto len = strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str());
-        if (len < 0) {
-            return len;
-        }
-        write_len += len;
+inline bool write_content(Stream &strm, const ContentProvider &content_provider,
+                          size_t offset, size_t length, T is_shutting_down,
+                          Error &error) {
+  size_t end_offset = offset + length;
+  auto ok = true;
+  DataSink data_sink;
+
+  data_sink.write = [&](const char *d, size_t l) -> bool {
+    if (ok) {
+      if (write_data(strm, d, l)) {
+        offset += l;
+      } else {
+        ok = false;
+      }
     }
-    for (const auto &x : headers) {
-        auto len = strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str());
-        if (len < 0) {
-            return len;
-        }
-        write_len += len;
+    return ok;
+  };
+
+  data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
+
+  while (offset < end_offset && !is_shutting_down()) {
+    if (!content_provider(offset, end_offset - offset, data_sink)) {
+      error = Error::Canceled;
+      return false;
     }
-    auto len = strm.write("\r\n");
-    if (len < 0) {
-        return len;
+    if (!ok) {
+      error = Error::Write;
+      return false;
     }
-    write_len += len;
-    return write_len;
+  }
+
+  error = Error::Success;
+  return true;
 }
 
-inline bool write_data(Stream &strm, const char *d, size_t l) {
-    size_t offset = 0;
-    while (offset < l) {
-        auto length = strm.write(d + offset, l - offset);
-        if (length < 0) {
-            return false;
-        }
-        offset += static_cast<size_t>(length);
-    }
-    return true;
+template <typename T>
+inline bool write_content(Stream &strm, const ContentProvider &content_provider,
+                          size_t offset, size_t length,
+                          const T &is_shutting_down) {
+  auto error = Error::Success;
+  return write_content(strm, content_provider, offset, length, is_shutting_down,
+                       error);
 }
 
 template <typename T>
-inline ssize_t write_content(Stream &strm,
-                             ContentProvider content_provider,
-                             size_t offset,
-                             size_t length,
-                             T is_shutting_down) {
-    size_t begin_offset = offset;
-    size_t end_offset   = offset + length;
-    auto ok             = true;
-    DataSink data_sink;
-
-    data_sink.write = [&](const char *d, size_t l) {
-        if (ok) {
-            offset += l;
-            if (!write_data(strm, d, l)) {
-                ok = false;
-            }
-        }
-    };
+inline bool
+write_content_without_length(Stream &strm,
+                             const ContentProvider &content_provider,
+                             const T &is_shutting_down) {
+  size_t offset = 0;
+  auto data_available = true;
+  auto ok = true;
+  DataSink data_sink;
+
+  data_sink.write = [&](const char *d, size_t l) -> bool {
+    if (ok) {
+      offset += l;
+      if (!write_data(strm, d, l)) { ok = false; }
+    }
+    return ok;
+  };
 
-    data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
+  data_sink.done = [&](void) { data_available = false; };
 
-    while (offset < end_offset && !is_shutting_down()) {
-        if (!content_provider(offset, end_offset - offset, data_sink)) {
-            return -1;
-        }
-        if (!ok) {
-            return -1;
+  data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
+
+  while (data_available && !is_shutting_down()) {
+    if (!content_provider(offset, 0, data_sink)) { return false; }
+    if (!ok) { return false; }
+  }
+  return true;
+}
+
+template <typename T, typename U>
+inline bool
+write_content_chunked(Stream &strm, const ContentProvider &content_provider,
+                      const T &is_shutting_down, U &compressor, Error &error) {
+  size_t offset = 0;
+  auto data_available = true;
+  auto ok = true;
+  DataSink data_sink;
+
+  data_sink.write = [&](const char *d, size_t l) -> bool {
+    if (ok) {
+      data_available = l > 0;
+      offset += l;
+
+      std::string payload;
+      if (compressor.compress(d, l, false,
+                              [&](const char *data, size_t data_len) {
+                                payload.append(data, data_len);
+                                return true;
+                              })) {
+        if (!payload.empty()) {
+          // Emit chunked response header and footer for each chunk
+          auto chunk =
+              from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
+          if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; }
         }
+      } else {
+        ok = false;
+      }
+    }
+    return ok;
+  };
+
+  data_sink.done = [&](void) {
+    if (!ok) { return; }
+
+    data_available = false;
+
+    std::string payload;
+    if (!compressor.compress(nullptr, 0, true,
+                             [&](const char *data, size_t data_len) {
+                               payload.append(data, data_len);
+                               return true;
+                             })) {
+      ok = false;
+      return;
+    }
+
+    if (!payload.empty()) {
+      // Emit chunked response header and footer for each chunk
+      auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
+      if (!write_data(strm, chunk.data(), chunk.size())) {
+        ok = false;
+        return;
+      }
+    }
+
+    static const std::string done_marker("0\r\n\r\n");
+    if (!write_data(strm, done_marker.data(), done_marker.size())) {
+      ok = false;
+    }
+  };
+
+  data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
+
+  while (data_available && !is_shutting_down()) {
+    if (!content_provider(offset, 0, data_sink)) {
+      error = Error::Canceled;
+      return false;
     }
+    if (!ok) {
+      error = Error::Write;
+      return false;
+    }
+  }
+
+  error = Error::Success;
+  return true;
+}
 
-    return static_cast<ssize_t>(offset - begin_offset);
+template <typename T, typename U>
+inline bool write_content_chunked(Stream &strm,
+                                  const ContentProvider &content_provider,
+                                  const T &is_shutting_down, U &compressor) {
+  auto error = Error::Success;
+  return write_content_chunked(strm, content_provider, is_shutting_down,
+                               compressor, error);
 }
 
 template <typename T>
-inline ssize_t write_content_without_length(Stream &strm,
-                                            ContentProvider content_provider,
-                                            T is_shutting_down) {
-    size_t offset       = 0;
-    auto data_available = true;
-    auto ok             = true;
-    DataSink data_sink;
-
-    data_sink.write = [&](const char *d, size_t l) {
-        if (ok) {
-            offset += l;
-            if (!write_data(strm, d, l)) {
-                ok = false;
-            }
-        }
-    };
+inline bool redirect(T &cli, Request &req, Response &res,
+                     const std::string &path, const std::string &location,
+                     Error &error) {
+  Request new_req = req;
+  new_req.path = path;
+  new_req.redirect_count_ -= 1;
+
+  if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) {
+    new_req.method = "GET";
+    new_req.body.clear();
+    new_req.headers.clear();
+  }
+
+  Response new_res;
+
+  auto ret = cli.send(new_req, new_res, error);
+  if (ret) {
+    req = new_req;
+    res = new_res;
+    res.location = location;
+  }
+  return ret;
+}
+
+inline std::string params_to_query_str(const Params &params) {
+  std::string query;
+
+  for (auto it = params.begin(); it != params.end(); ++it) {
+    if (it != params.begin()) { query += "&"; }
+    query += it->first;
+    query += "=";
+    query += encode_query_param(it->second);
+  }
+  return query;
+}
+
+inline void parse_query_text(const std::string &s, Params &params) {
+  std::set<std::string> cache;
+  split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) {
+    std::string kv(b, e);
+    if (cache.find(kv) != cache.end()) { return; }
+    cache.insert(kv);
+
+    std::string key;
+    std::string val;
+    split(b, e, '=', [&](const char *b2, const char *e2) {
+      if (key.empty()) {
+        key.assign(b2, e2);
+      } else {
+        val.assign(b2, e2);
+      }
+    });
+
+    if (!key.empty()) {
+      params.emplace(decode_url(key, true), decode_url(val, true));
+    }
+  });
+}
+
+inline bool parse_multipart_boundary(const std::string &content_type,
+                                     std::string &boundary) {
+  auto pos = content_type.find("boundary=");
+  if (pos == std::string::npos) { return false; }
+  boundary = content_type.substr(pos + 9);
+  if (boundary.length() >= 2 && boundary.front() == '"' &&
+      boundary.back() == '"') {
+    boundary = boundary.substr(1, boundary.size() - 2);
+  }
+  return !boundary.empty();
+}
+
+#ifdef CPPHTTPLIB_NO_EXCEPTIONS
+inline bool parse_range_header(const std::string &s, Ranges &ranges) {
+#else
+inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
+#endif
+  static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))");
+  std::smatch m;
+  if (std::regex_match(s, m, re_first_range)) {
+    auto pos = static_cast<size_t>(m.position(1));
+    auto len = static_cast<size_t>(m.length(1));
+    bool all_valid_ranges = true;
+    split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
+      if (!all_valid_ranges) return;
+      static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))");
+      std::cmatch cm;
+      if (std::regex_match(b, e, cm, re_another_range)) {
+        ssize_t first = -1;
+        if (!cm.str(1).empty()) {
+          first = static_cast<ssize_t>(std::stoll(cm.str(1)));
+        }
+
+        ssize_t last = -1;
+        if (!cm.str(2).empty()) {
+          last = static_cast<ssize_t>(std::stoll(cm.str(2)));
+        }
+
+        if (first != -1 && last != -1 && first > last) {
+          all_valid_ranges = false;
+          return;
+        }
+        ranges.emplace_back(std::make_pair(first, last));
+      }
+    });
+    return all_valid_ranges;
+  }
+  return false;
+#ifdef CPPHTTPLIB_NO_EXCEPTIONS
+}
+#else
+} catch (...) { return false; }
+#endif
 
-    data_sink.done = [&](void) { data_available = false; };
+class MultipartFormDataParser {
+public:
+  MultipartFormDataParser() = default;
 
-    data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
+  void set_boundary(std::string &&boundary) { boundary_ = boundary; }
 
-    while (data_available && !is_shutting_down()) {
-        if (!content_provider(offset, 0, data_sink)) {
-            return -1;
-        }
-        if (!ok) {
-            return -1;
-        }
-    }
+  bool is_valid() const { return is_valid_; }
 
-    return static_cast<ssize_t>(offset);
-}
+  bool parse(const char *buf, size_t n, const ContentReceiver &content_callback,
+             const MultipartContentHeader &header_callback) {
 
-template <typename T, typename U>
-inline ssize_t write_content_chunked(Stream &strm,
-                                     ContentProvider content_provider,
-                                     T is_shutting_down,
-                                     U &compressor) {
-    size_t offset                = 0;
-    auto data_available          = true;
-    ssize_t total_written_length = 0;
-    auto ok                      = true;
-    DataSink data_sink;
-
-    data_sink.write = [&](const char *d, size_t l) {
-        if (!ok) {
-            return;
-        }
+    // TODO: support 'filename*'
+    static const std::regex re_content_disposition(
+        R"~(^Content-Disposition:\s*form-data;\s*name="(.*?)"(?:;\s*filename="(.*?)")?(?:;\s*filename\*=\S+)?\s*$)~",
+        std::regex_constants::icase);
 
-        data_available = l > 0;
-        offset += l;
+    static const std::string dash_ = "--";
+    static const std::string crlf_ = "\r\n";
 
-        std::string payload;
-        if (!compressor.compress(d, l, false, [&](const char *data, size_t data_len) {
-                payload.append(data, data_len);
-                return true;
-            })) {
-            ok = false;
-            return;
-        }
+    buf_append(buf, n);
 
-        if (!payload.empty()) {
-            // Emit chunked response header and footer for each chunk
-            auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
-            if (write_data(strm, chunk.data(), chunk.size())) {
-                total_written_length += chunk.size();
-            } else {
-                ok = false;
-                return;
+    while (buf_size() > 0) {
+      switch (state_) {
+      case 0: { // Initial boundary
+        auto pattern = dash_ + boundary_ + crlf_;
+        if (pattern.size() > buf_size()) { return true; }
+        if (!buf_start_with(pattern)) { return false; }
+        buf_erase(pattern.size());
+        state_ = 1;
+        break;
+      }
+      case 1: { // New entry
+        clear_file_info();
+        state_ = 2;
+        break;
+      }
+      case 2: { // Headers
+        auto pos = buf_find(crlf_);
+        if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
+        while (pos < buf_size()) {
+          // Empty line
+          if (pos == 0) {
+            if (!header_callback(file_)) {
+              is_valid_ = false;
+              return false;
             }
-        }
-    };
+            buf_erase(crlf_.size());
+            state_ = 3;
+            break;
+          }
+
+          static const std::string header_name = "content-type:";
+          const auto header = buf_head(pos);
+          if (start_with_case_ignore(header, header_name)) {
+            file_.content_type = trim_copy(header.substr(header_name.size()));
+          } else {
+            std::smatch m;
+            if (std::regex_match(header, m, re_content_disposition)) {
+              file_.name = m[1];
+              file_.filename = m[2];
+            }
+          }
 
-    data_sink.done = [&](void) {
-        if (!ok) {
-            return;
+          buf_erase(pos + crlf_.size());
+          pos = buf_find(crlf_);
         }
+        if (state_ != 3) { return true; }
+        break;
+      }
+      case 3: { // Body
+        {
+          auto pattern = crlf_ + dash_;
+          if (pattern.size() > buf_size()) { return true; }
 
-        data_available = false;
-
-        std::string payload;
-        if (!compressor.compress(nullptr, 0, true, [&](const char *data, size_t data_len) {
-                payload.append(data, data_len);
-                return true;
-            })) {
-            ok = false;
-            return;
-        }
+          auto pos = buf_find(pattern);
 
-        if (!payload.empty()) {
-            // Emit chunked response header and footer for each chunk
-            auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
-            if (write_data(strm, chunk.data(), chunk.size())) {
-                total_written_length += chunk.size();
-            } else {
-                ok = false;
-                return;
-            }
-        }
+          if (!content_callback(buf_data(), pos)) {
+            is_valid_ = false;
+            return false;
+          }
 
-        static const std::string done_marker("0\r\n\r\n");
-        if (write_data(strm, done_marker.data(), done_marker.size())) {
-            total_written_length += done_marker.size();
-        } else {
-            ok = false;
+          buf_erase(pos);
         }
-    };
+        {
+          auto pattern = crlf_ + dash_ + boundary_;
+          if (pattern.size() > buf_size()) { return true; }
+
+          auto pos = buf_find(pattern);
+          if (pos < buf_size()) {
+            if (!content_callback(buf_data(), pos)) {
+              is_valid_ = false;
+              return false;
+            }
 
-    data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
+            buf_erase(pos + pattern.size());
+            state_ = 4;
+          } else {
+            if (!content_callback(buf_data(), pattern.size())) {
+              is_valid_ = false;
+              return false;
+            }
 
-    while (data_available && !is_shutting_down()) {
-        if (!content_provider(offset, 0, data_sink)) {
-            return -1;
+            buf_erase(pattern.size());
+          }
         }
-        if (!ok) {
-            return -1;
+        break;
+      }
+      case 4: { // Boundary
+        if (crlf_.size() > buf_size()) { return true; }
+        if (buf_start_with(crlf_)) {
+          buf_erase(crlf_.size());
+          state_ = 1;
+        } else {
+          auto pattern = dash_ + crlf_;
+          if (pattern.size() > buf_size()) { return true; }
+          if (buf_start_with(pattern)) {
+            buf_erase(pattern.size());
+            is_valid_ = true;
+            state_ = 5;
+          } else {
+            return true;
+          }
         }
+        break;
+      }
+      case 5: { // Done
+        is_valid_ = false;
+        return false;
+      }
+      }
     }
 
-    return total_written_length;
-}
-
-template <typename T>
-inline bool redirect(T &cli, const Request &req, Response &res, const std::string &path) {
-    Request new_req = req;
-    new_req.path    = path;
-    new_req.redirect_count -= 1;
-
-    if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) {
-        new_req.method = "GET";
-        new_req.body.clear();
-        new_req.headers.clear();
+    return true;
+  }
+
+private:
+  void clear_file_info() {
+    file_.name.clear();
+    file_.filename.clear();
+    file_.content_type.clear();
+  }
+
+  bool start_with_case_ignore(const std::string &a,
+                              const std::string &b) const {
+    if (a.size() < b.size()) { return false; }
+    for (size_t i = 0; i < b.size(); i++) {
+      if (::tolower(a[i]) != ::tolower(b[i])) { return false; }
     }
+    return true;
+  }
 
-    Response new_res;
-
-    auto ret = cli.send(new_req, new_res);
-    if (ret) {
-        res = new_res;
-    }
-    return ret;
-}
+  std::string boundary_;
 
-inline std::string params_to_query_str(const Params &params) {
-    std::string query;
+  size_t state_ = 0;
+  bool is_valid_ = false;
+  MultipartFormData file_;
 
-    for (auto it = params.begin(); it != params.end(); ++it) {
-        if (it != params.begin()) {
-            query += "&";
-        }
-        query += it->first;
-        query += "=";
-        query += encode_url(it->second);
+  // Buffer
+  bool start_with(const std::string &a, size_t spos, size_t epos,
+                  const std::string &b) const {
+    if (epos - spos < b.size()) { return false; }
+    for (size_t i = 0; i < b.size(); i++) {
+      if (a[i + spos] != b[i]) { return false; }
     }
-    return query;
-}
-
-inline void parse_query_text(const std::string &s, Params &params) {
-    split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) {
-        std::string key;
-        std::string val;
-        split(b, e, '=', [&](const char *b2, const char *e2) {
-            if (key.empty()) {
-                key.assign(b2, e2);
-            } else {
-                val.assign(b2, e2);
-            }
-        });
-
-        if (!key.empty()) {
-            params.emplace(decode_url(key, true), decode_url(val, true));
-        }
-    });
-}
+    return true;
+  }
 
-inline bool parse_multipart_boundary(const std::string &content_type, std::string &boundary) {
-    auto pos = content_type.find("boundary=");
-    if (pos == std::string::npos) {
-        return false;
-    }
-    boundary = content_type.substr(pos + 9);
-    if (boundary.length() >= 2 && boundary.front() == '"' && boundary.back() == '"') {
-        boundary = boundary.substr(1, boundary.size() - 2);
-    }
-    return !boundary.empty();
-}
+  size_t buf_size() const { return buf_epos_ - buf_spos_; }
 
-inline bool parse_range_header(const std::string &s, Ranges &ranges) {
-    static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))");
-    std::smatch m;
-    if (std::regex_match(s, m, re_first_range)) {
-        auto pos              = static_cast<size_t>(m.position(1));
-        auto len              = static_cast<size_t>(m.length(1));
-        bool all_valid_ranges = true;
-        split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
-            if (!all_valid_ranges)
-                return;
-            static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))");
-            std::cmatch cm;
-            if (std::regex_match(b, e, cm, re_another_range)) {
-                ssize_t first = -1;
-                if (!cm.str(1).empty()) {
-                    first = static_cast<ssize_t>(std::stoll(cm.str(1)));
-                }
-
-                ssize_t last = -1;
-                if (!cm.str(2).empty()) {
-                    last = static_cast<ssize_t>(std::stoll(cm.str(2)));
-                }
-
-                if (first != -1 && last != -1 && first > last) {
-                    all_valid_ranges = false;
-                    return;
-                }
-                ranges.emplace_back(std::make_pair(first, last));
-            }
-        });
-        return all_valid_ranges;
-    }
-    return false;
-}
+  const char *buf_data() const { return &buf_[buf_spos_]; }
 
-class MultipartFormDataParser {
-  public:
-    MultipartFormDataParser() = default;
+  std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); }
 
-    void set_boundary(std::string &&boundary) { boundary_ = boundary; }
+  bool buf_start_with(const std::string &s) const {
+    return start_with(buf_, buf_spos_, buf_epos_, s);
+  }
 
-    bool is_valid() const { return is_valid_; }
+  size_t buf_find(const std::string &s) const {
+    auto c = s.front();
 
-    template <typename T, typename U>
-    bool parse(const char *buf, size_t n, T content_callback, U header_callback) {
+    size_t off = buf_spos_;
+    while (off < buf_epos_) {
+      auto pos = off;
+      while (true) {
+        if (pos == buf_epos_) { return buf_size(); }
+        if (buf_[pos] == c) { break; }
+        pos++;
+      }
 
-        static const std::regex re_content_disposition(
-            "^Content-Disposition:\\s*form-data;\\s*name=\"(.*?)\"(?:;\\s*filename="
-            "\"(.*?)\")?\\s*$",
-            std::regex_constants::icase);
-        static const std::string dash_ = "--";
-        static const std::string crlf_ = "\r\n";
+      auto remaining_size = buf_epos_ - pos;
+      if (s.size() > remaining_size) { return buf_size(); }
 
-        buf_.append(buf, n);  // TODO: performance improvement
+      if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; }
 
-        while (!buf_.empty()) {
-            switch (state_) {
-                case 0:
-                {  // Initial boundary
-                    auto pattern = dash_ + boundary_ + crlf_;
-                    if (pattern.size() > buf_.size()) {
-                        return true;
-                    }
-                    auto pos = buf_.find(pattern);
-                    if (pos != 0) {
-                        return false;
-                    }
-                    buf_.erase(0, pattern.size());
-                    off_ += pattern.size();
-                    state_ = 1;
-                    break;
-                }
-                case 1:
-                {  // New entry
-                    clear_file_info();
-                    state_ = 2;
-                    break;
-                }
-                case 2:
-                {  // Headers
-                    auto pos = buf_.find(crlf_);
-                    while (pos != std::string::npos) {
-                        // Empty line
-                        if (pos == 0) {
-                            if (!header_callback(file_)) {
-                                is_valid_ = false;
-                                return false;
-                            }
-                            buf_.erase(0, crlf_.size());
-                            off_ += crlf_.size();
-                            state_ = 3;
-                            break;
-                        }
-
-                        static const std::string header_name = "content-type:";
-                        const auto header                    = buf_.substr(0, pos);
-                        if (start_with(header, header_name)) {
-                            file_.content_type = trim_copy(header.substr(header_name.size()));
-                        } else {
-                            std::smatch m;
-                            if (std::regex_match(header, m, re_content_disposition)) {
-                                file_.name     = m[1];
-                                file_.filename = m[2];
-                            }
-                        }
-
-                        buf_.erase(0, pos + crlf_.size());
-                        off_ += pos + crlf_.size();
-                        pos = buf_.find(crlf_);
-                    }
-                    if (state_ != 3) {
-                        return true;
-                    }
-                    break;
-                }
-                case 3:
-                {  // Body
-                    {
-                        auto pattern = crlf_ + dash_;
-                        if (pattern.size() > buf_.size()) {
-                            return true;
-                        }
-
-                        auto pos = buf_.find(pattern);
-                        if (pos == std::string::npos) {
-                            pos = buf_.size();
-                            while (pos > 0) {
-                                auto c = buf_[pos - 1];
-                                if (c != '\r' && c != '\n' && c != '-') {
-                                    break;
-                                }
-                                pos--;
-                            }
-                        }
-
-                        if (!content_callback(buf_.data(), pos)) {
-                            is_valid_ = false;
-                            return false;
-                        }
-
-                        off_ += pos;
-                        buf_.erase(0, pos);
-                    }
+      off = pos + 1;
+    }
 
-                    {
-                        auto pattern = crlf_ + dash_ + boundary_;
-                        if (pattern.size() > buf_.size()) {
-                            return true;
-                        }
-
-                        auto pos = buf_.find(pattern);
-                        if (pos != std::string::npos) {
-                            if (!content_callback(buf_.data(), pos)) {
-                                is_valid_ = false;
-                                return false;
-                            }
-
-                            off_ += pos + pattern.size();
-                            buf_.erase(0, pos + pattern.size());
-                            state_ = 4;
-                        } else {
-                            if (!content_callback(buf_.data(), pattern.size())) {
-                                is_valid_ = false;
-                                return false;
-                            }
-
-                            off_ += pattern.size();
-                            buf_.erase(0, pattern.size());
-                        }
-                    }
-                    break;
-                }
-                case 4:
-                {  // Boundary
-                    if (crlf_.size() > buf_.size()) {
-                        return true;
-                    }
-                    if (buf_.compare(0, crlf_.size(), crlf_) == 0) {
-                        buf_.erase(0, crlf_.size());
-                        off_ += crlf_.size();
-                        state_ = 1;
-                    } else {
-                        auto pattern = dash_ + crlf_;
-                        if (pattern.size() > buf_.size()) {
-                            return true;
-                        }
-                        if (buf_.compare(0, pattern.size(), pattern) == 0) {
-                            buf_.erase(0, pattern.size());
-                            off_ += pattern.size();
-                            is_valid_ = true;
-                            state_    = 5;
-                        } else {
-                            return true;
-                        }
-                    }
-                    break;
-                }
-                case 5:
-                {  // Done
-                    is_valid_ = false;
-                    return false;
-                }
-            }
-        }
+    return buf_size();
+  }
 
-        return true;
+  void buf_append(const char *data, size_t n) {
+    auto remaining_size = buf_size();
+    if (remaining_size > 0 && buf_spos_ > 0) {
+      for (size_t i = 0; i < remaining_size; i++) {
+        buf_[i] = buf_[buf_spos_ + i];
+      }
     }
+    buf_spos_ = 0;
+    buf_epos_ = remaining_size;
 
-  private:
-    void clear_file_info() {
-        file_.name.clear();
-        file_.filename.clear();
-        file_.content_type.clear();
+    if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); }
+
+    for (size_t i = 0; i < n; i++) {
+      buf_[buf_epos_ + i] = data[i];
     }
+    buf_epos_ += n;
+  }
 
-    std::string boundary_;
+  void buf_erase(size_t size) { buf_spos_ += size; }
 
-    std::string buf_;
-    size_t state_  = 0;
-    bool is_valid_ = false;
-    size_t off_    = 0;
-    MultipartFormData file_;
+  std::string buf_;
+  size_t buf_spos_ = 0;
+  size_t buf_epos_ = 0;
 };
 
 inline std::string to_lower(const char *beg, const char *end) {
-    std::string out;
-    auto it = beg;
-    while (it != end) {
-        out += static_cast<char>(::tolower(*it));
-        it++;
-    }
-    return out;
+  std::string out;
+  auto it = beg;
+  while (it != end) {
+    out += static_cast<char>(::tolower(*it));
+    it++;
+  }
+  return out;
 }
 
 inline std::string make_multipart_data_boundary() {
-    static const char data[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+  static const char data[] =
+      "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
 
-    std::random_device seed_gen;
-    std::mt19937 engine(seed_gen());
+  // std::random_device might actually be deterministic on some
+  // platforms, but due to lack of support in the c++ standard library,
+  // doing better requires either some ugly hacks or breaking portability.
+  std::random_device seed_gen;
 
-    std::string result = "--cpp-httplib-multipart-data-";
+  // Request 128 bits of entropy for initialization
+  std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
+  std::mt19937 engine(seed_sequence);
 
-    for (auto i = 0; i < 16; i++) {
-        result += data[engine() % (sizeof(data) - 1)];
-    }
+  std::string result = "--cpp-httplib-multipart-data-";
 
-    return result;
-}
+  for (auto i = 0; i < 16; i++) {
+    result += data[engine() % (sizeof(data) - 1)];
+  }
 
-inline std::pair<size_t, size_t> get_range_offset_and_length(const Request &req,
-                                                             size_t content_length,
-                                                             size_t index) {
-    auto r = req.ranges[index];
+  return result;
+}
 
-    if (r.first == -1 && r.second == -1) {
-        return std::make_pair(0, content_length);
-    }
+inline std::pair<size_t, size_t>
+get_range_offset_and_length(const Request &req, size_t content_length,
+                            size_t index) {
+  auto r = req.ranges[index];
 
-    auto slen = static_cast<ssize_t>(content_length);
+  if (r.first == -1 && r.second == -1) {
+    return std::make_pair(0, content_length);
+  }
 
-    if (r.first == -1) {
-        r.first  = slen - r.second;
-        r.second = slen - 1;
-    }
+  auto slen = static_cast<ssize_t>(content_length);
 
-    if (r.second == -1) {
-        r.second = slen - 1;
-    }
+  if (r.first == -1) {
+    r.first = (std::max)(static_cast<ssize_t>(0), slen - r.second);
+    r.second = slen - 1;
+  }
 
-    return std::make_pair(r.first, r.second - r.first + 1);
+  if (r.second == -1) { r.second = slen - 1; }
+  return std::make_pair(r.first, static_cast<size_t>(r.second - r.first) + 1);
 }
 
-inline std::string make_content_range_header_field(size_t offset,
-                                                   size_t length,
+inline std::string make_content_range_header_field(size_t offset, size_t length,
                                                    size_t content_length) {
-    std::string field = "bytes ";
-    field += std::to_string(offset);
-    field += "-";
-    field += std::to_string(offset + length - 1);
-    field += "/";
-    field += std::to_string(content_length);
-    return field;
+  std::string field = "bytes ";
+  field += std::to_string(offset);
+  field += "-";
+  field += std::to_string(offset + length - 1);
+  field += "/";
+  field += std::to_string(content_length);
+  return field;
 }
 
 template <typename SToken, typename CToken, typename Content>
-bool process_multipart_ranges_data(const Request &req,
-                                   Response &res,
+bool process_multipart_ranges_data(const Request &req, Response &res,
                                    const std::string &boundary,
                                    const std::string &content_type,
-                                   SToken stoken,
-                                   CToken ctoken,
+                                   SToken stoken, CToken ctoken,
                                    Content content) {
-    for (size_t i = 0; i < req.ranges.size(); i++) {
-        ctoken("--");
-        stoken(boundary);
-        ctoken("\r\n");
-        if (!content_type.empty()) {
-            ctoken("Content-Type: ");
-            stoken(content_type);
-            ctoken("\r\n");
-        }
-
-        auto offsets = get_range_offset_and_length(req, res.body.size(), i);
-        auto offset  = offsets.first;
-        auto length  = offsets.second;
-
-        ctoken("Content-Range: ");
-        stoken(make_content_range_header_field(offset, length, res.body.size()));
-        ctoken("\r\n");
-        ctoken("\r\n");
-        if (!content(offset, length)) {
-            return false;
-        }
-        ctoken("\r\n");
-    }
-
+  for (size_t i = 0; i < req.ranges.size(); i++) {
     ctoken("--");
     stoken(boundary);
-    ctoken("--\r\n");
-
-    return true;
-}
-
-inline std::string make_multipart_ranges_data(const Request &req,
-                                              Response &res,
-                                              const std::string &boundary,
-                                              const std::string &content_type) {
-    std::string data;
-
-    process_multipart_ranges_data(
-        req, res, boundary, content_type, [&](const std::string &token) { data += token; },
-        [&](const char *token) { data += token; },
-        [&](size_t offset, size_t length) {
-            data += res.body.substr(offset, length);
-            return true;
-        });
-
-    return data;
+    ctoken("\r\n");
+    if (!content_type.empty()) {
+      ctoken("Content-Type: ");
+      stoken(content_type);
+      ctoken("\r\n");
+    }
+
+    auto offsets = get_range_offset_and_length(req, res.body.size(), i);
+    auto offset = offsets.first;
+    auto length = offsets.second;
+
+    ctoken("Content-Range: ");
+    stoken(make_content_range_header_field(offset, length, res.body.size()));
+    ctoken("\r\n");
+    ctoken("\r\n");
+    if (!content(offset, length)) { return false; }
+    ctoken("\r\n");
+  }
+
+  ctoken("--");
+  stoken(boundary);
+  ctoken("--\r\n");
+
+  return true;
+}
+
+inline bool make_multipart_ranges_data(const Request &req, Response &res,
+                                       const std::string &boundary,
+                                       const std::string &content_type,
+                                       std::string &data) {
+  return process_multipart_ranges_data(
+      req, res, boundary, content_type,
+      [&](const std::string &token) { data += token; },
+      [&](const char *token) { data += token; },
+      [&](size_t offset, size_t length) {
+        if (offset < res.body.size()) {
+          data += res.body.substr(offset, length);
+          return true;
+        }
+        return false;
+      });
 }
 
-inline size_t get_multipart_ranges_data_length(const Request &req,
-                                               Response &res,
-                                               const std::string &boundary,
-                                               const std::string &content_type) {
-    size_t data_length = 0;
+inline size_t
+get_multipart_ranges_data_length(const Request &req, Response &res,
+                                 const std::string &boundary,
+                                 const std::string &content_type) {
+  size_t data_length = 0;
 
-    process_multipart_ranges_data(
-        req, res, boundary, content_type,
-        [&](const std::string &token) { data_length += token.size(); },
-        [&](const char *token) { data_length += strlen(token); },
-        [&](size_t /*offset*/, size_t length) {
-            data_length += length;
-            return true;
-        });
+  process_multipart_ranges_data(
+      req, res, boundary, content_type,
+      [&](const std::string &token) { data_length += token.size(); },
+      [&](const char *token) { data_length += strlen(token); },
+      [&](size_t /*offset*/, size_t length) {
+        data_length += length;
+        return true;
+      });
 
-    return data_length;
+  return data_length;
 }
 
 template <typename T>
-inline bool write_multipart_ranges_data(Stream &strm,
-                                        const Request &req,
+inline bool write_multipart_ranges_data(Stream &strm, const Request &req,
                                         Response &res,
                                         const std::string &boundary,
                                         const std::string &content_type,
-                                        T is_shutting_down) {
-    return process_multipart_ranges_data(
-        req, res, boundary, content_type, [&](const std::string &token) { strm.write(token); },
-        [&](const char *token) { strm.write(token); },
-        [&](size_t offset, size_t length) {
-            return write_content(strm, res.content_provider_, offset, length, is_shutting_down) >=
-                   0;
-        });
+                                        const T &is_shutting_down) {
+  return process_multipart_ranges_data(
+      req, res, boundary, content_type,
+      [&](const std::string &token) { strm.write(token); },
+      [&](const char *token) { strm.write(token); },
+      [&](size_t offset, size_t length) {
+        return write_content(strm, res.content_provider_, offset, length,
+                             is_shutting_down);
+      });
 }
 
-inline std::pair<size_t, size_t> get_range_offset_and_length(const Request &req,
-                                                             const Response &res,
-                                                             size_t index) {
-    auto r = req.ranges[index];
+inline std::pair<size_t, size_t>
+get_range_offset_and_length(const Request &req, const Response &res,
+                            size_t index) {
+  auto r = req.ranges[index];
 
-    if (r.second == -1) {
-        r.second = static_cast<ssize_t>(res.content_length_) - 1;
-    }
+  if (r.second == -1) {
+    r.second = static_cast<ssize_t>(res.content_length_) - 1;
+  }
 
-    return std::make_pair(r.first, r.second - r.first + 1);
+  return std::make_pair(r.first, r.second - r.first + 1);
 }
 
 inline bool expect_content(const Request &req) {
-    if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
-        req.method == "PRI" || req.method == "DELETE") {
-        return true;
-    }
-    // TODO: check if Content-Length is set
-    return false;
+  if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
+      req.method == "PRI" || req.method == "DELETE") {
+    return true;
+  }
+  // TODO: check if Content-Length is set
+  return false;
 }
 
 inline bool has_crlf(const char *s) {
-    auto p = s;
-    while (*p) {
-        if (*p == '\r' || *p == '\n') {
-            return true;
-        }
-        p++;
-    }
-    return false;
+  auto p = s;
+  while (*p) {
+    if (*p == '\r' || *p == '\n') { return true; }
+    p++;
+  }
+  return false;
 }
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-template <typename CTX, typename Init, typename Update, typename Final>
-inline std::string message_digest(const std::string &s,
-                                  Init init,
-                                  Update update,
-                                  Final final,
-                                  size_t digest_length) {
-    using namespace std;
+inline std::string message_digest(const std::string &s, const EVP_MD *algo) {
+  auto context = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>(
+      EVP_MD_CTX_new(), EVP_MD_CTX_free);
 
-    std::vector<unsigned char> md(digest_length, 0);
-    CTX ctx;
-    init(&ctx);
-    update(&ctx, s.data(), s.size());
-    final(md.data(), &ctx);
+  unsigned int hash_length = 0;
+  unsigned char hash[EVP_MAX_MD_SIZE];
 
-    stringstream ss;
-    for (auto c : md) {
-        ss << setfill('0') << setw(2) << hex << (unsigned int)c;
-    }
-    return ss.str();
+  EVP_DigestInit_ex(context.get(), algo, nullptr);
+  EVP_DigestUpdate(context.get(), s.c_str(), s.size());
+  EVP_DigestFinal_ex(context.get(), hash, &hash_length);
+
+  std::stringstream ss;
+  for (auto i = 0u; i < hash_length; ++i) {
+    ss << std::hex << std::setw(2) << std::setfill('0')
+       << (unsigned int)hash[i];
+  }
+
+  return ss.str();
 }
 
 inline std::string MD5(const std::string &s) {
-    return message_digest<MD5_CTX>(s, MD5_Init, MD5_Update, MD5_Final, MD5_DIGEST_LENGTH);
+  return message_digest(s, EVP_md5());
 }
 
 inline std::string SHA_256(const std::string &s) {
-    return message_digest<SHA256_CTX>(s, SHA256_Init, SHA256_Update, SHA256_Final,
-                                      SHA256_DIGEST_LENGTH);
+  return message_digest(s, EVP_sha256());
 }
 
 inline std::string SHA_512(const std::string &s) {
-    return message_digest<SHA512_CTX>(s, SHA512_Init, SHA512_Update, SHA512_Final,
-                                      SHA512_DIGEST_LENGTH);
+  return message_digest(s, EVP_sha512());
 }
 #endif
 
 #ifdef _WIN32
-#    ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
 // NOTE: This code came up with the following stackoverflow post:
 // https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store
 inline bool load_system_certs_on_windows(X509_STORE *store) {
-    auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT");
+  auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT");
 
-    if (!hStore) {
-        return false;
-    }
+  if (!hStore) { return false; }
 
-    PCCERT_CONTEXT pContext = NULL;
-    while (pContext = CertEnumCertificatesInStore(hStore, pContext)) {
-        auto encoded_cert = static_cast<const unsigned char *>(pContext->pbCertEncoded);
+  PCCERT_CONTEXT pContext = NULL;
+  while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
+         nullptr) {
+    auto encoded_cert =
+        static_cast<const unsigned char *>(pContext->pbCertEncoded);
 
-        auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
-        if (x509) {
-            X509_STORE_add_cert(store, x509);
-            X509_free(x509);
-        }
+    auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
+    if (x509) {
+      X509_STORE_add_cert(store, x509);
+      X509_free(x509);
     }
+  }
 
-    CertFreeCertificateContext(pContext);
-    CertCloseStore(hStore, 0);
+  CertFreeCertificateContext(pContext);
+  CertCloseStore(hStore, 0);
 
-    return true;
+  return true;
 }
-#    endif
+#endif
 
 class WSInit {
-  public:
-    WSInit() {
-        WSADATA wsaData;
-        WSAStartup(0x0002, &wsaData);
-    }
+public:
+  WSInit() {
+    WSADATA wsaData;
+    if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true;
+  }
 
-    ~WSInit() { WSACleanup(); }
+  ~WSInit() {
+    if (is_valid_) WSACleanup();
+  }
+
+  bool is_valid_ = false;
 };
 
 static WSInit wsinit_;
@@ -3616,1295 +4255,1521 @@ static WSInit wsinit_;
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
 inline std::pair<std::string, std::string> make_digest_authentication_header(
-    const Request &req,
-    const std::map<std::string, std::string> &auth,
-    size_t cnonce_count,
-    const std::string &cnonce,
-    const std::string &username,
-    const std::string &password,
-    bool is_proxy = false) {
-    using namespace std;
-
-    string nc;
-    {
-        stringstream ss;
-        ss << setfill('0') << setw(8) << hex << cnonce_count;
-        nc = ss.str();
-    }
-
-    auto qop = auth.at("qop");
+    const Request &req, const std::map<std::string, std::string> &auth,
+    size_t cnonce_count, const std::string &cnonce, const std::string &username,
+    const std::string &password, bool is_proxy = false) {
+  std::string nc;
+  {
+    std::stringstream ss;
+    ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count;
+    nc = ss.str();
+  }
+
+  std::string qop;
+  if (auth.find("qop") != auth.end()) {
+    qop = auth.at("qop");
     if (qop.find("auth-int") != std::string::npos) {
-        qop = "auth-int";
+      qop = "auth-int";
+    } else if (qop.find("auth") != std::string::npos) {
+      qop = "auth";
     } else {
-        qop = "auth";
+      qop.clear();
     }
+  }
 
-    std::string algo = "MD5";
-    if (auth.find("algorithm") != auth.end()) {
-        algo = auth.at("algorithm");
-    }
+  std::string algo = "MD5";
+  if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); }
 
-    string response;
-    {
-        auto H =
-            algo == "SHA-256" ? detail::SHA_256 : algo == "SHA-512" ? detail::SHA_512 : detail::MD5;
+  std::string response;
+  {
+    auto H = algo == "SHA-256"   ? detail::SHA_256
+             : algo == "SHA-512" ? detail::SHA_512
+                                 : detail::MD5;
 
-        auto A1 = username + ":" + auth.at("realm") + ":" + password;
+    auto A1 = username + ":" + auth.at("realm") + ":" + password;
 
-        auto A2 = req.method + ":" + req.path;
-        if (qop == "auth-int") {
-            A2 += ":" + H(req.body);
-        }
+    auto A2 = req.method + ":" + req.path;
+    if (qop == "auth-int") { A2 += ":" + H(req.body); }
 
-        response =
-            H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + ":" + qop + ":" + H(A2));
+    if (qop.empty()) {
+      response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2));
+    } else {
+      response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
+                   ":" + qop + ":" + H(A2));
     }
+  }
 
-    auto field = "Digest username=\"" + username + "\", realm=\"" + auth.at("realm") +
-                 "\", nonce=\"" + auth.at("nonce") + "\", uri=\"" + req.path +
-                 "\", algorithm=" + algo + ", qop=" + qop + ", nc=\"" + nc + "\", cnonce=\"" +
-                 cnonce + "\", response=\"" + response + "\"";
+  auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : "";
 
-    auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
-    return std::make_pair(key, field);
+  auto field = "Digest username=\"" + username + "\", realm=\"" +
+               auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
+               "\", uri=\"" + req.path + "\", algorithm=" + algo +
+               (qop.empty() ? ", response=\""
+                            : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" +
+                                  cnonce + "\", response=\"") +
+               response + "\"" +
+               (opaque.empty() ? "" : ", opaque=\"" + opaque + "\"");
+
+  auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
+  return std::make_pair(key, field);
 }
 #endif
 
 inline bool parse_www_authenticate(const Response &res,
                                    std::map<std::string, std::string> &auth,
                                    bool is_proxy) {
-    auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
-    if (res.has_header(auth_key)) {
-        static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~");
-        auto s         = res.get_header_value(auth_key);
-        auto pos       = s.find(' ');
-        if (pos != std::string::npos) {
-            auto type = s.substr(0, pos);
-            if (type == "Basic") {
-                return false;
-            } else if (type == "Digest") {
-                s        = s.substr(pos + 1);
-                auto beg = std::sregex_iterator(s.begin(), s.end(), re);
-                for (auto i = beg; i != std::sregex_iterator(); ++i) {
-                    auto m   = *i;
-                    auto key = s.substr(static_cast<size_t>(m.position(1)),
-                                        static_cast<size_t>(m.length(1)));
-                    auto val = m.length(2) > 0 ? s.substr(static_cast<size_t>(m.position(2)),
-                                                          static_cast<size_t>(m.length(2)))
-                                               : s.substr(static_cast<size_t>(m.position(3)),
-                                                          static_cast<size_t>(m.length(3)));
-                    auth[key] = val;
-                }
-                return true;
-            }
+  auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
+  if (res.has_header(auth_key)) {
+    static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~");
+    auto s = res.get_header_value(auth_key);
+    auto pos = s.find(' ');
+    if (pos != std::string::npos) {
+      auto type = s.substr(0, pos);
+      if (type == "Basic") {
+        return false;
+      } else if (type == "Digest") {
+        s = s.substr(pos + 1);
+        auto beg = std::sregex_iterator(s.begin(), s.end(), re);
+        for (auto i = beg; i != std::sregex_iterator(); ++i) {
+          auto m = *i;
+          auto key = s.substr(static_cast<size_t>(m.position(1)),
+                              static_cast<size_t>(m.length(1)));
+          auto val = m.length(2) > 0
+                         ? s.substr(static_cast<size_t>(m.position(2)),
+                                    static_cast<size_t>(m.length(2)))
+                         : s.substr(static_cast<size_t>(m.position(3)),
+                                    static_cast<size_t>(m.length(3)));
+          auth[key] = val;
         }
+        return true;
+      }
     }
-    return false;
+  }
+  return false;
 }
 
 // https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240
 inline std::string random_string(size_t length) {
-    auto randchar = []() -> char {
-        const char charset[] =
-            "0123456789"
-            "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-            "abcdefghijklmnopqrstuvwxyz";
-        const size_t max_index = (sizeof(charset) - 1);
-        return charset[static_cast<size_t>(rand()) % max_index];
-    };
-    std::string str(length, 0);
-    std::generate_n(str.begin(), length, randchar);
-    return str;
+  auto randchar = []() -> char {
+    const char charset[] = "0123456789"
+                           "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                           "abcdefghijklmnopqrstuvwxyz";
+    const size_t max_index = (sizeof(charset) - 1);
+    return charset[static_cast<size_t>(std::rand()) % max_index];
+  };
+  std::string str(length, 0);
+  std::generate_n(str.begin(), length, randchar);
+  return str;
 }
 
 class ContentProviderAdapter {
-  public:
-    explicit ContentProviderAdapter(ContentProviderWithoutLength &&content_provider)
-        : content_provider_(content_provider) {}
+public:
+  explicit ContentProviderAdapter(
+      ContentProviderWithoutLength &&content_provider)
+      : content_provider_(content_provider) {}
 
-    bool operator()(size_t offset, size_t, DataSink &sink) {
-        return content_provider_(offset, sink);
-    }
+  bool operator()(size_t offset, size_t, DataSink &sink) {
+    return content_provider_(offset, sink);
+  }
 
-  private:
-    ContentProviderWithoutLength content_provider_;
+private:
+  ContentProviderWithoutLength content_provider_;
 };
 
-}  // namespace detail
+} // namespace detail
 
-// Header utilities
-inline std::pair<std::string, std::string> make_range_header(Ranges ranges) {
-    std::string field = "bytes=";
-    auto i            = 0;
-    for (auto r : ranges) {
-        if (i != 0) {
-            field += ", ";
-        }
-        if (r.first != -1) {
-            field += std::to_string(r.first);
-        }
-        field += '-';
-        if (r.second != -1) {
-            field += std::to_string(r.second);
-        }
-        i++;
+inline std::string hosted_at(const char *hostname) {
+  std::vector<std::string> addrs;
+  hosted_at(hostname, addrs);
+  if (addrs.empty()) { return std::string(); }
+  return addrs[0];
+}
+
+inline void hosted_at(const char *hostname, std::vector<std::string> &addrs) {
+  struct addrinfo hints;
+  struct addrinfo *result;
+
+  memset(&hints, 0, sizeof(struct addrinfo));
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_protocol = 0;
+
+  if (getaddrinfo(hostname, nullptr, &hints, &result)) {
+#if defined __linux__ && !defined __ANDROID__
+    res_init();
+#endif
+    return;
+  }
+
+  for (auto rp = result; rp; rp = rp->ai_next) {
+    const auto &addr =
+        *reinterpret_cast<struct sockaddr_storage *>(rp->ai_addr);
+    std::string ip;
+    int dummy = -1;
+    if (detail::get_remote_ip_and_port(addr, sizeof(struct sockaddr_storage),
+                                       ip, dummy)) {
+      addrs.push_back(ip);
     }
-    return std::make_pair("Range", field);
+  }
+
+  freeaddrinfo(result);
 }
 
-inline std::pair<std::string, std::string> make_basic_authentication_header(
-    const std::string &username,
-    const std::string &password,
-    bool is_proxy = false) {
-    auto field = "Basic " + detail::base64_encode(username + ":" + password);
-    auto key   = is_proxy ? "Proxy-Authorization" : "Authorization";
-    return std::make_pair(key, field);
+inline std::string append_query_params(const char *path, const Params &params) {
+  std::string path_with_query = path;
+  const static std::regex re("[^?]+\\?.*");
+  auto delm = std::regex_match(path, re) ? '&' : '?';
+  path_with_query += delm + detail::params_to_query_str(params);
+  return path_with_query;
 }
 
-inline std::pair<std::string, std::string> make_bearer_token_authentication_header(
-    const std::string &token,
-    bool is_proxy = false) {
-    auto field = "Bearer " + token;
-    auto key   = is_proxy ? "Proxy-Authorization" : "Authorization";
-    return std::make_pair(key, field);
+// Header utilities
+inline std::pair<std::string, std::string> make_range_header(Ranges ranges) {
+  std::string field = "bytes=";
+  auto i = 0;
+  for (auto r : ranges) {
+    if (i != 0) { field += ", "; }
+    if (r.first != -1) { field += std::to_string(r.first); }
+    field += '-';
+    if (r.second != -1) { field += std::to_string(r.second); }
+    i++;
+  }
+  return std::make_pair("Range", std::move(field));
+}
+
+inline std::pair<std::string, std::string>
+make_basic_authentication_header(const std::string &username,
+                                 const std::string &password, bool is_proxy) {
+  auto field = "Basic " + detail::base64_encode(username + ":" + password);
+  auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
+  return std::make_pair(key, std::move(field));
+}
+
+inline std::pair<std::string, std::string>
+make_bearer_token_authentication_header(const std::string &token,
+                                        bool is_proxy = false) {
+  auto field = "Bearer " + token;
+  auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
+  return std::make_pair(key, std::move(field));
 }
 
 // Request implementation
 inline bool Request::has_header(const char *key) const {
-    return detail::has_header(headers, key);
+  return detail::has_header(headers, key);
 }
 
 inline std::string Request::get_header_value(const char *key, size_t id) const {
-    return detail::get_header_value(headers, key, id, "");
-}
-
-template <typename T>
-inline T Request::get_header_value(const char *key, size_t id) const {
-    return detail::get_header_value<T>(headers, key, id, 0);
+  return detail::get_header_value(headers, key, id, "");
 }
 
 inline size_t Request::get_header_value_count(const char *key) const {
-    auto r = headers.equal_range(key);
-    return static_cast<size_t>(std::distance(r.first, r.second));
+  auto r = headers.equal_range(key);
+  return static_cast<size_t>(std::distance(r.first, r.second));
 }
 
 inline void Request::set_header(const char *key, const char *val) {
-    if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
-        headers.emplace(key, val);
-    }
+  if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
+    headers.emplace(key, val);
+  }
 }
 
 inline void Request::set_header(const char *key, const std::string &val) {
-    if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) {
-        headers.emplace(key, val);
-    }
+  if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) {
+    headers.emplace(key, val);
+  }
 }
 
 inline bool Request::has_param(const char *key) const {
-    return params.find(key) != params.end();
+  return params.find(key) != params.end();
 }
 
 inline std::string Request::get_param_value(const char *key, size_t id) const {
-    auto rng = params.equal_range(key);
-    auto it  = rng.first;
-    std::advance(it, static_cast<ssize_t>(id));
-    if (it != rng.second) {
-        return it->second;
-    }
-    return std::string();
+  auto rng = params.equal_range(key);
+  auto it = rng.first;
+  std::advance(it, static_cast<ssize_t>(id));
+  if (it != rng.second) { return it->second; }
+  return std::string();
 }
 
 inline size_t Request::get_param_value_count(const char *key) const {
-    auto r = params.equal_range(key);
-    return static_cast<size_t>(std::distance(r.first, r.second));
+  auto r = params.equal_range(key);
+  return static_cast<size_t>(std::distance(r.first, r.second));
 }
 
 inline bool Request::is_multipart_form_data() const {
-    const auto &content_type = get_header_value("Content-Type");
-    return !content_type.find("multipart/form-data");
+  const auto &content_type = get_header_value("Content-Type");
+  return !content_type.rfind("multipart/form-data", 0);
 }
 
 inline bool Request::has_file(const char *key) const {
-    return files.find(key) != files.end();
+  return files.find(key) != files.end();
 }
 
 inline MultipartFormData Request::get_file_value(const char *key) const {
-    auto it = files.find(key);
-    if (it != files.end()) {
-        return it->second;
-    }
-    return MultipartFormData();
+  auto it = files.find(key);
+  if (it != files.end()) { return it->second; }
+  return MultipartFormData();
 }
 
 // Response implementation
 inline bool Response::has_header(const char *key) const {
-    return headers.find(key) != headers.end();
-}
-
-inline std::string Response::get_header_value(const char *key, size_t id) const {
-    return detail::get_header_value(headers, key, id, "");
+  return headers.find(key) != headers.end();
 }
 
-template <typename T>
-inline T Response::get_header_value(const char *key, size_t id) const {
-    return detail::get_header_value<T>(headers, key, id, 0);
+inline std::string Response::get_header_value(const char *key,
+                                              size_t id) const {
+  return detail::get_header_value(headers, key, id, "");
 }
 
 inline size_t Response::get_header_value_count(const char *key) const {
-    auto r = headers.equal_range(key);
-    return static_cast<size_t>(std::distance(r.first, r.second));
+  auto r = headers.equal_range(key);
+  return static_cast<size_t>(std::distance(r.first, r.second));
 }
 
 inline void Response::set_header(const char *key, const char *val) {
-    if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
-        headers.emplace(key, val);
-    }
+  if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
+    headers.emplace(key, val);
+  }
 }
 
 inline void Response::set_header(const char *key, const std::string &val) {
-    if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) {
-        headers.emplace(key, val);
-    }
+  if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) {
+    headers.emplace(key, val);
+  }
 }
 
 inline void Response::set_redirect(const char *url, int stat) {
-    if (!detail::has_crlf(url)) {
-        set_header("Location", url);
-        if (300 <= stat && stat < 400) {
-            this->status = stat;
-        } else {
-            this->status = 302;
-        }
+  if (!detail::has_crlf(url)) {
+    set_header("Location", url);
+    if (300 <= stat && stat < 400) {
+      this->status = stat;
+    } else {
+      this->status = 302;
     }
+  }
 }
 
 inline void Response::set_redirect(const std::string &url, int stat) {
-    set_redirect(url.c_str(), stat);
+  set_redirect(url.c_str(), stat);
 }
 
-inline void Response::set_content(const char *s, size_t n, const char *content_type) {
-    body.assign(s, n);
-    set_header("Content-Type", content_type);
-}
+inline void Response::set_content(const char *s, size_t n,
+                                  const char *content_type) {
+  body.assign(s, n);
 
-inline void Response::set_content(std::string s, const char *content_type) {
-    body = std::move(s);
-    set_header("Content-Type", content_type);
+  auto rng = headers.equal_range("Content-Type");
+  headers.erase(rng.first, rng.second);
+  set_header("Content-Type", content_type);
 }
 
-inline void Response::set_content_provider(size_t in_length,
-                                           const char *content_type,
-                                           ContentProvider provider,
-                                           const std::function<void()> &resource_releaser) {
-    assert(in_length > 0);
-    set_header("Content-Type", content_type);
-    content_length_                     = in_length;
-    content_provider_                   = std::move(provider);
-    content_provider_resource_releaser_ = resource_releaser;
-    is_chunked_content_provider         = false;
+inline void Response::set_content(const std::string &s,
+                                  const char *content_type) {
+  set_content(s.data(), s.size(), content_type);
 }
 
-inline void Response::set_content_provider(const char *content_type,
-                                           ContentProviderWithoutLength provider,
-                                           const std::function<void()> &resource_releaser) {
-    set_header("Content-Type", content_type);
-    content_length_                     = 0;
-    content_provider_                   = detail::ContentProviderAdapter(std::move(provider));
-    content_provider_resource_releaser_ = resource_releaser;
-    is_chunked_content_provider         = false;
+inline void Response::set_content_provider(
+    size_t in_length, const char *content_type, ContentProvider provider,
+    ContentProviderResourceReleaser resource_releaser) {
+  assert(in_length > 0);
+  set_header("Content-Type", content_type);
+  content_length_ = in_length;
+  content_provider_ = std::move(provider);
+  content_provider_resource_releaser_ = resource_releaser;
+  is_chunked_content_provider_ = false;
 }
 
-inline void Response::set_chunked_content_provider(const char *content_type,
-                                                   ContentProviderWithoutLength provider,
-                                                   const std::function<void()> &resource_releaser) {
-    set_header("Content-Type", content_type);
-    content_length_                     = 0;
-    content_provider_                   = detail::ContentProviderAdapter(std::move(provider));
-    content_provider_resource_releaser_ = resource_releaser;
-    is_chunked_content_provider         = true;
+inline void Response::set_content_provider(
+    const char *content_type, ContentProviderWithoutLength provider,
+    ContentProviderResourceReleaser resource_releaser) {
+  set_header("Content-Type", content_type);
+  content_length_ = 0;
+  content_provider_ = detail::ContentProviderAdapter(std::move(provider));
+  content_provider_resource_releaser_ = resource_releaser;
+  is_chunked_content_provider_ = false;
 }
 
-// Rstream implementation
-inline ssize_t Stream::write(const char *ptr) {
-    return write(ptr, strlen(ptr));
+inline void Response::set_chunked_content_provider(
+    const char *content_type, ContentProviderWithoutLength provider,
+    ContentProviderResourceReleaser resource_releaser) {
+  set_header("Content-Type", content_type);
+  content_length_ = 0;
+  content_provider_ = detail::ContentProviderAdapter(std::move(provider));
+  content_provider_resource_releaser_ = resource_releaser;
+  is_chunked_content_provider_ = true;
 }
 
-inline ssize_t Stream::write(const std::string &s) {
-    return write(s.data(), s.size());
+// Result implementation
+inline bool Result::has_request_header(const char *key) const {
+  return request_headers_.find(key) != request_headers_.end();
 }
 
-template <typename... Args>
-inline ssize_t Stream::write_format(const char *fmt, const Args &... args) {
-    const auto bufsiz = 2048;
-    std::array<char, bufsiz> buf;
-
-#if defined(_MSC_VER) && _MSC_VER < 1900
-    auto sn = _snprintf_s(buf.data(), bufsiz - 1, buf.size() - 1, fmt, args...);
-#else
-    auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...);
-#endif
-    if (sn <= 0) {
-        return sn;
-    }
+inline std::string Result::get_request_header_value(const char *key,
+                                                    size_t id) const {
+  return detail::get_header_value(request_headers_, key, id, "");
+}
 
-    auto n = static_cast<size_t>(sn);
+inline size_t Result::get_request_header_value_count(const char *key) const {
+  auto r = request_headers_.equal_range(key);
+  return static_cast<size_t>(std::distance(r.first, r.second));
+}
 
-    if (n >= buf.size() - 1) {
-        std::vector<char> glowable_buf(buf.size());
+// Stream implementation
+inline ssize_t Stream::write(const char *ptr) {
+  return write(ptr, strlen(ptr));
+}
 
-        while (n >= glowable_buf.size() - 1) {
-            glowable_buf.resize(glowable_buf.size() * 2);
-#if defined(_MSC_VER) && _MSC_VER < 1900
-            n = static_cast<size_t>(_snprintf_s(&glowable_buf[0], glowable_buf.size(),
-                                                glowable_buf.size() - 1, fmt, args...));
-#else
-            n = static_cast<size_t>(
-                snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...));
-#endif
-        }
-        return write(&glowable_buf[0], n);
-    } else {
-        return write(buf.data(), n);
-    }
+inline ssize_t Stream::write(const std::string &s) {
+  return write(s.data(), s.size());
 }
 
 namespace detail {
 
 // Socket stream implementation
-inline SocketStream::SocketStream(socket_t sock,
-                                  time_t read_timeout_sec,
+inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec,
                                   time_t read_timeout_usec,
                                   time_t write_timeout_sec,
                                   time_t write_timeout_usec)
-    : sock_(sock),
-      read_timeout_sec_(read_timeout_sec),
+    : sock_(sock), read_timeout_sec_(read_timeout_sec),
       read_timeout_usec_(read_timeout_usec),
       write_timeout_sec_(write_timeout_sec),
-      write_timeout_usec_(write_timeout_usec) {}
+      write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) {}
 
 inline SocketStream::~SocketStream() {}
 
 inline bool SocketStream::is_readable() const {
-    return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
+  return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
 }
 
 inline bool SocketStream::is_writable() const {
-    return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
+  return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
 }
 
 inline ssize_t SocketStream::read(char *ptr, size_t size) {
-    if (!is_readable()) {
-        return -1;
-    }
-
 #ifdef _WIN32
-    if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
-        return -1;
-    }
-    return recv(sock_, ptr, static_cast<int>(size), 0);
+  size =
+      (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
 #else
-    return handle_EINTR([&]() { return recv(sock_, ptr, size, 0); });
+  size = (std::min)(size,
+                    static_cast<size_t>((std::numeric_limits<ssize_t>::max)()));
 #endif
-}
 
-inline ssize_t SocketStream::write(const char *ptr, size_t size) {
-    if (!is_writable()) {
-        return -1;
+  if (read_buff_off_ < read_buff_content_size_) {
+    auto remaining_size = read_buff_content_size_ - read_buff_off_;
+    if (size <= remaining_size) {
+      memcpy(ptr, read_buff_.data() + read_buff_off_, size);
+      read_buff_off_ += size;
+      return static_cast<ssize_t>(size);
+    } else {
+      memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size);
+      read_buff_off_ += remaining_size;
+      return static_cast<ssize_t>(remaining_size);
     }
+  }
 
-#ifdef _WIN32
-    if (size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
-        return -1;
+  if (!is_readable()) { return -1; }
+
+  read_buff_off_ = 0;
+  read_buff_content_size_ = 0;
+
+  if (size < read_buff_size_) {
+    auto n = read_socket(sock_, read_buff_.data(), read_buff_size_,
+                         CPPHTTPLIB_RECV_FLAGS);
+    if (n <= 0) {
+      return n;
+    } else if (n <= static_cast<ssize_t>(size)) {
+      memcpy(ptr, read_buff_.data(), static_cast<size_t>(n));
+      return n;
+    } else {
+      memcpy(ptr, read_buff_.data(), size);
+      read_buff_off_ = size;
+      read_buff_content_size_ = static_cast<size_t>(n);
+      return static_cast<ssize_t>(size);
     }
-    return send(sock_, ptr, static_cast<int>(size), 0);
-#else
-    return handle_EINTR([&]() { return send(sock_, ptr, size, 0); });
+  } else {
+    return read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS);
+  }
+}
+
+inline ssize_t SocketStream::write(const char *ptr, size_t size) {
+  if (!is_writable()) { return -1; }
+
+#if defined(_WIN32) && !defined(_WIN64)
+  size =
+      (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
 #endif
+
+  return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS);
 }
 
-inline void SocketStream::get_remote_ip_and_port(std::string &ip, int &port) const {
-    return detail::get_remote_ip_and_port(sock_, ip, port);
+inline void SocketStream::get_remote_ip_and_port(std::string &ip,
+                                                 int &port) const {
+  return detail::get_remote_ip_and_port(sock_, ip, port);
 }
 
+inline socket_t SocketStream::socket() const { return sock_; }
+
 // Buffer stream implementation
-inline bool BufferStream::is_readable() const {
-    return true;
-}
+inline bool BufferStream::is_readable() const { return true; }
 
-inline bool BufferStream::is_writable() const {
-    return true;
-}
+inline bool BufferStream::is_writable() const { return true; }
 
 inline ssize_t BufferStream::read(char *ptr, size_t size) {
 #if defined(_MSC_VER) && _MSC_VER <= 1900
-    auto len_read = buffer._Copy_s(ptr, size, size, position);
+  auto len_read = buffer._Copy_s(ptr, size, size, position);
 #else
-    auto len_read = buffer.copy(ptr, size, position);
+  auto len_read = buffer.copy(ptr, size, position);
 #endif
-    position += static_cast<size_t>(len_read);
-    return static_cast<ssize_t>(len_read);
+  position += static_cast<size_t>(len_read);
+  return static_cast<ssize_t>(len_read);
 }
 
 inline ssize_t BufferStream::write(const char *ptr, size_t size) {
-    buffer.append(ptr, size);
-    return static_cast<ssize_t>(size);
+  buffer.append(ptr, size);
+  return static_cast<ssize_t>(size);
 }
 
-inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, int & /*port*/) const {}
+inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
+                                                 int & /*port*/) const {}
 
-inline const std::string &BufferStream::get_buffer() const {
-    return buffer;
-}
+inline socket_t BufferStream::socket() const { return 0; }
+
+inline const std::string &BufferStream::get_buffer() const { return buffer; }
 
-}  // namespace detail
+} // namespace detail
 
 // HTTP server implementation
 inline Server::Server()
-    : new_task_queue([] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }),
-      svr_sock_(INVALID_SOCKET),
-      is_running_(false) {
+    : new_task_queue(
+          [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }),
+      svr_sock_(INVALID_SOCKET), is_running_(false) {
 #ifndef _WIN32
-    signal(SIGPIPE, SIG_IGN);
+  signal(SIGPIPE, SIG_IGN);
 #endif
 }
 
 inline Server::~Server() {}
 
-inline Server &Server::Get(const char *pattern, Handler handler) {
-    get_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
-    return *this;
+inline Server &Server::Get(const std::string &pattern, Handler handler) {
+  get_handlers_.push_back(
+      std::make_pair(std::regex(pattern), std::move(handler)));
+  return *this;
 }
 
-inline Server &Server::Post(const char *pattern, Handler handler) {
-    post_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
-    return *this;
+inline Server &Server::Post(const std::string &pattern, Handler handler) {
+  post_handlers_.push_back(
+      std::make_pair(std::regex(pattern), std::move(handler)));
+  return *this;
 }
 
-inline Server &Server::Post(const char *pattern, HandlerWithContentReader handler) {
-    post_handlers_for_content_reader_.push_back(std::make_pair(std::regex(pattern), handler));
-    return *this;
+inline Server &Server::Post(const std::string &pattern,
+                            HandlerWithContentReader handler) {
+  post_handlers_for_content_reader_.push_back(
+      std::make_pair(std::regex(pattern), std::move(handler)));
+  return *this;
 }
 
-inline Server &Server::Put(const char *pattern, Handler handler) {
-    put_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
-    return *this;
+inline Server &Server::Put(const std::string &pattern, Handler handler) {
+  put_handlers_.push_back(
+      std::make_pair(std::regex(pattern), std::move(handler)));
+  return *this;
 }
 
-inline Server &Server::Put(const char *pattern, HandlerWithContentReader handler) {
-    put_handlers_for_content_reader_.push_back(std::make_pair(std::regex(pattern), handler));
-    return *this;
+inline Server &Server::Put(const std::string &pattern,
+                           HandlerWithContentReader handler) {
+  put_handlers_for_content_reader_.push_back(
+      std::make_pair(std::regex(pattern), std::move(handler)));
+  return *this;
 }
 
-inline Server &Server::Patch(const char *pattern, Handler handler) {
-    patch_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
-    return *this;
+inline Server &Server::Patch(const std::string &pattern, Handler handler) {
+  patch_handlers_.push_back(
+      std::make_pair(std::regex(pattern), std::move(handler)));
+  return *this;
 }
 
-inline Server &Server::Patch(const char *pattern, HandlerWithContentReader handler) {
-    patch_handlers_for_content_reader_.push_back(std::make_pair(std::regex(pattern), handler));
-    return *this;
+inline Server &Server::Patch(const std::string &pattern,
+                             HandlerWithContentReader handler) {
+  patch_handlers_for_content_reader_.push_back(
+      std::make_pair(std::regex(pattern), std::move(handler)));
+  return *this;
 }
 
-inline Server &Server::Delete(const char *pattern, Handler handler) {
-    delete_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
-    return *this;
+inline Server &Server::Delete(const std::string &pattern, Handler handler) {
+  delete_handlers_.push_back(
+      std::make_pair(std::regex(pattern), std::move(handler)));
+  return *this;
 }
 
-inline Server &Server::Delete(const char *pattern, HandlerWithContentReader handler) {
-    delete_handlers_for_content_reader_.push_back(std::make_pair(std::regex(pattern), handler));
-    return *this;
+inline Server &Server::Delete(const std::string &pattern,
+                              HandlerWithContentReader handler) {
+  delete_handlers_for_content_reader_.push_back(
+      std::make_pair(std::regex(pattern), std::move(handler)));
+  return *this;
 }
 
-inline Server &Server::Options(const char *pattern, Handler handler) {
-    options_handlers_.push_back(std::make_pair(std::regex(pattern), handler));
-    return *this;
+inline Server &Server::Options(const std::string &pattern, Handler handler) {
+  options_handlers_.push_back(
+      std::make_pair(std::regex(pattern), std::move(handler)));
+  return *this;
 }
 
-inline bool Server::set_base_dir(const char *dir, const char *mount_point) {
-    return set_mount_point(mount_point, dir);
+inline bool Server::set_base_dir(const std::string &dir,
+                                 const std::string &mount_point) {
+  return set_mount_point(mount_point, dir);
 }
 
-inline bool Server::set_mount_point(const char *mount_point, const char *dir) {
-    if (detail::is_dir(dir)) {
-        std::string mnt = mount_point ? mount_point : "/";
-        if (!mnt.empty() && mnt[0] == '/') {
-            base_dirs_.emplace_back(mnt, dir);
-            return true;
-        }
+inline bool Server::set_mount_point(const std::string &mount_point,
+                                    const std::string &dir, Headers headers) {
+  if (detail::is_dir(dir)) {
+    std::string mnt = !mount_point.empty() ? mount_point : "/";
+    if (!mnt.empty() && mnt[0] == '/') {
+      base_dirs_.push_back({mnt, dir, std::move(headers)});
+      return true;
     }
-    return false;
+  }
+  return false;
 }
 
-inline bool Server::remove_mount_point(const char *mount_point) {
-    for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
-        if (it->first == mount_point) {
-            base_dirs_.erase(it);
-            return true;
-        }
+inline bool Server::remove_mount_point(const std::string &mount_point) {
+  for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
+    if (it->mount_point == mount_point) {
+      base_dirs_.erase(it);
+      return true;
     }
-    return false;
+  }
+  return false;
 }
 
-inline void Server::set_file_extension_and_mimetype_mapping(const char *ext, const char *mime) {
-    file_extension_and_mimetype_map_[ext] = mime;
+inline Server &
+Server::set_file_extension_and_mimetype_mapping(const char *ext,
+                                                const char *mime) {
+  file_extension_and_mimetype_map_[ext] = mime;
+  return *this;
 }
 
-inline void Server::set_file_request_handler(Handler handler) {
-    file_request_handler_ = std::move(handler);
+inline Server &Server::set_file_request_handler(Handler handler) {
+  file_request_handler_ = std::move(handler);
+  return *this;
 }
 
-inline void Server::set_error_handler(Handler handler) {
-    error_handler_ = std::move(handler);
+inline Server &Server::set_error_handler(HandlerWithResponse handler) {
+  error_handler_ = std::move(handler);
+  return *this;
 }
 
-inline void Server::set_tcp_nodelay(bool on) {
-    tcp_nodelay_ = on;
+inline Server &Server::set_error_handler(Handler handler) {
+  error_handler_ = [handler](const Request &req, Response &res) {
+    handler(req, res);
+    return HandlerResponse::Handled;
+  };
+  return *this;
 }
 
-inline void Server::set_socket_options(SocketOptions socket_options) {
-    socket_options_ = socket_options;
+inline Server &Server::set_exception_handler(ExceptionHandler handler) {
+  exception_handler_ = std::move(handler);
+  return *this;
 }
 
-inline void Server::set_logger(Logger logger) {
-    logger_ = std::move(logger);
+inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) {
+  pre_routing_handler_ = std::move(handler);
+  return *this;
 }
 
-inline void Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
-    expect_100_continue_handler_ = std::move(handler);
+inline Server &Server::set_post_routing_handler(Handler handler) {
+  post_routing_handler_ = std::move(handler);
+  return *this;
 }
 
-inline void Server::set_keep_alive_max_count(size_t count) {
-    keep_alive_max_count_ = count;
+inline Server &Server::set_logger(Logger logger) {
+  logger_ = std::move(logger);
+  return *this;
 }
 
-inline void Server::set_keep_alive_timeout(time_t sec) {
-    keep_alive_timeout_sec_ = sec;
-}
-
-inline void Server::set_read_timeout(time_t sec, time_t usec) {
-    read_timeout_sec_  = sec;
-    read_timeout_usec_ = usec;
-}
-
-inline void Server::set_write_timeout(time_t sec, time_t usec) {
-    write_timeout_sec_  = sec;
-    write_timeout_usec_ = usec;
-}
-
-inline void Server::set_idle_interval(time_t sec, time_t usec) {
-    idle_interval_sec_  = sec;
-    idle_interval_usec_ = usec;
-}
+inline Server &
+Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
+  expect_100_continue_handler_ = std::move(handler);
 
-inline void Server::set_payload_max_length(size_t length) {
-    payload_max_length_ = length;
+  return *this;
 }
 
-inline bool Server::bind_to_port(const char *host, int port, int socket_flags) {
-    if (bind_internal(host, port, socket_flags) < 0)
-        return false;
-    return true;
-}
-inline int Server::bind_to_any_port(const char *host, int socket_flags) {
-    return bind_internal(host, 0, socket_flags);
+inline Server &Server::set_address_family(int family) {
+  address_family_ = family;
+  return *this;
 }
 
-inline bool Server::listen_after_bind() {
-    return listen_internal();
+inline Server &Server::set_tcp_nodelay(bool on) {
+  tcp_nodelay_ = on;
+  return *this;
 }
 
-inline bool Server::listen(const char *host, int port, int socket_flags) {
-    return bind_to_port(host, port, socket_flags) && listen_internal();
+inline Server &Server::set_socket_options(SocketOptions socket_options) {
+  socket_options_ = std::move(socket_options);
+  return *this;
 }
 
-inline bool Server::is_running() const {
-    return is_running_;
+inline Server &Server::set_default_headers(Headers headers) {
+  default_headers_ = std::move(headers);
+  return *this;
 }
-
-inline void Server::stop() {
-    if (is_running_) {
-        assert(svr_sock_ != INVALID_SOCKET);
-        std::atomic<socket_t> sock(svr_sock_.exchange(INVALID_SOCKET));
-        detail::shutdown_socket(sock);
-        detail::close_socket(sock);
-    }
+
+inline Server &Server::set_keep_alive_max_count(size_t count) {
+  keep_alive_max_count_ = count;
+  return *this;
 }
 
-inline bool Server::parse_request_line(const char *s, Request &req) {
-    const static std::regex re(
-        "(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|PRI) "
-        "(([^?]+)(?:\\?(.*?))?) (HTTP/1\\.[01])\r\n");
-
-    std::cmatch m;
-    if (std::regex_match(s, m, re)) {
-        req.version = std::string(m[5]);
-        req.method  = std::string(m[1]);
-        req.target  = std::string(m[2]);
-        req.path    = detail::decode_url(m[3], false);
-
-        // Parse query text
-        auto len = std::distance(m[4].first, m[4].second);
-        if (len > 0) {
-            detail::parse_query_text(m[4], req.params);
-        }
+inline Server &Server::set_keep_alive_timeout(time_t sec) {
+  keep_alive_timeout_sec_ = sec;
+  return *this;
+}
 
-        return true;
-    }
+inline Server &Server::set_read_timeout(time_t sec, time_t usec) {
+  read_timeout_sec_ = sec;
+  read_timeout_usec_ = usec;
+  return *this;
+}
 
-    return false;
+inline Server &Server::set_write_timeout(time_t sec, time_t usec) {
+  write_timeout_sec_ = sec;
+  write_timeout_usec_ = usec;
+  return *this;
 }
 
-inline bool Server::write_response(Stream &strm,
-                                   bool close_connection,
-                                   const Request &req,
-                                   Response &res) {
-    assert(res.status != -1);
+inline Server &Server::set_idle_interval(time_t sec, time_t usec) {
+  idle_interval_sec_ = sec;
+  idle_interval_usec_ = usec;
+  return *this;
+}
 
-    if (400 <= res.status && error_handler_) {
-        error_handler_(req, res);
-    }
+inline Server &Server::set_payload_max_length(size_t length) {
+  payload_max_length_ = length;
+  return *this;
+}
 
-    detail::BufferStream bstrm;
+inline bool Server::bind_to_port(const char *host, int port, int socket_flags) {
+  if (bind_internal(host, port, socket_flags) < 0) return false;
+  return true;
+}
+inline int Server::bind_to_any_port(const char *host, int socket_flags) {
+  return bind_internal(host, 0, socket_flags);
+}
 
-    // Response line
-    if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, detail::status_message(res.status))) {
-        return false;
-    }
+inline bool Server::listen_after_bind() { return listen_internal(); }
 
-    // Headers
-    if (close_connection || req.get_header_value("Connection") == "close") {
-        res.set_header("Connection", "close");
-    } else {
-        std::stringstream ss;
-        ss << "timeout=" << keep_alive_timeout_sec_ << ", max=" << keep_alive_max_count_;
-        res.set_header("Keep-Alive", ss.str());
-    }
+inline bool Server::listen(const char *host, int port, int socket_flags) {
+  return bind_to_port(host, port, socket_flags) && listen_internal();
+}
 
-    if (!res.has_header("Content-Type") &&
-        (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) {
-        res.set_header("Content-Type", "text/plain");
-    }
+inline bool Server::is_running() const { return is_running_; }
 
-    if (!res.has_header("Accept-Ranges") && req.method == "HEAD") {
-        res.set_header("Accept-Ranges", "bytes");
-    }
+inline void Server::stop() {
+  if (is_running_) {
+    assert(svr_sock_ != INVALID_SOCKET);
+    std::atomic<socket_t> sock(svr_sock_.exchange(INVALID_SOCKET));
+    detail::shutdown_socket(sock);
+    detail::close_socket(sock);
+  }
+}
 
-    std::string content_type;
-    std::string boundary;
+inline bool Server::parse_request_line(const char *s, Request &req) {
+  auto len = strlen(s);
+  if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; }
+  len -= 2;
+
+  {
+    size_t count = 0;
+
+    detail::split(s, s + len, ' ', [&](const char *b, const char *e) {
+      switch (count) {
+      case 0: req.method = std::string(b, e); break;
+      case 1: req.target = std::string(b, e); break;
+      case 2: req.version = std::string(b, e); break;
+      default: break;
+      }
+      count++;
+    });
 
-    if (req.ranges.size() > 1) {
-        boundary = detail::make_multipart_data_boundary();
+    if (count != 3) { return false; }
+  }
 
-        auto it = res.headers.find("Content-Type");
-        if (it != res.headers.end()) {
-            content_type = it->second;
-            res.headers.erase(it);
-        }
+  static const std::set<std::string> methods{
+      "GET",     "HEAD",    "POST",  "PUT",   "DELETE",
+      "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"};
 
-        res.headers.emplace("Content-Type", "multipart/byteranges; boundary=" + boundary);
-    }
+  if (methods.find(req.method) == methods.end()) { return false; }
 
-    auto type = detail::encoding_type(req, res);
+  if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; }
 
-    if (res.body.empty()) {
-        if (res.content_length_ > 0) {
-            size_t length = 0;
-            if (req.ranges.empty()) {
-                length = res.content_length_;
-            } else if (req.ranges.size() == 1) {
-                auto offsets = detail::get_range_offset_and_length(req, res.content_length_, 0);
-                auto offset  = offsets.first;
-                length       = offsets.second;
-                auto content_range =
-                    detail::make_content_range_header_field(offset, length, res.content_length_);
-                res.set_header("Content-Range", content_range);
-            } else {
-                length = detail::get_multipart_ranges_data_length(req, res, boundary, content_type);
-            }
-            res.set_header("Content-Length", std::to_string(length));
-        } else {
-            if (res.content_provider_) {
-                if (res.is_chunked_content_provider) {
-                    res.set_header("Transfer-Encoding", "chunked");
-                    if (type == detail::EncodingType::Gzip) {
-                        res.set_header("Content-Encoding", "gzip");
-                    } else if (type == detail::EncodingType::Brotli) {
-                        res.set_header("Content-Encoding", "br");
+  {
+    // Skip URL fragment
+    for (size_t i = 0; i < req.target.size(); i++) {
+      if (req.target[i] == '#') {
+        req.target.erase(i);
+        break;
+      }
+    }
+
+    size_t count = 0;
+
+    detail::split(req.target.data(), req.target.data() + req.target.size(), '?',
+                  [&](const char *b, const char *e) {
+                    switch (count) {
+                    case 0:
+                      req.path = detail::decode_url(std::string(b, e), false);
+                      break;
+                    case 1: {
+                      if (e - b > 0) {
+                        detail::parse_query_text(std::string(b, e), req.params);
+                      }
+                      break;
                     }
-                }
-            } else {
-                res.set_header("Content-Length", "0");
-            }
-        }
-    } else {
-        if (req.ranges.empty()) {
-            ;
-        } else if (req.ranges.size() == 1) {
-            auto offsets = detail::get_range_offset_and_length(req, res.body.size(), 0);
-            auto offset  = offsets.first;
-            auto length  = offsets.second;
-            auto content_range =
-                detail::make_content_range_header_field(offset, length, res.body.size());
-            res.set_header("Content-Range", content_range);
-            res.body = res.body.substr(offset, length);
-        } else {
-            res.body = detail::make_multipart_ranges_data(req, res, boundary, content_type);
-        }
-
-        if (type != detail::EncodingType::None) {
-            std::shared_ptr<detail::compressor> compressor;
+                    default: break;
+                    }
+                    count++;
+                  });
 
-            if (type == detail::EncodingType::Gzip) {
-#ifdef CPPHTTPLIB_ZLIB_SUPPORT
-                compressor = std::make_shared<detail::gzip_compressor>();
-                res.set_header("Content-Encoding", "gzip");
-#endif
-            } else if (type == detail::EncodingType::Brotli) {
-#ifdef CPPHTTPLIB_BROTLI_SUPPORT
-                compressor = std::make_shared<detail::brotli_compressor>();
-                res.set_header("Content-Encoding", "brotli");
-#endif
-            }
+    if (count > 2) { return false; }
+  }
 
-            if (compressor) {
-                std::string compressed;
+  return true;
+}
 
-                if (!compressor->compress(res.body.data(), res.body.size(), true,
-                                          [&](const char *data, size_t data_len) {
-                                              compressed.append(data, data_len);
-                                              return true;
-                                          })) {
-                    return false;
-                }
+inline bool Server::write_response(Stream &strm, bool close_connection,
+                                   const Request &req, Response &res) {
+  return write_response_core(strm, close_connection, req, res, false);
+}
 
-                res.body.swap(compressed);
-            }
-        }
+inline bool Server::write_response_with_content(Stream &strm,
+                                                bool close_connection,
+                                                const Request &req,
+                                                Response &res) {
+  return write_response_core(strm, close_connection, req, res, true);
+}
+
+inline bool Server::write_response_core(Stream &strm, bool close_connection,
+                                        const Request &req, Response &res,
+                                        bool need_apply_ranges) {
+  assert(res.status != -1);
+
+  if (400 <= res.status && error_handler_ &&
+      error_handler_(req, res) == HandlerResponse::Handled) {
+    need_apply_ranges = true;
+  }
+
+  std::string content_type;
+  std::string boundary;
+  if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); }
+
+  // Prepare additional headers
+  if (close_connection || req.get_header_value("Connection") == "close") {
+    res.set_header("Connection", "close");
+  } else {
+    std::stringstream ss;
+    ss << "timeout=" << keep_alive_timeout_sec_
+       << ", max=" << keep_alive_max_count_;
+    res.set_header("Keep-Alive", ss.str());
+  }
+
+  if (!res.has_header("Content-Type") &&
+      (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) {
+    res.set_header("Content-Type", "text/plain");
+  }
+
+  if (!res.has_header("Content-Length") && res.body.empty() &&
+      !res.content_length_ && !res.content_provider_) {
+    res.set_header("Content-Length", "0");
+  }
+
+  if (!res.has_header("Accept-Ranges") && req.method == "HEAD") {
+    res.set_header("Accept-Ranges", "bytes");
+  }
+
+  if (post_routing_handler_) { post_routing_handler_(req, res); }
+
+  // Response line and headers
+  {
+    detail::BufferStream bstrm;
 
-        auto length = std::to_string(res.body.size());
-        res.set_header("Content-Length", length);
+    if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status,
+                            detail::status_message(res.status))) {
+      return false;
     }
 
-    if (!detail::write_headers(bstrm, res, Headers())) {
-        return false;
-    }
+    if (!detail::write_headers(bstrm, res.headers)) { return false; }
 
     // Flush buffer
     auto &data = bstrm.get_buffer();
     strm.write(data.data(), data.size());
-
-    // Body
-    auto ret = true;
-    if (req.method != "HEAD") {
-        if (!res.body.empty()) {
-            if (!strm.write(res.body)) {
-                ret = false;
-            }
-        } else if (res.content_provider_) {
-            if (!write_content_with_provider(strm, req, res, boundary, content_type)) {
-                ret = false;
-            }
-        }
-    }
-
-    // Log
-    if (logger_) {
-        logger_(req, res);
-    }
-
-    return ret;
-}
-
-inline bool Server::write_content_with_provider(Stream &strm,
-                                                const Request &req,
-                                                Response &res,
-                                                const std::string &boundary,
-                                                const std::string &content_type) {
-    auto is_shutting_down = [this]() { return this->svr_sock_ == INVALID_SOCKET; };
-
-    if (res.content_length_ > 0) {
-        if (req.ranges.empty()) {
-            if (detail::write_content(strm, res.content_provider_, 0, res.content_length_,
-                                      is_shutting_down) < 0) {
-                return false;
-            }
-        } else if (req.ranges.size() == 1) {
-            auto offsets = detail::get_range_offset_and_length(req, res.content_length_, 0);
-            auto offset  = offsets.first;
-            auto length  = offsets.second;
-            if (detail::write_content(strm, res.content_provider_, offset, length,
-                                      is_shutting_down) < 0) {
-                return false;
-            }
-        } else {
-            if (!detail::write_multipart_ranges_data(strm, req, res, boundary, content_type,
-                                                     is_shutting_down)) {
-                return false;
-            }
-        }
+  }
+
+  // Body
+  auto ret = true;
+  if (req.method != "HEAD") {
+    if (!res.body.empty()) {
+      if (!strm.write(res.body)) { ret = false; }
+    } else if (res.content_provider_) {
+      if (write_content_with_provider(strm, req, res, boundary, content_type)) {
+        res.content_provider_success_ = true;
+      } else {
+        res.content_provider_success_ = false;
+        ret = false;
+      }
+    }
+  }
+
+  // Log
+  if (logger_) { logger_(req, res); }
+
+  return ret;
+}
+
+inline bool
+Server::write_content_with_provider(Stream &strm, const Request &req,
+                                    Response &res, const std::string &boundary,
+                                    const std::string &content_type) {
+  auto is_shutting_down = [this]() {
+    return this->svr_sock_ == INVALID_SOCKET;
+  };
+
+  if (res.content_length_ > 0) {
+    if (req.ranges.empty()) {
+      return detail::write_content(strm, res.content_provider_, 0,
+                                   res.content_length_, is_shutting_down);
+    } else if (req.ranges.size() == 1) {
+      auto offsets =
+          detail::get_range_offset_and_length(req, res.content_length_, 0);
+      auto offset = offsets.first;
+      auto length = offsets.second;
+      return detail::write_content(strm, res.content_provider_, offset, length,
+                                   is_shutting_down);
     } else {
-        if (res.is_chunked_content_provider) {
-            auto type = detail::encoding_type(req, res);
+      return detail::write_multipart_ranges_data(
+          strm, req, res, boundary, content_type, is_shutting_down);
+    }
+  } else {
+    if (res.is_chunked_content_provider_) {
+      auto type = detail::encoding_type(req, res);
 
-            std::shared_ptr<detail::compressor> compressor;
-            if (type == detail::EncodingType::Gzip) {
+      std::unique_ptr<detail::compressor> compressor;
+      if (type == detail::EncodingType::Gzip) {
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
-                compressor = std::make_shared<detail::gzip_compressor>();
+        compressor = detail::make_unique<detail::gzip_compressor>();
 #endif
-            } else if (type == detail::EncodingType::Brotli) {
+      } else if (type == detail::EncodingType::Brotli) {
 #ifdef CPPHTTPLIB_BROTLI_SUPPORT
-                compressor = std::make_shared<detail::brotli_compressor>();
+        compressor = detail::make_unique<detail::brotli_compressor>();
 #endif
-            } else {
-                compressor = std::make_shared<detail::nocompressor>();
-            }
-            assert(compressor != nullptr);
+      } else {
+        compressor = detail::make_unique<detail::nocompressor>();
+      }
+      assert(compressor != nullptr);
 
-            if (detail::write_content_chunked(strm, res.content_provider_, is_shutting_down,
-                                              *compressor) < 0) {
-                return false;
-            }
-        } else {
-            if (detail::write_content_without_length(strm, res.content_provider_,
-                                                     is_shutting_down) < 0) {
-                return false;
-            }
-        }
+      return detail::write_content_chunked(strm, res.content_provider_,
+                                           is_shutting_down, *compressor);
+    } else {
+      return detail::write_content_without_length(strm, res.content_provider_,
+                                                  is_shutting_down);
     }
-    return true;
+  }
 }
 
 inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
-    MultipartFormDataMap::iterator cur;
-    if (read_content_core(
-            strm, req, res,
-            // Regular
-            [&](const char *buf, size_t n) {
-                if (req.body.size() + n > req.body.max_size()) {
-                    return false;
-                }
-                req.body.append(buf, n);
-                return true;
-            },
-            // Multipart
-            [&](const MultipartFormData &file) {
-                cur = req.files.emplace(file.name, file);
-                return true;
-            },
-            [&](const char *buf, size_t n) {
-                auto &content = cur->second.content;
-                if (content.size() + n > content.max_size()) {
-                    return false;
-                }
-                content.append(buf, n);
-                return true;
-            })) {
-        const auto &content_type = req.get_header_value("Content-Type");
-        if (!content_type.find("application/x-www-form-urlencoded")) {
-            detail::parse_query_text(req.body, req.params);
-        }
-        return true;
+  MultipartFormDataMap::iterator cur;
+  if (read_content_core(
+          strm, req, res,
+          // Regular
+          [&](const char *buf, size_t n) {
+            if (req.body.size() + n > req.body.max_size()) { return false; }
+            req.body.append(buf, n);
+            return true;
+          },
+          // Multipart
+          [&](const MultipartFormData &file) {
+            cur = req.files.emplace(file.name, file);
+            return true;
+          },
+          [&](const char *buf, size_t n) {
+            auto &content = cur->second.content;
+            if (content.size() + n > content.max_size()) { return false; }
+            content.append(buf, n);
+            return true;
+          })) {
+    const auto &content_type = req.get_header_value("Content-Type");
+    if (!content_type.find("application/x-www-form-urlencoded")) {
+      if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
+        res.status = 413; // NOTE: should be 414?
+        return false;
+      }
+      detail::parse_query_text(req.body, req.params);
     }
-    return false;
+    return true;
+  }
+  return false;
 }
 
-inline bool Server::read_content_with_content_receiver(Stream &strm,
-                                                       Request &req,
-                                                       Response &res,
-                                                       ContentReceiver receiver,
-                                                       MultipartContentHeader multipart_header,
-                                                       ContentReceiver multipart_receiver) {
-    return read_content_core(strm, req, res, receiver, multipart_header, multipart_receiver);
+inline bool Server::read_content_with_content_receiver(
+    Stream &strm, Request &req, Response &res, ContentReceiver receiver,
+    MultipartContentHeader multipart_header,
+    ContentReceiver multipart_receiver) {
+  return read_content_core(strm, req, res, std::move(receiver),
+                           std::move(multipart_header),
+                           std::move(multipart_receiver));
 }
 
-inline bool Server::read_content_core(Stream &strm,
-                                      Request &req,
-                                      Response &res,
+inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
                                       ContentReceiver receiver,
                                       MultipartContentHeader mulitpart_header,
                                       ContentReceiver multipart_receiver) {
-    detail::MultipartFormDataParser multipart_form_data_parser;
-    ContentReceiver out;
-
-    if (req.is_multipart_form_data()) {
-        const auto &content_type = req.get_header_value("Content-Type");
-        std::string boundary;
-        if (!detail::parse_multipart_boundary(content_type, boundary)) {
-            res.status = 400;
-            return false;
-        }
-
-        multipart_form_data_parser.set_boundary(std::move(boundary));
-        out = [&](const char *buf, size_t n) {
-            /* For debug
-            size_t pos = 0;
-            while (pos < n) {
-              auto read_size = std::min<size_t>(1, n - pos);
-              auto ret = multipart_form_data_parser.parse(
-                  buf + pos, read_size, multipart_receiver, mulitpart_header);
-              if (!ret) { return false; }
-              pos += read_size;
-            }
-            return true;
-            */
-            return multipart_form_data_parser.parse(buf, n, multipart_receiver, mulitpart_header);
-        };
-    } else {
-        out = receiver;
-    }
+  detail::MultipartFormDataParser multipart_form_data_parser;
+  ContentReceiverWithProgress out;
 
-    if (req.method == "DELETE" && !req.has_header("Content-Length")) {
-        return true;
-    }
-
-    if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, out, true)) {
-        return false;
-    }
-
-    if (req.is_multipart_form_data()) {
-        if (!multipart_form_data_parser.is_valid()) {
-            res.status = 400;
-            return false;
-        }
-    }
+  if (req.is_multipart_form_data()) {
+    const auto &content_type = req.get_header_value("Content-Type");
+    std::string boundary;
+    if (!detail::parse_multipart_boundary(content_type, boundary)) {
+      res.status = 400;
+      return false;
+    }
+
+    multipart_form_data_parser.set_boundary(std::move(boundary));
+    out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) {
+      /* For debug
+      size_t pos = 0;
+      while (pos < n) {
+        auto read_size = (std::min)<size_t>(1, n - pos);
+        auto ret = multipart_form_data_parser.parse(
+            buf + pos, read_size, multipart_receiver, mulitpart_header);
+        if (!ret) { return false; }
+        pos += read_size;
+      }
+      return true;
+      */
+      return multipart_form_data_parser.parse(buf, n, multipart_receiver,
+                                              mulitpart_header);
+    };
+  } else {
+    out = [receiver](const char *buf, size_t n, uint64_t /*off*/,
+                     uint64_t /*len*/) { return receiver(buf, n); };
+  }
 
+  if (req.method == "DELETE" && !req.has_header("Content-Length")) {
     return true;
-}
+  }
 
-inline bool Server::handle_file_request(Request &req, Response &res, bool head) {
-    for (const auto &kv : base_dirs_) {
-        const auto &mount_point = kv.first;
-        const auto &base_dir    = kv.second;
-
-        // Prefix match
-        if (!req.path.compare(0, mount_point.size(), mount_point)) {
-            std::string sub_path = "/" + req.path.substr(mount_point.size());
-            if (detail::is_valid_path(sub_path)) {
-                auto path = base_dir + sub_path;
-                if (path.back() == '/') {
-                    path += "index.html";
-                }
-
-                if (detail::is_file(path)) {
-                    detail::read_file(path, res.body);
-                    auto type = detail::find_content_type(path, file_extension_and_mimetype_map_);
-                    if (type) {
-                        res.set_header("Content-Type", type);
-                    }
-                    res.status = 200;
-                    if (!head && file_request_handler_) {
-                        file_request_handler_(req, res);
-                    }
-                    return true;
-                }
-            }
-        }
-    }
+  if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr,
+                            out, true)) {
     return false;
-}
-
-inline socket_t Server::create_server_socket(const char *host,
-                                             int port,
-                                             int socket_flags,
-                                             SocketOptions socket_options) const {
-    return detail::create_socket(
-        host, port, socket_flags, tcp_nodelay_, socket_options,
-        [](socket_t sock, struct addrinfo &ai) -> bool {
-            if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
-                return false;
-            }
-            if (::listen(sock, 5)) {  // Listen through 5 channels
-                return false;
-            }
-            return true;
-        });
+  }
+
+  if (req.is_multipart_form_data()) {
+    if (!multipart_form_data_parser.is_valid()) {
+      res.status = 400;
+      return false;
+    }
+  }
+
+  return true;
+}
+
+inline bool Server::handle_file_request(const Request &req, Response &res,
+                                        bool head) {
+  for (const auto &entry : base_dirs_) {
+    // Prefix match
+    if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) {
+      std::string sub_path = "/" + req.path.substr(entry.mount_point.size());
+      if (detail::is_valid_path(sub_path)) {
+        auto path = entry.base_dir + sub_path;
+        if (path.back() == '/') { path += "index.html"; }
+
+        if (detail::is_file(path)) {
+          detail::read_file(path, res.body);
+          auto type =
+              detail::find_content_type(path, file_extension_and_mimetype_map_);
+          if (type) { res.set_header("Content-Type", type); }
+          for (const auto &kv : entry.headers) {
+            res.set_header(kv.first.c_str(), kv.second);
+          }
+          res.status = req.has_header("Range") ? 206 : 200;
+          if (!head && file_request_handler_) {
+            file_request_handler_(req, res);
+          }
+          return true;
+        }
+      }
+    }
+  }
+  return false;
+}
+
+inline socket_t
+Server::create_server_socket(const char *host, int port, int socket_flags,
+                             SocketOptions socket_options) const {
+  return detail::create_socket(
+      host, "", port, address_family_, socket_flags, tcp_nodelay_,
+      std::move(socket_options),
+      [](socket_t sock, struct addrinfo &ai) -> bool {
+        if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
+          return false;
+        }
+        if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { return false; }
+        return true;
+      });
 }
 
 inline int Server::bind_internal(const char *host, int port, int socket_flags) {
-    if (!is_valid()) {
-        return -1;
-    }
+  if (!is_valid()) { return -1; }
 
-    svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_);
-    if (svr_sock_ == INVALID_SOCKET) {
-        return -1;
-    }
+  svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_);
+  if (svr_sock_ == INVALID_SOCKET) { return -1; }
 
-    if (port == 0) {
-        struct sockaddr_storage addr;
-        socklen_t addr_len = sizeof(addr);
-        if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr), &addr_len) == -1) {
-            return -1;
-        }
-        if (addr.ss_family == AF_INET) {
-            return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port);
-        } else if (addr.ss_family == AF_INET6) {
-            return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port);
-        } else {
-            return -1;
-        }
+  if (port == 0) {
+    struct sockaddr_storage addr;
+    socklen_t addr_len = sizeof(addr);
+    if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr),
+                    &addr_len) == -1) {
+      return -1;
+    }
+    if (addr.ss_family == AF_INET) {
+      return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port);
+    } else if (addr.ss_family == AF_INET6) {
+      return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port);
     } else {
-        return port;
+      return -1;
     }
+  } else {
+    return port;
+  }
 }
 
 inline bool Server::listen_internal() {
-    auto ret    = true;
-    is_running_ = true;
+  auto ret = true;
+  is_running_ = true;
 
-    {
-        std::unique_ptr<TaskQueue> task_queue(new_task_queue());
+  {
+    std::unique_ptr<TaskQueue> task_queue(new_task_queue());
 
-        while (svr_sock_ != INVALID_SOCKET) {
+    while (svr_sock_ != INVALID_SOCKET) {
 #ifndef _WIN32
-            if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) {
+      if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) {
 #endif
-                auto val = detail::select_read(svr_sock_, idle_interval_sec_, idle_interval_usec_);
-                if (val == 0) {  // Timeout
-                    task_queue->on_idle();
-                    continue;
-                }
+        auto val = detail::select_read(svr_sock_, idle_interval_sec_,
+                                       idle_interval_usec_);
+        if (val == 0) { // Timeout
+          task_queue->on_idle();
+          continue;
+        }
 #ifndef _WIN32
-            }
+      }
 #endif
-            socket_t sock = accept(svr_sock_, nullptr, nullptr);
-
-            if (sock == INVALID_SOCKET) {
-                if (errno == EMFILE) {
-                    // The per-process limit of open file descriptors has been reached.
-                    // Try to accept new connections after a short sleep.
-                    std::this_thread::sleep_for(std::chrono::milliseconds(1));
-                    continue;
-                }
-                if (svr_sock_ != INVALID_SOCKET) {
-                    detail::close_socket(svr_sock_);
-                    ret = false;
-                } else {
-                    ;  // The server socket was closed by user.
-                }
-                break;
-            }
+      socket_t sock = accept(svr_sock_, nullptr, nullptr);
+
+      if (sock == INVALID_SOCKET) {
+        if (errno == EMFILE) {
+          // The per-process limit of open file descriptors has been reached.
+          // Try to accept new connections after a short sleep.
+          std::this_thread::sleep_for(std::chrono::milliseconds(1));
+          continue;
+        }
+        if (svr_sock_ != INVALID_SOCKET) {
+          detail::close_socket(svr_sock_);
+          ret = false;
+        } else {
+          ; // The server socket was closed by user.
+        }
+        break;
+      }
 
-#if __cplusplus > 201703L
-            task_queue->enqueue([=, this]() { process_and_close_socket(sock); });
+      {
+#ifdef _WIN32
+        auto timeout = static_cast<uint32_t>(read_timeout_sec_ * 1000 +
+                                             read_timeout_usec_ / 1000);
+        setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
+                   sizeof(timeout));
 #else
-            task_queue->enqueue([=]() { process_and_close_socket(sock); });
+        timeval tv;
+        tv.tv_sec = static_cast<long>(read_timeout_sec_);
+        tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec_);
+        setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
 #endif
-        }
+      }
+      {
+
+#ifdef _WIN32
+        auto timeout = static_cast<uint32_t>(write_timeout_sec_ * 1000 +
+                                             write_timeout_usec_ / 1000);
+        setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
+                   sizeof(timeout));
+#else
+        timeval tv;
+        tv.tv_sec = static_cast<long>(write_timeout_sec_);
+        tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec_);
+        setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
+#endif
+      }
 
-        task_queue->shutdown();
+#if __cplusplus > 201703L
+      task_queue->enqueue([=, this]() { process_and_close_socket(sock); });
+#else
+      task_queue->enqueue([=]() { process_and_close_socket(sock); });
+#endif
     }
 
-    is_running_ = false;
-    return ret;
+    task_queue->shutdown();
+  }
+
+  is_running_ = false;
+  return ret;
 }
 
 inline bool Server::routing(Request &req, Response &res, Stream &strm) {
-    // File handler
-    bool is_head_request = req.method == "HEAD";
-    if ((req.method == "GET" || is_head_request) &&
-        handle_file_request(req, res, is_head_request)) {
-        return true;
-    }
-
-    if (detail::expect_content(req)) {
-        // Content reader handler
-        {
-            ContentReader reader(
-                [&](ContentReceiver receiver) {
-                    return read_content_with_content_receiver(strm, req, res, receiver, nullptr,
-                                                              nullptr);
-                },
-                [&](MultipartContentHeader header, ContentReceiver receiver) {
-                    return read_content_with_content_receiver(strm, req, res, nullptr, header,
-                                                              receiver);
-                });
-
-            if (req.method == "POST") {
-                if (dispatch_request_for_content_reader(req, res, reader,
-                                                        post_handlers_for_content_reader_)) {
-                    return true;
-                }
-            } else if (req.method == "PUT") {
-                if (dispatch_request_for_content_reader(req, res, reader,
-                                                        put_handlers_for_content_reader_)) {
-                    return true;
-                }
-            } else if (req.method == "PATCH") {
-                if (dispatch_request_for_content_reader(req, res, reader,
-                                                        patch_handlers_for_content_reader_)) {
-                    return true;
-                }
-            } else if (req.method == "DELETE") {
-                if (dispatch_request_for_content_reader(req, res, reader,
-                                                        delete_handlers_for_content_reader_)) {
-                    return true;
-                }
-            }
-        }
+  if (pre_routing_handler_ &&
+      pre_routing_handler_(req, res) == HandlerResponse::Handled) {
+    return true;
+  }
 
-        // Read content into `req.body`
-        if (!read_content(strm, req, res)) {
-            return false;
-        }
-    }
+  // File handler
+  bool is_head_request = req.method == "HEAD";
+  if ((req.method == "GET" || is_head_request) &&
+      handle_file_request(req, res, is_head_request)) {
+    return true;
+  }
 
-    // Regular handler
-    if (req.method == "GET" || req.method == "HEAD") {
-        return dispatch_request(req, res, get_handlers_);
-    } else if (req.method == "POST") {
-        return dispatch_request(req, res, post_handlers_);
-    } else if (req.method == "PUT") {
-        return dispatch_request(req, res, put_handlers_);
-    } else if (req.method == "DELETE") {
-        return dispatch_request(req, res, delete_handlers_);
-    } else if (req.method == "OPTIONS") {
-        return dispatch_request(req, res, options_handlers_);
-    } else if (req.method == "PATCH") {
-        return dispatch_request(req, res, patch_handlers_);
+  if (detail::expect_content(req)) {
+    // Content reader handler
+    {
+      ContentReader reader(
+          [&](ContentReceiver receiver) {
+            return read_content_with_content_receiver(
+                strm, req, res, std::move(receiver), nullptr, nullptr);
+          },
+          [&](MultipartContentHeader header, ContentReceiver receiver) {
+            return read_content_with_content_receiver(strm, req, res, nullptr,
+                                                      std::move(header),
+                                                      std::move(receiver));
+          });
+
+      if (req.method == "POST") {
+        if (dispatch_request_for_content_reader(
+                req, res, std::move(reader),
+                post_handlers_for_content_reader_)) {
+          return true;
+        }
+      } else if (req.method == "PUT") {
+        if (dispatch_request_for_content_reader(
+                req, res, std::move(reader),
+                put_handlers_for_content_reader_)) {
+          return true;
+        }
+      } else if (req.method == "PATCH") {
+        if (dispatch_request_for_content_reader(
+                req, res, std::move(reader),
+                patch_handlers_for_content_reader_)) {
+          return true;
+        }
+      } else if (req.method == "DELETE") {
+        if (dispatch_request_for_content_reader(
+                req, res, std::move(reader),
+                delete_handlers_for_content_reader_)) {
+          return true;
+        }
+      }
+    }
+
+    // Read content into `req.body`
+    if (!read_content(strm, req, res)) { return false; }
+  }
+
+  // Regular handler
+  if (req.method == "GET" || req.method == "HEAD") {
+    return dispatch_request(req, res, get_handlers_);
+  } else if (req.method == "POST") {
+    return dispatch_request(req, res, post_handlers_);
+  } else if (req.method == "PUT") {
+    return dispatch_request(req, res, put_handlers_);
+  } else if (req.method == "DELETE") {
+    return dispatch_request(req, res, delete_handlers_);
+  } else if (req.method == "OPTIONS") {
+    return dispatch_request(req, res, options_handlers_);
+  } else if (req.method == "PATCH") {
+    return dispatch_request(req, res, patch_handlers_);
+  }
+
+  res.status = 400;
+  return false;
+}
+
+inline bool Server::dispatch_request(Request &req, Response &res,
+                                     const Handlers &handlers) {
+  for (const auto &x : handlers) {
+    const auto &pattern = x.first;
+    const auto &handler = x.second;
+
+    if (std::regex_match(req.path, req.matches, pattern)) {
+      handler(req, res);
+      return true;
+    }
+  }
+  return false;
+}
+
+inline void Server::apply_ranges(const Request &req, Response &res,
+                                 std::string &content_type,
+                                 std::string &boundary) {
+  if (req.ranges.size() > 1) {
+    boundary = detail::make_multipart_data_boundary();
+
+    auto it = res.headers.find("Content-Type");
+    if (it != res.headers.end()) {
+      content_type = it->second;
+      res.headers.erase(it);
+    }
+
+    res.headers.emplace("Content-Type",
+                        "multipart/byteranges; boundary=" + boundary);
+  }
+
+  auto type = detail::encoding_type(req, res);
+
+  if (res.body.empty()) {
+    if (res.content_length_ > 0) {
+      size_t length = 0;
+      if (req.ranges.empty()) {
+        length = res.content_length_;
+      } else if (req.ranges.size() == 1) {
+        auto offsets =
+            detail::get_range_offset_and_length(req, res.content_length_, 0);
+        auto offset = offsets.first;
+        length = offsets.second;
+        auto content_range = detail::make_content_range_header_field(
+            offset, length, res.content_length_);
+        res.set_header("Content-Range", content_range);
+      } else {
+        length = detail::get_multipart_ranges_data_length(req, res, boundary,
+                                                          content_type);
+      }
+      res.set_header("Content-Length", std::to_string(length));
+    } else {
+      if (res.content_provider_) {
+        if (res.is_chunked_content_provider_) {
+          res.set_header("Transfer-Encoding", "chunked");
+          if (type == detail::EncodingType::Gzip) {
+            res.set_header("Content-Encoding", "gzip");
+          } else if (type == detail::EncodingType::Brotli) {
+            res.set_header("Content-Encoding", "br");
+          }
+        }
+      }
+    }
+  } else {
+    if (req.ranges.empty()) {
+      ;
+    } else if (req.ranges.size() == 1) {
+      auto offsets =
+          detail::get_range_offset_and_length(req, res.body.size(), 0);
+      auto offset = offsets.first;
+      auto length = offsets.second;
+      auto content_range = detail::make_content_range_header_field(
+          offset, length, res.body.size());
+      res.set_header("Content-Range", content_range);
+      if (offset < res.body.size()) {
+        res.body = res.body.substr(offset, length);
+      } else {
+        res.body.clear();
+        res.status = 416;
+      }
+    } else {
+      std::string data;
+      if (detail::make_multipart_ranges_data(req, res, boundary, content_type,
+                                             data)) {
+        res.body.swap(data);
+      } else {
+        res.body.clear();
+        res.status = 416;
+      }
     }
 
-    res.status = 400;
-    return false;
-}
-
-inline bool Server::dispatch_request(Request &req, Response &res, const Handlers &handlers) {
+    if (type != detail::EncodingType::None) {
+      std::unique_ptr<detail::compressor> compressor;
+      std::string content_encoding;
 
-    try {
-        for (const auto &x : handlers) {
-            const auto &pattern = x.first;
-            const auto &handler = x.second;
+      if (type == detail::EncodingType::Gzip) {
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+        compressor = detail::make_unique<detail::gzip_compressor>();
+        content_encoding = "gzip";
+#endif
+      } else if (type == detail::EncodingType::Brotli) {
+#ifdef CPPHTTPLIB_BROTLI_SUPPORT
+        compressor = detail::make_unique<detail::brotli_compressor>();
+        content_encoding = "br";
+#endif
+      }
 
-            if (std::regex_match(req.path, req.matches, pattern)) {
-                handler(req, res);
-                return true;
-            }
+      if (compressor) {
+        std::string compressed;
+        if (compressor->compress(res.body.data(), res.body.size(), true,
+                                 [&](const char *data, size_t data_len) {
+                                   compressed.append(data, data_len);
+                                   return true;
+                                 })) {
+          res.body.swap(compressed);
+          res.set_header("Content-Encoding", content_encoding);
         }
-    } catch (const std::exception &ex) {
-        res.status = 500;
-        res.set_header("EXCEPTION_WHAT", ex.what());
-    } catch (...) {
-        res.status = 500;
-        res.set_header("EXCEPTION_WHAT", "UNKNOWN");
+      }
     }
-    return false;
+
+    auto length = std::to_string(res.body.size());
+    res.set_header("Content-Length", length);
+  }
 }
 
-inline bool Server::dispatch_request_for_content_reader(Request &req,
-                                                        Response &res,
-                                                        ContentReader content_reader,
-                                                        const HandlersForContentReader &handlers) {
-    for (const auto &x : handlers) {
-        const auto &pattern = x.first;
-        const auto &handler = x.second;
+inline bool Server::dispatch_request_for_content_reader(
+    Request &req, Response &res, ContentReader content_reader,
+    const HandlersForContentReader &handlers) {
+  for (const auto &x : handlers) {
+    const auto &pattern = x.first;
+    const auto &handler = x.second;
 
-        if (std::regex_match(req.path, req.matches, pattern)) {
-            handler(req, res, content_reader);
-            return true;
-        }
+    if (std::regex_match(req.path, req.matches, pattern)) {
+      handler(req, res, content_reader);
+      return true;
     }
-    return false;
+  }
+  return false;
 }
 
-inline bool Server::process_request(Stream &strm,
-                                    bool close_connection,
-                                    bool &connection_closed,
-                                    const std::function<void(Request &)> &setup_request) {
-    std::array<char, 2048> buf{};
+inline bool
+Server::process_request(Stream &strm, bool close_connection,
+                        bool &connection_closed,
+                        const std::function<void(Request &)> &setup_request) {
+  std::array<char, 2048> buf{};
 
-    detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
+  detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
 
-    // Connection has been closed on client
-    if (!line_reader.getline()) {
-        return false;
-    }
+  // Connection has been closed on client
+  if (!line_reader.getline()) { return false; }
 
-    Request req;
-    Response res;
+  Request req;
+  Response res;
 
-    res.version = "HTTP/1.1";
+  res.version = "HTTP/1.1";
 
-    // Check if the request URI doesn't exceed the limit
-    if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
-        Headers dummy;
-        detail::read_headers(strm, dummy);
-        res.status = 414;
-        return write_response(strm, close_connection, req, res);
+  for (const auto &header : default_headers_) {
+    if (res.headers.find(header.first) == res.headers.end()) {
+      res.headers.insert(header);
     }
+  }
 
-    // Request line and headers
-    if (!parse_request_line(line_reader.ptr(), req) || !detail::read_headers(strm, req.headers)) {
-        res.status = 400;
-        return write_response(strm, close_connection, req, res);
-    }
+#ifdef _WIN32
+  // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL).
+#else
+#ifndef CPPHTTPLIB_USE_POLL
+  // Socket file descriptor exceeded FD_SETSIZE...
+  if (strm.socket() >= FD_SETSIZE) {
+    Headers dummy;
+    detail::read_headers(strm, dummy);
+    res.status = 500;
+    return write_response(strm, close_connection, req, res);
+  }
+#endif
+#endif
 
-    if (req.get_header_value("Connection") == "close") {
-        connection_closed = true;
-    }
+  // Check if the request URI doesn't exceed the limit
+  if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
+    Headers dummy;
+    detail::read_headers(strm, dummy);
+    res.status = 414;
+    return write_response(strm, close_connection, req, res);
+  }
 
-    if (req.version == "HTTP/1.0" && req.get_header_value("Connection") != "Keep-Alive") {
-        connection_closed = true;
-    }
+  // Request line and headers
+  if (!parse_request_line(line_reader.ptr(), req) ||
+      !detail::read_headers(strm, req.headers)) {
+    res.status = 400;
+    return write_response(strm, close_connection, req, res);
+  }
 
-    strm.get_remote_ip_and_port(req.remote_addr, req.remote_port);
-    req.set_header("REMOTE_ADDR", req.remote_addr);
-    req.set_header("REMOTE_PORT", std::to_string(req.remote_port));
+  if (req.get_header_value("Connection") == "close") {
+    connection_closed = true;
+  }
 
-    if (req.has_header("Range")) {
-        const auto &range_header_value = req.get_header_value("Range");
-        if (!detail::parse_range_header(range_header_value, req.ranges)) {
-            // TODO: error
-        }
-    }
+  if (req.version == "HTTP/1.0" &&
+      req.get_header_value("Connection") != "Keep-Alive") {
+    connection_closed = true;
+  }
 
-    if (setup_request) {
-        setup_request(req);
-    }
+  strm.get_remote_ip_and_port(req.remote_addr, req.remote_port);
+  req.set_header("REMOTE_ADDR", req.remote_addr);
+  req.set_header("REMOTE_PORT", std::to_string(req.remote_port));
 
-    if (req.get_header_value("Expect") == "100-continue") {
-        auto status = 100;
-        if (expect_100_continue_handler_) {
-            status = expect_100_continue_handler_(req, res);
-        }
-        switch (status) {
-            case 100:
-            case 417:
-                strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status, detail::status_message(status));
-                break;
-            default:
-                return write_response(strm, close_connection, req, res);
-        }
+  if (req.has_header("Range")) {
+    const auto &range_header_value = req.get_header_value("Range");
+    if (!detail::parse_range_header(range_header_value, req.ranges)) {
+      res.status = 416;
+      return write_response(strm, close_connection, req, res);
     }
+  }
 
-    // Rounting
-    if (routing(req, res, strm)) {
-        if (res.status == -1) {
-            res.status = req.ranges.empty() ? 200 : 206;
-        }
+  if (setup_request) { setup_request(req); }
+
+  if (req.get_header_value("Expect") == "100-continue") {
+    auto status = 100;
+    if (expect_100_continue_handler_) {
+      status = expect_100_continue_handler_(req, res);
+    }
+    switch (status) {
+    case 100:
+    case 417:
+      strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
+                        detail::status_message(status));
+      break;
+    default: return write_response(strm, close_connection, req, res);
+    }
+  }
+
+  // Rounting
+  bool routed = false;
+#ifdef CPPHTTPLIB_NO_EXCEPTIONS
+  routed = routing(req, res, strm);
+#else
+  try {
+    routed = routing(req, res, strm);
+  } catch (std::exception &e) {
+    if (exception_handler_) {
+      exception_handler_(req, res, e);
+      routed = true;
     } else {
-        if (res.status == -1) {
-            res.status = 404;
-        }
+      res.status = 500;
+      res.set_header("EXCEPTION_WHAT", e.what());
     }
+  } catch (...) {
+    res.status = 500;
+    res.set_header("EXCEPTION_WHAT", "UNKNOWN");
+  }
+#endif
 
+  if (routed) {
+    if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; }
+    return write_response_with_content(strm, close_connection, req, res);
+  } else {
+    if (res.status == -1) { res.status = 404; }
     return write_response(strm, close_connection, req, res);
+  }
 }
 
-inline bool Server::is_valid() const {
-    return true;
-}
+inline bool Server::is_valid() const { return true; }
 
 inline bool Server::process_and_close_socket(socket_t sock) {
-    auto ret = detail::process_server_socket(
-        sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_, read_timeout_usec_,
-        write_timeout_sec_, write_timeout_usec_,
-        [this](Stream &strm, bool close_connection, bool &connection_closed) {
-            return process_request(strm, close_connection, connection_closed, nullptr);
-        });
+  auto ret = detail::process_server_socket(
+      svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
+      read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
+      write_timeout_usec_,
+      [this](Stream &strm, bool close_connection, bool &connection_closed) {
+        return process_request(strm, close_connection, connection_closed,
+                               nullptr);
+      });
 
-    detail::shutdown_socket(sock);
-    detail::close_socket(sock);
-    return ret;
+  detail::shutdown_socket(sock);
+  detail::close_socket(sock);
+  return ret;
 }
 
 // HTTP client implementation
@@ -4914,1020 +5779,1281 @@ inline ClientImpl::ClientImpl(const std::string &host)
 inline ClientImpl::ClientImpl(const std::string &host, int port)
     : ClientImpl(host, port, std::string(), std::string()) {}
 
-inline ClientImpl::ClientImpl(const std::string &host,
-                              int port,
+inline ClientImpl::ClientImpl(const std::string &host, int port,
                               const std::string &client_cert_path,
                               const std::string &client_key_path)
-    : host_(host),
-      port_(port),
-      host_and_port_(host_ + ":" + std::to_string(port_)),
-      client_cert_path_(client_cert_path),
-      client_key_path_(client_key_path) {}
+    : host_(host), port_(port),
+      host_and_port_(adjust_host_string(host) + ":" + std::to_string(port)),
+      client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
 
 inline ClientImpl::~ClientImpl() {
-    stop_core();
-}
-
-inline bool ClientImpl::is_valid() const {
-    return true;
+  std::lock_guard<std::mutex> guard(socket_mutex_);
+  shutdown_socket(socket_);
+  close_socket(socket_);
 }
 
-inline Error ClientImpl::get_last_error() const {
-    return error_;
-}
+inline bool ClientImpl::is_valid() const { return true; }
 
 inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
-    client_cert_path_        = rhs.client_cert_path_;
-    client_key_path_         = rhs.client_key_path_;
-    connection_timeout_sec_  = rhs.connection_timeout_sec_;
-    read_timeout_sec_        = rhs.read_timeout_sec_;
-    read_timeout_usec_       = rhs.read_timeout_usec_;
-    write_timeout_sec_       = rhs.write_timeout_sec_;
-    write_timeout_usec_      = rhs.write_timeout_usec_;
-    basic_auth_username_     = rhs.basic_auth_username_;
-    basic_auth_password_     = rhs.basic_auth_password_;
-    bearer_token_auth_token_ = rhs.bearer_token_auth_token_;
+  client_cert_path_ = rhs.client_cert_path_;
+  client_key_path_ = rhs.client_key_path_;
+  connection_timeout_sec_ = rhs.connection_timeout_sec_;
+  read_timeout_sec_ = rhs.read_timeout_sec_;
+  read_timeout_usec_ = rhs.read_timeout_usec_;
+  write_timeout_sec_ = rhs.write_timeout_sec_;
+  write_timeout_usec_ = rhs.write_timeout_usec_;
+  basic_auth_username_ = rhs.basic_auth_username_;
+  basic_auth_password_ = rhs.basic_auth_password_;
+  bearer_token_auth_token_ = rhs.bearer_token_auth_token_;
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-    digest_auth_username_ = rhs.digest_auth_username_;
-    digest_auth_password_ = rhs.digest_auth_password_;
-#endif
-    keep_alive_                    = rhs.keep_alive_;
-    follow_location_               = rhs.follow_location_;
-    tcp_nodelay_                   = rhs.tcp_nodelay_;
-    socket_options_                = rhs.socket_options_;
-    compress_                      = rhs.compress_;
-    decompress_                    = rhs.decompress_;
-    interface_                     = rhs.interface_;
-    proxy_host_                    = rhs.proxy_host_;
-    proxy_port_                    = rhs.proxy_port_;
-    proxy_basic_auth_username_     = rhs.proxy_basic_auth_username_;
-    proxy_basic_auth_password_     = rhs.proxy_basic_auth_password_;
-    proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_;
+  digest_auth_username_ = rhs.digest_auth_username_;
+  digest_auth_password_ = rhs.digest_auth_password_;
+#endif
+  keep_alive_ = rhs.keep_alive_;
+  follow_location_ = rhs.follow_location_;
+  url_encode_ = rhs.url_encode_;
+  address_family_ = rhs.address_family_;
+  tcp_nodelay_ = rhs.tcp_nodelay_;
+  socket_options_ = rhs.socket_options_;
+  compress_ = rhs.compress_;
+  decompress_ = rhs.decompress_;
+  interface_ = rhs.interface_;
+  proxy_host_ = rhs.proxy_host_;
+  proxy_port_ = rhs.proxy_port_;
+  proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
+  proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
+  proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_;
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-    proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
-    proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
+  proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
+  proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
 #endif
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-    server_certificate_verification_ = rhs.server_certificate_verification_;
+  ca_cert_file_path_ = rhs.ca_cert_file_path_;
+  ca_cert_dir_path_ = rhs.ca_cert_dir_path_;
+  ca_cert_store_ = rhs.ca_cert_store_;
 #endif
-    logger_ = rhs.logger_;
-}
-
-inline socket_t ClientImpl::create_client_socket() const {
-    if (!proxy_host_.empty() && proxy_port_ != -1) {
-        return detail::create_client_socket(proxy_host_.c_str(), proxy_port_, tcp_nodelay_,
-                                            socket_options_, connection_timeout_sec_,
-                                            connection_timeout_usec_, interface_, error_);
-    }
-    return detail::create_client_socket(host_.c_str(), port_, tcp_nodelay_, socket_options_,
-                                        connection_timeout_sec_, connection_timeout_usec_,
-                                        interface_, error_);
-}
-
-inline bool ClientImpl::create_and_connect_socket(Socket &socket) {
-    auto sock = create_client_socket();
-    if (sock == INVALID_SOCKET) {
-        return false;
-    }
-    socket.sock = sock;
-    return true;
-}
-
-inline void ClientImpl::close_socket(Socket &socket, bool /*process_socket_ret*/) {
-    detail::close_socket(socket.sock);
-    socket_.sock = INVALID_SOCKET;
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-    socket_.ssl = nullptr;
+  server_certificate_verification_ = rhs.server_certificate_verification_;
+#endif
+  logger_ = rhs.logger_;
+}
+
+inline socket_t ClientImpl::create_client_socket(Error &error) const {
+  if (!proxy_host_.empty() && proxy_port_ != -1) {
+    return detail::create_client_socket(
+        proxy_host_.c_str(), "", proxy_port_, address_family_, tcp_nodelay_,
+        socket_options_, connection_timeout_sec_, connection_timeout_usec_,
+        read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
+        write_timeout_usec_, interface_, error);
+  }
+
+  // Check is custom IP specified for host_
+  std::string ip;
+  auto it = addr_map_.find(host_);
+  if (it != addr_map_.end()) ip = it->second;
+
+  return detail::create_client_socket(
+      host_.c_str(), ip.c_str(), port_, address_family_, tcp_nodelay_,
+      socket_options_, connection_timeout_sec_, connection_timeout_usec_,
+      read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
+      write_timeout_usec_, interface_, error);
+}
+
+inline bool ClientImpl::create_and_connect_socket(Socket &socket,
+                                                  Error &error) {
+  auto sock = create_client_socket(error);
+  if (sock == INVALID_SOCKET) { return false; }
+  socket.sock = sock;
+  return true;
+}
+
+inline void ClientImpl::shutdown_ssl(Socket & /*socket*/,
+                                     bool /*shutdown_gracefully*/) {
+  // If there are any requests in flight from threads other than us, then it's
+  // a thread-unsafe race because individual ssl* objects are not thread-safe.
+  assert(socket_requests_in_flight_ == 0 ||
+         socket_requests_are_from_thread_ == std::this_thread::get_id());
+}
+
+inline void ClientImpl::shutdown_socket(Socket &socket) {
+  if (socket.sock == INVALID_SOCKET) { return; }
+  detail::shutdown_socket(socket.sock);
+}
+
+inline void ClientImpl::close_socket(Socket &socket) {
+  // If there are requests in flight in another thread, usually closing
+  // the socket will be fine and they will simply receive an error when
+  // using the closed socket, but it is still a bug since rarely the OS
+  // may reassign the socket id to be used for a new socket, and then
+  // suddenly they will be operating on a live socket that is different
+  // than the one they intended!
+  assert(socket_requests_in_flight_ == 0 ||
+         socket_requests_are_from_thread_ == std::this_thread::get_id());
+
+  // It is also a bug if this happens while SSL is still active
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+  assert(socket.ssl == nullptr);
 #endif
+  if (socket.sock == INVALID_SOCKET) { return; }
+  detail::close_socket(socket.sock);
+  socket.sock = INVALID_SOCKET;
 }
 
-inline bool ClientImpl::read_response_line(Stream &strm, Response &res) {
-    std::array<char, 2048> buf;
-
-    detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
+inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
+                                           Response &res) {
+  std::array<char, 2048> buf{};
 
-    if (!line_reader.getline()) {
-        return false;
-    }
+  detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
 
-    const static std::regex re("(HTTP/1\\.[01]) (\\d+) (.*?)\r\n");
+  if (!line_reader.getline()) { return false; }
 
-    std::cmatch m;
-    if (!std::regex_match(line_reader.ptr(), m, re)) {
-        return false;
-    }
-    res.version = std::string(m[1]);
-    res.status  = std::stoi(std::string(m[2]));
-    res.reason  = std::string(m[3]);
+#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
+  const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n");
+#else
+  const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n");
+#endif
 
-    // Ignore '100 Continue'
-    while (res.status == 100) {
-        if (!line_reader.getline()) {
-            return false;
-        }  // CRLF
-        if (!line_reader.getline()) {
-            return false;
-        }  // next response line
+  std::cmatch m;
+  if (!std::regex_match(line_reader.ptr(), m, re)) {
+    return req.method == "CONNECT";
+  }
+  res.version = std::string(m[1]);
+  res.status = std::stoi(std::string(m[2]));
+  res.reason = std::string(m[3]);
 
-        if (!std::regex_match(line_reader.ptr(), m, re)) {
-            return false;
-        }
-        res.version = std::string(m[1]);
-        res.status  = std::stoi(std::string(m[2]));
-        res.reason  = std::string(m[3]);
-    }
+  // Ignore '100 Continue'
+  while (res.status == 100) {
+    if (!line_reader.getline()) { return false; } // CRLF
+    if (!line_reader.getline()) { return false; } // next response line
 
-    return true;
+    if (!std::regex_match(line_reader.ptr(), m, re)) { return false; }
+    res.version = std::string(m[1]);
+    res.status = std::stoi(std::string(m[2]));
+    res.reason = std::string(m[3]);
+  }
+
+  return true;
 }
 
-inline bool ClientImpl::send(const Request &req, Response &res) {
-    std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
+inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
+  std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
 
-    {
-        std::lock_guard<std::mutex> guard(socket_mutex_);
+  {
+    std::lock_guard<std::mutex> guard(socket_mutex_);
 
-        auto is_alive = false;
-        if (socket_.is_open()) {
-            is_alive = detail::select_write(socket_.sock, 0, 0) > 0;
-            if (!is_alive) {
-                close_socket(socket_, false);
-            }
-        }
+    // Set this to false immediately - if it ever gets set to true by the end of
+    // the request, we know another thread instructed us to close the socket.
+    socket_should_be_closed_when_request_is_done_ = false;
 
-        if (!is_alive) {
-            if (!create_and_connect_socket(socket_)) {
-                return false;
-            }
+    auto is_alive = false;
+    if (socket_.is_open()) {
+      is_alive = detail::is_socket_alive(socket_.sock);
+      if (!is_alive) {
+        // Attempt to avoid sigpipe by shutting down nongracefully if it seems
+        // like the other side has already closed the connection Also, there
+        // cannot be any requests in flight from other threads since we locked
+        // request_mutex_, so safe to close everything immediately
+        const bool shutdown_gracefully = false;
+        shutdown_ssl(socket_, shutdown_gracefully);
+        shutdown_socket(socket_);
+        close_socket(socket_);
+      }
+    }
+
+    if (!is_alive) {
+      if (!create_and_connect_socket(socket_, error)) { return false; }
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-            // TODO: refactoring
-            if (is_ssl()) {
-                auto &scli = static_cast<SSLClient &>(*this);
-                if (!proxy_host_.empty() && proxy_port_ != -1) {
-                    bool success = false;
-                    if (!scli.connect_with_proxy(socket_, res, success)) {
-                        return success;
-                    }
-                }
-
-                if (!scli.initialize_ssl(socket_)) {
-                    return false;
-                }
-            }
+      // TODO: refactoring
+      if (is_ssl()) {
+        auto &scli = static_cast<SSLClient &>(*this);
+        if (!proxy_host_.empty() && proxy_port_ != -1) {
+          bool success = false;
+          if (!scli.connect_with_proxy(socket_, res, success, error)) {
+            return success;
+          }
+        }
+
+        if (!scli.initialize_ssl(socket_, error)) { return false; }
+      }
 #endif
-        }
     }
 
-    auto close_connection = !keep_alive_;
+    // Mark the current socket as being in use so that it cannot be closed by
+    // anyone else while this request is ongoing, even though we will be
+    // releasing the mutex.
+    if (socket_requests_in_flight_ > 1) {
+      assert(socket_requests_are_from_thread_ == std::this_thread::get_id());
+    }
+    socket_requests_in_flight_ += 1;
+    socket_requests_are_from_thread_ = std::this_thread::get_id();
+  }
+
+  for (const auto &header : default_headers_) {
+    if (req.headers.find(header.first) == req.headers.end()) {
+      req.headers.insert(header);
+    }
+  }
 
-    auto ret = process_socket(
-        socket_, [&](Stream &strm) { return handle_request(strm, req, res, close_connection); });
+  auto close_connection = !keep_alive_;
+  auto ret = process_socket(socket_, [&](Stream &strm) {
+    return handle_request(strm, req, res, close_connection, error);
+  });
 
-    if (close_connection || !ret) {
-        stop_core();
+  // Briefly lock mutex in order to mark that a request is no longer ongoing
+  {
+    std::lock_guard<std::mutex> guard(socket_mutex_);
+    socket_requests_in_flight_ -= 1;
+    if (socket_requests_in_flight_ <= 0) {
+      assert(socket_requests_in_flight_ == 0);
+      socket_requests_are_from_thread_ = std::thread::id();
     }
 
-    if (!ret) {
-        if (error_ == Error::Success) {
-            error_ = Error::Unknown;
-        }
+    if (socket_should_be_closed_when_request_is_done_ || close_connection ||
+        !ret) {
+      shutdown_ssl(socket_, true);
+      shutdown_socket(socket_);
+      close_socket(socket_);
     }
+  }
 
-    return ret;
+  if (!ret) {
+    if (error == Error::Success) { error = Error::Unknown; }
+  }
+
+  return ret;
 }
 
-inline bool ClientImpl::handle_request(Stream &strm,
-                                       const Request &req,
-                                       Response &res,
-                                       bool close_connection) {
-    if (req.path.empty()) {
-        error_ = Error::Connection;
-        return false;
-    }
+inline Result ClientImpl::send(const Request &req) {
+  auto req2 = req;
+  return send_(std::move(req2));
+}
 
-    bool ret;
+inline Result ClientImpl::send_(Request &&req) {
+  auto res = detail::make_unique<Response>();
+  auto error = Error::Success;
+  auto ret = send(req, *res, error);
+  return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)};
+}
 
-    if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) {
-        auto req2 = req;
-        req2.path = "http://" + host_and_port_ + req.path;
-        ret       = process_request(strm, req2, res, close_connection);
-    } else {
-        ret = process_request(strm, req, res, close_connection);
-    }
+inline bool ClientImpl::handle_request(Stream &strm, Request &req,
+                                       Response &res, bool close_connection,
+                                       Error &error) {
+  if (req.path.empty()) {
+    error = Error::Connection;
+    return false;
+  }
 
-    if (!ret) {
-        return false;
-    }
+  auto req_save = req;
 
-    if (300 < res.status && res.status < 400 && follow_location_) {
-        ret = redirect(req, res);
-    }
+  bool ret;
+
+  if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) {
+    auto req2 = req;
+    req2.path = "http://" + host_and_port_ + req.path;
+    ret = process_request(strm, req2, res, close_connection, error);
+    req = req2;
+    req.path = req_save.path;
+  } else {
+    ret = process_request(strm, req, res, close_connection, error);
+  }
+
+  if (!ret) { return false; }
+
+  if (300 < res.status && res.status < 400 && follow_location_) {
+    req = req_save;
+    ret = redirect(req, res, error);
+  }
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-    if ((res.status == 401 || res.status == 407) && req.authorization_count_ < 5) {
-        auto is_proxy        = res.status == 407;
-        const auto &username = is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
-        const auto &password = is_proxy ? proxy_digest_auth_password_ : digest_auth_password_;
-
-        if (!username.empty() && !password.empty()) {
-            std::map<std::string, std::string> auth;
-            if (detail::parse_www_authenticate(res, auth, is_proxy)) {
-                Request new_req = req;
-                new_req.authorization_count_ += 1;
-                auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
-                new_req.headers.erase(key);
-                new_req.headers.insert(detail::make_digest_authentication_header(
-                    req, auth, new_req.authorization_count_, detail::random_string(10), username,
-                    password, is_proxy));
-
-                Response new_res;
-
-                ret = send(new_req, new_res);
-                if (ret) {
-                    res = new_res;
-                }
-            }
-        }
-    }
+  if ((res.status == 401 || res.status == 407) &&
+      req.authorization_count_ < 5) {
+    auto is_proxy = res.status == 407;
+    const auto &username =
+        is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
+    const auto &password =
+        is_proxy ? proxy_digest_auth_password_ : digest_auth_password_;
+
+    if (!username.empty() && !password.empty()) {
+      std::map<std::string, std::string> auth;
+      if (detail::parse_www_authenticate(res, auth, is_proxy)) {
+        Request new_req = req;
+        new_req.authorization_count_ += 1;
+        new_req.headers.erase(is_proxy ? "Proxy-Authorization"
+                                       : "Authorization");
+        new_req.headers.insert(detail::make_digest_authentication_header(
+            req, auth, new_req.authorization_count_, detail::random_string(10),
+            username, password, is_proxy));
+
+        Response new_res;
+
+        ret = send(new_req, new_res, error);
+        if (ret) { res = new_res; }
+      }
+    }
+  }
 #endif
 
-    return ret;
+  return ret;
 }
 
-inline bool ClientImpl::redirect(const Request &req, Response &res) {
-    if (req.redirect_count == 0) {
-        error_ = Error::ExceedRedirectCount;
-        return false;
-    }
+inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
+  if (req.redirect_count_ == 0) {
+    error = Error::ExceedRedirectCount;
+    return false;
+  }
 
-    auto location = detail::decode_url(res.get_header_value("location"), true);
-    if (location.empty()) {
-        return false;
-    }
+  auto location = detail::decode_url(res.get_header_value("location"), true);
+  if (location.empty()) { return false; }
 
-    const static std::regex re(
-        R"(^(?:(https?):)?(?://([^:/?#]*)(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
+  const static std::regex re(
+      R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
 
-    std::smatch m;
-    if (!std::regex_match(location, m, re)) {
-        return false;
-    }
+  std::smatch m;
+  if (!std::regex_match(location, m, re)) { return false; }
 
-    auto scheme = is_ssl() ? "https" : "http";
+  auto scheme = is_ssl() ? "https" : "http";
 
-    auto next_scheme = m[1].str();
-    auto next_host   = m[2].str();
-    auto port_str    = m[3].str();
-    auto next_path   = m[4].str();
+  auto next_scheme = m[1].str();
+  auto next_host = m[2].str();
+  if (next_host.empty()) { next_host = m[3].str(); }
+  auto port_str = m[4].str();
+  auto next_path = m[5].str();
 
-    auto next_port = port_;
-    if (!port_str.empty()) {
-        next_port = std::stoi(port_str);
-    } else if (!next_scheme.empty()) {
-        next_port = next_scheme == "https" ? 443 : 80;
-    }
+  auto next_port = port_;
+  if (!port_str.empty()) {
+    next_port = std::stoi(port_str);
+  } else if (!next_scheme.empty()) {
+    next_port = next_scheme == "https" ? 443 : 80;
+  }
 
-    if (next_scheme.empty()) {
-        next_scheme = scheme;
-    }
-    if (next_host.empty()) {
-        next_host = host_;
-    }
-    if (next_path.empty()) {
-        next_path = "/";
-    }
+  if (next_scheme.empty()) { next_scheme = scheme; }
+  if (next_host.empty()) { next_host = host_; }
+  if (next_path.empty()) { next_path = "/"; }
 
-    if (next_scheme == scheme && next_host == host_ && next_port == port_) {
-        return detail::redirect(*this, req, res, next_path);
-    } else {
-        if (next_scheme == "https") {
+  if (next_scheme == scheme && next_host == host_ && next_port == port_) {
+    return detail::redirect(*this, req, res, next_path, location, error);
+  } else {
+    if (next_scheme == "https") {
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-            SSLClient cli(next_host.c_str(), next_port);
-            cli.copy_settings(*this);
-            auto ret = detail::redirect(cli, req, res, next_path);
-            if (!ret) {
-                error_ = cli.get_last_error();
-            }
-            return ret;
+      SSLClient cli(next_host.c_str(), next_port);
+      cli.copy_settings(*this);
+      if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); }
+      return detail::redirect(cli, req, res, next_path, location, error);
 #else
-            return false;
+      return false;
 #endif
-        } else {
-            ClientImpl cli(next_host.c_str(), next_port);
-            cli.copy_settings(*this);
-            auto ret = detail::redirect(cli, req, res, next_path);
-            if (!ret) {
-                error_ = cli.get_last_error();
-            }
-            return ret;
-        }
+    } else {
+      ClientImpl cli(next_host.c_str(), next_port);
+      cli.copy_settings(*this);
+      return detail::redirect(cli, req, res, next_path, location, error);
     }
+  }
 }
 
-inline bool ClientImpl::write_request(Stream &strm, const Request &req, bool close_connection) {
-    detail::BufferStream bstrm;
+inline bool ClientImpl::write_content_with_provider(Stream &strm,
+                                                    const Request &req,
+                                                    Error &error) {
+  auto is_shutting_down = []() { return false; };
 
-    // Request line
-    const auto &path = detail::encode_url(req.path);
+  if (req.is_chunked_content_provider_) {
+    // TODO: Brotli suport
+    std::unique_ptr<detail::compressor> compressor;
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+    if (compress_) {
+      compressor = detail::make_unique<detail::gzip_compressor>();
+    } else
+#endif
+    {
+      compressor = detail::make_unique<detail::nocompressor>();
+    }
+
+    return detail::write_content_chunked(strm, req.content_provider_,
+                                         is_shutting_down, *compressor, error);
+  } else {
+    return detail::write_content(strm, req.content_provider_, 0,
+                                 req.content_length_, is_shutting_down, error);
+  }
+} // namespace httplib
+
+inline bool ClientImpl::write_request(Stream &strm, Request &req,
+                                      bool close_connection, Error &error) {
+  // Prepare additional headers
+  if (close_connection) {
+    if (!req.has_header("Connection")) {
+      req.headers.emplace("Connection", "close");
+    }
+  }
+
+  if (!req.has_header("Host")) {
+    if (is_ssl()) {
+      if (port_ == 443) {
+        req.headers.emplace("Host", host_);
+      } else {
+        req.headers.emplace("Host", host_and_port_);
+      }
+    } else {
+      if (port_ == 80) {
+        req.headers.emplace("Host", host_);
+      } else {
+        req.headers.emplace("Host", host_and_port_);
+      }
+    }
+  }
 
-    bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str());
+  if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); }
 
-    // Additonal headers
-    Headers headers;
-    if (close_connection) {
-        headers.emplace("Connection", "close");
-    }
+#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
+  if (!req.has_header("User-Agent")) {
+    auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
+    req.headers.emplace("User-Agent", agent);
+  }
+#endif
 
-    if (!req.has_header("Host")) {
-        if (is_ssl()) {
-            if (port_ == 443) {
-                headers.emplace("Host", host_);
-            } else {
-                headers.emplace("Host", host_and_port_);
-            }
-        } else {
-            if (port_ == 80) {
-                headers.emplace("Host", host_);
-            } else {
-                headers.emplace("Host", host_and_port_);
-            }
+  if (req.body.empty()) {
+    if (req.content_provider_) {
+      if (!req.is_chunked_content_provider_) {
+        if (!req.has_header("Content-Length")) {
+          auto length = std::to_string(req.content_length_);
+          req.headers.emplace("Content-Length", length);
         }
+      }
+    } else {
+      if (req.method == "POST" || req.method == "PUT" ||
+          req.method == "PATCH") {
+        req.headers.emplace("Content-Length", "0");
+      }
     }
-
-    if (!req.has_header("Accept")) {
-        headers.emplace("Accept", "*/*");
+  } else {
+    if (!req.has_header("Content-Type")) {
+      req.headers.emplace("Content-Type", "text/plain");
     }
 
-    if (!req.has_header("User-Agent")) {
-        headers.emplace("User-Agent", "cpp-httplib/0.7");
+    if (!req.has_header("Content-Length")) {
+      auto length = std::to_string(req.body.size());
+      req.headers.emplace("Content-Length", length);
     }
+  }
 
-    if (req.body.empty()) {
-        if (req.content_provider) {
-            auto length = std::to_string(req.content_length);
-            headers.emplace("Content-Length", length);
-        } else {
-            headers.emplace("Content-Length", "0");
-        }
-    } else {
-        if (!req.has_header("Content-Type")) {
-            headers.emplace("Content-Type", "text/plain");
-        }
-
-        if (!req.has_header("Content-Length")) {
-            auto length = std::to_string(req.body.size());
-            headers.emplace("Content-Length", length);
-        }
+  if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) {
+    if (!req.has_header("Authorization")) {
+      req.headers.insert(make_basic_authentication_header(
+          basic_auth_username_, basic_auth_password_, false));
     }
+  }
 
-    if (!basic_auth_password_.empty()) {
-        headers.insert(
-            make_basic_authentication_header(basic_auth_username_, basic_auth_password_, false));
+  if (!proxy_basic_auth_username_.empty() &&
+      !proxy_basic_auth_password_.empty()) {
+    if (!req.has_header("Proxy-Authorization")) {
+      req.headers.insert(make_basic_authentication_header(
+          proxy_basic_auth_username_, proxy_basic_auth_password_, true));
     }
+  }
 
-    if (!proxy_basic_auth_username_.empty() && !proxy_basic_auth_password_.empty()) {
-        headers.insert(make_basic_authentication_header(proxy_basic_auth_username_,
-                                                        proxy_basic_auth_password_, true));
+  if (!bearer_token_auth_token_.empty()) {
+    if (!req.has_header("Authorization")) {
+      req.headers.insert(make_bearer_token_authentication_header(
+          bearer_token_auth_token_, false));
     }
+  }
 
-    if (!bearer_token_auth_token_.empty()) {
-        headers.insert(make_bearer_token_authentication_header(bearer_token_auth_token_, false));
+  if (!proxy_bearer_token_auth_token_.empty()) {
+    if (!req.has_header("Proxy-Authorization")) {
+      req.headers.insert(make_bearer_token_authentication_header(
+          proxy_bearer_token_auth_token_, true));
     }
+  }
 
-    if (!proxy_bearer_token_auth_token_.empty()) {
-        headers.insert(
-            make_bearer_token_authentication_header(proxy_bearer_token_auth_token_, true));
-    }
+  // Request line and headers
+  {
+    detail::BufferStream bstrm;
+
+    const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path;
+    bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str());
 
-    detail::write_headers(bstrm, req, headers);
+    detail::write_headers(bstrm, req.headers);
 
     // Flush buffer
     auto &data = bstrm.get_buffer();
     if (!detail::write_data(strm, data.data(), data.size())) {
-        error_ = Error::Write;
-        return false;
+      error = Error::Write;
+      return false;
     }
+  }
 
-    // Body
-    if (req.body.empty()) {
-        if (req.content_provider) {
-            size_t offset     = 0;
-            size_t end_offset = req.content_length;
-
-            bool ok = true;
+  // Body
+  if (req.body.empty()) {
+    return write_content_with_provider(strm, req, error);
+  }
 
-            DataSink data_sink;
-            data_sink.write = [&](const char *d, size_t l) {
-                if (ok) {
-                    if (detail::write_data(strm, d, l)) {
-                        offset += l;
-                    } else {
-                        ok = false;
-                    }
-                }
-            };
-            data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
-
-            while (offset < end_offset) {
-                if (!req.content_provider(offset, end_offset - offset, data_sink)) {
-                    error_ = Error::Canceled;
-                    return false;
-                }
-                if (!ok) {
-                    error_ = Error::Write;
-                    return false;
-                }
-            }
-        }
-    } else {
-        return detail::write_data(strm, req.body.data(), req.body.size());
-    }
+  if (!detail::write_data(strm, req.body.data(), req.body.size())) {
+    error = Error::Write;
+    return false;
+  }
 
-    return true;
+  return true;
 }
 
-inline std::shared_ptr<Response> ClientImpl::send_with_content_provider(
-    const char *method,
-    const char *path,
-    const Headers &headers,
-    const std::string &body,
-    size_t content_length,
-    ContentProvider content_provider,
-    const char *content_type) {
-
-    Request req;
-    req.method  = method;
-    req.headers = default_headers_;
-    req.headers.insert(headers.begin(), headers.end());
-    req.path = path;
+inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
+    Request &req,
+    // const char *method, const char *path, const Headers &headers,
+    const char *body, size_t content_length, ContentProvider content_provider,
+    ContentProviderWithoutLength content_provider_without_length,
+    const char *content_type, Error &error) {
 
-    if (content_type) {
-        req.headers.emplace("Content-Type", content_type);
-    }
+  if (content_type) { req.headers.emplace("Content-Type", content_type); }
 
 #ifdef CPPHTTPLIB_ZLIB_SUPPORT
-    if (compress_) {
-        detail::gzip_compressor compressor;
-
-        if (content_provider) {
-            auto ok       = true;
-            size_t offset = 0;
-
-            DataSink data_sink;
-            data_sink.write = [&](const char *data, size_t data_len) {
-                if (ok) {
-                    auto last = offset + data_len == content_length;
-
-                    auto ret = compressor.compress(data, data_len, last,
-                                                   [&](const char *data, size_t data_len) {
-                                                       req.body.append(data, data_len);
-                                                       return true;
-                                                   });
-
-                    if (ret) {
-                        offset += data_len;
-                    } else {
-                        ok = false;
-                    }
-                }
-            };
-            data_sink.is_writable = [&](void) { return ok && true; };
-
-            while (ok && offset < content_length) {
-                if (!content_provider(offset, content_length - offset, data_sink)) {
-                    error_ = Error::Canceled;
-                    return nullptr;
-                }
-            }
-        } else {
-            if (!compressor.compress(body.data(), body.size(), true,
-                                     [&](const char *data, size_t data_len) {
-                                         req.body.append(data, data_len);
-                                         return true;
-                                     })) {
-                return nullptr;
-            }
-        }
-
-        req.headers.emplace("Content-Encoding", "gzip");
-    } else
+  if (compress_) { req.headers.emplace("Content-Encoding", "gzip"); }
 #endif
-    {
-        if (content_provider) {
-            req.content_length   = content_length;
-            req.content_provider = content_provider;
-        } else {
-            req.body = body;
-        }
-    }
 
-    auto res = std::make_shared<Response>();
+#ifdef CPPHTTPLIB_ZLIB_SUPPORT
+  if (compress_ && !content_provider_without_length) {
+    // TODO: Brotli support
+    detail::gzip_compressor compressor;
 
-    return send(req, *res) ? res : nullptr;
-}
+    if (content_provider) {
+      auto ok = true;
+      size_t offset = 0;
+      DataSink data_sink;
 
-inline bool ClientImpl::process_request(Stream &strm,
-                                        const Request &req,
-                                        Response &res,
-                                        bool close_connection) {
-    // Send request
-    if (!write_request(strm, req, close_connection)) {
-        return false;
-    }
+      data_sink.write = [&](const char *data, size_t data_len) -> bool {
+        if (ok) {
+          auto last = offset + data_len == content_length;
 
-    // Receive response and headers
-    if (!read_response_line(strm, res) || !detail::read_headers(strm, res.headers)) {
-        error_ = Error::Read;
-        return false;
-    }
+          auto ret = compressor.compress(
+              data, data_len, last, [&](const char *compressed_data, size_t compressed_data_len) {
+                req.body.append(compressed_data, compressed_data_len);
+                return true;
+              });
 
-    if (req.response_handler) {
-        if (!req.response_handler(res)) {
-            error_ = Error::Canceled;
-            return false;
+          if (ret) {
+            offset += data_len;
+          } else {
+            ok = false;
+          }
         }
-    }
+        return ok;
+      };
 
-    // Body
-    if (req.method != "HEAD" && req.method != "CONNECT") {
-        auto out = req.content_receiver
-                       ? static_cast<ContentReceiver>([&](const char *buf, size_t n) {
-                             auto ret = req.content_receiver(buf, n);
-                             if (!ret) {
-                                 error_ = Error::Canceled;
-                             }
-                             return ret;
-                         })
-                       : static_cast<ContentReceiver>([&](const char *buf, size_t n) {
-                             if (res.body.size() + n > res.body.max_size()) {
-                                 return false;
-                             }
-                             res.body.append(buf, n);
-                             return true;
-                         });
-
-        auto progress = [&](uint64_t current, uint64_t total) {
-            if (!req.progress) {
-                return true;
-            }
-            auto ret = req.progress(current, total);
-            if (!ret) {
-                error_ = Error::Canceled;
-            }
-            return ret;
-        };
+      data_sink.is_writable = [&](void) { return ok && true; };
 
-        int dummy_status;
-        if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(), dummy_status,
-                                  progress, out, decompress_)) {
-            if (error_ != Error::Canceled) {
-                error_ = Error::Read;
-            }
-            return false;
+      while (ok && offset < content_length) {
+        if (!content_provider(offset, content_length - offset, data_sink)) {
+          error = Error::Canceled;
+          return nullptr;
         }
+      }
+    } else {
+      if (!compressor.compress(body, content_length, true,
+                               [&](const char *data, size_t data_len) {
+                                 req.body.append(data, data_len);
+                                 return true;
+                               })) {
+        error = Error::Compression;
+        return nullptr;
+      }
+    }
+  } else
+#endif
+  {
+    if (content_provider) {
+      req.content_length_ = content_length;
+      req.content_provider_ = std::move(content_provider);
+      req.is_chunked_content_provider_ = false;
+    } else if (content_provider_without_length) {
+      req.content_length_ = 0;
+      req.content_provider_ = detail::ContentProviderAdapter(
+          std::move(content_provider_without_length));
+      req.is_chunked_content_provider_ = true;
+      req.headers.emplace("Transfer-Encoding", "chunked");
+    } else {
+      req.body.assign(body, content_length);
+      ;
     }
+  }
 
-    if (res.get_header_value("Connection") == "close" ||
-        (res.version == "HTTP/1.0" && res.reason != "Connection established")) {
-        stop_core();
-    }
+  auto res = detail::make_unique<Response>();
+  return send(req, *res, error) ? std::move(res) : nullptr;
+}
 
-    // Log
-    if (logger_) {
-        logger_(req, res);
-    }
+inline Result ClientImpl::send_with_content_provider(
+    const char *method, const char *path, const Headers &headers,
+    const char *body, size_t content_length, ContentProvider content_provider,
+    ContentProviderWithoutLength content_provider_without_length,
+    const char *content_type) {
+  Request req;
+  req.method = method;
+  req.headers = headers;
+  req.path = path;
 
-    return true;
+  auto error = Error::Success;
+
+  auto res = send_with_content_provider(
+      req,
+      // method, path, headers,
+      body, content_length, std::move(content_provider),
+      std::move(content_provider_without_length), content_type, error);
+
+  return Result{std::move(res), error, std::move(req.headers)};
 }
 
-inline bool ClientImpl::process_socket(Socket &socket, std::function<bool(Stream &strm)> callback) {
-    return detail::process_client_socket(socket.sock, read_timeout_sec_, read_timeout_usec_,
-                                         write_timeout_sec_, write_timeout_usec_, callback);
+inline std::string
+ClientImpl::adjust_host_string(const std::string &host) const {
+  if (host.find(':') != std::string::npos) { return "[" + host + "]"; }
+  return host;
 }
 
-inline bool ClientImpl::is_ssl() const {
+inline bool ClientImpl::process_request(Stream &strm, Request &req,
+                                        Response &res, bool close_connection,
+                                        Error &error) {
+  // Send request
+  if (!write_request(strm, req, close_connection, error)) { return false; }
+
+  // Receive response and headers
+  if (!read_response_line(strm, req, res) ||
+      !detail::read_headers(strm, res.headers)) {
+    error = Error::Read;
     return false;
+  }
+
+  // Body
+  if ((res.status != 204) && req.method != "HEAD" && req.method != "CONNECT") {
+    auto redirect = 300 < res.status && res.status < 400 && follow_location_;
+
+    if (req.response_handler && !redirect) {
+      if (!req.response_handler(res)) {
+        error = Error::Canceled;
+        return false;
+      }
+    }
+
+    auto out =
+        req.content_receiver
+            ? static_cast<ContentReceiverWithProgress>(
+                  [&](const char *buf, size_t n, uint64_t off, uint64_t len) {
+                    if (redirect) { return true; }
+                    auto ret = req.content_receiver(buf, n, off, len);
+                    if (!ret) { error = Error::Canceled; }
+                    return ret;
+                  })
+            : static_cast<ContentReceiverWithProgress>(
+                  [&](const char *buf, size_t n, uint64_t /*off*/,
+                      uint64_t /*len*/) {
+                    if (res.body.size() + n > res.body.max_size()) {
+                      return false;
+                    }
+                    res.body.append(buf, n);
+                    return true;
+                  });
+
+    auto progress = [&](uint64_t current, uint64_t total) {
+      if (!req.progress || redirect) { return true; }
+      auto ret = req.progress(current, total);
+      if (!ret) { error = Error::Canceled; }
+      return ret;
+    };
+
+    int dummy_status;
+    if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
+                              dummy_status, std::move(progress), std::move(out),
+                              decompress_)) {
+      if (error != Error::Canceled) { error = Error::Read; }
+      return false;
+    }
+  }
+
+  if (res.get_header_value("Connection") == "close" ||
+      (res.version == "HTTP/1.0" && res.reason != "Connection established")) {
+    // TODO this requires a not-entirely-obvious chain of calls to be correct
+    // for this to be safe. Maybe a code refactor (such as moving this out to
+    // the send function and getting rid of the recursiveness of the mutex)
+    // could make this more obvious.
+
+    // This is safe to call because process_request is only called by
+    // handle_request which is only called by send, which locks the request
+    // mutex during the process. It would be a bug to call it from a different
+    // thread since it's a thread-safety issue to do these things to the socket
+    // if another thread is using the socket.
+    std::lock_guard<std::mutex> guard(socket_mutex_);
+    shutdown_ssl(socket_, true);
+    shutdown_socket(socket_);
+    close_socket(socket_);
+  }
+
+  // Log
+  if (logger_) { logger_(req, res); }
+
+  return true;
 }
 
-inline Result ClientImpl::Get2(std::string_view path, ClientOptions options) {
-    auto path2 = std::string(path);
-    return Get(path2.c_str(), std::move(options.headers), std::move(options.response_handler),
-               std::move(options.content_receiver), std::move(options.progress));
+inline bool
+ClientImpl::process_socket(const Socket &socket,
+                           std::function<bool(Stream &strm)> callback) {
+  return detail::process_client_socket(
+      socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
+      write_timeout_usec_, std::move(callback));
 }
 
+inline bool ClientImpl::is_ssl() const { return false; }
+
 inline Result ClientImpl::Get(const char *path) {
-    return Get(path, Headers(), Progress());
+  return Get(path, Headers(), Progress());
 }
 
 inline Result ClientImpl::Get(const char *path, Progress progress) {
-    return Get(path, Headers(), std::move(progress));
+  return Get(path, Headers(), std::move(progress));
 }
 
 inline Result ClientImpl::Get(const char *path, const Headers &headers) {
-    return Get(path, headers, Progress());
+  return Get(path, headers, Progress());
 }
 
-inline Result ClientImpl::Get(const char *path, const Headers &headers, Progress progress) {
-    Request req;
-    req.method  = "GET";
-    req.path    = path;
-    req.headers = default_headers_;
-    req.headers.insert(headers.begin(), headers.end());
-    req.progress = std::move(progress);
+inline Result ClientImpl::Get(const char *path, const Headers &headers,
+                              Progress progress) {
+  Request req;
+  req.method = "GET";
+  req.path = path;
+  req.headers = headers;
+  req.progress = std::move(progress);
 
-    auto res = std::make_shared<Response>();
-    auto ret = send(req, *res);
-    return Result{ret ? res : nullptr, get_last_error()};
+  return send_(std::move(req));
 }
 
-inline Result ClientImpl::Get(const char *path, ContentReceiver content_receiver) {
-    return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr);
+inline Result ClientImpl::Get(const char *path,
+                              ContentReceiver content_receiver) {
+  return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr);
 }
 
 inline Result ClientImpl::Get(const char *path,
                               ContentReceiver content_receiver,
                               Progress progress) {
-    return Get(path, Headers(), nullptr, std::move(content_receiver), std::move(progress));
+  return Get(path, Headers(), nullptr, std::move(content_receiver),
+             std::move(progress));
 }
 
-inline Result ClientImpl::Get(const char *path,
-                              const Headers &headers,
+inline Result ClientImpl::Get(const char *path, const Headers &headers,
                               ContentReceiver content_receiver) {
-    return Get(path, headers, nullptr, std::move(content_receiver), nullptr);
+  return Get(path, headers, nullptr, std::move(content_receiver), nullptr);
 }
 
-inline Result ClientImpl::Get(const char *path,
-                              const Headers &headers,
+inline Result ClientImpl::Get(const char *path, const Headers &headers,
                               ContentReceiver content_receiver,
                               Progress progress) {
-    return Get(path, headers, nullptr, std::move(content_receiver), std::move(progress));
+  return Get(path, headers, nullptr, std::move(content_receiver),
+             std::move(progress));
 }
 
 inline Result ClientImpl::Get(const char *path,
                               ResponseHandler response_handler,
                               ContentReceiver content_receiver) {
-    return Get(path, Headers(), std::move(response_handler), content_receiver, nullptr);
+  return Get(path, Headers(), std::move(response_handler),
+             std::move(content_receiver), nullptr);
 }
 
-inline Result ClientImpl::Get(const char *path,
-                              const Headers &headers,
+inline Result ClientImpl::Get(const char *path, const Headers &headers,
                               ResponseHandler response_handler,
                               ContentReceiver content_receiver) {
-    return Get(path, headers, std::move(response_handler), content_receiver, nullptr);
+  return Get(path, headers, std::move(response_handler),
+             std::move(content_receiver), nullptr);
 }
 
 inline Result ClientImpl::Get(const char *path,
                               ResponseHandler response_handler,
                               ContentReceiver content_receiver,
                               Progress progress) {
-    return Get(path, Headers(), std::move(response_handler), content_receiver, progress);
+  return Get(path, Headers(), std::move(response_handler),
+             std::move(content_receiver), std::move(progress));
 }
 
-inline Result ClientImpl::Get(const char *path,
+inline Result ClientImpl::Get(const char *path, const Headers &headers,
+                              ResponseHandler response_handler,
+                              ContentReceiver content_receiver,
+                              Progress progress) {
+  Request req;
+  req.method = "GET";
+  req.path = path;
+  req.headers = headers;
+  req.response_handler = std::move(response_handler);
+  req.content_receiver =
+      [content_receiver](const char *data, size_t data_length,
+                         uint64_t /*offset*/, uint64_t /*total_length*/) {
+        return content_receiver(data, data_length);
+      };
+  req.progress = std::move(progress);
+
+  return send_(std::move(req));
+}
+
+inline Result ClientImpl::Get(const char *path, const Params &params,
+                              const Headers &headers, Progress progress) {
+  if (params.empty()) { return Get(path, headers); }
+
+  std::string path_with_query = append_query_params(path, params);
+  return Get(path_with_query.c_str(), headers, progress);
+}
+
+inline Result ClientImpl::Get(const char *path, const Params &params,
+                              const Headers &headers,
+                              ContentReceiver content_receiver,
+                              Progress progress) {
+  return Get(path, params, headers, nullptr, content_receiver, progress);
+}
+
+inline Result ClientImpl::Get(const char *path, const Params &params,
                               const Headers &headers,
                               ResponseHandler response_handler,
                               ContentReceiver content_receiver,
                               Progress progress) {
-    Request req;
-    req.method  = "GET";
-    req.path    = path;
-    req.headers = default_headers_;
-    req.headers.insert(headers.begin(), headers.end());
-    req.response_handler = std::move(response_handler);
-    req.content_receiver = std::move(content_receiver);
-    req.progress         = std::move(progress);
+  if (params.empty()) {
+    return Get(path, headers, response_handler, content_receiver, progress);
+  }
 
-    auto res = std::make_shared<Response>();
-    auto ret = send(req, *res);
-    return Result{ret ? res : nullptr, get_last_error()};
+  std::string path_with_query = append_query_params(path, params);
+  return Get(path_with_query.c_str(), headers, response_handler,
+             content_receiver, progress);
 }
 
 inline Result ClientImpl::Head(const char *path) {
-    return Head(path, Headers());
+  return Head(path, Headers());
 }
 
 inline Result ClientImpl::Head(const char *path, const Headers &headers) {
-    Request req;
-    req.method  = "HEAD";
-    req.headers = default_headers_;
-    req.headers.insert(headers.begin(), headers.end());
-    req.path = path;
+  Request req;
+  req.method = "HEAD";
+  req.headers = headers;
+  req.path = path;
 
-    auto res = std::make_shared<Response>();
-    auto ret = send(req, *res);
-    return Result{ret ? res : nullptr, get_last_error()};
+  return send_(std::move(req));
 }
 
 inline Result ClientImpl::Post(const char *path) {
-    return Post(path, std::string(), nullptr);
+  return Post(path, std::string(), nullptr);
 }
 
-inline Result ClientImpl::Post(const char *path,
-                               const std::string &body,
+inline Result ClientImpl::Post(const char *path, const char *body,
+                               size_t content_length,
                                const char *content_type) {
-    return Post(path, Headers(), body, content_type);
+  return Post(path, Headers(), body, content_length, content_type);
 }
 
-inline Result ClientImpl::Post(const char *path,
-                               const Headers &headers,
+inline Result ClientImpl::Post(const char *path, const Headers &headers,
+                               const char *body, size_t content_length,
+                               const char *content_type) {
+  return send_with_content_provider("POST", path, headers, body, content_length,
+                                    nullptr, nullptr, content_type);
+}
+
+inline Result ClientImpl::Post(const char *path, const std::string &body,
+                               const char *content_type) {
+  return Post(path, Headers(), body, content_type);
+}
+
+inline Result ClientImpl::Post(const char *path, const Headers &headers,
                                const std::string &body,
                                const char *content_type) {
-    auto ret = send_with_content_provider("POST", path, headers, body, 0, nullptr, content_type);
-    return Result{ret, get_last_error()};
+  return send_with_content_provider("POST", path, headers, body.data(),
+                                    body.size(), nullptr, nullptr,
+                                    content_type);
 }
 
 inline Result ClientImpl::Post(const char *path, const Params &params) {
-    return Post(path, Headers(), params);
+  return Post(path, Headers(), params);
 }
 
-inline Result ClientImpl::Post(const char *path,
-                               size_t content_length,
+inline Result ClientImpl::Post(const char *path, size_t content_length,
                                ContentProvider content_provider,
                                const char *content_type) {
-    return Post(path, Headers(), content_length, content_provider, content_type);
+  return Post(path, Headers(), content_length, std::move(content_provider),
+              content_type);
 }
 
 inline Result ClientImpl::Post(const char *path,
-                               const Headers &headers,
+                               ContentProviderWithoutLength content_provider,
+                               const char *content_type) {
+  return Post(path, Headers(), std::move(content_provider), content_type);
+}
+
+inline Result ClientImpl::Post(const char *path, const Headers &headers,
                                size_t content_length,
                                ContentProvider content_provider,
                                const char *content_type) {
-    auto ret = send_with_content_provider("POST", path, headers, std::string(), content_length,
-                                          content_provider, content_type);
-    return Result{ret, get_last_error()};
+  return send_with_content_provider("POST", path, headers, nullptr,
+                                    content_length, std::move(content_provider),
+                                    nullptr, content_type);
 }
 
-inline Result ClientImpl::Post(const char *path, const Headers &headers, const Params &params) {
-    auto query = detail::params_to_query_str(params);
-    return Post(path, headers, query, "application/x-www-form-urlencoded");
+inline Result ClientImpl::Post(const char *path, const Headers &headers,
+                               ContentProviderWithoutLength content_provider,
+                               const char *content_type) {
+  return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr,
+                                    std::move(content_provider), content_type);
 }
 
-inline Result ClientImpl::Post(const char *path, const MultipartFormDataItems &items) {
-    return Post(path, Headers(), items);
+inline Result ClientImpl::Post(const char *path, const Headers &headers,
+                               const Params &params) {
+  auto query = detail::params_to_query_str(params);
+  return Post(path, headers, query, "application/x-www-form-urlencoded");
 }
 
 inline Result ClientImpl::Post(const char *path,
-                               const Headers &headers,
                                const MultipartFormDataItems &items) {
-    auto boundary = detail::make_multipart_data_boundary();
+  return Post(path, Headers(), items);
+}
+
+inline Result ClientImpl::Post(const char *path, const Headers &headers,
+                               const MultipartFormDataItems &items) {
+  return Post(path, headers, items, detail::make_multipart_data_boundary());
+}
+inline Result ClientImpl::Post(const char *path, const Headers &headers,
+                               const MultipartFormDataItems &items,
+                               const std::string &boundary) {
+  for (size_t i = 0; i < boundary.size(); i++) {
+    char c = boundary[i];
+    if (!std::isalnum(c) && c != '-' && c != '_') {
+      return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
+    }
+  }
 
-    std::string body;
+  std::string body;
 
-    for (const auto &item : items) {
-        body += "--" + boundary + "\r\n";
-        body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
-        if (!item.filename.empty()) {
-            body += "; filename=\"" + item.filename + "\"";
-        }
-        body += "\r\n";
-        if (!item.content_type.empty()) {
-            body += "Content-Type: " + item.content_type + "\r\n";
-        }
-        body += "\r\n";
-        body += item.content + "\r\n";
+  for (const auto &item : items) {
+    body += "--" + boundary + "\r\n";
+    body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
+    if (!item.filename.empty()) {
+      body += "; filename=\"" + item.filename + "\"";
     }
+    body += "\r\n";
+    if (!item.content_type.empty()) {
+      body += "Content-Type: " + item.content_type + "\r\n";
+    }
+    body += "\r\n";
+    body += item.content + "\r\n";
+  }
 
-    body += "--" + boundary + "--\r\n";
+  body += "--" + boundary + "--\r\n";
 
-    std::string content_type = "multipart/form-data; boundary=" + boundary;
-    return Post(path, headers, body, content_type.c_str());
+  std::string content_type = "multipart/form-data; boundary=" + boundary;
+  return Post(path, headers, body, content_type.c_str());
 }
 
 inline Result ClientImpl::Put(const char *path) {
-    return Put(path, std::string(), nullptr);
+  return Put(path, std::string(), nullptr);
 }
 
-inline Result ClientImpl::Put(const char *path, const std::string &body, const char *content_type) {
-    return Put(path, Headers(), body, content_type);
+inline Result ClientImpl::Put(const char *path, const char *body,
+                              size_t content_length, const char *content_type) {
+  return Put(path, Headers(), body, content_length, content_type);
 }
 
-inline Result ClientImpl::Put(const char *path,
-                              const Headers &headers,
+inline Result ClientImpl::Put(const char *path, const Headers &headers,
+                              const char *body, size_t content_length,
+                              const char *content_type) {
+  return send_with_content_provider("PUT", path, headers, body, content_length,
+                                    nullptr, nullptr, content_type);
+}
+
+inline Result ClientImpl::Put(const char *path, const std::string &body,
+                              const char *content_type) {
+  return Put(path, Headers(), body, content_type);
+}
+
+inline Result ClientImpl::Put(const char *path, const Headers &headers,
                               const std::string &body,
                               const char *content_type) {
-    auto ret = send_with_content_provider("PUT", path, headers, body, 0, nullptr, content_type);
-    return Result{ret, get_last_error()};
+  return send_with_content_provider("PUT", path, headers, body.data(),
+                                    body.size(), nullptr, nullptr,
+                                    content_type);
 }
 
-inline Result ClientImpl::Put(const char *path,
-                              size_t content_length,
+inline Result ClientImpl::Put(const char *path, size_t content_length,
                               ContentProvider content_provider,
                               const char *content_type) {
-    return Put(path, Headers(), content_length, content_provider, content_type);
+  return Put(path, Headers(), content_length, std::move(content_provider),
+             content_type);
 }
 
 inline Result ClientImpl::Put(const char *path,
-                              const Headers &headers,
+                              ContentProviderWithoutLength content_provider,
+                              const char *content_type) {
+  return Put(path, Headers(), std::move(content_provider), content_type);
+}
+
+inline Result ClientImpl::Put(const char *path, const Headers &headers,
                               size_t content_length,
                               ContentProvider content_provider,
                               const char *content_type) {
-    auto ret = send_with_content_provider("PUT", path, headers, std::string(), content_length,
-                                          content_provider, content_type);
-    return Result{ret, get_last_error()};
+  return send_with_content_provider("PUT", path, headers, nullptr,
+                                    content_length, std::move(content_provider),
+                                    nullptr, content_type);
+}
+
+inline Result ClientImpl::Put(const char *path, const Headers &headers,
+                              ContentProviderWithoutLength content_provider,
+                              const char *content_type) {
+  return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr,
+                                    std::move(content_provider), content_type);
 }
 
 inline Result ClientImpl::Put(const char *path, const Params &params) {
-    return Put(path, Headers(), params);
+  return Put(path, Headers(), params);
 }
 
-inline Result ClientImpl::Put(const char *path, const Headers &headers, const Params &params) {
-    auto query = detail::params_to_query_str(params);
-    return Put(path, headers, query, "application/x-www-form-urlencoded");
+inline Result ClientImpl::Put(const char *path, const Headers &headers,
+                              const Params &params) {
+  auto query = detail::params_to_query_str(params);
+  return Put(path, headers, query, "application/x-www-form-urlencoded");
 }
 
-inline Result ClientImpl::Patch(const char *path,
-                                const std::string &body,
+inline Result ClientImpl::Patch(const char *path) {
+  return Patch(path, std::string(), nullptr);
+}
+
+inline Result ClientImpl::Patch(const char *path, const char *body,
+                                size_t content_length,
                                 const char *content_type) {
-    return Patch(path, Headers(), body, content_type);
+  return Patch(path, Headers(), body, content_length, content_type);
 }
 
-inline Result ClientImpl::Patch(const char *path,
-                                const Headers &headers,
+inline Result ClientImpl::Patch(const char *path, const Headers &headers,
+                                const char *body, size_t content_length,
+                                const char *content_type) {
+  return send_with_content_provider("PATCH", path, headers, body,
+                                    content_length, nullptr, nullptr,
+                                    content_type);
+}
+
+inline Result ClientImpl::Patch(const char *path, const std::string &body,
+                                const char *content_type) {
+  return Patch(path, Headers(), body, content_type);
+}
+
+inline Result ClientImpl::Patch(const char *path, const Headers &headers,
                                 const std::string &body,
                                 const char *content_type) {
-    auto ret = send_with_content_provider("PATCH", path, headers, body, 0, nullptr, content_type);
-    return Result{ret, get_last_error()};
+  return send_with_content_provider("PATCH", path, headers, body.data(),
+                                    body.size(), nullptr, nullptr,
+                                    content_type);
 }
 
-inline Result ClientImpl::Patch(const char *path,
-                                size_t content_length,
+inline Result ClientImpl::Patch(const char *path, size_t content_length,
                                 ContentProvider content_provider,
                                 const char *content_type) {
-    return Patch(path, Headers(), content_length, content_provider, content_type);
+  return Patch(path, Headers(), content_length, std::move(content_provider),
+               content_type);
 }
 
 inline Result ClientImpl::Patch(const char *path,
-                                const Headers &headers,
+                                ContentProviderWithoutLength content_provider,
+                                const char *content_type) {
+  return Patch(path, Headers(), std::move(content_provider), content_type);
+}
+
+inline Result ClientImpl::Patch(const char *path, const Headers &headers,
                                 size_t content_length,
                                 ContentProvider content_provider,
                                 const char *content_type) {
-    auto ret = send_with_content_provider("PATCH", path, headers, std::string(), content_length,
-                                          content_provider, content_type);
-    return Result{ret, get_last_error()};
+  return send_with_content_provider("PATCH", path, headers, nullptr,
+                                    content_length, std::move(content_provider),
+                                    nullptr, content_type);
 }
 
-inline Result ClientImpl::Delete(const char *path) {
-    return Delete(path, Headers(), std::string(), nullptr);
+inline Result ClientImpl::Patch(const char *path, const Headers &headers,
+                                ContentProviderWithoutLength content_provider,
+                                const char *content_type) {
+  return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr,
+                                    std::move(content_provider), content_type);
 }
 
-inline Result ClientImpl::Delete(const char *path,
-                                 const std::string &body,
-                                 const char *content_type) {
-    return Delete(path, Headers(), body, content_type);
+inline Result ClientImpl::Delete(const char *path) {
+  return Delete(path, Headers(), std::string(), nullptr);
 }
 
 inline Result ClientImpl::Delete(const char *path, const Headers &headers) {
-    return Delete(path, headers, std::string(), nullptr);
+  return Delete(path, headers, std::string(), nullptr);
 }
 
-inline Result ClientImpl::Delete(const char *path,
-                                 const Headers &headers,
-                                 const std::string &body,
+inline Result ClientImpl::Delete(const char *path, const char *body,
+                                 size_t content_length,
                                  const char *content_type) {
-    Request req;
-    req.method  = "DELETE";
-    req.headers = default_headers_;
-    req.headers.insert(headers.begin(), headers.end());
-    req.path = path;
+  return Delete(path, Headers(), body, content_length, content_type);
+}
 
-    if (content_type) {
-        req.headers.emplace("Content-Type", content_type);
-    }
-    req.body = body;
+inline Result ClientImpl::Delete(const char *path, const Headers &headers,
+                                 const char *body, size_t content_length,
+                                 const char *content_type) {
+  Request req;
+  req.method = "DELETE";
+  req.headers = headers;
+  req.path = path;
 
-    auto res = std::make_shared<Response>();
-    auto ret = send(req, *res);
-    return Result{ret ? res : nullptr, get_last_error()};
+  if (content_type) { req.headers.emplace("Content-Type", content_type); }
+  req.body.assign(body, content_length);
+
+  return send_(std::move(req));
+}
+
+inline Result ClientImpl::Delete(const char *path, const std::string &body,
+                                 const char *content_type) {
+  return Delete(path, Headers(), body.data(), body.size(), content_type);
+}
+
+inline Result ClientImpl::Delete(const char *path, const Headers &headers,
+                                 const std::string &body,
+                                 const char *content_type) {
+  return Delete(path, headers, body.data(), body.size(), content_type);
 }
 
 inline Result ClientImpl::Options(const char *path) {
-    return Options(path, Headers());
+  return Options(path, Headers());
 }
 
 inline Result ClientImpl::Options(const char *path, const Headers &headers) {
-    Request req;
-    req.method  = "OPTIONS";
-    req.headers = default_headers_;
-    req.headers.insert(headers.begin(), headers.end());
-    req.path = path;
+  Request req;
+  req.method = "OPTIONS";
+  req.headers = headers;
+  req.path = path;
 
-    auto res = std::make_shared<Response>();
-    auto ret = send(req, *res);
-    return Result{ret ? res : nullptr, get_last_error()};
+  return send_(std::move(req));
 }
 
 inline size_t ClientImpl::is_socket_open() const {
-    std::lock_guard<std::mutex> guard(socket_mutex_);
-    return socket_.is_open();
+  std::lock_guard<std::mutex> guard(socket_mutex_);
+  return socket_.is_open();
 }
 
 inline void ClientImpl::stop() {
-    stop_core();
-    error_ = Error::Canceled;
-}
+  std::lock_guard<std::mutex> guard(socket_mutex_);
 
-inline void ClientImpl::stop_core() {
-    std::lock_guard<std::mutex> guard(socket_mutex_);
-    if (socket_.is_open()) {
-        detail::shutdown_socket(socket_.sock);
-        std::this_thread::sleep_for(std::chrono::milliseconds(1));
-        close_socket(socket_, true);
-        std::this_thread::sleep_for(std::chrono::milliseconds(1));
-    }
+  // If there is anything ongoing right now, the ONLY thread-safe thing we can
+  // do is to shutdown_socket, so that threads using this socket suddenly
+  // discover they can't read/write any more and error out. Everything else
+  // (closing the socket, shutting ssl down) is unsafe because these actions are
+  // not thread-safe.
+  if (socket_requests_in_flight_ > 0) {
+    shutdown_socket(socket_);
+
+    // Aside from that, we set a flag for the socket to be closed when we're
+    // done.
+    socket_should_be_closed_when_request_is_done_ = true;
+    return;
+  }
+
+  // Otherwise, sitll holding the mutex, we can shut everything down ourselves
+  shutdown_ssl(socket_, true);
+  shutdown_socket(socket_);
+  close_socket(socket_);
 }
 
 inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) {
-    connection_timeout_sec_  = sec;
-    connection_timeout_usec_ = usec;
+  connection_timeout_sec_ = sec;
+  connection_timeout_usec_ = usec;
 }
 
 inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) {
-    read_timeout_sec_  = sec;
-    read_timeout_usec_ = usec;
+  read_timeout_sec_ = sec;
+  read_timeout_usec_ = usec;
 }
 
 inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) {
-    write_timeout_sec_  = sec;
-    write_timeout_usec_ = usec;
+  write_timeout_sec_ = sec;
+  write_timeout_usec_ = usec;
 }
 
-inline void ClientImpl::set_basic_auth(const char *username, const char *password) {
-    basic_auth_username_ = username;
-    basic_auth_password_ = password;
+inline void ClientImpl::set_basic_auth(const char *username,
+                                       const char *password) {
+  basic_auth_username_ = username;
+  basic_auth_password_ = password;
 }
 
 inline void ClientImpl::set_bearer_token_auth(const char *token) {
-    bearer_token_auth_token_ = token;
+  bearer_token_auth_token_ = token;
 }
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-inline void ClientImpl::set_digest_auth(const char *username, const char *password) {
-    digest_auth_username_ = username;
-    digest_auth_password_ = password;
+inline void ClientImpl::set_digest_auth(const char *username,
+                                        const char *password) {
+  digest_auth_username_ = username;
+  digest_auth_password_ = password;
 }
 #endif
 
-inline void ClientImpl::set_keep_alive(bool on) {
-    keep_alive_ = on;
-}
+inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; }
+
+inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; }
+
+inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; }
 
-inline void ClientImpl::set_follow_location(bool on) {
-    follow_location_ = on;
+inline void
+ClientImpl::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
+  addr_map_ = std::move(addr_map);
 }
 
 inline void ClientImpl::set_default_headers(Headers headers) {
-    default_headers_ = std::move(headers);
+  default_headers_ = std::move(headers);
 }
 
-inline void ClientImpl::set_tcp_nodelay(bool on) {
-    tcp_nodelay_ = on;
+inline void ClientImpl::set_address_family(int family) {
+  address_family_ = family;
 }
 
+inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
+
 inline void ClientImpl::set_socket_options(SocketOptions socket_options) {
-    socket_options_ = socket_options;
+  socket_options_ = std::move(socket_options);
 }
 
-inline void ClientImpl::set_compress(bool on) {
-    compress_ = on;
-}
+inline void ClientImpl::set_compress(bool on) { compress_ = on; }
 
-inline void ClientImpl::set_decompress(bool on) {
-    decompress_ = on;
-}
+inline void ClientImpl::set_decompress(bool on) { decompress_ = on; }
 
-inline void ClientImpl::set_interface(const char *intf) {
-    interface_ = intf;
-}
+inline void ClientImpl::set_interface(const char *intf) { interface_ = intf; }
 
 inline void ClientImpl::set_proxy(const char *host, int port) {
-    proxy_host_ = host;
-    proxy_port_ = port;
+  proxy_host_ = host;
+  proxy_port_ = port;
 }
 
-inline void ClientImpl::set_proxy_basic_auth(const char *username, const char *password) {
-    proxy_basic_auth_username_ = username;
-    proxy_basic_auth_password_ = password;
+inline void ClientImpl::set_proxy_basic_auth(const char *username,
+                                             const char *password) {
+  proxy_basic_auth_username_ = username;
+  proxy_basic_auth_password_ = password;
 }
 
 inline void ClientImpl::set_proxy_bearer_token_auth(const char *token) {
-    proxy_bearer_token_auth_token_ = token;
+  proxy_bearer_token_auth_token_ = token;
+}
+
+#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
+inline void ClientImpl::set_proxy_digest_auth(const char *username,
+                                              const char *password) {
+  proxy_digest_auth_username_ = username;
+  proxy_digest_auth_password_ = password;
 }
+#endif
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-inline void ClientImpl::set_proxy_digest_auth(const char *username, const char *password) {
-    proxy_digest_auth_username_ = username;
-    proxy_digest_auth_password_ = password;
+inline void ClientImpl::set_ca_cert_path(const char *ca_cert_file_path,
+                                         const char *ca_cert_dir_path) {
+  if (ca_cert_file_path) { ca_cert_file_path_ = ca_cert_file_path; }
+  if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; }
+}
+
+inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) {
+  if (ca_cert_store && ca_cert_store != ca_cert_store_) {
+    ca_cert_store_ = ca_cert_store;
+  }
 }
 #endif
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
 inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
-    server_certificate_verification_ = enabled;
+  server_certificate_verification_ = enabled;
 }
 #endif
 
 inline void ClientImpl::set_logger(Logger logger) {
-    logger_ = std::move(logger);
+  logger_ = std::move(logger);
 }
 
 /*
@@ -5937,273 +7063,350 @@ inline void ClientImpl::set_logger(Logger logger) {
 namespace detail {
 
 template <typename U, typename V>
-inline SSL *ssl_new(socket_t sock,
-                    SSL_CTX *ctx,
-                    std::mutex &ctx_mutex,
-                    U SSL_connect_or_accept,
-                    V setup) {
-    SSL *ssl = nullptr;
-    {
+inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex,
+                    U SSL_connect_or_accept, V setup) {
+  SSL *ssl = nullptr;
+  {
+    std::lock_guard<std::mutex> guard(ctx_mutex);
+    ssl = SSL_new(ctx);
+  }
+
+  if (ssl) {
+    set_nonblocking(sock, true);
+    auto bio = BIO_new_socket(static_cast<int>(sock), BIO_NOCLOSE);
+    BIO_set_nbio(bio, 1);
+    SSL_set_bio(ssl, bio, bio);
+
+    if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) {
+      SSL_shutdown(ssl);
+      {
         std::lock_guard<std::mutex> guard(ctx_mutex);
-        ssl = SSL_new(ctx);
-    }
-
-    if (ssl) {
-        auto bio = BIO_new_socket(static_cast<int>(sock), BIO_NOCLOSE);
-        SSL_set_bio(ssl, bio, bio);
-
-        if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) {
-            SSL_shutdown(ssl);
-            {
-                std::lock_guard<std::mutex> guard(ctx_mutex);
-                SSL_free(ssl);
-            }
-            return nullptr;
-        }
+        SSL_free(ssl);
+      }
+      set_nonblocking(sock, false);
+      return nullptr;
+    }
+    BIO_set_nbio(bio, 0);
+    set_nonblocking(sock, false);
+  }
+
+  return ssl;
+}
+
+inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl,
+                       bool shutdown_gracefully) {
+  // sometimes we may want to skip this to try to avoid SIGPIPE if we know
+  // the remote has closed the network connection
+  // Note that it is not always possible to avoid SIGPIPE, this is merely a
+  // best-efforts.
+  if (shutdown_gracefully) { SSL_shutdown(ssl); }
+
+  std::lock_guard<std::mutex> guard(ctx_mutex);
+  SSL_free(ssl);
+}
+
+template <typename U>
+bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl,
+                                       U ssl_connect_or_accept,
+                                       time_t timeout_sec,
+                                       time_t timeout_usec) {
+  int res = 0;
+  while ((res = ssl_connect_or_accept(ssl)) != 1) {
+    auto err = SSL_get_error(ssl, res);
+    switch (err) {
+    case SSL_ERROR_WANT_READ:
+      if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; }
+      break;
+    case SSL_ERROR_WANT_WRITE:
+      if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; }
+      break;
+    default: break;
     }
-
-    return ssl;
+    return false;
+  }
+  return true;
 }
 
-inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, bool process_socket_ret) {
-    if (process_socket_ret) {
-        SSL_shutdown(ssl);  // shutdown only if not already closed by remote
-    }
-
-    std::lock_guard<std::mutex> guard(ctx_mutex);
-    SSL_free(ssl);
+template <typename T>
+inline bool process_server_socket_ssl(
+    const std::atomic<socket_t> &svr_sock, SSL *ssl, socket_t sock,
+    size_t keep_alive_max_count, time_t keep_alive_timeout_sec,
+    time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
+    time_t write_timeout_usec, T callback) {
+  return process_server_socket_core(
+      svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
+      [&](bool close_connection, bool &connection_closed) {
+        SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
+                             write_timeout_sec, write_timeout_usec);
+        return callback(strm, close_connection, connection_closed);
+      });
 }
 
 template <typename T>
-inline bool process_server_socket_ssl(SSL *ssl,
-                                      socket_t sock,
-                                      size_t keep_alive_max_count,
-                                      time_t keep_alive_timeout_sec,
-                                      time_t read_timeout_sec,
-                                      time_t read_timeout_usec,
-                                      time_t write_timeout_sec,
-                                      time_t write_timeout_usec,
-                                      T callback) {
-    return process_server_socket_core(
-        sock, keep_alive_max_count, keep_alive_timeout_sec,
-        [&](bool close_connection, bool &connection_closed) {
-            SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, write_timeout_sec,
-                                 write_timeout_usec);
-            return callback(strm, close_connection, connection_closed);
-        });
+inline bool
+process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec,
+                          time_t read_timeout_usec, time_t write_timeout_sec,
+                          time_t write_timeout_usec, T callback) {
+  SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
+                       write_timeout_sec, write_timeout_usec);
+  return callback(strm);
 }
 
-template <typename T>
-inline bool process_client_socket_ssl(SSL *ssl,
-                                      socket_t sock,
-                                      time_t read_timeout_sec,
-                                      time_t read_timeout_usec,
-                                      time_t write_timeout_sec,
-                                      time_t write_timeout_usec,
-                                      T callback) {
-    SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, write_timeout_sec,
-                         write_timeout_usec);
-    return callback(strm);
-}
-
-#    if OPENSSL_VERSION_NUMBER < 0x10100000L
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
 static std::shared_ptr<std::vector<std::mutex>> openSSL_locks_;
 
 class SSLThreadLocks {
-  public:
-    SSLThreadLocks() {
-        openSSL_locks_ = std::make_shared<std::vector<std::mutex>>(CRYPTO_num_locks());
-        CRYPTO_set_locking_callback(locking_callback);
-    }
-
-    ~SSLThreadLocks() { CRYPTO_set_locking_callback(nullptr); }
-
-  private:
-    static void locking_callback(int mode, int type, const char * /*file*/, int /*line*/) {
-        auto &lk = (*openSSL_locks_)[static_cast<size_t>(type)];
-        if (mode & CRYPTO_LOCK) {
-            lk.lock();
-        } else {
-            lk.unlock();
-        }
+public:
+  SSLThreadLocks() {
+    openSSL_locks_ =
+        std::make_shared<std::vector<std::mutex>>(CRYPTO_num_locks());
+    CRYPTO_set_locking_callback(locking_callback);
+  }
+
+  ~SSLThreadLocks() { CRYPTO_set_locking_callback(nullptr); }
+
+private:
+  static void locking_callback(int mode, int type, const char * /*file*/,
+                               int /*line*/) {
+    auto &lk = (*openSSL_locks_)[static_cast<size_t>(type)];
+    if (mode & CRYPTO_LOCK) {
+      lk.lock();
+    } else {
+      lk.unlock();
     }
+  }
 };
 
-#    endif
+#endif
 
 class SSLInit {
-  public:
-    SSLInit() {
-#    if OPENSSL_VERSION_NUMBER < 0x1010001fL
-        SSL_load_error_strings();
-        SSL_library_init();
-#    else
-        OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
-#    endif
-    }
+public:
+  SSLInit() {
+#if OPENSSL_VERSION_NUMBER < 0x1010001fL
+    SSL_load_error_strings();
+    SSL_library_init();
+#else
+    OPENSSL_init_ssl(
+        OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
+#endif
+  }
 
-    ~SSLInit() {
-#    if OPENSSL_VERSION_NUMBER < 0x1010001fL
-        ERR_free_strings();
-#    endif
-    }
+  ~SSLInit() {
+#if OPENSSL_VERSION_NUMBER < 0x1010001fL
+    ERR_free_strings();
+#endif
+  }
 
-  private:
-#    if OPENSSL_VERSION_NUMBER < 0x10100000L
-    SSLThreadLocks thread_init_;
-#    endif
+private:
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+  SSLThreadLocks thread_init_;
+#endif
 };
 
 // SSL socket stream implementation
-inline SSLSocketStream::SSLSocketStream(socket_t sock,
-                                        SSL *ssl,
+inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl,
                                         time_t read_timeout_sec,
                                         time_t read_timeout_usec,
                                         time_t write_timeout_sec,
                                         time_t write_timeout_usec)
-    : sock_(sock),
-      ssl_(ssl),
-      read_timeout_sec_(read_timeout_sec),
+    : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec),
       read_timeout_usec_(read_timeout_usec),
       write_timeout_sec_(write_timeout_sec),
       write_timeout_usec_(write_timeout_usec) {
-    {
-        timeval tv;
-        tv.tv_sec  = static_cast<long>(read_timeout_sec);
-        tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec);
-
-        setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<char *>(&tv), sizeof(tv));
-    }
-    {
-        timeval tv;
-        tv.tv_sec  = static_cast<long>(write_timeout_sec);
-        tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec);
-
-        setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<char *>(&tv), sizeof(tv));
-    }
+  SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY);
 }
 
 inline SSLSocketStream::~SSLSocketStream() {}
 
 inline bool SSLSocketStream::is_readable() const {
-    return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
+  return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
 }
 
 inline bool SSLSocketStream::is_writable() const {
-    return detail::select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
+  return detail::select_write(sock_, write_timeout_sec_, write_timeout_usec_) >
+         0;
 }
 
 inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
-    if (SSL_pending(ssl_) > 0 || is_readable()) {
-        return SSL_read(ssl_, ptr, static_cast<int>(size));
-    }
+  size_t readbytes = 0;
+  if (SSL_pending(ssl_) > 0) {
+    auto ret = SSL_read_ex(ssl_, ptr, size, &readbytes);
+    if (ret == 1) { return static_cast<ssize_t>(readbytes); }
+    if (SSL_get_error(ssl_, ret) == SSL_ERROR_ZERO_RETURN) { return 0; }
     return -1;
+  }
+  if (!is_readable()) { return -1; }
+
+  auto ret = SSL_read_ex(ssl_, ptr, size, &readbytes);
+  if (ret == 1) { return static_cast<ssize_t>(readbytes); }
+  auto err = SSL_get_error(ssl_, ret);
+  int n = 1000;
+#ifdef _WIN32
+  while (--n >= 0 &&
+         (err == SSL_ERROR_WANT_READ ||
+          (err == SSL_ERROR_SYSCALL && WSAGetLastError() == WSAETIMEDOUT))) {
+#else
+  while (--n >= 0 && err == SSL_ERROR_WANT_READ) {
+#endif
+    if (SSL_pending(ssl_) > 0) {
+      ret = SSL_read_ex(ssl_, ptr, size, &readbytes);
+      if (ret == 1) { return static_cast<ssize_t>(readbytes); }
+      if (SSL_get_error(ssl_, ret) == SSL_ERROR_ZERO_RETURN) { return 0; }
+      return -1;
+    }
+    if (!is_readable()) { return -1; }
+    std::this_thread::sleep_for(std::chrono::milliseconds(1));
+    ret = SSL_read_ex(ssl_, ptr, size, &readbytes);
+    if (ret == 1) { return static_cast<ssize_t>(readbytes); }
+    err = SSL_get_error(ssl_, ret);
+  }
+  if (err == SSL_ERROR_ZERO_RETURN) { return 0; }
+  return -1;
 }
 
 inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
-    if (is_writable()) {
-        return SSL_write(ssl_, ptr, static_cast<int>(size));
-    }
-    return -1;
+  if (!is_writable()) { return -1; }
+  size_t written = 0;
+  auto ret = SSL_write_ex(ssl_, ptr, size, &written);
+  if (ret == 1) { return static_cast<ssize_t>(written); }
+  auto err = SSL_get_error(ssl_, ret);
+  int n = 1000;
+#ifdef _WIN32
+  while (--n >= 0 &&
+         (err == SSL_ERROR_WANT_WRITE ||
+          (err == SSL_ERROR_SYSCALL && WSAGetLastError() == WSAETIMEDOUT))) {
+#else
+  while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) {
+#endif
+    if (!is_writable()) { return -1; }
+    std::this_thread::sleep_for(std::chrono::milliseconds(1));
+    ret = SSL_write_ex(ssl_, ptr, size, &written);
+    if (ret == 1) { return static_cast<ssize_t>(written); }
+    err = SSL_get_error(ssl_, ret);
+  }
+  if (err == SSL_ERROR_ZERO_RETURN) { return 0; }
+  return -1;
 }
 
-inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, int &port) const {
-    detail::get_remote_ip_and_port(sock_, ip, port);
+inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
+                                                    int &port) const {
+  detail::get_remote_ip_and_port(sock_, ip, port);
 }
 
+inline socket_t SSLSocketStream::socket() const { return sock_; }
+
 static SSLInit sslinit_;
 
-}  // namespace detail
+} // namespace detail
 
 // SSL HTTP server implementation
-inline SSLServer::SSLServer(const char *cert_path,
-                            const char *private_key_path,
+inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
                             const char *client_ca_cert_file_path,
-                            const char *client_ca_cert_dir_path) {
-    ctx_ = SSL_CTX_new(SSLv23_server_method());
+                            const char *client_ca_cert_dir_path,
+                            const char *private_key_password) {
+  ctx_ = SSL_CTX_new(TLS_server_method());
 
-    if (ctx_) {
-        SSL_CTX_set_options(ctx_, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
-                                      SSL_OP_NO_COMPRESSION |
-                                      SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
-
-        // auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
-        // SSL_CTX_set_tmp_ecdh(ctx_, ecdh);
-        // EC_KEY_free(ecdh);
-
-        if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 ||
-            SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != 1) {
-            SSL_CTX_free(ctx_);
-            ctx_ = nullptr;
-        } else if (client_ca_cert_file_path || client_ca_cert_dir_path) {
-            // if (client_ca_cert_file_path) {
-            //   auto list = SSL_load_client_CA_file(client_ca_cert_file_path);
-            //   SSL_CTX_set_client_CA_list(ctx_, list);
-            // }
-
-            SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, client_ca_cert_dir_path);
-
-            SSL_CTX_set_verify(
-                ctx_,
-                SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,  // SSL_VERIFY_CLIENT_ONCE,
-                nullptr);
-        }
+  if (ctx_) {
+    SSL_CTX_set_options(ctx_,
+                        SSL_OP_NO_COMPRESSION |
+                            SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+
+    SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION);
+
+    // add default password callback before opening encrypted private key
+    if (private_key_password != nullptr && (private_key_password[0] != '\0')) {
+      SSL_CTX_set_default_passwd_cb_userdata(ctx_,
+                                             (char *)private_key_password);
     }
+
+    if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 ||
+        SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) !=
+            1) {
+      SSL_CTX_free(ctx_);
+      ctx_ = nullptr;
+    } else if (client_ca_cert_file_path || client_ca_cert_dir_path) {
+      SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path,
+                                    client_ca_cert_dir_path);
+
+      SSL_CTX_set_verify(
+          ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
+    }
+  }
 }
 
-inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, X509_STORE *client_ca_cert_store) {
-    ctx_ = SSL_CTX_new(SSLv23_server_method());
+inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key,
+                            X509_STORE *client_ca_cert_store) {
+  ctx_ = SSL_CTX_new(TLS_server_method());
 
-    if (ctx_) {
-        SSL_CTX_set_options(ctx_, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
-                                      SSL_OP_NO_COMPRESSION |
-                                      SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
-
-        if (SSL_CTX_use_certificate(ctx_, cert) != 1 ||
-            SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) {
-            SSL_CTX_free(ctx_);
-            ctx_ = nullptr;
-        } else if (client_ca_cert_store) {
-
-            SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
-
-            SSL_CTX_set_verify(
-                ctx_,
-                SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,  // SSL_VERIFY_CLIENT_ONCE,
-                nullptr);
-        }
+  if (ctx_) {
+    SSL_CTX_set_options(ctx_,
+                        SSL_OP_NO_COMPRESSION |
+                            SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+
+    SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION);
+
+    if (SSL_CTX_use_certificate(ctx_, cert) != 1 ||
+        SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) {
+      SSL_CTX_free(ctx_);
+      ctx_ = nullptr;
+    } else if (client_ca_cert_store) {
+      SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
+
+      SSL_CTX_set_verify(
+          ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
     }
+  }
 }
 
-inline SSLServer::~SSLServer() {
-    if (ctx_) {
-        SSL_CTX_free(ctx_);
+inline SSLServer::SSLServer(
+    const std::function<bool(SSL_CTX &ssl_ctx)> &setup_ssl_ctx_callback) {
+  ctx_ = SSL_CTX_new(TLS_method());
+  if (ctx_) {
+    if (!setup_ssl_ctx_callback(*ctx_)) {
+      SSL_CTX_free(ctx_);
+      ctx_ = nullptr;
     }
+  }
 }
 
-inline bool SSLServer::is_valid() const {
-    return ctx_;
+inline SSLServer::~SSLServer() {
+  if (ctx_) { SSL_CTX_free(ctx_); }
 }
 
+inline bool SSLServer::is_valid() const { return ctx_; }
+
+inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; }
+
 inline bool SSLServer::process_and_close_socket(socket_t sock) {
-    auto ssl =
-        detail::ssl_new(sock, ctx_, ctx_mutex_, SSL_accept, [](SSL * /*ssl*/) { return true; });
-
-    if (ssl) {
-        auto ret = detail::process_server_socket_ssl(
-            ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_,
-            read_timeout_usec_, write_timeout_sec_, write_timeout_usec_,
-            [this, ssl](Stream &strm, bool close_connection, bool &connection_closed) {
-                return process_request(strm, close_connection, connection_closed,
-                                       [&](Request &req) { req.ssl = ssl; });
-            });
-
-        detail::ssl_delete(ctx_mutex_, ssl, ret);
-        return ret;
-    }
+  auto ssl = detail::ssl_new(
+      sock, ctx_, ctx_mutex_,
+      [&](SSL *ssl2) {
+        return detail::ssl_connect_or_accept_nonblocking(
+            sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_);
+      },
+      [](SSL * /*ssl2*/) { return true; });
+
+  bool ret = false;
+  if (ssl) {
+    ret = detail::process_server_socket_ssl(
+        svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
+        read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
+        write_timeout_usec_,
+        [this, ssl](Stream &strm, bool close_connection,
+                    bool &connection_closed) {
+          return process_request(strm, close_connection, connection_closed,
+                                 [&](Request &req) { req.ssl = ssl; });
+        });
 
-    detail::close_socket(sock);
-    return false;
+    // Shutdown gracefully if the result seemed successful, non-gracefully if
+    // the connection appeared to be closed.
+    const bool shutdown_gracefully = ret;
+    detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully);
+  }
+
+  detail::shutdown_socket(sock);
+  detail::close_socket(sock);
+  return ret;
 }
 
 // SSL HTTP client implementation
@@ -6213,727 +7416,814 @@ inline SSLClient::SSLClient(const std::string &host)
 inline SSLClient::SSLClient(const std::string &host, int port)
     : SSLClient(host, port, std::string(), std::string()) {}
 
-inline SSLClient::SSLClient(const std::string &host,
-                            int port,
+inline SSLClient::SSLClient(const std::string &host, int port,
                             const std::string &client_cert_path,
                             const std::string &client_key_path)
     : ClientImpl(host, port, client_cert_path, client_key_path) {
-    ctx_ = SSL_CTX_new(SSLv23_client_method());
+  ctx_ = SSL_CTX_new(TLS_client_method());
 
-    detail::split(&host_[0], &host_[host_.size()], '.', [&](const char *b, const char *e) {
-        host_components_.emplace_back(std::string(b, e));
-    });
-    if (!client_cert_path.empty() && !client_key_path.empty()) {
-        if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), SSL_FILETYPE_PEM) != 1 ||
-            SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), SSL_FILETYPE_PEM) != 1) {
-            SSL_CTX_free(ctx_);
-            ctx_ = nullptr;
-        }
+  detail::split(&host_[0], &host_[host_.size()], '.',
+                [&](const char *b, const char *e) {
+                  host_components_.emplace_back(std::string(b, e));
+                });
+
+  if (!client_cert_path.empty() && !client_key_path.empty()) {
+    if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(),
+                                     SSL_FILETYPE_PEM) != 1 ||
+        SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(),
+                                    SSL_FILETYPE_PEM) != 1) {
+      SSL_CTX_free(ctx_);
+      ctx_ = nullptr;
     }
+  }
 }
 
-inline SSLClient::SSLClient(const std::string &host,
-                            int port,
-                            X509 *client_cert,
-                            EVP_PKEY *client_key)
+inline SSLClient::SSLClient(const std::string &host, int port,
+                            X509 *client_cert, EVP_PKEY *client_key)
     : ClientImpl(host, port) {
-    ctx_ = SSL_CTX_new(SSLv23_client_method());
+  ctx_ = SSL_CTX_new(TLS_client_method());
 
-    detail::split(&host_[0], &host_[host_.size()], '.', [&](const char *b, const char *e) {
-        host_components_.emplace_back(std::string(b, e));
-    });
-    if (client_cert != nullptr && client_key != nullptr) {
-        if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 ||
-            SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) {
-            SSL_CTX_free(ctx_);
-            ctx_ = nullptr;
-        }
-    }
-}
+  detail::split(&host_[0], &host_[host_.size()], '.',
+                [&](const char *b, const char *e) {
+                  host_components_.emplace_back(std::string(b, e));
+                });
 
-inline SSLClient::~SSLClient() {
-    if (ctx_) {
-        SSL_CTX_free(ctx_);
+  if (client_cert != nullptr && client_key != nullptr) {
+    if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 ||
+        SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) {
+      SSL_CTX_free(ctx_);
+      ctx_ = nullptr;
     }
+  }
 }
 
-inline bool SSLClient::is_valid() const {
-    return ctx_;
+inline SSLClient::~SSLClient() {
+  if (ctx_) { SSL_CTX_free(ctx_); }
+  // Make sure to shut down SSL since shutdown_ssl will resolve to the
+  // base function rather than the derived function once we get to the
+  // base class destructor, and won't free the SSL (causing a leak).
+  shutdown_ssl_impl(socket_, true);
 }
 
-inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path,
-                                        const char *ca_cert_dir_path) {
-    if (ca_cert_file_path) {
-        ca_cert_file_path_ = ca_cert_file_path;
-    }
-    if (ca_cert_dir_path) {
-        ca_cert_dir_path_ = ca_cert_dir_path;
-    }
-}
+inline bool SSLClient::is_valid() const { return ctx_; }
 
 inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) {
-    if (ca_cert_store) {
-        if (ctx_) {
-            if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) {
-                // Free memory allocated for old cert and use new store `ca_cert_store`
-                SSL_CTX_set_cert_store(ctx_, ca_cert_store);
-            }
-        } else {
-            X509_STORE_free(ca_cert_store);
-        }
+  if (ca_cert_store) {
+    if (ctx_) {
+      if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) {
+        // Free memory allocated for old cert and use new store `ca_cert_store`
+        SSL_CTX_set_cert_store(ctx_, ca_cert_store);
+      }
+    } else {
+      X509_STORE_free(ca_cert_store);
     }
+  }
 }
 
 inline long SSLClient::get_openssl_verify_result() const {
-    return verify_result_;
-}
-
-inline SSL_CTX *SSLClient::ssl_context() const {
-    return ctx_;
-}
-
-inline bool SSLClient::create_and_connect_socket(Socket &socket) {
-    return is_valid() && ClientImpl::create_and_connect_socket(socket);
-}
-
-inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, bool &success) {
-    success = true;
-    Response res2;
-
-    if (!detail::process_client_socket(socket.sock, read_timeout_sec_, read_timeout_usec_,
-                                       write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
-                                           Request req2;
-                                           req2.method = "CONNECT";
-                                           req2.path   = host_and_port_;
-                                           return process_request(strm, req2, res2, false);
-                                       })) {
-        close_socket(socket, true);
-        success = false;
-        return false;
-    }
-
-    if (res2.status == 407) {
-        if (!proxy_digest_auth_username_.empty() && !proxy_digest_auth_password_.empty()) {
-            std::map<std::string, std::string> auth;
-            if (detail::parse_www_authenticate(res2, auth, true)) {
-                Response res3;
-                if (!detail::process_client_socket(
-                        socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
-                        write_timeout_usec_, [&](Stream &strm) {
-                            Request req3;
-                            req3.method = "CONNECT";
-                            req3.path   = host_and_port_;
-                            req3.headers.insert(detail::make_digest_authentication_header(
-                                req3, auth, 1, detail::random_string(10),
-                                proxy_digest_auth_username_, proxy_digest_auth_password_, true));
-                            return process_request(strm, req3, res3, false);
-                        })) {
-                    close_socket(socket, true);
-                    success = false;
-                    return false;
-                }
-            }
-        } else {
-            res = res2;
-            return false;
-        }
+  return verify_result_;
+}
+
+inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; }
+
+inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) {
+  return is_valid() && ClientImpl::create_and_connect_socket(socket, error);
+}
+
+// Assumes that socket_mutex_ is locked and that there are no requests in flight
+inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
+                                          bool &success, Error &error) {
+  success = true;
+  Response res2;
+  if (!detail::process_client_socket(
+          socket.sock, read_timeout_sec_, read_timeout_usec_,
+          write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
+            Request req2;
+            req2.method = "CONNECT";
+            req2.path = host_and_port_;
+            return process_request(strm, req2, res2, false, error);
+          })) {
+    // Thread-safe to close everything because we are assuming there are no
+    // requests in flight
+    shutdown_ssl(socket, true);
+    shutdown_socket(socket);
+    close_socket(socket);
+    success = false;
+    return false;
+  }
+
+  if (res2.status == 407) {
+    if (!proxy_digest_auth_username_.empty() &&
+        !proxy_digest_auth_password_.empty()) {
+      std::map<std::string, std::string> auth;
+      if (detail::parse_www_authenticate(res2, auth, true)) {
+        Response res3;
+        if (!detail::process_client_socket(
+                socket.sock, read_timeout_sec_, read_timeout_usec_,
+                write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
+                  Request req3;
+                  req3.method = "CONNECT";
+                  req3.path = host_and_port_;
+                  req3.headers.insert(detail::make_digest_authentication_header(
+                      req3, auth, 1, detail::random_string(10),
+                      proxy_digest_auth_username_, proxy_digest_auth_password_,
+                      true));
+                  return process_request(strm, req3, res3, false, error);
+                })) {
+          // Thread-safe to close everything because we are assuming there are
+          // no requests in flight
+          shutdown_ssl(socket, true);
+          shutdown_socket(socket);
+          close_socket(socket);
+          success = false;
+          return false;
+        }
+      }
+    } else {
+      res = res2;
+      return false;
     }
+  }
 
-    return true;
+  return true;
 }
 
 inline bool SSLClient::load_certs() {
-    bool ret = true;
-
-    std::call_once(initialize_cert_, [&]() {
-        std::lock_guard<std::mutex> guard(ctx_mutex_);
-        if (!ca_cert_file_path_.empty()) {
-            if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), nullptr)) {
-                ret = false;
-            }
-        } else if (!ca_cert_dir_path_.empty()) {
-            if (!SSL_CTX_load_verify_locations(ctx_, nullptr, ca_cert_dir_path_.c_str())) {
-                ret = false;
-            }
-        } else {
-#    ifdef _WIN32
-            detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
-#    else
+  bool ret = true;
+
+  std::call_once(initialize_cert_, [&]() {
+    std::lock_guard<std::mutex> guard(ctx_mutex_);
+    if (!ca_cert_file_path_.empty()) {
+      if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(),
+                                         nullptr)) {
+        ret = false;
+      }
+    } else if (!ca_cert_dir_path_.empty()) {
+      if (!SSL_CTX_load_verify_locations(ctx_, nullptr,
+                                         ca_cert_dir_path_.c_str())) {
+        ret = false;
+      }
+    } else {
+#ifdef _WIN32
+      detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
+#else
       SSL_CTX_set_default_verify_paths(ctx_);
-#    endif
-        }
-    });
+#endif
+    }
+  });
 
-    return ret;
+  return ret;
 }
 
-inline bool SSLClient::initialize_ssl(Socket &socket) {
-    auto ssl = detail::ssl_new(
-        socket.sock, ctx_, ctx_mutex_,
-        [&](SSL *ssl) {
-            if (server_certificate_verification_) {
-                if (!load_certs()) {
-                    error_ = Error::SSLLoadingCerts;
-                    return false;
-                }
-                SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr);
-            }
-
-            if (SSL_connect(ssl) != 1) {
-                error_ = Error::SSLConnection;
-                return false;
-            }
+inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
+  auto ssl = detail::ssl_new(
+      socket.sock, ctx_, ctx_mutex_,
+      [&](SSL *ssl2) {
+        if (server_certificate_verification_) {
+          if (!load_certs()) {
+            error = Error::SSLLoadingCerts;
+            return false;
+          }
+          SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr);
+        }
 
-            if (server_certificate_verification_) {
-                verify_result_ = SSL_get_verify_result(ssl);
+        if (!detail::ssl_connect_or_accept_nonblocking(
+                socket.sock, ssl2, SSL_connect, connection_timeout_sec_,
+                connection_timeout_usec_)) {
+          error = Error::SSLConnection;
+          return false;
+        }
 
-                if (verify_result_ != X509_V_OK) {
-                    error_ = Error::SSLServerVerification;
-                    return false;
-                }
+        if (server_certificate_verification_) {
+          verify_result_ = SSL_get_verify_result(ssl2);
 
-                auto server_cert = SSL_get_peer_certificate(ssl);
+          if (verify_result_ != X509_V_OK) {
+            error = Error::SSLServerVerification;
+            return false;
+          }
 
-                if (server_cert == nullptr) {
-                    error_ = Error::SSLServerVerification;
-                    return false;
-                }
+          auto server_cert = SSL_get_peer_certificate(ssl2);
 
-                if (!verify_host(server_cert)) {
-                    X509_free(server_cert);
-                    error_ = Error::SSLServerVerification;
-                    return false;
-                }
-                X509_free(server_cert);
-            }
+          if (server_cert == nullptr) {
+            error = Error::SSLServerVerification;
+            return false;
+          }
 
-            return true;
-        },
-        [&](SSL *ssl) {
-            SSL_set_tlsext_host_name(ssl, host_.c_str());
-            return true;
-        });
+          if (!verify_host(server_cert)) {
+            X509_free(server_cert);
+            error = Error::SSLServerVerification;
+            return false;
+          }
+          X509_free(server_cert);
+        }
 
-    if (ssl) {
-        socket.ssl = ssl;
         return true;
-    }
+      },
+      [&](SSL *ssl2) {
+        SSL_set_tlsext_host_name(ssl2, host_.c_str());
+        return true;
+      });
 
-    close_socket(socket, false);
-    return false;
+  if (ssl) {
+    socket.ssl = ssl;
+    return true;
+  }
+
+  shutdown_socket(socket);
+  close_socket(socket);
+  return false;
 }
 
-inline void SSLClient::close_socket(Socket &socket, bool process_socket_ret) {
-    detail::close_socket(socket.sock);
-    socket_.sock = INVALID_SOCKET;
-    if (socket.ssl) {
-        detail::ssl_delete(ctx_mutex_, socket.ssl, process_socket_ret);
-        socket_.ssl = nullptr;
-    }
+inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) {
+  shutdown_ssl_impl(socket, shutdown_gracefully);
 }
 
-inline bool SSLClient::process_socket(Socket &socket, std::function<bool(Stream &strm)> callback) {
-    assert(socket.ssl);
-    return detail::process_client_socket_ssl(socket.ssl, socket.sock, read_timeout_sec_,
-                                             read_timeout_usec_, write_timeout_sec_,
-                                             write_timeout_usec_, callback);
+inline void SSLClient::shutdown_ssl_impl(Socket &socket,
+                                         bool shutdown_gracefully) {
+  if (socket.sock == INVALID_SOCKET) {
+    assert(socket.ssl == nullptr);
+    return;
+  }
+  if (socket.ssl) {
+    detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully);
+    socket.ssl = nullptr;
+  }
+  assert(socket.ssl == nullptr);
 }
 
-inline bool SSLClient::is_ssl() const {
-    return true;
+inline bool
+SSLClient::process_socket(const Socket &socket,
+                          std::function<bool(Stream &strm)> callback) {
+  assert(socket.ssl);
+  return detail::process_client_socket_ssl(
+      socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_,
+      write_timeout_sec_, write_timeout_usec_, std::move(callback));
 }
 
+inline bool SSLClient::is_ssl() const { return true; }
+
 inline bool SSLClient::verify_host(X509 *server_cert) const {
-    /* Quote from RFC2818 section 3.1 "Server Identity"
-
-       If a subjectAltName extension of type dNSName is present, that MUST
-       be used as the identity. Otherwise, the (most specific) Common Name
-       field in the Subject field of the certificate MUST be used. Although
-       the use of the Common Name is existing practice, it is deprecated and
-       Certification Authorities are encouraged to use the dNSName instead.
-
-       Matching is performed using the matching rules specified by
-       [RFC2459].  If more than one identity of a given type is present in
-       the certificate (e.g., more than one dNSName name, a match in any one
-       of the set is considered acceptable.) Names may contain the wildcard
-       character * which is considered to match any single domain name
-       component or component fragment. E.g., *.a.com matches foo.a.com but
-       not bar.foo.a.com. f*.com matches foo.com but not bar.com.
-
-       In some cases, the URI is specified as an IP address rather than a
-       hostname. In this case, the iPAddress subjectAltName must be present
-       in the certificate and must exactly match the IP in the URI.
-
-    */
-    return verify_host_with_subject_alt_name(server_cert) ||
-           verify_host_with_common_name(server_cert);
-}
-
-inline bool SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
-    auto ret = false;
-
-    auto type = GEN_DNS;
-
-    struct in6_addr addr6;
-    struct in_addr addr;
-    size_t addr_len = 0;
-
-#    ifndef __MINGW32__
-    if (inet_pton(AF_INET6, host_.c_str(), &addr6)) {
-        type     = GEN_IPADD;
-        addr_len = sizeof(struct in6_addr);
-    } else if (inet_pton(AF_INET, host_.c_str(), &addr)) {
-        type     = GEN_IPADD;
-        addr_len = sizeof(struct in_addr);
-    }
-#    endif
-
-    auto alt_names = static_cast<const struct stack_st_GENERAL_NAME *>(
-        X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr));
-
-    if (alt_names) {
-        auto dsn_matched = false;
-        auto ip_mached   = false;
-
-        auto count = sk_GENERAL_NAME_num(alt_names);
-
-        for (decltype(count) i = 0; i < count && !dsn_matched; i++) {
-            auto val = sk_GENERAL_NAME_value(alt_names, i);
-            if (val->type == type) {
-                auto name     = (const char *)ASN1_STRING_get0_data(val->d.ia5);
-                auto name_len = (size_t)ASN1_STRING_length(val->d.ia5);
-
-                if (strlen(name) == name_len) {
-                    switch (type) {
-                        case GEN_DNS:
-                            dsn_matched = check_host_name(name, name_len);
-                            break;
-
-                        case GEN_IPADD:
-                            if (!memcmp(&addr6, name, addr_len) || !memcmp(&addr, name, addr_len)) {
-                                ip_mached = true;
-                            }
-                            break;
-                    }
-                }
-            }
-        }
+  /* Quote from RFC2818 section 3.1 "Server Identity"
+
+     If a subjectAltName extension of type dNSName is present, that MUST
+     be used as the identity. Otherwise, the (most specific) Common Name
+     field in the Subject field of the certificate MUST be used. Although
+     the use of the Common Name is existing practice, it is deprecated and
+     Certification Authorities are encouraged to use the dNSName instead.
+
+     Matching is performed using the matching rules specified by
+     [RFC2459].  If more than one identity of a given type is present in
+     the certificate (e.g., more than one dNSName name, a match in any one
+     of the set is considered acceptable.) Names may contain the wildcard
+     character * which is considered to match any single domain name
+     component or component fragment. E.g., *.a.com matches foo.a.com but
+     not bar.foo.a.com. f*.com matches foo.com but not bar.com.
+
+     In some cases, the URI is specified as an IP address rather than a
+     hostname. In this case, the iPAddress subjectAltName must be present
+     in the certificate and must exactly match the IP in the URI.
+
+  */
+  return verify_host_with_subject_alt_name(server_cert) ||
+         verify_host_with_common_name(server_cert);
+}
+
+inline bool
+SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
+  auto ret = false;
+
+  auto type = GEN_DNS;
+
+  struct in6_addr addr6;
+  struct in_addr addr;
+  size_t addr_len = 0;
+
+#ifndef __MINGW32__
+  if (inet_pton(AF_INET6, host_.c_str(), &addr6)) {
+    type = GEN_IPADD;
+    addr_len = sizeof(struct in6_addr);
+  } else if (inet_pton(AF_INET, host_.c_str(), &addr)) {
+    type = GEN_IPADD;
+    addr_len = sizeof(struct in_addr);
+  }
+#endif
+
+  auto alt_names = static_cast<const struct stack_st_GENERAL_NAME *>(
+      X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr));
+
+  if (alt_names) {
+    auto dsn_matched = false;
+    auto ip_mached = false;
+
+    auto count = sk_GENERAL_NAME_num(alt_names);
 
-        if (dsn_matched || ip_mached) {
-            ret = true;
+    for (decltype(count) i = 0; i < count && !dsn_matched; i++) {
+      auto val = sk_GENERAL_NAME_value(alt_names, i);
+      if (val->type == type) {
+        auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5);
+        auto name_len = (size_t)ASN1_STRING_length(val->d.ia5);
+
+        switch (type) {
+        case GEN_DNS: dsn_matched = check_host_name(name, name_len); break;
+
+        case GEN_IPADD:
+          if (!memcmp(&addr6, name, addr_len) ||
+              !memcmp(&addr, name, addr_len)) {
+            ip_mached = true;
+          }
+          break;
         }
+      }
     }
 
-    GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names);
-    return ret;
+    if (dsn_matched || ip_mached) { ret = true; }
+  }
+
+  GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names);
+  return ret;
 }
 
 inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const {
-    const auto subject_name = X509_get_subject_name(server_cert);
+  const auto subject_name = X509_get_subject_name(server_cert);
 
-    if (subject_name != nullptr) {
-        char name[BUFSIZ];
-        auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, name, sizeof(name));
+  if (subject_name != nullptr) {
+    char name[BUFSIZ];
+    auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName,
+                                              name, sizeof(name));
 
-        if (name_len != -1) {
-            return check_host_name(name, static_cast<size_t>(name_len));
-        }
+    if (name_len != -1) {
+      return check_host_name(name, static_cast<size_t>(name_len));
     }
+  }
 
-    return false;
+  return false;
 }
 
-inline bool SSLClient::check_host_name(const char *pattern, size_t pattern_len) const {
-    if (host_.size() == pattern_len && host_ == pattern) {
-        return true;
-    }
+inline bool SSLClient::check_host_name(const char *pattern,
+                                       size_t pattern_len) const {
+  if (host_.size() == pattern_len && host_ == pattern) { return true; }
 
-    // Wildcard match
-    // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484
-    std::vector<std::string> pattern_components;
-    detail::split(&pattern[0], &pattern[pattern_len], '.', [&](const char *b, const char *e) {
-        pattern_components.emplace_back(std::string(b, e));
-    });
+  // Wildcard match
+  // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484
+  std::vector<std::string> pattern_components;
+  detail::split(&pattern[0], &pattern[pattern_len], '.',
+                [&](const char *b, const char *e) {
+                  pattern_components.emplace_back(std::string(b, e));
+                });
 
-    if (host_components_.size() != pattern_components.size()) {
-        return false;
-    }
+  if (host_components_.size() != pattern_components.size()) { return false; }
 
-    auto itr = pattern_components.begin();
-    for (const auto &h : host_components_) {
-        auto &p = *itr;
-        if (p != h && p != "*") {
-            auto partial_match =
-                (p.size() > 0 && p[p.size() - 1] == '*' && !p.compare(0, p.size() - 1, h));
-            if (!partial_match) {
-                return false;
-            }
-        }
-        ++itr;
+  auto itr = pattern_components.begin();
+  for (const auto &h : host_components_) {
+    auto &p = *itr;
+    if (p != h && p != "*") {
+      auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' &&
+                            !p.compare(0, p.size() - 1, h));
+      if (!partial_match) { return false; }
     }
+    ++itr;
+  }
 
-    return true;
+  return true;
 }
 #endif
 
 // Universal client implementation
-inline Client::Client(const char *scheme_host_port)
+inline Client::Client(const std::string &scheme_host_port)
     : Client(scheme_host_port, std::string(), std::string()) {}
 
-inline Client::Client(const char *scheme_host_port,
+inline Client::Client(const std::string &scheme_host_port,
                       const std::string &client_cert_path,
                       const std::string &client_key_path) {
-    const static std::regex re(R"(^(?:([a-z]+)://)?([^:/?#]+)(?::(\d+))?)");
+  const static std::regex re(
+      R"((?:([a-z]+):\/\/)?(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)");
 
-    std::cmatch m;
-    if (std::regex_match(scheme_host_port, m, re)) {
-        auto scheme = m[1].str();
+  std::smatch m;
+  if (std::regex_match(scheme_host_port, m, re)) {
+    auto scheme = m[1].str();
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-        if (!scheme.empty() && (scheme != "http" && scheme != "https")) {
+    if (!scheme.empty() && (scheme != "http" && scheme != "https")) {
 #else
-        if (!scheme.empty() && scheme != "http") {
+    if (!scheme.empty() && scheme != "http") {
 #endif
-            return;
-        }
+#ifndef CPPHTTPLIB_NO_EXCEPTIONS
+      std::string msg = "'" + scheme + "' scheme is not supported.";
+      throw std::invalid_argument(msg);
+#endif
+      return;
+    }
 
-        auto is_ssl = scheme == "https";
+    auto is_ssl = scheme == "https";
 
-        auto host = m[2].str();
+    auto host = m[2].str();
+    if (host.empty()) { host = m[3].str(); }
 
-        auto port_str = m[3].str();
-        auto port     = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80);
+    auto port_str = m[4].str();
+    auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80);
 
-        if (is_ssl) {
+    if (is_ssl) {
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-            cli_ =
-                std::make_shared<SSLClient>(host.c_str(), port, client_cert_path, client_key_path);
-            is_ssl_ = is_ssl;
+      cli_ = detail::make_unique<SSLClient>(host.c_str(), port,
+                                            client_cert_path, client_key_path);
+      is_ssl_ = is_ssl;
 #endif
-        } else {
-            cli_ =
-                std::make_shared<ClientImpl>(host.c_str(), port, client_cert_path, client_key_path);
-        }
     } else {
-        cli_ =
-            std::make_shared<ClientImpl>(scheme_host_port, 80, client_cert_path, client_key_path);
+      cli_ = detail::make_unique<ClientImpl>(host.c_str(), port,
+                                             client_cert_path, client_key_path);
     }
+  } else {
+    cli_ = detail::make_unique<ClientImpl>(scheme_host_port, 80,
+                                           client_cert_path, client_key_path);
+  }
 }
 
 inline Client::Client(const std::string &host, int port)
-    : cli_(std::make_shared<ClientImpl>(host, port)) {}
+    : cli_(detail::make_unique<ClientImpl>(host, port)) {}
 
-inline Client::Client(const std::string &host,
-                      int port,
+inline Client::Client(const std::string &host, int port,
                       const std::string &client_cert_path,
                       const std::string &client_key_path)
-    : cli_(std::make_shared<ClientImpl>(host, port, client_cert_path, client_key_path)) {}
+    : cli_(detail::make_unique<ClientImpl>(host, port, client_cert_path,
+                                           client_key_path)) {}
 
 inline Client::~Client() {}
 
 inline bool Client::is_valid() const {
-    return cli_ != nullptr && cli_->is_valid();
-}
-
-inline Result Client::Get2(std::string_view path, ClientOptions options) {
-    return cli_->Get2(path, options);
+  return cli_ != nullptr && cli_->is_valid();
 }
 
-inline Result Client::Get(const char *path) {
-    return cli_->Get(path);
-}
+inline Result Client::Get(const char *path) { return cli_->Get(path); }
 inline Result Client::Get(const char *path, const Headers &headers) {
-    return cli_->Get(path, headers);
+  return cli_->Get(path, headers);
 }
 inline Result Client::Get(const char *path, Progress progress) {
-    return cli_->Get(path, progress);
+  return cli_->Get(path, std::move(progress));
 }
-inline Result Client::Get(const char *path, const Headers &headers, Progress progress) {
-    return cli_->Get(path, headers, progress);
+inline Result Client::Get(const char *path, const Headers &headers,
+                          Progress progress) {
+  return cli_->Get(path, headers, std::move(progress));
 }
 inline Result Client::Get(const char *path, ContentReceiver content_receiver) {
-    return cli_->Get(path, std::move(content_receiver));
+  return cli_->Get(path, std::move(content_receiver));
 }
-inline Result Client::Get(const char *path,
-                          const Headers &headers,
+inline Result Client::Get(const char *path, const Headers &headers,
                           ContentReceiver content_receiver) {
-    return cli_->Get(path, headers, std::move(content_receiver));
+  return cli_->Get(path, headers, std::move(content_receiver));
 }
-inline Result Client::Get(const char *path, ContentReceiver content_receiver, Progress progress) {
-    return cli_->Get(path, std::move(content_receiver), std::move(progress));
-}
-inline Result Client::Get(const char *path,
-                          const Headers &headers,
-                          ContentReceiver content_receiver,
+inline Result Client::Get(const char *path, ContentReceiver content_receiver,
                           Progress progress) {
-    return cli_->Get(path, headers, std::move(content_receiver), std::move(progress));
+  return cli_->Get(path, std::move(content_receiver), std::move(progress));
 }
-inline Result Client::Get(const char *path,
-                          ResponseHandler response_handler,
+inline Result Client::Get(const char *path, const Headers &headers,
+                          ContentReceiver content_receiver, Progress progress) {
+  return cli_->Get(path, headers, std::move(content_receiver),
+                   std::move(progress));
+}
+inline Result Client::Get(const char *path, ResponseHandler response_handler,
                           ContentReceiver content_receiver) {
-    return cli_->Get(path, std::move(response_handler), std::move(content_receiver));
+  return cli_->Get(path, std::move(response_handler),
+                   std::move(content_receiver));
 }
-inline Result Client::Get(const char *path,
-                          const Headers &headers,
+inline Result Client::Get(const char *path, const Headers &headers,
                           ResponseHandler response_handler,
                           ContentReceiver content_receiver) {
-    return cli_->Get(path, headers, std::move(response_handler), std::move(content_receiver));
+  return cli_->Get(path, headers, std::move(response_handler),
+                   std::move(content_receiver));
+}
+inline Result Client::Get(const char *path, ResponseHandler response_handler,
+                          ContentReceiver content_receiver, Progress progress) {
+  return cli_->Get(path, std::move(response_handler),
+                   std::move(content_receiver), std::move(progress));
 }
-inline Result Client::Get(const char *path,
+inline Result Client::Get(const char *path, const Headers &headers,
                           ResponseHandler response_handler,
-                          ContentReceiver content_receiver,
-                          Progress progress) {
-    return cli_->Get(path, std::move(response_handler), std::move(content_receiver),
-                     std::move(progress));
+                          ContentReceiver content_receiver, Progress progress) {
+  return cli_->Get(path, headers, std::move(response_handler),
+                   std::move(content_receiver), std::move(progress));
+}
+inline Result Client::Get(const char *path, const Params &params,
+                          const Headers &headers, Progress progress) {
+  return cli_->Get(path, params, headers, progress);
 }
-inline Result Client::Get(const char *path,
+inline Result Client::Get(const char *path, const Params &params,
+                          const Headers &headers,
+                          ContentReceiver content_receiver, Progress progress) {
+  return cli_->Get(path, params, headers, content_receiver, progress);
+}
+inline Result Client::Get(const char *path, const Params &params,
                           const Headers &headers,
                           ResponseHandler response_handler,
-                          ContentReceiver content_receiver,
-                          Progress progress) {
-    return cli_->Get(path, headers, response_handler, content_receiver, progress);
+                          ContentReceiver content_receiver, Progress progress) {
+  return cli_->Get(path, params, headers, response_handler, content_receiver,
+                   progress);
 }
 
-inline Result Client::Head(const char *path) {
-    return cli_->Head(path);
-}
+inline Result Client::Head(const char *path) { return cli_->Head(path); }
 inline Result Client::Head(const char *path, const Headers &headers) {
-    return cli_->Head(path, headers);
+  return cli_->Head(path, headers);
 }
 
-inline Result Client::Post(const char *path) {
-    return cli_->Post(path);
+inline Result Client::Post(const char *path) { return cli_->Post(path); }
+inline Result Client::Post(const char *path, const char *body,
+                           size_t content_length, const char *content_type) {
+  return cli_->Post(path, body, content_length, content_type);
 }
-inline Result Client::Post(const char *path, const std::string &body, const char *content_type) {
-    return cli_->Post(path, body, content_type);
+inline Result Client::Post(const char *path, const Headers &headers,
+                           const char *body, size_t content_length,
+                           const char *content_type) {
+  return cli_->Post(path, headers, body, content_length, content_type);
 }
-inline Result Client::Post(const char *path,
-                           const Headers &headers,
-                           const std::string &body,
+inline Result Client::Post(const char *path, const std::string &body,
                            const char *content_type) {
-    return cli_->Post(path, headers, body, content_type);
+  return cli_->Post(path, body, content_type);
 }
-inline Result Client::Post(const char *path,
-                           size_t content_length,
+inline Result Client::Post(const char *path, const Headers &headers,
+                           const std::string &body, const char *content_type) {
+  return cli_->Post(path, headers, body, content_type);
+}
+inline Result Client::Post(const char *path, size_t content_length,
                            ContentProvider content_provider,
                            const char *content_type) {
-    return cli_->Post(path, content_length, content_provider, content_type);
+  return cli_->Post(path, content_length, std::move(content_provider),
+                    content_type);
 }
 inline Result Client::Post(const char *path,
-                           const Headers &headers,
+                           ContentProviderWithoutLength content_provider,
+                           const char *content_type) {
+  return cli_->Post(path, std::move(content_provider), content_type);
+}
+inline Result Client::Post(const char *path, const Headers &headers,
                            size_t content_length,
                            ContentProvider content_provider,
                            const char *content_type) {
-    return cli_->Post(path, headers, content_length, content_provider, content_type);
+  return cli_->Post(path, headers, content_length, std::move(content_provider),
+                    content_type);
 }
-inline Result Client::Post(const char *path, const Params &params) {
-    return cli_->Post(path, params);
+inline Result Client::Post(const char *path, const Headers &headers,
+                           ContentProviderWithoutLength content_provider,
+                           const char *content_type) {
+  return cli_->Post(path, headers, std::move(content_provider), content_type);
 }
-inline Result Client::Post(const char *path, const Headers &headers, const Params &params) {
-    return cli_->Post(path, headers, params);
+inline Result Client::Post(const char *path, const Params &params) {
+  return cli_->Post(path, params);
 }
-inline Result Client::Post(const char *path, const MultipartFormDataItems &items) {
-    return cli_->Post(path, items);
+inline Result Client::Post(const char *path, const Headers &headers,
+                           const Params &params) {
+  return cli_->Post(path, headers, params);
 }
 inline Result Client::Post(const char *path,
-                           const Headers &headers,
                            const MultipartFormDataItems &items) {
-    return cli_->Post(path, headers, items);
+  return cli_->Post(path, items);
 }
-inline Result Client::Put(const char *path) {
-    return cli_->Put(path);
+inline Result Client::Post(const char *path, const Headers &headers,
+                           const MultipartFormDataItems &items) {
+  return cli_->Post(path, headers, items);
 }
-inline Result Client::Put(const char *path, const std::string &body, const char *content_type) {
-    return cli_->Put(path, body, content_type);
+inline Result Client::Post(const char *path, const Headers &headers,
+                           const MultipartFormDataItems &items,
+                           const std::string &boundary) {
+  return cli_->Post(path, headers, items, boundary);
 }
-inline Result Client::Put(const char *path,
-                          const Headers &headers,
-                          const std::string &body,
+inline Result Client::Put(const char *path) { return cli_->Put(path); }
+inline Result Client::Put(const char *path, const char *body,
+                          size_t content_length, const char *content_type) {
+  return cli_->Put(path, body, content_length, content_type);
+}
+inline Result Client::Put(const char *path, const Headers &headers,
+                          const char *body, size_t content_length,
                           const char *content_type) {
-    return cli_->Put(path, headers, body, content_type);
+  return cli_->Put(path, headers, body, content_length, content_type);
 }
-inline Result Client::Put(const char *path,
-                          size_t content_length,
+inline Result Client::Put(const char *path, const std::string &body,
+                          const char *content_type) {
+  return cli_->Put(path, body, content_type);
+}
+inline Result Client::Put(const char *path, const Headers &headers,
+                          const std::string &body, const char *content_type) {
+  return cli_->Put(path, headers, body, content_type);
+}
+inline Result Client::Put(const char *path, size_t content_length,
                           ContentProvider content_provider,
                           const char *content_type) {
-    return cli_->Put(path, content_length, content_provider, content_type);
+  return cli_->Put(path, content_length, std::move(content_provider),
+                   content_type);
 }
 inline Result Client::Put(const char *path,
-                          const Headers &headers,
+                          ContentProviderWithoutLength content_provider,
+                          const char *content_type) {
+  return cli_->Put(path, std::move(content_provider), content_type);
+}
+inline Result Client::Put(const char *path, const Headers &headers,
                           size_t content_length,
                           ContentProvider content_provider,
                           const char *content_type) {
-    return cli_->Put(path, headers, content_length, content_provider, content_type);
+  return cli_->Put(path, headers, content_length, std::move(content_provider),
+                   content_type);
+}
+inline Result Client::Put(const char *path, const Headers &headers,
+                          ContentProviderWithoutLength content_provider,
+                          const char *content_type) {
+  return cli_->Put(path, headers, std::move(content_provider), content_type);
 }
 inline Result Client::Put(const char *path, const Params &params) {
-    return cli_->Put(path, params);
+  return cli_->Put(path, params);
 }
-inline Result Client::Put(const char *path, const Headers &headers, const Params &params) {
-    return cli_->Put(path, headers, params);
+inline Result Client::Put(const char *path, const Headers &headers,
+                          const Params &params) {
+  return cli_->Put(path, headers, params);
 }
-inline Result Client::Patch(const char *path, const std::string &body, const char *content_type) {
-    return cli_->Patch(path, body, content_type);
+inline Result Client::Patch(const char *path) { return cli_->Patch(path); }
+inline Result Client::Patch(const char *path, const char *body,
+                            size_t content_length, const char *content_type) {
+  return cli_->Patch(path, body, content_length, content_type);
 }
-inline Result Client::Patch(const char *path,
-                            const Headers &headers,
-                            const std::string &body,
+inline Result Client::Patch(const char *path, const Headers &headers,
+                            const char *body, size_t content_length,
                             const char *content_type) {
-    return cli_->Patch(path, headers, body, content_type);
+  return cli_->Patch(path, headers, body, content_length, content_type);
 }
-inline Result Client::Patch(const char *path,
-                            size_t content_length,
+inline Result Client::Patch(const char *path, const std::string &body,
+                            const char *content_type) {
+  return cli_->Patch(path, body, content_type);
+}
+inline Result Client::Patch(const char *path, const Headers &headers,
+                            const std::string &body, const char *content_type) {
+  return cli_->Patch(path, headers, body, content_type);
+}
+inline Result Client::Patch(const char *path, size_t content_length,
                             ContentProvider content_provider,
                             const char *content_type) {
-    return cli_->Patch(path, content_length, content_provider, content_type);
+  return cli_->Patch(path, content_length, std::move(content_provider),
+                     content_type);
 }
 inline Result Client::Patch(const char *path,
-                            const Headers &headers,
+                            ContentProviderWithoutLength content_provider,
+                            const char *content_type) {
+  return cli_->Patch(path, std::move(content_provider), content_type);
+}
+inline Result Client::Patch(const char *path, const Headers &headers,
                             size_t content_length,
                             ContentProvider content_provider,
                             const char *content_type) {
-    return cli_->Patch(path, headers, content_length, content_provider, content_type);
+  return cli_->Patch(path, headers, content_length, std::move(content_provider),
+                     content_type);
 }
-inline Result Client::Delete(const char *path) {
-    return cli_->Delete(path);
-}
-inline Result Client::Delete(const char *path, const std::string &body, const char *content_type) {
-    return cli_->Delete(path, body, content_type);
+inline Result Client::Patch(const char *path, const Headers &headers,
+                            ContentProviderWithoutLength content_provider,
+                            const char *content_type) {
+  return cli_->Patch(path, headers, std::move(content_provider), content_type);
 }
+inline Result Client::Delete(const char *path) { return cli_->Delete(path); }
 inline Result Client::Delete(const char *path, const Headers &headers) {
-    return cli_->Delete(path, headers);
+  return cli_->Delete(path, headers);
 }
-inline Result Client::Delete(const char *path,
-                             const Headers &headers,
-                             const std::string &body,
+inline Result Client::Delete(const char *path, const char *body,
+                             size_t content_length, const char *content_type) {
+  return cli_->Delete(path, body, content_length, content_type);
+}
+inline Result Client::Delete(const char *path, const Headers &headers,
+                             const char *body, size_t content_length,
                              const char *content_type) {
-    return cli_->Delete(path, headers, body, content_type);
+  return cli_->Delete(path, headers, body, content_length, content_type);
 }
-inline Result Client::Options(const char *path) {
-    return cli_->Options(path);
+inline Result Client::Delete(const char *path, const std::string &body,
+                             const char *content_type) {
+  return cli_->Delete(path, body, content_type);
+}
+inline Result Client::Delete(const char *path, const Headers &headers,
+                             const std::string &body,
+                             const char *content_type) {
+  return cli_->Delete(path, headers, body, content_type);
 }
+inline Result Client::Options(const char *path) { return cli_->Options(path); }
 inline Result Client::Options(const char *path, const Headers &headers) {
-    return cli_->Options(path, headers);
+  return cli_->Options(path, headers);
 }
 
-inline bool Client::send(const Request &req, Response &res) {
-    return cli_->send(req, res);
+inline bool Client::send(Request &req, Response &res, Error &error) {
+  return cli_->send(req, res, error);
 }
 
-inline size_t Client::is_socket_open() const {
-    return cli_->is_socket_open();
-}
+inline Result Client::send(const Request &req) { return cli_->send(req); }
+
+inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); }
 
-inline void Client::stop() {
-    cli_->stop();
+inline void Client::stop() { cli_->stop(); }
+
+inline void
+Client::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
+  cli_->set_hostname_addr_map(std::move(addr_map));
 }
 
 inline void Client::set_default_headers(Headers headers) {
-    cli_->set_default_headers(std::move(headers));
+  cli_->set_default_headers(std::move(headers));
 }
 
-inline void Client::set_tcp_nodelay(bool on) {
-    cli_->set_tcp_nodelay(on);
+inline void Client::set_address_family(int family) {
+  cli_->set_address_family(family);
 }
+
+inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); }
+
 inline void Client::set_socket_options(SocketOptions socket_options) {
-    cli_->set_socket_options(socket_options);
+  cli_->set_socket_options(std::move(socket_options));
 }
 
 inline void Client::set_connection_timeout(time_t sec, time_t usec) {
-    cli_->set_connection_timeout(sec, usec);
+  cli_->set_connection_timeout(sec, usec);
 }
+
 inline void Client::set_read_timeout(time_t sec, time_t usec) {
-    cli_->set_read_timeout(sec, usec);
+  cli_->set_read_timeout(sec, usec);
 }
+
 inline void Client::set_write_timeout(time_t sec, time_t usec) {
-    cli_->set_write_timeout(sec, usec);
+  cli_->set_write_timeout(sec, usec);
 }
 
 inline void Client::set_basic_auth(const char *username, const char *password) {
-    cli_->set_basic_auth(username, password);
+  cli_->set_basic_auth(username, password);
 }
 inline void Client::set_bearer_token_auth(const char *token) {
-    cli_->set_bearer_token_auth(token);
+  cli_->set_bearer_token_auth(token);
 }
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-inline void Client::set_digest_auth(const char *username, const char *password) {
-    cli_->set_digest_auth(username, password);
+inline void Client::set_digest_auth(const char *username,
+                                    const char *password) {
+  cli_->set_digest_auth(username, password);
 }
 #endif
 
-inline void Client::set_keep_alive(bool on) {
-    cli_->set_keep_alive(on);
-}
+inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); }
 inline void Client::set_follow_location(bool on) {
-    cli_->set_follow_location(on);
+  cli_->set_follow_location(on);
 }
 
-inline void Client::set_compress(bool on) {
-    cli_->set_compress(on);
-}
+inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); }
 
-inline void Client::set_decompress(bool on) {
-    cli_->set_decompress(on);
-}
+inline void Client::set_compress(bool on) { cli_->set_compress(on); }
+
+inline void Client::set_decompress(bool on) { cli_->set_decompress(on); }
 
 inline void Client::set_interface(const char *intf) {
-    cli_->set_interface(intf);
+  cli_->set_interface(intf);
 }
 
 inline void Client::set_proxy(const char *host, int port) {
-    cli_->set_proxy(host, port);
+  cli_->set_proxy(host, port);
 }
-inline void Client::set_proxy_basic_auth(const char *username, const char *password) {
-    cli_->set_proxy_basic_auth(username, password);
+inline void Client::set_proxy_basic_auth(const char *username,
+                                         const char *password) {
+  cli_->set_proxy_basic_auth(username, password);
 }
 inline void Client::set_proxy_bearer_token_auth(const char *token) {
-    cli_->set_proxy_bearer_token_auth(token);
+  cli_->set_proxy_bearer_token_auth(token);
 }
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-inline void Client::set_proxy_digest_auth(const char *username, const char *password) {
-    cli_->set_proxy_digest_auth(username, password);
+inline void Client::set_proxy_digest_auth(const char *username,
+                                          const char *password) {
+  cli_->set_proxy_digest_auth(username, password);
 }
 #endif
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
 inline void Client::enable_server_certificate_verification(bool enabled) {
-    cli_->enable_server_certificate_verification(enabled);
+  cli_->enable_server_certificate_verification(enabled);
 }
 #endif
 
-inline void Client::set_logger(Logger logger) {
-    cli_->set_logger(logger);
-}
+inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); }
 
 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
-inline void Client::set_ca_cert_path(const char *ca_cert_file_path, const char *ca_cert_dir_path) {
-    if (is_ssl_) {
-        static_cast<SSLClient &>(*cli_).set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path);
-    }
+inline void Client::set_ca_cert_path(const char *ca_cert_file_path,
+                                     const char *ca_cert_dir_path) {
+  cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path);
 }
 
 inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) {
-    if (is_ssl_) {
-        static_cast<SSLClient &>(*cli_).set_ca_cert_store(ca_cert_store);
-    }
+  if (is_ssl_) {
+    static_cast<SSLClient &>(*cli_).set_ca_cert_store(ca_cert_store);
+  } else {
+    cli_->set_ca_cert_store(ca_cert_store);
+  }
 }
 
 inline long Client::get_openssl_verify_result() const {
-    if (is_ssl_) {
-        return static_cast<SSLClient &>(*cli_).get_openssl_verify_result();
-    }
-    return -1;  // NOTE: -1 doesn't match any of X509_V_ERR_???
+  if (is_ssl_) {
+    return static_cast<SSLClient &>(*cli_).get_openssl_verify_result();
+  }
+  return -1; // NOTE: -1 doesn't match any of X509_V_ERR_???
 }
 
 inline SSL_CTX *Client::ssl_context() const {
-    if (is_ssl_) {
-        return static_cast<SSLClient &>(*cli_).ssl_context();
-    }
-    return nullptr;
+  if (is_ssl_) { return static_cast<SSLClient &>(*cli_).ssl_context(); }
+  return nullptr;
 }
 #endif
 
 // ----------------------------------------------------------------------------
 
-}  // namespace httplib
+} // namespace httplib
 
-#endif  // CPPHTTPLIB_HTTPLIB_H
+#endif // CPPHTTPLIB_HTTPLIB_H
diff --git a/LiteLoader/Kernel/Command/DynamicCommandAPI.cpp b/LiteLoader/Kernel/Command/DynamicCommandAPI.cpp
--- a/LiteLoader/Kernel/Command/DynamicCommandAPI.cpp
+++ b/LiteLoader/Kernel/Command/DynamicCommandAPI.cpp
@@ -479,11 +479,11 @@ DynamicCommandInstance* DynamicCommand::_setup(std::unique_ptr<class DynamicComm
     try
     {
         if (!commandInstance)
-            return nullptr;
+            throw std::runtime_error("Command instance is null");
         if (!commandInstance->callback)
-            return nullptr;
+            throw std::runtime_error("Can't setup command without callback");
         if (commandInstance->overloads.empty())
-            return nullptr;
+            throw std::runtime_error("Can't setup command without overloads");
         // commandInstance->updateSoftEnum();
 
         for (auto& param : commandInstance->parameterDatas)
@@ -496,7 +496,7 @@ DynamicCommandInstance* DynamicCommand::_setup(std::unique_ptr<class DynamicComm
                     auto namesInBds = CommandRegistry::getEnumNames();
                     auto iter = std::find(namesInBds.begin(), namesInBds.end(), param.description);
                     if (iter == namesInBds.end())
-                        throw("Enum " + std::string(param.description) + "not found in command and BDS");
+                        throw std::runtime_error("Enum " + std::string(param.description) + "not found in command and BDS");
 #ifndef USE_PARSE_ENUM_STRING_ // fix Enum
                     commandInstance->setEnum(*iter, CommandRegistry::getEnumValues(*iter));
 #endif // USE_PARSE_ENUM_STRING
@@ -593,10 +593,10 @@ bool DynamicCommand::onServerCommandsRegister(CommandRegistry& registry)
         try
         {
             if (!LL::getPlugin(handler) && handler != GetCurrentModule())
-                throw "Plugin that registered command \"" + name + "\" not found";
+                throw std::runtime_error("Plugin that registered command \"" + name + "\" not found");
             auto res = DynamicCommand::_setup(std::move(command));
             if (!res)
-                throw "Command \"" + name + "\" setup failed";
+                throw std::runtime_error("Command \"" + name + "\" setup failed");
         }
         CatchDynamicCommandError("DynamicCommand::_setup - " + name, handler);
     };
@@ -667,6 +667,8 @@ std::unique_ptr<class DynamicCommandInstance> DynamicCommand::createCommand(std:
 DynamicCommandInstance const* DynamicCommand::setup(std::unique_ptr<class DynamicCommandInstance> commandInstance)
 {
     auto ptr = commandInstance.get();
+    if (!ptr)
+        throw std::runtime_error("DynamicCommand::setup - commandInstance is null");
     if (!serverCommandsRegistered)
     {
         delaySetupLock.lock();
@@ -676,7 +678,8 @@ DynamicCommandInstance const* DynamicCommand::setup(std::unique_ptr<class Dynami
     }
     //logger.warn("Registering command \"{}\" after RegCmdEvent, note that this is unstable!", commandInstance->getCommandName());
     Schedule::nextTick([instance{commandInstance.release()}]() {
-        DynamicCommand::_setup(std::unique_ptr<class DynamicCommandInstance>(instance));
+        if (!_setup(std::unique_ptr<class DynamicCommandInstance>(instance)))
+            logger.warn("Registering command \"{}\" failed", instance->getCommandName());
         updateAvailableCommands();
         });
     return ptr;
diff --git a/LiteLoader/Kernel/DB/RowSet.cpp b/LiteLoader/Kernel/DB/RowSet.cpp
--- a/LiteLoader/Kernel/DB/RowSet.cpp
+++ b/LiteLoader/Kernel/DB/RowSet.cpp
@@ -48,7 +48,7 @@ void RowSet::add(const Row& row)
         if (!header->check(row))
             throw std::runtime_error("RowSet::add: Row doesn't match header");
     }
-    else if (row.header && !header->empty())
+    else if (row.header && !row.header->empty())
     {
         header = row.header;
     }
diff --git a/LiteLoader/Kernel/EventAPI.cpp b/LiteLoader/Kernel/EventAPI.cpp
--- a/LiteLoader/Kernel/EventAPI.cpp
+++ b/LiteLoader/Kernel/EventAPI.cpp
@@ -367,7 +367,7 @@ THook(void, "?disconnect@ServerPlayer@@QEAAXXZ",
     {
         PlayerLeftEvent ev{};
         ev.mPlayer = sp;
-        ev.mXUID = "";
+        ev.mXUID = sp->getXuid();
         ev.call();
     }
     IF_LISTENED_END(PlayerLeftEvent)
diff --git a/LiteLoader/Kernel/MC/ActorAPI.cpp b/LiteLoader/Kernel/MC/ActorAPI.cpp
--- a/LiteLoader/Kernel/MC/ActorAPI.cpp
+++ b/LiteLoader/Kernel/MC/ActorAPI.cpp
@@ -106,6 +106,8 @@ ActorUniqueID Actor::getActorUniqueId() const {
 static_assert(sizeof(TeleportRotationData) == 32);
 bool Actor::teleport(Vec3 to, int dimID)
 {
+    if (!this->isAlive())
+        return false;
     char mem[48];
     auto computeTarget = (TeleportTarget * (*)(void*, class Actor&, class Vec3, class Vec3*, class AutomaticID<class Dimension, int>, std::optional<TeleportRotationData> const&, int))(&TeleportCommand::computeTarget);   
     auto target = computeTarget(mem, *this, to, nullptr, dimID, TeleportRotationData{getRotation().x, getRotation().y, {}}, 15);
diff --git a/LiteLoader/Kernel/MC/BiomeAPI.cpp b/LiteLoader/Kernel/MC/BiomeAPI.cpp
new file mode 100644
--- /dev/null
+++ b/LiteLoader/Kernel/MC/BiomeAPI.cpp
@@ -0,0 +1,68 @@
+#include <MC/Biome.hpp>
+#include <MC/BiomeRegistry.hpp>
+#include <MC/VanillaBiomes.hpp>
+#include <MC/Level.hpp>
+
+int Biome::getId() const
+{
+    return dAccess<int, 120>(this);
+}
+
+std::string const& Biome::getName() const
+{
+    return dAccess<std::string, 8>(this);
+}
+
+Biome* Biome::fromId(int id)
+{
+    auto& reg = Global<Level>->getBiomeRegistry();
+    return reg.lookupById(id);
+}
+
+Biome* Biome::fromName(std::string const& name)
+{
+    auto& reg = Global<Level>->getBiomeRegistry();
+    return reg.lookupByName(name);
+}
+
+std::vector<Biome*> Biome::getBiomesByType(VanillaBiomeTypes type)
+{
+    std::vector<Biome*> result;
+    auto& reg = Global<Level>->getBiomeRegistry();
+    reg.forEachBiome([&](Biome& biome) {
+        if (biome.getBiomeType() == type)
+            result.push_back(&biome);
+    });
+    return result;
+}
+
+#ifdef DEBUG
+
+#include <LoggerAPI.h>
+#include <MC/Minecraft.hpp>
+extern Logger logger;
+
+TClasslessInstanceHook2("startServerThread_TestBiome", void, "?startServerThread@ServerInstance@@QEAAXXZ")
+{
+    Global<Level> = Global<Minecraft>->getLevel();
+    auto& reg = Global<Level>->getBiomeRegistry();
+    assert(reg.isRegistrationFinished());
+    reg.forEachBiome([](class Biome& bio) {
+        logger.warn("id: {}, name: {}, typeId: {}", bio.getId(), bio.getName(), (int)bio.getBiomeType());
+        assert(&bio == Biome::fromId(bio.getId()));
+        assert(&bio == Biome::fromName(bio.getName()));
+    });
+    for (size_t i = 0; i < 20; i++)
+    {
+        auto biomes = Biome::getBiomesByType((VanillaBiomeTypes)i);
+        std::string biomesInfo = "";
+        for (auto biome : biomes)
+            biomesInfo += biome->getName() + ", ";
+        logger.warn("type: {}, biomes: {}", i, biomesInfo);
+    }
+    std::vector<Biome*> bimes = dAccess<std::vector<Biome*>>(&reg, 126 * 8);
+    __debugbreak();
+    return original(this);
+}
+
+#endif // DEBUG
diff --git a/LiteLoader/Kernel/MC/PlayerAPI.cpp b/LiteLoader/Kernel/MC/PlayerAPI.cpp
--- a/LiteLoader/Kernel/MC/PlayerAPI.cpp
+++ b/LiteLoader/Kernel/MC/PlayerAPI.cpp
@@ -12,6 +12,7 @@
 #include <MC/ServerNetworkHandler.hpp>
 #include <MC/NetworkIdentifier.hpp>
 #include <MC/NetworkPeer.hpp>
+#include <MC/ToastRequestPacket.hpp>
 
 #include <MC/ExtendedCertificate.hpp>
 #include <MC/ConnectionRequest.hpp>
@@ -30,6 +31,7 @@
 #include <MC/PlaySoundPacket.hpp>
 #include <MC/SetDisplayObjectivePacket.hpp>
 #include <MC/Block.hpp>
+#include <MC/AttributeInstance.hpp>
 
 #include <Impl/ObjectivePacketHelper.h>
 #include <Impl/FormPacketHelper.h>
@@ -37,6 +39,7 @@
 #include <bitset>
 #include <MC/ItemStackDescriptor.hpp>
 #include <MC/NetworkItemStackDescriptor.hpp>
+#include <MC/ToastRequestPacket.hpp>
 
 extern Logger logger;
 
@@ -351,6 +354,90 @@ bool Player::isOP()
     return isOperator();
 }
 
+int Player::getCurrentExperience() {
+    auto& attr = getAttribute(Player::EXPERIENCE);
+    float currentExp = getXpNeededForNextLevel() * attr.getCurrentValue();
+    return static_cast<int>(currentExp);
+}
+
+bool Player::setCurrentExperience(int exp) {
+    auto attr = getMutableAttribute(Player::EXPERIENCE);
+    if (!attr) return false;
+    int levelExp = getXpNeededForNextLevel();
+    attr->setCurrentValue(static_cast<float>(exp) / levelExp);
+    return true;
+}
+
+size_t Player::getTotalExperience() {
+    int level = getPlayerLevel();
+    size_t exp = getTotalXpNeededForLevel(level);
+    exp += getCurrentExperience();
+    return exp;
+}
+
+bool Player::setTotalExperience(size_t exp) {
+    resetPlayerLevel();
+    if (exp <= INT32_MAX) {
+        addExperience(static_cast<int>(exp));
+        return true;
+    } else {
+        addExperience(INT32_MAX);
+        size_t newExp = exp - INT32_MAX;
+        if (exp <= INT32_MAX) {
+            addExperience(static_cast<int>(exp));
+        } else {
+            // reach max level
+            addExperience(INT32_MAX); 
+        }
+        return true;
+    }
+}
+
+bool Player::reduceExperience(size_t exp) {
+    auto attr = getMutableAttribute(Player::EXPERIENCE);
+    if (!attr) return false;
+    int neededExp = getXpNeededForNextLevel();
+    int currExp = static_cast<int>(attr->getCurrentValue() * neededExp);
+    if (exp <= currExp) {
+        attr->setCurrentValue(static_cast<float>(currExp - exp) / neededExp);
+        return true;
+    }
+    attr->setCurrentValue(0);
+    size_t needExp = exp - currExp;
+    int level = getPlayerLevel();
+    while (level > 0) {
+        addLevels(-1);
+        int levelXp = getXpNeededForNextLevel();
+        if (needExp < levelXp) {
+            attr->setCurrentValue(static_cast<float>(levelXp - needExp) / getXpNeededForNextLevel());
+            return true;
+        }
+        needExp -= levelXp;
+        level = getPlayerLevel();
+    }
+    return false;
+}
+
+int Player::getXpNeededForLevel(int nextLevel) {
+    int const level = nextLevel - 1;
+    if (level / 15) {
+        if (level / 15 == 1)
+            return 5 * level - 38;
+        else
+            return 9 * level - 158;
+    } else {
+        return 2 * level + 7;
+    }
+}
+
+size_t Player::getTotalXpNeededForLevel(int level) {
+    size_t result = 0;
+    for (int i = 1; i <= level; i++) {
+        result += getXpNeededForLevel(i);
+    }
+    return result;
+}
+
 bool Player::crashClient() 
 {
     if (isSimulatedPlayer())
@@ -478,6 +565,11 @@ bool Player::sendTextPacket(string text, TextType Type) const
     return true;
 }
 
+bool Player::sendToastPacket(string title, string msg) {
+    sendNetworkPacket(ToastRequestPacket(title, msg));
+    return true;
+}
+
 bool Player::sendTitlePacket(string text, TitleType Type, int FadeInDuration, int RemainDuration, int FadeOutDuration) const 
 {
     BinaryStream wp;
@@ -850,4 +942,43 @@ bool Player::sendCustomFormPacket(const std::string& data, std::function<void(st
             logger.error("Player: {}", pl->getName());
         }
     });
-}
\ No newline at end of file
+}
+
+#ifdef DEBUG
+
+bool testPlayerExperience(Player& player) {
+    player.resetPlayerLevel();
+    size_t totalExp = player.getTotalExperience();
+    assert(totalExp == 0);
+    player.setTotalExperience(123456);
+    totalExp = player.getTotalExperience();
+    assert(totalExp == 123456);
+
+    assert(player.reduceExperience(12345));
+    int level = player.getPlayerLevel();
+    int exp = player.getCurrentExperience();
+    totalExp = player.getTotalExperience();
+    logger.warn("level: {} exp: {}, totalExp: {}", level, exp, totalExp);
+    assert(totalExp == 123456 - 12345);
+    player.resetPlayerLevel();
+    player.addLevels(12345);
+    level = player.getPlayerLevel();
+    exp = player.getCurrentExperience();
+    totalExp = player.getTotalExperience();
+    logger.warn("level: {} exp: {}, totalExp: {}", level, exp, totalExp);
+    player.resetPlayerLevel();
+    player.addExperience(INT32_MAX);
+    level = player.getPlayerLevel();
+    exp = player.getCurrentExperience();
+    totalExp = player.getTotalExperience();
+    logger.warn("level: {} exp: {}, totalExp: {}", level, exp, totalExp);
+    //for (int i = 0; i <= 1000; i++) {
+    //    int level = i * i;
+    //    int exp = Player::getXpNeededForLevel(level + 1);
+    //    size_t total = Player::getTotalXpNeededForLevel(level);
+    //    logger.warn("Level: {}, exp needed: {}, total exp: {}", level, exp, total);
+    //}
+    return true;
+}
+
+#endif // DEBUG
diff --git a/LiteLoader/Kernel/MC/Types.cpp b/LiteLoader/Kernel/MC/Types.cpp
--- a/LiteLoader/Kernel/MC/Types.cpp
+++ b/LiteLoader/Kernel/MC/Types.cpp
@@ -10,10 +10,8 @@
 // }
 
 extern Logger logger;
-namespace mce
-{
-std::string mce::Color::toConsoleCode(bool foreground) const
-{
+namespace mce {
+std::string mce::Color::toConsoleCode(bool foreground) const {
     fmt::v8::rgb rgb;
     rgb.r = (uint8_t)(r * 0xff);
     rgb.g = (uint8_t)(g * 0xff);
@@ -23,77 +21,210 @@ std::string mce::Color::toConsoleCode(bool foreground) const
     else
         return std::string(fmt::v8::detail::make_background_color<char>(fmt::v8::detail::color_type(rgb)));
 }
-std::string mce::Color::toNearestColorCode() const
-{
+std::string mce::Color::toNearestColorCode() const {
     return ColorFormat::nearestColorCodeFromColor(*this);
 };
-double mce::Color::distanceTo(mce::Color const dst) const
-{
-    long rmean = ((long)(r * 0xff) + (long)(dst.r * 0xff)) / 2;
-    long dr = (long)(r * 0xff) - (long)(dst.r * 0xff);
-    long dg = (long)(g * 0xff) - (long)(dst.g * 0xff);
-    long db = (long)(b * 0xff) - (long)(dst.b * 0xff);
-    return sqrt((((512 + rmean) * dr * dr) >> 8) + 4 * dg * dg + (((767 - rmean) * db * db) >> 8));
-}
-class mce::Color mce::Color::fromConsoleCode(std::string const& code)
-{
+class mce::Color mce::Color::fromConsoleCode(std::string const& code) {
     return ColorFormat::ColorFromConsoleCode(code);
 }
-mce::Color Color::fromColorCode(std::string const& code)
-{
+mce::Color Color::fromColorCode(std::string const& code) {
     return *ColorFormat::ColorFromColorCode(code);
 };
+mce::Color Color::sRGBToLinear() const {
+    float mr = r > 0.04045f ? pow(r / 1.055f + 0.055f, 2.4f) : r / 12.92f;
+    float mg = g > 0.04045f ? pow(g / 1.055f + 0.055f, 2.4f) : g / 12.92f;
+    float mb = b > 0.04045f ? pow(b / 1.055f + 0.055f, 2.4f) : b / 12.92f;
+    return Color(mr, mg, mb, a);
+};
+mce::Color Color::LinearTosRGB() const {
+    float mr = r > 0.0031308f ? 1.055f * pow(r, 1.0f / 2.4f) - 0.055f : r * 12.92f;
+    float mg = g > 0.0031308f ? 1.055f * pow(g, 1.0f / 2.4f) - 0.055f : g * 12.92f;
+    float mb = b > 0.0031308f ? 1.055f * pow(b, 1.0f / 2.4f) - 0.055f : b * 12.92f;
+    return Color(mr, mg, mb, a);
+};
+mce::Color Color::LinearToXYZ() const {
+    float mr = r * 0.4124f + g * 0.3576f + b * 0.1805f;
+    float mg = r * 0.2126f + g * 0.7152f + b * 0.0722f;
+    float mb = r * 0.0193f + g * 0.1192f + b * 0.9505f;
+    return Color(mr, mg, mb, a);
+};
+mce::Color Color::XYZToLinear() const {
+    float mr = r * 3.2410f + g * -1.5374f + b * -0.4986f;
+    float mg = r * -0.9692f + g * 1.8760f + b * 0.0416f;
+    float mb = r * 0.0556f + g * -0.2040f + b * 1.0570f;
+    return Color(mr, mg, mb, a);
+};
+mce::Color Color::XYZToLab() const {
+
+    float nx = r / 0.950489f;
+    float ny = g;
+    float nz = b / 1.08884f;
+
+    static const float delta  = 6.0f / 29.0f;
+    static const float delta2 = delta * delta;
+    static const float delta3 = delta2 * delta;
+
+    float mr = ny > delta3 ? pow(ny, 1.0f / 3.0f) : ny / (3.0f * delta2) + 4.0f / 29.0f;
+    float mg = nx > delta3 ? pow(nx, 1.0f / 3.0f) : nx / (3.0f * delta2) + 4.0f / 29.0f;
+    float mb = nz > delta3 ? pow(nz, 1.0f / 3.0f) : nz / (3.0f * delta2) + 4.0f / 29.0f;
+    return Color(116.0f * mr - 16.0f, 500.0f * (mg - mr), 200.0f * (mr - mb), a);
+};
+mce::Color Color::LabToXYZ() const {
+
+    float ny = (r + 16.0f) / 116.0f;
+    float nx = ny + g / 500.0f;
+    float nz = ny - b / 200.0f;
+
+    static const float delta  = 6.0f / 29.0f;
+    static const float delta2 = delta * delta;
+
+    float mr = nx > delta ? pow(nx, 3.0f) : (nx - 4.0f / 29.0f) * 3.0f * delta2;
+    float mg = ny > delta ? pow(ny, 3.0f) : (ny - 4.0f / 29.0f) * 3.0f * delta2;
+    float mb = nz > delta ? pow(nz, 3.0f) : (nz - 4.0f / 29.0f) * 3.0f * delta2;
+    return Color(mr * 0.950489f, mg, mb * 1.08884f, a);
+};
+double mce::Color::deltaE76(mce::Color const& dst) const {
+    auto   ma = this->sRGBToLinear().LinearToXYZ().XYZToLab();
+    auto   mb = dst.sRGBToLinear().LinearToXYZ().XYZToLab();
+    double dL = ma.r - mb.r;
+    double da = ma.g - mb.g;
+    double db = ma.b - mb.b;
+    return sqrt(dL * dL + da * da + db * db);
+}
+double mce::Color::deltaE94(mce::Color const& dst) const {
+    auto ma = this->sRGBToLinear().LinearToXYZ().XYZToLab();
+    auto mb = dst.sRGBToLinear().LinearToXYZ().XYZToLab();
+
+    double L1 = ma.r;
+    double L2 = mb.r;
+    double a1 = ma.g;
+    double a2 = mb.g;
+    double b1 = ma.b;
+    double b2 = mb.b;
+    double dL = L1 - L2;
+    double da = a1 - a2;
+    double db = b1 - b2;
+    double C1 = sqrt(a1 * a1 + b1 * b1);
+    double C2 = sqrt(a2 * a2 + b2 * b2);
+    double dC = C1 - C2;
+    double dH = sqrt(da * da + db * db - dC * dC);
+    double SL = 1.0;
+    double SC = 1.0 + 0.045 * C1;
+    double SH = 1.0 + 0.015 * C2;
+
+    dL = dL / SL;
+    dC = dC / SC;
+    dH = dH / SH;
+
+    return sqrt(dL * dL + dC * dC + dH * dH);
+}
+double mce::Color::deltaE00(mce::Color const& dst) const {
+    auto ma = this->sRGBToLinear().LinearToXYZ().XYZToLab();
+    auto mb = dst.sRGBToLinear().LinearToXYZ().XYZToLab();
+
+    double L1  = ma.r;
+    double L2  = mb.r;
+    double a1  = ma.g;
+    double a2  = mb.g;
+    double b1  = ma.b;
+    double b2  = mb.b;
+    double dL  = L1 - L2;
+    double da  = a1 - a2;
+    double db  = b1 - b2;
+    double C1  = sqrt(a1 * a1 + b1 * b1);
+    double C2  = sqrt(a2 * a2 + b2 * b2);
+    double L_  = (L1 + L2) * 0.5;
+    double C_  = (C1 + C2) * 0.5;
+    double C_7 = pow(C_, 7.0);
+    double G   = (1 - sqrt(C_7 / (C_7 + 6103515625))) * 0.5;
+    double a1p = a1 * (1 + G);
+    double a2p = a2 * (1 + G);
+    double C1p = sqrt(a1p * a1p + b1 * b1);
+    double C2p = sqrt(a2p * a2p + b2 * b2);
+    double C_p = (C1p + C2p) * 0.5;
+    double dCp = C1p - C2p;
+    double h1p = atan2(b1, a1p);
+    double h2p = atan2(b2, a2p);
+    if (h1p < 0) h1p += 6.2831853071796;
+    if (h2p < 0) h2p += 6.2831853071796;
+    h1p *= 57.2957795131;
+    h2p *= 57.2957795131;
+    double h_p = (h1p + h2p) * 0.5;
+    double dhp = 0;
+    double H_p = 0;
+        if (C1p == 0 || C2p == 0) {
+            H_p = h_p * 2;
+        } else {
+                if (180 < abs(h1p - h2p)) {
+                        if (h2p <= h1p) {
+                            dhp = h2p - h1p + 360;
+                        } else {
+                            dhp = h2p - h1p - 360;
+                        }
+                        if (h2p + h1p < 360) {
+                            H_p = h_p + 180;
+                        } else {
+                            H_p = h_p - 180;
+                        }
+                } else {
+                    dhp = h2p - h1p;
+                    H_p = h_p;
+                }
+        }
+    double T = 1 - 0.17 * cos((H_p - 30) * 0.0174532925199) + 0.24 * cos((2 * H_p) * 0.0174532925199) +
+               0.32 * cos((3 * H_p + 6) * 0.0174532925199) - 0.20 * cos((4 * H_p - 63.0) * 0.0174532925199);
+    double dHp = 2 * sqrt(C1p * C2p) * sin(dhp * 0.5 * 0.0174532925199);
+    L_ -= 50;
+    L_ *= L_;
+    double SL   = 1.0 + 0.015 * L_ / sqrt(L_ + 20);
+    double SC   = 1.0 + 0.045 * C_p;
+    double SH   = 1.0 + 0.015 * C_p * T;
+    double C_p7 = pow(C_p, 7.0);
+    double kH   = (H_p - 275) / 25;
+    double RT   = -2 * sqrt(C_p7 / (C_p7 + 6103515625)) * sin(60 * exp(-kH * kH) * 0.0174532925199);
+
+    dL  = dL / SL;
+    dCp = dCp / SC;
+    dHp = dHp / SH;
+
+    return sqrt(dL * dL + dCp * dCp + dHp * dHp + RT * dCp * dHp);
+}
+double mce::Color::distanceTo(mce::Color const& dst) const {
+    return this->deltaE00(dst);
+}
 } // namespace mce
 
-Vec3 BlockPos::toVec3() const
-{
+Vec3 BlockPos::toVec3() const {
     return {(float)x, (float)y, (float)z};
 }
 
-Vec3 BlockPos::bottomCenter() const
-{
+Vec3 BlockPos::bottomCenter() const {
     return {(float)x + 0.5f, (float)y, (float)z + 0.5f};
 }
 
-Vec3 BlockPos::center() const
-{
+Vec3 BlockPos::center() const {
     return {(float)x + 0.5f, (float)y + 0.5f, (float)z + 0.5f};
 }
 
-bool BlockPos::containedWithin(BoundingBox const& box) const
-{
-    return x >= box.min.x && y >= box.min.y && z >= box.min.z &&
-           x <= box.max.x && y <= box.max.y && z <= box.max.z;
+bool BlockPos::containedWithin(BoundingBox const& box) const {
+    return x >= box.min.x && y >= box.min.y && z >= box.min.z && x <= box.max.x && y <= box.max.y && z <= box.max.z;
 }
 
-BlockPos Vec3::toBlockPos() const
-{
-    auto px = (int)x;
-    auto py = (int)y;
-    auto pz = (int)z;
-    if (px < 0 && px != x)
-        px = px - 1;
-    if (py < 0 && py != y)
-        py = py - 1;
-    if (pz < 0 && pz != z)
-        pz = pz - 1;
-    return {px, py, pz};
+BlockPos Vec3::toBlockPos() const {
+    return BlockPos((int)std::floor((double)x), (int)std::floor((double)y), (int)std::floor((double)z));
 }
 
-float Vec3::length() const
-{
+float Vec3::length() const {
     return sqrt(x * x + y * y + z * z);
 }
 
-float Vec3::distanceTo(Vec3 const& a0) const
-{
+float Vec3::distanceTo(Vec3 const& a0) const {
     Vec3 tmp = *this - a0;
     return tmp.length();
 }
 
-AABB BoundingBox::toAABB() const
-{
+AABB BoundingBox::toAABB() const {
     Vec3 vec1 = min.toVec3();
-    Vec3 vec2 = min.toVec3();
+    Vec3 vec2 = max.toVec3();
     return {vec1, vec2 + 1.0f};
-}
\ No newline at end of file
+}
diff --git a/LiteLoader/Kernel/ScheduleAPI.cpp b/LiteLoader/Kernel/ScheduleAPI.cpp
--- a/LiteLoader/Kernel/ScheduleAPI.cpp
+++ b/LiteLoader/Kernel/ScheduleAPI.cpp
@@ -34,7 +34,7 @@ class ScheduleTaskData {
         , interval((long long)interval)
         , count(count)
         , taskId(++nextTaskId)
-        , handler(handler){};
+        , handler(handler) {};
 
     inline unsigned int getTaskId() {
         return taskId;
@@ -175,6 +175,13 @@ class ScheduleTaskQueueType
         }
 
     }
+
+    bool has(unsigned int taskId) {
+        for (auto& task : c)
+            if (task.taskId == taskId)
+                return true;
+        return false;
+    }
 };
 
 ScheduleTaskQueueType taskQueue;
@@ -254,4 +261,30 @@ bool ScheduleTask::cancel() {
     pendingCancelList.push_back(taskId);
     locker.unlock();
     return true;
+}
+
+bool ScheduleTask::isFinished() const {
+    locker.lock();
+    if (pendingClear) {
+        locker.unlock();
+        return true;
+    }
+    for (auto tid : pendingCancelList) { // 准备出队,但还在队里
+        if (tid == taskId) {
+            locker.unlock();
+            return true;
+        }
+    }
+    for (auto& t : pendingTaskList) { // 还没进队,不在队里
+        if (t.taskId == taskId) {
+            locker.unlock();
+            return false;
+        }
+    }
+    if (taskQueue.has(taskId)) { // 还在队里
+        locker.unlock();
+        return false;
+    }
+    locker.unlock();
+    return true;
 }
\ No newline at end of file
diff --git a/LiteLoader/Kernel/Utils/WinHelper.cpp b/LiteLoader/Kernel/Utils/WinHelper.cpp
--- a/LiteLoader/Kernel/Utils/WinHelper.cpp
+++ b/LiteLoader/Kernel/Utils/WinHelper.cpp
@@ -178,3 +178,19 @@ string GetModuleName(HMODULE handler)
     GetModuleFileNameEx(GetCurrentProcess(), handler, buf, MAX_PATH);
     return UTF82String(std::filesystem::path(buf).filename().u8string());
 }
+
+inline bool isWine()
+{
+    HMODULE ntdll = GetModuleHandle(L"ntdll.dll");
+    auto pwine_get_version = GetProcAddress(ntdll, "wine_get_version");
+    if (pwine_get_version)
+        return true;
+    else
+        return false;
+}
+
+bool IsWineEnvironment()
+{
+    static bool result = isWine();
+    return result;
+}
diff --git a/LiteLoader/LiteLoader.vcxproj b/LiteLoader/LiteLoader.vcxproj
--- a/LiteLoader/LiteLoader.vcxproj
+++ b/LiteLoader/LiteLoader.vcxproj
@@ -111,6 +111,7 @@ LibraryBuilder.exe
     <ClCompile Include="Kernel\DB\Session.cpp" />
     <ClCompile Include="Kernel\DB\Stmt.cpp" />
     <ClCompile Include="Kernel\I18nAPI_LL.cpp" />
+    <ClCompile Include="Kernel\MC\BiomeAPI.cpp" />
     <ClCompile Include="Kernel\NBT\PrettySnbtFormatAPI.cpp" />
     <ClCompile Include="Kernel\Compatibility.cpp" />
     <ClCompile Include="Kernel\MC\ColorFormatAPI.cpp" />
@@ -153,6 +154,7 @@ LibraryBuilder.exe
     <ClInclude Include="Header\MC\Automation.hpp" />
     <ClInclude Include="Header\MC\BinaryStream.hpp" />
     <ClInclude Include="Header\MC\BlockInstance.hpp" />
+    <ClInclude Include="Header\MC\BoundingBox.hpp" />
     <ClInclude Include="Header\MC\ByteArrayTag.hpp" />
     <ClInclude Include="Header\MC\ByteTag.hpp" />
     <ClInclude Include="Header\MC\ColorFormat.hpp" />
@@ -168,6 +170,8 @@ LibraryBuilder.exe
     <ClInclude Include="Header\MC\CommandSelectorBase.hpp" />
     <ClInclude Include="Header\MC\CompoundTag.hpp" />
     <ClInclude Include="Header\MC\CompoundTagVariant.hpp" />
+    <ClInclude Include="Header\MC\ContentIdentity.hpp" />
+    <ClInclude Include="Header\MC\Core.hpp" />
     <ClInclude Include="Header\MC\DataItem.hpp" />
     <ClInclude Include="Header\MC\DataItem2.hpp" />
     <ClInclude Include="Header\MC\DBStorage.hpp" />
@@ -184,14 +188,20 @@ LibraryBuilder.exe
     <ClInclude Include="Header\MC\IntArrayTag.hpp" />
     <ClInclude Include="Header\MC\IntTag.hpp" />
     <ClInclude Include="Header\MC\ItemStackBase.hpp" />
+    <ClInclude Include="Header\MC\ItemStackRequestAction.hpp" />
     <ClInclude Include="Header\MC\ListTag.hpp" />
     <ClInclude Include="Header\MC\MobEffectInstance.hpp" />
     <ClInclude Include="Header\MC\NbtIo.hpp" />
     <ClInclude Include="Header\MC\Packet.hpp" />
+    <ClInclude Include="Header\MC\PackIdVersion.hpp" />
+    <ClInclude Include="Header\MC\PackInfoData.hpp" />
+    <ClInclude Include="Header\MC\Path.hpp" />
     <ClInclude Include="Header\MC\PrettySnbtFormat.hpp" />
     <ClInclude Include="Header\MC\ReadOnlyBinaryStream.hpp" />
     <ClInclude Include="Header\MC\RelativeFloat.hpp" />
+    <ClInclude Include="Header\MC\ResourcePacksInfoPacket.hpp" />
     <ClInclude Include="Header\MC\ScoreboardId.hpp" />
+    <ClInclude Include="Header\MC\SemVersion.hpp" />
     <ClInclude Include="Header\MC\ShortTag.hpp" />
     <ClInclude Include="Header\MC\SimulatedPlayer.hpp" />
     <ClInclude Include="Header\MC\StringTag.hpp" />
diff --git a/LiteLoader/LiteLoader.vcxproj.filters b/LiteLoader/LiteLoader.vcxproj.filters
--- a/LiteLoader/LiteLoader.vcxproj.filters
+++ b/LiteLoader/LiteLoader.vcxproj.filters
@@ -381,6 +381,9 @@
     <ClCompile Include="Kernel\Utils\ShellLinkFile.cpp">
       <Filter>Kernel\Utils</Filter>
     </ClCompile>
+    <ClCompile Include="Kernel\MC\BiomeAPI.cpp">
+      <Filter>Kernel</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="Header\HookAPI.h">
@@ -782,6 +785,33 @@
     <ClInclude Include="Header\Utils\ShellLinkFile.h">
       <Filter>Header\Utils</Filter>
     </ClInclude>
+    <ClInclude Include="Header\MC\ItemStackRequestAction.hpp">
+      <Filter>Header\MC</Filter>
+    </ClInclude>
+    <ClInclude Include="Header\MC\Path.hpp">
+      <Filter>Header\MC</Filter>
+    </ClInclude>
+    <ClInclude Include="Header\MC\BoundingBox.hpp">
+      <Filter>Header\MC</Filter>
+    </ClInclude>
+    <ClInclude Include="Header\MC\ContentIdentity.hpp">
+      <Filter>Header\MC</Filter>
+    </ClInclude>
+    <ClInclude Include="Header\MC\Core.hpp">
+      <Filter>Header\MC</Filter>
+    </ClInclude>
+    <ClInclude Include="Header\MC\PackIdVersion.hpp">
+      <Filter>Header\MC</Filter>
+    </ClInclude>
+    <ClInclude Include="Header\MC\PackInfoData.hpp">
+      <Filter>Header\MC</Filter>
+    </ClInclude>
+    <ClInclude Include="Header\MC\ResourcePacksInfoPacket.hpp">
+      <Filter>Header\MC</Filter>
+    </ClInclude>
+    <ClInclude Include="Header\MC\SemVersion.hpp">
+      <Filter>Header\MC</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <Library Include="Lib\third-party\openssl\libcrypto.lib">
diff --git a/LiteLoader/Main/BuiltinBugFix.cpp b/LiteLoader/Main/BuiltinBugFix.cpp
--- a/LiteLoader/Main/BuiltinBugFix.cpp
+++ b/LiteLoader/Main/BuiltinBugFix.cpp
@@ -252,11 +252,10 @@ TClasslessInstanceHook(__int64, "?teleportEntity@EndGatewayBlockActor@@QEAAXAEAV
 }
 
 //fix Wine Stop
-extern bool isWine();
 TClasslessInstanceHook(void, "?leaveGameSync@ServerInstance@@QEAAXXZ")
 {
     original(this);
-    if (isWine())
+    if (IsWineEnvironment())
     {
         std::cerr << "Quit correctly" << std::endl;
         auto proc = GetCurrentProcess();
@@ -305,4 +304,32 @@ TClasslessInstanceHook(void, "?fireEventPlayerTeleported@MinecraftEventing@@SAXP
     original(this, a1, a2, a3, a4);
 }
 
+// set stdin mode to text mode if in wine environment
+inline bool _tryFixConsoleInputMode()
+{
+    if (LL::globalConfig.enableFixMcBug && IsWineEnvironment())
+    {
+        int result = _setmode(_fileno(stdin), _O_U8TEXT);
+        if (result == -1)
+        {
+            logger.error("Cannot set stdin to utf8 text mode");
+            return false;
+        }
+        return true;
+    }
+    return true;
+}
+
+// Fix wine console input
+THook(std::wistream&,
+      "??$getline@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@std@@YAAEAV?$basic_istream@_WU?$char_traits@_W@std@@@0@$$"
+      "QEAV10@AEAV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@0@_W@Z",
+      std::wistream&& ret, std::wstring& a1, wchar_t a2)
+{
+    if (&ret == &std::wcin)
+    {
+        static bool fixed = _tryFixConsoleInputMode();
+    }
+    return original(std::move(ret), a1, a2);
+}
 
diff --git a/LiteLoader/Main/Config.cpp b/LiteLoader/Main/Config.cpp
--- a/LiteLoader/Main/Config.cpp
+++ b/LiteLoader/Main/Config.cpp
@@ -167,38 +167,46 @@ namespace LL {
     }
 } // namespace LL
 
+inline bool SaveConfig(nlohmann::json& config) {
+    std::ofstream of(LITELOADER_CONFIG_FILE);
+    if (of) {
+        of << config.dump(4);
+        return true;
+    } else {
+        logger.error("Configuration File Creation failed!");
+        return false;
+    }
+}
 
 bool LL::LoadLLConfig()
 {
     try
     {
         auto content = ReadAllFile(LITELOADER_CONFIG_FILE);
-        if (!content)
+        if (!content || content.value().empty())
         {
             logger.warn("LL Config File <{}> not found. Creating configuration file...", LITELOADER_CONFIG_FILE);
             filesystem::create_directories(filesystem::path(LITELOADER_CONFIG_FILE).remove_filename().u8string());
-            std::ofstream of(LITELOADER_CONFIG_FILE);
-            if (of)
-            {
-                of << nlohmann::json(LL::globalConfig).dump(4);
-            }
-            else
-            {
-                logger.error("Configuration File Creation failed!");
-            }
+            LL::SaveLLConfig();
         }
         else
         {
-            auto out = nlohmann::json::parse(*content, nullptr, true, true);
-            LL::globalConfig = out;
-            std::ofstream of(LITELOADER_CONFIG_FILE);
-            if (of)
-            {
-                of << nlohmann::json(LL::globalConfig).dump(4);
-            }
-            else
-            {
-                logger.error("Configuration File Creation failed!");
+            try {
+                auto out = nlohmann::json::parse(*content, nullptr, false, true);
+                LL::globalConfig = out;
+                auto config = nlohmann::json(LL::globalConfig);
+                if (out != config) {
+                    logger.warn("LL Config File <{}> is outdated.",
+                                LITELOADER_CONFIG_FILE);
+                    logger.warn("Updating configuration file...");
+                    return SaveConfig(config);
+                }
+                return true;
+            } catch (const nlohmann::json::exception& e) {
+                logger.error("Fail to parse config file <{}> !", LITELOADER_CONFIG_FILE);
+                logger.error("{}", e.what());
+                logger.error("Using default config file...");
+                return LL::SaveLLConfig();
             }
         }
     }
@@ -214,4 +222,9 @@ bool LL::LoadLLConfig()
         return false;
     }
     return true;
-}
\ No newline at end of file
+}
+
+bool LL::SaveLLConfig() {
+    auto config = nlohmann::json(LL::globalConfig);
+    return SaveConfig(config);
+}
diff --git a/LiteLoader/Main/Config.h b/LiteLoader/Main/Config.h
--- a/LiteLoader/Main/Config.h
+++ b/LiteLoader/Main/Config.h
@@ -59,7 +59,8 @@ struct LLConfig
     bool enableOutputFilter = false;
     bool onlyFilterConsoleOutput = true;
     bool enableEconomyCore = true;
-    std::vector<std::string> outputFilterRegex;
+    std::vector<std::string> outputFilterRegex = {};
+
     // Runtime Config
     LLServerStatus serverStatus = LLServerStatus::Starting;
     std::thread::id tickThreadId;
@@ -72,6 +73,8 @@ void inline to_json(nlohmann::json& j, const LLConfig& conf);
 void inline from_json(const nlohmann::json& j, LLConfig& conf);
 
 bool LoadLLConfig();
+
+bool SaveLLConfig();
 } // namespace LL
 
 /////////////////////// LL AutoUpgrade ///////////////////////
diff --git a/LiteLoader/Main/CrashLogger.cpp b/LiteLoader/Main/CrashLogger.cpp
--- a/LiteLoader/Main/CrashLogger.cpp
+++ b/LiteLoader/Main/CrashLogger.cpp
@@ -11,16 +11,6 @@ using namespace LL;
 
 Logger crashLogger("CrashLogger");
 
-inline bool isWine()
-{
-    HMODULE ntdll = GetModuleHandle(L"ntdll.dll");
-    auto pwine_get_version = GetProcAddress(ntdll, "wine_get_version");
-    if (pwine_get_version)
-        return true;
-    else
-        return false;
-}
-
 bool LL::StartCrashLoggerProcess()
 {
     if (IsDebuggerPresent())
@@ -28,7 +18,7 @@ bool LL::StartCrashLoggerProcess()
         crashLogger.info("Existing debugger detected. Builtin CrashLogger will not work.");
         return true;
     }
-    if (isWine())
+    if (IsWineEnvironment())
     {
         crashLogger.info("Wine Environment detected. Builtin CrashLogger will not work.");
         return true;
diff --git a/LiteLoader/Main/Loader.cpp b/LiteLoader/Main/Loader.cpp
--- a/LiteLoader/Main/Loader.cpp
+++ b/LiteLoader/Main/Loader.cpp
@@ -209,7 +209,7 @@ void LL::LoadMain() {
                     info += "<" + fileVersion + ">";
                 }
                 logger.error("Plugin [{}] throws an std::exception in onPostInit", info);
-                logger.error("Exception: ", TextEncoding::toUTF8(e.what()));
+                logger.error("Exception: {}", TextEncoding::toUTF8(e.what()));
                 logger.error("Fail to init this plugin!");
             }
             catch (...)
diff --git a/LiteLoader/Main/Version.h b/LiteLoader/Main/Version.h
--- a/LiteLoader/Main/Version.h
+++ b/LiteLoader/Main/Version.h
@@ -6,7 +6,7 @@
 
 #define LITELOADER_VERSION_MAJOR    2
 #define LITELOADER_VERSION_MINOR    3
-#define LITELOADER_VERSION_REVISION 0
+#define LITELOADER_VERSION_REVISION 1
 #define LITELOADER_VERSION_ACTIONS  0
 #define LITELOADER_VERSION_STATUS   LITELOADER_VERSION_DEV
 
diff --git a/MBCHANGELOG.txt b/MBCHANGELOG.txt
--- a/MBCHANGELOG.txt
+++ b/MBCHANGELOG.txt
@@ -1,31 +1,38 @@
 [HEADING=1][LiteLoader Release Note][/HEADING]
-LiteLoaderBDS-2.3.0更新已发布，适应于BDS-1.19.1，协议版本527
+LiteLoaderBDS-2.3.1更新已发布，适应于BDS-1.19.1，协议版本527
 
 [HEADING=2][新内容][/HEADING]
 [LIST]
-[*]支持BDS1.19.0
-[*]配置文件新添经济核心（LLMoney）开关
-[*]LLMoney远程调用支持
+[*]新增HttpRequest和HttpResponse的支持
+[*]新添群系API
+[*]新添经验相关API
+[*]新添sendToast API
 [/LIST]
 
 [HEADING=2][改变][/HEADING]
 [LIST]
-[*]更新远程调用API
-[*]LLSE的改变
-[LIST]
-[*]bl.setNbt
-[/LIST]
+[*]改进动态命令的错误输出
 [/LIST]
 
 [HEADING=2][BUG修复][/HEADING]
 [LIST]
 [*]修复LL Bugs
 [LIST]
-[*]Actor::isPlayer()
+[*]修复Actor::Teleport
+[*]修复Sqlite的bug,[URL='https://github.com/LiteLDev/LiteLoaderBDS/issues/452']#452[/URL]
+[*]修复Vec3::toBlockPos的错误
+[*]修复PlayerLeftEvent的Xuid
+[*]修复配置文件加载出现的错误
+[*]修复配置加载失败时造成的LLSE出现错误
 [/LIST]
 [*]修复LLSE Bugs
 [LIST]
-[*]pl.getRespawnPosition
+[*]修复未输出onPostInit错误的原因
+[*]修复NbtListClass的错误
+[*]修复JsonConfigFile的错误,[URL='https://github.com/LiteLDev/LiteLoaderBDS/issues/456']#456[/URL]
 [/LIST]
+[*]修复BDS Bugs
+[LIST]
+[*]修复Wine控制台无法输出的问题
 [/LIST]
-
+[/LIST]
\ No newline at end of file
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 # LiteLoaderBDS - Epoch-making & Cross-language Bedrock Dedicated Servers Plugin Loader
 
 [![status](https://img.shields.io/github/workflow/status/LiteLDev/LiteLoader/Build%20LiteLoader?style=for-the-badge)](https://github.com/LiteLDev/LiteLoader/actions)
-[![Discord](https://img.shields.io/discord/849252980430864384?style=for-the-badge)](https://discord.gg/27KTrxHc9t)
+[![Discord](https://img.shields.io/discord/849252980430864384?color=blue&label=Discord&style=for-the-badge)](https://discord.gg/27KTrxHc9t)
 [![Telegram](https://img.shields.io/badge/telegram-LiteLoader-%232CA5E0?style=for-the-badge&logo=Telegram)](https://t.me/liteloader)
 [
 ![Latest Tag](https://img.shields.io/github/v/tag/LiteLDev/LiteLoader?label=LATEST%20TAG&style=for-the-badge)
diff --git a/README_zh-cn.md b/README_zh-cn.md
--- a/README_zh-cn.md
+++ b/README_zh-cn.md
@@ -1,7 +1,7 @@
 # LiteLoaderBDS - 划时代 x 跨语言BDS插件加载器
 
 [![status](https://img.shields.io/github/workflow/status/LiteLDev/LiteLoader/Build%20LiteLoader?style=for-the-badge)](https://github.com/LiteLDev/LiteLoader/actions)
-[![Discord](https://img.shields.io/discord/849252980430864384?style=for-the-badge)](https://discord.gg/27KTrxHc9t)
+[![Discord](https://img.shields.io/discord/849252980430864384?color=blue&label=Discord&style=for-the-badge)](https://discord.gg/27KTrxHc9t)
 [![Telegram](https://img.shields.io/badge/telegram-LiteLoader-%232CA5E0?style=for-the-badge&logo=Telegram)](https://t.me/liteloader)
 [
 ![Latest Tag](https://img.shields.io/github/v/tag/LiteLDev/LiteLoader?label=LATEST%20TAG&style=for-the-badge)
diff --git a/RELEASE/plugins/LiteLoader/CrashLogger.dll b/RELEASE/plugins/LiteLoader/CrashLogger.dll
Binary files a/RELEASE/plugins/LiteLoader/CrashLogger.dll and b/RELEASE/plugins/LiteLoader/CrashLogger.dll differ
diff --git a/ScriptEngine/API/APIHelp.cpp b/ScriptEngine/API/APIHelp.cpp
--- a/ScriptEngine/API/APIHelp.cpp
+++ b/ScriptEngine/API/APIHelp.cpp
@@ -20,6 +20,7 @@
 #include <API/DataAPI.h>
 #include <API/DatabaseAPI.h>
 #include <API/PlayerAPI.h>
+#include <API/NetworkAPI.h>
 #include <Global.hpp>
 #include <Engine/EngineOwnData.h>
 
@@ -162,6 +163,18 @@ void PrintValue(T &out, Local<Value> v)
             {
                 out << "<DBStmt>";
             }
+            if (IsInstanceOf<HttpServerClass>(v))
+            {
+                out << "<HttpServer>";
+            }
+            if (IsInstanceOf<HttpRequestClass>(v))
+            {
+                out << "<HttpRequest>";
+            }
+            if (IsInstanceOf<HttpResponseClass>(v))
+            {
+                out << "<HttpResponse>";
+            }
 
             Local<Object> obj = v.asObject();
             std::vector<std::string> keys = obj.getKeyNames();
@@ -230,9 +243,22 @@ bool CheckIsFloat(const Local<Value> &num)
     }
 }
 
-
 ///////////////////// Json To Value /////////////////////
 
+Local<Value> BigInteger_Helper(fifo_json& i)
+{
+    if (i.is_number_integer())
+    {
+        if (i.is_number_unsigned())
+        {
+            auto ui = i.get<uint64_t>();
+            if (ui <= LLONG_MAX) return Number::newNumber((int64_t)ui);
+            return Number::newNumber((double)ui);
+        }
+        return Number::newNumber(i.get<int64_t>());
+    }
+}
+
 void JsonToValue_Helper(Local<Array> &res, fifo_json &j);
 
 void JsonToValue_Helper(Local<Object> &res, const string &key, fifo_json &j)
@@ -244,7 +270,7 @@ void JsonToValue_Helper(Local<Object> &res, const string &key, fifo_json &j)
         break;
     case fifo_json::value_t::number_integer:
     case fifo_json::value_t::number_unsigned:
-        res.set(key, Number::newNumber(j.get<int>()));
+        res.set(key, BigInteger_Helper(j));
         break;
     case fifo_json::value_t::number_float:
         res.set(key, Number::newNumber(j.get<double>()));
@@ -286,7 +312,7 @@ void JsonToValue_Helper(Local<Array> &res, fifo_json &j)
         break;
     case fifo_json::value_t::number_integer:
     case fifo_json::value_t::number_unsigned:
-        res.add(Number::newNumber(j.get<int>()));
+        res.add(BigInteger_Helper(j));
         break;
     case fifo_json::value_t::number_float:
         res.add(Number::newNumber(j.get<double>()));
@@ -330,7 +356,7 @@ Local<Value> JsonToValue(fifo_json j)
         break;
     case fifo_json::value_t::number_integer:
     case fifo_json::value_t::number_unsigned:
-        res = Number::newNumber(j.get<int>());
+        res = BigInteger_Helper(j);
         break;
     case fifo_json::value_t::number_float:
         res = Number::newNumber(j.get<double>());
diff --git a/ScriptEngine/API/APIHelp.h b/ScriptEngine/API/APIHelp.h
--- a/ScriptEngine/API/APIHelp.h
+++ b/ScriptEngine/API/APIHelp.h
@@ -73,7 +73,6 @@ bool inline IsInstanceOf(Local<Value> v)
 // 参数数量错误输出
 #define LOG_WRONG_ARGS_COUNT() LOG_ERROR_WITH_SCRIPT_INFO("Wrong number of arguments!");
 
-
 // 至少COUNT个参数
 #define CHECK_ARGS_COUNT(ARGS,COUNT) \
     if(ARGS.size()<COUNT) \
@@ -135,6 +134,14 @@ bool inline IsInstanceOf(Local<Value> v)
         return nullptr; \
     }
 
+// 检查是否TYPE类型_Setter
+#define CHECK_ARG_TYPE_S(ARG, TYPE) \
+    if (ARG.getKind() != TYPE)      \
+    {                               \
+        LOG_WRONG_ARG_TYPE();       \
+        return;             \
+    }
+
 // 截获引擎异常_Constructor
 #define CATCH_C(LOG) \
     catch(const Exception& e) \
@@ -164,6 +171,36 @@ bool inline IsInstanceOf(Local<Value> v)
         return nullptr; \
     }
 
+// 截获引擎异常_Setter
+#define CATCH_S(LOG)                                                 \
+    catch (const Exception& e)                                       \
+    {                                                                \
+        logger.error(LOG##"\n");                                     \
+        PrintException(e);                                           \
+        logger.error("In Plugin: " + ENGINE_OWN_DATA()->pluginName); \
+        return;                                                      \
+    }                                                                \
+    catch (const std::exception& e)                                  \
+    {                                                                \
+        logger.error("C++ Uncaught Exception Detected!");            \
+        logger.error(TextEncoding::toUTF8(e.what()));                \
+        LOG_ERROR_WITH_SCRIPT_INFO();                                \
+        return;                                                      \
+    }                                                                \
+    catch (const seh_exception& e)                                   \
+    {                                                                \
+        logger.error("SEH Uncaught Exception Detected!");            \
+        logger.error(TextEncoding::toUTF8(e.what()));                \
+        LOG_ERROR_WITH_SCRIPT_INFO();                                \
+        return;                                                      \
+    }                                                                \
+    catch (...)                                                      \
+    {                                                                \
+        logger.error("Uncaught Exception Detected!");                \
+        LOG_ERROR_WITH_SCRIPT_INFO();                                \
+        return;                                                      \
+    }
+
 
 // 截获引擎异常_Constructor
 #define CATCH_WITHOUT_RETURN(LOG) \
diff --git a/ScriptEngine/API/NbtAPI.cpp b/ScriptEngine/API/NbtAPI.cpp
--- a/ScriptEngine/API/NbtAPI.cpp
+++ b/ScriptEngine/API/NbtAPI.cpp
@@ -1377,7 +1377,7 @@ Local<Value> NbtListClass::setString(const Arguments& args)
 {
     CHECK_ARGS_COUNT(args, 2);
     CHECK_ARG_TYPE(args[0], ValueKind::kNumber);
-    CHECK_ARG_TYPE(args[1], ValueKind::kNumber);
+    CHECK_ARG_TYPE(args[1], ValueKind::kString);
 
     try
     {
@@ -1406,7 +1406,7 @@ Local<Value> NbtListClass::setByteArray(const Arguments& args)
 {
     CHECK_ARGS_COUNT(args, 2);
     CHECK_ARG_TYPE(args[0], ValueKind::kNumber);
-    CHECK_ARG_TYPE(args[1], ValueKind::kNumber);
+    CHECK_ARG_TYPE(args[1], ValueKind::kByteBuffer);
 
     try
     {
diff --git a/ScriptEngine/API/NetworkAPI.cpp b/ScriptEngine/API/NetworkAPI.cpp
--- a/ScriptEngine/API/NetworkAPI.cpp
+++ b/ScriptEngine/API/NetworkAPI.cpp
@@ -42,6 +42,58 @@ ClassDefine<WSClientClass> WSClientClassBuilder =
         .property("Closed", [] { return Number::newNumber((int)WebSocketClient::Status::Closed); })
         .build();
 
+ClassDefine<HttpServerClass> HttpServerClassBuilder =
+    defineClass<HttpServerClass>("HttpServer")
+        .constructor(&HttpServerClass::constructor)
+        .instanceFunction("onGet", &HttpServerClass::onGet)
+        .instanceFunction("onPut", &HttpServerClass::onPut)
+        .instanceFunction("onPost", &HttpServerClass::onPost)
+        .instanceFunction("onPatch", &HttpServerClass::onPatch)
+        .instanceFunction("onDelete", &HttpServerClass::onDelete)
+        .instanceFunction("onOptions", &HttpServerClass::onOptions)
+
+        .instanceFunction("onPreRouting", &HttpServerClass::onPreRouting)
+        .instanceFunction("onPostRouting", &HttpServerClass::onPostRouting)
+        .instanceFunction("onError", &HttpServerClass::onError)
+        .instanceFunction("onException", &HttpServerClass::onException)
+
+        .instanceFunction("listen", &HttpServerClass::listen)
+        .instanceFunction("startAt", &HttpServerClass::listen)
+        .instanceFunction("stop", &HttpServerClass::stop)
+        .instanceFunction("close", &HttpServerClass::stop)
+        .instanceFunction("isRunning", &HttpServerClass::isRunning)
+        .build();
+
+ClassDefine<HttpRequestClass> HttpRequestClassBuilder =
+    defineClass<HttpRequestClass>("HttpRequest")
+        .constructor(nullptr)
+        .instanceFunction("getHeader", &HttpRequestClass::getHeader)
+
+        .instanceProperty("headers", &HttpRequestClass::getHeaders)
+        .instanceProperty("method", &HttpRequestClass::getMethod)
+        .instanceProperty("body", &HttpRequestClass::getBody)
+        .instanceProperty("path", &HttpRequestClass::getPath)
+        .instanceProperty("params", &HttpRequestClass::getParams)
+        .instanceProperty("query", &HttpRequestClass::getParams)
+        .instanceProperty("remoteAddr", &HttpRequestClass::getRemoteAddr)
+        .instanceProperty("remotePort", &HttpRequestClass::getRemotePort)
+        .instanceProperty("version", &HttpRequestClass::getVersion)
+        .instanceProperty("matches", &HttpRequestClass::getRegexMatches)
+        .build();
+
+ClassDefine<HttpResponseClass> HttpResponseClassBuilder =
+    defineClass<HttpResponseClass>("HttpResponse")
+        .constructor(nullptr)
+        .instanceFunction("getHeader", &HttpResponseClass::getHeader)
+        .instanceFunction("setHeader", &HttpResponseClass::setHeader)
+        .instanceFunction("write", &HttpResponseClass::write)
+
+        .instanceProperty("headers", &HttpResponseClass::getHeaders, &HttpResponseClass::setHeaders)
+        .instanceProperty("status", &HttpResponseClass::getStatus, &HttpResponseClass::setStatus)
+        .instanceProperty("body", &HttpResponseClass::getBody, &HttpResponseClass::setBody)
+        .instanceProperty("version", &HttpResponseClass::getVersion, &HttpResponseClass::setVersion)
+        .instanceProperty("reason", &HttpResponseClass::getReason, &HttpResponseClass::setReason)
+        .build();
 
 //生成函数
 WSClientClass::WSClientClass(const Local<Object>& scriptObj)
@@ -371,6 +423,688 @@ Local<Value> WSClientClass::errorCode(const Arguments& args)
     CATCH("Fail in errorCode!");
 }
 
+//////////////////// Class HttpServer ////////////////////
+
+using namespace httplib;
+
+#define ADD_CALLBACK(method, path, func) \
+callbacks.emplace(make_pair(path, HttpServerCallback{EngineScope::currentEngine(), script::Global<Function>{func}, HttpRequestType::method, path})); \
+svr->##method##(path.c_str(), [this, engine = EngineScope::currentEngine()](const Request& req, Response& resp) { \
+    if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())      \
+        return;                                                                                   \
+    auto task = Schedule::nextTick([this, engine, req, &resp] {                                   \
+        if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())  \
+            return;                                                 \
+        EngineScope enter(engine);                                  \
+        for (auto& [k, v] : this->callbacks)                        \
+        {                                                           \
+            if (v.type != HttpRequestType::method) return;          \
+            std::regex rgx(k);                                      \
+            std::smatch matches;                                    \
+            if (std::regex_match(req.path, matches, rgx))           \
+            {                                                       \
+                if (matches == req.matches)                         \
+                {                                                   \
+                    auto reqObj = new HttpRequestClass(req);        \
+                    auto respObj = new HttpResponseClass(resp);     \
+                    v.func.get().call({}, reqObj, respObj);         \
+                    resp = *respObj->get();                         \
+                    break;                                          \
+                }                                                   \
+            }                                                       \
+        }                                                           \
+    });                                                             \
+    while (!task.isFinished())                                      \
+        std::this_thread::sleep_for(std::chrono::milliseconds(1));                                                                                  \
+});                                                                                                                                                 
+
+HttpServerClass::HttpServerClass(const Local<Object>& scriptObj)
+    : ScriptClass(scriptObj)
+    , svr(new Server)
+{
+}
+HttpServerClass::HttpServerClass()
+    : ScriptClass(ScriptClass::ConstructFromCpp<HttpServerClass>{})
+    , svr(new Server)
+{
+}
+
+HttpServerClass::~HttpServerClass()
+{
+    svr->stop();
+}
+
+HttpServerClass* HttpServerClass::constructor(const Arguments& args)
+{
+    return new HttpServerClass(args.thiz());
+}
+
+Local<Value> HttpServerClass::onGet(const Arguments& args)
+{
+    CHECK_ARGS_COUNT(args, 2);
+    CHECK_ARG_TYPE(args[0], ValueKind::kString);
+    CHECK_ARG_TYPE(args[1], ValueKind::kFunction);
+
+    try {
+        auto path = args[0].toStr();
+        auto func = args[1].asFunction();
+        ADD_CALLBACK(Get, path, func);
+        /* for debug
+        callbacks.emplace(make_pair(path, HttpServerCallback{EngineScope::currentEngine(), script::Global<Function>{func}, HttpRequestType::Get, path}));
+        svr->Get(path.c_str(), [this, engine = EngineScope::currentEngine()](const Request& req, Response& resp) {
+            if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
+                return;
+            auto task = Schedule::nextTick([this, engine, req, &resp] {
+                if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
+                    return;
+                EngineScope enter(engine);
+                for (auto& [k, v] : this->callbacks)
+                {
+                    if (v.type != HttpRequestType::Get) return;
+                    std::regex rgx(k);
+                    std::smatch matches;
+                    if (std::regex_match(req.path, matches, rgx))
+                    {
+                        if (matches == req.matches)
+                        {
+                            auto reqObj = new HttpRequestClass(req);
+                            auto respObj = new HttpResponseClass(resp);
+                            v.func.get().call({}, reqObj, respObj);
+                            resp = *respObj->get();
+                            break;
+                        }
+                    }
+                }
+            });
+            while (!task.isFinished())
+                std::this_thread::sleep_for(std::chrono::milliseconds(1));
+        });
+        */
+        return this->getScriptObject();
+    }
+    CATCH("Fail in onGet")
+}
+
+Local<Value> HttpServerClass::onPut(const Arguments& args)
+{
+    CHECK_ARGS_COUNT(args, 2);
+    CHECK_ARG_TYPE(args[0], ValueKind::kString);
+    CHECK_ARG_TYPE(args[1], ValueKind::kFunction);
+
+    try {
+        auto path = args[0].toStr();
+        auto func = args[1].asFunction();
+        ADD_CALLBACK(Put, path, func);
+        return this->getScriptObject();
+    }
+    CATCH("Fail in onPut!");
+}
+
+Local<Value> HttpServerClass::onPost(const Arguments& args)
+{
+    CHECK_ARGS_COUNT(args, 2);
+    CHECK_ARG_TYPE(args[0], ValueKind::kString);
+    CHECK_ARG_TYPE(args[1], ValueKind::kFunction);
+
+    try {
+        auto path = args[0].toStr();
+        auto func = args[1].asFunction();
+        ADD_CALLBACK(Post, path, func);
+        return this->getScriptObject();
+    }
+    CATCH("Fail in onPost!");
+}
+
+Local<Value> HttpServerClass::onPatch(const Arguments& args)
+{
+    CHECK_ARGS_COUNT(args, 2);
+    CHECK_ARG_TYPE(args[0], ValueKind::kString);
+    CHECK_ARG_TYPE(args[1], ValueKind::kFunction);
+
+    try {
+        auto path = args[0].toStr();
+        auto func = args[1].asFunction();
+        ADD_CALLBACK(Patch, path, func);
+        return this->getScriptObject();
+    }
+    CATCH("Fail in onPatch!");
+}
+
+Local<Value> HttpServerClass::onDelete(const Arguments& args)
+{
+    CHECK_ARGS_COUNT(args, 2);
+    CHECK_ARG_TYPE(args[0], ValueKind::kString);
+    CHECK_ARG_TYPE(args[1], ValueKind::kFunction);
+
+    try {
+        auto path = args[0].toStr();
+        auto func = args[1].asFunction();
+        ADD_CALLBACK(Delete, path, func);
+        return this->getScriptObject();
+    }
+    CATCH("Fail in onDelete!");
+}
+
+Local<Value> HttpServerClass::onOptions(const Arguments& args)
+{
+    CHECK_ARGS_COUNT(args, 2);
+    CHECK_ARG_TYPE(args[0], ValueKind::kString);
+    CHECK_ARG_TYPE(args[1], ValueKind::kFunction);
+
+    try {
+        auto path = args[0].toStr();
+        auto func = args[1].asFunction();
+        ADD_CALLBACK(Options, path, func);
+        return this->getScriptObject();
+    }
+    CATCH("Fail in onOptions!");
+}
+
+
+
+Local<Value> HttpServerClass::onPreRouting(const Arguments& args)
+{
+    CHECK_ARGS_COUNT(args, 1);
+    CHECK_ARG_TYPE(args[0], ValueKind::kFunction);
+
+    try {
+        preRoutingCallback = {EngineScope::currentEngine(), script::Global{args[0].asFunction()}};
+        svr->set_pre_routing_handler([this, engine = EngineScope::currentEngine()]
+                                     (const Request& req, Response& resp) {
+            if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
+                    return Server::HandlerResponse::Unhandled;
+            bool handled = false;
+            auto task = Schedule::nextTick([this, engine, req, &resp, &handled] {
+                if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
+                    return;
+                EngineScope enter(engine);
+                auto reqObj = new HttpRequestClass(req);
+                auto respObj = new HttpResponseClass(resp);
+                auto res = this->preRoutingCallback.func.get().call({}, reqObj, respObj);
+                if (res.isBoolean() && res.asBoolean().value() == false)
+                {
+                    handled = true;
+                }
+                resp = *respObj->get();
+            });
+            while (!task.isFinished())
+                std::this_thread::sleep_for(std::chrono::milliseconds(1));
+            return handled ? Server::HandlerResponse::Handled : Server::HandlerResponse::Unhandled;
+        });
+        return this->getScriptObject();
+    }
+    CATCH("Fail in onPreRouting!");
+}
+
+Local<Value> HttpServerClass::onPostRouting(const Arguments& args)
+{
+    CHECK_ARGS_COUNT(args, 1);
+    CHECK_ARG_TYPE(args[0], ValueKind::kFunction);
+
+    try {
+        postRoutingCallback = {EngineScope::currentEngine(), script::Global{args[0].asFunction()}};
+        svr->set_post_routing_handler([this, engine = EngineScope::currentEngine()]
+                                     (const Request& req, Response& resp) {
+            if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
+                    return;
+            auto task = Schedule::nextTick([this, engine, req, &resp] {
+                if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
+                    return;
+                EngineScope enter(engine);
+                auto reqObj = new HttpRequestClass(req);
+                auto respObj = new HttpResponseClass(resp);
+                this->postRoutingCallback.func.get().call({}, reqObj, respObj);
+                resp = *respObj->get();
+            });
+            while (!task.isFinished())
+                std::this_thread::sleep_for(std::chrono::milliseconds(1));
+        });
+        return this->getScriptObject();
+    }
+    CATCH("Fail in onPostRouting!");
+}
+
+Local<Value> HttpServerClass::onError(const Arguments& args)
+{
+    CHECK_ARGS_COUNT(args, 1);
+    CHECK_ARG_TYPE(args[0], ValueKind::kFunction);
+
+    try {
+        errorCallback = {EngineScope::currentEngine(), script::Global{args[0].asFunction()}};
+        svr->set_error_handler([this, engine = EngineScope::currentEngine()]
+                               (const Request& req, Response& resp) {
+            if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
+                    return;
+            auto task = Schedule::nextTick([this, engine, req, &resp] {
+                if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
+                    return;
+                EngineScope enter(engine);
+                auto reqObj = new HttpRequestClass(req);
+                auto respObj = new HttpResponseClass(resp);
+                this->errorCallback.func.get().call({}, reqObj, respObj);
+                resp = *respObj->get();
+            });
+            while (!task.isFinished())
+                std::this_thread::sleep_for(std::chrono::milliseconds(1));
+        });
+        return this->getScriptObject();
+    }
+    CATCH("Fail in onError!");
+}
+
+Local<Value> HttpServerClass::onException(const Arguments& args)
+{
+    CHECK_ARGS_COUNT(args, 1);
+    CHECK_ARG_TYPE(args[0], ValueKind::kFunction);
+
+    try {
+        exceptionCallback = {EngineScope::currentEngine(), script::Global{args[0].asFunction()}};
+        svr->set_exception_handler([this, engine = EngineScope::currentEngine()]
+                                   (const Request& req, Response& resp, std::exception& e) {
+            if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
+                    return;
+            auto task = Schedule::nextTick([this, engine, req, &resp, e] {
+                if (LL::isServerStopping() || !EngineManager::isValid(engine) || engine->isDestroying())
+                    return;
+                EngineScope enter(engine);
+                auto reqObj = new HttpRequestClass(req);
+                auto respObj = new HttpResponseClass(resp);
+                this->exceptionCallback.func.get().call({}, reqObj, respObj, String::newString(e.what()));
+                resp = *respObj->get();
+            });
+            while (!task.isFinished())
+                std::this_thread::sleep_for(std::chrono::milliseconds(1));
+        });
+        return this->getScriptObject();
+    }
+    CATCH("Fail in onException!");
+}
+
+Local<Value> HttpServerClass::listen(const Arguments& args)
+{
+    if (args.size() == 1)
+    {
+        CHECK_ARG_TYPE(args[0], ValueKind::kNumber);
+    }
+    else if (args.size() == 2)
+    {
+        CHECK_ARG_TYPE(args[0], ValueKind::kString);
+        CHECK_ARG_TYPE(args[1], ValueKind::kNumber);
+    }
+    else
+    {
+        LOG_WRONG_ARG_TYPE();
+        return Local<Value>();
+    }
+    
+    try {
+        string addr = "127.0.0.1";
+        int port = 80;
+        if (args.size() == 2)
+        {
+            addr = args[0].toStr();
+            port = args[1].asNumber().toInt32();
+        }
+        else
+        {
+            port = args[0].asNumber().toInt32();
+        }
+        if (port < 0 || port > 65535)
+        {
+            throw script::Exception("Invalid port number! (0~65535)");
+        }
+
+        RecordOperation(ENGINE_OWN_DATA()->pluginName, "StartHttpServer", fmt::format("on {}:{}", addr, port));
+        
+        thread th([this](string addr, int port) {
+            svr->listen(addr.c_str(), port);
+        }, addr, port);
+        th.detach();
+        return this->getScriptObject(); // return self
+    }
+    CATCH("Fail in listen!");
+}
+
+Local<Value> HttpServerClass::stop(const Arguments& args)
+{
+    CHECK_ARGS_COUNT(args, 0);
+
+    try {
+        RecordOperation(ENGINE_OWN_DATA()->pluginName, "StopHttpServer", "");
+        svr->stop();
+        return Local<Value>();
+    }
+    CATCH("Fail in stop!");
+}
+
+Local<Value> HttpServerClass::isRunning(const Arguments& args)
+{
+    CHECK_ARGS_COUNT(args, 0);
+
+    try {
+        return Boolean::newBoolean(svr->is_running());
+    }
+    CATCH("Fail in isRunning!");
+}
+
+Local<Object> Headers2Object(const Headers& headers) 
+{
+    auto obj = Object::newObject();
+    for (auto& header : headers)
+    {
+        obj.set(header.first, Array::newArray());
+    }
+    for (auto& header : headers)
+    {
+        auto arr = obj.get(header.first).asArray();
+        arr.add(String::newString(header.second));
+        obj.set(header.first, arr);
+    }
+    return obj;
+}
+
+Local<Object> Params2Object(const Params& params) 
+{
+    auto obj = Object::newObject();
+    for (auto& param : params)
+    {
+        if (params.count(param.first) == 1)
+            obj.set(param.first, param.second);
+        else {
+            if (!obj.has(param.first)) {
+                obj.set(param.first, Array::newArray());
+            }
+            auto arr = obj.get(param.first).asArray();
+            arr.add(String::newString(param.second));
+            obj.set(param.first, arr);
+        }
+    }
+    return obj;
+}
+
+HttpRequestClass::HttpRequestClass(const Local<Object>& scriptObj, const Request& req)
+    : ScriptClass(scriptObj)
+    , req(new Request(req))
+{
+}
+HttpRequestClass::HttpRequestClass(const Request& req)
+    : ScriptClass(ScriptClass::ConstructFromCpp<HttpRequestClass>{})
+    , req(new Request(req))
+{
+}
+
+std::shared_ptr<Request> HttpRequestClass::get()
+{
+    return req;
+}
+
+Local<Value> HttpRequestClass::getHeaders()
+{
+    try {
+        return Headers2Object(req->headers);
+    }
+    CATCH("Fail in getHeaders!");
+}
+
+Local<Value> HttpRequestClass::getHeader(const Arguments& args)
+{
+    CHECK_ARGS_COUNT(args, 1);
+    CHECK_ARG_TYPE(args[0], ValueKind::kString);
+
+    try {
+        auto key = args[0].toStr();
+        if (req->headers.count(key) == 0)
+            return Array::newArray();
+        auto value = req->headers.equal_range(key);
+        auto arr = Array::newArray();
+        for (auto it = value.first; it != value.second; ++it)
+        {
+            arr.add(String::newString(it->second));
+        }
+        return arr;
+    }
+    CATCH("Fail in getHeader!");
+}
+
+Local<Value> HttpRequestClass::getBody()
+{
+    try {
+        return String::newString(req->body);
+    }
+    CATCH("Fail in getBody!");
+}
+
+Local<Value> HttpRequestClass::getMethod()
+{
+    try {
+        return String::newString(req->method);
+    }
+    CATCH("Fail in getMethod!");
+}
+
+Local<Value> HttpRequestClass::getPath()
+{
+    try {
+        return String::newString(req->path);
+    }
+    CATCH("Fail in getPath!");
+}
+
+Local<Value> HttpRequestClass::getParams()
+{
+    try {
+        return Params2Object(req->params);
+    }
+    CATCH("Fail in getParams!");
+}
+
+Local<Value> HttpRequestClass::getRemoteAddr()
+{
+    try {
+        return String::newString(req->remote_addr);
+    }
+    CATCH("Fail in getRemoteAddr!");
+}
+
+Local<Value> HttpRequestClass::getRemotePort()
+{
+    try {
+        return Number::newNumber(req->remote_port);
+    }
+    CATCH("Fail in getRemotePort!");
+}
+
+
+Local<Value> HttpRequestClass::getVersion()
+{
+    try {
+        return String::newString(req->version);
+    }
+    CATCH("Fail in getVersion!");
+}
+
+Local<Value> HttpRequestClass::getRegexMatches()
+{
+    try {
+        auto smt = req->matches;
+        auto arr = Array::newArray();
+        for (auto& match : smt)
+        {
+            arr.add(String::newString(match));
+        }
+        return arr;
+    }
+    CATCH("Fail in getRegexMatches!");
+}
+
+HttpResponseClass::HttpResponseClass(const Local<Object>& scriptObj, const Response& resp)
+    : ScriptClass(scriptObj)
+    , resp(new Response(resp))
+{
+}
+HttpResponseClass::HttpResponseClass(const Response& resp)
+    : ScriptClass(ScriptClass::ConstructFromCpp<HttpResponseClass>{})
+    , resp(new Response(resp))
+{
+}
+
+std::shared_ptr<Response> HttpResponseClass::get()
+{
+    return resp;
+}
+
+Local<Value> HttpResponseClass::setHeader(const Arguments& args)
+{
+    CHECK_ARGS_COUNT(args, 2);
+    CHECK_ARG_TYPE(args[0], ValueKind::kString);
+
+    try {
+        auto key = args[0].toStr();
+        string value;
+        if (args[1].isString())
+            value = args[1].toStr();
+        else
+            value = ValueToString(args[1]);
+        resp->headers.insert(make_pair(key, value));
+        return this->getScriptObject(); // return self
+    }
+    CATCH("Fail in setHeader!");
+}
+
+Local<Value> HttpResponseClass::getHeader(const Arguments& args)
+{
+    CHECK_ARGS_COUNT(args, 1);
+    CHECK_ARG_TYPE(args[0], ValueKind::kString);
+
+    try {
+        auto key = args[0].toStr();
+        if (resp->headers.count(key) == 0)
+            return Array::newArray();
+        auto value = resp->headers.equal_range(key);
+        auto arr = Array::newArray();
+        for (auto it = value.first; it != value.second; ++it)
+        {
+            arr.add(String::newString(it->second));
+        }
+        return arr;
+    }
+    CATCH("Fail in getHeader!");
+}
+
+Local<Value> HttpResponseClass::write(const Arguments& args)
+{
+    try {
+        for (size_t i = 0ULL; i < args.size(); i++) {
+            resp->body += ValueToString(args[i]);
+        }
+        return this->getScriptObject();
+    }
+    CATCH("Fail in write!");
+}
+
+void HttpResponseClass::setHeaders(const Local<Value>& headers)
+{
+    CHECK_ARG_TYPE_S(headers, ValueKind::kObject);
+
+    try {
+        auto keys = headers.asObject().getKeys();
+        for (auto& key : keys)
+        {
+            auto key_str = key.toString();
+            auto value = headers.asObject().get(key);
+            if (value.isArray()) {
+                for (size_t i = 0ULL; i < value.asArray().size(); ++i)
+                {
+                    resp->headers.insert(make_pair(key_str, ValueToString(value.asArray().get(i))));
+                }
+                return;
+            }
+            auto val_str = ValueToString(value);
+            resp->headers.insert(make_pair(key_str, val_str));
+        }
+    }
+    CATCH_S("Fail in setHeaders!");
+}
+
+void HttpResponseClass::setStatus(const Local<Value>& status) 
+{
+    CHECK_ARG_TYPE_S(status, ValueKind::kNumber);
+
+    try {
+        resp->status = status.toInt();
+    }
+    CATCH_S("Fail in setStatus!");
+}
+
+void HttpResponseClass::setBody(const Local<Value>& body)
+{
+    CHECK_ARG_TYPE_S(body, ValueKind::kString);
+
+    try {
+        resp->body = body.toStr();
+    }
+    CATCH_S("Fail in setBody!");
+}
+
+void HttpResponseClass::setReason(const Local<Value>& reason)
+{
+    CHECK_ARG_TYPE_S(reason, ValueKind::kString);
+
+    try {
+        resp->reason = reason.toStr();
+    }
+    CATCH_S("Fail in setReason!");
+}
+
+void HttpResponseClass::setVersion(const Local<Value>& version)
+{
+    CHECK_ARG_TYPE_S(version, ValueKind::kString);
+
+    try {
+        resp->version = version.toStr();
+    }
+    CATCH_S("Fail in setVersion!");
+}
+
+Local<Value> HttpResponseClass::getHeaders()
+{
+    try {
+        return Headers2Object(resp->headers);
+    }
+    CATCH("Fail in getHeaders!");
+}
+
+Local<Value> HttpResponseClass::getStatus()
+{
+    try {
+        return Number::newNumber(resp->status);
+    }
+    CATCH("Fail in getStatus!");
+}
+
+Local<Value> HttpResponseClass::getBody()
+{
+    try {
+        return String::newString(resp->body);
+    }
+    CATCH("Fail in getBody!");
+}
+
+Local<Value> HttpResponseClass::getReason()
+{
+    try {
+        return String::newString(resp->reason);
+    }
+    CATCH("Fail in getReason!");
+}
+
+Local<Value> HttpResponseClass::getVersion()
+{
+    try {
+        return String::newString(resp->version);
+    }
+    CATCH("Fail in getVersion!");
+}
 
 //////////////////// APIs ////////////////////
 
diff --git a/ScriptEngine/API/NetworkAPI.h b/ScriptEngine/API/NetworkAPI.h
--- a/ScriptEngine/API/NetworkAPI.h
+++ b/ScriptEngine/API/NetworkAPI.h
@@ -3,6 +3,7 @@
 
 #include <list>
 #include <third-party/LightWebSocketClient/include/WebSocketClient.h>
+#include <third-party/httplib/httplib.h>
 using namespace cyanray;
 
 //////////////////// Types ////////////////////
@@ -19,6 +20,18 @@ struct ListenerListType
     script::Global<Function> func;
 };
 
+enum class HttpRequestType : char
+{
+    Get = 0, Post, Put, Delete, Options, Patch, Head
+};
+
+struct HttpServerCallback
+{
+    ScriptEngine* engine;
+    script::Global<Function> func;
+    HttpRequestType type;
+    std::string path;
+};
 
 //////////////////// Network Static ////////////////////
 
@@ -69,4 +82,88 @@ class WSClientClass : public ScriptClass
     //For Compatibility
     static Local<Object> newWSClient();
 };
-extern ClassDefine<WSClientClass> WSClientClassBuilder;
\ No newline at end of file
+extern ClassDefine<WSClientClass> WSClientClassBuilder;
+
+class HttpServerClass : public ScriptClass
+{
+
+protected:
+    std::shared_ptr<httplib::Server> svr = nullptr;
+    std::multimap<std::string, HttpServerCallback> callbacks;
+    HttpServerCallback errorCallback, exceptionCallback, preRoutingCallback, postRoutingCallback;
+
+public:
+    HttpServerClass(const Local<Object>& scriptObj);
+    HttpServerClass();
+    ~HttpServerClass();
+    static HttpServerClass* constructor(const Arguments& args);
+
+    Local<Value> onGet(const Arguments& args);
+    Local<Value> onPut(const Arguments& args);
+    Local<Value> onPost(const Arguments& args);
+    Local<Value> onPatch(const Arguments& args);
+    Local<Value> onDelete(const Arguments& args);
+    Local<Value> onOptions(const Arguments& args);
+
+    Local<Value> onPreRouting(const Arguments& args);
+    Local<Value> onPostRouting(const Arguments& args);
+    Local<Value> onError(const Arguments& args);
+    Local<Value> onException(const Arguments& args);
+    
+    Local<Value> listen(const Arguments& args);
+    Local<Value> stop(const Arguments& args);
+    Local<Value> isRunning(const Arguments& args);
+};
+extern ClassDefine<HttpServerClass> HttpServerClassBuilder;
+
+class HttpRequestClass : public ScriptClass
+{
+    std::shared_ptr<httplib::Request> req;
+
+public:
+    HttpRequestClass(const Local<Object>& scriptObj, const httplib::Request& req = {});
+    HttpRequestClass(const httplib::Request& req = {});
+    //static HttpRequestClass* constructor(const Arguments& args);
+    std::shared_ptr<httplib::Request> get();
+
+    Local<Value> getHeaders();
+    Local<Value> getHeader(const Arguments& args);
+    Local<Value> getBody();
+    Local<Value> getMethod();
+    Local<Value> getPath();
+    Local<Value> getParams();
+    Local<Value> getRemoteAddr();
+    Local<Value> getRemotePort();
+    Local<Value> getVersion();
+    Local<Value> getRegexMatches();
+    //Local<Value> getMultiFormData();
+};
+extern ClassDefine<HttpRequestClass> HttpRequestClassBuilder;
+
+class HttpResponseClass : public ScriptClass
+{
+    std::shared_ptr<httplib::Response> resp;
+
+public:
+    HttpResponseClass(const Local<Object>& scriptObj, const httplib::Response& resp = {});
+    HttpResponseClass(const httplib::Response& resp);
+    //static HttpResponseClass* constructor(const Arguments& args);
+    std::shared_ptr<httplib::Response> get();
+
+    Local<Value> setHeader(const Arguments& args);
+    Local<Value> getHeader(const Arguments& args);
+    Local<Value> write(const Arguments& args);
+
+    void setHeaders(const Local<Value>& headers);
+    void setStatus(const Local<Value>& status);
+    void setBody(const Local<Value>& body);
+    void setReason(const Local<Value>& reason);
+    void setVersion(const Local<Value>& version);
+
+    Local<Value> getHeaders();
+    Local<Value> getStatus();
+    Local<Value> getBody();
+    Local<Value> getReason();
+    Local<Value> getVersion();
+};
+extern ClassDefine<HttpResponseClass> HttpResponseClassBuilder;
\ No newline at end of file
diff --git a/ScriptEngine/API/PlayerAPI.cpp b/ScriptEngine/API/PlayerAPI.cpp
--- a/ScriptEngine/API/PlayerAPI.cpp
+++ b/ScriptEngine/API/PlayerAPI.cpp
@@ -79,6 +79,7 @@ ClassDefine<PlayerClass> PlayerClassBuilder =
         .instanceFunction("clearItem", &PlayerClass::clearItem)
         .instanceFunction("isSprinting", &PlayerClass::isSprinting)
         .instanceFunction("setSprinting", &PlayerClass::setSprinting)
+        .instanceFunction("sendToast", &PlayerClass::sendToast)
 
         .instanceFunction("getBlockStandingOn", &PlayerClass::getBlockStandingOn)
         .instanceFunction("getDevice", &PlayerClass::getDevice)
@@ -1898,3 +1899,19 @@ Local<Value> PlayerClass::removeItem(const Arguments& args)
     }
     CATCH("Fail in removeItem!")
 }
+
+
+Local<Value> PlayerClass::sendToast(const Arguments& args) {
+    CHECK_ARGS_COUNT(args, 2);
+    CHECK_ARG_TYPE(args[0], ValueKind::kString);
+    CHECK_ARG_TYPE(args[1], ValueKind::kString);
+    try {
+        Player* player = get();
+
+        if (!player) return Local<Value>();
+
+        player->sendToastPacket(args[0].toStr(), args[1].toStr());
+        return Boolean::newBoolean(true);
+    }
+    CATCH("Fail in sendToast!");
+}
\ No newline at end of file
diff --git a/ScriptEngine/API/PlayerAPI.h b/ScriptEngine/API/PlayerAPI.h
--- a/ScriptEngine/API/PlayerAPI.h
+++ b/ScriptEngine/API/PlayerAPI.h
@@ -62,6 +62,7 @@ class PlayerClass : public ScriptClass
     Local<Value> clearItem(const Arguments& args);
     Local<Value> isSprinting(const Arguments& args);
     Local<Value> setSprinting(const Arguments& args);
+    Local<Value> sendToast(const Arguments& args);
 
     Local<Value> getBlockStandingOn(const Arguments& args);
     Local<Value> getDevice(const Arguments& args);
diff --git a/ScriptEngine/BindAPIs.cpp b/ScriptEngine/BindAPIs.cpp
--- a/ScriptEngine/BindAPIs.cpp
+++ b/ScriptEngine/BindAPIs.cpp
@@ -94,4 +94,7 @@ void BindAPIs(ScriptEngine* engine)
     engine->registerNativeClass<CommandClass>(CommandClassBuilder);
     engine->registerNativeClass<CommandOriginClass>(CommandOriginClassBuilder);
     engine->registerNativeClass<CommandOutputClass>(CommandOutputClassBuilder);
+    engine->registerNativeClass<HttpServerClass>(HttpServerClassBuilder);
+    engine->registerNativeClass<HttpRequestClass>(HttpRequestClassBuilder);
+    engine->registerNativeClass<HttpResponseClass>(HttpResponseClassBuilder);
 }
\ No newline at end of file
diff --git a/ScriptEngine/ScriptEngine.cpp b/ScriptEngine/ScriptEngine.cpp
--- a/ScriptEngine/ScriptEngine.cpp
+++ b/ScriptEngine/ScriptEngine.cpp
@@ -46,6 +46,7 @@ void LoadConfigFile()
         auto content = ReadAllFile(LITELOADER_CONFIG_FILE);
         if (content) {
             globalConfig = fifo_json::parse(*content, nullptr, true, true);
+            return;
         }
     }
     catch (const nlohmann::json::exception& e) {
@@ -55,6 +56,7 @@ void LoadConfigFile()
     catch (...) {
         logger.error("Fail to load config file <{}> !", LITELOADER_CONFIG_FILE);
     }
+    globalConfig = fifo_json::object();
 }
 
 void entry()
diff --git a/Scripts/UploadSDK.cmd b/Scripts/UploadSDK.cmd
--- a/Scripts/UploadSDK.cmd
+++ b/Scripts/UploadSDK.cmd
@@ -23,6 +23,14 @@ echo [INFO] Fetching LiteLoaderSDK to GitHub ...
 echo.
 
 for /f "delims=" %%i in ('git rev-parse --abbrev-ref HEAD') do set LL_SDK_NOW_BRANCH=%%i
+for /f "delims=" %%i in ('git describe') do set LL_NOW_TAG_LONG=%%i
+for /f "delims=-" %%i in ('git describe') do set LL_NOW_TAG=%%i
+
+echo LL_SDK_NOW_BRANCH %LL_SDK_NOW_BRANCH%
+echo LL_NOW_TAG_LONG %LL_NOW_TAG_LONG%
+echo LL_NOW_TAG %LL_NOW_TAG%
+echo.
+
 if not exist LiteLoaderSDK/Header/ (
     echo [WARNING] LiteLoaderSDK files no found. Pulling from remote...
     echo.
@@ -54,7 +62,12 @@ if "%LL_SDK_NOW_STATUS%" neq "" (
     echo [INFO] Modified files found.
     echo.
     git add .
-    git commit -m "Update LiteLoaderSDK"
+    if "%LL_SDK_NOW_BRANCH%" == "main" (
+        git commit -m "From LiteLoader %LL_NOW_TAG%"
+        git tag %LL_NOW_TAG%
+    ) else (
+        git commit -m "From LiteLoader %LL_NOW_TAG_LONG%"
+    )
     echo.
     echo [INFO] Pushing to origin...
     echo.
diff --git a/Scripts/install.sh b/Scripts/install.sh
--- a/Scripts/install.sh
+++ b/Scripts/install.sh
@@ -1,8 +1,8 @@
 #!/bin/bash
 # Install wine
 codename=$(lsb_release -c --short)
-BDSVER="1.18.33.02"
-LLVER="2.2.5"
+BDSVER="1.19.1.01"
+LLVER="2.3.0"
 sudo apt install -y wget software-properties-common unzip
 sudo dpkg --add-architecture i386
 wget -nc https://dl.winehq.org/wine-builds/winehq.key
diff --git a/Tools/CrashLogger/CrashLogger_Daemon/Logger.cpp b/Tools/CrashLogger/CrashLogger_Daemon/Logger.cpp
--- a/Tools/CrashLogger/CrashLogger_Daemon/Logger.cpp
+++ b/Tools/CrashLogger/CrashLogger_Daemon/Logger.cpp
@@ -45,7 +45,7 @@ bool CreateLogFiles()
 		CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
 	if (hDumpFile == INVALID_HANDLE_VALUE || hDumpFile == NULL)
 	{
-		log("[CrashLogger][ERROR] Fail to open CoreDump file! Error Code:%d\n", GetLastError());
+		log("[CrashLogger][ERROR] Fail to open CoreDump file! Error Code: 0x%X\n", GetLastError());
 	}
 
 	string tracePath = STACKTRACE_OUTPUT_PATH_PREFIX + dateTimeStr + ".log";
@@ -71,7 +71,7 @@ void CoreDump(PEXCEPTION_POINTERS e)
 		
 		if (!MiniDumpWriteDump(hProcess, dProcessId, hDumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL))
 		{
-			log("[CrashLogger][ERROR] Fail to Generate Minidump! Error Code: %u\n", GetLastError());
+			log("[CrashLogger][ERROR] Fail to Generate Minidump! Error Code: 0x%X\n", GetLastError());
 		}
 		else
 			printf("-- Minidump generated in Directory ./logs/Crash\n");
diff --git a/docs/en_US/Development/EventAPI/PlayerEvents.md b/docs/en_US/Development/EventAPI/PlayerEvents.md
--- a/docs/en_US/Development/EventAPI/PlayerEvents.md
+++ b/docs/en_US/Development/EventAPI/PlayerEvents.md
@@ -409,7 +409,7 @@ Replacement Item: Old Item Object's `type` does not equal the new item's `type`,
   `function(player,pos)`
 - Parameters: 
   - player : `Player`  
-    THe player that is moving.
+    The player that is moving.
   - pos : `FloatPos`  
     The current position of the player.
 - Intercept event: cannot be intercepted.
diff --git a/docs/en_US/Development/GameAPI/Player.md b/docs/en_US/Development/GameAPI/Player.md
--- a/docs/en_US/Development/GameAPI/Player.md
+++ b/docs/en_US/Development/GameAPI/Player.md
@@ -181,6 +181,29 @@ mc.broadcast("Hello everyone ~ ");
 
 ```
 
+#### Display a toast to the top of the screen
+
+`pl.sendToast(title,message)`
+
+- Parameters: 
+
+  - title : `String`  
+    The title of the toast.  
+
+  - message : `string`  
+    the message that the toast may contain alongside the title.
+
+- Return value: Whether the message was sent successfully.
+- Return value type: `Boolean`
+
+```Javascript
+[JS]
+pl.sendToast("Hello","everyone ~");
+[Lua]
+
+```
+
+
 #### Execute a Command as a Player 
 
 `pl.runcmd(cmd)`
diff --git a/docs/zh_CN/Development/GameAPI/Player.md b/docs/zh_CN/Development/GameAPI/Player.md
--- a/docs/zh_CN/Development/GameAPI/Player.md
+++ b/docs/zh_CN/Development/GameAPI/Player.md
@@ -179,6 +179,29 @@ mc.broadcast("Hello everyone ~ ");
 
 ```
 
+#### 在屏幕上方显示消息(类似于成就完成)
+
+`pl.sendToast(title,message)`
+
+- 参数：
+
+  - title : `String`  
+    待发送的标题
+
+  - message : `String`  
+    待发送的文本  
+
+- 返回值：是否成功发送
+
+- 返回值类型：`Boolean`
+
+```clike
+[Js]
+pl.sendToast("Hello","everyone ~");
+[Lua]
+
+```
+
 #### 以某个玩家身份执行一条命令 
 
 `pl.runcmd(cmd)`
diff --git a/docs/zh_CN/Development/SystemAPI/Network.md b/docs/zh_CN/Development/SystemAPI/Network.md
--- a/docs/zh_CN/Development/SystemAPI/Network.md
+++ b/docs/zh_CN/Development/SystemAPI/Network.md
@@ -3,6 +3,12 @@
 下面这些API为脚本提供了基本的网络接口。  
 如果有更复杂的需求，可以使用各自语言平台的网络库来完成任务  
 
+## 目录
+- 🔌 [WebSocket 客户端对象 API](#🔌-websocket-客户端对象-api)
+- 📡 [Http 服务端对象 API](#📡-http-服务端对象-api)
+  - [Http 请求对象 API](#http-请求对象-api)
+  - [Http 响应对象 API](#http-响应对象-api)
+
 ### 发送一个异步HTTP(s) Get请求  
 
 `network.httpGet(url[,header],callback)`
@@ -222,4 +228,343 @@
 - 返回值：上一次错误产生的错误码
 - 返回值类型：`Integer`
 
-如果在上述接口使用中遇到了失败，可以从这里获取上一个错误码
\ No newline at end of file
+如果在上述接口使用中遇到了失败，可以从这里获取上一个错误码
+
+## 📡 Http 服务端对象 API
+
+LLSE提供了简易的Http服务端服务，可以用于Webhook等**数据传输量较小**的Web服务。  
+如果您想搭建网站，更推荐使用老牌Web服务软件如Apache, Nginx等。
+
+### 创建一个新的服务器对象
+
+[Js]  `new HttpServer()`  
+[Lua] `HttpServer()`
+
+- 返回值：新的服务器对象
+- 返回值类型：`HttpServer`
+
+<br>
+
+### 监听 Get 请求
+
+`svr.onGet(path, callback)`
+
+- 参数：
+  - path : `String`  
+    请求目录，以`/`开头，可以包含正则表达式。如: `/test/(.+)`
+  - callback : `Function<(HttpRequest, HttpResponse)>`  
+    回调函数，在收到符合path的GET请求回调
+- 返回值：处理完毕的服务器对象（便于连锁进行其他操作）
+- 返回值类型：`HttpServer`
+
+<br>
+
+### 监听 Put 请求
+
+`svr.onPut(path, callback)`
+
+- 参数：
+  - path : `String`  
+    请求目录，以`/`开头，可以包含正则表达式。如: `/test/(.+)`
+  - callback : `Function<(HttpRequest, HttpResponse)>`  
+    回调函数，在收到符合path的PUT请求回调
+- 返回值：处理完毕的服务器对象（便于连锁进行其他操作）
+- 返回值类型：`HttpServer`
+
+<br>
+
+### 监听 Post 请求
+
+`svr.onPost(path, callback)`
+
+- 参数：
+  - path : `String`  
+    请求目录，以`/`开头，可以包含正则表达式。如: `/test/(.+)`
+  - callback : `Function<(HttpRequest, HttpResponse)>`  
+    回调函数，在收到符合path的POST请求回调
+- 返回值：处理完毕的服务器对象（便于连锁进行其他操作）
+- 返回值类型：`HttpServer`
+
+<br>
+
+### 监听 Patch 请求
+
+`svr.onPatch(path, callback)`
+
+- 参数：
+  - path : `String`  
+    请求目录，以`/`开头，可以包含正则表达式。如: `/test/(.+)`
+  - callback : `Function<(HttpRequest, HttpResponse)>`  
+    回调函数，在收到符合path的PATCH请求回调
+- 返回值：处理完毕的服务器对象（便于连锁进行其他操作）
+- 返回值类型：`HttpServer`
+
+<br>
+
+### 监听 Delete 请求
+
+`svr.onDelete(path, callback)`
+
+- 参数：
+  - path : `String`  
+    请求目录，以`/`开头，可以包含正则表达式。如: `/test/(.+)`
+  - callback : `Function<(HttpRequest, HttpResponse)>`  
+    回调函数，在收到符合path的DELETE请求回调
+- 返回值：处理完毕的服务器对象（便于连锁进行其他操作）
+- 返回值类型：`HttpServer`
+
+<br>
+
+### 监听 Options 请求
+
+`svr.onOptions(path, callback)`
+
+- 参数：
+  - path : `String`  
+    请求目录，以`/`开头，可以包含正则表达式。如: `/test/(.+)`
+  - callback : `Function<(HttpRequest, HttpResponse)>`  
+    回调函数，在收到符合path的OPTIONS请求回调
+- 返回值：处理完毕的服务器对象（便于连锁进行其他操作）
+- 返回值类型：`HttpServer`
+
+<br>
+
+### 监听请求 - 说明
+
+请注意，请求监听是有优先级的。  
+如果有多个路径同时满足正则表达式，则选择先定义的路径。  
+例如：  
+```js
+svr.onGet('/test/123', (req, res) => {
+  res.write('test');
+}).onGet('/test/(.+)', (req, res) => {
+  res.write('get /test/');
+}).onGet('/test/foo', (req, res) => {
+  res.write("bar");
+});
+// 请求 /test/123 时，第一个回调函数将被调用; 而请求 /test/foo 时，第二个回调函数将被调用（尽管第三个回调函数的路径更匹配）
+```
+
+### 监听 PreRouting 预路由事件
+
+`svr.onPreRouting(callback)`
+
+- 参数：
+  - callback : `Function<bool(HttpRequest, HttpResponse)>`  
+    回调函数，在收到请求时调用。在回调函数中可以修改响应，如果返回`false`，则不会继续路由至对应路径的回调函数(但仍然会触发PostRouting事件)。
+- 返回值：处理完毕的服务器对象（便于连锁进行其他操作）
+- 返回值类型：`HttpServer`
+
+<br>
+
+### 监听 PostRouting 后路由事件
+
+`svr.onPostRouting(callback)`
+
+- 参数：
+  - callback : `Function<(HttpRequest, HttpResponse)>`  
+    回调函数，在对应目录的回调函数(或PreRouting事件)执行完毕后调用，在回调函数中可以修改响应
+- 返回值：处理完毕的服务器对象（便于连锁进行其他操作）
+- 返回值类型：`HttpServer`
+
+<br>
+
+### 监听 Error 错误事件
+
+`svr.onError(callback)`
+
+- 参数：
+  - callback : `Function<(HttpRequest, HttpResponse)>`  
+    回调函数，在错误(状态码 >= 400)时调用
+- 返回值：处理完毕的服务器对象（便于连锁进行其他操作）
+- 返回值类型：`HttpServer`
+
+<br>
+
+### 监听 Exception 异常事件
+
+`svr.onException(callback)`
+
+- 参数：
+  - callback : `Function<(HttpRequest, HttpResponse, String)>`  
+    回调函数，在handler中有抛出异常时调用，参数3为异常信息
+- 返回值：处理完毕的服务器对象（便于连锁进行其他操作）
+- 返回值类型：`HttpServer`
+
+<br>
+
+### 监听端口并开启服务器
+
+`svr.listen(addr, port)`  
+`svr.startAt(addr, port)`
+
+- 参数：
+  - addr : `String`  
+    监听地址，可以是IP地址或域名
+  - port : `Number`  
+    监听端口
+- 返回值：处理完毕的服务器对象（便于连锁进行其他操作）
+- 返回值类型：`HttpServer`
+
+<br>
+
+### 停止服务器
+
+`svr.stop()`
+
+- 返回值：无
+
+<br>
+
+### 获取服务器是否正在运行
+
+`svr.isRunning()`
+
+- 返回值：服务器正在运行与否
+- 返回值类型：`Boolean`
+
+<br>
+
+## Http 请求对象 API
+
+### Http请求对象 - 属性
+| 属性             | 含义               | 类型     | 例                           |
+|------------------|-------------------|----------|-----------------------------|
+| `req.method`     | 请求方法           | `String` | `'GET'`                     |
+| `req.path`       | 请求路径           | `String` | `'/233'`                    |
+| `req.query`      | 请求查询参数        | `Object` | `{a: 'b', c: ['d', 'e']}`   |
+| `req.params`      | 请求查询参数(同上)  | `Object` | `{a: 'b', c: ['d', 'e']}`   |
+| `req.headers`    | 请求头             | `Object` | `{a: ['b'], c: ['d', 'e']}` |
+| `req.body`       | 请求内容           | `String` | `'awa'`                     |
+| `req.remoteAddr` | 请求源地址         | `String` | `'114.51.41.91'`            |
+| `req.remotePort` | 请求源端口         | `Number` | `11451`                     |
+| `req.version`    | 请求版本           | `String` | `'HTTP/1.1'`                |
+| `req.matches`    | 请求路径正则匹配结果 | `Array`  | `['/test11', '11']`         |
+
+这些对象属性都是只读的，无法被修改，并且只能在请求回调函数中使用(理论上复制到外部也可以，但不推荐)。  
+
+***注意：***
+1. **`req.matches[0]` 为原始文本，其后面的元素才是匹配结果**
+2. **`req.header["Name"]` 为数组而不是字符串**
+
+***扩展例子：***
+1. `?k=v1&k=v2&k1=v1` => `{k: ['v1', 'v2'], k1: 'v1'}`
+2. `/test/(.+)/(.+)` => `/test/ll/233` => `['/test/ll/233', 'll', '233']`
+
+<br>
+
+### 获取指定请求头的值
+
+`req.getHeader(name)`
+
+- 参数：
+  - name : `String`  
+    请求头名称
+- 返回值：请求头的值数组(如果没有该请求头，则返回`[]`空数组)
+- 返回值类型：`Array<String>`
+
+<br>
+
+## Http 响应对象 API
+
+### Http响应对象 - 属性
+| 属性             | 含义               | 类型     | 例                           |
+|------------------|-------------------|----------|-----------------------------|
+| `res.status`     | 响应状态码         | `Number` | `200`                       |
+| `res.headers`    | 响应头             | `Object` | `{a: ['b'], c: ['d', 'e']}` |
+| `res.body`       | 响应内容           | `String` | `'awa'`                     |
+| `res.version`    | 响应版本           | `String` | `'HTTP/1.1'`                |
+| `res.reason`     | 错误原因           | `String` | `'OK'`                      |
+
+这些属性可以修改且应当被修改，但只有在回调函数中修改才有效
+
+<br>
+
+### 获取指定响应头的值
+
+`req.getHeader(name)`
+
+- 参数：
+  - name : `String`  
+    响应头名称
+- 返回值：响应头的值数组(如果没有该响应头，则返回`[]`空数组)
+- 返回值类型：`Array<String>`
+
+<br>
+
+### 设置指定响应头的值
+
+`req.setHeader(name, value)`
+
+- 参数：
+  - name : `String`  
+    响应头名称
+  - value : `Any`  
+    响应头值
+- 返回值：处理完毕的响应对象
+- 返回值类型：`HttpResponse`
+
+<br>
+
+### 写入响应内容
+
+`res.write(content1, content2, ...)`
+
+- 参数：
+  - content : `Any`  
+    响应内容
+- 返回值：处理完毕的响应对象
+- 返回值类型：`HttpResponse`
+- 注：本函数在目前相当于`res.body += content1 + content2 + ...`
+
+<br>
+
+## Http API 样例
+
+```js
+let server = new HttpServer();
+server.onGet("/hello(.+)", (req, resp) => {
+	logger.info("http_server_test: run: onGet: Received a request from ", req.remoteAddr, 
+				':', req.remotePort, " for ", req.path);
+	resp.write("<html><body>")
+		.write("<h1>It works!</h1>")
+		.write("<br/>")
+		.write("<p>Request Headers: ", JSON.stringify(req.headers, null, 4), "</p>")
+		.write("<p>Request Body: ", req.body, "</p>")
+		.write("<p>Your Address: ", req.remoteAddr, ':', req.remotePort, "</p>")
+		.write("<p>HTTP Version: ", req.version, "</p>")
+		.write("<p>Method: ", req.method, "</p>")
+		.write("<p>Path: ", req.path, "</p>")
+		.write("<p>Parsed Query: ", JSON.stringify(req.query, null, 4), "</p>")
+		.write("<p>Path Regex Matches: ", JSON.stringify(req.matches), "</p>")
+		.write("</body></html>")
+	resp.status = 200;
+	resp.reason = "OK";
+	resp.setHeader("Content-Type", "text/html");
+}).onGet("/404", (req, resp) => {
+	resp.status = 404;
+	resp.reason = "Not Found";
+	resp.write("Not Found");
+}).onGet("/test-redirect", (req, resp) => {
+	// https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Redirections
+	resp.status = 301;
+	resp.reason = "Moved Permanently";
+	resp.setHeader("Location", "https://github.com");
+}).onGet("/(.+)", (req, resp) => {
+	resp.write("Hello World! ", req.matches);
+}).onPreRouting((req, resp) => {
+	logger.info("http_server_test: run: onPreRouting: resp.body.length: ", resp.body.length);
+	if (req.path == "/test-prerouting") {
+		resp.body = "Hello World!";
+		resp.status = 200;
+		resp.reason = "OK";
+		return false;
+	}
+}).onPostRouting((req, resp) => {
+	logger.info("http_server_test: run: onPostRouting: resp.body.length: ", resp.body.length);
+}).onError((req, resp) => {
+	resp.write("\nonError called");
+}).listen(this.listen_address, this.listen_port);
+logger.info("http_server_test: run: Server listening on " + this.listen_address + ":" + this.listen_port);
+```
+请查看 [UnitTest](https://github.com/LiteLDev/LiteLoaderBDS/tree/main/Test/UnitTest.js)


Test patch:
diff --git a/Test/DBTest_LLSE.js b/Test/DBTest_LLSE.js
--- a/Test/DBTest_LLSE.js
+++ b/Test/DBTest_LLSE.js
@@ -36,6 +36,8 @@ function test_db(session)
     do {
         log(stmt.fetch());
     } while (stmt.step());
+    stmt = session.prepare("SELECT * FROM test_llse WHERE a = 'foo' OR a = 'bar'");
+    log(stmt.execute().fetchAll());
     session.close();
 }
 
diff --git a/Test/UnitTest.js b/Test/UnitTest.js
--- a/Test/UnitTest.js
+++ b/Test/UnitTest.js
@@ -1,9 +1,5 @@
 const PLUGIN_DIR = "./plugins/UnitTest/";
 
-function init() {
-	logger.setTitle("UnitTest");
-}
-
 // Class Ll
 const ll_test = {
     run: function() {
@@ -33,7 +29,136 @@ const file_test = {
 		file2.close();
 	}
 }
+// Class HttpServer
+const http_server_test = {
+	listen_address: "127.0.0.1",
+	listen_port: 23333,
+	server: null,
+	run: function() {
+		this.server = new HttpServer();
+		this.server.onGet("/hello(.+)", (req, resp) => {
+			logger.info("http_server_test: run: onGet: Received a request from ", req.remoteAddr, 
+						':', req.remotePort, " for ", req.path);
+			resp.write("<html><body>")
+				.write("<h1>It works!</h1>")
+				.write("<br/>")
+				.write("<p>Request Headers: ", JSON.stringify(req.headers, null, 4), "</p>")
+				.write("<p>Request Body: ", req.body, "</p>")
+				.write("<p>Your Address: ", req.remoteAddr, ':', req.remotePort, "</p>")
+				.write("<p>HTTP Version: ", req.version, "</p>")
+				.write("<p>Method: ", req.method, "</p>")
+				.write("<p>Path: ", req.path, "</p>")
+				.write("<p>Parsed Query: ", JSON.stringify(req.query, null, 4), "</p>")
+				.write("<p>Path Regex Matches: ", JSON.stringify(req.matches), "</p>")
+				.write("</body></html>")
+			resp.status = 200;
+			resp.reason = "OK";
+			resp.setHeader("Content-Type", "text/html");
+		}).onGet("/404", (req, resp) => {
+			resp.status = 404;
+			resp.reason = "Not Found";
+			resp.write("Not Found");
+		}).onGet("/test-redirect", (req, resp) => {
+			// https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Redirections
+			resp.status = 301;
+			resp.reason = "Moved Permanently";
+			resp.setHeader("Location", "https://github.com");
+		}).onGet("/(.+)", (req, resp) => {
+			resp.write("Hello World! ", req.matches);
+		}).onPreRouting((req, resp) => {
+			logger.info("http_server_test: run: onPreRouting: resp.body.length: ", resp.body.length);
+			if (req.path == "/test-prerouting") {
+				resp.body = "Hello World!";
+				resp.status = 200;
+				resp.reason = "OK";
+				return false;
+			}
+		}).onPostRouting((req, resp) => {
+			logger.info("http_server_test: run: onPostRouting: resp.body.length: ", resp.body.length);
+		}).onError((req, resp) => {
+			resp.write("\nonError called");
+		}).listen(this.listen_address, this.listen_port);
+		logger.info("http_server_test: run: Server listening on " + this.listen_address + ":" + this.listen_port);
+	},
+	stop: function() {
+		this.server.stop();
+		logger.info("http_server_test: stop: Server stopped");
+	},
+	command: {
+		subcmd_name: "http_server",
+		register: function(cmd) {
+			cmd.setEnum("http_server_action_stop", ["stop"]);
+			cmd.mandatory("http_server_action", ParamType.Enum, "http_server_action_stop");
+			cmd.overload(["cmd", "http_server_action_stop"]);
+		},
+		handler: function(_cmd, _ori, _outp, res) {
+			if (res["http_server_action"] == "stop") {
+				http_server_test.stop();
+			}
+		}
+	}
+}
+// Class JsonConfigFile
+const json_config_file_test = {
+	file: PLUGIN_DIR + "json_config_file_test.json",
+	run: function() {
+		let config = new JsonConfigFile(this.file);
+		config.init("test1", "test");
+		config.init("test2", [1145141919810, "test2"]);
+		config.init("test3", 2333333333);
+		config.init("test4", {test: "test"});
+		logger.info("json_config_file_test: run: test1: ", config.get("test1"));
+		logger.info("json_config_file_test: run: test2: ", config.get("test2"));
+		logger.info("json_config_file_test: run: test3: ", config.get("test3"));
+		logger.info("json_config_file_test: run: test4: ", config.get("test4"));
+	}
+}
+
+///////////////////////////////////// CLASSES //////////////////////////////////////
+const CLASSES = [
+	ll_test,
+	file_test,
+	http_server_test,
+	json_config_file_test
+];
+
+///////////////////////////////// UNIT TEST COMMAND /////////////////////////////////
+function RegisterUnitTestCommand() {
+	let cmd = mc.newCommand("unit_test", "UnitTest command", PermType.GameMasters, 0x80, "utest");
+	let subcmds = [];
+	let handlers = {};
+	CLASSES.forEach(clazz => {
+		if (clazz["command"] != null) {
+			subcmds.push(clazz.command.subcmd_name);
+			handlers[clazz.command.subcmd_name] = clazz.command.handler;
+		}
+	});
+	cmd.setEnum("subcmds", subcmds);
+	cmd.mandatory("cmd", ParamType.Enum, "subcmds");
+	cmd.overload(["cmd"]);
+	CLASSES.forEach(clazz => {
+		if (clazz["command"] != null) {
+			clazz.command.register(cmd);
+		}
+	});
+	cmd.setCallback(function(cmd, ori, outp, res) {
+		if (res.cmd != null) {
+			let handler = handlers[res.cmd];
+			if (handler != null) {
+				handler(cmd, ori, outp, res);
+			}
+		}
+	});
+	cmd.setup();
+}
 
+
+function init() {
+	logger.setTitle("UnitTest");
+	RegisterUnitTestCommand();
+}
 init();
 ll_test.run();
-file_test.run();
\ No newline at end of file
+file_test.run();
+http_server_test.run();
+json_config_file_test.run();
\ No newline at end of file