1
#include "source/exe/admin_response.h"
2

            
3
#include "envoy/server/admin.h"
4

            
5
#include "source/server/admin/admin_filter.h"
6
#include "source/server/admin/utils.h"
7

            
8
namespace Envoy {
9

            
10
AdminResponse::AdminResponse(Server::Instance& server, absl::string_view path,
11
                             absl::string_view method, SharedPtrSet response_set)
12
21
    : server_(server), opt_admin_(server.admin()), shared_response_set_(response_set) {
13
21
  request_headers_->setMethod(method);
14
21
  request_headers_->setPath(path);
15
21
}
16

            
17
21
AdminResponse::~AdminResponse() {
18
21
  cancel();
19
21
  shared_response_set_->detachResponse(this);
20
21
}
21

            
22
18
void AdminResponse::getHeaders(HeadersFn fn) {
23
18
  auto request_headers = [response = shared_from_this()]() { response->requestHeaders(); };
24

            
25
  // First check for cancelling or termination.
26
18
  {
27
18
    absl::MutexLock lock(mutex_);
28
18
    ASSERT(headers_fn_ == nullptr);
29
18
    if (cancelled_) {
30
1
      return;
31
1
    }
32
17
    headers_fn_ = fn;
33
17
    if (terminated_ || !opt_admin_) {
34
2
      sendErrorLockHeld();
35
2
      return;
36
2
    }
37
17
  }
38
15
  server_.dispatcher().post(request_headers);
39
15
}
40

            
41
24
void AdminResponse::nextChunk(BodyFn fn) {
42
24
  auto request_next_chunk = [response = shared_from_this()]() { response->requestNextChunk(); };
43

            
44
  // Note the caller may race a call to nextChunk with the server being
45
  // terminated.
46
24
  {
47
24
    absl::MutexLock lock(mutex_);
48
24
    ASSERT(body_fn_ == nullptr);
49
24
    if (cancelled_) {
50
1
      return;
51
1
    }
52
23
    body_fn_ = fn;
53
23
    if (terminated_ || !opt_admin_) {
54
3
      sendAbortChunkLockHeld();
55
3
      return;
56
3
    }
57
23
  }
58

            
59
  // Note that nextChunk may be called from any thread -- it's the callers choice,
60
  // including the Envoy main thread, which would occur if the caller initiates
61
  // the request of a chunk upon receipt of the previous chunk.
62
  //
63
  // In that case it may race against the AdminResponse object being deleted,
64
  // in which case the callbacks, held in a shared_ptr, will be cancelled
65
  // from the destructor. If that happens *before* we post to the main thread,
66
  // we will just skip and never call fn.
67
20
  server_.dispatcher().post(request_next_chunk);
68
20
}
69

            
70
// Called by the user if it is not longer interested in the result of the
71
// admin request. After calling cancel() the caller must not call nextChunk or
72
// getHeaders.
73
29
void AdminResponse::cancel() {
74
29
  absl::MutexLock lock(mutex_);
75
29
  cancelled_ = true;
76
29
  headers_fn_ = nullptr;
77
29
  body_fn_ = nullptr;
78
29
}
79

            
80
22
bool AdminResponse::cancelled() const {
81
22
  absl::MutexLock lock(mutex_);
82
22
  return cancelled_;
83
22
}
84

            
85
// Called from terminateAdminRequests when the Envoy server
86
// terminates. After this is called, the caller may need to complete the
87
// admin response, and so calls to getHeader and nextChunk remain valid,
88
// resulting in 503 and an empty body.
89
15
void AdminResponse::terminate() {
90
15
  ASSERT_IS_MAIN_OR_TEST_THREAD();
91
15
  absl::MutexLock lock(mutex_);
92
15
  if (!terminated_) {
93
15
    terminated_ = true;
94
15
    sendErrorLockHeld();
95
15
    sendAbortChunkLockHeld();
96
15
  }
97
15
}
98

            
99
15
void AdminResponse::requestHeaders() {
100
15
  ASSERT_IS_MAIN_OR_TEST_THREAD();
101
15
  {
102
15
    absl::MutexLock lock(mutex_);
103
15
    if (cancelled_ || terminated_) {
104
1
      return;
105
1
    }
106
15
  }
107
14
  Server::AdminFilter filter(*opt_admin_);
108
14
  filter.decodeHeaders(*request_headers_, false);
109
14
  request_ = opt_admin_->makeRequest(filter);
110
14
  code_ = request_->start(*response_headers_);
111
14
  {
112
14
    absl::MutexLock lock(mutex_);
113
14
    if (headers_fn_ == nullptr || cancelled_) {
114
1
      return;
115
1
    }
116
13
    Server::Utility::populateFallbackResponseHeaders(code_, *response_headers_);
117
13
    headers_fn_(code_, *response_headers_);
118
13
    headers_fn_ = nullptr;
119
13
  }
120
13
}
121

            
122
20
void AdminResponse::requestNextChunk() {
123
20
  ASSERT_IS_MAIN_OR_TEST_THREAD();
124
20
  {
125
20
    absl::MutexLock lock(mutex_);
126
20
    if (cancelled_ || terminated_ || !more_data_) {
127
1
      return;
128
1
    }
129
20
  }
130
19
  ASSERT(response_.length() == 0);
131
19
  more_data_ = request_->nextChunk(response_);
132
19
  {
133
19
    absl::MutexLock lock(mutex_);
134
19
    if (sent_end_stream_ || cancelled_) {
135
1
      return;
136
1
    }
137
18
    sent_end_stream_ = !more_data_;
138
18
    body_fn_(response_, more_data_);
139
18
    ASSERT(response_.length() == 0);
140
18
    body_fn_ = nullptr;
141
18
  }
142
18
}
143

            
144
18
void AdminResponse::sendAbortChunkLockHeld() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
145
18
  if (!sent_end_stream_ && body_fn_ != nullptr) {
146
3
    response_.drain(response_.length());
147
3
    body_fn_(response_, false);
148
3
    sent_end_stream_ = true;
149
3
  }
150
18
  body_fn_ = nullptr;
151
18
}
152

            
153
17
void AdminResponse::sendErrorLockHeld() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
154
17
  if (headers_fn_ != nullptr) {
155
2
    code_ = Http::Code::InternalServerError;
156
2
    Server::Utility::populateFallbackResponseHeaders(code_, *response_headers_);
157
2
    headers_fn_(code_, *response_headers_);
158
2
    headers_fn_ = nullptr;
159
2
  }
160
17
}
161

            
162
29
void AdminResponse::PtrSet::terminateAdminRequests() {
163
29
  ASSERT_IS_MAIN_OR_TEST_THREAD();
164

            
165
29
  absl::MutexLock lock(mutex_);
166
29
  accepting_admin_requests_ = false;
167
29
  for (AdminResponse* response : response_set_) {
168
    // Consider the possibility of response being deleted due to its creator
169
    // dropping its last reference right here. From its destructor it will call
170
    // detachResponse(), which is mutex-ed against this loop, so before the
171
    // memory becomes invalid, the call to terminate will complete.
172
14
    response->terminate();
173
14
  }
174
29
  response_set_.clear();
175
29
}
176

            
177
21
void AdminResponse::PtrSet::attachResponse(AdminResponse* response) {
178
21
  absl::MutexLock lock(mutex_);
179
21
  if (accepting_admin_requests_) {
180
20
    response_set_.insert(response);
181
20
  } else {
182
1
    response->terminate();
183
1
  }
184
21
}
185

            
186
21
void AdminResponse::PtrSet::detachResponse(AdminResponse* response) {
187
21
  absl::MutexLock lock(mutex_);
188
21
  response_set_.erase(response);
189
21
}
190

            
191
} // namespace Envoy