Skip to content

Commit d369c92

Browse files
author
nmolotilov
committed
fix userver: fix streaming middlewares integration
1. Removed special error handling logic from HandleRequestStream because it is not needed; errors are correctly handled by common middleware 2. Changed so that all middleware headers are placed before the user-defined part of handling (because for streaming headers are sent after the first chunk that happens in the user-defined part). To support this, added to HttpResponse different sctructures for system and user headers, so that while handling errors user headers can be erased, while leaving system headers commit_hash:7a3ae6ec36fea916afd4a31407e5842271a9d112
1 parent 598240c commit d369c92

19 files changed

+188
-136
lines changed

core/functional_tests/basic_chaos/tests/httpclient/test_httpclient_stream.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,24 @@ async def mock(request):
121121
response = await call()
122122
assert response.status == 200
123123
assert mock.times_called > 1
124+
125+
126+
async def test_required_headers(call, gate, mockserver):
127+
@mockserver.handler('/test')
128+
async def mock(request):
129+
return mockserver.make_response('OK!')
130+
131+
required_headers = ['X-YaRequestId', 'X-YaSpanId', 'X-YaTraceId']
132+
133+
response = await call()
134+
assert response.status == 200
135+
assert all(key in response.headers for key in required_headers)
136+
137+
await gate.stop_accepting()
138+
await gate.sockets_close() # close keepalive connections
139+
assert gate.connections_count() == 0
140+
141+
response = await call()
142+
assert response.status == 500
143+
assert all(key in response.headers for key in required_headers)
144+
assert gate.connections_count() == 0

core/include/userver/server/handlers/http_handler_base.hpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,19 @@ class HttpHandlerBase : public HandlerBase {
9797

9898
/// Takes the exception and formats it into response, as specified by
9999
/// exception.
100-
void HandleCustomHandlerException(const http::HttpRequest& request, const CustomHandlerException& ex) const;
100+
void HandleCustomHandlerException(
101+
const http::HttpRequest& request,
102+
request::RequestContext& context,
103+
const CustomHandlerException& ex
104+
) const;
101105

102106
/// Takes the exception and formats it into response as an internal server
103107
/// error.
104-
void HandleUnknownException(const http::HttpRequest& request, const std::exception& ex) const;
108+
void HandleUnknownException(
109+
const http::HttpRequest& request,
110+
request::RequestContext& context,
111+
const std::exception& ex
112+
) const;
105113

106114
/// Helper function to log an unknown exception
107115
void LogUnknownException(const std::exception& ex, std::optional<logging::Level> log_level_override = {}) const;

core/include/userver/server/http/http_request.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ class HttpRequest final {
293293
friend class HttpRequestHandler;
294294

295295
struct Impl;
296-
utils::FastPimpl<Impl, 1648, 16> pimpl_;
296+
utils::FastPimpl<Impl, 1904, 16> pimpl_;
297297
};
298298

299299
} // namespace server::http

