diff --git a/src/shrpx_api_downstream_connection.cc b/src/shrpx_api_downstream_connection.cc index a7e2e25d..46413652 100644 --- a/src/shrpx_api_downstream_connection.cc +++ b/src/shrpx_api_downstream_connection.cc @@ -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 iov; diff --git a/src/shrpx_api_downstream_connection.h b/src/shrpx_api_downstream_connection.h index 5c1dd4eb..2a0c4b1e 100644 --- a/src/shrpx_api_downstream_connection.h +++ b/src/shrpx_api_downstream_connection.h @@ -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 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