// // // 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 #include #include #include #include #include #include #include #include #include "absl/status/statusor.h" #include "absl/strings/str_format.h" #include "absl/time/clock.h" #include "absl/time/time.h" #include #include #include #include #include #include #include "src/core/lib/channel/channel_args.h" #include "src/core/lib/gpr/subprocess.h" #include "src/core/lib/gprpp/orphanable.h" #include "src/core/lib/gprpp/status_helper.h" #include "src/core/lib/gprpp/sync.h" #include "src/core/lib/gprpp/time.h" #include "src/core/lib/gprpp/time_util.h" #include "src/core/lib/http/httpcli.h" #include "src/core/lib/http/httpcli_ssl_credentials.h" #include "src/core/lib/http/parser.h" #include "src/core/lib/iomgr/closure.h" #include "src/core/lib/iomgr/error.h" #include "src/core/lib/iomgr/exec_ctx.h" #include "src/core/lib/iomgr/iomgr_fwd.h" #include "src/core/lib/iomgr/polling_entity.h" #include "src/core/lib/iomgr/pollset.h" #include "src/core/lib/security/credentials/credentials.h" // IWYU pragma: keep #include "src/core/lib/uri/uri_parser.h" #include "test/core/http/httpcli_test_util.h" #include "test/core/util/fake_udp_and_tcp_server.h" #include "test/core/util/test_config.h" namespace { grpc_core::Timestamp NSecondsTime(int seconds) { return grpc_core::Timestamp::FromTimespecRoundUp( grpc_timeout_seconds_to_deadline(seconds)); } absl::Time AbslDeadlineSeconds(int s) { return grpc_core::ToAbslTime(grpc_timeout_seconds_to_deadline(s)); } int g_argc; char** g_argv; int g_server_port; gpr_subprocess* g_server; class HttpsCliTest : public ::testing::Test { public: HttpsCliTest() { grpc_init(); grpc_core::ExecCtx exec_ctx; grpc_pollset* pollset = static_cast(gpr_zalloc(grpc_pollset_size())); grpc_pollset_init(pollset, &mu_); pops_ = grpc_polling_entity_create_from_pollset(pollset); } ~HttpsCliTest() override { { grpc_core::ExecCtx exec_ctx; grpc_pollset_shutdown( grpc_polling_entity_pollset(&pops_), GRPC_CLOSURE_CREATE(DestroyPops, &pops_, grpc_schedule_on_exec_ctx)); } grpc_shutdown(); } void RunAndKick(const std::function& f) { grpc_core::MutexLockForGprMu lock(mu_); f(); GPR_ASSERT(GRPC_LOG_IF_ERROR( "pollset_kick", grpc_pollset_kick(grpc_polling_entity_pollset(&pops_), nullptr))); } void PollUntil(const std::function& predicate, absl::Time deadline) { gpr_mu_lock(mu_); while (!predicate()) { GPR_ASSERT(absl::Now() < deadline); grpc_pollset_worker* worker = nullptr; GPR_ASSERT(GRPC_LOG_IF_ERROR( "pollset_work", grpc_pollset_work(grpc_polling_entity_pollset(&pops_), &worker, NSecondsTime(1)))); gpr_mu_unlock(mu_); gpr_mu_lock(mu_); } gpr_mu_unlock(mu_); } grpc_polling_entity* pops() { return &pops_; } protected: static void SetUpTestSuite() { auto test_server = grpc_core::testing::StartHttpRequestTestServer( g_argc, g_argv, true /* use_ssl */); g_server = test_server.server; g_server_port = test_server.port; } static void TearDownTestSuite() { gpr_subprocess_destroy(g_server); } private: static void DestroyPops(void* p, grpc_error_handle /*error*/) { grpc_polling_entity* pops = static_cast(p); grpc_pollset_destroy(grpc_polling_entity_pollset(pops)); gpr_free(grpc_polling_entity_pollset(pops)); } gpr_mu* mu_; grpc_polling_entity pops_; }; struct RequestState { explicit RequestState(HttpsCliTest* test) : test(test) {} ~RequestState() { grpc_core::ExecCtx exec_ctx; grpc_http_response_destroy(&response); } HttpsCliTest* test; bool done = false; grpc_http_response response = {}; }; void OnFinish(void* arg, grpc_error_handle error) { RequestState* request_state = static_cast(arg); const char* expect = "Hello world!" "

This is a test