core/include/userver/server/http/http_response.hpp

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,14 @@ class HttpResponse final : public request::ResponseBase {
6666

6767
/// @brief Add a new response header or rewrite an existing one.
6868
/// @returns true if the header was set. Returns false if headers
69-
/// were already sent for stream'ed response and the new header was not set.
69+
/// were already sent for stream'ed response and the new header was not set,
70+
/// or the header overrides system header.
7071
bool SetHeader(std::string name, std::string value);
7172

7273
/// @brief Add a new response header or rewrite an existing one.
7374
/// @returns true if the header was set. Returns false if headers
74-
/// were already sent for stream'ed response and the new header was not set.
75+
/// were already sent for stream'ed response and the new header was not set,
76+
/// or the header overrides system header.
7577
bool SetHeader(std::string_view name, std::string value);
7678

7779
/// @overload
@@ -88,10 +90,15 @@ class HttpResponse final : public request::ResponseBase {
8890
/// were already sent for stream'ed response and the new status was not set.
8991
bool SetStatus(HttpStatus status);
9092

91-
/// @brief Remove all headers from response.
93+
/// @brief Set the end of system headers.
94+
/// All headers written before this call are considered system; after - user.
95+
/// User headers can't overwrite system headers.
96+
void SetSystemHeadersEnd();
97+
98+
/// @brief Remove all headers from response, except system headers.
9299
/// @returns true if the headers were cleared. Returns false if headers
93100
/// were already sent for stream'ed response and the headers were not cleared.
94-
bool ClearHeaders();
101+
bool ClearUserHeaders();
95102

96103
/// @brief Sets a cookie if it was not set before.
97104
void SetCookie(Cookie cookie);
@@ -102,8 +109,11 @@ class HttpResponse final : public request::ResponseBase {
102109
/// @return HTTP response status
103110
HttpStatus GetStatus() const { return status_; }
104111

105-
/// @return List of HTTP headers names.
106-
HeadersMapKeys GetHeaderNames() const;
112+
/// @return List of HTTP system headers names.
113+
HeadersMapKeys GetSystemHeaderNames() const;
114+
115+
/// @return List of HTTP user headers names.
116+
HeadersMapKeys GetUserHeaderNames() const;
107117

108118
/// @return Value of the header with case insensitive name header_name, or an
109119
/// empty string if no such header.
@@ -155,9 +165,11 @@ class HttpResponse final : public request::ResponseBase {
155165

156166
const HttpRequest& request_;
157167
HttpStatus status_ = HttpStatus::kOk;
158-
HeadersMap headers_;
168+
HeadersMap system_headers_;
169+
HeadersMap user_headers_;
159170
CookiesMap cookies_;
160171

172+
bool system_headers_ended_ = false;
161173
engine::SingleConsumerEvent headers_end_{engine::SingleConsumerEvent::NoAutoReset()};
162174
std::optional<Queue::Consumer> body_stream_;
163175
Producer body_stream_producer_;

core/include/userver/server/http/http_response_body_stream.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,13 @@ class ResponseBodyStream final {
2020

2121
// Send a chunk of response data. It may NOT generate
2222
// exactly one HTTP chunk per call to PushBodyChunk().
23+
// May not be called after SetBody().
2324
void PushBodyChunk(std::string&& chunk, engine::Deadline deadline);
2425

26+
// Set full response body instead of sending chunks.
27+
// May not be called after PushBodyChunk().
28+
void SetBody(std::string&& body);
29+
2530
void SetHeader(const std::string&, const std::string&);
2631

2732
void SetHeader(std::string_view, const std::string&);
@@ -38,6 +43,7 @@ class ResponseBodyStream final {
3843
ResponseBodyStream(HttpResponse::Producer&& queue_producer, HttpResponse& http_response);
3944

4045
bool headers_ended_{false};
46+
bool headers_end_sent_{false};
4147
HttpResponse::Producer queue_producer_;
4248
HttpResponse& http_response_;
4349
};

core/src/server/handlers/http_handler_base.cpp

Lines changed: 15 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -74,21 +74,6 @@ void SetFormattedErrorResponse(http::HttpResponse& http_response, FormattedError
7474
}
7575
}
7676

77-
void SetFormattedErrorResponse(
78-
server::http::ResponseBodyStream& response_body_stream,
79-
FormattedErrorData&& formatted_error,
80-
engine::Deadline deadline
81-
) {
82-
if (formatted_error.content_type) {
83-
response_body_stream.SetHeader(
84-
USERVER_NAMESPACE::http::headers::kContentType,
85-
std::move(formatted_error.content_type->ToString())
86-
);
87-
}
88-
response_body_stream.SetEndOfHeaders();
89-
response_body_stream.PushBodyChunk(std::move(formatted_error.external_body), deadline);
90-
}
91-
9277
std::unordered_map<int, logging::Level> ParseStatusCodesLogLevel(
9378
const std::unordered_map<std::string, std::string>& codes
9479
) {
@@ -211,51 +196,15 @@ HttpHandlerBase::HttpHandlerBase(
211196
HttpHandlerBase::~HttpHandlerBase() { statistics_holder_.Unregister(); }
212197

213198
void HttpHandlerBase::HandleRequestStream(http::HttpRequest& http_request, request::RequestContext& context) const {
214-
auto& response = http_request.GetHttpResponse();
215-
const utils::ScopeGuard scope([&response] { response.SetHeadersEnd(); });
199+
auto& http_response = http_request.GetHttpResponse();
200+
server::http::ResponseBodyStream response_body_stream(http_response.GetBodyProducer(), http_response);
216201

217-
server::http::ResponseBodyStream response_body_stream{response.GetBodyProducer(), response};
218-
219-
// Just in case HandleStreamRequest() throws an exception.
220-
// Though it can be changed in HandleStreamRequest().
221-
response_body_stream.SetStatusCode(500);
222-
223-
try {
224-
HandleStreamRequest(http_request, context, response_body_stream);
225-
} catch (const CustomHandlerException& e) {
226-
response_body_stream.SetStatusCode(http::GetHttpStatus(e));
227-
228-
for (const auto& [name, value] : e.GetExtraHeaders()) {
229-
response_body_stream.SetHeader(name, value);
230-
}
231-
if (e.IsExternalErrorBodyFormatted()) {
232-
response_body_stream.SetEndOfHeaders();
233-
response_body_stream.PushBodyChunk(std::string{e.GetExternalErrorBody()}, engine::Deadline());
234-
} else {
235-
auto formatted_error = GetFormattedExternalErrorBody(e);
236-
SetFormattedErrorResponse(response_body_stream, std::move(formatted_error), engine::Deadline());
237-
}
238-
} catch (const std::exception& e) {
239-
if (engine::current_task::ShouldCancel()) {
240-
LOG_WARNING()
241-
<< "request task cancelled, exception in '" << HandlerName() << "' handler in handle_request: " << e;
242-
response_body_stream.SetStatusCode(http::HttpStatus::kClientClosedRequest);
243-
} else {
244-
LOG_ERROR() << "exception in '" << HandlerName() << "' handler in handle_request: " << e;
245-
response_body_stream.SetStatusCode(500);
246-
SetFormattedErrorResponse(
247-
response,
248-
GetFormattedExternalErrorBody({
249-
HandlerErrorCode::kServerSideError,
250-
ExternalBody{response.GetData()},
251-
})
252-
);
253-
}
254-
}
202+
HandleStreamRequest(http_request, context, response_body_stream);
255203
}
256204

257205
void HttpHandlerBase::HandleHttpRequest(http::HttpRequest& http_request, request::RequestContext& context) const {
258206
auto& response = http_request.GetHttpResponse();
207+
response.SetSystemHeadersEnd();
259208

260209
// Don't hold the config snapshot for too long, especially with streaming.
261210
context.GetInternalContext().ResetConfigSnapshot();
@@ -273,6 +222,7 @@ void HttpHandlerBase::PrepareAndHandleRequest(http::HttpRequest& http_request, r
273222
auto& response = http_request.GetHttpResponse();
274223

275224
context.GetInternalContext().SetConfigSnapshot(config_source_.GetSnapshot());
225+
SetResponseServerHostname(response);
276226
try {
277227
UASSERT(first_middleware_);
278228
first_middleware_->HandleRequest(http_request, context);
@@ -286,7 +236,6 @@ void HttpHandlerBase::PrepareAndHandleRequest(http::HttpRequest& http_request, r
286236
response.SetStatus(http::HttpStatus::kInternalServerError);
287237
}
288238

289-
SetResponseServerHostname(response);
290239
response.SetHeadersEnd();
291240
}
292241

@@ -434,8 +383,11 @@ std::string HttpHandlerBase::GetUrlForLoggingChecked(const http::HttpRequest& re
434383
}
435384
}
436385

437-
void HttpHandlerBase::HandleCustomHandlerException(const http::HttpRequest& request, const CustomHandlerException& ex)
438-
const {
386+
void HttpHandlerBase::HandleCustomHandlerException(
387+
const http::HttpRequest& request,
388+
request::RequestContext&,
389+
const CustomHandlerException& ex
390+
) const {
439391
auto http_status = http::GetHttpStatus(ex);
440392
const auto level = GetLogLevelForResponseStatus(http_status);
441393
LOG(level) << "custom handler exception in '" << HandlerName() << "' handler: msg=" << ex;
@@ -452,7 +404,11 @@ void HttpHandlerBase::HandleCustomHandlerException(const http::HttpRequest& requ
452404
}
453405
}
454406

455-
void HttpHandlerBase::HandleUnknownException(const http::HttpRequest& request, const std::exception& ex) const {
407+
void HttpHandlerBase::HandleUnknownException(
408+
const http::HttpRequest& request,
409+
request::RequestContext&,
410+
const std::exception& ex
411+
) const {
456412
LogUnknownException(ex);
457413

458414
auto& response = request.GetHttpResponse();

core/src/server/http/http2_writer.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,23 +144,29 @@ class Http2ResponseWriter final {
144144
private:
145145
Http2HeaderWriter GetHeaders() const {
146146
// Preallocate space for all headers
147-
Http2HeaderWriter header_writer{response_.headers_.size() + response_.cookies_.size() + 3};
147+
Http2HeaderWriter header_writer{
148+
response_.system_headers_.size() + response_.user_headers_.size() + response_.cookies_.size() + 3
149+
};
148150

149151
header_writer.AddKeyValue(
150152
USERVER_NAMESPACE::http::headers::k2::kStatus,
151153
fmt::to_string(static_cast<std::uint16_t>(response_.status_))
152154
);
153155

154-
const auto& headers = response_.headers_;
155-
const auto end = headers.cend();
156-
if (headers.find(USERVER_NAMESPACE::http::headers::kDate) == end) {
156+
if (!response_.HasHeader(USERVER_NAMESPACE::http::headers::kDate)) {
157157
header_writer.AddKeyValue(USERVER_NAMESPACE::http::headers::kDate, std::string{impl::GetCachedDate()});
158158
}
159-
if (headers.find(USERVER_NAMESPACE::http::headers::kContentType) == end) {
159+
if (!response_.HasHeader(USERVER_NAMESPACE::http::headers::kContentType)) {
160160
header_writer.AddKeyValue(USERVER_NAMESPACE::http::headers::kContentType, kDefaultContentType);
161161
}
162162
const std::string_view content_length_header{USERVER_NAMESPACE::http::headers::kContentLength};
163-
for (const auto& [key, value] : headers) {
163+
for (const auto& [key, value] : response_.system_headers_) {
164+
if (key == content_length_header) {
165+
continue;
166+
}
167+
header_writer.AddKeyValue(key, value);
168+
}
169+
for (const auto& [key, value] : response_.user_headers_) {
164170
if (key == content_length_header) {
165171
continue;
166172
}

core/src/server/http/http_request.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -323,12 +323,7 @@ void HttpRequest::MarkAsInternalServerError() const {
323323
// TODO : refactor, this being here is a bit ridiculous
324324
pimpl_->response.SetStatus(http::HttpStatus::kInternalServerError);
325325
pimpl_->response.SetData({});
326-
327-
std::string server_header = pimpl_->response.GetHeader(USERVER_NAMESPACE::http::headers::kServer);
328-
pimpl_->response.ClearHeaders();
329-
if (!server_header.empty()) {
330-
pimpl_->response.SetHeader(USERVER_NAMESPACE::http::headers::kServer, std::move(server_header));
331-
}
326+
pimpl_->response.ClearUserHeaders();
332327
}
333328

334329
void HttpRequest::SetHttpHandler(const handlers::HttpHandlerBase& handler) { pimpl_->handler = &handler; }

0 commit comments

Comments
 (0)