Skip to content
Permalink
Browse files
WebServer: Implement "Connection: keep-alive"
Responses can be sent in chunked form or not on an individual basis by setting ResponseIface_WebServer::isChunked. Currently, we set this to true for all HTTP/1.1 requests. (In the future, it should be decided by examining the request headers.) When true, we return the following header fields:

Transfer-Encoding: chunked\r\n
Connection: keep-alive\r\n

The message body is then sent in chunked form using OutPipe_HTTPChunked, which implements the protocol described by https://en.wikipedia.org/wiki/Chunked_transfer_encoding. The connection is kept alive for additional requests.
  • Loading branch information
preshing committed Dec 17, 2020
1 parent 2348c85 commit 7de2cc1
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 85 deletions.
@@ -8,6 +8,7 @@
#include <web-common/FetchFromFileSystem.h>
#include <web-common/SourceCode.h>
#include <web-common/Echo.h>
#include <web-common/OutPipe_HTTPChunked.h>

using namespace ply;
using namespace web;
@@ -12,11 +12,12 @@ namespace ply {
//---------------------------------------------------------------
// Primitives for printing numeric types
//----------------------------------------------------------------
PLY_INLINE u8 toDigit(u32 d) {
return (d <= 35) ? "0123456789abcdefghijklmnopqrstuvwxyz"[d] : '?';
PLY_INLINE u8 toDigit(u32 d, bool capitalize = false) {
const char* digitTable = capitalize ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" : "0123456789abcdefghijklmnopqrstuvwxyz";
return (d <= 35) ? digitTable[d] : '?';
}

PLY_NO_INLINE void printString(OutStream* outs, u64 value, u32 radix) {
PLY_NO_INLINE void printString(OutStream* outs, u64 value, u32 radix, bool capitalize) {
PLY_ASSERT(radix >= 2);
u8 digitBuffer[64];
s32 digitIndex = PLY_STATIC_ARRAY_SIZE(digitBuffer);
@@ -28,24 +29,24 @@ PLY_NO_INLINE void printString(OutStream* outs, u64 value, u32 radix) {
u64 quotient = value / radix;
u32 digit = u32(value - quotient * radix);
PLY_ASSERT(digitIndex > 0);
digitBuffer[--digitIndex] = toDigit(digit);
digitBuffer[--digitIndex] = toDigit(digit, capitalize);
value = quotient;
}
}

outs->write({digitBuffer + digitIndex, (u32) PLY_STATIC_ARRAY_SIZE(digitBuffer) - digitIndex});
}

PLY_NO_INLINE void printString(OutStream* outs, s64 value, u32 radix) {
PLY_NO_INLINE void printString(OutStream* outs, s64 value, u32 radix, bool capitalize) {
if (value >= 0) {
printString(outs, (u64) value, radix);
printString(outs, (u64) value, radix, capitalize);
} else {
outs->writeByte('-');
printString(outs, (u64) -value, radix);
printString(outs, (u64) -value, radix, capitalize);
}
}

PLY_NO_INLINE void printString(OutStream* outs, double value, u32 radix) {
PLY_NO_INLINE void printString(OutStream* outs, double value, u32 radix, bool capitalize) {
PLY_ASSERT(radix >= 2);

#if PLY_COMPILER_GCC
@@ -68,7 +69,7 @@ PLY_NO_INLINE void printString(OutStream* outs, double value, u32 radix) {
u32 radix6 = radix3 * radix3;
if (value == 0.0 || (value * radix3 > radix && value < radix6)) {
u64 fixedPoint = u64(value * radix3);
printString(outs, fixedPoint / radix3, radix);
printString(outs, fixedPoint / radix3, radix, capitalize);
outs->writeByte('.');
u64 fractionalPart = fixedPoint % radix3;
{
@@ -77,7 +78,7 @@ PLY_NO_INLINE void printString(OutStream* outs, double value, u32 radix) {
for (s32 i = 2; i >= 0; i--) {
u64 quotient = fractionalPart / radix;
u32 digit = u32(fractionalPart - quotient * radix);
digitBuffer[i] = toDigit(digit);
digitBuffer[i] = toDigit(digit, capitalize);
fractionalPart = quotient;
}
outs->write({digitBuffer, PLY_STATIC_ARRAY_SIZE(digitBuffer)});
@@ -88,15 +89,15 @@ PLY_NO_INLINE void printString(OutStream* outs, double value, u32 radix) {
double exponent = floor(logBase);
double m = value / pow(radix, exponent); // mantissa (initially)
s32 digit = clamp<s32>((s32) floor(m), 1, radix - 1);
outs->writeByte(toDigit(digit));
outs->writeByte(toDigit(digit, capitalize));
outs->writeByte('.');
for (u32 i = 0; i < 3; i++) {
m = (m - digit) * radix;
digit = clamp<s32>((s32) floor(m), 0, radix - 1);
outs->writeByte(toDigit(digit));
outs->writeByte(toDigit(digit, capitalize));
}
outs->writeByte('e');
printString(outs, (s64) exponent, radix);
printString(outs, (s64) exponent, radix, capitalize);
}
}
}
@@ -156,15 +157,15 @@ PLY_NO_INLINE void fmt::TypePrinter<fmt::WithRadix>::print(StringWriter* sw,
const fmt::WithRadix& value) {
switch (value.type) {
case WithRadix::U64: {
printString(sw, value.u64_, value.radix);
printString(sw, value.u64_, value.radix, value.capitalize);
break;
}
case WithRadix::S64: {
printString(sw, value.s64_, value.radix);
printString(sw, value.s64_, value.radix, value.capitalize);
break;
}
case WithRadix::Double: {
printString(sw, value.double_, value.radix);
printString(sw, value.double_, value.radix, value.capitalize);
break;
}
default: {
@@ -180,15 +181,15 @@ PLY_NO_INLINE void fmt::TypePrinter<StringView>::print(StringWriter* sw, StringV
}

PLY_NO_INLINE void fmt::TypePrinter<u64>::print(StringWriter* sw, u64 value) {
printString(sw, value, 10);
printString(sw, value, 10, false);
}

PLY_NO_INLINE void fmt::TypePrinter<s64>::print(StringWriter* sw, s64 value) {
printString(sw, value, 10);
printString(sw, value, 10, false);
}

PLY_NO_INLINE void fmt::TypePrinter<double>::print(StringWriter* sw, double value) {
printString(sw, value, 10);
printString(sw, value, 10, false);
}

PLY_NO_INLINE void fmt::TypePrinter<bool>::print(StringWriter* sw, bool value) {
@@ -165,34 +165,38 @@ struct WithRadix {
s64 s64_;
};
u32 type : 2;
u32 radix : 30;
u32 capitalize : 1;
u32 radix : 29;

template <typename SrcType,
typename std::enable_if_t<std::is_floating_point<SrcType>::value, int> = 0>
inline WithRadix(SrcType v, u32 radix) {
inline WithRadix(SrcType v, u32 radix, bool capitalize = false) {
this->double_ = v;
this->type = U64;
this->capitalize = capitalize ? 1 : 0;
this->radix = radix;
}
template <typename SrcType,
typename std::enable_if_t<std::is_unsigned<SrcType>::value, int> = 0>
inline WithRadix(SrcType v, u32 radix) {
inline WithRadix(SrcType v, u32 radix, bool capitalize = false) {
this->u64_ = v;
this->type = U64;
this->capitalize = capitalize ? 1 : 0;
this->radix = radix;
}
template <typename SrcType, typename std::enable_if_t<std::is_signed<SrcType>::value, int> = 0>
inline WithRadix(SrcType v, u32 radix) {
inline WithRadix(SrcType v, u32 radix, bool capitalize = false) {
this->s64_ = v;
this->type = S64;
this->capitalize = capitalize ? 1 : 0;
this->radix = radix;
}
};

struct Hex : WithRadix {
template <typename SrcType,
typename std::enable_if_t<std::is_arithmetic<SrcType>::value, int> = 0>
inline Hex(SrcType v) : WithRadix{v, 16} {
inline Hex(SrcType v, bool capitalize = false) : WithRadix{v, 16, capitalize} {
}
};

@@ -11,8 +11,9 @@ namespace ply {
namespace web {

PLY_NO_INLINE void echo_serve(const void*, StringView requestPath, ResponseIface* responseIface) {
StringWriter* sw = responseIface->respondWithStream(ResponseCode::OK)->strWriter();
StringWriter* sw = responseIface->beginResponseHeader(ResponseCode::OK)->strWriter();
*sw << "Content-type: text/html\r\n\r\n";
responseIface->endResponseHeader();
*sw << R"(<html>
<head><title>Echo</title></head>
<body>
@@ -56,9 +56,10 @@ PLY_NO_INLINE void FetchFromFileSystem::serve(const FetchFromFileSystem* params,
return;
}

OutStream* outs = responseIface->respondWithStream(ResponseCode::OK);
OutStream* outs = responseIface->beginResponseHeader(ResponseCode::OK);
outs->strWriter()->format("Content-Type: {}\r\n", cursor->mimeType);
*outs->strWriter() << "Cache-Control: max-age=1200\r\n\r\n";
responseIface->endResponseHeader();
outs->write(bin);
}

@@ -0,0 +1,44 @@
/*------------------------------------
///\ Plywood C++ Framework
\\\/ https://plywood.arc80.com/
------------------------------------*/
#include <web-common/Core.h>
#include <web-common/OutPipe_HTTPChunked.h>

namespace ply {
namespace web {

PLY_NO_INLINE void OutPipe_HTTPChunked_destroy(OutPipe* outPipe_) {
OutPipe_HTTPChunked* outPipe = static_cast<OutPipe_HTTPChunked*>(outPipe_);
// End of chunk stream
*outPipe->outs->strWriter() << "0\r\n\r\n";
outPipe->outs->flushMem();
}

PLY_NO_INLINE bool OutPipe_HTTPChunked_write(OutPipe* outPipe_, ConstBufferView srcBuf) {
OutPipe_HTTPChunked* outPipe = static_cast<OutPipe_HTTPChunked*>(outPipe_);
if (outPipe->chunkMode) {
PLY_ASSERT(srcBuf.numBytes > 0);
outPipe->outs->strWriter()->format("{}\r\n", fmt::Hex{srcBuf.numBytes, true});
}
outPipe->outs->write(srcBuf);
if (outPipe->chunkMode) {
*outPipe->outs->strWriter() << "\r\n";
}
return !outPipe->outs->atEOF();
}

PLY_NO_INLINE bool OutPipe_HTTPChunked_flush(OutPipe* outPipe_, bool toDevice) {
OutPipe_HTTPChunked* outPipe = static_cast<OutPipe_HTTPChunked*>(outPipe_);
return outPipe->outs->flush(toDevice);
}

OutPipe::Funcs OutPipe_HTTPChunked::Funcs_ = {
OutPipe_HTTPChunked_destroy,
OutPipe_HTTPChunked_write,
OutPipe_HTTPChunked_flush,
OutPipe::seek_Empty,
};

} // namespace web
} // namespace ply
@@ -0,0 +1,28 @@
/*------------------------------------
///\ Plywood C++ Framework
\\\/ https://plywood.arc80.com/
------------------------------------*/
#pragma once
#include <web-common/Core.h>

namespace ply {
namespace web {

//-----------------------------------------------------------------------
// OutPipe_HTTPChunked
//-----------------------------------------------------------------------
struct OutPipe_HTTPChunked : OutPipe {
static Funcs Funcs_;
OptionallyOwned<OutStream> outs;
bool chunkMode = false;

PLY_INLINE OutPipe_HTTPChunked(OptionallyOwned<OutStream>&& outs)
: OutPipe{&Funcs_}, outs{std::move(outs)} {
}
PLY_INLINE void setChunkMode(bool chunkMode) {
this->chunkMode = chunkMode;
}
};

} // namespace web
} // namespace ply
@@ -41,7 +41,8 @@ struct ResponseIface {

// The request handler must call respondWith first, then manually write any optional headers,
// followed by a blank \r\n line, followed by the content.
virtual OutStream* respondWithStream(ResponseCode responseCode) = 0;
virtual OutStream* beginResponseHeader(ResponseCode responseCode) = 0;
virtual void endResponseHeader() = 0;
void respondGeneric(ResponseCode responseCode);
};

0 comments on commit 7de2cc1

Please sign in to comment.