"; GPR_ASSERT(error.ok()); grpc_http_response response = request_state->response; gpr_log(GPR_INFO, "response status=%d error=%s", response.status, grpc_core::StatusToString(error).c_str()); GPR_ASSERT(response.status == 200); GPR_ASSERT(response.body_length == strlen(expect)); GPR_ASSERT(0 == memcmp(expect, response.body, response.body_length)); request_state->test->RunAndKick( [request_state]() { request_state->done = true; }); } void OnFinishExpectFailure(void* arg, grpc_error_handle error) { RequestState* request_state = static_cast(arg); grpc_http_response response = request_state->response; gpr_log(GPR_INFO, "response status=%d error=%s", response.status, grpc_core::StatusToString(error).c_str()); GPR_ASSERT(!error.ok()); request_state->test->RunAndKick( [request_state]() { request_state->done = true; }); } TEST_F(HttpsCliTest, Get) { RequestState request_state(this); grpc_http_request req; grpc_core::ExecCtx exec_ctx; std::string host = absl::StrFormat("localhost:%d", g_server_port); gpr_log(GPR_INFO, "requesting from %s", host.c_str()); memset(&req, 0, sizeof(req)); grpc_arg ssl_override_arg = grpc_channel_arg_string_create( const_cast(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG), const_cast("foo.test.google.fr")); grpc_channel_args args = {1, &ssl_override_arg}; auto uri = grpc_core::URI::Create("https", host, "/get", {} /* query params */, "" /* fragment */); GPR_ASSERT(uri.ok()); grpc_core::OrphanablePtr http_request = grpc_core::HttpRequest::Get( std::move(*uri), &args, pops(), &req, NSecondsTime(15), GRPC_CLOSURE_CREATE(OnFinish, &request_state, grpc_schedule_on_exec_ctx), &request_state.response, grpc_core::CreateHttpRequestSSLCredentials()); http_request->Start(); PollUntil([&request_state]() { return request_state.done; }, AbslDeadlineSeconds(60)); } TEST_F(HttpsCliTest, Post) { RequestState request_state(this); grpc_http_request req; grpc_core::ExecCtx exec_ctx; std::string host = absl::StrFormat("localhost:%d", g_server_port); gpr_log(GPR_INFO, "posting to %s", host.c_str()); memset(&req, 0, sizeof(req)); req.body = const_cast("hello"); req.body_length = 5; grpc_arg ssl_override_arg = grpc_channel_arg_string_create( const_cast(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG), const_cast("foo.test.google.fr")); grpc_channel_args args = {1, &ssl_override_arg}; auto uri = grpc_core::URI::Create("https", host, "/post", {} /* query params */, "" /* fragment */); GPR_ASSERT(uri.ok()); grpc_core::OrphanablePtr http_request = grpc_core::HttpRequest::Post( std::move(*uri), &args /* channel args */, pops(), &req, NSecondsTime(15), GRPC_CLOSURE_CREATE(OnFinish, &request_state, grpc_schedule_on_exec_ctx), &request_state.response, grpc_core::CreateHttpRequestSSLCredentials()); http_request->Start(); PollUntil([&request_state]() { return request_state.done; }, AbslDeadlineSeconds(60)); } // The goal of this test is to make sure that we can cancel HTTP requests // while they're waiting for a response from the server to finish their // SSL handshakes. Note that the main focus of this test is to just exercise // the relevant code paths and make sure there aren't any crashes etc., rather // than to make sure that cancellation happens in a timely manner. TEST_F(HttpsCliTest, CancelGetDuringSSLHandshake) { // Start up a fake TCP server which accepts connections and then hangs, // i.e. it won't send any bytes back to the client. grpc_core::testing::FakeUdpAndTcpServer fake_http_server( grpc_core::testing::FakeUdpAndTcpServer::AcceptMode:: kWaitForClientToSendFirstBytes, grpc_core::testing::FakeUdpAndTcpServer::CloseSocketUponCloseFromPeer); // Use multiple threads to try to trigger races etc. int kNumThreads = 10; std::vector threads; threads.reserve(kNumThreads); for (int i = 0; i < kNumThreads; i++) { grpc_core::testing::FakeUdpAndTcpServer* fake_http_server_ptr = &fake_http_server; threads.push_back(std::thread([this, fake_http_server_ptr]() { RequestState request_state(this); grpc_http_request req; grpc_core::ExecCtx exec_ctx; memset(&req, 0, sizeof(req)); grpc_arg ssl_override_arg = grpc_channel_arg_string_create( const_cast(GRPC_SSL_TARGET_NAME_OVERRIDE_ARG), const_cast("foo.test.google.fr")); grpc_channel_args args = {1, &ssl_override_arg}; auto uri = grpc_core::URI::Create( "https", fake_http_server_ptr->address(), "/get", {} /* query params */, "" /* fragment */); grpc_core::OrphanablePtr http_request = grpc_core::HttpRequest::Get( std::move(*uri), &args, pops(), &req, NSecondsTime(120), GRPC_CLOSURE_CREATE(OnFinishExpectFailure, &request_state, grpc_schedule_on_exec_ctx), &request_state.response, grpc_core::CreateHttpRequestSSLCredentials()); // Start a request. It will establish a TCP connection to the // server and then begin an SSL handshake. The server won't send // anything back though, so it will be stuck in its SSL handshake, // waiting for the firt response from the server. http_request->Start(); exec_ctx.Flush(); std::thread cancel_thread([&http_request]() { // Give one second to let the client get into the middle of its // SSL handshake, and then cancel the request. gpr_sleep_until(grpc_timeout_seconds_to_deadline(1)); grpc_core::ExecCtx exec_ctx; http_request.reset(); }); // Poll with a deadline explicitly lower than the request timeout, so // that we know that the request timeout isn't just kicking in. PollUntil([&request_state]() { return request_state.done; }, AbslDeadlineSeconds(60)); cancel_thread.join(); })); } for (auto& t : threads) { t.join(); } } } // namespace int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); grpc::testing::TestEnvironment env(&argc, argv); // launch the test server later, so that --gtest_list_tests works g_argc = argc; g_argv = argv; // run tests return RUN_ALL_TESTS(); }