Skip to content
10 changes: 10 additions & 0 deletions api/envoy/config/core/v3/substitution_format_string.proto
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,14 @@ message SubstitutionFormatString {
// empty string, so that empty values are omitted entirely.
// * for ``json_format`` the keys with null values are omitted in the output structure.
bool omit_empty_values = 3;

// Specify a content_type for text_format. This will be ignored for json_format.

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.

Please specify defaults when no content_type is specified.

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.

Nit: *content_type* for *text_format*

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.

Why ignore for JSON? I feel there are JSON content type variants you might want to support, e.g. https://stackoverflow.com/questions/477816/what-is-the-correct-json-content-type

// Currently supported content-type value includes ``text/html; charset=UTF-8`` and ``text/plain``
// with default value being ``text/plain``.

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.

I think we can avoid relying on well-known values, see below comments. Other than this, this API is fine.

//
// .. code-block::
//
// content_type: "text/html; charset=UTF-8"
//
string content_type = 4;
}
10 changes: 10 additions & 0 deletions api/envoy/config/core/v4alpha/substitution_format_string.proto

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

5 changes: 4 additions & 1 deletion docs/root/configuration/http/http_conn_man/local_reply.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ The response body content type can be customized. If not specified, the content

Local reply format can be specified as :ref:`SubstitutionFormatString <envoy_v3_api_msg_config.core.v3.SubstitutionFormatString>`. It supports :ref:`text_format <envoy_v3_api_field_config.core.v3.SubstitutionFormatString.text_format>` and :ref:`json_format <envoy_v3_api_field_config.core.v3.SubstitutionFormatString.json_format>`.

If local reply format is :ref:`text_format <envoy_v3_api_field_config.core.v3.SubstitutionFormatString.text_format>` then content-type can be modified further via :ref:`content_type <envoy_v3_api_field_config.core.v3.SubstitutionFormatString.content_type>`. Supported content-type values include `text/html; charset=UTF-8` and `text/plain` with default value being `text/plain`.

Example of a LocalReplyConfig with `body_format` field.

.. code-block::
Expand All @@ -63,7 +65,8 @@ Example of a LocalReplyConfig with `body_format` field.
runtime_key: key_b
status_code: 401
body_format_override:
text_format: "%LOCAL_REPLY_BODY% %REQ(:path)%"
text_format: "<h1>%LOCAL_REPLY_BODY% %REQ(:path)%</h1>"
content_type: "text/html; charset=UTF-8"
- filter:
status_code_filter:
comparison:
Expand Down
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ New Features
* load balancer: added a :ref:`configuration<envoy_v3_api_msg_config.cluster.v3.Cluster.LeastRequestLbConfig>` option to specify the active request bias used by the least request load balancer.
* load balancer: added an :ref:`option <envoy_v3_api_field_config.cluster.v3.Cluster.LbSubsetConfig.LbSubsetSelector.single_host_per_subset>` to optimize subset load balancing when there is only one host per subset.
* load balancer: added support for bounded load per host for consistent hash load balancers via :ref:`hash_balance_factor <envoy_api_field_Cluster.CommonLbConfig.consistent_hashing_lb_config>`.
* local_reply config: added :ref:`content_type<envoy_v3_api_field_config.core.v3.SubstitutionFormatString.content_type>` option to set content-type for textformat.
* lua: added Lua APIs to access :ref:`SSL connection info <config_http_filters_lua_ssl_socket_info>` object.
* lua: added Lua API for :ref:`base64 escaping a string <config_http_filters_lua_stream_handle_api_base64_escape>`.
* lua: added new :ref:`source_code <envoy_v3_api_field_extensions.filters.http.lua.v3.LuaPerRoute.source_code>` field to support the dispatching of inline Lua code in per route configuration of Lua filter.
Expand Down

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

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

9 changes: 9 additions & 0 deletions source/common/formatter/substitution_format_string.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "common/formatter/substitution_format_string.h"

#include "common/formatter/substitution_formatter.h"
#include "common/http/headers.h"

