Skip to content

Commit 6ee5a79

Browse files
committed
Revert "fix server HTTP keep-alive"
1 parent df6bb4e commit 6ee5a79

File tree

5 files changed

+88
-39
lines changed

5 files changed

+88
-39
lines changed

src/Server/HTTP/HTTPServerRequest.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,11 @@ HTTPServerRequest::HTTPServerRequest(HTTPContextPtr context, HTTPServerResponse
5656
/// and retry with exactly the same (incomplete) set of rows.
5757
/// That's why we have to check body size if it's provided.
5858
if (getChunkedTransferEncoding())
59-
{
6059
stream = std::make_unique<HTTPChunkedReadBuffer>(std::move(in), HTTP_MAX_CHUNK_SIZE);
61-
stream_is_bounded = true;
62-
}
6360
else if (hasContentLength())
6461
{
6562
size_t content_length = getContentLength();
6663
stream = std::make_unique<LimitReadBuffer>(std::move(in), LimitReadBuffer::Settings{.read_no_less = content_length, .read_no_more = content_length, .expect_eof = true});
67-
stream_is_bounded = true;
6864
}
6965
else if (getMethod() != HTTPRequest::HTTP_GET && getMethod() != HTTPRequest::HTTP_HEAD && getMethod() != HTTPRequest::HTTP_DELETE)
7066
{

src/Server/HTTP/HTTPServerRequest.h

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,6 @@ class HTTPServerRequest : public HTTPRequest
4747
X509Certificate peerCertificate() const;
4848
#endif
4949

50-
bool canKeepAlive() const
51-
{
52-
if (stream && stream_is_bounded)
53-
return stream->eof();
54-
55-
return false;
56-
}
57-
5850
private:
5951
/// Limits for basic sanity checks when reading a header
6052
enum Limits
@@ -73,7 +65,6 @@ class HTTPServerRequest : public HTTPRequest
7365
Poco::Net::SocketAddress client_address;
7466
Poco::Net::SocketAddress server_address;
7567

76-
bool stream_is_bounded = false;
7768
bool secure;
7869

7970
void readRequest(ReadBuffer & in);

src/Server/HTTP/HTTPServerResponse.cpp

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <Poco/Net/HTTPHeaderStream.h>
1111
#include <Poco/Net/HTTPStream.h>
1212
#include <IO/NullWriteBuffer.h>
13+
#include <sstream>
1314

1415

1516
namespace DB
@@ -39,6 +40,9 @@ std::shared_ptr<WriteBuffer> HTTPServerResponse::send()
3940
// the connection would be poisoned.
4041
// Next request over that connection reads previously unreaded message as a HTTP status line
4142

43+
// Send header
44+
Poco::Net::HTTPHeaderOutputStream hs(session);
45+
write(hs);
4246
// make sure that nothing is sent to the client if it was HTTP_HEAD request
4347
stream = std::make_shared<NullWriteBuffer>(write_event);
4448

@@ -49,36 +53,75 @@ std::shared_ptr<WriteBuffer> HTTPServerResponse::send()
4953
// but if we do, then it is safer to close the connection at the end
5054
setKeepAlive(false);
5155

56+
// Send header
57+
Poco::Net::HTTPHeaderOutputStream hs(session);
58+
write(hs);
5259
stream = std::make_shared<AutoFinalizedWriteBuffer<WriteBufferFromPocoSocket>>(session.socket(), write_event);
5360
}
5461
else if (getChunkedTransferEncoding())
5562
{
63+
// Send header
64+
Poco::Net::HTTPHeaderOutputStream hs(session);
65+
write(hs);
5666
stream = std::make_shared<AutoFinalizedWriteBuffer<HTTPWriteBufferChunked>>(session.socket(), write_event);
5767
}
5868
else if (hasContentLength())
5969
{
70+
// Send header
71+
Poco::Net::HTTPHeaderOutputStream hs(session);
72+
write(hs);
6073
stream = std::make_shared<AutoFinalizedWriteBuffer<HTTPWriteBufferFixedLength>>(session.socket(), getContentLength(), write_event);
6174
}
6275
else
6376
{
6477
setKeepAlive(false);
65-
78+
// Send header
79+
Poco::Net::HTTPHeaderOutputStream hs(session);
80+
write(hs);
6681
stream = std::make_shared<AutoFinalizedWriteBuffer<WriteBufferFromPocoSocket>>(session.socket(), write_event);
6782
}
6883

69-
Poco::Net::HTTPHeaderOutputStream hs(session);
70-
beginWrite(hs);
71-
hs << "\r\n";
72-
hs.flush();
73-
84+
send_started = true;
7485
return stream;
7586
}
7687

