\$\begingroup\$
\$\endgroup\$
The StreamInput
class is used to wrap an existing std::iostream. It is either provided a body length or the encoding type (transport-encoding: chunked). It will then allow the user to read data from the stream until the body section is exausted (not allowing the user to read more than is available in the body).
This stream is retrieved from the Request
object provided to the user.
StreamInput.h
#ifndef THORSANVIL_NISSE_NISSEHTTP_STREAMINPUT_H
#define THORSANVIL_NISSE_NISSEHTTP_STREAMINPUT_H
#include "NisseHTTPConfig.h"
#include "Util.h"
#include <iostream>
namespace ThorsAnvil::Nisse::NisseHTTP
{
class StreamBufInput: public std::streambuf
{
public:
using Complete = std::function<void()>;
typedef std::streambuf::traits_type traits;
typedef traits::int_type int_type;
typedef traits::char_type char_type;
private:
std::streamsize remaining;
std::streambuf* buffer;
bool chunked;
bool firstChunk;
Complete complete;
public:
StreamBufInput(Complete&& complete = [](){});
StreamBufInput(std::istream& stream, std::size_t length, Complete&& complete = [](){});
StreamBufInput(std::istream& stream, Encoding encoding, Complete&& complete = [](){});
StreamBufInput(StreamBufInput&& move) noexcept;
StreamBufInput& operator=(StreamBufInput&& move) noexcept;
StreamBufInput(StreamBufInput const&) = delete;
StreamBufInput& operator=(StreamBufInput const&) = delete;
void swap(StreamBufInput& other) noexcept;
friend void swap(StreamBufInput& lhs, StreamBufInput& rhs) {lhs.swap(rhs);}
protected:
// Read:
virtual int_type uflow() override;
virtual std::streamsize xsgetn(char_type* s, std::streamsize count) override;
private:
void checkBuffer();
void getNextChunk();
};
class StreamInput: public std::istream
{
StreamBufInput buffer;
public:
StreamInput()
: std::istream(nullptr)
, buffer()
{}
StreamInput(std::istream& stream, std::size_t length)
: std::istream(nullptr)
, buffer(stream, length)
{
rdbuf(&buffer);
}
StreamInput(std::istream& stream, Encoding encoding)
: std::istream(nullptr)
, buffer(stream, encoding)
{
rdbuf(&buffer);
}
void addBuffer(StreamBufInput&& newBuffer)
{
buffer = std::move(newBuffer);
rdbuf(&buffer);
clear();
}
};
}
#endif
StreamInput.cpp
#include "StreamInput.h"
using namespace ThorsAnvil::Nisse::NisseHTTP;
StreamBufInput::StreamBufInput(Complete&& complete)
: remaining(0)
, buffer(nullptr)
, chunked(false)
, firstChunk(false)
, complete(std::move(complete))
{}
StreamBufInput::StreamBufInput(std::istream& stream, std::size_t length, Complete&& complete)
: remaining(length)
, buffer(stream.rdbuf())
, chunked(false)
, firstChunk(false)
, complete(std::move(complete))
{}
StreamBufInput::StreamBufInput(std::istream& stream, Encoding /*encoding*/, Complete&& complete)
: remaining(0)
, buffer(stream.rdbuf())
, chunked(true)
, firstChunk(true)
, complete(std::move(complete))
{}
StreamBufInput::StreamBufInput(StreamBufInput&& move) noexcept
: remaining(std::exchange(move.remaining, 0))
, buffer(std::exchange(move.buffer, nullptr))
, chunked(std::exchange(move.chunked, false))
, firstChunk(std::exchange(move.firstChunk, false))
, complete(std::exchange(move.complete, [](){}))
{}
StreamBufInput& StreamBufInput::operator=(StreamBufInput&& move) noexcept
{
remaining = 0;
buffer = nullptr;
chunked = false;
firstChunk = false;
complete = [](){};
swap(move);
return *this;
}
void StreamBufInput::swap(StreamBufInput& other) noexcept
{
std::streambuf::swap(other);
using std::swap;
swap(remaining, other.remaining);
swap(buffer, other.buffer);
swap(chunked, other.chunked);
swap(firstChunk,other.firstChunk);
swap(complete, other.complete);
}
// Read:
StreamBufInput::int_type StreamBufInput::uflow()
{
//std::cerr << "uflow\n";
if (remaining == 0) {
getNextChunk();
}
if (remaining == 0) {
return traits::eof();
}
--remaining;
int val = buffer->sbumpc();
//std::cerr << "Got: " << val << " >" << static_cast<char>(val) << "<\n";
return val;
}
std::streamsize StreamBufInput::xsgetn(char_type* s, std::streamsize count)
{
//std::cerr << "\n\n\n==================\nxsgetn\n";
//std::cerr << "\tRemaining: " << remaining << " CK: " << chunked << " : " << count << "\n";
std::streamsize result = 0;
while (remaining != 0 || chunked)
{
std::streamsize nextChunk = std::min(count, remaining);
std::streamsize got = buffer->sgetn(s, nextChunk);
s += got;
count -= got;
result += got;
remaining -= got;
//std::cerr << "\tRead: " << nextChunk << " > " << got << "\n";
if (count == 0) {
break;
}
if (remaining == 0) {
getNextChunk();
}
}
return result;
}
void StreamBufInput::checkBuffer()
{
}
void StreamBufInput::getNextChunk()
{
if (!chunked)
{
complete();
return;
}
if (firstChunk) {
firstChunk = false;
}
else
{
// Each chunk terminated by '\r\n'
buffer->sbumpc();
buffer->sbumpc();
}
int next;
while ((next = buffer->sbumpc()) != traits::eof())
{
if (next == '\r')
{
next = buffer->sbumpc(); // Check it is '\n'
break;
}
next = (next >= 'a' && next <= 'f') ? next - 'a' + 10
: (next >= 'A' && next <= 'F') ? next - 'A' + 10
: (next >= '0' && next <= '9') ? next - '0'
: 0;
remaining = (remaining * 16) + next;
}
if (remaining == 0)
{
chunked = false;
complete();
}
}
asked Oct 20, 2024 at 20:50
lang-cpp