namespace Envoy {
namespace Formatter {
Expand All @@ -25,5 +26,13 @@ FormatterPtr SubstitutionFormatStringUtils::fromProtoConfig(
return nullptr;
}

absl::string_view SubstitutionFormatStringUtils::getContentType(
const envoy::config::core::v3::SubstitutionFormatString& config) {
if (config.content_type() == Http::Headers::get().ContentTypeValues.Html) {
return Http::Headers::get().ContentTypeValues.Html;
}

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.

Not sure, but can we pass config.content_type() value as is? And yes, when it is not set and it is not JSON we return text/plain. WDYT @htuch?

@deveshkandpal24121990 deveshkandpal24121990 Sep 14, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@dio - I tried doing that initially, however config.content_type() returns std:string& and after the value would get assigned to absl:string_view content_type_ , the address it points to is not valid anymore after the scope of the method is over. My understanding about string_view is that it's a read-only pointer to the string object being assigned. One way to fix that was to change the datatype of content_type_ to std::string, however I wasn't sure if that is encouraged ? Any other ideas ?

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.

Sure, make content_type_ a std::string.

return Http::Headers::get().ContentTypeValues.Text;
}

} // namespace Formatter
} // namespace Envoy
6 changes: 6 additions & 0 deletions source/common/formatter/substitution_format_string.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ class SubstitutionFormatStringUtils {
*/
static FormatterPtr createJsonFormatter(const ProtobufWkt::Struct& struct_format,
bool preserve_types, bool omit_empty_values);

/**
* Returns content-type from config SubstitutionFormatString
*/
static absl::string_view
getContentType(const envoy::config::core::v3::SubstitutionFormatString& config);
};

} // namespace Formatter
Expand Down
2 changes: 1 addition & 1 deletion source/common/local_reply/local_reply.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class BodyFormatter {
config.format_case() ==
envoy::config::core::v3::SubstitutionFormatString::FormatCase::kJsonFormat
? Http::Headers::get().ContentTypeValues.Json
: Http::Headers::get().ContentTypeValues.Text) {}
: Formatter::SubstitutionFormatStringUtils::getContentType(config)) {}

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.

So, given: https://github.com/envoyproxy/envoy/pull/13019/files#r488273786, here we can have: config.content_type().empty() ? Http::Headers::get().ContentTypeValues.Text : config.content_type().


void format(const Http::RequestHeaderMap& request_headers,
const Http::ResponseHeaderMap& response_headers,
Expand Down
10 changes: 10 additions & 0 deletions test/common/formatter/substitution_format_string_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,15 @@ TEST_F(SubstitutionFormatStringUtilsTest, TestInvalidConfigs) {
}
}

TEST_F(SubstitutionFormatStringUtilsTest, TestFromProtoConfigContentType) {
const std::string yaml = R"EOF(
text_format: "<h1>Sample html</h1>"
content_type: "text/html; charset=UTF-8"
)EOF";
TestUtility::loadFromYaml(yaml, config_);
const absl::string_view contentType = SubstitutionFormatStringUtils::getContentType(config_);
EXPECT_EQ("text/html; charset=UTF-8", contentType);
}

} // namespace Formatter
} // namespace Envoy
57 changes: 57 additions & 0 deletions test/common/local_reply/local_reply_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -336,5 +336,62 @@ TEST_F(LocalReplyTest, TestHeaderAddition) {
ASSERT_EQ(out[1], "append-bar3");
}

TEST_F(LocalReplyTest, TestMapperWithContentType) {
// Match with response_code, and rewrite the code and body.
const std::string yaml = R"(
mappers:
- filter:
status_code_filter:
comparison:
op: EQ
value:
default_value: 400
runtime_key: key_b
status_code: 401
body:
inline_string: "401 body text"
body_format_override:
text_format: "<h1>%LOCAL_REPLY_BODY%</h1>"
content_type: "text/html; charset=UTF-8"
- filter:
status_code_filter:
comparison:
op: EQ
value:
default_value: 410
runtime_key: key_b
status_code: 411
body:
inline_string: "411 body text"
body_format:
text_format: "<h1>%LOCAL_REPLY_BODY%</h1> %RESPONSE_CODE% default formatter"
content_type: "text/html; charset=UTF-8"
)";
TestUtility::loadFromYaml(yaml, config_);
auto local = Factory::create(config_, context_);

// code=400 matches the first filter; rewrite code and body
// has its own formatter.
resetData(400);
local->rewrite(&request_headers_, response_headers_, stream_info_, code_, body_, content_type_);
EXPECT_EQ(code_, static_cast<Http::Code>(401));
EXPECT_EQ(stream_info_.response_code_, 401U);
EXPECT_EQ(response_headers_.Status()->value().getStringView(), "401");
EXPECT_EQ(content_type_, "text/html; charset=UTF-8");

const std::string expected = R"(<h1>401 body text</h1>)";
EXPECT_EQ(body_, expected);

// code=410 matches the second filter; rewrite code and body
Comment thread
dio marked this conversation as resolved.
// but using default formatter.
resetData(410);
local->rewrite(&request_headers_, response_headers_, stream_info_, code_, body_, content_type_);
EXPECT_EQ(code_, static_cast<Http::Code>(411));
EXPECT_EQ(stream_info_.response_code_, 411U);
EXPECT_EQ(response_headers_.Status()->value().getStringView(), "411");
EXPECT_EQ(body_, "<h1>411 body text</h1> 411 default formatter");
EXPECT_EQ(content_type_, "text/html; charset=UTF-8");
}

} // namespace LocalReply
} // namespace Envoy