77-
/// Only this method is called inside WriteBufferFromHTTPServerResponse
78-
void HTTPServerResponse::beginWrite(std::ostream & ostr)
88+
std::pair<std::shared_ptr<WriteBuffer>, std::shared_ptr<WriteBuffer>> HTTPServerResponse::beginSend()
7989
{
80-
allowKeepAliveIFFRequestIsFullyRead();
90+
poco_assert(!stream);
91+
poco_assert(!header_stream);
92+
93+
/// NOTE: Code is not exception safe.
8194

95+
if ((request && request->getMethod() == HTTPRequest::HTTP_HEAD) || getStatus() < 200 || getStatus() == HTTPResponse::HTTP_NO_CONTENT
96+
|| getStatus() == HTTPResponse::HTTP_NOT_MODIFIED)
97+
{
98+
throw Poco::Exception("HTTPServerResponse::beginSend is invalid for HEAD request");
99+
}
100+
101+
if (hasContentLength())
102+
{
103+
throw Poco::Exception("HTTPServerResponse::beginSend is invalid for response with Content-Length header");
104+
}
105+
106+
// Write header to buffer
107+
std::stringstream header; //STYLE_CHECK_ALLOW_STD_STRING_STREAM
108+
beginWrite(header);
109+
// Send header
110+
auto str = header.str();
111+
header_stream = std::make_shared<AutoFinalizedWriteBuffer<WriteBufferFromPocoSocket>>(session.socket(), write_event, str.size());
112+
header_stream->write(str.data(), str.size());
113+
114+
if (getChunkedTransferEncoding())
115+
stream = std::make_shared<AutoFinalizedWriteBuffer<HTTPWriteBufferChunked>>(session.socket(), write_event);
116+
else
117+
stream = std::make_shared<AutoFinalizedWriteBuffer<WriteBufferFromPocoSocket>>(session.socket(), write_event);
118+
119+
send_started = true;
120+
return std::make_pair(header_stream, stream);
121+
}
122+
123+
void HTTPServerResponse::beginWrite(std::ostream & ostr) const
124+
{
82125
HTTPResponse::beginWrite(ostr);
83126
send_started = true;
84127
}
@@ -87,11 +130,9 @@ void HTTPServerResponse::sendBuffer(const void * buffer, std::size_t length)
87130
{
88131
setContentLength(static_cast<int>(length));
89132
setChunkedTransferEncoding(false);
90-
91133
// Send header
92134
Poco::Net::HTTPHeaderOutputStream hs(session);
93-
beginWrite(hs);
94-
hs << "\r\n";
135+
write(hs);
95136
hs.flush();
96137

97138
if (request && request->getMethod() != HTTPRequest::HTTP_HEAD)
@@ -125,17 +166,8 @@ void HTTPServerResponse::redirect(const std::string & uri, HTTPStatus status)
125166

126167
// Send header
127168
Poco::Net::HTTPHeaderOutputStream hs(session);
128-
beginWrite(hs);
129-
hs << "\r\n";
169+
write(hs);
130170
hs.flush();
131171
}
132172

133-
void HTTPServerResponse::allowKeepAliveIFFRequestIsFullyRead()
134-
{
135-
/// Connection can only be reused if we've fully read the previous request and all its POST data.
136-
/// Otherwise we'd misinterpret the leftover data as part of the next request's header.
137-
/// HTTPServerRequest::canKeepAlive() checks that request stream is bounded and is fully read.
138-
if (!request || !request->canKeepAlive())
139-
setKeepAlive(false);
140-
}
141173
}

src/Server/HTTP/HTTPServerResponse.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,17 @@ class HTTPServerResponse : public HTTPResponse
234234
/// or redirect() has been called.
235235
std::shared_ptr<WriteBuffer> send();
236236

237-
/// Dangerous, it is not a virtual method in HTTPResponse but it is redefined here
237+
/// Sends the response headers to the client
238+
/// but do not finish headers with \r\n,
239+
/// allowing to continue sending additional header fields.
240+
///
241+
/// Must not be called after send(), sendFile(), sendBuffer()
242+
/// or redirect() has been called.
243+
std::pair<std::shared_ptr<WriteBuffer>, std::shared_ptr<WriteBuffer>> beginSend();
244+
238245
/// Override to correctly mark that the data send had been started for
239246
/// zero-copy response (i.e. replicated fetches).
240-
void beginWrite(std::ostream & ostr);
247+
void beginWrite(std::ostream & ostr) const;
241248

242249
/// Sends the response header to the client, followed
243250
/// by the contents of the given buffer.
@@ -277,8 +284,6 @@ class HTTPServerResponse : public HTTPResponse
277284

278285
const Poco::Net::HTTPServerSession & getSession() const { return session; }
279286

280-
void allowKeepAliveIFFRequestIsFullyRead();
281-
282287
private:
283288
Poco::Net::HTTPServerSession & session;
284289
HTTPServerRequest * request = nullptr;

src/Server/HTTPHandler.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,31 @@ void HTTPHandler::processQuery(
617617
{},
618618
handle_exception_in_output_format,
619619
query_finish_callback);
620+
621+
/// HTTPChunkedReadBuffer doesn't consume the final \r\n0\r\n\r\n if the caller reads exactly all bytes,
622+
/// without checking for eof() (i.e. trying to read past the end) after that.
623+
/// If those leftovers are then seen by the next request's HTTPServerRequest::readRequest,
624+
/// it would in theory produce the error:
625+
/// Invalid HTTP version string: /?query=I,
626+
/// because that extra 0 field shifts all fields by one, and uri ends up interpreted as version.
627+
/// There are a few options how to prevent that:
628+
/// 1. Officially require that IInputFormat must drain the input buffer, make sure all implementations do that.
629+
/// 2. Make the HTTP handler drain the request's POST read buffer after the request.
630+
/// 3. Make HTTPChunkedReadBuffer eagerly drain the final \r\n0\r\n\r\n before returning the final bytes of data,
631+
// but that seems very inconvenient to implement because of how zero-copying is done in HTTPChunkedReadBuffer::nextImpl()
632+
/// You can find an option (2) implemented below.
633+
if (in_post_maybe_compressed->available())
634+
{
635+
String remaining;
636+
readStringUntilEOF(remaining, *in_post_maybe_compressed);
637+
auto hex_string = hexString(remaining.data(), remaining.size());
638+
#if defined(DEBUG_OR_SANITIZER_BUILD)
639+
throw Exception(ErrorCodes::LOGICAL_ERROR,
640+
"There is still some data in the buffer: {}", hex_string);
641+
#else
642+
LOG_WARNING(log, "There is still some data in the buffer: {}", hex_string);
643+
#endif
644+
}
620645
}
621646

622647
bool HTTPHandler::trySendExceptionToClient(

0 commit comments

Comments
 (0)