diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e8225b057632..c9d1d9ac09c1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -323,6 +323,12 @@ updates: interval: daily time: "06:00" +- package-ecosystem: "gomod" + directory: "/contrib/golang/filters/http/test/test_data/websocket" + schedule: + interval: daily + time: "06:00" + - package-ecosystem: "gomod" directory: "/contrib/golang/filters/network/test/test_data" groups: diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 648e95be8aa8..bce9384827b7 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -32,6 +32,10 @@ behavior_changes: :ref:`TlvsMetadata type `. This change can be temporarily disabled by setting the runtime flag ``envoy.reloadable_features.use_typed_metadata_in_proxy_protocol_listener`` to ``false``. +- area: golang + change: | + Move ``Continue``, ``SendLocalReply`` and ``RecoverPanic` from ``FilterCallbackHandler`` to ``DecoderFilterCallbacks`` and + ``EncoderFilterCallbacks``, to support full-duplex processing. minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* diff --git a/contrib/golang/common/dso/dso.cc b/contrib/golang/common/dso/dso.cc index ee8b0abda452..ad7904ebda06 100644 --- a/contrib/golang/common/dso/dso.cc +++ b/contrib/golang/common/dso/dso.cc @@ -81,13 +81,13 @@ void HttpFilterDsoImpl::envoyGoFilterDestroyHttpPluginConfig(GoUint64 p0, GoInt return envoy_go_filter_destroy_http_plugin_config_(p0, p1); } -GoUint64 HttpFilterDsoImpl::envoyGoFilterOnHttpHeader(httpRequest* p0, GoUint64 p1, GoUint64 p2, +GoUint64 HttpFilterDsoImpl::envoyGoFilterOnHttpHeader(processState* p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) { ASSERT(envoy_go_filter_on_http_header_ != nullptr); return envoy_go_filter_on_http_header_(p0, p1, p2, p3); } -GoUint64 HttpFilterDsoImpl::envoyGoFilterOnHttpData(httpRequest* p0, GoUint64 p1, GoUint64 p2, +GoUint64 HttpFilterDsoImpl::envoyGoFilterOnHttpData(processState* p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) { ASSERT(envoy_go_filter_on_http_data_ != nullptr); return envoy_go_filter_on_http_data_(p0, p1, p2, p3); diff --git a/contrib/golang/common/dso/dso.h b/contrib/golang/common/dso/dso.h index 2ad0be8ada66..f18c32f01e91 100644 --- a/contrib/golang/common/dso/dso.h +++ b/contrib/golang/common/dso/dso.h @@ -35,9 +35,9 @@ class HttpFilterDso : public Dso { virtual GoUint64 envoyGoFilterMergeHttpPluginConfig(GoUint64 p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) PURE; virtual void envoyGoFilterDestroyHttpPluginConfig(GoUint64 p0, GoInt p1) PURE; - virtual GoUint64 envoyGoFilterOnHttpHeader(httpRequest* p0, GoUint64 p1, GoUint64 p2, + virtual GoUint64 envoyGoFilterOnHttpHeader(processState* p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) PURE; - virtual GoUint64 envoyGoFilterOnHttpData(httpRequest* p0, GoUint64 p1, GoUint64 p2, + virtual GoUint64 envoyGoFilterOnHttpData(processState* p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) PURE; virtual void envoyGoFilterOnHttpLog(httpRequest* p0, int p1) PURE; virtual void envoyGoFilterOnHttpDestroy(httpRequest* p0, int p1) PURE; @@ -53,9 +53,10 @@ class HttpFilterDsoImpl : public HttpFilterDso { GoUint64 envoyGoFilterMergeHttpPluginConfig(GoUint64 p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) override; void envoyGoFilterDestroyHttpPluginConfig(GoUint64 p0, GoInt p1) override; - GoUint64 envoyGoFilterOnHttpHeader(httpRequest* p0, GoUint64 p1, GoUint64 p2, + GoUint64 envoyGoFilterOnHttpHeader(processState* p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) override; - GoUint64 envoyGoFilterOnHttpData(httpRequest* p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) override; + GoUint64 envoyGoFilterOnHttpData(processState* p0, GoUint64 p1, GoUint64 p2, + GoUint64 p3) override; void envoyGoFilterOnHttpLog(httpRequest* p0, int p1) override; void envoyGoFilterOnHttpDestroy(httpRequest* p0, int p1) override; void envoyGoRequestSemaDec(httpRequest* p0) override; @@ -65,9 +66,9 @@ class HttpFilterDsoImpl : public HttpFilterDso { GoUint64 (*envoy_go_filter_merge_http_plugin_config_)(GoUint64 p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) = {nullptr}; void (*envoy_go_filter_destroy_http_plugin_config_)(GoUint64 p0, GoInt p1) = {nullptr}; - GoUint64 (*envoy_go_filter_on_http_header_)(httpRequest* p0, GoUint64 p1, GoUint64 p2, + GoUint64 (*envoy_go_filter_on_http_header_)(processState* p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) = {nullptr}; - GoUint64 (*envoy_go_filter_on_http_data_)(httpRequest* p0, GoUint64 p1, GoUint64 p2, + GoUint64 (*envoy_go_filter_on_http_data_)(processState* p0, GoUint64 p1, GoUint64 p2, GoUint64 p3) = {nullptr}; void (*envoy_go_filter_on_http_log_)(httpRequest* p0, GoUint64 p1) = {nullptr}; void (*envoy_go_filter_on_http_destroy_)(httpRequest* p0, GoUint64 p1) = {nullptr}; diff --git a/contrib/golang/common/dso/libgolang.h b/contrib/golang/common/dso/libgolang.h index 7d400fbc1e30..b94f0c066f36 100644 --- a/contrib/golang/common/dso/libgolang.h +++ b/contrib/golang/common/dso/libgolang.h @@ -110,12 +110,12 @@ extern GoUint64 envoyGoFilterMergeHttpPluginConfig(GoUint64 name_ptr, GoUint64 n // go:linkname envoyGoFilterOnHttpHeader // github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http.envoyGoFilterOnHttpHeader -extern GoUint64 envoyGoFilterOnHttpHeader(httpRequest* r, GoUint64 end_stream, GoUint64 header_num, +extern GoUint64 envoyGoFilterOnHttpHeader(processState* r, GoUint64 end_stream, GoUint64 header_num, GoUint64 header_bytes); // go:linkname envoyGoFilterOnHttpData // github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http.envoyGoFilterOnHttpData -extern GoUint64 envoyGoFilterOnHttpData(httpRequest* r, GoUint64 end_stream, GoUint64 buffer, +extern GoUint64 envoyGoFilterOnHttpData(processState* s, GoUint64 end_stream, GoUint64 buffer, GoUint64 length); // go:linkname envoyGoFilterOnHttpLog diff --git a/contrib/golang/common/dso/test/mocks.h b/contrib/golang/common/dso/test/mocks.h index 777068961d26..5a9de43656c9 100644 --- a/contrib/golang/common/dso/test/mocks.h +++ b/contrib/golang/common/dso/test/mocks.h @@ -18,9 +18,9 @@ class MockHttpFilterDsoImpl : public HttpFilterDso { (GoUint64 p0, GoUint64 p1, GoUint64 p2, GoUint64 p3)); MOCK_METHOD(void, envoyGoFilterDestroyHttpPluginConfig, (GoUint64 p0, GoInt p1)); MOCK_METHOD(GoUint64, envoyGoFilterOnHttpHeader, - (httpRequest * p0, GoUint64 p1, GoUint64 p2, GoUint64 p3)); + (processState * p0, GoUint64 p1, GoUint64 p2, GoUint64 p3)); MOCK_METHOD(GoUint64, envoyGoFilterOnHttpData, - (httpRequest * p0, GoUint64 p1, GoUint64 p2, GoUint64 p3)); + (processState * p0, GoUint64 p1, GoUint64 p2, GoUint64 p3)); MOCK_METHOD(void, envoyGoFilterOnHttpLog, (httpRequest * p0, int p1)); MOCK_METHOD(void, envoyGoFilterOnHttpDestroy, (httpRequest * p0, int p1)); MOCK_METHOD(void, envoyGoRequestSemaDec, (httpRequest * p0)); diff --git a/contrib/golang/common/dso/test/test_data/simple.go b/contrib/golang/common/dso/test/test_data/simple.go index 1af57bfb6b8e..bf5f028f8f9a 100644 --- a/contrib/golang/common/dso/test/test_data/simple.go +++ b/contrib/golang/common/dso/test/test_data/simple.go @@ -5,6 +5,10 @@ typedef struct { int foo; } httpRequest; +typedef struct { + int state; +} processState; + typedef struct { unsigned long long int plugin_name_ptr; unsigned long long int plugin_name_len; @@ -43,12 +47,12 @@ func envoyGoFilterMergeHttpPluginConfig(namePtr, nameLen, parentId, childId uint } //export envoyGoFilterOnHttpHeader -func envoyGoFilterOnHttpHeader(r *C.httpRequest, endStream, headerNum, headerBytes uint64) uint64 { +func envoyGoFilterOnHttpHeader(s *C.processState, endStream, headerNum, headerBytes uint64) uint64 { return 0 } //export envoyGoFilterOnHttpData -func envoyGoFilterOnHttpData(r *C.httpRequest, endStream, buffer, length uint64) uint64 { +func envoyGoFilterOnHttpData(s *C.processState, endStream, buffer, length uint64) uint64 { return 0 } diff --git a/contrib/golang/common/go/api/api.h b/contrib/golang/common/go/api/api.h index e2f6314871a0..e6825f04e9fe 100644 --- a/contrib/golang/common/go/api/api.h +++ b/contrib/golang/common/go/api/api.h @@ -13,10 +13,19 @@ typedef struct { // NOLINT(modernize-use-using) uint64_t len; } Cstring; +struct httpRequest; + typedef struct { // NOLINT(modernize-use-using) + struct httpRequest* req; + int is_encoding; + int state; +} processState; + +typedef struct httpRequest { // NOLINT(modernize-use-using) Cstring plugin_name; uint64_t configId; - int phase; + // The ID of the worker that is processing this request, this enables the go filter to dedicate + // memory to each worker and not require locks uint32_t worker_id; } httpRequest; @@ -53,31 +62,33 @@ typedef enum { // NOLINT(modernize-use-using) CAPISerializationFailure = -8, } CAPIStatus; -CAPIStatus envoyGoFilterHttpClearRouteCache(void* r); -CAPIStatus envoyGoFilterHttpContinue(void* r, int status); -CAPIStatus envoyGoFilterHttpSendLocalReply(void* r, int response_code, void* body_text_data, +/* These APIs are related to the decode/encode phase, use the pointer of processState. */ +CAPIStatus envoyGoFilterHttpContinue(void* s, int status); +CAPIStatus envoyGoFilterHttpSendLocalReply(void* s, int response_code, void* body_text_data, int body_text_len, void* headers, int headers_num, long long int grpc_status, void* details_data, int details_len); -CAPIStatus envoyGoFilterHttpSendPanicReply(void* r, void* details_data, int details_len); +CAPIStatus envoyGoFilterHttpSendPanicReply(void* s, void* details_data, int details_len); -CAPIStatus envoyGoFilterHttpGetHeader(void* r, void* key_data, int key_len, uint64_t* value_data, +CAPIStatus envoyGoFilterHttpGetHeader(void* s, void* key_data, int key_len, uint64_t* value_data, int* value_len); -CAPIStatus envoyGoFilterHttpCopyHeaders(void* r, void* strs, void* buf); -CAPIStatus envoyGoFilterHttpSetHeaderHelper(void* r, void* key_data, int key_len, void* value_data, +CAPIStatus envoyGoFilterHttpCopyHeaders(void* s, void* strs, void* buf); +CAPIStatus envoyGoFilterHttpSetHeaderHelper(void* s, void* key_data, int key_len, void* value_data, int value_len, headerAction action); -CAPIStatus envoyGoFilterHttpRemoveHeader(void* r, void* key_data, int key_len); +CAPIStatus envoyGoFilterHttpRemoveHeader(void* s, void* key_data, int key_len); -CAPIStatus envoyGoFilterHttpGetBuffer(void* r, uint64_t buffer, void* value); -CAPIStatus envoyGoFilterHttpDrainBuffer(void* r, uint64_t buffer, uint64_t length); -CAPIStatus envoyGoFilterHttpSetBufferHelper(void* r, uint64_t buffer, void* data, int length, +CAPIStatus envoyGoFilterHttpGetBuffer(void* s, uint64_t buffer, void* value); +CAPIStatus envoyGoFilterHttpDrainBuffer(void* s, uint64_t buffer, uint64_t length); +CAPIStatus envoyGoFilterHttpSetBufferHelper(void* s, uint64_t buffer, void* data, int length, bufferAction action); -CAPIStatus envoyGoFilterHttpCopyTrailers(void* r, void* strs, void* buf); -CAPIStatus envoyGoFilterHttpSetTrailer(void* r, void* key_data, int key_len, void* value, +CAPIStatus envoyGoFilterHttpCopyTrailers(void* s, void* strs, void* buf); +CAPIStatus envoyGoFilterHttpSetTrailer(void* s, void* key_data, int key_len, void* value, int value_len, headerAction action); -CAPIStatus envoyGoFilterHttpRemoveTrailer(void* r, void* key_data, int key_len); +CAPIStatus envoyGoFilterHttpRemoveTrailer(void* s, void* key_data, int key_len); +/* These APIs have nothing to do with the decode/encode phase, use the pointer of httpRequest. */ +CAPIStatus envoyGoFilterHttpClearRouteCache(void* r); CAPIStatus envoyGoFilterHttpGetStringValue(void* r, int id, uint64_t* value_data, int* value_len); CAPIStatus envoyGoFilterHttpGetIntegerValue(void* r, int id, uint64_t* value); @@ -86,12 +97,7 @@ CAPIStatus envoyGoFilterHttpGetDynamicMetadata(void* r, void* name_data, int nam CAPIStatus envoyGoFilterHttpSetDynamicMetadata(void* r, void* name_data, int name_len, void* key_data, int key_len, void* buf_data, int buf_len); - -void envoyGoFilterLog(uint32_t level, void* message_data, int message_len); -uint32_t envoyGoFilterLogLevel(); - void envoyGoFilterHttpFinalize(void* r, int reason); -void envoyGoConfigHttpFinalize(void* c); CAPIStatus envoyGoFilterHttpSetStringFilterState(void* r, void* key_data, int key_len, void* value_data, int value_len, int state_type, @@ -101,6 +107,12 @@ CAPIStatus envoyGoFilterHttpGetStringFilterState(void* r, void* key_data, int ke CAPIStatus envoyGoFilterHttpGetStringProperty(void* r, void* key_data, int key_len, uint64_t* value_data, int* value_len, int* rc); +/* These APIs have nothing to do with request */ +void envoyGoFilterLog(uint32_t level, void* message_data, int message_len); +uint32_t envoyGoFilterLogLevel(); + +/* These APIs are related to config, use the pointer of config. */ +void envoyGoConfigHttpFinalize(void* c); CAPIStatus envoyGoFilterHttpDefineMetric(void* c, uint32_t metric_type, void* name_data, int name_len, uint32_t* metric_id); CAPIStatus envoyGoFilterHttpIncrementMetric(void* c, uint32_t metric_id, int64_t offset); diff --git a/contrib/golang/common/go/api/capi.go b/contrib/golang/common/go/api/capi.go index e76639587e2d..945235ccdf5d 100644 --- a/contrib/golang/common/go/api/capi.go +++ b/contrib/golang/common/go/api/capi.go @@ -20,27 +20,30 @@ package api import "unsafe" type HttpCAPI interface { - ClearRouteCache(r unsafe.Pointer) - HttpContinue(r unsafe.Pointer, status uint64) - HttpSendLocalReply(r unsafe.Pointer, responseCode int, bodyText string, headers map[string][]string, grpcStatus int64, details string) + /* These APIs are related to the decode/encode phase, use the pointer of processState. */ + HttpContinue(s unsafe.Pointer, status uint64) + HttpSendLocalReply(s unsafe.Pointer, responseCode int, bodyText string, headers map[string][]string, grpcStatus int64, details string) // Send a specialized reply that indicates that the filter has failed on the go side. Internally this is used for // when unhandled panics are detected. - HttpSendPanicReply(r unsafe.Pointer, details string) + HttpSendPanicReply(s unsafe.Pointer, details string) // experience api, memory unsafe - HttpGetHeader(r unsafe.Pointer, key string) string - HttpCopyHeaders(r unsafe.Pointer, num uint64, bytes uint64) map[string][]string - HttpSetHeader(r unsafe.Pointer, key string, value string, add bool) - HttpRemoveHeader(r unsafe.Pointer, key string) + HttpGetHeader(s unsafe.Pointer, key string) string + HttpCopyHeaders(s unsafe.Pointer, num uint64, bytes uint64) map[string][]string + HttpSetHeader(s unsafe.Pointer, key string, value string, add bool) + HttpRemoveHeader(s unsafe.Pointer, key string) + + HttpGetBuffer(s unsafe.Pointer, bufferPtr uint64, length uint64) []byte + HttpDrainBuffer(s unsafe.Pointer, bufferPtr uint64, length uint64) + HttpSetBufferHelper(s unsafe.Pointer, bufferPtr uint64, value string, action BufferAction) + HttpSetBytesBufferHelper(s unsafe.Pointer, bufferPtr uint64, value []byte, action BufferAction) - HttpGetBuffer(r unsafe.Pointer, bufferPtr uint64, length uint64) []byte - HttpDrainBuffer(r unsafe.Pointer, bufferPtr uint64, length uint64) - HttpSetBufferHelper(r unsafe.Pointer, bufferPtr uint64, value string, action BufferAction) - HttpSetBytesBufferHelper(r unsafe.Pointer, bufferPtr uint64, value []byte, action BufferAction) + HttpCopyTrailers(s unsafe.Pointer, num uint64, bytes uint64) map[string][]string + HttpSetTrailer(s unsafe.Pointer, key string, value string, add bool) + HttpRemoveTrailer(s unsafe.Pointer, key string) - HttpCopyTrailers(r unsafe.Pointer, num uint64, bytes uint64) map[string][]string - HttpSetTrailer(r unsafe.Pointer, key string, value string, add bool) - HttpRemoveTrailer(r unsafe.Pointer, key string) + /* These APIs have nothing to do with the decode/encode phase, use the pointer of httpRequest. */ + ClearRouteCache(r unsafe.Pointer) HttpGetStringValue(r unsafe.Pointer, id int) (string, bool) HttpGetIntegerValue(r unsafe.Pointer, id int) (uint64, bool) @@ -48,21 +51,23 @@ type HttpCAPI interface { HttpGetDynamicMetadata(r unsafe.Pointer, filterName string) map[string]interface{} HttpSetDynamicMetadata(r unsafe.Pointer, filterName string, key string, value interface{}) - HttpLog(level LogType, message string) - HttpLogLevel() LogType - - HttpFinalize(r unsafe.Pointer, reason int) - HttpConfigFinalize(c unsafe.Pointer) - HttpSetStringFilterState(r unsafe.Pointer, key string, value string, stateType StateType, lifeSpan LifeSpan, streamSharing StreamSharing) HttpGetStringFilterState(r unsafe.Pointer, key string) string HttpGetStringProperty(r unsafe.Pointer, key string) (string, error) + HttpFinalize(r unsafe.Pointer, reason int) + + /* These APIs are related to config, use the pointer of config. */ HttpDefineMetric(c unsafe.Pointer, metricType MetricType, name string) uint32 HttpIncrementMetric(c unsafe.Pointer, metricId uint32, offset int64) HttpGetMetric(c unsafe.Pointer, metricId uint32) uint64 HttpRecordMetric(c unsafe.Pointer, metricId uint32, value uint64) + HttpConfigFinalize(c unsafe.Pointer) + + /* These APIs have nothing to do with request */ + HttpLog(level LogType, message string) + HttpLogLevel() LogType } type NetworkCAPI interface { diff --git a/contrib/golang/common/go/api/filter.go b/contrib/golang/common/go/api/filter.go index b75e00f686bd..f33e1e90ae71 100644 --- a/contrib/golang/common/go/api/filter.go +++ b/contrib/golang/common/go/api/filter.go @@ -152,18 +152,10 @@ type StreamInfo interface { type StreamFilterCallbacks interface { StreamInfo() StreamInfo -} -type FilterCallbacks interface { - StreamFilterCallbacks // ClearRouteCache clears the route cache for the current request, and filtermanager will re-fetch the route in the next filter. // Please be careful to invoke it, since filtermanager will raise an 404 route_not_found response when failed to re-fetch a route. ClearRouteCache() - // Continue or SendLocalReply should be last API invoked, no more code after them. - Continue(StatusType) - SendLocalReply(responseCode int, bodyText string, headers map[string][]string, grpcStatus int64, details string) - // RecoverPanic recover panic in defer and terminate the request by SendLocalReply with 500 status code. - RecoverPanic() Log(level LogType, msg string) LogLevel() LogType // GetProperty fetch Envoy attribute and return the value as a string. @@ -180,8 +172,29 @@ type FilterCallbacks interface { // TODO add more for filter callbacks } +// FilterProcessCallbacks is the interface for filter to process request/response in decode/encode phase. +type FilterProcessCallbacks interface { + // Continue or SendLocalReply should be last API invoked, no more code after them. + Continue(StatusType) + SendLocalReply(responseCode int, bodyText string, headers map[string][]string, grpcStatus int64, details string) + // RecoverPanic recover panic in defer and terminate the request by SendLocalReply with 500 status code. + RecoverPanic() +} + +type DecoderFilterCallbacks interface { + FilterProcessCallbacks +} + +type EncoderFilterCallbacks interface { + FilterProcessCallbacks +} + type FilterCallbackHandler interface { - FilterCallbacks + StreamFilterCallbacks + // DecoderFilterCallbacks could only be used in DecodeXXX phases. + DecoderFilterCallbacks() DecoderFilterCallbacks + // EncoderFilterCallbacks could only be used in EncodeXXX phases. + EncoderFilterCallbacks() EncoderFilterCallbacks } type DynamicMetadata interface { diff --git a/contrib/golang/filters/http/source/cgo.cc b/contrib/golang/filters/http/source/cgo.cc index 5117bc7b8b9d..0ab8c3aea9e9 100644 --- a/contrib/golang/filters/http/source/cgo.cc +++ b/contrib/golang/filters/http/source/cgo.cc @@ -51,11 +51,31 @@ std::vector stringsFromGoSlice(void* slice_data, int slice_len) { extern "C" { #endif +CAPIStatus envoyGoFilterProcessStateHandlerWrapper( + void* s, std::function&, ProcessorState&)> f) { + auto state = static_cast(reinterpret_cast(s)); + if (!state->isProcessingInGo()) { + return CAPIStatus::CAPINotInGo; + } + auto req = static_cast(state->req); + auto weak_filter = req->weakFilter(); + if (auto filter = weak_filter.lock()) { + return f(filter, *state); + } + return CAPIStatus::CAPIFilterIsGone; +} + CAPIStatus envoyGoFilterHandlerWrapper(void* r, std::function&)> f) { - auto req = reinterpret_cast(r); + auto req = reinterpret_cast(r); auto weak_filter = req->weakFilter(); if (auto filter = weak_filter.lock()) { + // Though it's memory safe without this limitation. + // But it's not a good idea to run Go code after continue back to Envoy C++, + // so, add this limitation. + if (!filter->isProcessingInGo()) { + return CAPIStatus::CAPINotInGo; + } return f(filter); } return CAPIStatus::CAPIFilterIsGone; @@ -71,25 +91,22 @@ envoyGoConfigHandlerWrapper(void* c, std::function& filter) -> CAPIStatus { return filter->clearRouteCache(); }); -} - -CAPIStatus envoyGoFilterHttpContinue(void* r, int status) { - return envoyGoFilterHandlerWrapper(r, [status](std::shared_ptr& filter) -> CAPIStatus { - return filter->continueStatus(static_cast(status)); - }); +CAPIStatus envoyGoFilterHttpContinue(void* s, int status) { + return envoyGoFilterProcessStateHandlerWrapper( + s, [status](std::shared_ptr& filter, ProcessorState& state) -> CAPIStatus { + return filter->continueStatus(state, static_cast(status)); + }); } -CAPIStatus envoyGoFilterHttpSendLocalReply(void* r, int response_code, void* body_text_data, +CAPIStatus envoyGoFilterHttpSendLocalReply(void* s, int response_code, void* body_text_data, int body_text_len, void* headers, int headers_num, long long int grpc_status, void* details_data, int details_len) { - return envoyGoFilterHandlerWrapper( - r, + return envoyGoFilterProcessStateHandlerWrapper( + s, [response_code, body_text_data, body_text_len, headers, headers_num, grpc_status, - details_data, details_len](std::shared_ptr& filter) -> CAPIStatus { + details_data, + details_len](std::shared_ptr& filter, ProcessorState& state) -> CAPIStatus { auto header_values = stringsFromGoSlice(headers, headers_num); std::function modify_headers = [header_values](Http::ResponseHeaderMap& headers) -> void { @@ -105,105 +122,128 @@ CAPIStatus envoyGoFilterHttpSendLocalReply(void* r, int response_code, void* bod // Deep clone the GoString into C++, since the GoString may be freed after the function // returns, while they may still be used in the callback. - return filter->sendLocalReply(static_cast(response_code), + return filter->sendLocalReply(state, static_cast(response_code), copyStringFromGoPointer(body_text_data, body_text_len), modify_headers, status, copyStringFromGoPointer(details_data, details_len)); }); } +CAPIStatus envoyGoFilterHttpSendPanicReply(void* s, void* details_data, int details_len) { + return envoyGoFilterProcessStateHandlerWrapper( + s, + [details_data, details_len](std::shared_ptr& filter, + ProcessorState& state) -> CAPIStatus { + // Since this is only used for logs we don't need to deep copy. + auto details = stringViewFromGoPointer(details_data, details_len); + return filter->sendPanicReply(state, details); + }); +} + // unsafe API, without copy memory from c to go. -CAPIStatus envoyGoFilterHttpGetHeader(void* r, void* key_data, int key_len, uint64_t* value_data, +CAPIStatus envoyGoFilterHttpGetHeader(void* s, void* key_data, int key_len, uint64_t* value_data, int* value_len) { - return envoyGoFilterHandlerWrapper( - r, [key_data, key_len, value_data, value_len](std::shared_ptr& filter) -> CAPIStatus { + return envoyGoFilterProcessStateHandlerWrapper( + s, + [key_data, key_len, value_data, value_len](std::shared_ptr& filter, + ProcessorState& state) -> CAPIStatus { auto key_str = stringViewFromGoPointer(key_data, key_len); - return filter->getHeader(key_str, value_data, value_len); + return filter->getHeader(state, key_str, value_data, value_len); }); } -CAPIStatus envoyGoFilterHttpCopyHeaders(void* r, void* strs, void* buf) { - return envoyGoFilterHandlerWrapper(r, [strs, buf](std::shared_ptr& filter) -> CAPIStatus { - auto go_strs = reinterpret_cast(strs); - auto go_buf = reinterpret_cast(buf); - return filter->copyHeaders(go_strs, go_buf); - }); +CAPIStatus envoyGoFilterHttpCopyHeaders(void* s, void* strs, void* buf) { + return envoyGoFilterProcessStateHandlerWrapper( + s, [strs, buf](std::shared_ptr& filter, ProcessorState& state) -> CAPIStatus { + auto go_strs = reinterpret_cast(strs); + auto go_buf = reinterpret_cast(buf); + return filter->copyHeaders(state, go_strs, go_buf); + }); } -CAPIStatus envoyGoFilterHttpSetHeaderHelper(void* r, void* key_data, int key_len, void* value_data, +CAPIStatus envoyGoFilterHttpSetHeaderHelper(void* s, void* key_data, int key_len, void* value_data, int value_len, headerAction act) { - return envoyGoFilterHandlerWrapper(r, - [key_data, key_len, value_data, value_len, - act](std::shared_ptr& filter) -> CAPIStatus { - auto key_str = stringViewFromGoPointer(key_data, key_len); - auto value_str = - stringViewFromGoPointer(value_data, value_len); - return filter->setHeader(key_str, value_str, act); - }); + return envoyGoFilterProcessStateHandlerWrapper( + s, + [key_data, key_len, value_data, value_len, act](std::shared_ptr& filter, + ProcessorState& state) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + auto value_str = stringViewFromGoPointer(value_data, value_len); + return filter->setHeader(state, key_str, value_str, act); + }); } -CAPIStatus envoyGoFilterHttpRemoveHeader(void* r, void* key_data, int key_len) { - return envoyGoFilterHandlerWrapper( - r, [key_data, key_len](std::shared_ptr& filter) -> CAPIStatus { +CAPIStatus envoyGoFilterHttpRemoveHeader(void* s, void* key_data, int key_len) { + return envoyGoFilterProcessStateHandlerWrapper( + s, [key_data, key_len](std::shared_ptr& filter, ProcessorState& state) -> CAPIStatus { auto key_str = stringViewFromGoPointer(key_data, key_len); - return filter->removeHeader(key_str); + return filter->removeHeader(state, key_str); }); } -CAPIStatus envoyGoFilterHttpGetBuffer(void* r, uint64_t buffer_ptr, void* data) { - return envoyGoFilterHandlerWrapper( - r, [buffer_ptr, data](std::shared_ptr& filter) -> CAPIStatus { +CAPIStatus envoyGoFilterHttpGetBuffer(void* s, uint64_t buffer_ptr, void* data) { + return envoyGoFilterProcessStateHandlerWrapper( + s, [buffer_ptr, data](std::shared_ptr& filter, ProcessorState& state) -> CAPIStatus { auto buffer = reinterpret_cast(buffer_ptr); - return filter->copyBuffer(buffer, reinterpret_cast(data)); + return filter->copyBuffer(state, buffer, reinterpret_cast(data)); }); } -CAPIStatus envoyGoFilterHttpDrainBuffer(void* r, uint64_t buffer_ptr, uint64_t length) { - return envoyGoFilterHandlerWrapper( - r, [buffer_ptr, length](std::shared_ptr& filter) -> CAPIStatus { +CAPIStatus envoyGoFilterHttpDrainBuffer(void* s, uint64_t buffer_ptr, uint64_t length) { + return envoyGoFilterProcessStateHandlerWrapper( + s, + [buffer_ptr, length](std::shared_ptr& filter, ProcessorState& state) -> CAPIStatus { auto buffer = reinterpret_cast(buffer_ptr); - return filter->drainBuffer(buffer, length); + return filter->drainBuffer(state, buffer, length); }); } -CAPIStatus envoyGoFilterHttpSetBufferHelper(void* r, uint64_t buffer_ptr, void* data, int length, +CAPIStatus envoyGoFilterHttpSetBufferHelper(void* s, uint64_t buffer_ptr, void* data, int length, bufferAction action) { - return envoyGoFilterHandlerWrapper( - r, [buffer_ptr, data, length, action](std::shared_ptr& filter) -> CAPIStatus { + return envoyGoFilterProcessStateHandlerWrapper( + s, + [buffer_ptr, data, length, action](std::shared_ptr& filter, + ProcessorState& state) -> CAPIStatus { auto buffer = reinterpret_cast(buffer_ptr); auto value = stringViewFromGoPointer(data, length); - return filter->setBufferHelper(buffer, value, action); + return filter->setBufferHelper(state, buffer, value, action); }); } -CAPIStatus envoyGoFilterHttpCopyTrailers(void* r, void* strs, void* buf) { - return envoyGoFilterHandlerWrapper(r, [strs, buf](std::shared_ptr& filter) -> CAPIStatus { - auto go_strs = reinterpret_cast(strs); - auto go_buf = reinterpret_cast(buf); - return filter->copyTrailers(go_strs, go_buf); - }); +CAPIStatus envoyGoFilterHttpCopyTrailers(void* s, void* strs, void* buf) { + return envoyGoFilterProcessStateHandlerWrapper( + s, [strs, buf](std::shared_ptr& filter, ProcessorState& state) -> CAPIStatus { + auto go_strs = reinterpret_cast(strs); + auto go_buf = reinterpret_cast(buf); + return filter->copyTrailers(state, go_strs, go_buf); + }); } -CAPIStatus envoyGoFilterHttpSetTrailer(void* r, void* key_data, int key_len, void* value_data, +CAPIStatus envoyGoFilterHttpSetTrailer(void* s, void* key_data, int key_len, void* value_data, int value_len, headerAction act) { - return envoyGoFilterHandlerWrapper(r, - [key_data, key_len, value_data, value_len, - act](std::shared_ptr& filter) -> CAPIStatus { - auto key_str = stringViewFromGoPointer(key_data, key_len); - auto value_str = - stringViewFromGoPointer(value_data, value_len); - return filter->setTrailer(key_str, value_str, act); - }); + return envoyGoFilterProcessStateHandlerWrapper( + s, + [key_data, key_len, value_data, value_len, act](std::shared_ptr& filter, + ProcessorState& state) -> CAPIStatus { + auto key_str = stringViewFromGoPointer(key_data, key_len); + auto value_str = stringViewFromGoPointer(value_data, value_len); + return filter->setTrailer(state, key_str, value_str, act); + }); } -CAPIStatus envoyGoFilterHttpRemoveTrailer(void* r, void* key_data, int key_len) { - return envoyGoFilterHandlerWrapper( - r, [key_data, key_len](std::shared_ptr& filter) -> CAPIStatus { +CAPIStatus envoyGoFilterHttpRemoveTrailer(void* s, void* key_data, int key_len) { + return envoyGoFilterProcessStateHandlerWrapper( + s, [key_data, key_len](std::shared_ptr& filter, ProcessorState& state) -> CAPIStatus { auto key_str = stringViewFromGoPointer(key_data, key_len); - return filter->removeTrailer(key_str); + return filter->removeTrailer(state, key_str); }); } +CAPIStatus envoyGoFilterHttpClearRouteCache(void* r) { + return envoyGoFilterHandlerWrapper( + r, [](std::shared_ptr& filter) -> CAPIStatus { return filter->clearRouteCache(); }); +} + CAPIStatus envoyGoFilterHttpGetStringValue(void* r, int id, uint64_t* value_data, int* value_len) { return envoyGoFilterHandlerWrapper( r, [id, value_data, value_len](std::shared_ptr& filter) -> CAPIStatus { @@ -244,8 +284,20 @@ void envoyGoFilterHttpFinalize(void* r, int reason) { UNREFERENCED_PARAMETER(reason); // req is used by go, so need to use raw memory and then it is safe to release at the gc finalize // phase of the go object. - auto req = reinterpret_cast(r); - delete req; + auto req = reinterpret_cast(r); + auto weak_filter = req->weakFilter(); + if (auto filter = weak_filter.lock()) { + // Finalize must happens after onDestory, that means Filter is marked as destroyed. + // When filter is still existing, it could happens in very low rate, since Golang GC + // finalizer delays execution. + // Now, the race is there might be filter method running, i.e. continueStatusInternal may invoke + // onDestroy, and check state in request after it. + // So, we'd better to defer delete the request. + filter->deferredDeleteRequest(req); + } else { + // It's safe to delete directly since filter is not existing. + delete req; + } } void envoyGoConfigHttpFinalize(void* c) { @@ -255,15 +307,6 @@ void envoyGoConfigHttpFinalize(void* c) { delete config; } -CAPIStatus envoyGoFilterHttpSendPanicReply(void* r, void* details_data, int details_len) { - return envoyGoFilterHandlerWrapper( - r, [details_data, details_len](std::shared_ptr& filter) -> CAPIStatus { - // Since this is only used for logs we don't need to deep copy. - auto details = stringViewFromGoPointer(details_data, details_len); - return filter->sendPanicReply(details); - }); -} - CAPIStatus envoyGoFilterHttpSetStringFilterState(void* r, void* key_data, int key_len, void* value_data, int value_len, int state_type, int life_span, int stream_sharing) { diff --git a/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go b/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go index 5f44b7acdfa5..590238f23cb3 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go +++ b/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go @@ -110,19 +110,16 @@ func capiStatusToErr(status C.CAPIStatus) error { return errors.New("unknown status") } -func (c *httpCApiImpl) ClearRouteCache(r unsafe.Pointer) { - res := C.envoyGoFilterHttpClearRouteCache(r) - handleCApiStatus(res) -} - -func (c *httpCApiImpl) HttpContinue(r unsafe.Pointer, status uint64) { - res := C.envoyGoFilterHttpContinue(r, C.int(status)) +func (c *httpCApiImpl) HttpContinue(s unsafe.Pointer, status uint64) { + state := (*processState)(s) + res := C.envoyGoFilterHttpContinue(unsafe.Pointer(state.processState), C.int(status)) handleCApiStatus(res) } // Only may panic with errRequestFinished, errFilterDestroyed or errNotInGo, // won't panic with errInvalidPhase and others, otherwise will cause deadloop, see RecoverPanic for the details. -func (c *httpCApiImpl) HttpSendLocalReply(r unsafe.Pointer, responseCode int, bodyText string, headers map[string][]string, grpcStatus int64, details string) { +func (c *httpCApiImpl) HttpSendLocalReply(s unsafe.Pointer, responseCode int, bodyText string, headers map[string][]string, grpcStatus int64, details string) { + state := (*processState)(s) hLen := len(headers) strs := make([]*C.char, 0, hLen*2) defer func() { @@ -138,27 +135,30 @@ func (c *httpCApiImpl) HttpSendLocalReply(r unsafe.Pointer, responseCode int, bo strs = append(strs, keyStr, valueStr) } } - res := C.envoyGoFilterHttpSendLocalReply(r, C.int(responseCode), + res := C.envoyGoFilterHttpSendLocalReply(unsafe.Pointer(state.processState), C.int(responseCode), unsafe.Pointer(unsafe.StringData(bodyText)), C.int(len(bodyText)), unsafe.Pointer(unsafe.SliceData(strs)), C.int(len(strs)), C.longlong(grpcStatus), unsafe.Pointer(unsafe.StringData(details)), C.int(len(details))) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpSendPanicReply(r unsafe.Pointer, details string) { - res := C.envoyGoFilterHttpSendPanicReply(r, unsafe.Pointer(unsafe.StringData(details)), C.int(len(details))) +func (c *httpCApiImpl) HttpSendPanicReply(s unsafe.Pointer, details string) { + state := (*processState)(s) + res := C.envoyGoFilterHttpSendPanicReply(unsafe.Pointer(state.processState), unsafe.Pointer(unsafe.StringData(details)), C.int(len(details))) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpGetHeader(r unsafe.Pointer, key string) string { +func (c *httpCApiImpl) HttpGetHeader(s unsafe.Pointer, key string) string { + state := (*processState)(s) var valueData C.uint64_t var valueLen C.int - res := C.envoyGoFilterHttpGetHeader(r, unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), &valueData, &valueLen) + res := C.envoyGoFilterHttpGetHeader(unsafe.Pointer(state.processState), unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), &valueData, &valueLen) handleCApiStatus(res) return unsafe.String((*byte)(unsafe.Pointer(uintptr(valueData))), int(valueLen)) } -func (c *httpCApiImpl) HttpCopyHeaders(r unsafe.Pointer, num uint64, bytes uint64) map[string][]string { +func (c *httpCApiImpl) HttpCopyHeaders(s unsafe.Pointer, num uint64, bytes uint64) map[string][]string { + state := (*processState)(s) var strs []string if num <= maxStackAllocedHeaderSize { // NOTE: only const length slice may be allocated on stack. @@ -173,7 +173,7 @@ func (c *httpCApiImpl) HttpCopyHeaders(r unsafe.Pointer, num uint64, bytes uint6 // we have to make sure the all strings is not using before reusing, // but strings may be alive beyond the request life. buf := make([]byte, bytes) - res := C.envoyGoFilterHttpCopyHeaders(r, unsafe.Pointer(unsafe.SliceData(strs)), unsafe.Pointer(unsafe.SliceData(buf))) + res := C.envoyGoFilterHttpCopyHeaders(unsafe.Pointer(state.processState), unsafe.Pointer(unsafe.SliceData(strs)), unsafe.Pointer(unsafe.SliceData(buf))) handleCApiStatus(res) m := make(map[string][]string, num) @@ -191,44 +191,50 @@ func (c *httpCApiImpl) HttpCopyHeaders(r unsafe.Pointer, num uint64, bytes uint6 return m } -func (c *httpCApiImpl) HttpSetHeader(r unsafe.Pointer, key string, value string, add bool) { +func (c *httpCApiImpl) HttpSetHeader(s unsafe.Pointer, key string, value string, add bool) { + state := (*processState)(s) var act C.headerAction if add { act = C.HeaderAdd } else { act = C.HeaderSet } - res := C.envoyGoFilterHttpSetHeaderHelper(r, unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), + res := C.envoyGoFilterHttpSetHeaderHelper(unsafe.Pointer(state.processState), unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), unsafe.Pointer(unsafe.StringData(value)), C.int(len(value)), act) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpRemoveHeader(r unsafe.Pointer, key string) { - res := C.envoyGoFilterHttpRemoveHeader(r, unsafe.Pointer(unsafe.StringData(key)), C.int(len(key))) +func (c *httpCApiImpl) HttpRemoveHeader(s unsafe.Pointer, key string) { + state := (*processState)(s) + res := C.envoyGoFilterHttpRemoveHeader(unsafe.Pointer(state.processState), unsafe.Pointer(unsafe.StringData(key)), C.int(len(key))) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpGetBuffer(r unsafe.Pointer, bufferPtr uint64, length uint64) []byte { +func (c *httpCApiImpl) HttpGetBuffer(s unsafe.Pointer, bufferPtr uint64, length uint64) []byte { + state := (*processState)(s) buf := make([]byte, length) - res := C.envoyGoFilterHttpGetBuffer(r, C.uint64_t(bufferPtr), unsafe.Pointer(unsafe.SliceData(buf))) + res := C.envoyGoFilterHttpGetBuffer(unsafe.Pointer(state.processState), C.uint64_t(bufferPtr), unsafe.Pointer(unsafe.SliceData(buf))) handleCApiStatus(res) return unsafe.Slice(unsafe.SliceData(buf), length) } -func (c *httpCApiImpl) HttpDrainBuffer(r unsafe.Pointer, bufferPtr uint64, length uint64) { - res := C.envoyGoFilterHttpDrainBuffer(r, C.uint64_t(bufferPtr), C.uint64_t(length)) +func (c *httpCApiImpl) HttpDrainBuffer(s unsafe.Pointer, bufferPtr uint64, length uint64) { + state := (*processState)(s) + res := C.envoyGoFilterHttpDrainBuffer(unsafe.Pointer(state.processState), C.uint64_t(bufferPtr), C.uint64_t(length)) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpSetBufferHelper(r unsafe.Pointer, bufferPtr uint64, value string, action api.BufferAction) { - c.httpSetBufferHelper(r, bufferPtr, unsafe.Pointer(unsafe.StringData(value)), C.int(len(value)), action) +func (c *httpCApiImpl) HttpSetBufferHelper(s unsafe.Pointer, bufferPtr uint64, value string, action api.BufferAction) { + state := (*processState)(s) + c.httpSetBufferHelper(state, bufferPtr, unsafe.Pointer(unsafe.StringData(value)), C.int(len(value)), action) } -func (c *httpCApiImpl) HttpSetBytesBufferHelper(r unsafe.Pointer, bufferPtr uint64, value []byte, action api.BufferAction) { - c.httpSetBufferHelper(r, bufferPtr, unsafe.Pointer(unsafe.SliceData(value)), C.int(len(value)), action) +func (c *httpCApiImpl) HttpSetBytesBufferHelper(s unsafe.Pointer, bufferPtr uint64, value []byte, action api.BufferAction) { + state := (*processState)(s) + c.httpSetBufferHelper(state, bufferPtr, unsafe.Pointer(unsafe.SliceData(value)), C.int(len(value)), action) } -func (c *httpCApiImpl) httpSetBufferHelper(r unsafe.Pointer, bufferPtr uint64, data unsafe.Pointer, length C.int, action api.BufferAction) { +func (c *httpCApiImpl) httpSetBufferHelper(state *processState, bufferPtr uint64, data unsafe.Pointer, length C.int, action api.BufferAction) { var act C.bufferAction switch action { case api.SetBuffer: @@ -238,11 +244,12 @@ func (c *httpCApiImpl) httpSetBufferHelper(r unsafe.Pointer, bufferPtr uint64, d case api.PrependBuffer: act = C.Prepend } - res := C.envoyGoFilterHttpSetBufferHelper(r, C.uint64_t(bufferPtr), data, length, act) + res := C.envoyGoFilterHttpSetBufferHelper(unsafe.Pointer(state.processState), C.uint64_t(bufferPtr), data, length, act) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpCopyTrailers(r unsafe.Pointer, num uint64, bytes uint64) map[string][]string { +func (c *httpCApiImpl) HttpCopyTrailers(s unsafe.Pointer, num uint64, bytes uint64) map[string][]string { + state := (*processState)(s) var strs []string if num <= maxStackAllocedHeaderSize { // NOTE: only const length slice may be allocated on stack. @@ -257,7 +264,7 @@ func (c *httpCApiImpl) HttpCopyTrailers(r unsafe.Pointer, num uint64, bytes uint // we have to make sure the all strings is not using before reusing, // but strings may be alive beyond the request life. buf := make([]byte, bytes) - res := C.envoyGoFilterHttpCopyTrailers(r, unsafe.Pointer(unsafe.SliceData(strs)), unsafe.Pointer(unsafe.SliceData(buf))) + res := C.envoyGoFilterHttpCopyTrailers(unsafe.Pointer(state.processState), unsafe.Pointer(unsafe.SliceData(strs)), unsafe.Pointer(unsafe.SliceData(buf))) handleCApiStatus(res) m := make(map[string][]string, num) @@ -275,33 +282,41 @@ func (c *httpCApiImpl) HttpCopyTrailers(r unsafe.Pointer, num uint64, bytes uint return m } -func (c *httpCApiImpl) HttpSetTrailer(r unsafe.Pointer, key string, value string, add bool) { +func (c *httpCApiImpl) HttpSetTrailer(s unsafe.Pointer, key string, value string, add bool) { + state := (*processState)(s) var act C.headerAction if add { act = C.HeaderAdd } else { act = C.HeaderSet } - res := C.envoyGoFilterHttpSetTrailer(r, unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), + res := C.envoyGoFilterHttpSetTrailer(unsafe.Pointer(state.processState), unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), unsafe.Pointer(unsafe.StringData(value)), C.int(len(value)), act) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpRemoveTrailer(r unsafe.Pointer, key string) { - res := C.envoyGoFilterHttpRemoveTrailer(r, unsafe.Pointer(unsafe.StringData(key)), C.int(len(key))) +func (c *httpCApiImpl) HttpRemoveTrailer(s unsafe.Pointer, key string) { + state := (*processState)(s) + res := C.envoyGoFilterHttpRemoveTrailer(unsafe.Pointer(state.processState), unsafe.Pointer(unsafe.StringData(key)), C.int(len(key))) + handleCApiStatus(res) +} + +func (c *httpCApiImpl) ClearRouteCache(r unsafe.Pointer) { + req := (*httpRequest)(r) + res := C.envoyGoFilterHttpClearRouteCache(unsafe.Pointer(req.req)) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpGetStringValue(rr unsafe.Pointer, id int) (string, bool) { - r := (*httpRequest)(rr) +func (c *httpCApiImpl) HttpGetStringValue(r unsafe.Pointer, id int) (string, bool) { + req := (*httpRequest)(r) // add a lock to protect filter->req_->strValue field in the Envoy side, from being writing concurrency, // since there might be multiple concurrency goroutines invoking this API on the Go side. - r.mutex.Lock() - defer r.mutex.Unlock() + req.mutex.Lock() + defer req.mutex.Unlock() var valueData C.uint64_t var valueLen C.int - res := C.envoyGoFilterHttpGetStringValue(unsafe.Pointer(r.req), C.int(id), &valueData, &valueLen) + res := C.envoyGoFilterHttpGetStringValue(unsafe.Pointer(req.req), C.int(id), &valueData, &valueLen) if res == C.CAPIValueNotFound { return "", false } @@ -312,8 +327,9 @@ func (c *httpCApiImpl) HttpGetStringValue(rr unsafe.Pointer, id int) (string, bo } func (c *httpCApiImpl) HttpGetIntegerValue(r unsafe.Pointer, id int) (uint64, bool) { + req := (*httpRequest)(r) var value C.uint64_t - res := C.envoyGoFilterHttpGetIntegerValue(r, C.int(id), &value) + res := C.envoyGoFilterHttpGetIntegerValue(unsafe.Pointer(req.req), C.int(id), &value) if res == C.CAPIValueNotFound { return 0, false } @@ -321,20 +337,20 @@ func (c *httpCApiImpl) HttpGetIntegerValue(r unsafe.Pointer, id int) (uint64, bo return uint64(value), true } -func (c *httpCApiImpl) HttpGetDynamicMetadata(rr unsafe.Pointer, filterName string) map[string]interface{} { - r := (*httpRequest)(rr) - r.mutex.Lock() - defer r.mutex.Unlock() - r.markMayWaitingCallback() +func (c *httpCApiImpl) HttpGetDynamicMetadata(r unsafe.Pointer, filterName string) map[string]interface{} { + req := (*httpRequest)(r) + req.mutex.Lock() + defer req.mutex.Unlock() + req.markMayWaitingCallback() var valueData C.uint64_t var valueLen C.int - res := C.envoyGoFilterHttpGetDynamicMetadata(unsafe.Pointer(r.req), + res := C.envoyGoFilterHttpGetDynamicMetadata(unsafe.Pointer(req.req), unsafe.Pointer(unsafe.StringData(filterName)), C.int(len(filterName)), &valueData, &valueLen) if res == C.CAPIYield { - r.checkOrWaitCallback() + req.checkOrWaitCallback() } else { - r.markNoWaitingCallback() + req.markNoWaitingCallback() handleCApiStatus(res) } buf := unsafe.Slice((*byte)(unsafe.Pointer(uintptr(valueData))), int(valueLen)) @@ -345,6 +361,7 @@ func (c *httpCApiImpl) HttpGetDynamicMetadata(rr unsafe.Pointer, filterName stri } func (c *httpCApiImpl) HttpSetDynamicMetadata(r unsafe.Pointer, filterName string, key string, value interface{}) { + req := (*httpRequest)(r) v, err := structpb.NewValue(value) if err != nil { panic(err) @@ -353,57 +370,40 @@ func (c *httpCApiImpl) HttpSetDynamicMetadata(r unsafe.Pointer, filterName strin if err != nil { panic(err) } - res := C.envoyGoFilterHttpSetDynamicMetadata(r, + res := C.envoyGoFilterHttpSetDynamicMetadata(unsafe.Pointer(req.req), unsafe.Pointer(unsafe.StringData(filterName)), C.int(len(filterName)), unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), unsafe.Pointer(unsafe.SliceData(buf)), C.int(len(buf))) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpLog(level api.LogType, message string) { - C.envoyGoFilterLog(C.uint32_t(level), unsafe.Pointer(unsafe.StringData(message)), C.int(len(message))) -} - -func (c *httpCApiImpl) HttpLogLevel() api.LogType { - return api.GetLogLevel() -} - func (c *httpCApiImpl) HttpFinalize(r unsafe.Pointer, reason int) { - C.envoyGoFilterHttpFinalize(r, C.int(reason)) -} - -func (c *httpCApiImpl) HttpConfigFinalize(cfg unsafe.Pointer) { - C.envoyGoConfigHttpFinalize(cfg) -} - -var cAPI api.HttpCAPI = &httpCApiImpl{} - -// SetHttpCAPI for mock cAPI -func SetHttpCAPI(api api.HttpCAPI) { - cAPI = api + req := (*httpRequest)(r) + C.envoyGoFilterHttpFinalize(unsafe.Pointer(req.req), C.int(reason)) } func (c *httpCApiImpl) HttpSetStringFilterState(r unsafe.Pointer, key string, value string, stateType api.StateType, lifeSpan api.LifeSpan, streamSharing api.StreamSharing) { - res := C.envoyGoFilterHttpSetStringFilterState(r, + req := (*httpRequest)(r) + res := C.envoyGoFilterHttpSetStringFilterState(unsafe.Pointer(req.req), unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), unsafe.Pointer(unsafe.StringData(value)), C.int(len(value)), C.int(stateType), C.int(lifeSpan), C.int(streamSharing)) handleCApiStatus(res) } -func (c *httpCApiImpl) HttpGetStringFilterState(rr unsafe.Pointer, key string) string { - r := (*httpRequest)(rr) +func (c *httpCApiImpl) HttpGetStringFilterState(r unsafe.Pointer, key string) string { + req := (*httpRequest)(r) var valueData C.uint64_t var valueLen C.int - r.mutex.Lock() - defer r.mutex.Unlock() - r.markMayWaitingCallback() - res := C.envoyGoFilterHttpGetStringFilterState(unsafe.Pointer(r.req), + req.mutex.Lock() + defer req.mutex.Unlock() + req.markMayWaitingCallback() + res := C.envoyGoFilterHttpGetStringFilterState(unsafe.Pointer(req.req), unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), &valueData, &valueLen) if res == C.CAPIYield { - r.checkOrWaitCallback() + req.checkOrWaitCallback() } else { - r.markNoWaitingCallback() + req.markNoWaitingCallback() handleCApiStatus(res) } @@ -411,21 +411,21 @@ func (c *httpCApiImpl) HttpGetStringFilterState(rr unsafe.Pointer, key string) s return strings.Clone(value) } -func (c *httpCApiImpl) HttpGetStringProperty(rr unsafe.Pointer, key string) (string, error) { - r := (*httpRequest)(rr) +func (c *httpCApiImpl) HttpGetStringProperty(r unsafe.Pointer, key string) (string, error) { + req := (*httpRequest)(r) var valueData C.uint64_t var valueLen C.int var rc C.int - r.mutex.Lock() - defer r.mutex.Unlock() - r.markMayWaitingCallback() - res := C.envoyGoFilterHttpGetStringProperty(unsafe.Pointer(r.req), + req.mutex.Lock() + defer req.mutex.Unlock() + req.markMayWaitingCallback() + res := C.envoyGoFilterHttpGetStringProperty(unsafe.Pointer(req.req), unsafe.Pointer(unsafe.StringData(key)), C.int(len(key)), &valueData, &valueLen, &rc) if res == C.CAPIYield { - r.checkOrWaitCallback() + req.checkOrWaitCallback() res = C.CAPIStatus(rc) } else { - r.markNoWaitingCallback() + req.markNoWaitingCallback() handleCApiStatus(res) } @@ -437,6 +437,25 @@ func (c *httpCApiImpl) HttpGetStringProperty(rr unsafe.Pointer, key string) (str return "", capiStatusToErr(res) } +func (c *httpCApiImpl) HttpLog(level api.LogType, message string) { + C.envoyGoFilterLog(C.uint32_t(level), unsafe.Pointer(unsafe.StringData(message)), C.int(len(message))) +} + +func (c *httpCApiImpl) HttpLogLevel() api.LogType { + return api.GetLogLevel() +} + +var cAPI api.HttpCAPI = &httpCApiImpl{} + +// SetHttpCAPI for mock cAPI +func SetHttpCAPI(api api.HttpCAPI) { + cAPI = api +} + +func (c *httpCApiImpl) HttpConfigFinalize(cfg unsafe.Pointer) { + C.envoyGoConfigHttpFinalize(cfg) +} + func (c *httpCApiImpl) HttpDefineMetric(cfg unsafe.Pointer, metricType api.MetricType, name string) uint32 { var value C.uint32_t res := C.envoyGoFilterHttpDefineMetric(cfg, C.uint32_t(metricType), unsafe.Pointer(unsafe.StringData(name)), C.int(len(name)), &value) diff --git a/contrib/golang/filters/http/source/go/pkg/http/filter.go b/contrib/golang/filters/http/source/go/pkg/http/filter.go index 7c348890eb7a..62ef2c19fcb9 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/filter.go +++ b/contrib/golang/filters/http/source/go/pkg/http/filter.go @@ -64,6 +64,7 @@ type panicInfo struct { paniced bool details string } + type httpRequest struct { req *C.httpRequest httpFilter api.StreamFilter @@ -76,6 +77,106 @@ type httpRequest struct { // 1. protect req_->strValue in the C++ side from being used concurrently. // 2. protect waitingCallback from being modified in markMayWaitingCallback concurrently. mutex sync.Mutex + + // decodingState and encodingState are part of httpRequest, not another GC object. + // So, no cycle reference, GC finalizer could work well. + decodingState processState + encodingState processState + streamInfo streamInfo +} + +// processState implements the FilterCallbacks interface. +type processState struct { + request *httpRequest + processState *C.processState +} + +const ( + // Values align with "enum class FilterState" in C++ + ProcessingHeader = 1 + ProcessingData = 4 + ProcessingTrailer = 6 +) + +func (s *processState) Phase() api.EnvoyRequestPhase { + if s.processState.is_encoding == 0 { + switch int(s.processState.state) { + case ProcessingHeader: + return api.DecodeHeaderPhase + case ProcessingData: + return api.DecodeDataPhase + case ProcessingTrailer: + return api.DecodeTrailerPhase + } + } + // s.processState.is_encoding == 1 + switch int(s.processState.state) { + case ProcessingHeader: + return api.EncodeHeaderPhase + case ProcessingData: + return api.EncodeDataPhase + case ProcessingTrailer: + return api.EncodeTrailerPhase + } + panic(fmt.Errorf("unexpected state, is_encoding: %d, state: %d", s.processState.is_encoding, s.processState.state)) +} + +func (s *processState) Continue(status api.StatusType) { + cAPI.HttpContinue(unsafe.Pointer(s), uint64(status)) +} + +func (s *processState) SendLocalReply(responseCode int, bodyText string, headers map[string][]string, grpcStatus int64, details string) { + cAPI.HttpSendLocalReply(unsafe.Pointer(s), responseCode, bodyText, headers, grpcStatus, details) +} + +func (s *processState) sendPanicReply(details string) { + defer s.RecoverPanic() + cAPI.HttpSendPanicReply(unsafe.Pointer(s), details) +} + +func (s *processState) RecoverPanic() { + if e := recover(); e != nil { + buf := debug.Stack() + + if e == errRequestFinished || e == errFilterDestroyed { + api.LogInfof("http: panic serving: %v (Client may cancel the request prematurely)\n%s", e, buf) + } else { + api.LogErrorf("http: panic serving: %v\n%s", e, buf) + } + + switch e { + case errRequestFinished, errFilterDestroyed: + // do nothing + + case errNotInGo: + // We can not send local reply now, since not in go now, + // will delay to the next time entering Go. + s.request.pInfo = panicInfo{ + paniced: true, + details: fmt.Sprint(e), + } + + default: + // The following safeReplyPanic should only may get errRequestFinished, + // errFilterDestroyed or errNotInGo, won't hit this branch, so, won't dead loop here. + + // errInvalidPhase, or other panic, not from not-ok C return status. + // It's safe to try send a local reply with 500 status. + s.sendPanicReply(fmt.Sprint(e)) + } + } +} + +func (r *httpRequest) StreamInfo() api.StreamInfo { + return &r.streamInfo +} + +func (r *httpRequest) DecoderFilterCallbacks() api.DecoderFilterCallbacks { + return &r.decodingState +} + +func (r *httpRequest) EncoderFilterCallbacks() api.EncoderFilterCallbacks { + return &r.encodingState } // markWaitingOnEnvoy marks the request may be waiting a callback from envoy. @@ -122,12 +223,8 @@ func (r *httpRequest) pluginName() string { return C.GoStringN(r.req.plugin_name.data, C.int(r.req.plugin_name.len)) } -func (r *httpRequest) sendPanicReply(details string) { - defer r.RecoverPanic() - cAPI.HttpSendPanicReply(unsafe.Pointer(r.req), details) -} - -func (r *httpRequest) RecoverPanic() { +// recover goroutine to stop Envoy process crashing when panic happens +func (r *httpRequest) recoverPanic() { if e := recover(); e != nil { buf := debug.Stack() @@ -136,32 +233,11 @@ func (r *httpRequest) RecoverPanic() { } else { api.LogErrorf("http: panic serving: %v\n%s", e, buf) } - - switch e { - case errRequestFinished, errFilterDestroyed: - // do nothing - - case errNotInGo: - // We can not send local reply now, since not in go now, - // will delay to the next time entering Go. - r.pInfo = panicInfo{ - paniced: true, - details: fmt.Sprint(e), - } - - default: - // The following safeReplyPanic should only may get errRequestFinished, - // errFilterDestroyed or errNotInGo, won't hit this branch, so, won't dead loop here. - - // errInvalidPhase, or other panic, not from not-ok C return status. - // It's safe to try send a local reply with 500 status. - r.sendPanicReply(fmt.Sprint(e)) - } } } func (r *httpRequest) ClearRouteCache() { - cAPI.ClearRouteCache(unsafe.Pointer(r.req)) + cAPI.ClearRouteCache(unsafe.Pointer(r)) } func (r *httpRequest) Continue(status api.StatusType) { @@ -192,14 +268,8 @@ func (r *httpRequest) GetProperty(key string) (string, error) { return cAPI.HttpGetStringProperty(unsafe.Pointer(r), key) } -func (r *httpRequest) StreamInfo() api.StreamInfo { - return &streamInfo{ - request: r, - } -} - func (r *httpRequest) Finalize(reason int) { - cAPI.HttpFinalize(unsafe.Pointer(r.req), reason) + cAPI.HttpFinalize(unsafe.Pointer(r), reason) } type streamInfo struct { @@ -217,7 +287,7 @@ func (s *streamInfo) FilterChainName() string { } func (s *streamInfo) Protocol() (string, bool) { - if protocol, ok := cAPI.HttpGetIntegerValue(unsafe.Pointer(s.request.req), ValueProtocol); ok { + if protocol, ok := cAPI.HttpGetIntegerValue(unsafe.Pointer(s.request), ValueProtocol); ok { if name, ok := protocolsIdToName[protocol]; ok { return name, true } @@ -227,7 +297,7 @@ func (s *streamInfo) Protocol() (string, bool) { } func (s *streamInfo) ResponseCode() (uint32, bool) { - if code, ok := cAPI.HttpGetIntegerValue(unsafe.Pointer(s.request.req), ValueResponseCode); ok { + if code, ok := cAPI.HttpGetIntegerValue(unsafe.Pointer(s.request), ValueResponseCode); ok { return uint32(code), true } return 0, false @@ -238,7 +308,7 @@ func (s *streamInfo) ResponseCodeDetails() (string, bool) { } func (s *streamInfo) AttemptCount() uint32 { - count, _ := cAPI.HttpGetIntegerValue(unsafe.Pointer(s.request.req), ValueAttemptCount) + count, _ := cAPI.HttpGetIntegerValue(unsafe.Pointer(s.request), ValueAttemptCount) return uint32(count) } @@ -257,7 +327,7 @@ func (d *dynamicMetadata) Get(filterName string) map[string]interface{} { } func (d *dynamicMetadata) Set(filterName string, key string, value interface{}) { - cAPI.HttpSetDynamicMetadata(unsafe.Pointer(d.request.req), filterName, key, value) + cAPI.HttpSetDynamicMetadata(unsafe.Pointer(d.request), filterName, key, value) } func (s *streamInfo) DownstreamLocalAddress() string { @@ -303,7 +373,7 @@ func (s *streamInfo) FilterState() api.FilterState { } func (f *filterState) SetString(key, value string, stateType api.StateType, lifeSpan api.LifeSpan, streamSharing api.StreamSharing) { - cAPI.HttpSetStringFilterState(unsafe.Pointer(f.request.req), key, value, stateType, lifeSpan, streamSharing) + cAPI.HttpSetStringFilterState(unsafe.Pointer(f.request), key, value, stateType, lifeSpan, streamSharing) } func (f *filterState) GetString(key string) string { diff --git a/contrib/golang/filters/http/source/go/pkg/http/shim.go b/contrib/golang/filters/http/source/go/pkg/http/shim.go index d79a80659ab7..229f2c9aa0fc 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/shim.go +++ b/contrib/golang/filters/http/source/go/pkg/http/shim.go @@ -105,10 +105,34 @@ func requestFinalize(r *httpRequest) { r.Finalize(api.NormalFinalize) } +func getOrCreateState(s *C.processState) *processState { + r := s.req + req := getRequest(r) + if req == nil { + req = createRequest(r) + } + if s.is_encoding == 0 { + if req.decodingState.processState == nil { + req.decodingState.processState = s + } + return &req.decodingState + } + + // s.is_encoding == 1 + if req.encodingState.processState == nil { + req.encodingState.processState = s + } + return &req.encodingState +} + func createRequest(r *C.httpRequest) *httpRequest { req := &httpRequest{ req: r, } + req.decodingState.request = req + req.encodingState.request = req + req.streamInfo.request = req + req.cond.L = &req.waitingLock // NP: make sure filter will be deleted. runtime.SetFinalizer(req, requestFinalize) @@ -130,32 +154,38 @@ func getRequest(r *C.httpRequest) *httpRequest { return Requests.GetReq(r) } +func getState(s *C.processState) *processState { + r := s.req + req := getRequest(r) + if s.is_encoding == 0 { + return &req.decodingState + } + // s.is_encoding == 1 + return &req.encodingState +} + //export envoyGoFilterOnHttpHeader -func envoyGoFilterOnHttpHeader(r *C.httpRequest, endStream, headerNum, headerBytes uint64) uint64 { - var req *httpRequest - phase := api.EnvoyRequestPhase(r.phase) +func envoyGoFilterOnHttpHeader(s *C.processState, endStream, headerNum, headerBytes uint64) uint64 { // early SendLocalReply or OnLogDownstreamStart may run before the header handling - req = getRequest(r) - if req == nil { - req = createRequest(r) - } + state := getOrCreateState(s) + req := state.request if req.pInfo.paniced { // goroutine panic in the previous state that could not sendLocalReply, delay terminating the request here, // to prevent error from spreading. - req.sendPanicReply(req.pInfo.details) + state.sendPanicReply(req.pInfo.details) return uint64(api.LocalReply) } - defer req.RecoverPanic() + defer state.RecoverPanic() f := req.httpFilter var status api.StatusType - switch phase { + switch state.Phase() { case api.DecodeHeaderPhase: header := &requestHeaderMapImpl{ requestOrResponseHeaderMapImpl{ headerMapImpl{ - request: req, + state: state, headerNum: headerNum, headerBytes: headerBytes, }, @@ -166,7 +196,7 @@ func envoyGoFilterOnHttpHeader(r *C.httpRequest, endStream, headerNum, headerByt header := &requestTrailerMapImpl{ requestOrResponseTrailerMapImpl{ headerMapImpl{ - request: req, + state: state, headerNum: headerNum, headerBytes: headerBytes, }, @@ -177,7 +207,7 @@ func envoyGoFilterOnHttpHeader(r *C.httpRequest, endStream, headerNum, headerByt header := &responseHeaderMapImpl{ requestOrResponseHeaderMapImpl{ headerMapImpl{ - request: req, + state: state, headerNum: headerNum, headerBytes: headerBytes, }, @@ -188,7 +218,7 @@ func envoyGoFilterOnHttpHeader(r *C.httpRequest, endStream, headerNum, headerByt header := &responseTrailerMapImpl{ requestOrResponseTrailerMapImpl{ headerMapImpl{ - request: req, + state: state, headerNum: headerNum, headerBytes: headerBytes, }, @@ -205,21 +235,23 @@ func envoyGoFilterOnHttpHeader(r *C.httpRequest, endStream, headerNum, headerByt } //export envoyGoFilterOnHttpData -func envoyGoFilterOnHttpData(r *C.httpRequest, endStream, buffer, length uint64) uint64 { - req := getRequest(r) +func envoyGoFilterOnHttpData(s *C.processState, endStream, buffer, length uint64) uint64 { + state := getState(s) + + req := state.request if req.pInfo.paniced { // goroutine panic in the previous state that could not sendLocalReply, delay terminating the request here, // to prevent error from spreading. - req.sendPanicReply(req.pInfo.details) + state.sendPanicReply(req.pInfo.details) return uint64(api.LocalReply) } - defer req.RecoverPanic() + defer state.RecoverPanic() f := req.httpFilter - isDecode := api.EnvoyRequestPhase(r.phase) == api.DecodeDataPhase + isDecode := state.Phase() == api.DecodeDataPhase buf := &httpBuffer{ - request: req, + state: state, envoyBufferInstance: buffer, length: length, } @@ -240,7 +272,7 @@ func envoyGoFilterOnHttpLog(r *C.httpRequest, logType uint64) { req = createRequest(r) } - defer req.RecoverPanic() + defer req.recoverPanic() v := api.AccessLogType(logType) @@ -261,7 +293,7 @@ func envoyGoFilterOnHttpLog(r *C.httpRequest, logType uint64) { func envoyGoFilterOnHttpDestroy(r *C.httpRequest, reason uint64) { req := getRequest(r) // do nothing even when req.panic is true, since filter is already destroying. - defer req.RecoverPanic() + defer req.recoverPanic() req.resumeWaitCallback() @@ -281,6 +313,6 @@ func envoyGoFilterOnHttpDestroy(r *C.httpRequest, reason uint64) { //export envoyGoRequestSemaDec func envoyGoRequestSemaDec(r *C.httpRequest) { req := getRequest(r) - defer req.RecoverPanic() + defer req.recoverPanic() req.resumeWaitCallback() } diff --git a/contrib/golang/filters/http/source/go/pkg/http/type.go b/contrib/golang/filters/http/source/go/pkg/http/type.go index 4b1dae8cb0a8..df3bb4f60597 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/type.go +++ b/contrib/golang/filters/http/source/go/pkg/http/type.go @@ -36,7 +36,7 @@ const ( // api.HeaderMap type headerMapImpl struct { - request *httpRequest + state *processState headers map[string][]string headerNum uint64 headerBytes uint64 @@ -49,13 +49,13 @@ type requestOrResponseHeaderMapImpl struct { func (h *requestOrResponseHeaderMapImpl) initHeaders() { if h.headers == nil { - h.headers = cAPI.HttpCopyHeaders(unsafe.Pointer(h.request.req), h.headerNum, h.headerBytes) + h.headers = cAPI.HttpCopyHeaders(unsafe.Pointer(h.state), h.headerNum, h.headerBytes) } } func (h *requestOrResponseHeaderMapImpl) GetRaw(key string) string { // GetRaw is case-sensitive - return cAPI.HttpGetHeader(unsafe.Pointer(h.request.req), key) + return cAPI.HttpGetHeader(unsafe.Pointer(h.state), key) } func (h *requestOrResponseHeaderMapImpl) Get(key string) (string, bool) { @@ -93,7 +93,7 @@ func (h *requestOrResponseHeaderMapImpl) Set(key, value string) { if h.headers != nil { h.headers[key] = []string{value} } - cAPI.HttpSetHeader(unsafe.Pointer(h.request.req), key, value, false) + cAPI.HttpSetHeader(unsafe.Pointer(h.state), key, value, false) } func (h *requestOrResponseHeaderMapImpl) Add(key, value string) { @@ -108,7 +108,7 @@ func (h *requestOrResponseHeaderMapImpl) Add(key, value string) { h.headers[key] = []string{value} } } - cAPI.HttpSetHeader(unsafe.Pointer(h.request.req), key, value, true) + cAPI.HttpSetHeader(unsafe.Pointer(h.state), key, value, true) } func (h *requestOrResponseHeaderMapImpl) Del(key string) { @@ -120,7 +120,7 @@ func (h *requestOrResponseHeaderMapImpl) Del(key string) { defer h.mutex.Unlock() h.initHeaders() delete(h.headers, key) - cAPI.HttpRemoveHeader(unsafe.Pointer(h.request.req), key) + cAPI.HttpRemoveHeader(unsafe.Pointer(h.state), key) } func (h *requestOrResponseHeaderMapImpl) Range(f func(key, value string) bool) { @@ -227,12 +227,12 @@ type requestOrResponseTrailerMapImpl struct { func (h *requestOrResponseTrailerMapImpl) initTrailers() { if h.headers == nil { - h.headers = cAPI.HttpCopyTrailers(unsafe.Pointer(h.request.req), h.headerNum, h.headerBytes) + h.headers = cAPI.HttpCopyTrailers(unsafe.Pointer(h.state), h.headerNum, h.headerBytes) } } func (h *requestOrResponseTrailerMapImpl) GetRaw(key string) string { - return cAPI.HttpGetHeader(unsafe.Pointer(h.request.req), key) + return cAPI.HttpGetHeader(unsafe.Pointer(h.state), key) } func (h *requestOrResponseTrailerMapImpl) Get(key string) (string, bool) { @@ -271,7 +271,7 @@ func (h *requestOrResponseTrailerMapImpl) Set(key, value string) { h.headers[key] = []string{value} } - cAPI.HttpSetTrailer(unsafe.Pointer(h.request.req), key, value, false) + cAPI.HttpSetTrailer(unsafe.Pointer(h.state), key, value, false) } func (h *requestOrResponseTrailerMapImpl) Add(key, value string) { @@ -286,7 +286,7 @@ func (h *requestOrResponseTrailerMapImpl) Add(key, value string) { h.headers[key] = []string{value} } } - cAPI.HttpSetTrailer(unsafe.Pointer(h.request.req), key, value, true) + cAPI.HttpSetTrailer(unsafe.Pointer(h.state), key, value, true) } func (h *requestOrResponseTrailerMapImpl) Del(key string) { @@ -295,7 +295,7 @@ func (h *requestOrResponseTrailerMapImpl) Del(key string) { defer h.mutex.Unlock() h.initTrailers() delete(h.headers, key) - cAPI.HttpRemoveTrailer(unsafe.Pointer(h.request.req), key) + cAPI.HttpRemoveTrailer(unsafe.Pointer(h.state), key) } func (h *requestOrResponseTrailerMapImpl) Range(f func(key, value string) bool) { @@ -358,7 +358,7 @@ var _ api.ResponseTrailerMap = (*responseTrailerMapImpl)(nil) // api.BufferInstance type httpBuffer struct { - request *httpRequest + state *processState envoyBufferInstance uint64 length uint64 value []byte @@ -367,21 +367,21 @@ type httpBuffer struct { var _ api.BufferInstance = (*httpBuffer)(nil) func (b *httpBuffer) Write(p []byte) (n int, err error) { - cAPI.HttpSetBytesBufferHelper(unsafe.Pointer(b.request.req), b.envoyBufferInstance, p, api.AppendBuffer) + cAPI.HttpSetBytesBufferHelper(unsafe.Pointer(b.state), b.envoyBufferInstance, p, api.AppendBuffer) n = len(p) b.length += uint64(n) return n, nil } func (b *httpBuffer) WriteString(s string) (n int, err error) { - cAPI.HttpSetBufferHelper(unsafe.Pointer(b.request.req), b.envoyBufferInstance, s, api.AppendBuffer) + cAPI.HttpSetBufferHelper(unsafe.Pointer(b.state), b.envoyBufferInstance, s, api.AppendBuffer) n = len(s) b.length += uint64(n) return n, nil } func (b *httpBuffer) WriteByte(p byte) error { - cAPI.HttpSetBufferHelper(unsafe.Pointer(b.request.req), b.envoyBufferInstance, string(p), api.AppendBuffer) + cAPI.HttpSetBufferHelper(unsafe.Pointer(b.state), b.envoyBufferInstance, string(p), api.AppendBuffer) b.length++ return nil } @@ -408,7 +408,7 @@ func (b *httpBuffer) Bytes() []byte { if b.length == 0 { return nil } - b.value = cAPI.HttpGetBuffer(unsafe.Pointer(b.request.req), b.envoyBufferInstance, b.length) + b.value = cAPI.HttpGetBuffer(unsafe.Pointer(b.state), b.envoyBufferInstance, b.length) return b.value } @@ -422,7 +422,7 @@ func (b *httpBuffer) Drain(offset int) { size = b.length } - cAPI.HttpDrainBuffer(unsafe.Pointer(b.request.req), b.envoyBufferInstance, size) + cAPI.HttpDrainBuffer(unsafe.Pointer(b.state), b.envoyBufferInstance, size) b.length -= size } @@ -439,7 +439,7 @@ func (b *httpBuffer) String() string { if b.length == 0 { return "" } - b.value = cAPI.HttpGetBuffer(unsafe.Pointer(b.request.req), b.envoyBufferInstance, b.length) + b.value = cAPI.HttpGetBuffer(unsafe.Pointer(b.state), b.envoyBufferInstance, b.length) return string(b.value) } @@ -449,7 +449,7 @@ func (b *httpBuffer) Append(data []byte) error { } func (b *httpBuffer) Prepend(data []byte) error { - cAPI.HttpSetBytesBufferHelper(unsafe.Pointer(b.request.req), b.envoyBufferInstance, data, api.PrependBuffer) + cAPI.HttpSetBytesBufferHelper(unsafe.Pointer(b.state), b.envoyBufferInstance, data, api.PrependBuffer) b.length += uint64(len(data)) return nil } @@ -460,19 +460,19 @@ func (b *httpBuffer) AppendString(s string) error { } func (b *httpBuffer) PrependString(s string) error { - cAPI.HttpSetBufferHelper(unsafe.Pointer(b.request.req), b.envoyBufferInstance, s, api.PrependBuffer) + cAPI.HttpSetBufferHelper(unsafe.Pointer(b.state), b.envoyBufferInstance, s, api.PrependBuffer) b.length += uint64(len(s)) return nil } func (b *httpBuffer) Set(data []byte) error { - cAPI.HttpSetBytesBufferHelper(unsafe.Pointer(b.request.req), b.envoyBufferInstance, data, api.SetBuffer) + cAPI.HttpSetBytesBufferHelper(unsafe.Pointer(b.state), b.envoyBufferInstance, data, api.SetBuffer) b.length = uint64(len(data)) return nil } func (b *httpBuffer) SetString(s string) error { - cAPI.HttpSetBufferHelper(unsafe.Pointer(b.request.req), b.envoyBufferInstance, s, api.SetBuffer) + cAPI.HttpSetBufferHelper(unsafe.Pointer(b.state), b.envoyBufferInstance, s, api.SetBuffer) b.length = uint64(len(s)) return nil } diff --git a/contrib/golang/filters/http/source/golang_filter.cc b/contrib/golang/filters/http/source/golang_filter.cc index e8648ffbaa59..d63525cb7683 100644 --- a/contrib/golang/filters/http/source/golang_filter.cc +++ b/contrib/golang/filters/http/source/golang_filter.cc @@ -32,10 +32,9 @@ namespace HttpFilters { namespace Golang { Http::LocalErrorStatus Filter::onLocalReply(const LocalReplyData& data) { - auto& state = getProcessorState(); - ASSERT(state.isThreadSafe()); - ENVOY_LOG(debug, "golang filter onLocalReply, state: {}, phase: {}, code: {}", state.stateStr(), - state.phaseStr(), int(data.code_)); + ASSERT(isThreadSafe()); + ENVOY_LOG(debug, "golang filter onLocalReply, decoding state: {}, encoding state: {}, code: {}", + decoding_state_.stateStr(), encoding_state_.stateStr(), int(data.code_)); return Http::LocalErrorStatus::Continue; } @@ -43,8 +42,8 @@ Http::LocalErrorStatus Filter::onLocalReply(const LocalReplyData& data) { Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) { ProcessorState& state = decoding_state_; - ENVOY_LOG(debug, "golang filter decodeHeaders, state: {}, phase: {}, end_stream: {}", - state.stateStr(), state.phaseStr(), end_stream); + ENVOY_LOG(debug, "golang filter decodeHeaders, decoding state: {}, end_stream: {}", + state.stateStr(), end_stream); request_headers_ = &headers; @@ -57,9 +56,8 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_stream) { ProcessorState& state = decoding_state_; - ENVOY_LOG(debug, - "golang filter decodeData, state: {}, phase: {}, data length: {}, end_stream: {}", - state.stateStr(), state.phaseStr(), data.length(), end_stream); + ENVOY_LOG(debug, "golang filter decodeData, decoding state: {}, data length: {}, end_stream: {}", + state.stateStr(), data.length(), end_stream); state.setEndStream(end_stream); @@ -75,10 +73,7 @@ Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_strea Http::FilterTrailersStatus Filter::decodeTrailers(Http::RequestTrailerMap& trailers) { ProcessorState& state = decoding_state_; - ENVOY_LOG(debug, "golang filter decodeTrailers, state: {}, phase: {}", state.stateStr(), - state.phaseStr()); - - state.setSeenTrailers(); + ENVOY_LOG(debug, "golang filter decodeTrailers, decoding state: {}", state.stateStr()); bool done = doTrailer(state, trailers); @@ -86,51 +81,18 @@ Http::FilterTrailersStatus Filter::decodeTrailers(Http::RequestTrailerMap& trail } Http::FilterHeadersStatus Filter::encodeHeaders(Http::ResponseHeaderMap& headers, bool end_stream) { - ProcessorState& state = getProcessorState(); - ENVOY_LOG(debug, "golang filter encodeHeaders, state: {}, phase: {}, end_stream: {}", - state.stateStr(), state.phaseStr(), end_stream); + ProcessorState& state = encoding_state_; + ENVOY_LOG(debug, "golang filter encodeHeaders, encoding state: {}, end_stream: {}", + state.stateStr(), end_stream); - encoding_state_.setEndStream(end_stream); + state.setEndStream(end_stream); + activation_response_headers_ = dynamic_cast(&headers); - // NP: may enter encodeHeaders in any phase & any state_, + // NP: may enter encodeHeaders in any state, // since other filters or filtermanager could call encodeHeaders or sendLocalReply in any time. // eg. filtermanager may invoke sendLocalReply, when scheme is invalid, // with "Sending local reply with details // http1.invalid_scheme" details. - if (state.state() != FilterState::Done) { - ENVOY_LOG(debug, - "golang filter enter encodeHeaders early, maybe sendLocalReply or encodeHeaders " - "happened, current state: {}, phase: {}", - state.stateStr(), state.phaseStr()); - - ENVOY_LOG(debug, "golang filter drain data buffer since enter encodeHeaders early"); - // NP: is safe to overwrite it since go code won't read it directly - // need drain buffer to enable read when it's high watermark - state.drainBufferData(); - - // get the state before changing it. - bool in_go = state.isProcessingInGo(); - - if (in_go) { - // NP: wait go returns to avoid concurrency conflict in go side. - local_reply_waiting_go_ = true; - ENVOY_LOG(debug, "waiting go returns before handle the local reply from other filter"); - - // NP: save to another local_headers_ variable to avoid conflict, - // since the headers_ may be used in Go side. - local_headers_ = &headers; - - // can not use "StopAllIterationAndWatermark" here, since Go decodeHeaders may return - // stopAndBuffer, that means it need data buffer and not continue header. - return Http::FilterHeadersStatus::StopIteration; - - } else { - ENVOY_LOG(debug, "golang filter clear do data buffer before continue encodeHeader, " - "since no go code is running"); - state.doDataList.clearAll(); - } - } - - enter_encoding_ = true; + // This means DecodeXXX & EncodeXXX may run concurrently in Golang side. bool done = doHeaders(encoding_state_, headers, end_stream); @@ -138,20 +100,13 @@ Http::FilterHeadersStatus Filter::encodeHeaders(Http::ResponseHeaderMap& headers } Http::FilterDataStatus Filter::encodeData(Buffer::Instance& data, bool end_stream) { - ProcessorState& state = getProcessorState(); - ENVOY_LOG(debug, - "golang filter encodeData, state: {}, phase: {}, data length: {}, end_stream: {}", - state.stateStr(), state.phaseStr(), data.length(), end_stream); - - encoding_state_.setEndStream(end_stream); + ProcessorState& state = encoding_state_; + ENVOY_LOG(debug, "golang filter encodeData, encoding state: {}, data length: {}, end_stream: {}", + state.stateStr(), data.length(), end_stream); - if (local_reply_waiting_go_) { - ENVOY_LOG(debug, "golang filter appending data to buffer"); - encoding_state_.addBufferData(data); - return Http::FilterDataStatus::StopIterationNoBuffer; - } + state.setEndStream(end_stream); - bool done = doData(encoding_state_, data, end_stream); + bool done = doData(state, data, end_stream); if (done) { state.doDataList.moveOut(data); @@ -162,18 +117,10 @@ Http::FilterDataStatus Filter::encodeData(Buffer::Instance& data, bool end_strea } Http::FilterTrailersStatus Filter::encodeTrailers(Http::ResponseTrailerMap& trailers) { - ProcessorState& state = getProcessorState(); - ENVOY_LOG(debug, "golang filter encodeTrailers, state: {}, phase: {}", state.stateStr(), - state.phaseStr()); + ProcessorState& state = encoding_state_; + ENVOY_LOG(debug, "golang filter encodeTrailers, encoding state: {}", state.stateStr()); - encoding_state_.setSeenTrailers(); - - if (local_reply_waiting_go_) { - // NP: save to another local_trailers_ variable to avoid conflict, - // since the trailers_ may be used in Go side. - local_trailers_ = &trailers; - return Http::FilterTrailersStatus::StopIteration; - } + activation_response_trailers_ = dynamic_cast(&trailers); bool done = doTrailer(encoding_state_, trailers); @@ -183,8 +130,11 @@ Http::FilterTrailersStatus Filter::encodeTrailers(Http::ResponseTrailerMap& trai void Filter::onDestroy() { ENVOY_LOG(debug, "golang filter on destroy"); - // do nothing, stream reset may happen before entering this filter. - if (req_ == nullptr) { + // initRequest haven't be called yet, which mean haven't called into Go. + if (req_->configId == 0) { + // should release the req object, since stream reset may happen before calling into Go side, + // which means no GC finializer will be invoked to release this C++ object. + delete req_; return; } @@ -197,8 +147,9 @@ void Filter::onDestroy() { has_destroyed_ = true; } - auto& state = getProcessorState(); - auto reason = state.isProcessingInGo() ? DestroyReason::Terminate : DestroyReason::Normal; + auto reason = (decoding_state_.isProcessingInGo() || encoding_state_.isProcessingInGo()) + ? DestroyReason::Terminate + : DestroyReason::Normal; dynamic_lib_->envoyGoFilterOnHttpDestroy(req_, int(reason)); } @@ -210,22 +161,18 @@ void Filter::log(const Formatter::HttpFormatterContext& log_context, switch (log_context.accessLogType()) { case Envoy::AccessLog::AccessLogType::DownstreamStart: case Envoy::AccessLog::AccessLogType::DownstreamPeriodic: - case Envoy::AccessLog::AccessLogType::DownstreamEnd: { - auto& state = getProcessorState(); - - if (req_ == nullptr) { - // log called by AccessLogDownstreamStart will happen before doHeaders - initRequest(state); - + case Envoy::AccessLog::AccessLogType::DownstreamEnd: + // log called by AccessLogDownstreamStart will happen before doHeaders + if (initRequest()) { request_headers_ = static_cast( const_cast(&log_context.requestHeaders())); } - state.enterLog(); - req_->phase = static_cast(state.phase()); + // This only run in the work thread, it's safe even without lock. + is_golang_processing_log_ = true; dynamic_lib_->envoyGoFilterOnHttpLog(req_, int(log_context.accessLogType())); - state.leaveLog(); - } break; + is_golang_processing_log_ = false; + break; default: // skip calling with unsupported log types break; @@ -236,62 +183,54 @@ void Filter::log(const Formatter::HttpFormatterContext& log_context, GolangStatus Filter::doHeadersGo(ProcessorState& state, Http::RequestOrResponseHeaderMap& headers, bool end_stream) { - ENVOY_LOG(debug, "golang filter passing data to golang, state: {}, phase: {}, end_stream: {}", - state.stateStr(), state.phaseStr(), end_stream); + ENVOY_LOG(debug, "golang filter passing header to golang, state: {}, end_stream: {}", + state.stateStr(), end_stream); - if (req_ == nullptr) { - initRequest(state); - } + initRequest(); - req_->phase = static_cast(state.phase()); - { - Thread::LockGuard lock(mutex_); - headers_ = &headers; - } - auto status = dynamic_lib_->envoyGoFilterOnHttpHeader(req_, end_stream ? 1 : 0, headers.size(), + auto s = dynamic_cast(&state); + auto status = dynamic_lib_->envoyGoFilterOnHttpHeader(s, end_stream ? 1 : 0, headers.size(), headers.byteSize()); return static_cast(status); } bool Filter::doHeaders(ProcessorState& state, Http::RequestOrResponseHeaderMap& headers, bool end_stream) { - ENVOY_LOG(debug, "golang filter doHeaders, state: {}, phase: {}, end_stream: {}", - state.stateStr(), state.phaseStr(), end_stream); + ENVOY_LOG(debug, "golang filter doHeaders, state: {}, end_stream: {}", state.stateStr(), + end_stream); ASSERT(state.isBufferDataEmpty()); + state.headers = &headers; state.processHeader(end_stream); auto status = doHeadersGo(state, headers, end_stream); auto done = state.handleHeaderGolangStatus(status); if (done) { - Thread::LockGuard lock(mutex_); - headers_ = nullptr; + state.headers = nullptr; } return done; } bool Filter::doDataGo(ProcessorState& state, Buffer::Instance& data, bool end_stream) { - ENVOY_LOG(debug, "golang filter passing data to golang, state: {}, phase: {}, end_stream: {}", - state.stateStr(), state.phaseStr(), end_stream); + ENVOY_LOG(debug, "golang filter passing data to golang, state: {}, end_stream: {}", + state.stateStr(), end_stream); state.processData(end_stream); Buffer::Instance& buffer = state.doDataList.push(data); - ASSERT(req_ != nullptr); - req_->phase = static_cast(state.phase()); + auto s = dynamic_cast(&state); auto status = dynamic_lib_->envoyGoFilterOnHttpData( - req_, end_stream ? 1 : 0, reinterpret_cast(&buffer), buffer.length()); + s, end_stream ? 1 : 0, reinterpret_cast(&buffer), buffer.length()); return state.handleDataGolangStatus(static_cast(status)); } bool Filter::doData(ProcessorState& state, Buffer::Instance& data, bool end_stream) { - ENVOY_LOG(debug, "golang filter doData, state: {}, phase: {}, end_stream: {}", state.stateStr(), - state.phaseStr(), end_stream); + ENVOY_LOG(debug, "golang filter doData, state: {}, end_stream: {}", state.stateStr(), end_stream); bool done = false; - switch (state.state()) { + switch (state.filterState()) { case FilterState::WaitingData: done = doDataGo(state, data, end_stream); break; @@ -304,7 +243,7 @@ bool Filter::doData(ProcessorState& state, Buffer::Instance& data, bool end_stre } // check state again since data_buffer may be full and sendLocalReply with 413. // TODO: better not trigger 413 here. - if (state.state() == FilterState::WaitingAllData) { + if (state.filterState() == FilterState::WaitingAllData) { done = doDataGo(state, data, end_stream); } break; @@ -328,33 +267,26 @@ bool Filter::doData(ProcessorState& state, Buffer::Instance& data, bool end_stre } bool Filter::doTrailerGo(ProcessorState& state, Http::HeaderMap& trailers) { - ENVOY_LOG(debug, "golang filter passing trailers to golang, state: {}, phase: {}", - state.stateStr(), state.phaseStr()); + ENVOY_LOG(debug, "golang filter passing trailers to golang, state: {}", state.stateStr()); state.processTrailer(); - ASSERT(req_ != nullptr); - req_->phase = static_cast(state.phase()); - auto status = - dynamic_lib_->envoyGoFilterOnHttpHeader(req_, 1, trailers.size(), trailers.byteSize()); + auto s = dynamic_cast(&state); + auto status = dynamic_lib_->envoyGoFilterOnHttpHeader(s, 1, trailers.size(), trailers.byteSize()); return state.handleTrailerGolangStatus(static_cast(status)); } bool Filter::doTrailer(ProcessorState& state, Http::HeaderMap& trailers) { - ENVOY_LOG(debug, "golang filter doTrailer, state: {}, phase: {}", state.stateStr(), - state.phaseStr()); + ENVOY_LOG(debug, "golang filter doTrailer, state: {}", state.stateStr()); ASSERT(!state.getEndStream() && !state.isProcessingEndStream()); - { - Thread::LockGuard lock(mutex_); - trailers_ = &trailers; - } + state.trailers = &trailers; bool done = false; Buffer::OwnedImpl body; - switch (state.state()) { + switch (state.filterState()) { case FilterState::WaitingTrailer: done = doTrailerGo(state, trailers); break; @@ -367,9 +299,9 @@ bool Filter::doTrailer(ProcessorState& state, Http::HeaderMap& trailers) { if (!state.isBufferDataEmpty()) { done = doDataGo(state, state.getBufferData(), false); // NP: can not use done as condition here, since done will be false - // maybe we can remove the done variable totally? by using state_ only? + // maybe we can remove the done variable totally? by using state only? // continue trailers - if (state.state() == FilterState::WaitingTrailer) { + if (state.filterState() == FilterState::WaitingTrailer) { state.continueDoData(); done = doTrailerGo(state, trailers); } @@ -388,71 +320,17 @@ bool Filter::doTrailer(ProcessorState& state, Http::HeaderMap& trailers) { break; } - ENVOY_LOG(debug, "golang filter doTrailer, return: {}", done); + ENVOY_LOG(debug, "golang filter doTrailer, return: {}, seen trailers: {}", done, + state.trailers != nullptr); return done; } /*** APIs for go call C ***/ -CAPIStatus Filter::clearRouteCache() { - Thread::LockGuard lock(mutex_); - if (has_destroyed_) { - ENVOY_LOG(debug, "golang filter has been destroyed"); - return CAPIStatus::CAPIFilterIsDestroy; - } - auto& state = getProcessorState(); - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; - } - ENVOY_LOG(debug, "golang filter clearing route cache"); - decoding_state_.getFilterCallbacks()->downstreamCallbacks()->clearRouteCache(); - return CAPIStatus::CAPIOK; -} - -void Filter::continueEncodeLocalReply(ProcessorState& state) { - ENVOY_LOG(debug, - "golang filter continue encodeHeader(local reply from other filters) after return from " - "go, current state: {}, phase: {}", - state.stateStr(), state.phaseStr()); - - ENVOY_LOG(debug, "golang filter drain do data buffer before continueEncodeLocalReply"); - state.doDataList.clearAll(); - - local_reply_waiting_go_ = false; - // should use encoding_state_ now - enter_encoding_ = true; - - auto header_end_stream = encoding_state_.getEndStream(); - if (local_trailers_ != nullptr) { - Thread::LockGuard lock(mutex_); - trailers_ = local_trailers_; - header_end_stream = false; - } - if (!encoding_state_.isBufferDataEmpty()) { - header_end_stream = false; - } - // NP: we not overwrite state end_stream in doHeadersGo - encoding_state_.processHeader(header_end_stream); - auto status = doHeadersGo(encoding_state_, *local_headers_, header_end_stream); - continueStatusInternal(status); -} - -void Filter::continueStatusInternal(GolangStatus status) { - ProcessorState& state = getProcessorState(); +void Filter::continueStatusInternal(ProcessorState& state, GolangStatus status) { ASSERT(state.isThreadSafe()); - auto saved_state = state.state(); - - if (local_reply_waiting_go_) { - ENVOY_LOG(debug, - "other filter already trigger sendLocalReply, ignoring the continue status: {}, " - "state: {}, phase: {}", - int(status), state.stateStr(), state.phaseStr()); - - continueEncodeLocalReply(state); - return; - } + auto saved_state = state.filterState(); auto done = state.handleGolangStatus(status); if (done) { @@ -482,10 +360,14 @@ void Filter::continueStatusInternal(GolangStatus status) { } } + ENVOY_LOG(debug, + "after done handle golang status, status: {}, state: {}, done: {}, seen trailers: {}", + int(status), state.stateStr(), done, state.trailers != nullptr); + // TODO: state should also grow in this case // state == WaitingData && bufferData is empty && seen trailers - auto current_state = state.state(); + auto current_state = state.filterState(); if ((current_state == FilterState::WaitingData && (!state.isBufferDataEmpty() || state.getEndStream())) || (current_state == FilterState::WaitingAllData && state.isStreamEnd())) { @@ -498,10 +380,8 @@ void Filter::continueStatusInternal(GolangStatus status) { } } - Thread::ReleasableLockGuard lock(mutex_); - if (state.state() == FilterState::WaitingTrailer && trailers_ != nullptr) { - auto trailers = trailers_; - lock.release(); + if (state.filterState() == FilterState::WaitingTrailer && state.trailers != nullptr) { + auto trailers = state.trailers; auto done = doTrailerGo(state, *trailers); if (done) { state.continueProcessing(); @@ -510,22 +390,11 @@ void Filter::continueStatusInternal(GolangStatus status) { } void Filter::sendLocalReplyInternal( - Http::Code response_code, absl::string_view body_text, + ProcessorState& state, Http::Code response_code, absl::string_view body_text, std::function modify_headers, Grpc::Status::GrpcStatus grpc_status, absl::string_view details) { - ENVOY_LOG(debug, "sendLocalReply Internal, response code: {}", int(response_code)); - - ProcessorState& state = getProcessorState(); - - if (local_reply_waiting_go_) { - ENVOY_LOG(debug, - "other filter already invoked sendLocalReply or encodeHeaders, ignoring the local " - "reply from go, code: {}, body: {}, details: {}", - int(response_code), body_text, details); - - continueEncodeLocalReply(state); - return; - } + ENVOY_LOG(debug, "sendLocalReply Internal, state: {}, response code: {}", state.stateStr(), + int(response_code)); ENVOY_LOG(debug, "golang filter drain do data buffer before sendLocalReply"); state.doDataList.clearAll(); @@ -537,7 +406,7 @@ void Filter::sendLocalReplyInternal( } CAPIStatus -Filter::sendLocalReply(Http::Code response_code, std::string body_text, +Filter::sendLocalReply(ProcessorState& state, Http::Code response_code, std::string body_text, std::function modify_headers, Grpc::Status::GrpcStatus grpc_status, std::string details) { // lock until this function return since it may running in a Go thread. @@ -546,7 +415,6 @@ Filter::sendLocalReply(Http::Code response_code, std::string body_text, ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; @@ -554,19 +422,19 @@ Filter::sendLocalReply(Http::Code response_code, std::string body_text, ENVOY_LOG(debug, "sendLocalReply, response code: {}", int(response_code)); auto weak_ptr = weak_from_this(); - state.getDispatcher().post( - [this, &state, weak_ptr, response_code, body_text, modify_headers, grpc_status, details] { - if (!weak_ptr.expired() && !hasDestroyed()) { - ASSERT(state.isThreadSafe()); - sendLocalReplyInternal(response_code, body_text, modify_headers, grpc_status, details); - } else { - ENVOY_LOG(debug, "golang filter has gone or destroyed in sendLocalReply"); - } - }); + state.getDispatcher().post([this, &state, weak_ptr, response_code, body_text, modify_headers, + grpc_status, details] { + if (!weak_ptr.expired() && !hasDestroyed()) { + ASSERT(state.isThreadSafe()); + sendLocalReplyInternal(state, response_code, body_text, modify_headers, grpc_status, details); + } else { + ENVOY_LOG(debug, "golang filter has gone or destroyed in sendLocalReply"); + } + }); return CAPIStatus::CAPIOK; }; -CAPIStatus Filter::sendPanicReply(absl::string_view details) { +CAPIStatus Filter::sendPanicReply(ProcessorState& state, absl::string_view details) { config_->stats().panic_error_.inc(); ENVOY_LOG(error, "[go_plugin_http][{}] {}", config_->pluginName(), absl::StrCat("filter paniced with error details: ", details)); @@ -574,24 +442,23 @@ CAPIStatus Filter::sendPanicReply(absl::string_view details) { // we don't want to leak the operational details of the service for security reasons. // Operators should be able to view the details via the log message above // and use the stats for o11y - return sendLocalReply(Http::Code::InternalServerError, "error happened in filter\r\n", nullptr, - Grpc::Status::WellKnownGrpcStatus::Ok, ""); + return sendLocalReply(state, Http::Code::InternalServerError, "error happened in filter\r\n", + nullptr, Grpc::Status::WellKnownGrpcStatus::Ok, ""); } -CAPIStatus Filter::continueStatus(GolangStatus status) { +CAPIStatus Filter::continueStatus(ProcessorState& state, GolangStatus status) { // lock until this function return since it may running in a Go thread. Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } - ENVOY_LOG(debug, "golang filter continue from Go, status: {}, state: {}, phase: {}", int(status), - state.stateStr(), state.phaseStr()); + ENVOY_LOG(debug, "golang filter continue from Go, status: {}, state: {}", int(status), + state.stateStr()); auto weak_ptr = weak_from_this(); // TODO: skip post event to dispatcher, and return continue in the caller, @@ -599,7 +466,7 @@ CAPIStatus Filter::continueStatus(GolangStatus status) { state.getDispatcher().post([this, &state, weak_ptr, status] { if (!weak_ptr.expired() && !hasDestroyed()) { ASSERT(state.isThreadSafe()); - continueStatusInternal(status); + continueStatusInternal(state, status); } else { ENVOY_LOG(debug, "golang filter has gone or destroyed in continueStatus event"); } @@ -607,20 +474,20 @@ CAPIStatus Filter::continueStatus(GolangStatus status) { return CAPIStatus::CAPIOK; } -CAPIStatus Filter::getHeader(absl::string_view key, uint64_t* value_data, int* value_len) { +CAPIStatus Filter::getHeader(ProcessorState& state, absl::string_view key, uint64_t* value_data, + int* value_len) { Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } - auto m = state.isProcessingHeader() ? headers_ : trailers_; + auto m = state.headers; if (m == nullptr) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } auto result = m->get(Http::LowerCaseString(key)); @@ -660,40 +527,41 @@ void copyHeaderMapToGo(Http::HeaderMap& m, GoString* go_strs, char* go_buf) { }); } -CAPIStatus Filter::copyHeaders(GoString* go_strs, char* go_buf) { +CAPIStatus Filter::copyHeaders(ProcessorState& state, GoString* go_strs, char* go_buf) { Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } - if (headers_ == nullptr) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + auto headers = state.headers; + if (headers == nullptr) { + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } - copyHeaderMapToGo(*headers_, go_strs, go_buf); + copyHeaderMapToGo(*headers, go_strs, go_buf); return CAPIStatus::CAPIOK; } // It won't take affect immidiately while it's invoked from a Go thread, instead, it will post a // callback to run in the envoy worker thread. -CAPIStatus Filter::setHeader(absl::string_view key, absl::string_view value, headerAction act) { +CAPIStatus Filter::setHeader(ProcessorState& state, absl::string_view key, absl::string_view value, + headerAction act) { Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } - if (headers_ == nullptr) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + auto headers = state.headers; + if (headers == nullptr) { + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } @@ -701,11 +569,11 @@ CAPIStatus Filter::setHeader(absl::string_view key, absl::string_view value, hea // it's safe to write header in the safe thread. switch (act) { case HeaderAdd: - headers_->addCopy(Http::LowerCaseString(key), value); + headers->addCopy(Http::LowerCaseString(key), value); break; case HeaderSet: - headers_->setCopy(Http::LowerCaseString(key), value); + headers->setCopy(Http::LowerCaseString(key), value); break; default: @@ -720,16 +588,15 @@ CAPIStatus Filter::setHeader(absl::string_view key, absl::string_view value, hea // dispatch a callback to write header in the envoy safe thread, to make the write operation // safety. otherwise, there might be race between reading in the envoy worker thread and writing // in the Go thread. - state.getDispatcher().post([this, weak_ptr, key_str, value_str, act] { + state.getDispatcher().post([this, headers, weak_ptr, key_str, value_str, act] { if (!weak_ptr.expired() && !hasDestroyed()) { - Thread::LockGuard lock(mutex_); switch (act) { case HeaderAdd: - headers_->addCopy(Http::LowerCaseString(key_str), value_str); + headers->addCopy(Http::LowerCaseString(key_str), value_str); break; case HeaderSet: - headers_->setCopy(Http::LowerCaseString(key_str), value_str); + headers->setCopy(Http::LowerCaseString(key_str), value_str); break; default: @@ -746,24 +613,24 @@ CAPIStatus Filter::setHeader(absl::string_view key, absl::string_view value, hea // It won't take affect immidiately while it's invoked from a Go thread, instead, it will post a // callback to run in the envoy worker thread. -CAPIStatus Filter::removeHeader(absl::string_view key) { +CAPIStatus Filter::removeHeader(ProcessorState& state, absl::string_view key) { Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } - if (headers_ == nullptr) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + auto headers = state.headers; + if (headers == nullptr) { + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } if (state.isThreadSafe()) { // it's safe to write header in the safe thread. - headers_->remove(Http::LowerCaseString(key)); + headers->remove(Http::LowerCaseString(key)); } else { // should deep copy the string_view before post to dipatcher callback. auto key_str = std::string(key); @@ -772,10 +639,9 @@ CAPIStatus Filter::removeHeader(absl::string_view key) { // dispatch a callback to write header in the envoy safe thread, to make the write operation // safety. otherwise, there might be race between reading in the envoy worker thread and writing // in the Go thread. - state.getDispatcher().post([this, weak_ptr, key_str] { + state.getDispatcher().post([this, weak_ptr, headers, key_str] { if (!weak_ptr.expired() && !hasDestroyed()) { - Thread::LockGuard lock(mutex_); - headers_->remove(Http::LowerCaseString(key_str)); + headers->remove(Http::LowerCaseString(key_str)); } else { ENVOY_LOG(debug, "golang filter has gone or destroyed in removeHeader"); } @@ -784,20 +650,19 @@ CAPIStatus Filter::removeHeader(absl::string_view key) { return CAPIStatus::CAPIOK; } -CAPIStatus Filter::copyBuffer(Buffer::Instance* buffer, char* data) { +CAPIStatus Filter::copyBuffer(ProcessorState& state, Buffer::Instance* buffer, char* data) { // lock until this function return since it may running in a Go thread. Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } if (!state.doDataList.checkExisting(buffer)) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } for (const Buffer::RawSlice& slice : buffer->getRawSlices()) { @@ -809,20 +674,19 @@ CAPIStatus Filter::copyBuffer(Buffer::Instance* buffer, char* data) { return CAPIStatus::CAPIOK; } -CAPIStatus Filter::drainBuffer(Buffer::Instance* buffer, uint64_t length) { +CAPIStatus Filter::drainBuffer(ProcessorState& state, Buffer::Instance* buffer, uint64_t length) { // lock until this function return since it may running in a Go thread. Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } if (!state.doDataList.checkExisting(buffer)) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } @@ -830,21 +694,20 @@ CAPIStatus Filter::drainBuffer(Buffer::Instance* buffer, uint64_t length) { return CAPIStatus::CAPIOK; } -CAPIStatus Filter::setBufferHelper(Buffer::Instance* buffer, absl::string_view& value, - bufferAction action) { +CAPIStatus Filter::setBufferHelper(ProcessorState& state, Buffer::Instance* buffer, + absl::string_view& value, bufferAction action) { // lock until this function return since it may running in a Go thread. Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } if (!state.doDataList.checkExisting(buffer)) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } if (action == bufferAction::Set) { @@ -858,48 +721,49 @@ CAPIStatus Filter::setBufferHelper(Buffer::Instance* buffer, absl::string_view& return CAPIStatus::CAPIOK; } -CAPIStatus Filter::copyTrailers(GoString* go_strs, char* go_buf) { +CAPIStatus Filter::copyTrailers(ProcessorState& state, GoString* go_strs, char* go_buf) { Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } - if (trailers_ == nullptr) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + auto trailers = state.trailers; + if (trailers == nullptr) { + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } - copyHeaderMapToGo(*trailers_, go_strs, go_buf); + copyHeaderMapToGo(*trailers, go_strs, go_buf); return CAPIStatus::CAPIOK; } -CAPIStatus Filter::setTrailer(absl::string_view key, absl::string_view value, headerAction act) { +CAPIStatus Filter::setTrailer(ProcessorState& state, absl::string_view key, absl::string_view value, + headerAction act) { Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } - if (trailers_ == nullptr) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + auto trailers = state.trailers; + if (trailers == nullptr) { + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } if (state.isThreadSafe()) { switch (act) { case HeaderAdd: - trailers_->addCopy(Http::LowerCaseString(key), value); + trailers->addCopy(Http::LowerCaseString(key), value); break; case HeaderSet: - trailers_->setCopy(Http::LowerCaseString(key), value); + trailers->setCopy(Http::LowerCaseString(key), value); break; default: @@ -914,16 +778,15 @@ CAPIStatus Filter::setTrailer(absl::string_view key, absl::string_view value, he // dispatch a callback to write trailer in the envoy safe thread, to make the write operation // safety. otherwise, there might be race between reading in the envoy worker thread and // writing in the Go thread. - state.getDispatcher().post([this, weak_ptr, key_str, value_str, act] { + state.getDispatcher().post([this, trailers, weak_ptr, key_str, value_str, act] { if (!weak_ptr.expired() && !hasDestroyed()) { - Thread::LockGuard lock(mutex_); switch (act) { case HeaderAdd: - trailers_->addCopy(Http::LowerCaseString(key_str), value_str); + trailers->addCopy(Http::LowerCaseString(key_str), value_str); break; case HeaderSet: - trailers_->setCopy(Http::LowerCaseString(key_str), value_str); + trailers->setCopy(Http::LowerCaseString(key_str), value_str); break; default: @@ -937,23 +800,23 @@ CAPIStatus Filter::setTrailer(absl::string_view key, absl::string_view value, he return CAPIStatus::CAPIOK; } -CAPIStatus Filter::removeTrailer(absl::string_view key) { +CAPIStatus Filter::removeTrailer(ProcessorState& state, absl::string_view key) { Thread::LockGuard lock(mutex_); if (has_destroyed_) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); if (!state.isProcessingInGo()) { ENVOY_LOG(debug, "golang filter is not processing Go"); return CAPIStatus::CAPINotInGo; } - if (trailers_ == nullptr) { - ENVOY_LOG(debug, "invoking cgo api at invalid phase: {}", __func__); + auto trailers = state.trailers; + if (trailers == nullptr) { + ENVOY_LOG(debug, "invoking cgo api at invalid state: {}", __func__); return CAPIStatus::CAPIInvalidPhase; } if (state.isThreadSafe()) { - trailers_->remove(Http::LowerCaseString(key)); + trailers->remove(Http::LowerCaseString(key)); } else { // should deep copy the string_view before post to dipatcher callback. auto key_str = std::string(key); @@ -962,10 +825,9 @@ CAPIStatus Filter::removeTrailer(absl::string_view key) { // dispatch a callback to write trailer in the envoy safe thread, to make the write operation // safety. otherwise, there might be race between reading in the envoy worker thread and writing // in the Go thread. - state.getDispatcher().post([this, weak_ptr, key_str] { + state.getDispatcher().post([this, trailers, weak_ptr, key_str] { if (!weak_ptr.expired() && !hasDestroyed()) { - Thread::LockGuard lock(mutex_); - trailers_->remove(Http::LowerCaseString(key_str)); + trailers->remove(Http::LowerCaseString(key_str)); } else { ENVOY_LOG(debug, "golang filter has gone or destroyed in removeTrailer"); } @@ -974,6 +836,30 @@ CAPIStatus Filter::removeTrailer(absl::string_view key) { return CAPIStatus::CAPIOK; } +CAPIStatus Filter::clearRouteCache() { + Thread::LockGuard lock(mutex_); + if (has_destroyed_) { + ENVOY_LOG(debug, "golang filter has been destroyed"); + return CAPIStatus::CAPIFilterIsDestroy; + } + if (isThreadSafe()) { + ENVOY_LOG(debug, "golang filter clearing route cache"); + decoding_state_.getFilterCallbacks()->downstreamCallbacks()->clearRouteCache(); + } else { + ENVOY_LOG(debug, "golang filter posting clear route cache callback"); + auto weak_ptr = weak_from_this(); + getDispatcher().post([this, weak_ptr] { + if (!weak_ptr.expired() && !hasDestroyed()) { + ENVOY_LOG(debug, "golang filter clearing route cache"); + decoding_state_.getFilterCallbacks()->downstreamCallbacks()->clearRouteCache(); + } else { + ENVOY_LOG(info, "golang filter has gone or destroyed in clearRouteCache"); + } + }); + } + return CAPIStatus::CAPIOK; +} + CAPIStatus Filter::getIntegerValue(int id, uint64_t* value) { // lock until this function return since it may running in a Go thread. Thread::LockGuard lock(mutex_); @@ -981,30 +867,25 @@ CAPIStatus Filter::getIntegerValue(int id, uint64_t* value) { ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; - } switch (static_cast(id)) { case EnvoyValue::Protocol: - if (!state.streamInfo().protocol().has_value()) { + if (!streamInfo().protocol().has_value()) { return CAPIStatus::CAPIValueNotFound; } - *value = static_cast(state.streamInfo().protocol().value()); + *value = static_cast(streamInfo().protocol().value()); break; case EnvoyValue::ResponseCode: - if (!state.streamInfo().responseCode().has_value()) { + if (!streamInfo().responseCode().has_value()) { return CAPIStatus::CAPIValueNotFound; } - *value = state.streamInfo().responseCode().value(); + *value = streamInfo().responseCode().value(); break; case EnvoyValue::AttemptCount: - if (!state.streamInfo().attemptCount().has_value()) { + if (!streamInfo().attemptCount().has_value()) { return CAPIStatus::CAPIValueNotFound; } - *value = state.streamInfo().attemptCount().value(); + *value = streamInfo().attemptCount().value(); break; default: RELEASE_ASSERT(false, absl::StrCat("invalid integer value id: ", id)); @@ -1019,65 +900,58 @@ CAPIStatus Filter::getStringValue(int id, uint64_t* value_data, int* value_len) ENVOY_LOG(debug, "golang filter has been destroyed"); return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; - } // refer the string to req_->strValue, not deep clone, make sure it won't be freed while reading // it on the Go side. switch (static_cast(id)) { case EnvoyValue::RouteName: - req_->strValue = state.streamInfo().getRouteName(); + req_->strValue = streamInfo().getRouteName(); break; case EnvoyValue::FilterChainName: { - const auto filter_chain_info = state.streamInfo().downstreamAddressProvider().filterChainInfo(); + const auto filter_chain_info = streamInfo().downstreamAddressProvider().filterChainInfo(); req_->strValue = filter_chain_info.has_value() ? std::string(filter_chain_info->name()) : std::string(); break; } case EnvoyValue::ResponseCodeDetails: - if (!state.streamInfo().responseCodeDetails().has_value()) { + if (!streamInfo().responseCodeDetails().has_value()) { return CAPIStatus::CAPIValueNotFound; } - req_->strValue = state.streamInfo().responseCodeDetails().value(); + req_->strValue = streamInfo().responseCodeDetails().value(); break; case EnvoyValue::DownstreamLocalAddress: - req_->strValue = state.streamInfo().downstreamAddressProvider().localAddress()->asString(); + req_->strValue = streamInfo().downstreamAddressProvider().localAddress()->asString(); break; case EnvoyValue::DownstreamRemoteAddress: - req_->strValue = state.streamInfo().downstreamAddressProvider().remoteAddress()->asString(); + req_->strValue = streamInfo().downstreamAddressProvider().remoteAddress()->asString(); break; case EnvoyValue::UpstreamLocalAddress: - if (state.streamInfo().upstreamInfo() && - state.streamInfo().upstreamInfo()->upstreamLocalAddress()) { - req_->strValue = state.streamInfo().upstreamInfo()->upstreamLocalAddress()->asString(); + if (streamInfo().upstreamInfo() && streamInfo().upstreamInfo()->upstreamLocalAddress()) { + req_->strValue = streamInfo().upstreamInfo()->upstreamLocalAddress()->asString(); } else { return CAPIStatus::CAPIValueNotFound; } break; case EnvoyValue::UpstreamRemoteAddress: - if (state.streamInfo().upstreamInfo() && - state.streamInfo().upstreamInfo()->upstreamRemoteAddress()) { - req_->strValue = state.streamInfo().upstreamInfo()->upstreamRemoteAddress()->asString(); + if (streamInfo().upstreamInfo() && streamInfo().upstreamInfo()->upstreamRemoteAddress()) { + req_->strValue = streamInfo().upstreamInfo()->upstreamRemoteAddress()->asString(); } else { return CAPIStatus::CAPIValueNotFound; } break; case EnvoyValue::UpstreamClusterName: - if (state.streamInfo().upstreamClusterInfo().has_value() && - state.streamInfo().upstreamClusterInfo().value()) { - req_->strValue = state.streamInfo().upstreamClusterInfo().value()->name(); + if (streamInfo().upstreamClusterInfo().has_value() && + streamInfo().upstreamClusterInfo().value()) { + req_->strValue = streamInfo().upstreamClusterInfo().value()->name(); } else { return CAPIStatus::CAPIValueNotFound; } break; case EnvoyValue::VirtualClusterName: - if (!state.streamInfo().virtualClusterName().has_value()) { + if (!streamInfo().virtualClusterName().has_value()) { return CAPIStatus::CAPIValueNotFound; } - req_->strValue = state.streamInfo().virtualClusterName().value(); + req_->strValue = streamInfo().virtualClusterName().value(); break; default: RELEASE_ASSERT(false, absl::StrCat("invalid string value id: ", id)); @@ -1096,19 +970,13 @@ CAPIStatus Filter::getDynamicMetadata(const std::string& filter_name, uint64_t* return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; - } - - if (!state.isThreadSafe()) { + if (!isThreadSafe()) { auto weak_ptr = weak_from_this(); ENVOY_LOG(debug, "golang filter getDynamicMetadata posting request to dispatcher"); - state.getDispatcher().post([this, &state, weak_ptr, filter_name, buf_data, buf_len] { + getDispatcher().post([this, weak_ptr, filter_name, buf_data, buf_len] { ENVOY_LOG(debug, "golang filter getDynamicMetadata request in worker thread"); if (!weak_ptr.expired() && !hasDestroyed()) { - populateSliceWithMetadata(state, filter_name, buf_data, buf_len); + populateSliceWithMetadata(filter_name, buf_data, buf_len); dynamic_lib_->envoyGoRequestSemaDec(req_); } else { ENVOY_LOG(info, "golang filter has gone or destroyed in getDynamicMetadata"); @@ -1117,15 +985,15 @@ CAPIStatus Filter::getDynamicMetadata(const std::string& filter_name, uint64_t* return CAPIStatus::CAPIYield; } else { ENVOY_LOG(debug, "golang filter getDynamicMetadata replying directly"); - populateSliceWithMetadata(state, filter_name, buf_data, buf_len); + populateSliceWithMetadata(filter_name, buf_data, buf_len); } return CAPIStatus::CAPIOK; } -void Filter::populateSliceWithMetadata(ProcessorState& state, const std::string& filter_name, - uint64_t* buf_data, int* buf_len) { - const auto& metadata = state.streamInfo().dynamicMetadata().filter_metadata(); +void Filter::populateSliceWithMetadata(const std::string& filter_name, uint64_t* buf_data, + int* buf_len) { + const auto& metadata = streamInfo().dynamicMetadata().filter_metadata(); const auto filter_it = metadata.find(filter_name); if (filter_it != metadata.end()) { filter_it->second.SerializeToString(&req_->strValue); @@ -1143,21 +1011,15 @@ CAPIStatus Filter::setDynamicMetadata(std::string filter_name, std::string key, return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; - } - - if (!state.isThreadSafe()) { + if (!isThreadSafe()) { auto weak_ptr = weak_from_this(); // Since go only waits for the CAPI return code we need to create a deep copy // of the buffer slice and pass that to the dispatcher. auto buff_copy = std::string(buf); - state.getDispatcher().post([this, &state, weak_ptr, filter_name, key, buff_copy] { + getDispatcher().post([this, weak_ptr, filter_name, key, buff_copy] { if (!weak_ptr.expired() && !hasDestroyed()) { - ASSERT(state.isThreadSafe()); - setDynamicMetadataInternal(state, filter_name, key, buff_copy); + ASSERT(isThreadSafe()); + setDynamicMetadataInternal(filter_name, key, buff_copy); } else { ENVOY_LOG(info, "golang filter has gone or destroyed in setDynamicMetadata"); } @@ -1166,19 +1028,19 @@ CAPIStatus Filter::setDynamicMetadata(std::string filter_name, std::string key, } // it's safe to do it here since we are in the safe envoy worker thread now. - setDynamicMetadataInternal(state, filter_name, key, buf); + setDynamicMetadataInternal(filter_name, key, buf); return CAPIStatus::CAPIOK; } -void Filter::setDynamicMetadataInternal(ProcessorState& state, std::string filter_name, - std::string key, const absl::string_view& buf) { +void Filter::setDynamicMetadataInternal(std::string filter_name, std::string key, + const absl::string_view& buf) { ProtobufWkt::Struct value; ProtobufWkt::Value v; v.ParseFromArray(buf.data(), buf.length()); (*value.mutable_fields())[key] = v; - state.streamInfo().setDynamicMetadata(filter_name, value); + streamInfo().setDynamicMetadata(filter_name, value); } CAPIStatus Filter::setStringFilterState(absl::string_view key, absl::string_view value, @@ -1190,14 +1052,8 @@ CAPIStatus Filter::setStringFilterState(absl::string_view key, absl::string_view return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; - } - - if (state.isThreadSafe()) { - state.streamInfo().filterState()->setData( + if (isThreadSafe()) { + streamInfo().filterState()->setData( key, std::make_shared(value), static_cast(state_type), static_cast(life_span), @@ -1206,11 +1062,10 @@ CAPIStatus Filter::setStringFilterState(absl::string_view key, absl::string_view auto key_str = std::string(key); auto filter_state = std::make_shared(value); auto weak_ptr = weak_from_this(); - state.getDispatcher().post( - [this, &state, weak_ptr, key_str, filter_state, state_type, life_span, stream_sharing] { + getDispatcher().post( + [this, weak_ptr, key_str, filter_state, state_type, life_span, stream_sharing] { if (!weak_ptr.expired() && !hasDestroyed()) { - Thread::LockGuard lock(mutex_); - state.streamInfo().filterState()->setData( + streamInfo().filterState()->setData( key_str, filter_state, static_cast(state_type), static_cast(life_span), static_cast(stream_sharing)); @@ -1231,15 +1086,8 @@ CAPIStatus Filter::getStringFilterState(absl::string_view key, uint64_t* value_d return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; - } - - if (state.isThreadSafe()) { - auto go_filter_state = - state.streamInfo().filterState()->getDataReadOnly(key); + if (isThreadSafe()) { + auto go_filter_state = streamInfo().filterState()->getDataReadOnly(key); if (go_filter_state) { req_->strValue = go_filter_state->value(); *value_data = reinterpret_cast(req_->strValue.data()); @@ -1248,10 +1096,10 @@ CAPIStatus Filter::getStringFilterState(absl::string_view key, uint64_t* value_d } else { auto key_str = std::string(key); auto weak_ptr = weak_from_this(); - state.getDispatcher().post([this, &state, weak_ptr, key_str, value_data, value_len] { + getDispatcher().post([this, weak_ptr, key_str, value_data, value_len] { if (!weak_ptr.expired() && !hasDestroyed()) { auto go_filter_state = - state.streamInfo().filterState()->getDataReadOnly(key_str); + streamInfo().filterState()->getDataReadOnly(key_str); if (go_filter_state) { req_->strValue = go_filter_state->value(); *value_data = reinterpret_cast(req_->strValue.data()); @@ -1276,27 +1124,17 @@ CAPIStatus Filter::getStringProperty(absl::string_view path, uint64_t* value_dat return CAPIStatus::CAPIFilterIsDestroy; } - auto& state = getProcessorState(); - if (!state.isProcessingInGo()) { - ENVOY_LOG(debug, "golang filter is not processing Go"); - return CAPIStatus::CAPINotInGo; - } - // to access the headers_ and its friends we need to hold the lock activation_request_headers_ = dynamic_cast(request_headers_); - if (enter_encoding_) { - activation_response_headers_ = dynamic_cast(headers_); - activation_response_trailers_ = dynamic_cast(trailers_); - } - if (state.isThreadSafe()) { - return getStringPropertyCommon(path, value_data, value_len, state); + if (isThreadSafe()) { + return getStringPropertyCommon(path, value_data, value_len); } auto weak_ptr = weak_from_this(); - state.getDispatcher().post([this, &state, weak_ptr, path, value_data, value_len, rc] { + getDispatcher().post([this, weak_ptr, path, value_data, value_len, rc] { if (!weak_ptr.expired() && !hasDestroyed()) { - *rc = getStringPropertyCommon(path, value_data, value_len, state); + *rc = getStringPropertyCommon(path, value_data, value_len); dynamic_lib_->envoyGoRequestSemaDec(req_); } else { ENVOY_LOG(info, "golang filter has gone or destroyed in getStringProperty"); @@ -1306,8 +1144,8 @@ CAPIStatus Filter::getStringProperty(absl::string_view path, uint64_t* value_dat } CAPIStatus Filter::getStringPropertyCommon(absl::string_view path, uint64_t* value_data, - int* value_len, ProcessorState& state) { - activation_info_ = &state.streamInfo(); + int* value_len) { + activation_info_ = &streamInfo(); CAPIStatus status = getStringPropertyInternal(path, &req_->strValue); if (status == CAPIStatus::CAPIOK) { *value_data = reinterpret_cast(req_->strValue.data()); @@ -1454,20 +1292,33 @@ CAPIStatus Filter::serializeStringValue(Filters::Common::Expr::CelValue value, } } -void Filter::initRequest(ProcessorState& state) { - // req is used by go, so need to use raw memory and then it is safe to release at the gc - // finalize phase of the go object. - req_ = new httpRequestInternal(weak_from_this()); - req_->configId = getMergedConfigId(state); - req_->plugin_name.data = config_->pluginName().data(); - req_->plugin_name.len = config_->pluginName().length(); - req_->worker_id = worker_id_; +bool Filter::initRequest() { + if (req_->configId == 0) { + req_->setWeakFilter(weak_from_this()); + req_->configId = getMergedConfigId(); + return true; + } + return false; +} + +void Filter::deferredDeleteRequest(HttpRequestInternal* req) { + ASSERT(req == req_, "invalid request pointer"); + auto& dispatcher = getDispatcher(); + if (dispatcher.isThreadSafe()) { + auto r = std::make_unique(req); + dispatcher.deferredDelete(std::move(r)); + } else { + dispatcher.post([&dispatcher, req] { + auto r = std::make_unique(req); + dispatcher.deferredDelete(std::move(r)); + }); + } } /* ConfigId */ -uint64_t Filter::getMergedConfigId(ProcessorState& state) { - Http::StreamFilterCallbacks* callbacks = state.getFilterCallbacks(); +uint64_t Filter::getMergedConfigId() { + Http::StreamFilterCallbacks* callbacks = decoding_state_.getFilterCallbacks(); // get all of the per route config std::list route_config_list; @@ -1639,7 +1490,7 @@ FilterConfigPerRoute::FilterConfigPerRoute( for (const auto& it : config.plugins_config()) { auto plugin_name = it.first; auto route_plugin = it.second; - RoutePluginConfigPtr conf(new RoutePluginConfig(plugin_name, route_plugin)); + RoutePluginConfigPtr conf = std::make_shared(plugin_name, route_plugin); ENVOY_LOG(debug, "per route golang filter config, type_url: {}", route_plugin.config().type_url()); plugins_config_.insert({plugin_name, std::move(conf)}); @@ -1703,13 +1554,12 @@ uint64_t RoutePluginConfig::getConfigId() { auto buf_ptr = reinterpret_cast(buf.data()); auto name_ptr = reinterpret_cast(plugin_name_.data()); - config_ = new httpConfig(); - config_->plugin_name_ptr = name_ptr; - config_->plugin_name_len = plugin_name_.length(); - config_->config_ptr = buf_ptr; - config_->config_len = buf.length(); - config_->is_route_config = 1; - return dso_lib_->envoyGoFilterNewHttpPluginConfig(config_); + config_.plugin_name_ptr = name_ptr; + config_.plugin_name_len = plugin_name_.length(); + config_.config_ptr = buf_ptr; + config_.config_len = buf.length(); + config_.is_route_config = 1; + return dso_lib_->envoyGoFilterNewHttpPluginConfig(&config_); }; uint64_t RoutePluginConfig::getMergedConfigId(uint64_t parent_id) { @@ -1751,12 +1601,6 @@ uint64_t RoutePluginConfig::getMergedConfigId(uint64_t parent_id) { return merged_config_id_; }; -/* ProcessorState */ -ProcessorState& Filter::getProcessorState() { - return enter_encoding_ ? dynamic_cast(encoding_state_) - : dynamic_cast(decoding_state_); -}; - } // namespace Golang } // namespace HttpFilters } // namespace Extensions diff --git a/contrib/golang/filters/http/source/golang_filter.h b/contrib/golang/filters/http/source/golang_filter.h index ec9dcdf41754..f9435eee147c 100644 --- a/contrib/golang/filters/http/source/golang_filter.h +++ b/contrib/golang/filters/http/source/golang_filter.h @@ -14,7 +14,6 @@ #include "source/extensions/filters/common/expr/evaluator.h" #include "contrib/envoy/extensions/filters/http/golang/v3alpha/golang.pb.h" -#include "contrib/golang/common/dso/dso.h" #include "contrib/golang/filters/http/source/processor_state.h" #include "contrib/golang/filters/http/source/stats.h" @@ -94,6 +93,7 @@ class FilterConfig : public std::enable_shared_from_this, // TODO(StarryVae): use rwlock. Thread::MutexBasicLockable mutex_{}; MetricStoreSharedPtr metric_store_ ABSL_GUARDED_BY(mutex_); + // filter level config is created in C++ side, and freed by Golang GC finalizer. httpConfigInternal* config_{nullptr}; }; @@ -119,7 +119,8 @@ class RoutePluginConfig : public std::enable_shared_from_this uint64_t cached_parent_id_ ABSL_GUARDED_BY(mutex_){0}; absl::Mutex mutex_; - httpConfig* config_{nullptr}; + // route level config, no Golang GC finalizer. + httpConfig config_; }; using RoutePluginConfigPtr = std::shared_ptr; @@ -160,7 +161,44 @@ enum class EnvoyValue { VirtualClusterName, }; -struct httpRequestInternal; +class Filter; + +// Go code only touch the fields in httpRequest +class HttpRequestInternal : public httpRequest { +public: + HttpRequestInternal(Filter& filter) + : decoding_state_(filter, this), encoding_state_(filter, this) { + configId = 0; + } + + void setWeakFilter(std::weak_ptr f) { filter_ = f; } + std::weak_ptr weakFilter() { return filter_; } + + DecodingProcessorState& decodingState() { return decoding_state_; } + EncodingProcessorState& encodingState() { return encoding_state_; } + + // anchor a string temporarily, make sure it won't be freed before copied to Go. + std::string strValue; + +private: + std::weak_ptr filter_; + + // The state of the filter on both the encoding and decoding side. + DecodingProcessorState decoding_state_; + EncodingProcessorState encoding_state_; +}; + +// Wrapper HttpRequestInternal to DeferredDeletable. +// Since we want keep httpRequest at the top of the HttpRequestInternal, +// so, HttpRequestInternal can not inherit the virtual class DeferredDeletable. +class HttpRequestInternalWrapper : public Envoy::Event::DeferredDeletable { +public: + HttpRequestInternalWrapper(HttpRequestInternal* req) : req_(req) {} + ~HttpRequestInternalWrapper() override { delete req_; } + +private: + HttpRequestInternal* req_; +}; /** * See docs/configuration/http_filters/golang_extension_filter.rst @@ -173,8 +211,16 @@ class Filter : public Http::StreamFilter, public: explicit Filter(FilterConfigSharedPtr config, Dso::HttpFilterDsoPtr dynamic_lib, uint32_t worker_id) - : config_(config), dynamic_lib_(dynamic_lib), decoding_state_(*this), encoding_state_(*this), - worker_id_(worker_id) {} + : config_(config), dynamic_lib_(dynamic_lib), req_(new HttpRequestInternal(*this)), + decoding_state_(req_->decodingState()), encoding_state_(req_->encodingState()) { + // req is used by go, so need to use raw memory and then it is safe to release at the gc + // finalize phase of the go object. + req_->plugin_name.data = config_->pluginName().data(); + req_->plugin_name.len = config_->pluginName().length(); + req_->worker_id = worker_id; + ENVOY_LOG(debug, "initilizing Golang Filter, decode state: {}, encode state: {}", + decoding_state_.stateStr(), encoding_state_.stateStr()); + } // Http::StreamFilterBase void onDestroy() ABSL_LOCKS_EXCLUDED(mutex_) override; @@ -212,25 +258,29 @@ class Filter : public Http::StreamFilter, void onStreamComplete() override {} CAPIStatus clearRouteCache(); - CAPIStatus continueStatus(GolangStatus status); + CAPIStatus continueStatus(ProcessorState& state, GolangStatus status); - CAPIStatus sendLocalReply(Http::Code response_code, std::string body_text, + CAPIStatus sendLocalReply(ProcessorState& state, Http::Code response_code, std::string body_text, std::function modify_headers, Grpc::Status::GrpcStatus grpc_status, std::string details); - CAPIStatus sendPanicReply(absl::string_view details); - - CAPIStatus getHeader(absl::string_view key, uint64_t* value_data, int* value_len); - CAPIStatus copyHeaders(GoString* go_strs, char* go_buf); - CAPIStatus setHeader(absl::string_view key, absl::string_view value, headerAction act); - CAPIStatus removeHeader(absl::string_view key); - CAPIStatus copyBuffer(Buffer::Instance* buffer, char* data); - CAPIStatus drainBuffer(Buffer::Instance* buffer, uint64_t length); - CAPIStatus setBufferHelper(Buffer::Instance* buffer, absl::string_view& value, - bufferAction action); - CAPIStatus copyTrailers(GoString* go_strs, char* go_buf); - CAPIStatus setTrailer(absl::string_view key, absl::string_view value, headerAction act); - CAPIStatus removeTrailer(absl::string_view key); + CAPIStatus sendPanicReply(ProcessorState& state, absl::string_view details); + + CAPIStatus getHeader(ProcessorState& state, absl::string_view key, uint64_t* value_data, + int* value_len); + CAPIStatus copyHeaders(ProcessorState& state, GoString* go_strs, char* go_buf); + CAPIStatus setHeader(ProcessorState& state, absl::string_view key, absl::string_view value, + headerAction act); + CAPIStatus removeHeader(ProcessorState& state, absl::string_view key); + CAPIStatus copyBuffer(ProcessorState& state, Buffer::Instance* buffer, char* data); + CAPIStatus drainBuffer(ProcessorState& state, Buffer::Instance* buffer, uint64_t length); + CAPIStatus setBufferHelper(ProcessorState& state, Buffer::Instance* buffer, + absl::string_view& value, bufferAction action); + CAPIStatus copyTrailers(ProcessorState& state, GoString* go_strs, char* go_buf); + CAPIStatus setTrailer(ProcessorState& state, absl::string_view key, absl::string_view value, + headerAction act); + CAPIStatus removeTrailer(ProcessorState& state, absl::string_view key); + CAPIStatus getStringValue(int id, uint64_t* value_data, int* value_len); CAPIStatus getIntegerValue(int id, uint64_t* value); @@ -242,12 +292,21 @@ class Filter : public Http::StreamFilter, CAPIStatus getStringProperty(absl::string_view path, uint64_t* value_data, int* value_len, GoInt32* rc); + bool isProcessingInGo() { + return is_golang_processing_log_ || decoding_state_.isProcessingInGo() || + encoding_state_.isProcessingInGo(); + } + void deferredDeleteRequest(HttpRequestInternal* req); + private: bool hasDestroyed() { Thread::LockGuard lock(mutex_); return has_destroyed_; }; - ProcessorState& getProcessorState(); + const StreamInfo::StreamInfo& streamInfo() const { return decoding_state_.streamInfo(); } + StreamInfo::StreamInfo& streamInfo() { return decoding_state_.streamInfo(); } + bool isThreadSafe() { return decoding_state_.isThreadSafe(); }; + Event::Dispatcher& getDispatcher() { return decoding_state_.getDispatcher(); } bool doHeaders(ProcessorState& state, Http::RequestOrResponseHeaderMap& headers, bool end_stream); GolangStatus doHeadersGo(ProcessorState& state, Http::RequestOrResponseHeaderMap& headers, @@ -257,26 +316,26 @@ class Filter : public Http::StreamFilter, bool doTrailer(ProcessorState& state, Http::HeaderMap& trailers); bool doTrailerGo(ProcessorState& state, Http::HeaderMap& trailers); - void initRequest(ProcessorState& state); + // return true when it is first inited. + bool initRequest(); - uint64_t getMergedConfigId(ProcessorState& state); + uint64_t getMergedConfigId(); void continueEncodeLocalReply(ProcessorState& state); - void continueStatusInternal(GolangStatus status); + void continueStatusInternal(ProcessorState& state, GolangStatus status); void continueData(ProcessorState& state); - void sendLocalReplyInternal(Http::Code response_code, absl::string_view body_text, + void sendLocalReplyInternal(ProcessorState& state, Http::Code response_code, + absl::string_view body_text, std::function modify_headers, Grpc::Status::GrpcStatus grpc_status, absl::string_view details); - void setDynamicMetadataInternal(ProcessorState& state, std::string filter_name, std::string key, + void setDynamicMetadataInternal(std::string filter_name, std::string key, const absl::string_view& buf); - void populateSliceWithMetadata(ProcessorState& state, const std::string& filter_name, - uint64_t* buf_data, int* buf_len); + void populateSliceWithMetadata(const std::string& filter_name, uint64_t* buf_data, int* buf_len); - CAPIStatus getStringPropertyCommon(absl::string_view path, uint64_t* value_data, int* value_len, - ProcessorState& state); + CAPIStatus getStringPropertyCommon(absl::string_view path, uint64_t* value_data, int* value_len); CAPIStatus getStringPropertyInternal(absl::string_view path, std::string* result); absl::optional findValue(absl::string_view name, Protobuf::Arena* arena); @@ -285,49 +344,24 @@ class Filter : public Http::StreamFilter, const FilterConfigSharedPtr config_; Dso::HttpFilterDsoPtr dynamic_lib_; - Http::RequestOrResponseHeaderMap* headers_ ABSL_GUARDED_BY(mutex_){nullptr}; - Http::HeaderMap* trailers_ ABSL_GUARDED_BY(mutex_){nullptr}; - - // save temp values from local reply - Http::RequestOrResponseHeaderMap* local_headers_{nullptr}; - Http::HeaderMap* local_trailers_{nullptr}; - // save temp values for fetching request attributes in the later phase, // like getting request size Http::RequestOrResponseHeaderMap* request_headers_{nullptr}; - // The state of the filter on both the encoding and decoding side. - DecodingProcessorState decoding_state_; - EncodingProcessorState encoding_state_; + HttpRequestInternal* req_{nullptr}; - httpRequestInternal* req_{nullptr}; + // The state of the filter on both the encoding and decoding side. + // They are stored in HttpRequestInternal since Go need to read them, + // And it's safe to read them before onDestroy in C++ side. + DecodingProcessorState& decoding_state_; + EncodingProcessorState& encoding_state_; - // lock for has_destroyed_ and the functions get/set/copy/remove/etc that operate on the - // headers_/trailers_/etc, to avoid race between envoy c thread and go thread (when calling back - // from go). it should also be okay without this lock in most cases, just for extreme case. + // lock for has_destroyed_/etc, to avoid race between envoy c thread and go thread (when calling + // back from go). Thread::MutexBasicLockable mutex_{}; bool has_destroyed_ ABSL_GUARDED_BY(mutex_){false}; - // other filter trigger sendLocalReply during go processing in async. - // will wait go return before continue. - // this variable is read/write in safe thread, do no need lock. - bool local_reply_waiting_go_{false}; - - // the filter enter encoding phase - bool enter_encoding_{false}; - - // The ID of the worker that is processing this request, this enables the go filter to dedicate - // memory to each worker and not require locks - uint32_t worker_id_ = 0; -}; - -// Go code only touch the fields in httpRequest -struct httpRequestInternal : httpRequest { - std::weak_ptr filter_; - // anchor a string temporarily, make sure it won't be freed before copied to Go. - std::string strValue; - httpRequestInternal(std::weak_ptr f) { filter_ = f; } - std::weak_ptr weakFilter() { return filter_; } + bool is_golang_processing_log_{false}; }; struct httpConfigInternal : httpConfig { diff --git a/contrib/golang/filters/http/source/processor_state.cc b/contrib/golang/filters/http/source/processor_state.cc index f301e83abdda..44059e006e88 100644 --- a/contrib/golang/filters/http/source/processor_state.cc +++ b/contrib/golang/filters/http/source/processor_state.cc @@ -12,7 +12,7 @@ Buffer::Instance& BufferList::push(Buffer::Instance& data) { bytes_ += data.length(); auto ptr = std::make_unique(); - Buffer::Instance& buffer = *ptr.get(); + Buffer::Instance& buffer = *ptr; buffer.move(data); queue_.push_back(std::move(ptr)); @@ -48,10 +48,10 @@ bool BufferList::checkExisting(Buffer::Instance* data) { // headers_ should set to nullptr when return true. bool ProcessorState::handleHeaderGolangStatus(GolangStatus status) { - ENVOY_LOG(debug, "golang filter handle header status, state: {}, phase: {}, status: {}", - stateStr(), phaseStr(), int(status)); + ENVOY_LOG(debug, "golang filter handle header status, state: {}, status: {}", stateStr(), + int(status)); - ASSERT(state_ == FilterState::ProcessingHeader); + ASSERT(filterState() == FilterState::ProcessingHeader); bool done = false; switch (status) { @@ -65,19 +65,19 @@ bool ProcessorState::handleHeaderGolangStatus(GolangStatus status) { case GolangStatus::Continue: if (do_end_stream_) { - state_ = FilterState::Done; + setFilterState(FilterState::Done); } else { - state_ = FilterState::WaitingData; + setFilterState(FilterState::WaitingData); } done = true; break; case GolangStatus::StopAndBuffer: - state_ = FilterState::WaitingAllData; + setFilterState(FilterState::WaitingAllData); break; case GolangStatus::StopAndBufferWatermark: - state_ = FilterState::WaitingData; + setFilterState(FilterState::WaitingData); break; default: @@ -85,17 +85,17 @@ bool ProcessorState::handleHeaderGolangStatus(GolangStatus status) { break; } - ENVOY_LOG(debug, "golang filter after handle header status, state: {}, phase: {}, status: {}", - stateStr(), phaseStr(), int(status)); + ENVOY_LOG(debug, "golang filter after handle header status, state: {}, status: {}", stateStr(), + int(status)); return done; }; bool ProcessorState::handleDataGolangStatus(const GolangStatus status) { - ENVOY_LOG(debug, "golang filter handle data status, state: {}, phase: {}, status: {}", stateStr(), - phaseStr(), int(status)); + ENVOY_LOG(debug, "golang filter handle data status, state: {}, status: {}", stateStr(), + int(status)); - ASSERT(state_ == FilterState::ProcessingData); + ASSERT(filterState() == FilterState::ProcessingData); bool done = false; @@ -112,9 +112,9 @@ bool ProcessorState::handleDataGolangStatus(const GolangStatus status) { case GolangStatus::Continue: if (do_end_stream_) { - state_ = FilterState::Done; + setFilterState(FilterState::Done); } else { - state_ = FilterState::WaitingData; + setFilterState(FilterState::WaitingData); } done = true; break; @@ -124,7 +124,7 @@ bool ProcessorState::handleDataGolangStatus(const GolangStatus status) { ENVOY_LOG(error, "want more data while stream is end"); // TODO: terminate the stream? } - state_ = FilterState::WaitingAllData; + setFilterState(FilterState::WaitingAllData); break; case GolangStatus::StopAndBufferWatermark: @@ -132,7 +132,7 @@ bool ProcessorState::handleDataGolangStatus(const GolangStatus status) { ENVOY_LOG(error, "want more data while stream is end"); // TODO: terminate the stream? } - state_ = FilterState::WaitingData; + setFilterState(FilterState::WaitingData); break; case GolangStatus::StopNoBuffer: @@ -141,7 +141,7 @@ bool ProcessorState::handleDataGolangStatus(const GolangStatus status) { // TODO: terminate the stream? } doDataList.clearLatest(); - state_ = FilterState::WaitingData; + setFilterState(FilterState::WaitingData); break; default: @@ -151,13 +151,13 @@ bool ProcessorState::handleDataGolangStatus(const GolangStatus status) { } // see trailers and no buffered data - if (seen_trailers_ && isBufferDataEmpty()) { - ENVOY_LOG(error, "see trailers and buffer is empty"); - state_ = FilterState::WaitingTrailer; + if (trailers != nullptr && isBufferDataEmpty()) { + ENVOY_LOG(debug, "see trailers and buffer is empty"); + setFilterState(FilterState::WaitingTrailer); } - ENVOY_LOG(debug, "golang filter after handle data status, state: {}, phase: {}, status: {}", - int(state_), phaseStr(), int(status)); + ENVOY_LOG(debug, "golang filter after handle data status, state: {}, status: {}", stateStr(), + int(status)); return done; }; @@ -165,10 +165,10 @@ bool ProcessorState::handleDataGolangStatus(const GolangStatus status) { // should set trailers_ to nullptr when return true. // means we should not read/write trailers then, since trailers will pass to next fitler. bool ProcessorState::handleTrailerGolangStatus(const GolangStatus status) { - ENVOY_LOG(debug, "golang filter handle trailer status, state: {}, phase: {}, status: {}", - stateStr(), phaseStr(), int(status)); + ENVOY_LOG(debug, "golang filter handle trailer status, state: {}, status: {}", stateStr(), + int(status)); - ASSERT(state_ == FilterState::ProcessingTrailer); + ASSERT(filterState() == FilterState::ProcessingTrailer); auto done = false; @@ -182,7 +182,7 @@ bool ProcessorState::handleTrailerGolangStatus(const GolangStatus status) { break; case GolangStatus::Continue: - state_ = FilterState::Done; + setFilterState(FilterState::Done); done = true; break; @@ -192,8 +192,8 @@ bool ProcessorState::handleTrailerGolangStatus(const GolangStatus status) { break; } - ENVOY_LOG(debug, "golang filter after handle trailer status, state: {}, phase: {}, status: {}", - stateStr(), phaseStr(), int(status)); + ENVOY_LOG(debug, "golang filter after handle trailer status, state: {}, status: {}", stateStr(), + int(status)); return done; }; @@ -204,12 +204,12 @@ bool ProcessorState::handleGolangStatus(GolangStatus status) { ASSERT(isProcessingInGo(), "unexpected state"); ENVOY_LOG(debug, - "before handle golang status, status: {}, state: {}, phase: {}, " - "do_end_stream_: {}", - int(status), stateStr(), phaseStr(), do_end_stream_); + "before handle golang status, status: {}, state: {}, " + "do_end_stream_: {}, seen trailers: {}", + int(status), stateStr(), do_end_stream_, trailers != nullptr); bool done = false; - switch (state_) { + switch (filterState()) { case FilterState::ProcessingHeader: done = handleHeaderGolangStatus(status); break; @@ -227,9 +227,9 @@ bool ProcessorState::handleGolangStatus(GolangStatus status) { } ENVOY_LOG(debug, - "after handle golang status, status: {}, state: {}, phase: {}, " - "do_end_stream_: {}", - int(status), stateStr(), phaseStr(), do_end_stream_); + "after handle golang status, status: {}, state: {}, " + "do_end_stream_: {}, done: {}, seen trailers: {}", + int(status), stateStr(), do_end_stream_, done, trailers != nullptr); return done; } @@ -244,8 +244,8 @@ void ProcessorState::drainBufferData() { } } -std::string ProcessorState::stateStr() { - switch (state_) { +std::string state2Str(FilterState state) { + switch (state) { case FilterState::WaitingHeader: return "WaitingHeader"; case FilterState::ProcessingHeader: @@ -260,61 +260,17 @@ std::string ProcessorState::stateStr() { return "WaitingTrailer"; case FilterState::ProcessingTrailer: return "ProcessingTrailer"; - case FilterState::Log: - return "Log"; case FilterState::Done: return "Done"; default: - return "unknown"; + return "unknown(" + std::to_string(static_cast(state)) + ")"; } } -Phase ProcessorState::state2Phase() { - Phase phase; - switch (state_) { - case FilterState::WaitingHeader: - case FilterState::ProcessingHeader: - phase = Phase::DecodeHeader; - break; - case FilterState::WaitingData: - case FilterState::WaitingAllData: - case FilterState::ProcessingData: - phase = Phase::DecodeData; - break; - case FilterState::WaitingTrailer: - case FilterState::ProcessingTrailer: - phase = Phase::DecodeTrailer; - break; - case FilterState::Log: - phase = Phase::Log; - break; - // decode Done state means encode header phase, encode done state means done phase - case FilterState::Done: - phase = Phase::EncodeHeader; - break; - } - return phase; -}; - -std::string ProcessorState::phaseStr() { - switch (phase()) { - case Phase::DecodeHeader: - return "DecodeHeader"; - case Phase::DecodeData: - return "DecodeData"; - case Phase::DecodeTrailer: - return "DecodeTrailer"; - case Phase::EncodeHeader: - return "EncodeHeader"; - case Phase::EncodeData: - return "EncodeData"; - case Phase::EncodeTrailer: - return "EncodeTrailer"; - case Phase::Log: - return "Log"; - default: - return "unknown"; - } +std::string ProcessorState::stateStr() { + std::string prefix = is_encoding == 1 ? "encoder" : "decoder"; + auto state_str = state2Str(filterState()); + return prefix + ":" + state_str; } void DecodingProcessorState::addBufferData(Buffer::Instance& data) { @@ -328,7 +284,7 @@ void DecodingProcessorState::addBufferData(Buffer::Instance& data) { } }, [this]() -> void { - if (state_ == FilterState::WaitingAllData) { + if (filterState() == FilterState::WaitingAllData) { // On the request path exceeding buffer limits will result in a 413. ENVOY_LOG(debug, "golang filter decode data buffer is full, reply with 413"); decoder_callbacks_->sendLocalReply( @@ -360,7 +316,7 @@ void EncodingProcessorState::addBufferData(Buffer::Instance& data) { } }, [this]() -> void { - if (state_ == FilterState::WaitingAllData) { + if (filterState() == FilterState::WaitingAllData) { ENVOY_LOG(debug, "golang filter encode data buffer is full, reply with 500"); // In this case, sendLocalReply will either send a response directly to the encoder, or diff --git a/contrib/golang/filters/http/source/processor_state.h b/contrib/golang/filters/http/source/processor_state.h index d0acc260cb95..d537d68a327c 100644 --- a/contrib/golang/filters/http/source/processor_state.h +++ b/contrib/golang/filters/http/source/processor_state.h @@ -13,6 +13,7 @@ #include "source/common/http/utility.h" #include "absl/status/status.h" +#include "contrib/golang/common/dso/dso.h" namespace Envoy { namespace Extensions { @@ -59,26 +60,10 @@ enum class FilterState { WaitingTrailer, // Processing trailer in Go ProcessingTrailer, - // Log in Go - Log, // All done Done, }; -/* - * request phase - */ -enum class Phase { - DecodeHeader = 1, - DecodeData, - DecodeTrailer, - EncodeHeader, - EncodeData, - EncodeTrailer, - Log, - Done, -}; - /** * An enum specific for Golang status. */ @@ -93,26 +78,29 @@ enum class GolangStatus { StopNoBuffer, }; -class ProcessorState : public Logger::Loggable, NonCopyable { +class ProcessorState : public processState, public Logger::Loggable, NonCopyable { public: - explicit ProcessorState(Filter& filter) : filter_(filter) {} + explicit ProcessorState(Filter& filter, httpRequest* r) : filter_(filter) { + req = r; + setFilterState(FilterState::WaitingHeader); + } virtual ~ProcessorState() = default; - FilterState state() const { return state_; } + FilterState filterState() const { return static_cast(state); } + void setFilterState(FilterState st) { state = static_cast(st); } std::string stateStr(); - virtual Phase phase() PURE; - std::string phaseStr(); + virtual Http::StreamFilterCallbacks* getFilterCallbacks() const PURE; bool isProcessingInGo() { - return state_ == FilterState::ProcessingHeader || state_ == FilterState::ProcessingData || - state_ == FilterState::ProcessingTrailer || state_ == FilterState::Log; + return filterState() == FilterState::ProcessingHeader || + filterState() == FilterState::ProcessingData || + filterState() == FilterState::ProcessingTrailer; } - bool isProcessingHeader() { return state_ == FilterState::ProcessingHeader; } - Http::StreamFilterCallbacks* getFilterCallbacks() { return filter_callbacks_; }; + bool isProcessingHeader() { return filterState() == FilterState::ProcessingHeader; } - bool isThreadSafe() { return filter_callbacks_->dispatcher().isThreadSafe(); }; - Event::Dispatcher& getDispatcher() { return filter_callbacks_->dispatcher(); } + bool isThreadSafe() { return getFilterCallbacks()->dispatcher().isThreadSafe(); }; + Event::Dispatcher& getDispatcher() { return getFilterCallbacks()->dispatcher(); } /* data buffer */ // add data to state buffer @@ -122,7 +110,6 @@ class ProcessorState : public Logger::Loggable, NonCopyable { bool isBufferDataEmpty() { return data_buffer_ == nullptr || data_buffer_->length() == 0; }; void drainBufferData(); - void setSeenTrailers() { seen_trailers_ = true; } bool isProcessingEndStream() { return do_end_stream_; } virtual void continueProcessing() PURE; @@ -134,38 +121,33 @@ class ProcessorState : public Logger::Loggable, NonCopyable { Buffer::OwnedImpl data_to_write; doDataList.moveOut(data_to_write); + ENVOY_LOG(debug, "golang filter injecting data to filter chain, end_stream: {}", + do_end_stream_); injectDataToFilterChain(data_to_write, do_end_stream_); } void processHeader(bool end_stream) { - ASSERT(state_ == FilterState::WaitingHeader); - state_ = FilterState::ProcessingHeader; + ASSERT(filterState() == FilterState::WaitingHeader); + setFilterState(FilterState::ProcessingHeader); do_end_stream_ = end_stream; } void processData(bool end_stream) { - ASSERT(state_ == FilterState::WaitingData || - (state_ == FilterState::WaitingAllData && (end_stream || seen_trailers_))); - state_ = FilterState::ProcessingData; + ASSERT(filterState() == FilterState::WaitingData || + (filterState() == FilterState::WaitingAllData && (end_stream || trailers != nullptr))); + setFilterState(FilterState::ProcessingData); + do_end_stream_ = end_stream; } void processTrailer() { - ASSERT(state_ == FilterState::WaitingTrailer || state_ == FilterState::WaitingData || - state_ == FilterState::WaitingAllData); - state_ = FilterState::ProcessingTrailer; + ASSERT(filterState() == FilterState::WaitingTrailer || + filterState() == FilterState::WaitingData || + filterState() == FilterState::WaitingAllData); + setFilterState(FilterState::ProcessingTrailer); do_end_stream_ = true; } - void enterLog() { - prev_state_ = state_; - state_ = FilterState::Log; - } - void leaveLog() { - state_ = prev_state_; - prev_state_ = FilterState::Log; - } - bool handleHeaderGolangStatus(const GolangStatus status); bool handleDataGolangStatus(const GolangStatus status); bool handleTrailerGolangStatus(const GolangStatus status); @@ -175,44 +157,42 @@ class ProcessorState : public Logger::Loggable, NonCopyable { std::function modify_headers, Grpc::Status::GrpcStatus grpc_status, absl::string_view details) PURE; - const StreamInfo::StreamInfo& streamInfo() const { return filter_callbacks_->streamInfo(); } - StreamInfo::StreamInfo& streamInfo() { return filter_callbacks_->streamInfo(); } + const StreamInfo::StreamInfo& streamInfo() const { return getFilterCallbacks()->streamInfo(); } + StreamInfo::StreamInfo& streamInfo() { return getFilterCallbacks()->streamInfo(); } void setEndStream(bool end_stream) { end_stream_ = end_stream; } bool getEndStream() { return end_stream_; } // seen trailers also means stream is end - bool isStreamEnd() { return end_stream_ || seen_trailers_; } + bool isStreamEnd() { return end_stream_ || trailers != nullptr; } + + Http::RequestOrResponseHeaderMap* headers{nullptr}; + Http::HeaderMap* trailers{nullptr}; BufferList doDataList; protected: - Phase state2Phase(); Filter& filter_; - Http::StreamFilterCallbacks* filter_callbacks_{nullptr}; bool watermark_requested_{false}; Buffer::InstancePtr data_buffer_{nullptr}; - FilterState state_{FilterState::WaitingHeader}; - FilterState prev_state_{FilterState::Done}; bool end_stream_{false}; bool do_end_stream_{false}; - bool seen_trailers_{false}; }; class DecodingProcessorState : public ProcessorState { public: - explicit DecodingProcessorState(Filter& filter) : ProcessorState(filter) {} + explicit DecodingProcessorState(Filter& filter, httpRequest* r) : ProcessorState(filter, r) { + is_encoding = 0; + } void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) { decoder_callbacks_ = &callbacks; - filter_callbacks_ = &callbacks; } + Http::StreamFilterCallbacks* getFilterCallbacks() const override { return decoder_callbacks_; } void injectDataToFilterChain(Buffer::Instance& data, bool end_stream) override { decoder_callbacks_->injectDecodedDataToFilterChain(data, end_stream); } - Phase phase() override { return state2Phase(); }; - void addBufferData(Buffer::Instance& data) override; void continueProcessing() override { @@ -222,10 +202,10 @@ class DecodingProcessorState : public ProcessorState { void sendLocalReply(Http::Code response_code, absl::string_view body_text, std::function modify_headers, Grpc::Status::GrpcStatus grpc_status, absl::string_view details) override { - // it's safe to reset state_, since it is read/write in safe thread. + // it's safe to reset filterState(), since it is read/write in safe thread. ENVOY_LOG(debug, "golang filter phase grow to EncodeHeader and state grow to WaitHeader before " "sendLocalReply"); - state_ = FilterState::WaitingHeader; + setFilterState(FilterState::WaitingHeader); decoder_callbacks_->sendLocalReply(response_code, body_text, modify_headers, grpc_status, details); }; @@ -236,19 +216,19 @@ class DecodingProcessorState : public ProcessorState { class EncodingProcessorState : public ProcessorState { public: - explicit EncodingProcessorState(Filter& filter) : ProcessorState(filter) {} + explicit EncodingProcessorState(Filter& filter, httpRequest* r) : ProcessorState(filter, r) { + is_encoding = 1; + } void setEncoderFilterCallbacks(Http::StreamEncoderFilterCallbacks& callbacks) { encoder_callbacks_ = &callbacks; - filter_callbacks_ = &callbacks; } + Http::StreamFilterCallbacks* getFilterCallbacks() const override { return encoder_callbacks_; } void injectDataToFilterChain(Buffer::Instance& data, bool end_stream) override { encoder_callbacks_->injectEncodedDataToFilterChain(data, end_stream); } - Phase phase() override { return static_cast(static_cast(state2Phase()) + 3); }; - void addBufferData(Buffer::Instance& data) override; void continueProcessing() override { diff --git a/contrib/golang/filters/http/test/BUILD b/contrib/golang/filters/http/test/BUILD index d6df96579249..bd23137031ce 100644 --- a/contrib/golang/filters/http/test/BUILD +++ b/contrib/golang/filters/http/test/BUILD @@ -98,3 +98,26 @@ envoy_cc_fuzz_test( "@envoy_api//contrib/envoy/extensions/filters/http/golang/v3alpha:pkg_cc_proto", ], ) + +envoy_cc_test( + name = "websocket_integration_test", + size = "large", + srcs = ["websocket_integration_test.cc"], + data = [ + "//contrib/golang/filters/http/test/test_data/websocket:filter.so", + ], + tags = [ + "cpu:3", + ], + deps = [ + "//contrib/golang/filters/http/source:config", + "//source/common/http:header_map_lib", + "//source/extensions/access_loggers/file:config", + "//source/extensions/filters/http/buffer:config", + "//test/integration:http_protocol_integration_lib", + "//test/integration:websocket_integration_test_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", + ], +) diff --git a/contrib/golang/filters/http/test/golang_filter_fuzz_test.cc b/contrib/golang/filters/http/test/golang_filter_fuzz_test.cc index 6773ec59a174..c15444c31e61 100644 --- a/contrib/golang/filters/http/test/golang_filter_fuzz_test.cc +++ b/contrib/golang/filters/http/test/golang_filter_fuzz_test.cc @@ -59,7 +59,7 @@ DEFINE_PROTO_FUZZER(const envoy::extensions::filters::http::golang::GolangFilter ON_CALL(*dso_lib.get(), envoyGoFilterOnHttpDestroy(_, _)) .WillByDefault(Invoke([&](httpRequest* p0, int) -> void { // delete the filter->req_, make LeakSanitizer happy. - auto req = reinterpret_cast(p0); + auto req = reinterpret_cast(p0); delete req; })); diff --git a/contrib/golang/filters/http/test/golang_filter_test.cc b/contrib/golang/filters/http/test/golang_filter_test.cc index a5e785edd40c..916203470031 100644 --- a/contrib/golang/filters/http/test/golang_filter_test.cc +++ b/contrib/golang/filters/http/test/golang_filter_test.cc @@ -176,8 +176,9 @@ TEST_F(GolangHttpFilterTest, ScriptHeadersOnlyRequestHeadersOnly) { TEST_F(GolangHttpFilterTest, SetHeaderAtWrongStage) { InSequence s; setup(PASSTHROUGH, genSoPath(PASSTHROUGH), PASSTHROUGH); + auto req = new HttpRequestInternal(*filter_); - EXPECT_EQ(CAPINotInGo, filter_->setHeader("foo", "bar", HeaderSet)); + EXPECT_EQ(CAPINotInGo, filter_->setHeader(req->decodingState(), "foo", "bar", HeaderSet)); } // invalid config for routeconfig filter diff --git a/contrib/golang/filters/http/test/golang_integration_test.cc b/contrib/golang/filters/http/test/golang_integration_test.cc index 3362621ebc65..bbc543632e91 100644 --- a/contrib/golang/filters/http/test/golang_integration_test.cc +++ b/contrib/golang/filters/http/test/golang_integration_test.cc @@ -340,7 +340,9 @@ name: golang entries = upstream_request_->trailers()->get(Http::LowerCaseString("existed-trailer")); EXPECT_EQ(2, entries.size()); EXPECT_EQ("foo", entries[0]->value().getStringView()); - EXPECT_EQ("bar", entries[1]->value().getStringView()); + if (entries.size() == 2) { + EXPECT_EQ("bar", entries[1]->value().getStringView()); + } // check trailer value which set in golang: x-test-trailer-0 entries = upstream_request_->trailers()->get(Http::LowerCaseString("x-test-trailer-0")); @@ -662,16 +664,7 @@ name: golang cleanup(); } - void cleanup() { - codec_client_->close(); - - if (fake_upstream_connection_ != nullptr) { - AssertionResult result = fake_upstream_connection_->close(); - RELEASE_ASSERT(result, result.message()); - result = fake_upstream_connection_->waitForDisconnect(); - RELEASE_ASSERT(result, result.message()); - } - } + void cleanup() { cleanupUpstreamAndDownstream(); } void testDynamicMetadata(std::string path) { initializeBasicFilter(BASIC, "*", true); diff --git a/contrib/golang/filters/http/test/test_data/access_log/filter.go b/contrib/golang/filters/http/test/test_data/access_log/filter.go index 750e19215b87..8b7e8233f630 100644 --- a/contrib/golang/filters/http/test/test_data/access_log/filter.go +++ b/contrib/golang/filters/http/test/test_data/access_log/filter.go @@ -36,11 +36,11 @@ func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api. query, _ := f.callbacks.GetProperty("request.query") if query == "periodic=1" { go func() { - defer f.callbacks.RecoverPanic() + defer f.callbacks.DecoderFilterCallbacks().RecoverPanic() // trigger AccessLogDownstreamPeriodic time.Sleep(110 * time.Millisecond) - f.callbacks.Continue(api.Continue) + f.callbacks.DecoderFilterCallbacks().Continue(api.Continue) }() return api.Running } diff --git a/contrib/golang/filters/http/test/test_data/action/filter.go b/contrib/golang/filters/http/test/test_data/action/filter.go index 0ef080859cb8..5829945d16f5 100644 --- a/contrib/golang/filters/http/test/test_data/action/filter.go +++ b/contrib/golang/filters/http/test/test_data/action/filter.go @@ -41,8 +41,8 @@ func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api. if decodeHeadersRet != "" { if async != "" { go func() { - defer f.callbacks.RecoverPanic() - f.callbacks.Continue(getStatus(decodeHeadersRet)) + defer f.callbacks.DecoderFilterCallbacks().RecoverPanic() + f.callbacks.DecoderFilterCallbacks().Continue(getStatus(decodeHeadersRet)) }() return api.Running } @@ -59,8 +59,8 @@ func (f *filter) EncodeHeaders(header api.ResponseHeaderMap, endStream bool) api if encodeHeadersRet != "" { if async != "" { go func() { - defer f.callbacks.RecoverPanic() - f.callbacks.Continue(getStatus(encodeHeadersRet)) + defer f.callbacks.EncoderFilterCallbacks().RecoverPanic() + f.callbacks.EncoderFilterCallbacks().Continue(getStatus(encodeHeadersRet)) }() return api.Running } diff --git a/contrib/golang/filters/http/test/test_data/basic/filter.go b/contrib/golang/filters/http/test/test_data/basic/filter.go index 7a0d668d7658..680980979cef 100644 --- a/contrib/golang/filters/http/test/test_data/basic/filter.go +++ b/contrib/golang/filters/http/test/test_data/basic/filter.go @@ -84,21 +84,21 @@ func (f *filter) initRequest(header api.RequestHeaderMap) { f.clearRoute = f.query_params.Get("clearRoute") != "" } -func (f *filter) fail(msg string, a ...any) api.StatusType { +func (f *filter) fail(callbacks api.FilterProcessCallbacks, msg string, a ...any) api.StatusType { body := fmt.Sprintf(msg, a...) f.callbacks.Log(api.Error, fmt.Sprintf("test failed: %s", body)) - f.callbacks.SendLocalReply(500, body, nil, 0, "") + callbacks.SendLocalReply(500, body, nil, 0, "") return api.LocalReply } -func (f *filter) sendLocalReply(phase string) api.StatusType { +func (f *filter) sendLocalReply(callbacks api.FilterProcessCallbacks, phase string) api.StatusType { headers := map[string][]string{ "Content-type": {"text/html"}, "test-phase": {phase}, "x-two-values": {"foo", "bar"}, } body := fmt.Sprintf("forbidden from go in %s\r\n", phase) - f.callbacks.SendLocalReply(403, body, headers, 0, "") + callbacks.SendLocalReply(403, body, headers, 0, "") return api.LocalReply } @@ -127,7 +127,7 @@ func (f *filter) decodeHeaders(header api.RequestHeaderMap, endStream bool) api. api.LogCriticalf("log test %v", endStream) if f.callbacks.LogLevel() != api.GetLogLevel() { - return f.fail("log level mismatch") + return f.fail(f.callbacks.DecoderFilterCallbacks(), "log level mismatch") } if f.sleep { @@ -139,21 +139,21 @@ func (f *filter) decodeHeaders(header api.RequestHeaderMap, endStream bool) api. md := f.callbacks.StreamInfo().DynamicMetadata() empty_metadata := md.Get("filter.go") if len(empty_metadata) != 0 { - return f.fail("Metadata should be empty") + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Metadata should be empty") } md.Set("filter.go", "foo", "bar") metadata := md.Get("filter.go") if len(metadata) == 0 { - return f.fail("Metadata should not be empty") + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Metadata should not be empty") } k, ok := metadata["foo"] if !ok { - return f.fail("Metadata foo should be found") + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Metadata foo should be found") } if fmt.Sprint(k) != "bar" { - return f.fail("Metadata foo has unexpected value %v", k) + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Metadata foo has unexpected value %v", k) } } @@ -164,12 +164,12 @@ func (f *filter) decodeHeaders(header api.RequestHeaderMap, endStream bool) api. header.Add("go-state-test-header-key", val) if strings.Contains(f.localreplay, "decode-header") { - return f.sendLocalReply("decode-header") + return f.sendLocalReply(f.callbacks.DecoderFilterCallbacks(), "decode-header") } header.Range(func(key, value string) bool { if key == ":path" && value != f.path { - f.fail("path not match in Range") + f.fail(f.callbacks.DecoderFilterCallbacks(), "path not match in Range") return false } return true @@ -177,7 +177,7 @@ func (f *filter) decodeHeaders(header api.RequestHeaderMap, endStream bool) api. header.RangeWithCopy(func(key, value string) bool { if key == ":path" && value != f.path { - f.fail("path not match in RangeWithCopy") + f.fail(f.callbacks.DecoderFilterCallbacks(), "path not match in RangeWithCopy") return false } return true @@ -199,47 +199,47 @@ func (f *filter) decodeHeaders(header api.RequestHeaderMap, endStream bool) api. header_map := header.GetAllHeaders() if !reflect.DeepEqual(f.all_headers, header_map) { - return f.fail("GetAllHeaders returned incorrect data, expected:\n%v\n got:\n%v", f.all_headers, header_map) + return f.fail(f.callbacks.DecoderFilterCallbacks(), "GetAllHeaders returned incorrect data, expected:\n%v\n got:\n%v", f.all_headers, header_map) } header.Set(test_header_key, "new-value") if !reflect.DeepEqual(header_map[test_header_key], []string{old_value}) { - return f.fail("GetAllHeaders output changed - expected '%v', got '%v'", []string{old_value}, header_map[test_header_key]) + return f.fail(f.callbacks.DecoderFilterCallbacks(), "GetAllHeaders output changed - expected '%v', got '%v'", []string{old_value}, header_map[test_header_key]) } origin, found := header.Get("x-test-header-0") hdrs := header.Values("x-test-header-0") if found { if origin != hdrs[0] { - return f.fail("Values return incorrect data %v", hdrs) + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Values return incorrect data %v", hdrs) } } else if hdrs != nil { - return f.fail("Values return unexpected data %v", hdrs) + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Values return unexpected data %v", hdrs) } if found { upperCase, _ := header.Get("X-Test-Header-0") if upperCase != origin { - return f.fail("Get should be case-insensitive") + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Get should be case-insensitive") } upperCaseHdrs := header.Values("X-Test-Header-0") if hdrs[0] != upperCaseHdrs[0] { - return f.fail("Values should be case-insensitive") + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Values should be case-insensitive") } } header.Add("UpperCase", "header") if hdr, _ := header.Get("uppercase"); hdr != "header" { - return f.fail("Add should be case-insensitive") + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Add should be case-insensitive") } header.Set("UpperCase", "header") if hdr, _ := header.Get("uppercase"); hdr != "header" { - return f.fail("Set should be case-insensitive") + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Set should be case-insensitive") } header.Del("UpperCase") if hdr, _ := header.Get("uppercase"); hdr != "" { - return f.fail("Del should be case-insensitive") + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Del should be case-insensitive") } header.Add("existed-header", "bar") @@ -274,13 +274,13 @@ func (f *filter) decodeData(buffer api.BufferInstance, endStream bool) api.Statu time.Sleep(time.Millisecond * 100) // sleep 100 ms } if strings.Contains(f.localreplay, "decode-data") { - return f.sendLocalReply("decode-data") + return f.sendLocalReply(f.callbacks.DecoderFilterCallbacks(), "decode-data") } f.req_body_length += uint64(buffer.Len()) if buffer.Len() != 0 { data := buffer.String() if string(buffer.Bytes()) != data { - return f.sendLocalReply(fmt.Sprintf("data in bytes: %s vs data in string: %s", + return f.sendLocalReply(f.callbacks.DecoderFilterCallbacks(), fmt.Sprintf("data in bytes: %s vs data in string: %s", string(buffer.Bytes()), data)) } @@ -307,37 +307,38 @@ func (f *filter) decodeTrailers(trailers api.RequestTrailerMap) api.StatusType { time.Sleep(time.Millisecond * 100) // sleep 100 ms } if strings.Contains(f.localreplay, "decode-trailer") { - return f.sendLocalReply("decode-trailer") + return f.sendLocalReply(f.callbacks.DecoderFilterCallbacks(), "decode-trailer") } trailers.Add("existed-trailer", "bar") trailers.Set("x-test-trailer-0", "bar") trailers.Del("x-test-trailer-1") - if trailers.GetRaw("existed-trailer") == "foo" { + existed, _ := trailers.Get("existed-trailer") + if existed == "foo" { trailers.Add("x-test-trailer-2", "bar") } upperCase, _ := trailers.Get("X-Test-Trailer-0") if upperCase != "bar" { - return f.fail("Get should be case-insensitive") + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Get should be case-insensitive") } upperCaseHdrs := trailers.Values("X-Test-Trailer-0") if upperCaseHdrs[0] != "bar" { - return f.fail("Values should be case-insensitive") + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Values should be case-insensitive") } trailers.Add("UpperCase", "trailers") if hdr, _ := trailers.Get("uppercase"); hdr != "trailers" { - return f.fail("Add should be case-insensitive") + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Add should be case-insensitive") } trailers.Set("UpperCase", "trailers") if hdr, _ := trailers.Get("uppercase"); hdr != "trailers" { - return f.fail("Set should be case-insensitive") + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Set should be case-insensitive") } trailers.Del("UpperCase") if hdr, _ := trailers.Get("uppercase"); hdr != "" { - return f.fail("Del should be case-insensitive") + return f.fail(f.callbacks.DecoderFilterCallbacks(), "Del should be case-insensitive") } if f.panic == "decode-trailer" { @@ -351,7 +352,7 @@ func (f *filter) encodeHeaders(header api.ResponseHeaderMap, endStream bool) api time.Sleep(time.Millisecond * 100) // sleep 100 ms } if strings.Contains(f.localreplay, "encode-header") { - return f.sendLocalReply("encode-header") + return f.sendLocalReply(f.callbacks.EncoderFilterCallbacks(), "encode-header") } if protocol, ok := f.callbacks.StreamInfo().Protocol(); ok { @@ -374,10 +375,10 @@ func (f *filter) encodeHeaders(header api.ResponseHeaderMap, endStream bool) api hdrs := header.Values("x-test-header-0") if found { if origin != hdrs[0] { - return f.fail("Values return incorrect data %v", hdrs) + return f.fail(f.callbacks.EncoderFilterCallbacks(), "Values return incorrect data %v", hdrs) } } else if hdrs != nil { - return f.fail("Values return unexpected data %v", hdrs) + return f.fail(f.callbacks.EncoderFilterCallbacks(), "Values return unexpected data %v", hdrs) } if status, ok := header.Status(); ok { @@ -417,7 +418,7 @@ func (f *filter) encodeData(buffer api.BufferInstance, endStream bool) api.Statu time.Sleep(time.Millisecond * 100) // sleep 100 ms } if strings.Contains(f.localreplay, "encode-data") { - return f.sendLocalReply("encode-data") + return f.sendLocalReply(f.callbacks.EncoderFilterCallbacks(), "encode-data") } data := buffer.String() buffer.SetString(strings.ToUpper(data)) @@ -433,7 +434,7 @@ func (f *filter) encodeTrailers(trailers api.ResponseTrailerMap) api.StatusType time.Sleep(time.Millisecond * 100) // sleep 100 ms } if strings.Contains(f.localreplay, "encode-trailer") { - return f.sendLocalReply("encode-trailer") + return f.sendLocalReply(f.callbacks.EncoderFilterCallbacks(), "encode-trailer") } if f.panic == "encode-trailer" { @@ -446,11 +447,11 @@ func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api. f.initRequest(header) if f.async { go func() { - defer f.callbacks.RecoverPanic() + defer f.callbacks.DecoderFilterCallbacks().RecoverPanic() status := f.decodeHeaders(header, endStream) if status != api.LocalReply { - f.callbacks.Continue(status) + f.callbacks.DecoderFilterCallbacks().Continue(status) } }() return api.Running @@ -463,11 +464,11 @@ func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api. func (f *filter) DecodeData(buffer api.BufferInstance, endStream bool) api.StatusType { if f.async { go func() { - defer f.callbacks.RecoverPanic() + defer f.callbacks.DecoderFilterCallbacks().RecoverPanic() status := f.decodeData(buffer, endStream) if status != api.LocalReply { - f.callbacks.Continue(status) + f.callbacks.DecoderFilterCallbacks().Continue(status) } }() return api.Running @@ -480,11 +481,11 @@ func (f *filter) DecodeData(buffer api.BufferInstance, endStream bool) api.Statu func (f *filter) DecodeTrailers(trailers api.RequestTrailerMap) api.StatusType { if f.async { go func() { - defer f.callbacks.RecoverPanic() + defer f.callbacks.DecoderFilterCallbacks().RecoverPanic() status := f.decodeTrailers(trailers) if status != api.LocalReply { - f.callbacks.Continue(status) + f.callbacks.DecoderFilterCallbacks().Continue(status) } }() return api.Running @@ -497,11 +498,11 @@ func (f *filter) DecodeTrailers(trailers api.RequestTrailerMap) api.StatusType { func (f *filter) EncodeHeaders(header api.ResponseHeaderMap, endStream bool) api.StatusType { if f.async { go func() { - defer f.callbacks.RecoverPanic() + defer f.callbacks.EncoderFilterCallbacks().RecoverPanic() status := f.encodeHeaders(header, endStream) if status != api.LocalReply { - f.callbacks.Continue(status) + f.callbacks.EncoderFilterCallbacks().Continue(status) } }() return api.Running @@ -514,11 +515,11 @@ func (f *filter) EncodeHeaders(header api.ResponseHeaderMap, endStream bool) api func (f *filter) EncodeData(buffer api.BufferInstance, endStream bool) api.StatusType { if f.async { go func() { - defer f.callbacks.RecoverPanic() + defer f.callbacks.EncoderFilterCallbacks().RecoverPanic() status := f.encodeData(buffer, endStream) if status != api.LocalReply { - f.callbacks.Continue(status) + f.callbacks.EncoderFilterCallbacks().Continue(status) } }() return api.Running @@ -531,11 +532,11 @@ func (f *filter) EncodeData(buffer api.BufferInstance, endStream bool) api.Statu func (f *filter) EncodeTrailers(trailers api.ResponseTrailerMap) api.StatusType { if f.async { go func() { - defer f.callbacks.RecoverPanic() + defer f.callbacks.EncoderFilterCallbacks().RecoverPanic() status := f.encodeTrailers(trailers) if status != api.LocalReply { - f.callbacks.Continue(status) + f.callbacks.EncoderFilterCallbacks().Continue(status) } }() return api.Running diff --git a/contrib/golang/filters/http/test/test_data/echo/filter.go b/contrib/golang/filters/http/test/test_data/echo/filter.go index 0b53066a3cff..0ae00890fba3 100644 --- a/contrib/golang/filters/http/test/test_data/echo/filter.go +++ b/contrib/golang/filters/http/test/test_data/echo/filter.go @@ -19,7 +19,7 @@ func (f *filter) sendLocalReply() api.StatusType { echoBody := f.config.echoBody { body := fmt.Sprintf("%s, path: %s\r\n", echoBody, f.path) - f.callbacks.SendLocalReply(403, body, nil, 0, "") + f.callbacks.DecoderFilterCallbacks().SendLocalReply(403, body, nil, 0, "") } // Force GC to free the body string. // For the case that C++ shouldn't touch the memory of the body string, diff --git a/contrib/golang/filters/http/test/test_data/metric/filter.go b/contrib/golang/filters/http/test/test_data/metric/filter.go index 063234407f37..ca390e053b0c 100644 --- a/contrib/golang/filters/http/test/test_data/metric/filter.go +++ b/contrib/golang/filters/http/test/test_data/metric/filter.go @@ -61,11 +61,11 @@ func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api. f.initRequest(header) if f.async { go func() { - defer f.callbacks.RecoverPanic() + defer f.callbacks.DecoderFilterCallbacks().RecoverPanic() status := f.decodeHeaders(header, endStream) if status != api.LocalReply { - f.callbacks.Continue(status) + f.callbacks.DecoderFilterCallbacks().Continue(status) } }() return api.Running diff --git a/contrib/golang/filters/http/test/test_data/websocket/BUILD b/contrib/golang/filters/http/test/test_data/websocket/BUILD new file mode 100644 index 000000000000..bf9dbe54e186 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/websocket/BUILD @@ -0,0 +1,22 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary") + +licenses(["notice"]) # Apache 2 + +go_binary( + name = "filter.so", + srcs = [ + "config.go", + "filter.go", + ], + out = "filter.so", + cgo = True, + importpath = "github.com/envoyproxy/envoy/contrib/golang/filters/http/test/test_data/websocket", + linkmode = "c-shared", + visibility = ["//visibility:public"], + deps = [ + "//contrib/golang/common/go/api", + "//contrib/golang/filters/http/source/go/pkg/http", + "@org_golang_google_protobuf//types/known/anypb", + "@org_golang_google_protobuf//types/known/structpb", + ], +) diff --git a/contrib/golang/filters/http/test/test_data/websocket/config.go b/contrib/golang/filters/http/test/test_data/websocket/config.go new file mode 100644 index 000000000000..0886c96295f8 --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/websocket/config.go @@ -0,0 +1,21 @@ +package main + +import ( + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + "github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http" +) + +const Name = "websocket" + +func init() { + http.RegisterHttpFilterFactoryAndConfigParser(Name, filterFactory, http.NullParser) +} + +func filterFactory(c interface{}, callbacks api.FilterCallbackHandler) api.StreamFilter { + return &filter{ + callbacks: callbacks, + } +} + +func main() { +} diff --git a/contrib/golang/filters/http/test/test_data/websocket/filter.go b/contrib/golang/filters/http/test/test_data/websocket/filter.go new file mode 100644 index 000000000000..5e01829ed01d --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/websocket/filter.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" +) + +type filter struct { + api.PassThroughStreamFilter + + callbacks api.FilterCallbackHandler +} + +func (f *filter) DecodeHeaders(header api.RequestHeaderMap, endStream bool) api.StatusType { + header.Set("test-websocket-req-key", "foo") + return api.Continue +} + +func (f *filter) DecodeData(buffer api.BufferInstance, endStream bool) api.StatusType { + f.callbacks.Log(api.Error, fmt.Sprintf("body: %s, end_stream: %v", buffer.String(), endStream)) + if !endStream && buffer.Len() != 0 { + buffer.PrependString("Hello_") + } + return api.Continue +} + +func (f *filter) EncodeHeaders(header api.ResponseHeaderMap, endStream bool) api.StatusType { + header.Set("test-websocket-rsp-key", "bar") + return api.Continue +} + +func (f *filter) EncodeData(buffer api.BufferInstance, endStream bool) api.StatusType { + f.callbacks.Log(api.Error, fmt.Sprintf("body: %s, end_stream: %v", buffer.String(), endStream)) + if !endStream && buffer.Len() != 0 { + buffer.PrependString("Bye_") + } + return api.Continue +} diff --git a/contrib/golang/filters/http/test/test_data/websocket/go.mod b/contrib/golang/filters/http/test/test_data/websocket/go.mod new file mode 100644 index 000000000000..a348a66b3b6c --- /dev/null +++ b/contrib/golang/filters/http/test/test_data/websocket/go.mod @@ -0,0 +1,9 @@ +module example.com/websocket + +go 1.20 + +require github.com/envoyproxy/envoy v1.24.0 + +require google.golang.org/protobuf v1.33.0 // indirect + +replace github.com/envoyproxy/envoy => ../../../../../../../ diff --git a/contrib/golang/filters/http/test/websocket_integration_test.cc b/contrib/golang/filters/http/test/websocket_integration_test.cc new file mode 100644 index 000000000000..455bfeae67f2 --- /dev/null +++ b/contrib/golang/filters/http/test/websocket_integration_test.cc @@ -0,0 +1,113 @@ +#include + +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" + +#include "source/common/http/header_map_impl.h" +#include "source/common/protobuf/utility.h" + +#include "test/integration/utility.h" +#include "test/integration/websocket_integration_test.h" +#include "test/test_common/network_utility.h" +#include "test/test_common/printers.h" +#include "test/test_common/utility.h" + +#include "absl/strings/str_cat.h" +#include "gtest/gtest.h" + +namespace Envoy { + +INSTANTIATE_TEST_SUITE_P(Protocols, WebsocketIntegrationTest, + testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams()), + HttpProtocolIntegrationTest::protocolTestParamsToString); + +ConfigHelper::HttpModifierFunction setRouteUsingWebsocket() { + return [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { hcm.add_upgrade_configs()->set_upgrade_type("websocket"); }; +} + +void WebsocketIntegrationTest::initialize() { HttpProtocolIntegrationTest::initialize(); } + +std::string genSoPath(std::string name) { + return TestEnvironment::substitute( + "{{ test_rundir }}/contrib/golang/filters/http/test/test_data/" + name + "/filter.so"); +} + +std::string filterConfig(const std::string& name) { + const auto yaml_fmt = R"EOF( +name: golang +typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config + library_id: %s + library_path: %s + plugin_name: %s + plugin_config: + "@type": type.googleapis.com/xds.type.v3.TypedStruct + value: + echo_body: "echo from go" + match_path: "/echo" +)EOF"; + + return absl::StrFormat(yaml_fmt, name, genSoPath(name), name); +} + +TEST_P(WebsocketIntegrationTest, WebsocketGolangFilterChain) { + if (downstreamProtocol() != Http::CodecType::HTTP1 || + upstreamProtocol() != Http::CodecType::HTTP1) { + return; + } + + config_helper_.addConfigModifier(setRouteUsingWebsocket()); + config_helper_.prependFilter(filterConfig("websocket")); + config_helper_.skipPortUsageValidation(); + + initialize(); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("http")); + + // Send upgrade request without CL and TE headers + ASSERT_TRUE(tcp_client->write( + "GET / HTTP/1.1\r\nHost: host\r\nconnection: upgrade\r\nupgrade: websocket\r\n\r\n", false, + false)); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + ASSERT(fake_upstream_connection != nullptr); + std::string received_data; + ASSERT_TRUE(fake_upstream_connection->waitForData( + FakeRawConnection::waitForInexactMatch("\r\n\r\n"), &received_data)); + // Make sure Envoy did not add TE or CL headers + ASSERT_FALSE(absl::StrContains(received_data, "content-length")); + ASSERT_FALSE(absl::StrContains(received_data, "transfer-encoding")); + // Make sure Golang plugin take affects + ASSERT_TRUE(absl::StrContains(received_data, "test-websocket-req-key: foo")); + ASSERT_TRUE(fake_upstream_connection->write( + "HTTP/1.1 101 Switching Protocols\r\nconnection: upgrade\r\nupgrade: websocket\r\n\r\n", + false)); + + tcp_client->waitForData("\r\n\r\n", false); + // Make sure Envoy did not add TE or CL on the response path + ASSERT_FALSE(absl::StrContains(tcp_client->data(), "content-length")); + ASSERT_FALSE(absl::StrContains(tcp_client->data(), "transfer-encoding")); + // Make sure Golang plugin take affects + ASSERT_TRUE(absl::StrContains(tcp_client->data(), "test-websocket-rsp-key: bar")); + + fake_upstream_connection->clearData(); + // Send data and make sure Envoy did not add chunk framing + ASSERT_TRUE(tcp_client->write("foo bar\r\n", false, false)); + ASSERT_TRUE(fake_upstream_connection->waitForData(FakeRawConnection::waitForInexactMatch("\r\n"), + &received_data)); + // Make sure Golang plugin take affects + ASSERT_TRUE(absl::StrContains(received_data, "Hello_foo bar")); + + tcp_client->clearData(); + // Send response data and make sure Envoy did not add chunk framing on the response path + ASSERT_TRUE(fake_upstream_connection->write("bar foo\r\n", false)); + tcp_client->waitForData("bar foo\r\n", false); + // Make sure Golang plugin take affects + ASSERT_TRUE(absl::StrContains(tcp_client->data(), "Bye_bar foo")); + tcp_client->close(); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); +} + +} // namespace Envoy diff --git a/examples/golang-http/simple/filter.go b/examples/golang-http/simple/filter.go index 5eeddae31306..52afefaac091 100644 --- a/examples/golang-http/simple/filter.go +++ b/examples/golang-http/simple/filter.go @@ -21,7 +21,7 @@ type filter struct { func (f *filter) sendLocalReplyInternal() api.StatusType { body := fmt.Sprintf("%s, path: %s\r\n", f.config.echoBody, f.path) - f.callbacks.SendLocalReply(200, body, nil, 0, "") + f.callbacks.DecoderFilterCallbacks().SendLocalReply(200, body, nil, 0, "") // Remember to return LocalReply when the request is replied locally return api.LocalReply }