Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BreakBeforeBraces: Attach
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# From https://github.com/XiangpengHao/cxx-cmake-example/blob/master/fastly/CMakeLists.txt
cmake_minimum_required(VERSION 3.29)

project(compute-sdk-cpp VERSION 0.3.0 LANGUAGES CXX)
project(compute-sdk-cpp VERSION 0.4.0 LANGUAGES CXX)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is fine for this PR, but I think we should generally leave version bumps to releases.


if(VERBOSE)
set(VERBOSE_FLAG "--verbose")
Expand Down Expand Up @@ -85,6 +85,9 @@ add_custom_command(
target_include_directories(fastly-thin PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_BINARY_DIR}/include)
target_include_directories(fastly-sys PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_BINARY_DIR}/include)

target_link_libraries(fastly-thin PUBLIC fastly-sys-orig fastly::sys)

Expand Down
42 changes: 41 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "fastly-sys"
version = "0.3.0"
version = "0.4.0"
authors = ["Kat Marchán <kat.marchan@fastly.com>"]
edition = "2024"

Expand All @@ -12,6 +12,8 @@ http = "1.3.1"
log = "0.4.27"
log-fastly = "0.11.5"
thiserror = "2.0.12"
esi = "0.6.1"
quick-xml = "0.38.3"

[build-dependencies]
cxx-build = "1.0"
Expand Down
2 changes: 1 addition & 1 deletion Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.

INPUT = README.md CONTRIBUTING.md examples target/release/dist/fastly
INPUT = README.md CONTRIBUTING.md examples include/fastly

# This tag can be used to specify the character encoding of the source files
# that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses
Expand Down
31 changes: 31 additions & 0 deletions examples/esi.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#include "fastly/sdk.h"

int main() {
fastly::log::init_simple("logs", fastly::log::LogLevelFilter::Debug);
auto req{fastly::http::Request::from_client()};

auto bereq = fastly::http::Request(fastly::http::Method::GET,
"https://esi-cpp-demo.edgecompute.app/")
.with_auto_decompress_gzip(true);
auto beresp = bereq.clone_without_body().send("esi-cpp-demo").value();

// Pass in the request made to the backend as a template for fragment
// requests: this ensures that the fragment requests also ask for gzipped
// content and automatically gunzip the response content.
fastly::esi::Processor processor(std::move(bereq));
auto dispatch_fragment_request = [](fastly::http::Request req)
Comment thread
zkat marked this conversation as resolved.
-> std::optional<fastly::esi::PendingFragmentContent> {
auto pending = req.send_async("esi-cpp-demo");
if (pending) {
return fastly::esi::PendingFragmentContent{std::move(*pending)};
} else {
return std::nullopt;
}
};

auto result = processor.process_response(
beresp, std::nullopt, dispatch_fragment_request, std::nullopt);
if (!result) {
fastly::log::error("Failed to process response");
}
}
6 changes: 4 additions & 2 deletions examples/fastly.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ name = "cpp-example"
service_id = ""

[local_server]
backends.fastly.url = "https://www.fastly.com"
backends.wikipedia.url = "https://en.wikipedia.org"
[local_server.backends]
fastly.url = "https://www.fastly.com"
wikipedia.url = "https://en.wikipedia.org"
esi-cpp-demo.url = "https://esi-cpp-demo.edgecompute.app"
20 changes: 20 additions & 0 deletions include/fastly/detail/access_bridge_internals.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#ifndef FASTLY_ACCESS_BRIDGE_INTERNALS_H
#define FASTLY_ACCESS_BRIDGE_INTERNALS_H

#include <fastly/sdk-sys.h>

namespace fastly::detail {
// This type can be used to access the inner `rust::Box` of
// various wrapper types in the C++ SDK.
// It can also be used to construct wrapper types from raw pointers.
// This is intended for internal use only.
struct AccessBridgeInternals {
template <class T> static auto &get(T &obj) { return obj.inner(); }
template <class T> static auto &get(const T &obj) { return obj.inner(); }
template <class T, class U> static auto from_raw(U *ptr) {
return T(rust::Box<U>::from_raw(ptr));
}
};
} // namespace fastly::detail

#endif
30 changes: 30 additions & 0 deletions include/fastly/detail/rust_bridge_tags.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Some types (primarily callback types) must be defined in C++, but depend upon
// types defined in Rust. To break this circular dependency, we define empty
// "tag" structs here in C++ that can be referenced both from C++ and Rust,
// allowing Rust to pass pointers to these types back to C++ without needing to
// know their full definition.
//
// C++ types that implement the tags should inherit from the tag structs and the
// bindings should cast to/from the tag types as necessary.

#ifndef FASTLY_DETAIL_RUST_BRIDGE_TAGS_H
#define FASTLY_DETAIL_RUST_BRIDGE_TAGS_H

namespace fastly::detail::rust_bridge_tags {
namespace esi {
// esi.h:DispatchFragmentRequestFn
struct DispatchFragmentRequestFnTag {
// Ensure that only classes that inherit from this tag can be used as
// DispatchFragmentRequestFn.
protected:
DispatchFragmentRequestFnTag() = default;
};
// esi.h:ProcessFragmentResponseFn
struct ProcessFragmentResponseFnTag {
protected:
ProcessFragmentResponseFnTag() = default;
};
} // namespace esi
} // namespace fastly::detail::rust_bridge_tags

#endif
123 changes: 123 additions & 0 deletions include/fastly/esi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#ifndef FASTLY_ESI_H
#define FASTLY_ESI_H

