-
Notifications
You must be signed in to change notification settings - Fork 656
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Fix up HTTP message framing edge cases. #298
Conversation
Sorry about the size of this patch, but FWIW the vast, vast majority of it is a series of tests to clarify our handling of the various edge cases. |
Sources/NIOHTTP1/HTTPDecoder.swift
Outdated
|
||
if ((head.status.code / 100 == 1) || // 1XX codes | ||
(head.status.code == 204) || | ||
(head.status.code == 304)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: You don't need the (...)
around rich of these
Sources/NIOHTTP1/HTTPDecoder.swift
Outdated
} | ||
|
||
// We don't need the cumulation buffer, if we're holding it. | ||
self.cumulationBuffer = nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Lukasa should we set this to nil before we call channelReadComplete
to reduce the reference count and so minimize the chances of CoW ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, in fact we can set it to nil
very early in this function without danger.
case .body: | ||
self.seenBody = true | ||
case .end: | ||
self.seenEnd = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we also assert that we not seen the same state before ?
XCTAssertTrue(handler.receivedEnd) | ||
XCTAssertTrue(handler.eof) | ||
|
||
XCTAssertNoThrow(try channel.finish()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should actually assert the return value of channel.finish()
XCTAssertFalse(handler.seenBody) | ||
XCTAssert(handler.seenEnd) | ||
|
||
XCTAssertNoThrow(try channel.finish()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should actually assert the return value of channel.finish()
XCTAssertFalse(handler.seenBody) | ||
XCTAssert(handler.seenEnd) | ||
|
||
XCTAssertNoThrow(try channel.finish()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should actually assert the return value of channel.finish()
} | ||
|
||
// Must spin the loop. | ||
(channel.eventLoop as! EmbeddedEventLoop).run() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assert that the channel is not active anymore
} | ||
|
||
// Must spin the loop. | ||
(channel.eventLoop as! EmbeddedEventLoop).run() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert the channel is not native anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should actually assert the return value of channel.finish()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, we can't do that here because channel.finish()
will throw if the channel is already closed.
} | ||
|
||
// Must spin the loop. | ||
(channel.eventLoop as! EmbeddedEventLoop).run() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert the channel is not active anymore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should actually assert the return value of channel.finish()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, we can't do that here because channel.finish()
will throw if the channel is already closed.
XCTAssertFalse(handler.seenBody) | ||
XCTAssert(handler.seenEnd) | ||
|
||
XCTAssertNoThrow(try channel.finish()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert the return value
} | ||
|
||
// Must spin the loop. | ||
(channel.eventLoop as! EmbeddedEventLoop).run() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert that the channel is not active anymore and also call channel.finish()
and assert return value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, we can't do that here because channel.finish()
will throw if the channel is already closed.
c8df6c6
to
9364f0c
Compare
Ok @normanmaurer, please re-review. |
|
||
/// Tests for the HTTP decoder's handling of message body framing. | ||
/// | ||
/// Mostly tests assertions in RFC 7230 § 3.3.3. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Lukasa nit: consider linking the rfc directly via markdown. (feel free to ignore tho)
XCTAssertTrue(try channel.writeOutbound(HTTPClientRequestPart.head(HTTPRequestHead(version: .init(major: 1, minor: 1), | ||
method: requestMethod, | ||
uri: "/")))) | ||
_ = channel.readOutbound() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we ensure this not returns nil ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just one thing... LGTM otherwise
XCTAssertTrue(try channel.writeOutbound(HTTPClientRequestPart.head(HTTPRequestHead(version: .init(major: 1, minor: 1), | ||
method: .GET, | ||
uri: "/")))) | ||
_ = channel.readOutbound() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we ensure this not return nil ?
9364f0c
to
e3c3bd9
Compare
Ok, addressed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work!
@swift-nio-bot test this please |
1 similar comment
@swift-nio-bot test this please |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks so much, this is amazing!!! I left two comments but still approved it but it'd be awesome if you could rename those errno
variables.
Sources/NIOHTTP1/HTTPDecoder.swift
Outdated
// No check to state.currentError becuase, if we hit it before, we already threw that | ||
// error. This never calls any of the callbacks that set that field anyway. Instead we | ||
// just check if the errno is set and throw. | ||
let errno = parser.http_errno |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a huge fan of naming variables errno
because if you rename them and forget the usage sites, it'll still work. Mind making it httpError
or httpErrno
or something?
Sources/NIOHTTP1/HTTPDecoder.swift
Outdated
@@ -395,7 +416,8 @@ public class HTTPDecoder<HTTPMessageT>: ByteToMessageDecoder, AnyHTTPDecoder { | |||
|
|||
let errno = parser.http_errno | |||
if errno != 0 { | |||
throw HTTPParserError.httpError(fromCHTTPParserErrno: http_errno(rawValue: errno))! | |||
self.state.currentError = HTTPParserError.httpError(fromCHTTPParserErrno: http_errno(rawValue: errno))! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whilst we're on it, mind also renaming that errno
variable up there?
@Lukasa compilation failed though:
|
Motivation: HTTP message framing has a number of edge cases that NIO currently does not tolerate. We should decide what our position is on each of these edge cases and handle it appropriately. Modifications: Provide an extensive test suite that codifies our expected handling of these edge cases. Fix divergences from this behaviour. Result: Better tolerance for the weird corners of HTTP.
e36e55b
to
70bd13e
Compare
@jw Ok, that should be addressed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks, approved!
@swift-nio-bot test this please |
Motivation:
HTTP message framing has a number of edge cases that NIO currently does
not tolerate. We should decide what our position is on each of these edge
cases and handle it appropriately.
Modifications:
Provide an extensive test suite that codifies our expected handling of
these edge cases. Fix divergences from this behaviour.
Result:
Better tolerance for the weird corners of HTTP.
Resolves #254.