1
#include "source/common/router/shadow_writer_impl.h"
2

            
3
#include <chrono>
4
#include <string>
5

            
6
#include "source/common/common/assert.h"
7
#include "source/common/http/header_utility.h"
8
#include "source/common/http/headers.h"
9

            
10
#include "absl/strings/str_join.h"
11

            
12
namespace Envoy {
13
namespace Router {
14

            
15
namespace {
16

            
17
82
std::string shadowAppendedHost(absl::string_view host) {
18
82
  ASSERT(!host.empty());
19
  // Switch authority to add a shadow postfix. This allows upstream logging to
20
  // make more sense.
21
82
  absl::string_view::size_type port_start = Http::HeaderUtility::getPortStart(host);
22
82
  if (port_start == absl::string_view::npos) {
23
77
    return absl::StrCat(host, "-shadow");
24
77
  }
25
5
  return absl::StrCat(host.substr(0, port_start), "-shadow",
26
5
                      host.substr(port_start, host.length()));
27
82
}
28

            
29
} // namespace
30

            
31
void ShadowWriterImpl::shadow(const std::string& cluster, Http::RequestMessagePtr&& request,
32
54
                              const Http::AsyncClient::RequestOptions& options) {
33
54
  const auto thread_local_cluster =
34
54
      getClusterAndPreprocessHeadersAndOptions(cluster, request->headers(), options);
35
54
  if (thread_local_cluster == nullptr) {
36
1
    return;
37
1
  }
38
  // This is basically fire and forget. We don't handle cancelling.
39
53
  thread_local_cluster->httpAsyncClient().send(std::move(request), *this, options);
40
53
}
41

            
42
Http::AsyncClient::OngoingRequest*
43
ShadowWriterImpl::streamingShadow(const std::string& cluster, Http::RequestHeaderMapPtr&& headers,
44
35
                                  const Http::AsyncClient::RequestOptions& options) {
45
35
  const auto thread_local_cluster =
46
35
      getClusterAndPreprocessHeadersAndOptions(cluster, *headers, options);
47
35
  if (thread_local_cluster == nullptr) {
48
    return nullptr;
49
  }
50
35
  return thread_local_cluster->httpAsyncClient().startRequest(std::move(headers), *this, options);
51
35
}
52

            
53
Upstream::ThreadLocalCluster* ShadowWriterImpl::getClusterAndPreprocessHeadersAndOptions(
54
    absl::string_view cluster, Http::RequestHeaderMap& headers,
55
89
    const Http::AsyncClient::RequestOptions& options) {
56
  // It's possible that the cluster specified in the route configuration no longer exists due
57
  // to a CDS removal. Check that it still exists before shadowing.
58
  // TODO(mattklein123): Optimally we would have a stat but for now just fix the crashing issue.
59
89
  const auto thread_local_cluster = cm_.getThreadLocalCluster(cluster);
60
89
  if (thread_local_cluster == nullptr) {
61
1
    ENVOY_LOG(debug, "shadow cluster '{}' does not exist", cluster);
62
1
    return nullptr;
63
1
  }
64

            
65
  // Append "-shadow" suffix if option is disabled.
66
88
  if (!options.is_shadow_suffixed_disabled) {
67
82
    headers.setHost(shadowAppendedHost(headers.getHostValue()));
68
82
  }
69

            
70
88
  const_cast<Http::AsyncClient::RequestOptions&>(options).setIsShadow(true);
71
88
  return thread_local_cluster;
72
89
}
73

            
74
} // namespace Router
75
} // namespace Envoy