nghttpx: Refactor API downstream connection to allow more endpoints

This commit is contained in:
Tatsuhiro Tsujikawa 2017-02-19 22:30:31 +09:00
parent 0797e89a90
commit dc15832030
2 changed files with 122 additions and 12 deletions

View File

@ -33,8 +33,27 @@
namespace shrpx {
namespace {
// List of API endpoints
const APIEndpoint apis[] = {
APIEndpoint{
StringRef::from_lit("/api/v1beta1/backendconfig"), true,
(1 << API_METHOD_POST) | (1 << API_METHOD_PUT),
&APIDownstreamConnection::handle_backendconfig,
},
};
} // namespace
namespace {
// The method string. This must be same order of APIMethod.
constexpr StringRef API_METHOD_STRING[] = {
StringRef::from_lit("GET"), StringRef::from_lit("POST"),
StringRef::from_lit("PUT"),
};
} // namespace
APIDownstreamConnection::APIDownstreamConnection(Worker *worker)
: worker_(worker), abandoned_(false) {}
: worker_(worker), api_(nullptr), shutdown_read_(false) {}
APIDownstreamConnection::~APIDownstreamConnection() {}
@ -64,7 +83,7 @@ enum {
int APIDownstreamConnection::send_reply(unsigned int http_status,
int api_status) {
abandoned_ = true;
shutdown_read_ = true;
auto upstream = downstream_->get_upstream();
@ -130,23 +149,45 @@ int APIDownstreamConnection::send_reply(unsigned int http_status,
int APIDownstreamConnection::push_request_headers() {
auto &req = downstream_->request();
auto &resp = downstream_->response();
auto path =
StringRef{std::begin(req.path),
std::find(std::begin(req.path), std::end(req.path), '?')};
if (path != StringRef::from_lit("/api/v1beta1/backendconfig")) {
for (auto &p : apis) {
if (p.path == path) {
api_ = &p;
break;
}
}
if (!api_) {
send_reply(404, API_FAILURE);
return 0;
}
if (req.method != HTTP_POST && req.method != HTTP_PUT) {
resp.fs.add_header_token(StringRef::from_lit("allow"),
StringRef::from_lit("POST, PUT"), false, -1);
send_reply(405, API_FAILURE);
switch (req.method) {
case HTTP_GET:
if (!(api_->allowed_methods & (1 << API_METHOD_GET))) {
error_method_not_allowed();
return 0;
}
break;
case HTTP_POST:
if (!(api_->allowed_methods & (1 << API_METHOD_POST))) {
error_method_not_allowed();
return 0;
}
break;
case HTTP_PUT:
if (!(api_->allowed_methods & (1 << API_METHOD_PUT))) {
error_method_not_allowed();
return 0;
}
break;
default:
error_method_not_allowed();
return 0;
}
@ -161,9 +202,42 @@ int APIDownstreamConnection::push_request_headers() {
return 0;
}
int APIDownstreamConnection::error_method_not_allowed() {
auto &resp = downstream_->response();
size_t len = 0;
for (uint8_t i = 0; i < API_METHOD_MAX; ++i) {
if (api_->allowed_methods & (1 << i)) {
// The length of method + ", "
len += API_METHOD_STRING[i].size() + 2;
}
}
assert(len > 0);
auto &balloc = downstream_->get_block_allocator();
auto iov = make_byte_ref(balloc, len + 1);
auto p = iov.base;
for (uint8_t i = 0; i < API_METHOD_MAX; ++i) {
if (api_->allowed_methods & (1 << i)) {
auto &s = API_METHOD_STRING[i];
p = std::copy(std::begin(s), std::end(s), p);
p = std::copy_n(", ", 2, p);
}
}
p -= 2;
*p = '\0';
resp.fs.add_header_token(StringRef::from_lit("allow"), StringRef{iov.base, p},
false, -1);
return send_reply(405, API_FAILURE);
}
int APIDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
size_t datalen) {
if (abandoned_) {
if (shutdown_read_ || !api_->require_body) {
return 0;
}
@ -187,10 +261,14 @@ int APIDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
}
int APIDownstreamConnection::end_upload_data() {
if (abandoned_) {
if (shutdown_read_) {
return 0;
}
return api_->handler(*this);
}
int APIDownstreamConnection::handle_backendconfig() {
auto output = downstream_->get_request_buf();
std::array<struct iovec, 2> iov;

View File

@ -26,11 +26,36 @@
#define SHRPX_API_DOWNSTREAM_CONNECTION_H
#include "shrpx_downstream_connection.h"
#include "template.h"
using namespace nghttp2;
namespace shrpx {
class Worker;
// If new method is added, don't forget to update API_METHOD_STRING as
// well.
enum APIMethod {
API_METHOD_GET,
API_METHOD_POST,
API_METHOD_PUT,
API_METHOD_MAX,
};
class APIDownstreamConnection;
struct APIEndpoint {
// Endpoint path. It must start with "/api/".
StringRef path;
// true if we evaluate request body.
bool require_body;
// Allowed methods. This is bitwise OR of one or more of (1 <<
// APIMethod value).
uint8_t allowed_methods;
std::function<int(APIDownstreamConnection &)> handler;
};
class APIDownstreamConnection : public DownstreamConnection {
public:
APIDownstreamConnection(Worker *worker);
@ -59,10 +84,17 @@ public:
virtual DownstreamAddr *get_addr() const;
int send_reply(unsigned int http_status, int api_status);
int error_method_not_allowed();
// Handles backendconfig API request.
int handle_backendconfig();
private:
Worker *worker_;
bool abandoned_;
// This points to the requested APIEndpoint struct.
const APIEndpoint *api_;
// true if we stop reading request body.
bool shutdown_read_;
};
} // namespace shrpx