-
Notifications
You must be signed in to change notification settings - Fork 719
Add support for automatic HTTP error reporting. #268
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
bc7e1c8
4053b91
6dd12a0
7bbd31d
d3cbff5
e0c0d42
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // This source file is part of the SwiftNIO open source project | ||
| // | ||
| // Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors | ||
| // Licensed under Apache License v2.0 | ||
| // | ||
| // See LICENSE.txt for license information | ||
| // See CONTRIBUTORS.txt for the list of SwiftNIO project authors | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| import NIO | ||
|
|
||
| /// A simple channel handler that catches errors emitted by parsing HTTP requests | ||
| /// and sends 400 Bad Request responses. | ||
| /// | ||
| /// This channel handler provides the basic behaviour that the majority of simple HTTP | ||
| /// servers want. This handler does not suppress the parser errors: it allows them to | ||
| /// continue to pass through the pipeline so that other handlers (e.g. logging ones) can | ||
| /// deal with the error. | ||
| public final class HTTPServerProtocolErrorHandler: ChannelInboundHandler { | ||
| public typealias InboundIn = HTTPServerRequestPart | ||
| public typealias InboundOut = HTTPServerRequestPart | ||
| public typealias OutboundOut = HTTPServerResponsePart | ||
|
|
||
| public func errorCaught(ctx: ChannelHandlerContext, error: Error) { | ||
| guard error is HTTPParserError else { | ||
| ctx.fireErrorCaught(error) | ||
| return | ||
| } | ||
|
|
||
| // Any HTTPParserError is automatically fatal, and we don't actually need (or want) to | ||
| // provide that error to the client: we just want to tell it that it screwed up and then | ||
| // let the rest of the pipeline shut the door in its face. | ||
| // | ||
| // A side note here: we cannot block or do any delayed work. ByteToMessageDecoder is going | ||
| // to come along and close the channel right after we return from this function. | ||
| let headers = HTTPHeaders([("Connection", "close"), ("Content-Length", "0")]) | ||
| let head = HTTPResponseHead(version: .init(major: 1, minor: 1), status: .badRequest, headers: headers) | ||
| ctx.write(self.wrapOutboundOut(.head(head)), promise: nil) | ||
| ctx.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Lukasa should we also attach a callback to the future which closes the channel once written ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. Any of these errors will cause a channel to be closed by the HTTPDecoder, so I don't think it's required. |
||
|
|
||
| // Now pass the error on in case someone else wants to see it. | ||
| ctx.fireErrorCaught(error) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // This source file is part of the SwiftNIO open source project | ||
| // | ||
| // Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors | ||
| // Licensed under Apache License v2.0 | ||
| // | ||
| // See LICENSE.txt for license information | ||
| // See CONTRIBUTORS.txt for the list of SwiftNIO project authors | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // HTTPServerProtocolErrorHandlerTest+XCTest.swift | ||
| // | ||
| import XCTest | ||
|
|
||
| /// | ||
| /// NOTE: This file was generated by generate_linux_tests.rb | ||
| /// | ||
| /// Do NOT edit this file directly as it will be regenerated automatically when needed. | ||
| /// | ||
|
|
||
| extension HTTPServerProtocolErrorHandlerTest { | ||
|
|
||
| static var allTests : [(String, (HTTPServerProtocolErrorHandlerTest) -> () throws -> Void)] { | ||
| return [ | ||
| ("testHandlesBasicErrors", testHandlesBasicErrors), | ||
| ("testIgnoresNonParserErrors", testIgnoresNonParserErrors), | ||
| ] | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // This source file is part of the SwiftNIO open source project | ||
| // | ||
| // Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors | ||
| // Licensed under Apache License v2.0 | ||
| // | ||
| // See LICENSE.txt for license information | ||
| // See CONTRIBUTORS.txt for the list of SwiftNIO project authors | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| import XCTest | ||
| import NIO | ||
| import NIOHTTP1 | ||
|
|
||
| class HTTPServerProtocolErrorHandlerTest: XCTestCase { | ||
| func testHandlesBasicErrors() throws { | ||
| let channel = EmbeddedChannel() | ||
| XCTAssertNoThrow(try channel.pipeline.configureHTTPServerPipeline(withErrorHandling: true).wait()) | ||
|
|
||
| var buffer = channel.allocator.buffer(capacity: 1024) | ||
| buffer.write(staticString: "GET / HTTP/1.1\r\nContent-Length: -4\r\n\r\n") | ||
| do { | ||
| try channel.writeInbound(buffer) | ||
| } catch HTTPParserError.invalidContentLength { | ||
| // This error is expected | ||
| } | ||
| (channel.eventLoop as! EmbeddedEventLoop).run() | ||
|
|
||
| // The channel should be closed at this stage. | ||
| XCTAssertNoThrow(try channel.closeFuture.wait()) | ||
|
|
||
| // We expect exactly one ByteBuffer in the output. | ||
| guard case .some(.byteBuffer(var written)) = channel.readOutbound() else { | ||
| XCTFail("No writes") | ||
| return | ||
| } | ||
|
|
||
| XCTAssertNil(channel.readOutbound()) | ||
|
|
||
| // Check the response. | ||
| assertResponseIs(response: written.readString(length: written.readableBytes)!, | ||
| expectedResponseLine: "HTTP/1.1 400 Bad Request", | ||
| expectedResponseHeaders: ["connection: close", "content-length: 0"]) | ||
| } | ||
|
|
||
| func testIgnoresNonParserErrors() throws { | ||
| enum DummyError: Error { | ||
| case error | ||
| } | ||
| let channel = EmbeddedChannel() | ||
| XCTAssertNoThrow(try channel.pipeline.configureHTTPServerPipeline(withErrorHandling: true).wait()) | ||
|
|
||
| channel.pipeline.fireErrorCaught(DummyError.error) | ||
| do { | ||
| try channel.throwIfErrorCaught() | ||
| XCTFail("No error caught") | ||
| } catch DummyError.error { | ||
| // ok | ||
| } catch { | ||
| XCTFail("Unexpected error: \(error)") | ||
| } | ||
|
|
||
| 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.
is 1 .1 a good choice here or should we use 1.0 ?
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.
What value do we obtain by using 1.0?
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.
Was just wondering if 1.0 would be a better choice but I guess 1.1 is more used these days so all good