Skip to content

Commit

Permalink
[k2] refactor http stuff (#1200)
Browse files Browse the repository at this point in the history
mainly put all the private APIs under kphp::http namespace
  • Loading branch information
apolyakov authored Jan 10, 2025
1 parent b3dc973 commit 4c27b83
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 56 deletions.
39 changes: 24 additions & 15 deletions runtime-light/server/http/http-server-state.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,28 @@
#include "runtime-light/allocator/allocator.h"
#include "runtime-light/core/std/containers.h"

enum class HttpMethod : uint8_t { GET, POST, HEAD, OTHER };
namespace kphp {

namespace HttpHeader {
namespace http {

enum class method : uint8_t { get, post, head, other };

enum class connection_kind : uint8_t { keep_alive, close };

enum status : uint16_t {
NO_STATUS = 0,
OK = 200,
CREATED = 201,
MULTIPLE_CHOICES = 300,
FOUND = 302,
BAD_REQUEST = 400,
};

namespace headers {

inline constexpr std::string_view HOST = "host";
inline constexpr std::string_view COOKIE = "cookie";
inline constexpr std::string_view LOCATION = "location";
inline constexpr std::string_view SET_COOKIE = "set-cookie";
inline constexpr std::string_view CONNECTION = "connection";
inline constexpr std::string_view CONTENT_TYPE = "content-type";
Expand All @@ -28,18 +44,11 @@ inline constexpr std::string_view AUTHORIZATION = "authorization";
inline constexpr std::string_view ACCEPT_ENCODING = "accept-encoding";
inline constexpr std::string_view CONTENT_ENCODING = "content-encoding";

} // namespace HttpHeader
} // namespace headers

enum class HttpConnectionKind : uint8_t { KEEP_ALIVE, CLOSE };
} // namespace http

enum HttpStatus : uint16_t {
NO_STATUS = 0,
OK = 200,
CREATED = 201,
MULTIPLE_CHOICES = 300,
FOUND = 302,
BAD_REQUEST = 400,
};
} // namespace kphp

struct HttpServerInstanceState final : private vk::not_copyable {
using header_t = kphp::stl::string<kphp::memory::script_allocator>;
Expand All @@ -51,9 +60,9 @@ struct HttpServerInstanceState final : private vk::not_copyable {
std::optional<string> opt_raw_post_data;

uint32_t encoding{};
uint64_t status_code{HttpStatus::NO_STATUS};
HttpMethod http_method{HttpMethod::OTHER};
HttpConnectionKind connection_kind{HttpConnectionKind::CLOSE};
uint64_t status_code{kphp::http::status::NO_STATUS};
kphp::http::method http_method{kphp::http::method::other};
kphp::http::connection_kind connection_kind{kphp::http::connection_kind::close};

private:
headers_map_t headers_;
Expand Down
58 changes: 33 additions & 25 deletions runtime-light/server/http/init-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,31 +145,31 @@ std::string_view process_headers(tl::K2InvokeHttp &invoke_http, PhpScriptBuiltIn
const std::string_view h_value_view{h_value.c_str(), h_value.size()};

using namespace PhpServerSuperGlobalIndices;
if (h_name_view == HttpHeader::ACCEPT_ENCODING) {
if (h_name_view == kphp::http::headers::ACCEPT_ENCODING) {
if (absl::StrContains(h_value_view, ENCODING_GZIP)) {
http_server_instance_st.encoding |= HttpServerInstanceState::ENCODING_GZIP;
}
if (absl::StrContains(h_value_view, ENCODING_DEFLATE)) {
http_server_instance_st.encoding |= HttpServerInstanceState::ENCODING_DEFLATE;
}
} else if (h_name_view == HttpHeader::CONNECTION) {
} else if (h_name_view == kphp::http::headers::CONNECTION) {
if (h_value_view == CONNECTION_KEEP_ALIVE) [[likely]] {
http_server_instance_st.connection_kind = HttpConnectionKind::KEEP_ALIVE;
http_server_instance_st.connection_kind = kphp::http::connection_kind::keep_alive;
} else if (h_value_view == CONNECTION_CLOSE) [[likely]] {
http_server_instance_st.connection_kind = HttpConnectionKind::CLOSE;
http_server_instance_st.connection_kind = kphp::http::connection_kind::close;
} else {
php_error("unexpected connection header: %s", h_value_view.data());
}
} else if (h_name_view == HttpHeader::COOKIE) {
} else if (h_name_view == kphp::http::headers::COOKIE) {
process_cookie_header(h_value, superglobals);
} else if (h_name_view == HttpHeader::HOST) {
} else if (h_name_view == kphp::http::headers::HOST) {
server.set_value(string{SERVER_NAME.data(), SERVER_NAME.size()}, h_value);
} else if (h_name_view == HttpHeader::AUTHORIZATION) {
} else if (h_name_view == kphp::http::headers::AUTHORIZATION) {
process_authorization_header(h_value, superglobals);
} else if (h_name_view == HttpHeader::CONTENT_TYPE) {
} else if (h_name_view == kphp::http::headers::CONTENT_TYPE) {
content_type = h_value_view;
continue;
} else if (h_name_view == HttpHeader::CONTENT_LENGTH) {
} else if (h_name_view == kphp::http::headers::CONTENT_LENGTH) {
int32_t content_length{};
const auto [_, ec]{std::from_chars(h_value_view.data(), h_value_view.data() + h_value_view.size(), content_length)};
if (ec != std::errc{} || content_length != invoke_http.body.size()) [[unlikely]] {
Expand All @@ -195,21 +195,25 @@ std::string_view process_headers(tl::K2InvokeHttp &invoke_http, PhpScriptBuiltIn

} // namespace

void init_http_server(tl::K2InvokeHttp &&invoke_http) noexcept {
namespace kphp {

namespace http {

void init_server(tl::K2InvokeHttp &&invoke_http) noexcept {
auto &superglobals{InstanceState::get().php_script_mutable_globals_singleton.get_superglobals()};
auto &server{superglobals.v$_SERVER};
auto &http_server_instance_st{HttpServerInstanceState::get()};

{ // determine HTTP method
const std::string_view http_method{invoke_http.method.c_str(), invoke_http.method.size()};
if (http_method == GET_METHOD) {
http_server_instance_st.http_method = HttpMethod::GET;
http_server_instance_st.http_method = method::get;
} else if (http_method == POST_METHOD) {
http_server_instance_st.http_method = HttpMethod::POST;
http_server_instance_st.http_method = method::post;
} else if (http_method == HEAD_METHOD) [[likely]] {
http_server_instance_st.http_method = HttpMethod::HEAD;
http_server_instance_st.http_method = method::head;
} else {
http_server_instance_st.http_method = HttpMethod::OTHER;
http_server_instance_st.http_method = method::other;
}
}

Expand Down Expand Up @@ -267,10 +271,10 @@ void init_http_server(tl::K2InvokeHttp &&invoke_http) noexcept {
server.set_value(string{SCRIPT_URI.data(), SCRIPT_URI.size()}, script_uri);
}

if (http_server_instance_st.http_method == HttpMethod::GET) {
if (http_server_instance_st.http_method == method::get) {
server.set_value(string{ARGC.data(), ARGC.size()}, static_cast<int64_t>(1));
server.set_value(string{ARGV.data(), ARGV.size()}, invoke_http.uri.opt_query.value_or(string{}));
} else if (http_server_instance_st.http_method == HttpMethod::POST) {
} else if (http_server_instance_st.http_method == method::post) {
if (content_type == CONTENT_TYPE_APP_FORM_URLENCODED) {
f$parse_str(invoke_http.body, superglobals.v$_POST);
} else if (content_type == CONTENT_TYPE_MULTIPART_FORM_DATA) {
Expand All @@ -295,18 +299,18 @@ void init_http_server(tl::K2InvokeHttp &&invoke_http) noexcept {

// add content-type header
auto &static_SB{RuntimeContext::get().static_SB};
static_SB.clean() << HttpHeader::CONTENT_TYPE.data() << ": " << CONTENT_TYPE_TEXT_WIN1251.data();
header({static_SB.c_str(), static_SB.size()}, true, HttpStatus::NO_STATUS);
static_SB.clean() << headers::CONTENT_TYPE.data() << ": " << CONTENT_TYPE_TEXT_WIN1251.data();
header({static_SB.c_str(), static_SB.size()}, true, status::NO_STATUS);
// add connection kind header
const auto connection_kind{http_server_instance_st.connection_kind == HttpConnectionKind::KEEP_ALIVE ? CONNECTION_KEEP_ALIVE : CONNECTION_CLOSE};
static_SB.clean() << HttpHeader::CONNECTION.data() << ": " << connection_kind.data();
const auto connection_kind{http_server_instance_st.connection_kind == connection_kind::keep_alive ? CONNECTION_KEEP_ALIVE : CONNECTION_CLOSE};
static_SB.clean() << headers::CONNECTION.data() << ": " << connection_kind.data();
}

task_t<void> finalize_http_server(const string_buffer &output) noexcept {
task_t<void> finalize_server(const string_buffer &output) noexcept {
auto &http_server_instance_st{HttpServerInstanceState::get()};

string body{};
if (http_server_instance_st.http_method != HttpMethod::HEAD) {
if (http_server_instance_st.http_method != method::head) {
body = output.str();
const bool gzip_encoded{static_cast<bool>(http_server_instance_st.encoding & HttpServerInstanceState::ENCODING_GZIP)};
const bool deflate_encoded{static_cast<bool>(http_server_instance_st.encoding & HttpServerInstanceState::ENCODING_DEFLATE)};
Expand All @@ -318,13 +322,13 @@ task_t<void> finalize_http_server(const string_buffer &output) noexcept {
body = std::move(*encoded_body);

auto &static_SB{RuntimeContext::get().static_SB};
static_SB.clean() << HttpHeader::CONTENT_ENCODING.data() << ": " << (gzip_encoded ? ENCODING_GZIP.data() : ENCODING_DEFLATE.data());
header({static_SB.c_str(), static_SB.size()}, true, HttpStatus::NO_STATUS);
static_SB.clean() << headers::CONTENT_ENCODING.data() << ": " << (gzip_encoded ? ENCODING_GZIP.data() : ENCODING_DEFLATE.data());
header({static_SB.c_str(), static_SB.size()}, true, status::NO_STATUS);
}
}
}

const auto status_code{http_server_instance_st.status_code == HttpStatus::NO_STATUS ? HttpStatus::OK : http_server_instance_st.status_code};
const auto status_code{http_server_instance_st.status_code == status::NO_STATUS ? status::OK : http_server_instance_st.status_code};
tl::httpResponse http_response{.version = tl::HttpVersion{.version = tl::HttpVersion::Version::V11},
.status_code = static_cast<int32_t>(status_code),
.headers = {},
Expand All @@ -348,3 +352,7 @@ task_t<void> finalize_http_server(const string_buffer &output) noexcept {
php_warning("can't write component result to stream %" PRIu64, instance_st.standard_stream());
}
}

} // namespace http

} // namespace kphp
12 changes: 10 additions & 2 deletions runtime-light/server/http/init-functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
#include "runtime-light/coroutine/task.h"
#include "runtime-light/tl/tl-functions.h"

void init_http_server(tl::K2InvokeHttp &&invoke_http) noexcept;
namespace kphp {

task_t<void> finalize_http_server(const string_buffer &output) noexcept;
namespace http {

void init_server(tl::K2InvokeHttp &&invoke_http) noexcept;

task_t<void> finalize_server(const string_buffer &output) noexcept;

} // namespace http

} // namespace kphp
4 changes: 2 additions & 2 deletions runtime-light/server/init-functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ using ServerQuery = std::variant<tl::K2InvokeHttp, tl::K2InvokeJobWorker>;

inline void init_server(ServerQuery &&query) noexcept {
static constexpr std::string_view SERVER_SOFTWARE_VALUE = "K2/KPHP";
static constexpr std::string_view SERVER_SIGNATURE_VALUE = "K2/KPHP Server v0.0.0";
static constexpr std::string_view SERVER_SIGNATURE_VALUE = "K2/KPHP Server v0.0.1";

// common initialization
{
Expand All @@ -34,7 +34,7 @@ inline void init_server(ServerQuery &&query) noexcept {
using query_t = std::remove_cvref_t<decltype(query)>;

if constexpr (std::same_as<query_t, tl::K2InvokeHttp>) {
init_http_server(std::forward<decltype(query)>(query));
kphp::http::init_server(std::forward<decltype(query)>(query));
} else if constexpr (std::same_as<query_t, tl::K2InvokeJobWorker>) {
init_job_server(std::forward<decltype(query)>(query));
} else {
Expand Down
2 changes: 1 addition & 1 deletion runtime-light/state/init-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,6 @@ task_t<uint64_t> init_kphp_server_component() noexcept {

task_t<void> finalize_kphp_server_component(const string_buffer &output) noexcept {
if (JobWorkerServerInstanceState::get().kind == JobWorkerServerInstanceState::Kind::Invalid) {
co_await finalize_http_server(output); // http server case
co_await kphp::http::finalize_server(output);
}
}
33 changes: 24 additions & 9 deletions runtime-light/stdlib/server/http-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,24 @@ namespace {

constexpr std::string_view HTTP_DATE = R"(D, d M Y H:i:s \G\M\T)";
constexpr std::string_view HTTP_STATUS_PREFIX = "http/";
constexpr std::string_view HTTP_LOCATION_HEADER_PREFIX = "location:";

bool http_status_header(std::string_view header) noexcept {
const auto lowercase_prefix{header | std::views::take(HTTP_STATUS_PREFIX.size()) | std::views::transform([](auto c) noexcept { return std::tolower(c); })};
return std::ranges::equal(lowercase_prefix, HTTP_STATUS_PREFIX);
}

bool http_location_header(std::string_view header) noexcept {
const auto lowercase_prefix{header | std::views::take(HTTP_LOCATION_HEADER_PREFIX.size())
{
const std::pair<std::string_view, std::string_view> parts{absl::StrSplit(header, absl::MaxSplits(':', 1))};
auto [name_view, value_view]{parts};
if (name_view.size() + value_view.size() + 1 != header.size()) [[unlikely]] {
return false;
}
}

const auto lowercase_prefix{header | std::views::take(kphp::http::headers::LOCATION.size())
| std::views::transform([](auto c) noexcept { return std::tolower(c); })};
return std::ranges::equal(lowercase_prefix, HTTP_LOCATION_HEADER_PREFIX);
return std::ranges::equal(lowercase_prefix, kphp::http::headers::LOCATION);
}

std::optional<uint64_t> valid_http_status_header(std::string_view header) noexcept {
Expand Down Expand Up @@ -91,6 +98,10 @@ std::optional<uint64_t> valid_http_status_header(std::string_view header) noexce

} // namespace

namespace kphp {

namespace http {

void header(std::string_view header_view, bool replace, int64_t response_code) noexcept {
if (response_code < 0) [[unlikely]] {
return php_warning("http response code can't be negative: %" PRIi64, response_code);
Expand Down Expand Up @@ -128,22 +139,26 @@ void header(std::string_view header_view, bool replace, int64_t response_code) n

// Location: special case
const bool can_return_redirect{
response_code == HttpStatus::NO_STATUS && http_server_instance_st.status_code != HttpStatus::CREATED
&& (http_server_instance_st.status_code < HttpStatus::MULTIPLE_CHOICES || http_server_instance_st.status_code >= HttpStatus::BAD_REQUEST)};
response_code == status::NO_STATUS && http_server_instance_st.status_code != status::CREATED
&& (http_server_instance_st.status_code < status::MULTIPLE_CHOICES || http_server_instance_st.status_code >= status::BAD_REQUEST)};
if (can_return_redirect && http_location_header(header_view)) {
http_server_instance_st.status_code = HttpStatus::FOUND;
http_server_instance_st.status_code = status::FOUND;
}

if (!header_view.empty() && response_code != HttpStatus::NO_STATUS) {
if (!header_view.empty() && response_code != status::NO_STATUS) {
http_server_instance_st.status_code = response_code;
}
}

} // namespace http

} // namespace kphp

void f$setrawcookie(const string &name, const string &value, int64_t expire_or_options, const string &path, const string &domain, bool secure,
bool http_only) noexcept {
auto &static_SB_spare{RuntimeContext::get().static_SB_spare};

static_SB_spare.clean() << HttpHeader::SET_COOKIE.data() << ": " << name << '=';
static_SB_spare.clean() << kphp::http::headers::SET_COOKIE.data() << ": " << name << '=';
if (value.empty()) {
static_SB_spare << "DELETED; expires=Thu, 01 Jan 1970 00:00:01 GMT";
} else {
Expand All @@ -164,5 +179,5 @@ void f$setrawcookie(const string &name, const string &value, int64_t expire_or_o
if (http_only) {
static_SB_spare << "; HttpOnly";
}
header({static_SB_spare.c_str(), static_SB_spare.size()}, false, HttpStatus::NO_STATUS);
kphp::http::header({static_SB_spare.c_str(), static_SB_spare.size()}, false, kphp::http::status::NO_STATUS);
}
12 changes: 10 additions & 2 deletions runtime-light/stdlib/server/http-functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,18 @@
#include "runtime-common/stdlib/server/url-functions.h"
#include "runtime-light/server/http/http-server-state.h"

namespace kphp {

namespace http {

void header(std::string_view header, bool replace, int64_t response_code) noexcept;

inline void f$header(const string &str, bool replace = true, int64_t response_code = HttpStatus::NO_STATUS) noexcept {
header({str.c_str(), str.size()}, replace, response_code);
} // namespace http

} // namespace kphp

inline void f$header(const string &str, bool replace = true, int64_t response_code = kphp::http::status::NO_STATUS) noexcept {
kphp::http::header({str.c_str(), str.size()}, replace, response_code);
}

void f$setrawcookie(const string &name, const string &value, int64_t expire_or_options = 0, const string &path = {}, const string &domain = {},
Expand Down

0 comments on commit 4c27b83

Please sign in to comment.