#include <concepts>
#include <fastly/detail/rust_bridge_tags.h>
#include <fastly/error.h>
#include <fastly/expected.h>
#include <fastly/http/request.h>
#include <fastly/http/response.h>
#include <fastly/sdk-sys.h>
#include <functional>
#include <optional>
#include <string>
#include <utility>
#include <variant>

namespace fastly::esi {
/// Used to configure optional behaviour within the ESI processor.
struct Configuration {
public:
/// Create a new configuration object.
/// \param namespc The namespace to use for ESI tags.
Comment thread
zkat marked this conversation as resolved.
/// \param is_escaped_content Whether to escape content by default.
Configuration(std::string namespc = "esi", bool is_escaped_content = true)
: namespace_(std::move(namespc)),
is_escaped_content_(is_escaped_content) {}

std::string_view get_namespace() const { return namespace_; }
bool is_escaped_content() const { return is_escaped_content_; }

private:
std::string namespace_;
bool is_escaped_content_;
};

/// Content that can be returned from a fragment request dispatcher. This can
/// either be a pending request, a response, or an empty value to indicate that
/// no content is available.
using PendingFragmentContent =
std::variant<http::request::PendingRequest, http::Response, std::monostate>;

/// A callback type used to dispatch requests for ESI fragments.
class DispatchFragmentRequestFn
: public detail::rust_bridge_tags::esi::DispatchFragmentRequestFnTag {
public:
/// The type of the dispatch function.
using function_type =
std::function<std::optional<PendingFragmentContent>(Request)>;

template <std::convertible_to<function_type> F>
DispatchFragmentRequestFn(F &&fn) : fn_(std::forward<F>(fn)) {}

private:
friend detail::AccessBridgeInternals;
auto &inner() const { return fn_; }
function_type fn_;
};

/// A callback type used to process responses from ESI fragment requests.
class ProcessFragmentResponseFn
: public detail::rust_bridge_tags::esi::ProcessFragmentResponseFnTag {
public:
/// The type of the processing function.
using function_type =
std::function<std::optional<Response>(Request &, Response)>;

template <std::convertible_to<function_type> F>
ProcessFragmentResponseFn(F &&fn) : fn_(std::forward<F>(fn)) {}

private:
friend detail::AccessBridgeInternals;
auto &inner() const { return fn_; }
function_type fn_;
};

/// An ESI processor that can process a response containing ESI tags, dispatch
/// requests for fragments, and process the fragment responses.
class Processor {
public:
/// Create a new ESI processor with the given configuration.
Processor(std::optional<Request> original_request_metadata = std::nullopt,
Configuration config = Configuration());

/// Process a response containing ESI tags, optionally using the given
/// callbacks to dispatch requests for fragments and process the fragment
/// responses.
///
/// \param src_document The response containing ESI tags to process.
/// \param client_response_metadata Optional original client request data used
/// for fragment requests.
/// \param dispatch_fragment_request Optional callback to dispatch requests
/// for fragments.
/// \param process_fragment_response Optional callback to process fragment
/// responses.
fastly::expected<void> process_response(
Response &src_document,
std::optional<Response> client_response_metadata = std::nullopt,
std::optional<DispatchFragmentRequestFn> dispatch_fragment_request =
std::nullopt,
std::optional<ProcessFragmentResponseFn> process_fragment_response =
std::nullopt);

/// Process a string containing ESI tags, optionally using the given
/// callbacks to dispatch requests for fragments and process the fragment
/// responses.
/// \param src_document The string containing ESI tags to process.
/// \param dispatch_fragment_request Optional callback to dispatch requests
/// for fragments.
/// \param process_fragment_response Optional callback to process fragment
/// responses.
fastly::expected<std::string> process_document(
const std::string &src_document,
std::optional<DispatchFragmentRequestFn> dispatch_fragment_request =
std::nullopt,
std::optional<ProcessFragmentResponseFn> process_fragment_response =
std::nullopt);

private:
rust::Box<fastly::sys::esi::Processor> processor_;
};

} // namespace fastly::esi
#endif
6 changes: 5 additions & 1 deletion include/fastly/http/request.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <algorithm>
#include <fastly/backend.h>
#include <fastly/detail/access_bridge_internals.h>
#include <fastly/error.h>
#include <fastly/http/body.h>
#include <fastly/http/header.h>
Expand Down Expand Up @@ -40,7 +41,7 @@ namespace request {
/// `http::select`. It can also be discarded if the request was sent for effects
/// it might have, and the response is unimportant.
class PendingRequest {

friend detail::AccessBridgeInternals;
friend Request;
friend std::pair<fastly::expected<Response>, std::vector<PendingRequest>>
select(std::vector<PendingRequest> &reqs);
Expand All @@ -65,6 +66,7 @@ class PendingRequest {
Request cloned_sent_req();

private:
auto &inner() { return req; }
rust::Box<fastly::sys::http::request::PendingRequest> req;

PendingRequest(rust::Box<fastly::sys::http::request::PendingRequest> r)
Expand Down Expand Up @@ -147,6 +149,7 @@ select(std::vector<PendingRequest> &reqs);
class Request {
friend request::PendingRequest;
friend Response;
friend detail::AccessBridgeInternals;

public:
/// Create a new request with the given method and URL, no headers, and an
Expand Down Expand Up @@ -737,6 +740,7 @@ class Request {
bool is_cacheable();

private:
auto &inner() { return req; }
Request(rust::Box<fastly::sys::http::Request> r) : req(std::move(r)) {};
rust::Box<fastly::sys::http::Request> req;
};
Expand Down
Loading