diff --git a/include/envoy/http/BUILD b/include/envoy/http/BUILD index cff775b80d104..8d28d08caa94f 100644 --- a/include/envoy/http/BUILD +++ b/include/envoy/http/BUILD @@ -83,3 +83,8 @@ envoy_cc_library( name = "protocol_interface", hdrs = ["protocol.h"], ) + +envoy_cc_library( + name = "query_params_interface", + hdrs = ["query_params.h"], +) diff --git a/include/envoy/http/query_params.h b/include/envoy/http/query_params.h new file mode 100644 index 0000000000000..1d235879ea98f --- /dev/null +++ b/include/envoy/http/query_params.h @@ -0,0 +1,16 @@ +#include +#include + +namespace Envoy { +namespace Http { +namespace Utility { + +// TODO(jmarantz): this should probably be a proper class, with methods to serialize +// using proper formatting. Perhaps similar to +// https://github.com/apache/incubator-pagespeed-mod/blob/master/pagespeed/kernel/http/query_params.h + +typedef std::map QueryParams; + +} // namespace Utility +} // namespace Http +} // namespace Envoy diff --git a/include/envoy/server/BUILD b/include/envoy/server/BUILD index 766c7ace26981..fae78b50ab2ad 100644 --- a/include/envoy/server/BUILD +++ b/include/envoy/server/BUILD @@ -27,6 +27,7 @@ envoy_cc_library( "//include/envoy/http:codes_interface", "//include/envoy/http:filter_interface", "//include/envoy/http:header_map_interface", + "//include/envoy/http:query_params_interface", "//include/envoy/network:listen_socket_interface", ], ) @@ -92,6 +93,7 @@ envoy_cc_library( ":options_interface", "//include/envoy/access_log:access_log_interface", "//include/envoy/api:api_interface", + "//include/envoy/http:query_params_interface", "//include/envoy/init:init_interface", "//include/envoy/local_info:local_info_interface", "//include/envoy/ratelimit:ratelimit_interface", diff --git a/include/envoy/server/admin.h b/include/envoy/server/admin.h index 8d7d1063f9c88..2ed9f3e45e61f 100644 --- a/include/envoy/server/admin.h +++ b/include/envoy/server/admin.h @@ -8,6 +8,7 @@ #include "envoy/http/codes.h" #include "envoy/http/filter.h" #include "envoy/http/header_map.h" +#include "envoy/http/query_params.h" #include "envoy/network/listen_socket.h" #include "envoy/server/config_tracker.h" @@ -44,6 +45,7 @@ class AdminStream { */ virtual const Http::HeaderMap& getRequestHeaders() const PURE; }; + /** * This macro is used to add handlers to the Admin HTTP Endpoint. It builds * a callback that executes X when the specified admin handler is hit. This macro can be @@ -108,6 +110,22 @@ class Admin { * @return ConfigTracker& tracker for /config_dump endpoint. */ virtual ConfigTracker& getConfigTracker() PURE; + + /** + * Executes an admin request with the specified query params. Note: this must + * be called from Envoy's main thread. + * + * @param path the path of the admin URL. + * @param param the query-params passed to the admin request handler. + * @param method the HTTP method (POST or GET). + * @param response_headers populated the the response headers from executing the request, + * most notably content-type. + * @param body populated with the response-body from the admin request. + * @return Http::Code The HTTP response code from the admin request. + */ + virtual Http::Code request(absl::string_view path, const Http::Utility::QueryParams& params, + absl::string_view method, Http::HeaderMap& response_headers, + std::string& body) PURE; }; } // namespace Server diff --git a/source/common/buffer/buffer_impl.cc b/source/common/buffer/buffer_impl.cc index dd824f38652d1..f7bdfcd12aa46 100644 --- a/source/common/buffer/buffer_impl.cc +++ b/source/common/buffer/buffer_impl.cc @@ -183,5 +183,22 @@ OwnedImpl::OwnedImpl(const Instance& data) : OwnedImpl() { add(data); } OwnedImpl::OwnedImpl(const void* data, uint64_t size) : OwnedImpl() { add(data, size); } +std::string OwnedImpl::toString() const { + uint64_t num_slices = getRawSlices(nullptr, 0); + RawSlice slices[num_slices]; + getRawSlices(slices, num_slices); + size_t len = 0; + for (RawSlice& slice : slices) { + len += slice.len_; + } + std::string output; + output.reserve(len); + for (RawSlice& slice : slices) { + output.append(static_cast(slice.mem_), slice.len_); + } + + return output; +} + } // namespace Buffer } // namespace Envoy diff --git a/source/common/buffer/buffer_impl.h b/source/common/buffer/buffer_impl.h index f2ffbfbee2b32..4d9a2f4aa7b6f 100644 --- a/source/common/buffer/buffer_impl.h +++ b/source/common/buffer/buffer_impl.h @@ -86,6 +86,12 @@ class OwnedImpl : public LibEventInstance { int write(int fd) override; void postProcess() override {} + /** + * Construct a flattened string from a buffer. + * @return the flattened string. + */ + std::string toString() const; + Event::Libevent::BufferPtr& buffer() override { return buffer_; } private: diff --git a/source/common/http/BUILD b/source/common/http/BUILD index d79dfc7f82b7b..13ab86a359b86 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -243,6 +243,7 @@ envoy_cc_library( "//include/envoy/http:codes_interface", "//include/envoy/http:filter_interface", "//include/envoy/http:header_map_interface", + "//include/envoy/http:query_params_interface", "//source/common/buffer:buffer_lib", "//source/common/common:assert_lib", "//source/common/common:empty_string", diff --git a/source/common/http/utility.cc b/source/common/http/utility.cc index 8d43ef598c30a..afd0510dbf77c 100644 --- a/source/common/http/utility.cc +++ b/source/common/http/utility.cc @@ -377,5 +377,17 @@ MessagePtr Utility::prepareHeaders(const ::envoy::api::v2::core::HttpUri& http_u return message; } +// TODO(jmarantz): make QueryParams a real class and put this serializer there, +// along with proper URL escaping of the name and value. +std::string Utility::queryParamsToString(const QueryParams& params) { + std::string out; + std::string delim = "?"; + for (auto p : params) { + absl::StrAppend(&out, delim, p.first, "=", p.second); + delim = "&"; + } + return out; +} + } // namespace Http } // namespace Envoy diff --git a/source/common/http/utility.h b/source/common/http/utility.h index 1958263fd1231..2e00748d6b698 100644 --- a/source/common/http/utility.h +++ b/source/common/http/utility.h @@ -10,6 +10,7 @@ #include "envoy/http/codes.h" #include "envoy/http/filter.h" #include "envoy/http/message.h" +#include "envoy/http/query_params.h" #include "common/json/json_loader.h" @@ -17,181 +18,178 @@ namespace Envoy { namespace Http { +namespace Utility { /** - * General HTTP utilities. - */ -class Utility { -public: - typedef std::map QueryParams; - - /** - * Append to x-forwarded-for header. - * @param headers supplies the headers to append to. - * @param remote_address supplies the remote address to append. - */ - static void appendXff(HeaderMap& headers, const Network::Address::Instance& remote_address); - - /** - * Append to via header. - * @param headers supplies the headers to append to. - * @param via supplies the via header to append. - */ - static void appendVia(HeaderMap& headers, const std::string& via); - - /** - * Creates an SSL (https) redirect path based on the input host and path headers. - * @param headers supplies the request headers. - * @return std::string the redirect path. - */ - static std::string createSslRedirectPath(const HeaderMap& headers); - - /** - * Parse a URL into query parameters. - * @param url supplies the url to parse. - * @return QueryParams the parsed parameters, if any. - */ - static QueryParams parseQueryString(absl::string_view url); - - /** - * Finds the start of the query string in a path - * @param path supplies a HeaderString& to search for the query string - * @return const char* a pointer to the beginning of the query string, or the end of the - * path if there is no query - */ - static const char* findQueryStringStart(const HeaderString& path); - - /** - * Parse a particular value out of a cookie - * @param headers supplies the headers to get the cookie from. - * @param key the key for the particular cookie value to return - * @return std::string the parsed cookie value, or "" if none exists - **/ - static std::string parseCookieValue(const HeaderMap& headers, const std::string& key); - - /** - * Check whether a Set-Cookie header for the given cookie name exists - * @param headers supplies the headers to search for the cookie - * @param key the name of the cookie to search for - * @return bool true if the cookie is set, false otherwise - */ - static bool hasSetCookie(const HeaderMap& headers, const std::string& key); - - /** - * Produce the value for a Set-Cookie header with the given parameters. - * @param key is the name of the cookie that is being set. - * @param value the value to set the cookie to; this value is trusted. - * @param path the path for the cookie, or the empty string to not set a path. - * @param max_age the length of time for which the cookie is valid, or zero - * @param httponly true if the cookie should have HttpOnly appended. - * to create a session cookie. - * @return std::string a valid Set-Cookie header value string - */ - static std::string makeSetCookieValue(const std::string& key, const std::string& value, - const std::string& path, const std::chrono::seconds max_age, - bool httponly); - - /** - * Get the response status from the response headers. - * @param headers supplies the headers to get the status from. - * @return uint64_t the response code or throws an exception if the headers are invalid. - */ - static uint64_t getResponseStatus(const HeaderMap& headers); - - /** - * Determine whether this is a WebSocket Upgrade request. - * This function returns true if the following HTTP headers and values are present: - * - Connection: Upgrade - * - Upgrade: websocket - */ - static bool isWebSocketUpgradeRequest(const HeaderMap& headers); - - /** - * @return Http2Settings An Http2Settings populated from the - * envoy::api::v2::core::Http2ProtocolOptions config. - */ - static Http2Settings parseHttp2Settings(const envoy::api::v2::core::Http2ProtocolOptions& config); - - /** - * @return Http1Settings An Http1Settings populated from the - * envoy::api::v2::core::Http1ProtocolOptions config. - */ - static Http1Settings parseHttp1Settings(const envoy::api::v2::core::Http1ProtocolOptions& config); - - /** - * Create a locally generated response using filter callbacks. - * @param is_grpc tells if this is a response to a gRPC request. - * @param callbacks supplies the filter callbacks to use. - * @param is_reset boolean reference that indicates whether a stream has been reset. It is the - * responsibility of the caller to ensure that this is set to false if onDestroy() - * is invoked in the context of sendLocalReply(). - * @param response_code supplies the HTTP response code. - * @param body_text supplies the optional body text which is sent using the text/plain content - * type. - */ - static void sendLocalReply(bool is_grpc, StreamDecoderFilterCallbacks& callbacks, - const bool& is_reset, Code response_code, - const std::string& body_text); - - /** - * Create a locally generated response using the provided lambdas. - * @param is_grpc tells if this is a response to a gRPC request. - * @param encode_headers supplies the function to encode response headers. - * @param encode_data supplies the function to encode the response body. - * @param is_reset boolean reference that indicates whether a stream has been reset. It is the - * responsibility of the caller to ensure that this is set to false if onDestroy() - * is invoked in the context of sendLocalReply(). - * @param response_code supplies the HTTP response code. - * @param body_text supplies the optional body text which is sent using the text/plain content - * type. - */ - static void - sendLocalReply(bool is_grpc, - std::function encode_headers, - std::function encode_data, - const bool& is_reset, Code response_code, const std::string& body_text); - - struct GetLastAddressFromXffInfo { - // Last valid address pulled from the XFF header. - Network::Address::InstanceConstSharedPtr address_; - // Whether this is the only address in the XFF header. - bool single_address_; - }; - - /** - * Retrieves the last IPv4/IPv6 address in the x-forwarded-for header. - * @param request_headers supplies the request headers. - * @param num_to_skip specifies the number of addresses at the end of the XFF header - * to ignore when identifying the "last" address. - * @return GetLastAddressFromXffInfo information about the last address in the XFF header. - * @see GetLastAddressFromXffInfo for more information. - */ - static GetLastAddressFromXffInfo getLastAddressFromXFF(const Http::HeaderMap& request_headers, - uint32_t num_to_skip = 0); - - /** - * Get the string for the given http protocol. - * @param protocol for which to return the string representation. - * @return string representation of the protocol. - */ - static const std::string& getProtocolString(const Protocol p); - - /** - * Extract host and path from a URI. The host may contain port. - * This function doesn't validate if the URI is valid. It only parses the URI with following - * format: scheme://host/path. - * @param the input URI string - * @param the output host string. - * @param the output path string. - */ - static void extractHostPathFromUri(const absl::string_view& uri, absl::string_view& host, - absl::string_view& path); - - /** - * Prepare headers for a HttpUri. - */ - static MessagePtr prepareHeaders(const ::envoy::api::v2::core::HttpUri& http_uri); + * Append to x-forwarded-for header. + * @param headers supplies the headers to append to. + * @param remote_address supplies the remote address to append. + */ +void appendXff(HeaderMap& headers, const Network::Address::Instance& remote_address); + +/** + * Append to via header. + * @param headers supplies the headers to append to. + * @param via supplies the via header to append. + */ +void appendVia(HeaderMap& headers, const std::string& via); + +/** + * Creates an SSL (https) redirect path based on the input host and path headers. + * @param headers supplies the request headers. + * @return std::string the redirect path. + */ +std::string createSslRedirectPath(const HeaderMap& headers); + +/** + * Parse a URL into query parameters. + * @param url supplies the url to parse. + * @return QueryParams the parsed parameters, if any. + */ +QueryParams parseQueryString(absl::string_view url); + +/** + * Finds the start of the query string in a path + * @param path supplies a HeaderString& to search for the query string + * @return const char* a pointer to the beginning of the query string, or the end of the + * path if there is no query + */ +const char* findQueryStringStart(const HeaderString& path); + +/** + * Parse a particular value out of a cookie + * @param headers supplies the headers to get the cookie from. + * @param key the key for the particular cookie value to return + * @return std::string the parsed cookie value, or "" if none exists + **/ +std::string parseCookieValue(const HeaderMap& headers, const std::string& key); + +/** + * Check whether a Set-Cookie header for the given cookie name exists + * @param headers supplies the headers to search for the cookie + * @param key the name of the cookie to search for + * @return bool true if the cookie is set, false otherwise + */ +bool hasSetCookie(const HeaderMap& headers, const std::string& key); + +/** + * Produce the value for a Set-Cookie header with the given parameters. + * @param key is the name of the cookie that is being set. + * @param value the value to set the cookie to; this value is trusted. + * @param path the path for the cookie, or the empty string to not set a path. + * @param max_age the length of time for which the cookie is valid, or zero + * @param httponly true if the cookie should have HttpOnly appended. + * to create a session cookie. + * @return std::string a valid Set-Cookie header value string + */ +std::string makeSetCookieValue(const std::string& key, const std::string& value, + const std::string& path, const std::chrono::seconds max_age, + bool httponly); + +/** + * Get the response status from the response headers. + * @param headers supplies the headers to get the status from. + * @return uint64_t the response code or throws an exception if the headers are invalid. + */ +uint64_t getResponseStatus(const HeaderMap& headers); + +/** + * Determine whether this is a WebSocket Upgrade request. + * This function returns true if the following HTTP headers and values are present: + * - Connection: Upgrade + * - Upgrade: websocket + */ +bool isWebSocketUpgradeRequest(const HeaderMap& headers); + +/** + * @return Http2Settings An Http2Settings populated from the + * envoy::api::v2::core::Http2ProtocolOptions config. + */ +Http2Settings parseHttp2Settings(const envoy::api::v2::core::Http2ProtocolOptions& config); + +/** + * @return Http1Settings An Http1Settings populated from the + * envoy::api::v2::core::Http1ProtocolOptions config. + */ +Http1Settings parseHttp1Settings(const envoy::api::v2::core::Http1ProtocolOptions& config); + +/** + * Create a locally generated response using filter callbacks. + * @param is_grpc tells if this is a response to a gRPC request. + * @param callbacks supplies the filter callbacks to use. + * @param is_reset boolean reference that indicates whether a stream has been reset. It is the + * responsibility of the caller to ensure that this is set to false if onDestroy() + * is invoked in the context of sendLocalReply(). + * @param response_code supplies the HTTP response code. + * @param body_text supplies the optional body text which is sent using the text/plain content + * type. + */ +void sendLocalReply(bool is_grpc, StreamDecoderFilterCallbacks& callbacks, const bool& is_reset, + Code response_code, const std::string& body_text); + +/** + * Create a locally generated response using the provided lambdas. + * @param is_grpc tells if this is a response to a gRPC request. + * @param encode_headers supplies the function to encode response headers. + * @param encode_data supplies the function to encode the response body. + * @param is_reset boolean reference that indicates whether a stream has been reset. It is the + * responsibility of the caller to ensure that this is set to false if onDestroy() + * is invoked in the context of sendLocalReply(). + * @param response_code supplies the HTTP response code. + * @param body_text supplies the optional body text which is sent using the text/plain content + * type. + */ +void sendLocalReply(bool is_grpc, + std::function encode_headers, + std::function encode_data, + const bool& is_reset, Code response_code, const std::string& body_text); + +struct GetLastAddressFromXffInfo { + // Last valid address pulled from the XFF header. + Network::Address::InstanceConstSharedPtr address_; + // Whether this is the only address in the XFF header. + bool single_address_; }; +/** + * Retrieves the last IPv4/IPv6 address in the x-forwarded-for header. + * @param request_headers supplies the request headers. + * @param num_to_skip specifies the number of addresses at the end of the XFF header + * to ignore when identifying the "last" address. + * @return GetLastAddressFromXffInfo information about the last address in the XFF header. + * @see GetLastAddressFromXffInfo for more information. + */ +GetLastAddressFromXffInfo getLastAddressFromXFF(const Http::HeaderMap& request_headers, + uint32_t num_to_skip = 0); + +/** + * Get the string for the given http protocol. + * @param protocol for which to return the string representation. + * @return string representation of the protocol. + */ +const std::string& getProtocolString(const Protocol p); + +/** + * Extract host and path from a URI. The host may contain port. + * This function doesn't validate if the URI is valid. It only parses the URI with following + * format: scheme://host/path. + * @param the input URI string + * @param the output host string. + * @param the output path string. + */ +void extractHostPathFromUri(const absl::string_view& uri, absl::string_view& host, + absl::string_view& path); + +/** + * Prepare headers for a HttpUri. + */ +MessagePtr prepareHeaders(const ::envoy::api::v2::core::HttpUri& http_uri); + +/** + * Serialize query-params into a string. + */ +std::string queryParamsToString(const QueryParams& query_params); + +} // namespace Utility } // namespace Http } // namespace Envoy diff --git a/source/server/config_validation/admin.cc b/source/server/config_validation/admin.cc index 33ad37ea130ca..1174c1462871e 100644 --- a/source/server/config_validation/admin.cc +++ b/source/server/config_validation/admin.cc @@ -13,5 +13,10 @@ const Network::Socket& ValidationAdmin::socket() { NOT_IMPLEMENTED; }; ConfigTracker& ValidationAdmin::getConfigTracker() { return config_tracker_; }; +Http::Code ValidationAdmin::request(absl::string_view, const Http::Utility::QueryParams&, + absl::string_view, Http::HeaderMap&, std::string&) { + NOT_IMPLEMENTED; +} + } // namespace Server } // namespace Envoy diff --git a/source/server/config_validation/admin.h b/source/server/config_validation/admin.h index e7944c1db9095..3e8f072b0a391 100644 --- a/source/server/config_validation/admin.h +++ b/source/server/config_validation/admin.h @@ -20,6 +20,9 @@ class ValidationAdmin : public Admin { bool removeHandler(const std::string&) override; const Network::Socket& socket() override; ConfigTracker& getConfigTracker() override; + Http::Code request(absl::string_view path, const Http::Utility::QueryParams& params, + absl::string_view method, Http::HeaderMap& response_headers, + std::string& body) override; private: ConfigTrackerImpl config_tracker_; diff --git a/source/server/http/admin.cc b/source/server/http/admin.cc index b80e62e59cb82..752ca8e9eac02 100644 --- a/source/server/http/admin.cc +++ b/source/server/http/admin.cc @@ -125,6 +125,22 @@ const char AdminHtmlEnd[] = R"( )"; +void populateFallbackResponseHeaders(Http::Code code, Http::HeaderMap& header_map) { + header_map.insertStatus().value(std::to_string(enumToInt(code))); + const auto& headers = Http::Headers::get(); + if (header_map.ContentType() == nullptr) { + // Default to text-plain if unset. + header_map.insertContentType().value().setReference(headers.ContentTypeValues.TextUtf8); + } + // Default to 'no-cache' if unset, but not 'no-store' which may break the back button. + if (header_map.CacheControl() == nullptr) { + header_map.insertCacheControl().value().setReference(headers.CacheControlValues.NoCacheMaxAge0); + } + + // Under no circumstance should browsers sniff content-type. + header_map.addReference(headers.XContentTypeOptions, headers.XContentTypeOptionValues.Nosniff); +} + } // namespace AdminFilter::AdminFilter(AdminImpl& parent) : parent_(parent) {} @@ -758,20 +774,7 @@ void AdminFilter::onComplete() { Http::HeaderMapPtr header_map{new Http::HeaderMapImpl}; RELEASE_ASSERT(request_headers_); Http::Code code = parent_.runCallback(path, *header_map, response, *this); - header_map->insertStatus().value(std::to_string(enumToInt(code))); - const auto& headers = Http::Headers::get(); - if (header_map->ContentType() == nullptr) { - // Default to text-plain if unset. - header_map->insertContentType().value().setReference(headers.ContentTypeValues.TextUtf8); - } - // Default to 'no-cache' if unset, but not 'no-store' which may break the back button. - if (header_map->CacheControl() == nullptr) { - header_map->insertCacheControl().value().setReference( - headers.CacheControlValues.NoCacheMaxAge0); - } - - // Under no circumstance should browsers sniff content-type. - header_map->addReference(headers.XContentTypeOptions, headers.XContentTypeOptionValues.Nosniff); + populateFallbackResponseHeaders(code, *header_map); callbacks_->encodeHeaders(std::move(header_map), end_stream_on_complete_ && response.length() == 0); @@ -992,5 +995,23 @@ bool AdminImpl::removeHandler(const std::string& prefix) { return false; } +Http::Code AdminImpl::request(absl::string_view path, const Http::Utility::QueryParams& params, + absl::string_view method, Http::HeaderMap& response_headers, + std::string& body) { + AdminFilter filter(*this); + Http::HeaderMapImpl request_headers; + request_headers.insertMethod().value(method.data(), method.size()); + filter.decodeHeaders(request_headers, false); + std::string path_and_query = absl::StrCat(path, Http::Utility::queryParamsToString(params)); + Buffer::OwnedImpl response; + + // TODO(jmarantz): rather than serializing params here and then re-parsing in the handler, + // change the callback signature to take the query-params separately. + Http::Code code = runCallback(path_and_query, response_headers, response, filter); + populateFallbackResponseHeaders(code, response_headers); + body = response.toString(); + return code; +} + } // namespace Server } // namespace Envoy diff --git a/source/server/http/admin.h b/source/server/http/admin.h index 5eec2bcf5569c..50e100bacd5a1 100644 --- a/source/server/http/admin.h +++ b/source/server/http/admin.h @@ -106,6 +106,9 @@ class AdminImpl : public Admin, Http::ConnectionManagerListenerStats& listenerStats() override { return listener_.stats_; } bool proxy100Continue() const override { return false; } const Http::Http1Settings& http1Settings() const override { return http1_settings_; } + Http::Code request(absl::string_view path, const Http::Utility::QueryParams& params, + absl::string_view method, Http::HeaderMap& response_headers, + std::string& body) override; private: /** diff --git a/test/common/buffer/owned_impl_test.cc b/test/common/buffer/owned_impl_test.cc index 54b866d7038b4..9977ea5112c99 100644 --- a/test/common/buffer/owned_impl_test.cc +++ b/test/common/buffer/owned_impl_test.cc @@ -4,6 +4,7 @@ #include "test/mocks/api/mocks.h" #include "test/test_common/threadsafe_singleton_injector.h" +#include "absl/strings/str_cat.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -32,7 +33,7 @@ TEST_F(OwnedImplTest, AddBufferFragmentNoCleanup) { EXPECT_EQ(0, buffer.length()); } -TEST_F(OwnedImplTest, addBufferFragmentWithCleanup) { +TEST_F(OwnedImplTest, AddBufferFragmentWithCleanup) { char input[] = "hello world"; BufferFragmentImpl frag(input, 11, [this](const void*, size_t, const BufferFragmentImpl*) { release_callback_called_ = true; @@ -50,7 +51,7 @@ TEST_F(OwnedImplTest, addBufferFragmentWithCleanup) { EXPECT_TRUE(release_callback_called_); } -TEST_F(OwnedImplTest, addBufferFragmentDynamicAllocation) { +TEST_F(OwnedImplTest, AddBufferFragmentDynamicAllocation) { char input_stack[] = "hello world"; char* input = new char[11]; std::copy(input_stack, input_stack + 11, input); @@ -75,7 +76,7 @@ TEST_F(OwnedImplTest, addBufferFragmentDynamicAllocation) { EXPECT_TRUE(release_callback_called_); } -TEST_F(OwnedImplTest, write) { +TEST_F(OwnedImplTest, Write) { Api::MockOsSysCalls os_sys_calls; TestThreadsafeSingletonInjector os_calls(&os_sys_calls); @@ -113,7 +114,7 @@ TEST_F(OwnedImplTest, write) { EXPECT_EQ(0, buffer.length()); } -TEST_F(OwnedImplTest, read) { +TEST_F(OwnedImplTest, Read) { Api::MockOsSysCalls os_sys_calls; TestThreadsafeSingletonInjector os_calls(&os_sys_calls); @@ -134,6 +135,21 @@ TEST_F(OwnedImplTest, read) { EXPECT_EQ(0, buffer.length()); } +TEST_F(OwnedImplTest, ToString) { + Buffer::OwnedImpl buffer; + EXPECT_EQ("", buffer.toString()); + auto append = [&buffer](absl::string_view str) { buffer.add(str.data(), str.size()); }; + append("Hello, "); + EXPECT_EQ("Hello, ", buffer.toString()); + append("world!"); + EXPECT_EQ("Hello, world!", buffer.toString()); + + // From debug inspection, I find that a second fragment is created at >1000 bytes. + std::string long_string(5000, 'A'); + append(long_string); + EXPECT_EQ(absl::StrCat("Hello, world!" + long_string), buffer.toString()); +} + } // namespace } // namespace Buffer } // namespace Envoy diff --git a/test/common/http/utility_test.cc b/test/common/http/utility_test.cc index 8517dbc4ca534..cd04127fbd27e 100644 --- a/test/common/http/utility_test.cc +++ b/test/common/http/utility_test.cc @@ -390,5 +390,12 @@ TEST(HttpUtility, TestPrepareHeaders) { EXPECT_STREQ("dns.name", message->headers().Host()->value().c_str()); } +TEST(HttpUtility, QueryParamsToString) { + EXPECT_EQ("", Utility::queryParamsToString(Utility::QueryParams({}))); + EXPECT_EQ("?a=1", Utility::queryParamsToString(Utility::QueryParams({{"a", "1"}}))); + EXPECT_EQ("?a=1&b=2", + Utility::queryParamsToString(Utility::QueryParams({{"a", "1"}, {"b", "2"}}))); +} + } // namespace Http } // namespace Envoy diff --git a/test/mocks/server/mocks.h b/test/mocks/server/mocks.h index 0c97317d88329..9bfd7e1a45680 100644 --- a/test/mocks/server/mocks.h +++ b/test/mocks/server/mocks.h @@ -35,6 +35,7 @@ #include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/mocks.h" +#include "absl/strings/string_view.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "spdlog/spdlog.h" @@ -110,6 +111,10 @@ class MockAdmin : public Admin { MOCK_METHOD1(removeHandler, bool(const std::string& prefix)); MOCK_METHOD0(socket, Network::Socket&()); MOCK_METHOD0(getConfigTracker, ConfigTracker&()); + MOCK_METHOD5(request, + Http::Code(absl::string_view path, const Http::Utility::QueryParams& query_params, + absl::string_view method, Http::HeaderMap& response_headers, + std::string& body)); NiceMock config_tracker_; }; diff --git a/test/server/http/admin_test.cc b/test/server/http/admin_test.cc index bf61d9bde2488..0845d225eaaf4 100644 --- a/test/server/http/admin_test.cc +++ b/test/server/http/admin_test.cc @@ -24,6 +24,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::HasSubstr; using testing::InSequence; using testing::Invoke; using testing::NiceMock; @@ -650,6 +651,38 @@ TEST_P(AdminInstanceTest, TracingStatsDisabled) { } } +TEST_P(AdminInstanceTest, GetRequest) { + Http::HeaderMapImpl response_headers; + std::string body; + EXPECT_EQ(Http::Code::OK, admin_.request("/server_info", Http::Utility::QueryParams(), "GET", + response_headers, body)); + EXPECT_TRUE(absl::StartsWith(body, "envoy ")) << body; + EXPECT_THAT(std::string(response_headers.ContentType()->value().getStringView()), + HasSubstr("text/plain")); +} + +TEST_P(AdminInstanceTest, GetRequestJson) { + Http::HeaderMapImpl response_headers; + std::string body; + EXPECT_EQ(Http::Code::OK, + admin_.request("/stats", Http::Utility::QueryParams({{"format", "json"}}), "GET", + response_headers, body)); + EXPECT_THAT(body, HasSubstr("{\"stats\":[")); + EXPECT_THAT(std::string(response_headers.ContentType()->value().getStringView()), + HasSubstr("application/json")); +} + +TEST_P(AdminInstanceTest, PostRequest) { + Http::HeaderMapImpl response_headers; + std::string body; + EXPECT_NO_LOGS( + EXPECT_EQ(Http::Code::OK, admin_.request("/healthcheck/fail", Http::Utility::QueryParams(), + "POST", response_headers, body))); + EXPECT_EQ(body, "OK\n"); + EXPECT_THAT(std::string(response_headers.ContentType()->value().getStringView()), + HasSubstr("text/plain")); +} + class PrometheusStatsFormatterTest : public testing::Test { protected: void addCounter(const std::string& name, std::vector cluster_tags) { diff --git a/test/server/server_test.cc b/test/server/server_test.cc index d34b8f1f2004c..72710b45aa25c 100644 --- a/test/server/server_test.cc +++ b/test/server/server_test.cc @@ -243,7 +243,8 @@ TEST_P(ServerInstanceImplTest, NoOptionsPassed) { Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("127.0.0.1")), hooks_, restart_, stats_store_, fakelock_, component_factory_, std::make_unique>(), thread_local_)), - EnvoyException, "unable to read file: ") + EnvoyException, "unable to read file: "); } + } // namespace Server } // namespace Envoy diff --git a/test/test_common/utility.cc b/test/test_common/utility.cc index 091af1780ae95..3dfdfeb0dfa05 100644 --- a/test/test_common/utility.cc +++ b/test/test_common/utility.cc @@ -70,18 +70,6 @@ bool TestUtility::buffersEqual(const Buffer::Instance& lhs, const Buffer::Instan return true; } -std::string TestUtility::bufferToString(const Buffer::Instance& buffer) { - std::string output; - uint64_t num_slices = buffer.getRawSlices(nullptr, 0); - Buffer::RawSlice slices[num_slices]; - buffer.getRawSlices(slices, num_slices); - for (Buffer::RawSlice& slice : slices) { - output.append(static_cast(slice.mem_), slice.len_); - } - - return output; -} - void TestUtility::feedBufferWithRandomCharacters(Buffer::Instance& buffer, uint64_t n_char, uint64_t seed) { const std::string sample = "Neque porro quisquam est qui dolorem ipsum.."; diff --git a/test/test_common/utility.h b/test/test_common/utility.h index 4f6a4c1faf315..ddecc92b61b0a 100644 --- a/test/test_common/utility.h +++ b/test/test_common/utility.h @@ -12,6 +12,7 @@ #include "envoy/network/address.h" #include "envoy/stats/stats.h" +#include "common/buffer/buffer_impl.h" #include "common/common/c_smart_ptr.h" #include "common/http/header_map_impl.h" #include "common/protobuf/utility.h" @@ -102,7 +103,10 @@ class TestUtility { * @param buffer supplies the buffer to convert. * @return std::string the converted string. */ - static std::string bufferToString(const Buffer::Instance& buffer); + static std::string bufferToString(const Buffer::OwnedImpl& buffer) { + // TODO(jmarantz): remove this indirection and update all ~53 call sites. + return buffer.toString(); + } /** * Feed a buffer with random characters.