| // |
| // Copyright 2015 gRPC authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| // |
| |
| #include <grpc/support/port_platform.h> |
| |
| #include "src/core/ext/filters/http/client/http_client_filter.h" |
| |
| #include <algorithm> |
| #include <functional> |
| #include <initializer_list> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/status/status.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/str_join.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/optional.h" |
| |
| #include <grpc/grpc.h> |
| #include <grpc/status.h> |
| |
| #include "src/core/lib/channel/channel_args.h" |
| #include "src/core/lib/channel/channel_stack.h" |
| #include "src/core/lib/promise/context.h" |
| #include "src/core/lib/promise/latch.h" |
| #include "src/core/lib/promise/map.h" |
| #include "src/core/lib/promise/pipe.h" |
| #include "src/core/lib/promise/poll.h" |
| #include "src/core/lib/promise/race.h" |
| #include "src/core/lib/resource_quota/arena.h" |
| #include "src/core/lib/slice/percent_encoding.h" |
| #include "src/core/lib/transport/status_conversion.h" |
| #include "src/core/lib/transport/transport_fwd.h" |
| #include "src/core/lib/transport/transport_impl.h" |
| |
| namespace grpc_core { |
| |
| const grpc_channel_filter HttpClientFilter::kFilter = |
| MakePromiseBasedFilter<HttpClientFilter, FilterEndpoint::kClient, |
| kFilterExaminesServerInitialMetadata>("http-client"); |
| |
| namespace { |
| absl::Status CheckServerMetadata(ServerMetadata* b) { |
| if (auto* status = b->get_pointer(HttpStatusMetadata())) { |
| // If both gRPC status and HTTP status are provided in the response, we |
| // should prefer the gRPC status code, as mentioned in |
| // https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md. |
| // |
| const grpc_status_code* grpc_status = b->get_pointer(GrpcStatusMetadata()); |
| if (grpc_status != nullptr || *status == 200) { |
| b->Remove(HttpStatusMetadata()); |
| } else { |
| return absl::Status( |
| static_cast<absl::StatusCode>( |
| grpc_http2_status_to_grpc_status(*status)), |
| absl::StrCat("Received http2 header with status: ", *status)); |
| } |
| } |
| |
| if (Slice* grpc_message = b->get_pointer(GrpcMessageMetadata())) { |
| *grpc_message = PermissivePercentDecodeSlice(std::move(*grpc_message)); |
| } |
| |
| b->Remove(ContentTypeMetadata()); |
| return absl::OkStatus(); |
| } |
| |
| HttpSchemeMetadata::ValueType SchemeFromArgs(const ChannelArgs& args) { |
| HttpSchemeMetadata::ValueType scheme = HttpSchemeMetadata::Parse( |
| args.GetString(GRPC_ARG_HTTP2_SCHEME).value_or(""), |
| [](absl::string_view, const Slice&) {}); |
| if (scheme == HttpSchemeMetadata::kInvalid) return HttpSchemeMetadata::kHttp; |
| return scheme; |
| } |
| |
| Slice UserAgentFromArgs(const ChannelArgs& args, const char* transport_name) { |
| std::vector<std::string> fields; |
| auto add = [&fields](absl::string_view x) { |
| if (!x.empty()) fields.push_back(std::string(x)); |
| }; |
| |
| add(args.GetString(GRPC_ARG_PRIMARY_USER_AGENT_STRING).value_or("")); |
| add(absl::StrFormat("grpc-c/%s (%s; %s)", grpc_version_string(), |
| GPR_PLATFORM_STRING, transport_name)); |
| add(args.GetString(GRPC_ARG_SECONDARY_USER_AGENT_STRING).value_or("")); |
| |
| return Slice::FromCopiedString(absl::StrJoin(fields, " ")); |
| } |
| } // namespace |
| |
| ArenaPromise<ServerMetadataHandle> HttpClientFilter::MakeCallPromise( |
| CallArgs call_args, NextPromiseFactory next_promise_factory) { |
| auto& md = call_args.client_initial_metadata; |
| if (test_only_use_put_requests_) { |
| md->Set(HttpMethodMetadata(), HttpMethodMetadata::kPut); |
| } else { |
| md->Set(HttpMethodMetadata(), HttpMethodMetadata::kPost); |
| } |
| md->Set(HttpSchemeMetadata(), scheme_); |
| md->Set(TeMetadata(), TeMetadata::kTrailers); |
| md->Set(ContentTypeMetadata(), ContentTypeMetadata::kApplicationGrpc); |
| md->Set(UserAgentMetadata(), user_agent_.Ref()); |
| |
| auto* initial_metadata_err = |
| GetContext<Arena>()->New<Latch<ServerMetadataHandle>>(); |
| |
| call_args.server_initial_metadata->InterceptAndMap( |
| [initial_metadata_err]( |
| ServerMetadataHandle md) -> absl::optional<ServerMetadataHandle> { |
| auto r = CheckServerMetadata(md.get()); |
| if (!r.ok()) { |
| initial_metadata_err->Set(ServerMetadataFromStatus(r)); |
| return absl::nullopt; |
| } |
| return std::move(md); |
| }); |
| |
| return Race(Map(next_promise_factory(std::move(call_args)), |
| [](ServerMetadataHandle md) -> ServerMetadataHandle { |
| auto r = CheckServerMetadata(md.get()); |
| if (!r.ok()) return ServerMetadataFromStatus(r); |
| return md; |
| }), |
| initial_metadata_err->Wait()); |
| } |
| |
| HttpClientFilter::HttpClientFilter(HttpSchemeMetadata::ValueType scheme, |
| Slice user_agent, |
| bool test_only_use_put_requests) |
| : scheme_(scheme), |
| user_agent_(std::move(user_agent)), |
| test_only_use_put_requests_(test_only_use_put_requests) {} |
| |
| absl::StatusOr<HttpClientFilter> HttpClientFilter::Create( |
| const ChannelArgs& args, ChannelFilter::Args) { |
| auto* transport = args.GetObject<grpc_transport>(); |
| if (transport == nullptr) { |
| return absl::InvalidArgumentError("HttpClientFilter needs a transport"); |
| } |
| return HttpClientFilter( |
| SchemeFromArgs(args), UserAgentFromArgs(args, transport->vtable->name), |
| args.GetInt(GRPC_ARG_TEST_ONLY_USE_PUT_REQUESTS).value_or(false)); |
| } |
| |
| } // namespace grpc_core |