blob: 23c80974c38aa9043d533b39b0126055b0ff2d99 [file] [log] [blame]
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License
#ifndef __ENCODER_HPP__
#define __ENCODER_HPP__
#include <stdint.h>
#include <time.h>
#include <limits>
#include <map>
#include <sstream>
#include <process/http.hpp>
#include <process/process.hpp>
#include <stout/foreach.hpp>
#include <stout/gzip.hpp>
#include <stout/hashmap.hpp>
#include <stout/numify.hpp>
#include <stout/os.hpp>
namespace process {
const uint32_t GZIP_MINIMUM_BODY_LENGTH = 1024;
class Encoder
{
public:
enum Kind
{
DATA,
FILE
};
Encoder() = default;
virtual ~Encoder() {}
virtual Kind kind() const = 0;
virtual void backup(size_t length) = 0;
virtual size_t remaining() const = 0;
};
class DataEncoder : public Encoder
{
public:
DataEncoder(const std::string& _data)
: data(_data), index(0) {}
DataEncoder(std::string&& _data)
: data(std::move(_data)), index(0) {}
~DataEncoder() override {}
Kind kind() const override
{
return Encoder::DATA;
}
virtual const char* next(size_t* length)
{
size_t temp = index;
index = data.size();
*length = data.size() - temp;
return data.data() + temp;
}
void backup(size_t length) override
{
if (index >= length) {
index -= length;
}
}
size_t remaining() const override
{
return data.size() - index;
}
private:
const std::string data;
size_t index;
};
class MessageEncoder : public DataEncoder
{
public:
MessageEncoder(const Message& message)
: DataEncoder(encode(message)) {}
static std::string encode(const Message& message)
{
std::ostringstream out;
out << "POST ";
// Nothing keeps the 'id' component of a PID from being an empty
// string which would create a malformed path that has two
// '//' unless we check for it explicitly.
// TODO(benh): Make the 'id' part of a PID optional so when it's
// missing it's clear that we're simply addressing an ip:port.
if (message.to.id != "") {
out << "/" << message.to.id;
}
out << "/" << message.name << " HTTP/1.1\r\n"
<< "User-Agent: libprocess/" << message.from << "\r\n"
<< "Libprocess-From: " << message.from << "\r\n"
<< "Connection: Keep-Alive\r\n"
<< "Host: " << message.to.address << "\r\n";
if (message.body.size() > 0) {
out << "Transfer-Encoding: chunked\r\n\r\n"
<< std::hex << message.body.size() << "\r\n";
out.write(message.body.data(), message.body.size());
out << "\r\n"
<< "0\r\n"
<< "\r\n";
} else {
out << "\r\n";
}
return out.str();
}
};
class HttpResponseEncoder : public DataEncoder
{
public:
HttpResponseEncoder(
const http::Response& response,
const http::Request& request)
: DataEncoder(encode(response, request)) {}
static std::string encode(
const http::Response& response,
const http::Request& request)
{
std::ostringstream out;
// TODO(benh): Check version?
out << "HTTP/1.1 " << response.status << "\r\n";
auto headers = response.headers;
// HTTP 1.1 requires the "Date" header. In the future once we
// start checking the version (above) then we can conditionally
// add this header, but for now, we always do.
time_t rawtime;
time(&rawtime);
char date[256];
tm tm_;
PCHECK(os::gmtime_r(&rawtime, &tm_) != nullptr)
<< "Failed to convert the current time to a tm struct "
<< "using os::gmtime_r()";
// TODO(benh): Check return code of strftime!
strftime(date, 256, "%a, %d %b %Y %H:%M:%S GMT", &tm_);
headers["Date"] = date;
// Should we compress this response?
std::string body = response.body;
if (response.type == http::Response::BODY &&
response.body.length() >= GZIP_MINIMUM_BODY_LENGTH &&
!headers.contains("Content-Encoding") &&
request.acceptsEncoding("gzip")) {
Try<std::string> compressed = gzip::compress(body);
if (compressed.isError()) {
LOG(WARNING) << "Failed to gzip response body: " << compressed.error();
} else {
body = std::move(compressed.get());
headers["Content-Length"] = stringify(body.length());
headers["Content-Encoding"] = "gzip";
}
}
foreachpair (const std::string& key, const std::string& value, headers) {
out << key << ": " << value << "\r\n";
}
// Add a Content-Length header if the response is of type "none"
// or "body" and no Content-Length header has been supplied.
if (response.type == http::Response::NONE &&
!headers.contains("Content-Length")) {
out << "Content-Length: 0\r\n";
} else if (response.type == http::Response::BODY &&
!headers.contains("Content-Length")) {
out << "Content-Length: " << body.size() << "\r\n";
}
// Use a CRLF to mark end of headers.
out << "\r\n";
// Add the body if necessary.
if (response.type == http::Response::BODY) {
// If the Content-Length header was supplied, only write as much data
// as the length specifies.
Result<uint32_t> length = numify<uint32_t>(headers.get("Content-Length"));
if (length.isSome() && length.get() <= body.length()) {
out.write(body.data(), length.get());
} else {
out.write(body.data(), body.size());
}
}
return out.str();
}
};
class FileEncoder : public Encoder
{
public:
FileEncoder(int_fd _fd, size_t _size)
: fd(_fd), size(static_cast<off_t>(_size)), index(0)
{
// NOTE: For files, we expect the size to be derived from `stat`-ing
// the file. The `struct stat` returns the size in `off_t` form,
// meaning that it is a programmer error to construct the `FileEncoder`
// with a size greater the max value of `off_t`.
CHECK_LE(_size, static_cast<size_t>(std::numeric_limits<off_t>::max()));
}
~FileEncoder() override
{
CHECK_SOME(os::close(fd)) << "Failed to close file descriptor";
}
Kind kind() const override
{
return Encoder::FILE;
}
virtual int_fd next(off_t* offset, size_t* length)
{
off_t temp = index;
index = size;
*offset = temp;
*length = size - temp;
return fd;
}
void backup(size_t length) override
{
if (index >= static_cast<off_t>(length)) {
index -= static_cast<off_t>(length);
}
}
size_t remaining() const override
{
return static_cast<size_t>(size - index);
}
private:
int_fd fd;
off_t size;
off_t index;
};
} // namespace process {
#endif // __ENCODER_HPP__