0
\$\begingroup\$

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
\$\endgroup\$

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.