Commit ad8e24eb authored by octal's avatar octal

Major rework of streams.

An Http Response is now written in two phases:
   - First the headers are written, and then a ResponseWriter is
     returned once all headers have been written.
   - A ResponseWriter is basically a wrapper around a NetworkStream
     which is a streambuf, allowing it to be used with an ostream object
   - Once a ResponseWriter has been constructed, headers become
     read-only and can not be written anymore
parent 8e34663b
......@@ -18,8 +18,13 @@ class MyHandler : public Net::Http::Handler {
.add(std::make_shared<Header::Server>("lys"))
.add(std::make_shared<Header::ContentType>(MIME(Text, Plain)));
response.send(Net::Http::Code::Ok, "PONG").then([](ssize_t bytes) {
//std::cout << "Sent total of " << bytes << " bytes" << std::endl;
auto w = response.writer(Net::Http::Code::Ok);
std::ostream os(w);
os << "PONG";
w.send().then([](ssize_t bytes) {
std::cout << "Sent total of " << bytes << " bytes" << std::endl;
}, Async::IgnoreException);
}
......
......@@ -207,21 +207,23 @@ namespace Private {
if (bytesRead > 0) {
// How many bytes do we still need to read ?
const size_t remaining = contentLength - bytesRead;
auto start = cursor;
StreamCursor::Token token(cursor);
const size_t available = cursor.remaining();
// Could be refactored in a single function / lambda but I'm too lazy
// for that right now
if (!cursor.advance(remaining)) {
const size_t available = cursor.remaining();
if (available < remaining) {
cursor.advance(available);
request->body_ += token.text();
request->body_.append(cursor.offset(start), available);
bytesRead += available;
cursor.advance(available);
return State::Again;
}
else {
request->body_.append(cursor.offset(), remaining);
cursor.advance(remaining);
request->body_ += token.text();
}
}
......@@ -231,20 +233,18 @@ namespace Private {
request->body_.reserve(contentLength);
auto start = cursor;
StreamCursor::Token token(cursor);
const size_t available = cursor.remaining();
// We have an incomplete body, read what we can
if (!cursor.advance(contentLength)) {
const size_t available = cursor.remaining();
request->body_.append(cursor.offset(start), available);
bytesRead += available;
if (available < contentLength) {
cursor.advance(available);
request->body_ += token.text();
bytesRead += available;
return State::Again;
}
request->body_.append(cursor.offset(start), contentLength);
cursor.advance(contentLength);
request->body_ = token.text();
}
bytesRead = 0;
......@@ -348,115 +348,52 @@ Request::query() const {
return query_;
}
Response::Response()
: Message()
, bufSize_(Const::MaxBuffer << 1)
, buffer_(new char[bufSize_])
{ }
Response::Response(Response&& other)
: peer_(other.peer_)
, bufSize_(other.bufSize_)
, buffer_(std::move(other.buffer_))
{ }
Response&
Response::operator=(Response&& other) {
peer_ = other.peer_;
bufSize_ = other.bufSize_;
buffer_ = std::move(other.buffer_);
return *this;
void
ResponseWriter::writeStatusLine() {
}
void
Response::associatePeer(const std::shared_ptr<Tcp::Peer>& peer)
{
if (peer_.use_count() > 0)
throw std::runtime_error("A peer was already associated to the response");
ResponseWriter::writeHeaders() {
std::ostream os(&stream_);
peer_ = peer;
}
Async::Promise<ssize_t>
Response::send(Code code) {
return send(code, "");
}
Async::Promise<ssize_t>
Response::send(Code code, const std::string& body, const Mime::MediaType& mime)
ResponseWriter::send()
{
auto body = stream_.buffer();
char *beg = buffer_.get();
Io::OutArrayBuf obuf(beg, beg + bufSize_, Io::Init::ZeroOut);
NetworkStream stream(512 + body.len);
std::ostream os(&stream);
std::ostream stream(&obuf);
#define OUT(...) \
do { \
__VA_ARGS__; \
if (!stream) { \
return Async::Promise<ssize_t>::rejected(Net::Error("Could not write to stream: insufficient space")); \
if (!os) { \
return Async::Promise<ssize_t>::rejected(Error("Response exceeded buffer size")); \
} \
} while (0);
OUT(stream << "HTTP/1.1 ");
OUT(stream << static_cast<int>(code));
OUT(stream << ' ');
OUT(stream << code);
OUT(stream << crlf);
if (mime.isValid()) {
auto contentType = headers_.tryGet<Header::ContentType>();
if (contentType)
contentType->setMime(mime);
else {
OUT(writeHeader<Header::ContentType>(stream, mime));
}
}
OUT(os << "HTTP/1.1 ");
OUT(os << static_cast<int>(code_));
OUT(os << ' ');
OUT(os << code_);
OUT(os << crlf);
for (const auto& header: headers_.list()) {
OUT(stream << header->name() << ": ");
OUT(header->write(stream));
OUT(stream << crlf);
}
if (!body.empty()) {
OUT(writeHeader<Header::ContentLength>(stream, body.size()));
OUT(stream << crlf);
OUT(stream << body);
}
else {
OUT(stream << crlf);
OUT(os << header->name() << ": ");
OUT(header->write(os));
OUT(os << crlf);
}
#undef OUT
return peer()->send(buffer_.get(), obuf.len());
}
void
Response::setMime(const Mime::MediaType& mime)
{
headers_.add(std::make_shared<Header::ContentType>(mime));
}
Header::Collection&
Response::headers() {
return headers_;
}
OUT(writeHeader<Header::ContentLength>(os, body.len));
OUT(os << crlf);
const Header::Collection&
Response::headers() const {
return headers_;
}
OUT(os.write(static_cast<const char *>(body.data), body.len));
std::shared_ptr<Tcp::Peer>
Response::peer() const {
if (peer_.expired()) {
throw std::runtime_error("Broken pipe");
}
auto buf = stream.buffer();
return peer_.lock();
return peer()->send(buf.data, buf.len);
}
void
......
......@@ -9,6 +9,7 @@
#include <type_traits>
#include <stdexcept>
#include <array>
#include <sstream>
#include "listener.h"
#include "net.h"
#include "http_headers.h"
......@@ -32,7 +33,15 @@ namespace Private {
class Message {
public:
Message();
Message(const Message& other) = default;
Message& operator=(const Message& other) = default;
Message(Message&& other) = default;
Message& operator=(Message&& other) = default;
Version version_;
Code code_;
Header::Collection headers_;
std::string body_;
......@@ -81,39 +90,163 @@ private:
class Handler;
class ResponseWriter : private Message {
public:
ResponseWriter(const ResponseWriter& other) = delete;
ResponseWriter& operator=(const ResponseWriter& other) = delete;
ResponseWriter(ResponseWriter&& other)
: Message(std::move(other))
, peer_(std::move(other.peer_))
, stream_(std::move(other.stream_))
{ }
ResponseWriter& operator=(ResponseWriter&& other) {
Message::operator=(std::move(other));
peer_ = std::move(other.peer_);
stream_ = std::move(other.stream_);
return *this;
}
friend class Response;
const Header::Collection& headers() const {
return headers_;
}
Code code() const {
return code_;
}
std::streambuf* rdbuf() {
return &stream_;
}
operator std::streambuf*() {
return &stream_;
}
Async::Promise<ssize_t> send();
private:
ResponseWriter(Message&& other, size_t size, std::weak_ptr<Tcp::Peer> peer)
: Message(std::move(other))
, stream_(size)
, peer_(peer)
{
}
ResponseWriter(const Message& other, size_t size, std::weak_ptr<Tcp::Peer> peer)
: Message(other)
, stream_(size)
, peer_(peer)
{
}
std::shared_ptr<Tcp::Peer> peer() const {
if (peer_.expired()) {
throw std::runtime_error("Broken pipe");
}
return peer_.lock();
}
void writeStatusLine();
void writeHeaders();
NetworkStream stream_;
std::weak_ptr<Tcp::Peer> peer_;
};
// 6. Response
class Response : private Message {
public:
friend class Handler;
Response(const Response& other) = delete;
Response& operator=(const Response& other) = delete;
static constexpr size_t DefaultStreamSize = 512;
// C++11: std::weak_ptr move constructor is C++14 only so the default
// version of move constructor / assignement operator does not work and we
// have to define it ourself
Response(Response&& other);
Response& operator=(Response&& other);
Response(Response&& other)
: Message(std::move(other))
, peer_(other.peer_)
{ }
Response& operator=(Response&& other) {
peer_ = other.peer_;
return *this;
}
Header::Collection& headers();
const Header::Collection& headers() const;
const Header::Collection& headers() const {
return headers_;
}
Header::Collection& headers() {
return headers_;
}
Code code() const {
return code_;
}
void setMime(const Mime::MediaType& mime);
void setMime(const Mime::MediaType& mime) {
headers().add(std::make_shared<Header::ContentType>(mime));
}
Async::Promise<ssize_t> send(Code code) {
return send(code, "");
}
Async::Promise<ssize_t> send(
Code code,
const std::string& body,
const Mime::MediaType &mime = Mime::MediaType())
{
code_ = code;
if (mime.isValid()) {
auto contentType = headers_.tryGet<Header::ContentType>();
if (contentType)
contentType->setMime(mime);
else
headers_.add(std::make_shared<Header::ContentType>(mime));
}
Async::Promise<ssize_t> send(Code code);
Async::Promise<ssize_t> send(Code code, const std::string& body, const Mime::MediaType &mime = Mime::MediaType());
ResponseWriter w(*this, body.size(), peer_);
std::ostream os(w);
os << body;
if (!os)
return Async::Promise<ssize_t>::rejected(Error("Response exceeded buffer size"));
return w.send();
}
ResponseWriter
writer(Code code, size_t size = DefaultStreamSize) {
code_ = code;
return ResponseWriter(std::move(*this), size, peer_);
}
private:
Response();
Response()
: Message()
{ }
std::shared_ptr<Tcp::Peer> peer() const;
std::shared_ptr<Tcp::Peer> peer() const {
if (peer_.expired()) {
throw std::runtime_error("Broken pipe");
}
void associatePeer(const std::shared_ptr<Tcp::Peer>& peer);
std::weak_ptr<Tcp::Peer> peer_;
return peer_.lock();
}
size_t bufSize_;
std::unique_ptr<char[]> buffer_;
void associatePeer(const std::shared_ptr<Tcp::Peer>& peer) {
if (peer_.use_count() > 0)
throw std::runtime_error("A peer was already associated to the response");
peer_ = peer;
}
std::weak_ptr<Tcp::Peer> peer_;
};
namespace Private {
......@@ -165,7 +298,7 @@ namespace Private {
Parser()
: contentLength(-1)
, currentStep(0)
, cursor(buffer)
, cursor(&buffer)
{
allSteps[0].reset(new RequestLineStep(&request));
allSteps[1].reset(new HeadersStep(&request));
......@@ -175,7 +308,7 @@ namespace Private {
Parser(const char* data, size_t len)
: contentLength(-1)
, currentStep(0)
, cursor(buffer)
, cursor(&buffer)
{
allSteps[0].reset(new RequestLineStep(&request));
allSteps[1].reset(new HeadersStep(&request));
......
......@@ -118,8 +118,8 @@ CacheControl::parseRaw(const char* str, size_t len) {
#undef VALUE
StreamBuf buf(str, len);
StreamCursor cursor(buf);
RawStreamBuf<> buf(const_cast<char *>(str), len);
StreamCursor cursor(&buf);
const char *begin = str;
auto memsize = [&](size_t s) {
......@@ -328,8 +328,8 @@ UserAgent::write(std::ostream& os) const {
void
Accept::parseRaw(const char *str, size_t len) {
StreamBuf buf(str, len);
StreamCursor cursor(buf);
RawStreamBuf<char> buf(const_cast<char *>(str), len);
StreamCursor cursor(&buf);
do {
int c;
......@@ -347,7 +347,7 @@ Accept::parseRaw(const char *str, size_t len) {
if (!cursor.advance(1))
throw std::runtime_error("Ill-formed Accept header");
if ((c = cursor.next()) == StreamCursor::Eof || c == ',')
if ((c = cursor.next()) == StreamCursor::Eof || c == ',' || c == 0)
throw std::runtime_error("Ill-formed Accept header");
while (!cursor.eof() && cursor.current() == ' ')
......
......@@ -60,8 +60,8 @@ MediaType::parseRaw(const char* str, size_t len) {
throw HttpError(Http::Code::Unsupported_Media_Type, str);
};
StreamBuf buf(str, len);
StreamCursor cursor(buf);
RawStreamBuf<char> buf(const_cast<char *>(str), len);
StreamCursor cursor(&buf);
raw_ = string(str, len);
......@@ -156,7 +156,8 @@ MediaType::parseRaw(const char* str, size_t len) {
while (!cursor.eof()) {
if (cursor.current() == ';' || cursor.current() == ' ') {
if (cursor.next() == StreamCursor::Eof)
int c;
if ((c = cursor.next()) == StreamCursor::Eof || c == 0)
raise("Malformed Media Type, expected parameter got EOF");
cursor.advance(1);
}
......@@ -179,7 +180,8 @@ MediaType::parseRaw(const char* str, size_t len) {
StreamCursor::Token keyToken(cursor);
(void) match_until('=', cursor);
if (cursor.eof() || cursor.next() == StreamCursor::Eof)
int c;
if (cursor.eof() || (c = cursor.next()) == StreamCursor::Eof || c == 0)
raise("Unfinished Media Type parameter");
std::string key = keyToken.text();
......
......@@ -7,40 +7,45 @@
#include <algorithm>
#include <iostream>
void
BasicStreamBuf::setArea(const char* begin, const char* end, const char* brk) {
this->begin = begin;
this->end = end;
this->brk = brk;
}
NetworkStream::int_type
NetworkStream::overflow(NetworkStream::int_type ch) {
if (!traits_type::eq_int_type(ch, traits_type::eof())) {
const auto size = data_.size();
if (size < maxSize_) {
reserve(size * 2);
*pptr() = ch;
pbump(1);
return traits_type::not_eof(ch);
}
}
StreamBuf::StreamBuf(const char* begin, const char* end) {
setArea(begin, end, end);
return traits_type::eof();
}
StreamBuf::StreamBuf(const char *begin, const char* end, const char* brk) {
setArea(begin, end, brk);
void
NetworkStream::reserve(size_t size)
{
if (size > maxSize_) size = maxSize_;
const size_t oldSize = data_.size();
data_.resize(size);
this->setp(&data_[0] + oldSize, &data_[0] + size);
}
StreamBuf::StreamBuf(const char* begin, size_t len) {
setArea(begin, begin + len, begin + len);
}
bool
StreamCursor::advance(size_t count) {
if (value + count > buf.totalAvailable())
if (count > buf->in_avail())
return false;
else if (value + count > buf.breakAvailable())
return false;
for (size_t i = 0; i < count; ++i) {
buf->sbumpc();
}
value += count;
return true;
}
bool
StreamCursor::eol() const {
return buf.begin[value] == CR && next() == LF;
return buf->sgetc() == CR && next() == LF;
}
bool
......@@ -50,48 +55,45 @@ StreamCursor::eof() const {
int
StreamCursor::next() const {
if (value + 1 >= buf.totalAvailable())
return Eof;
else if (value + 1 > buf.breakAvailable())
if (buf->in_avail() < 1)
return Eof;
return buf.begin[value + 1];
return buf->snext();
}
char
StreamCursor::current() const {
return buf.begin[value];
return buf->sgetc();
}
const char*
StreamCursor::offset() const {
return buf.begin + value;
return buf->curptr();
}
const char*
StreamCursor::offset(size_t off) const {
return buf.begin + off;
return buf->begptr() + off;
}
size_t
StreamCursor::diff(size_t other) const {
return value - other;
return buf->position() - other;
}
size_t
StreamCursor::diff(const StreamCursor& other) const {
return other.value - value;
return other.buf->position() - buf->position();
}
size_t
StreamCursor::remaining() const {
return buf.breakAvailable() - value;
return buf->in_avail();
}
void
StreamCursor::reset() {
value = 0;
buf->reset();
}
bool
......
......@@ -9,43 +9,78 @@
#include <cstddef>
#include <stdexcept>
#include <cstring>
#include <streambuf>
#include <vector>
#include <limits>
static constexpr char CR = 0xD;
static constexpr char LF = 0xA;
class BasicStreamBuf {
template<typename CharT = char>
class StreamBuf : public std::basic_streambuf<CharT> {
public:
friend class StreamCursor;
typedef std::basic_streambuf<CharT> Base;
typedef typename Base::traits_type traits_type;
size_t totalAvailable() const { return end - begin; }
size_t breakAvailable() const { return brk - begin; }
void setArea(char* begin, char *current, char *end) {
this->setg(begin, current, end);
}
protected:
BasicStreamBuf() { }
CharT *begptr() const {
return this->eback();
}
void setArea(const char *begin, const char *end, const char* brk);
CharT* curptr() const {
return this->gptr();
}
CharT* endptr() const {
return this->egptr();
}
size_t position() const {
return this->gptr() - this->eback();
}
void reset() {
this->setg(nullptr, nullptr, nullptr);
}
typename Base::int_type snext() const {
if (this->gptr() == this->egptr()) {
return traits_type::eof();
}
const CharT* gptr = this->gptr();
return *(gptr + 1);
}
private:
const char *begin;
const char *brk;
const char *end;
};
class StreamBuf : public BasicStreamBuf {
template<typename CharT = char>
class RawStreamBuf : public StreamBuf<CharT> {
public:
StreamBuf(const char* begin, const char* end);
StreamBuf(const char *begin, const char* end, const char* brk);
StreamBuf(const char* begin, size_t len);
typedef StreamBuf<CharT> Base;
RawStreamBuf(char* begin, char* end) {
Base::setg(begin, begin, end);
}
RawStreamBuf(char* begin, size_t len) {
Base::setg(begin, begin, begin + len);
}
};
template<size_t N>
class ArrayStreamBuf : public BasicStreamBuf {
template<size_t N, typename CharT = char>
class ArrayStreamBuf : public StreamBuf<CharT> {
public:
typedef StreamBuf<CharT> Base;
ArrayStreamBuf()
: size(0)
{
memset(bytes, 0, N);
setArea(bytes, bytes, bytes);
Base::setg(bytes, bytes, bytes);
}
template<size_t M>
......@@ -53,7 +88,7 @@ public:
static_assert(M <= N, "Source array exceeds maximum capacity");
memcpy(bytes, arr, M);
size = M;
setArea(bytes, bytes + N, bytes + M);
Base::setg(bytes, bytes, bytes + M);
}
bool feed(const char* data, size_t len) {
......@@ -62,15 +97,15 @@ public:
}
memcpy(bytes + size, data, len);
Base::setg(bytes, bytes + size, bytes + size + len);
size += len;
setArea(bytes, bytes + N, bytes + size);
return true;
}
void reset() {
memset(bytes, 0, N);
size = 0;
setArea(bytes, bytes, bytes);
Base::setg(bytes, bytes, bytes);
}
private:
......@@ -78,58 +113,123 @@ private:
size_t size;
};
class NetworkStream : public StreamBuf<char> {
public:
struct Buffer {
Buffer(const void *data, size_t len)
: data(data)
, len(len)
{ }
const void* data;
const size_t len;
};
typedef StreamBuf<char> Base;
typedef typename Base::traits_type traits_type;
typedef typename Base::int_type int_type;
NetworkStream(
size_t size,
size_t maxSize = std::numeric_limits<uint32_t>::max())
: maxSize_(maxSize)
{
reserve(size);
}
NetworkStream(const NetworkStream& other) = delete;
NetworkStream& operator=(const NetworkStream& other) = delete;
NetworkStream(NetworkStream&& other)
: maxSize_(other.maxSize_)
, data_(std::move(other.data_)) {
setp(other.pptr(), other.epptr());
other.setp(nullptr, nullptr);
}
NetworkStream& operator=(NetworkStream&& other) {
maxSize_ = other.maxSize_;
data_ = std::move(other.data_);
setp(other.pptr(), other.epptr());
other.setp(nullptr, nullptr);
return *this;
}
Buffer buffer() const {
return Buffer(data_.data(), pptr() - &data_[0]);
}
protected:
int_type overflow(int_type ch);
private:
void reserve(size_t size);
size_t maxSize_;
std::vector<char> data_;
};
class StreamCursor {
public:
StreamCursor(const BasicStreamBuf& buf, size_t initialPos = 0)
StreamCursor(StreamBuf<char>* buf, size_t initialPos = 0)
: buf(buf)
, value(initialPos)
{ }
{
advance(initialPos);
}
static constexpr int Eof = -1;
struct Token {
Token(StreamCursor& cursor)
: cursor(cursor)
, pos(cursor.value)
, position(cursor.buf->position())
, eback(cursor.buf->begptr())
, gptr(cursor.buf->curptr())
, egptr(cursor.buf->endptr())
{ }
size_t start() const { return pos; }
size_t start() const { return position; }
size_t end() const {
return cursor.value;
return cursor.buf->position();
}
size_t size() const {
return cursor.value - pos;
return end() - start();
}
std::string text() {
const char *beg = cursor.offset(pos);
return std::string(beg, size());
return std::string(gptr, size());
}
const char* rawText() const {
return cursor.offset(pos);
return gptr;
}
private:
StreamCursor& cursor;
size_t pos;
size_t position;
char *eback;
char *gptr;
char *egptr;
};
struct Revert {
Revert(StreamCursor& cursor)
: cursor(cursor)
, pos(cursor.value)
, eback(cursor.buf->begptr())
, gptr(cursor.buf->curptr())
, egptr(cursor.buf->endptr())
, active(true)
{ }
~Revert() {
if (active)
cursor.value = pos;
revert();
}
void revert() {
cursor.value = pos;
cursor.buf->setArea(eback, gptr, egptr);
}
void ignore() {
......@@ -138,12 +238,15 @@ public:
private:
StreamCursor& cursor;
size_t pos;
char *eback;
char *gptr;
char *egptr;
bool active;
};
bool advance(size_t count);
operator size_t() const { return value; }
operator size_t() const { return buf->position(); }
bool eol() const;
bool eof() const;
......@@ -160,11 +263,11 @@ public:
void reset();
private:
const BasicStreamBuf& buf;
size_t value;
public:
StreamBuf<char>* buf;
};
enum class CaseSensitivity {
Sensitive, Insensitive
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment