/src/swift-nio/Sources/NIOHTTP1/HTTPServerProtocolErrorHandler.swift
Line | Count | Source |
1 | | //===----------------------------------------------------------------------===// |
2 | | // |
3 | | // This source file is part of the SwiftNIO open source project |
4 | | // |
5 | | // Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors |
6 | | // Licensed under Apache License v2.0 |
7 | | // |
8 | | // See LICENSE.txt for license information |
9 | | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors |
10 | | // |
11 | | // SPDX-License-Identifier: Apache-2.0 |
12 | | // |
13 | | //===----------------------------------------------------------------------===// |
14 | | import NIOCore |
15 | | |
16 | | /// A simple channel handler that catches errors emitted by parsing HTTP requests |
17 | | /// and sends 400 Bad Request responses. |
18 | | /// |
19 | | /// This channel handler provides the basic behaviour that the majority of simple HTTP |
20 | | /// servers want. This handler does not suppress the parser errors: it allows them to |
21 | | /// continue to pass through the pipeline so that other handlers (e.g. logging ones) can |
22 | | /// deal with the error. |
23 | | public final class HTTPServerProtocolErrorHandler: ChannelDuplexHandler, RemovableChannelHandler { |
24 | | public typealias InboundIn = HTTPServerRequestPart |
25 | | public typealias InboundOut = HTTPServerRequestPart |
26 | | public typealias OutboundIn = HTTPServerResponsePart |
27 | | public typealias OutboundOut = HTTPServerResponsePart |
28 | | |
29 | 0 | private var hasUnterminatedResponse: Bool = false |
30 | | |
31 | 0 | public init() {} |
32 | | |
33 | 0 | public func errorCaught(context: ChannelHandlerContext, error: Error) { |
34 | 0 | guard error is HTTPParserError else { |
35 | 0 | context.fireErrorCaught(error) |
36 | 0 | return |
37 | 0 | } |
38 | 0 |
|
39 | 0 | // Any HTTPParserError is automatically fatal, and we don't actually need (or want) to |
40 | 0 | // provide that error to the client: we just want to tell it that it screwed up and then |
41 | 0 | // let the rest of the pipeline shut the door in its face. However, we can only send an |
42 | 0 | // HTTP error response if another response hasn't started yet. |
43 | 0 | // |
44 | 0 | // A side note here: we cannot block or do any delayed work. ByteToMessageDecoder is going |
45 | 0 | // to come along and close the channel right after we return from this function. |
46 | 0 | if !self.hasUnterminatedResponse { |
47 | 0 | let headers = HTTPHeaders([("Connection", "close"), ("Content-Length", "0")]) |
48 | 0 | let head = HTTPResponseHead(version: .http1_1, status: .badRequest, headers: headers) |
49 | 0 | context.write(HTTPServerProtocolErrorHandler.wrapOutboundOut(.head(head)), promise: nil) |
50 | 0 | context.writeAndFlush(HTTPServerProtocolErrorHandler.wrapOutboundOut(.end(nil)), promise: nil) |
51 | 0 | } |
52 | 0 |
|
53 | 0 | // Now pass the error on in case someone else wants to see it. |
54 | 0 | context.fireErrorCaught(error) |
55 | 0 | } |
56 | | |
57 | 0 | public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) { |
58 | 0 | let res = HTTPServerProtocolErrorHandler.unwrapOutboundIn(data) |
59 | 0 | switch res { |
60 | 0 | case .head(let head) where head.isInformational: |
61 | 0 | precondition(!self.hasUnterminatedResponse) |
62 | 0 | case .head: |
63 | 0 | precondition(!self.hasUnterminatedResponse) |
64 | 0 | self.hasUnterminatedResponse = true |
65 | 0 | case .body: |
66 | 0 | precondition(self.hasUnterminatedResponse) |
67 | 0 | case .end: |
68 | 0 | precondition(self.hasUnterminatedResponse) |
69 | 0 | self.hasUnterminatedResponse = false |
70 | 0 | } |
71 | 0 | context.write(data, promise: promise) |
72 | 0 | } |
73 | | } |
74 | | |
75 | | @available(*, unavailable) |
76 | | extension HTTPServerProtocolErrorHandler: Sendable {} |