-
Notifications
You must be signed in to change notification settings - Fork 656
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for automatic HTTP error reporting.
Motivation: Currently the HTTP decoders can throw errors, but they will be ignored and lead to a simple EOF. That's not ideal: in most cases we should make a best-effort attempt to send a 4XX error code before we shut the client down. Modifications: Provided a new ChannelHandler that generates 400 errors when the HTTP decoder fails. Added a flag to automatically add that handler to the channel pipeline. Added the handler to the HTTP sample server. Enabled integration test 12. Result: Easier error handling for HTTP servers.
- Loading branch information
Showing
9 changed files
with
166 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
|
||
// Now pass the error on in case someone else wants to see it. | ||
ctx.fireErrorCaught(error) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
Tests/NIOHTTP1Tests/HTTPServerProtocolErrorHandlerTest+XCTest.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
] | ||
} | ||
} | ||
|
59 changes: 59 additions & 0 deletions
59
Tests/NIOHTTP1Tests/HTTPServerProtocolErrorHandlerTest.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// 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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters