├── Sources ├── PerfectCHTTPParser │ ├── http_parser.h │ └── include │ │ ├── module.modulemap │ │ └── http_parser.h └── PerfectHTTPServer │ ├── HTTP2 │ ├── HTTP2PrefaceValidator.swift │ ├── HTTP2SessionSettings.swift │ ├── HTTP2.swift │ ├── HTTP2FrameWriter.swift │ ├── HTTP2Frame.swift │ ├── HTTP2FrameReader.swift │ ├── HTTP2Response.swift │ ├── HTTP2Request.swift │ ├── HTTP2Session.swift │ └── HTTP2Client.swift │ ├── HTTPMultiplexer.swift │ ├── HTTPContentCompression.swift │ ├── HTTP11 │ ├── HTTP11Response.swift │ └── HTTP11Request.swift │ ├── HTTPServerExConfig.swift │ ├── HTTPServer.swift │ └── HTTPServerEx.swift ├── .gitattributes ├── Tests ├── LinuxMain.swift └── PerfectHTTPServerTests │ └── PerfectHTTPServerTests.swift ├── Package.swift ├── .gitignore ├── LICENSE.zh_CN ├── README.md ├── README.zh_CN.md └── LICENSE /Sources/PerfectCHTTPParser/http_parser.h: -------------------------------------------------------------------------------- 1 | 2 | #include "include/http_parser.h" 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | Sources/CHTTPParser/* linguist-vendored 2 | Sources/CZlib/* linguist-vendored 3 | -------------------------------------------------------------------------------- /Sources/PerfectCHTTPParser/include/module.modulemap: -------------------------------------------------------------------------------- 1 | module PerfectCHTTPParser { 2 | header "http_parser.h" 3 | export * 4 | } 5 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PerfectHTTPServerTests 3 | 4 | XCTMain([ 5 | testCase(PerfectHTTPServerTests.allTests), 6 | ]) 7 | -------------------------------------------------------------------------------- /Sources/PerfectHTTPServer/HTTP2/HTTP2PrefaceValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTP2PrefaceValidator.swift 3 | // PerfectHTTPServer 4 | // 5 | // Created by Kyle Jessup on 2017-06-22. 6 | // 7 | // 8 | 9 | import PerfectNet 10 | 11 | let prefaceBytes = Array(http2ConnectionPreface.utf8) 12 | 13 | struct HTTP2PrefaceValidator { 14 | init(_ net: NetTCP, timeoutSeconds: Double, callback: @escaping () -> ()) { 15 | net.readBytesFully(count: prefaceBytes.count, timeoutSeconds: timeoutSeconds) { 16 | bytes in 17 | guard let b = bytes, b == prefaceBytes else { 18 | return net.close() 19 | } 20 | callback() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.1 2 | // Generated automatically by Perfect Assistant 2 3 | // Date: 2018-03-28 18:54:02 +0000 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "PerfectHTTPServer", 8 | products: [ 9 | .library(name: "PerfectHTTPServer", targets: ["PerfectHTTPServer"]) 10 | ], 11 | dependencies: [ 12 | .package(url: "https://github.com/PerfectlySoft/Perfect-Net.git", from: "3.1.2"), 13 | .package(url: "https://github.com/PerfectlySoft/Perfect-HTTP.git", from: "3.0.12"), 14 | .package(url: "https://github.com/PerfectlySoft/Perfect-CZlib-src.git", from: "0.0.0") 15 | ], 16 | targets: [ 17 | .target(name: "PerfectCHTTPParser", dependencies: []), 18 | .target(name: "PerfectHTTPServer", dependencies: ["PerfectCHTTPParser", "PerfectNet", "PerfectHTTP", "PerfectCZlib"]), 19 | .testTarget(name: "PerfectHTTPServerTests", dependencies: ["PerfectHTTPServer"]) 20 | ] 21 | ) 22 | -------------------------------------------------------------------------------- /Sources/PerfectHTTPServer/HTTP2/HTTP2SessionSettings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTP2SessionSettings.swift 3 | // PerfectHTTPServer 4 | // 5 | // Created by Kyle Jessup on 2017-06-20. 6 | // 7 | // 8 | 9 | typealias HTTP2Setting = UInt16 10 | 11 | let headerTableSize: HTTP2Setting = 0x1 12 | let enablePush: HTTP2Setting = 0x2 13 | let maxConcurrentStreams: HTTP2Setting = 0x3 14 | let initialWindowSize: HTTP2Setting = 0x4 15 | let maxFrameSize: HTTP2Setting = 0x5 16 | let maxHeaderListSize: HTTP2Setting = 0x6 17 | 18 | struct HTTP2SessionSettings { 19 | var headerTableSize: Int 20 | var enablePush: Bool 21 | var maxConcurrentStreams: Int 22 | var initialWindowSize: Int 23 | var maxFrameSize: Int 24 | var maxHeaderListSize: Int 25 | 26 | init() { 27 | headerTableSize = 4096 28 | enablePush = true 29 | maxConcurrentStreams = Int.max 30 | initialWindowSize = 65535 31 | maxFrameSize = 16384 32 | maxHeaderListSize = Int.max 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/PerfectHTTPServer/HTTP2/HTTP2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTP2.swift 3 | // PerfectLib 4 | // 5 | // Created by Kyle Jessup on 2016-02-18. 6 | // Copyright © 2016 PerfectlySoft. All rights reserved. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | let settingsHeaderTableSize: UInt16 = 0x1 21 | let settingsEnablePush: UInt16 = 0x2 22 | let settingsMaxConcurrentStreams: UInt16 = 0x3 23 | let settingsInitialWindowSize: UInt16 = 0x4 24 | let settingsMaxFrameSize: UInt16 = 0x5 25 | let settingsMaxHeaderListSize: UInt16 = 0x6 26 | 27 | let http2ConnectionPreface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" 28 | 29 | public var http2Debug = false 30 | 31 | enum HTTP2StreamState { 32 | case idle, reserved, open, halfClosed, closed 33 | } 34 | 35 | enum HTTP2Error: UInt32 { 36 | case noError = 0x0 37 | case protocolError = 0x1 38 | case internalError = 0x2 39 | case flowControlError = 0x3 40 | case settingsTimeout = 0x4 41 | case streamClosed = 0x5 42 | case frameSizeError = 0x6 43 | case refusedStream = 0x7 44 | case cancel = 0x8 45 | case compressionError = 0x9 46 | case connectError = 0xa 47 | case enhanceYourCalm = 0xb 48 | case inadequateSecurity = 0xc 49 | case http11Required = 0xd 50 | } 51 | 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | Package.resolved 40 | 41 | # CocoaPods 42 | # 43 | # We recommend against adding the Pods directory to your .gitignore. However 44 | # you should judge for yourself, the pros and cons are mentioned at: 45 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 46 | # 47 | # Pods/ 48 | 49 | # Carthage 50 | # 51 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 52 | # Carthage/Checkouts 53 | 54 | Carthage/Build 55 | 56 | # fastlane 57 | # 58 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 59 | # screenshots whenever they are needed. 60 | # For more information about the recommended setup visit: 61 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 62 | 63 | fastlane/report.xml 64 | fastlane/Preview.html 65 | fastlane/screenshots 66 | fastlane/test_output 67 | 68 | Packages/ 69 | *.xcodeproj/ 70 | .DS_Store 71 | -------------------------------------------------------------------------------- /Sources/PerfectHTTPServer/HTTP2/HTTP2FrameWriter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTP2FrameWriter.swift 3 | // PerfectHTTPServer 4 | // 5 | // Created by Kyle Jessup on 2017-06-21. 6 | // 7 | // 8 | 9 | import PerfectNet 10 | import PerfectThread 11 | import Dispatch 12 | 13 | class HTTP2FrameWriter { 14 | private let net: NetTCP 15 | private var enqueuedFrames = [HTTP2Frame]() 16 | private let enqueuedFramesLock = Threading.Event() 17 | private let writeFramesThread = DispatchQueue(label: "HTTP2FrameWriter") 18 | private weak var errorDelegate: HTTP2NetErrorDelegate? 19 | 20 | init(_ net: NetTCP, errorDelegate: HTTP2NetErrorDelegate) { 21 | self.net = net 22 | self.errorDelegate = errorDelegate 23 | startFrameWriting() 24 | } 25 | 26 | func waitUntilEmpty(_ callback: () -> ()) { 27 | while true { 28 | enqueuedFramesLock.lock() 29 | if enqueuedFrames.isEmpty { 30 | callback() 31 | } else { 32 | _ = enqueuedFramesLock.wait(seconds: 0.1) 33 | } 34 | enqueuedFramesLock.unlock() 35 | } 36 | } 37 | 38 | func enqueueFrame(_ frame: HTTP2Frame, highPriority: Bool = false) { 39 | enqueuedFramesLock.lock() 40 | if highPriority { 41 | enqueuedFrames.insert(frame, at: 0) 42 | } else { 43 | enqueuedFrames.append(frame) 44 | } 45 | enqueuedFramesLock.signal() 46 | enqueuedFramesLock.unlock() 47 | } 48 | 49 | func enqueueFrames(_ frames: [HTTP2Frame], highPriority: Bool = false) { 50 | enqueuedFramesLock.lock() 51 | if highPriority { 52 | enqueuedFrames = frames + enqueuedFrames 53 | } else { 54 | enqueuedFrames.append(contentsOf: frames) 55 | } 56 | enqueuedFramesLock.signal() 57 | enqueuedFramesLock.unlock() 58 | } 59 | 60 | private func startFrameWriting() { 61 | guard net.isValid else { 62 | return 63 | } 64 | writeFramesThread.async { 65 | self.enqueuedFramesLock.lock() 66 | if self.enqueuedFrames.isEmpty { 67 | _ = self.enqueuedFramesLock.wait(seconds: 0.5) 68 | } 69 | guard let frame = self.enqueuedFrames.first else { 70 | self.enqueuedFramesLock.unlock() 71 | return self.startFrameWriting() 72 | } 73 | self.enqueuedFrames.remove(at: 0) 74 | self.enqueuedFramesLock.unlock() 75 | frame.willSendCallback?() 76 | let bytes = frame.headerBytes() + (frame.payload ?? []) 77 | self.net.write(bytes: bytes) { 78 | wrote in 79 | guard wrote == bytes.count else { 80 | frame.sentCallback?(false) 81 | self.signalNetworkError() 82 | return 83 | } 84 | frame.sentCallback?(true) 85 | self.startFrameWriting() 86 | } 87 | } 88 | } 89 | 90 | func signalNetworkError() { 91 | enqueuedFramesLock.lock() 92 | enqueuedFrames.removeAll() 93 | enqueuedFramesLock.unlock() 94 | errorDelegate?.networkShutdown() 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /LICENSE.zh_CN: -------------------------------------------------------------------------------- 1 | Apache许可证 2 | 2.0版 2004年1月 3 | http://www.apache.org/licenses/ 4 | 5 | 关于使用、复制和分发的条款 6 | 7 | 定义 8 | "许可证"是指根据本文件第1到第9部分关于使用、复制和分发的条款。 9 | 10 | "许可证颁发者"是指版权所有者或者由版权所有者授权许可证的实体。 11 | 12 | "法律实体"是指实施实体和进行控制的所有其它实体受该实体控制,或者受该实体集中控制。根据此定义,"控制"是指(i)让无论是否签订协议的上述实体,进行指导或管理的直接权利或间接权利,或者(ii)拥有百分之五十(50%)或以上已发行股票的所有者,或者(iii)上述实体的实权所有者。 13 | 14 | "用户"(或"用户的")是指行使本许可证所授予权限的个人或法律实体。 15 | 16 | "源程序"形式是指对包括但不限于软件源代码、文件源程序和配置文件进行修改的首选形式。 17 | 18 | "目标"形式是指对源程序形式进行机械转换或翻译的任何形式,包括但不限于对编译的目标代码,生成的文件以及转换为其它媒体类型。 19 | 20 | "作品"是指根据本许可证所制作的源程序形式或目标形式的著作,在著作中包含的或附加的版权通知(在下面附录中提供了一个示例)。 21 | 22 | "衍生作品"是指基于作品(或从作品衍生而来)的源程序形式或目标形式的任何作品,以及编辑修订、注释、详细描述或其它修订等构成原创著作作品的整体。根据本许可证,衍生作品不得包括与作品及其衍生作品分离之作品,或仅与作品及其衍生作品的接口相链接(或按名称结合)之作品。 23 | 24 | "贡献"是指任何著作作品,包括作品的原始版本和对该作品或衍生作品所做的任何修订或补充,意在提交给许可证颁发者以让版权所有者或代表版权所有者的授权个人或法律实体包含在其作品中。根据此定义,"提交"一词表示发送给许可证颁发者或其代表人,任何电子的、口头的或书面的交流信息形式,包括但不限于在由许可证颁发者或者代表其管理的电子邮件清单、源代码控制系统、以及发布跟踪系统上为讨论和提高作品的交流,但不包括由版权所有者以书面形式明显标注或指定为"非贡献"的交流活动。 25 | 26 | "贡献者"是指许可证颁发者和代表从许可证颁发者接受之贡献的并随后包含在作品之贡献中的任何个人或法律实体。 27 | 28 | 版权许可证的授予。根据本许可证的条款,每个贡献者授予用户永久性的、全球性的、非专有性的、免费的、无版权费的、不可撤销的版权许可证以源程序形式或目标形式复制、准备衍生作品、公开显示、公开执行、授予分许可证、以及分发作品和这样的衍生作品。 29 | 专利许可证的授予。根据本许可证的条款,每个贡献者授予用户永久性的、全球性的、非专有性的、免费的、无版权费的、不可撤销的(除在本部分进行说明)专利许可证对作品进行制作、让人制作、使用、提供销售、销售、进口和其它转让,且这样的许可证仅适用于在所递交作品的贡献中因可由单一的或多个这样的贡献者授予而必须侵犯的申请专利。如果用户对任何实体针对作品或作品中所涉及贡献提出因直接性或贡献性专利侵权而提起专利法律诉讼(包括交互诉讼请求或反索赔),那么根据本许可证,授予用户针对作品的任何专利许可证将在提起上述诉讼之日起终止。 30 | 重新分发。用户可在任何媒介中复制和分发作品或衍生作品之副本,无论是否修订,还是以源程序形式或目标形式,条件是用户需满足下列条款: 31 | 用户必须为作品或衍生作品的任何其他接收者提供本许可证的副本;并且 32 | 用户必须让任何修改过的文件附带明显的通知,声明用户已更改文件;并且 33 | 用户必须从作品的源程序形式中保留衍生作品源程序形式的用户所分发的所有版权、专利、商标和属性通知,但不包括不属于衍生作品任何部分的类似通知;并且 34 | 如果作品将"通知"文本文件包括为其分发作品的一部分,那么用户分发的任何衍生作品中须至少在下列地方之一包括,在这样的通知文件中所包含的属性通知的可读副本,但不包括那些不属于衍生作品任何部分的通知:在作为衍生作品一部分而分发的通知文本文件中;如果与衍生作品一起提供则在源程序形式或文件中;或者通常作为第三方通知出现的时候和地方,在衍生作品中产生的画面中。通知文件的内容仅供信息提供,并未对许可证进行修改。用户可在其分发的衍生作品中在作品的通知文本后或作为附录添加自己的属性通知,条件是附加的属性通知不得构成修改本许可证。 35 | 用户可以为自身所做出的修订添加自己的版权声明并可对自身所做出修订内容或为这样的衍生作品作为整体的使用、复制或分发提供附加或不同的条款,条件是用户对作品的使用、复制和分发必须符合本许可证中声明的条款。 36 | 37 | 贡献的提交。除非用户明确声明,在作品中由用户向许可证颁发者的提交若要包含在贡献中,必须在无任何附加条款下符合本许可证的条款。尽管上面如此规定,执行许可证颁发者有关贡献的条款时,任何情况下均不得替代或修改任何单独许可证协议的条款。 38 | 商标。本许可证并未授予用户使用许可证颁发者的商号、商标、服务标记或产品名称,除非将这些名称用于合理性和惯例性描述作品起源和复制通知文件的内容时。 39 | 保证否认条款。除非因适用法律需要或书面同意,许可证颁发者以"按原样"基础提供作品(并且每个贡献者提供其贡献),无任何明示的或暗示的保证或条件,包括但不限于关于所有权、不侵权、商品适销性、或适用性的保证或条件。用户仅对使用或重新分发作品的正确性负责,并需承担根据本许可证行使权限时的任何风险。 40 | 责任限制条款。在任何情况下并根据任何法律,无论是因侵权(包括过失)或根据合同,还是其它原因,除非根据适用法律需要(例如故意行为和重大过失行为)或经书面同意,即使贡献者事先已被告知发生损害的可能性,任何贡献者不就用户因使用本许可证或不能使用或无法使用作品(包括但不限于商誉损失、停工、计算机失效或故障,或任何商业损坏或损失)而造成的损失,包括直接的、非直接的、特殊的、意外的或间接的字符损坏而负责。 41 | 接受保证或附加责任。重新分发作品或及其衍生作品时,用户可选择提供或为符合本许可证承担之支持、担保、赔偿或其它职责义务和/或权利而收取费用。但是,在承担上述义务时,用户只可代表用户本身和用户本身责任来执行,无需代表任何其它贡献者,并且用户仅可保证、防护并保持每个贡献者不受任何因此而产生的责任或对因用户自身承担这样的保证或附加责任而对这样的贡献者所提出的索赔。 42 | 条款结束 43 | 44 | 附录:如何向用户作品中应用Apache许可证。 45 | 46 | 若要向用户作品应用Apache许可证,请附加下列样本通知,将括号"[]"中的字段以用户自身的区分信息来替换(但不包括括号)。文本必须以文件格式适当的注释句法包含在其中。另外建议将文件名或类别名以及目的说明包含在相同的"打印页"上作为版权通知,以更加容易的区分出第三方档案。 47 | 48 | 版权所有[yyyy][版权所有者的名称] 49 | 50 | 根据2.0版本Apache许可证("许可证")授权; 51 | 根据本许可证,用户可以不使用此文件。 52 | 用户可从下列网址获得许可证副本: 53 | 54 | http://www.apache.org/licenses/LICENSE-2.0 55 | 56 | 除非因适用法律需要或书面同意, 57 | 根据许可证分发的软件是基于"按原样"基础提供, 58 | 无任何明示的或暗示的保证或条件。 59 | 详见根据许可证许可下,特定语言的管辖权限和限制。 60 | -------------------------------------------------------------------------------- /Sources/PerfectHTTPServer/HTTP2/HTTP2Frame.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTP2Frame.swift 3 | // PerfectHTTPServer 4 | // 5 | // Created by Kyle Jessup on 2017-06-20. 6 | 7 | enum HTTP2FrameType: UInt8 { 8 | case data = 0x0 9 | case headers = 0x1 10 | case priority = 0x2 11 | case cancelStream = 0x3 12 | case settings = 0x4 13 | case pushPromise = 0x5 14 | case ping = 0x6 15 | case goAway = 0x7 16 | case windowUpdate = 0x8 17 | case continuation = 0x9 18 | 19 | var description: String { 20 | switch self { 21 | case .data: return "HTTP2_DATA" 22 | case .headers: return "HTTP2_HEADERS" 23 | case .priority: return "HTTP2_PRIORITY" 24 | case .cancelStream: return "HTTP2_RST_STREAM" 25 | case .settings: return "HTTP2_SETTINGS" 26 | case .pushPromise: return "HTTP2_PUSH_PROMISE" 27 | case .ping: return "HTTP2_PING" 28 | case .goAway: return "HTTP2_GOAWAY" 29 | case .windowUpdate: return "HTTP2_WINDOW_UPDATE" 30 | case .continuation: return "HTTP2_CONTINUATION" 31 | } 32 | } 33 | } 34 | 35 | typealias HTTP2FrameFlag = UInt8 36 | 37 | let flagEndStream: HTTP2FrameFlag = 0x1 38 | let flagEndHeaders: HTTP2FrameFlag = 0x4 39 | let flagPadded: HTTP2FrameFlag = 0x8 40 | let flagPriority: HTTP2FrameFlag = 0x20 41 | let flagSettingsAck: HTTP2FrameFlag = 0x1 42 | let flagPingAck: HTTP2FrameFlag = 0x1 43 | 44 | struct HTTP2Frame { 45 | let length: UInt32 // 24-bit 46 | let type: HTTP2FrameType 47 | let flags: UInt8 48 | let streamId: UInt32 // 31-bit 49 | var payload: [UInt8]? 50 | var sentCallback: ((Bool) -> ())? = nil 51 | var willSendCallback: (() -> ())? = nil 52 | 53 | // Deprecate this 54 | init(length: UInt32, 55 | type: UInt8, 56 | flags: UInt8 = 0, 57 | streamId: UInt32 = 0, 58 | payload: [UInt8]? = nil) { 59 | self.length = length 60 | self.type = HTTP2FrameType(rawValue: type)! 61 | self.flags = flags 62 | self.streamId = streamId 63 | self.payload = payload 64 | } 65 | 66 | init(type: HTTP2FrameType, 67 | flags: UInt8 = 0, 68 | streamId: UInt32 = 0, 69 | payload: [UInt8]? = nil) { 70 | self.length = UInt32(payload?.count ?? 0) 71 | self.type = type 72 | self.flags = flags 73 | self.streamId = streamId 74 | self.payload = payload 75 | } 76 | 77 | var typeStr: String { 78 | return type.description 79 | } 80 | 81 | var flagsStr: String { 82 | var s = "" 83 | if flags == 0 { 84 | s.append("NO FLAGS") 85 | } 86 | if (flags & flagEndStream) != 0 { 87 | s.append(" +HTTP2_END_STREAM") 88 | } 89 | if (flags & flagEndHeaders) != 0 { 90 | s.append(" +HTTP2_END_HEADERS") 91 | } 92 | return s 93 | } 94 | 95 | func headerBytes() -> [UInt8] { 96 | var data = [UInt8]() 97 | 98 | let l = length.hostToNet >> 8 99 | data.append(UInt8(l & 0xFF)) 100 | data.append(UInt8((l >> 8) & 0xFF)) 101 | data.append(UInt8((l >> 16) & 0xFF)) 102 | 103 | data.append(type.rawValue) 104 | data.append(flags) 105 | 106 | let s = streamId.hostToNet 107 | data.append(UInt8(s & 0xFF)) 108 | data.append(UInt8((s >> 8) & 0xFF)) 109 | data.append(UInt8((s >> 16) & 0xFF)) 110 | data.append(UInt8((s >> 24) & 0xFF)) 111 | return data 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Sources/PerfectHTTPServer/HTTP2/HTTP2FrameReader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTP2FrameReader.swift 3 | // PerfectHTTPServer 4 | // 5 | // Created by Kyle Jessup on 2017-06-20. 6 | // 7 | // 8 | 9 | import PerfectNet 10 | import PerfectThread 11 | import Dispatch 12 | 13 | enum HTTP2FrameSubscriptionType: UInt8 { 14 | case headers = 0x1 15 | case settings = 0x4 16 | case pushPromise = 0x5 17 | case ping = 0x6 18 | case goAway = 0x7 19 | case windowUpdate = 0x8 20 | case sessionTimeout = 0xFF 21 | } 22 | 23 | class HTTP2FrameReader { 24 | private let net: NetTCP 25 | private weak var errorDelegate: HTTP2NetErrorDelegate? 26 | private weak var frameReceiver: HTTP2FrameReceiver? 27 | 28 | private var readFrames = [HTTP2Frame]() 29 | private let readFramesEvent = Threading.Event() 30 | 31 | // no frames in queue and no frames read 32 | var noFrameReadTimeout = 60.0*5.0 33 | private let readFramesThread = DispatchQueue(label: "HTTP2FrameReader") 34 | 35 | private let processFramesThread = DispatchQueue(label: "HTTP2FrameProcessor") 36 | 37 | private var shouldTimeout: Bool { 38 | readFramesEvent.lock() 39 | defer { 40 | readFramesEvent.unlock() 41 | } 42 | return readFrames.isEmpty 43 | } 44 | 45 | init(_ net: NetTCP, frameReceiver: HTTP2FrameReceiver, errorDelegate: HTTP2NetErrorDelegate) { 46 | self.net = net 47 | self.frameReceiver = frameReceiver 48 | self.errorDelegate = errorDelegate 49 | startReadFrames() 50 | } 51 | 52 | private func startReadFrames() { 53 | readFramesThread.async { 54 | self.readHTTP2Frame { 55 | frame in 56 | if let frame = frame, let frameReceiver = self.frameReceiver { 57 | frameReceiver.receiveFrame(frame) 58 | self.startReadFrames() // evaluate if this should just be a loop 59 | } // else we are dead. stop reading 60 | } 61 | } 62 | } 63 | 64 | private func readHTTP2Frame(callback: @escaping (HTTP2Frame?) -> ()) { 65 | let net = self.net 66 | net.readBytesFully(count: 9, timeoutSeconds: noFrameReadTimeout) { 67 | bytes in 68 | if let b = bytes { 69 | var header = self.bytesToHeader(b) 70 | let length = Int(header.length) 71 | if length > 0 { 72 | net.readBytesFully(count: length, timeoutSeconds: self.noFrameReadTimeout) { 73 | bytes in 74 | guard let bytes = bytes, bytes.count == length else { 75 | callback(nil) 76 | self.errorDelegate?.networkShutdown() 77 | return 78 | } 79 | header.payload = bytes 80 | callback(header) 81 | } 82 | } else { 83 | callback(header) 84 | } 85 | } else { 86 | callback(nil) 87 | self.errorDelegate?.networkShutdown() 88 | } 89 | } 90 | } 91 | 92 | private func bytesToHeader(_ b: [UInt8]) -> HTTP2Frame { 93 | let payloadLength = (UInt32(b[0]) << 16) + (UInt32(b[1]) << 8) + UInt32(b[2]) 94 | let type = b[3] 95 | let flags = b[4] 96 | var sid = UInt32(b[5]) 97 | sid <<= 8 98 | sid += UInt32(b[6]) 99 | sid <<= 8 100 | sid += UInt32(b[7]) 101 | sid <<= 8 102 | sid += UInt32(b[8]) 103 | sid &= ~0x80000000 104 | return HTTP2Frame(length: payloadLength, type: type, flags: flags, streamId: sid, payload: nil) 105 | } 106 | } 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /Sources/PerfectHTTPServer/HTTPMultiplexer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPMultiplexer.swift 3 | // HTTP2Test 4 | // 5 | // Created by Kyle Jessup on 2017-06-29. 6 | // 7 | // 8 | 9 | import PerfectNet 10 | import PerfectLib 11 | #if os(Linux) 12 | import SwiftGlibc 13 | import LinuxBridge 14 | #else 15 | import Darwin 16 | #endif 17 | 18 | class HTTPMultiplexer: ServerInstance { 19 | var synthServer = HTTPServer.Server() 20 | var servers: [String:HTTPServer] = [:] 21 | var port: UInt16 = 0 22 | var address: String = "" 23 | var net: NetTCPSSL? 24 | 25 | func addServer(_ server: HTTPServer) throws { 26 | let name = server.serverName.lowercased() 27 | if let _ = servers[name] { 28 | throw HTTPServer.LaunchFailure(message: "Server name \"\(name)\" already exists. Each server listening on the same port must have a unique server name.", configuration: synthServer) 29 | } 30 | guard nil != server.ssl else { 31 | throw HTTPServer.LaunchFailure(message: "Server name \"\(name)\" must be a secure server.", configuration: synthServer) 32 | } 33 | if servers.count == 0 { 34 | // first server 35 | port = server.serverPort 36 | address = server.serverAddress 37 | synthServer = .init(name: "HTTPMultiplexer - \(address):\(port)", 38 | address: address, 39 | port: Int(port), 40 | routes: .init()) 41 | } else { 42 | guard server.serverPort == port && server.serverAddress == address else { 43 | throw HTTPServer.LaunchFailure(message: "Server name \"\(name)\" does not have same address or port as rest of group. Want \(address):\(port) got \(server.serverAddress):\(server.serverPort)", configuration: synthServer) 44 | } 45 | } 46 | servers[name] = server 47 | } 48 | 49 | func start() throws { 50 | guard let net = self.net else { 51 | throw HTTPServer.LaunchFailure(message: "Server not bound.", configuration: synthServer) 52 | } 53 | for (name, server) in servers { 54 | guard let (cert, key) = server.ssl else { 55 | throw HTTPServer.LaunchFailure(message: "Server name \"\(name)\" must have certificate file.", configuration: synthServer) 56 | } 57 | if let verifyMode = server.certVerifyMode, 58 | let cert = server.caCert, 59 | verifyMode != .sslVerifyNone { 60 | 61 | guard net.setClientCA(path: cert, verifyMode: verifyMode, forHost: name) else { 62 | let code = Int32(net.errorCode()) 63 | throw HTTPServer.LaunchFailure(message: "Error setting clientCA: \(cert) \(net.errorStr(forCode: code))", configuration: synthServer) 64 | } 65 | } 66 | guard net.useCertificateChainFile(cert: cert, forHost: name) else { 67 | let code = Int32(net.errorCode()) 68 | throw HTTPServer.LaunchFailure(message: "Error setting certificate chain file: \(cert) \(net.errorStr(forCode: code))", configuration: synthServer) 69 | } 70 | guard net.usePrivateKeyFile(cert: key, forHost: name) else { 71 | let code = Int32(net.errorCode()) 72 | throw HTTPServer.LaunchFailure(message: "Error setting private key file: \(key) \(net.errorStr(forCode: code))", configuration: synthServer) 73 | } 74 | guard net.checkPrivateKey(forHost: name) else { 75 | let code = Int32(net.errorCode()) 76 | throw HTTPServer.LaunchFailure(message: "Error validating private key file: \(net.errorStr(forCode: code))", configuration: synthServer) 77 | } 78 | net.enableALPN(protocols: server.alpnSupport.map { $0.rawValue }, forHost: name) 79 | } 80 | net.listen() 81 | var flag = 1 82 | _ = setsockopt(net.fd.fd, Int32(IPPROTO_TCP), TCP_NODELAY, &flag, UInt32(MemoryLayout.size)) 83 | 84 | defer { net.close() } 85 | 86 | Log.info(message: "Starting multi-server for \(servers.count) hosts on \(self.address):\(self.port)") 87 | 88 | net.forEachAccept { 89 | net in 90 | guard let net = net as? NetTCPSSL else { 91 | return 92 | } 93 | let sn = net.serverNameIdentified 94 | if nil != sn, let server = self.servers[sn!.lowercased()] { 95 | server.accepted(net: net) 96 | } else if let server = self.servers["*"] { 97 | server.accepted(net: net) 98 | } else { 99 | net.close() 100 | Log.warning(message: "Client wanted host \(sn ?? "*"). Server not found.") 101 | } 102 | } 103 | } 104 | 105 | func bind() throws { 106 | guard nil == self.net else { 107 | throw HTTPServer.LaunchFailure(message: "Server already bound.", configuration: synthServer) 108 | } 109 | guard !servers.isEmpty else { 110 | throw HTTPServer.LaunchFailure(message: "No servers have been added.", configuration: synthServer) 111 | } 112 | 113 | let net = NetTCPSSL() 114 | try net.bind(port: port, address: address) 115 | self.net = net 116 | } 117 | 118 | func stop() { 119 | net?.shutdown() 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Perfect-HTTPServer [简体中文](README.zh_CN.MD) 2 | 3 |

4 | 5 | Get Involed with Perfect! 6 | 7 |

8 | 9 |

10 | 11 | Star Perfect On Github 12 | 13 | 14 | Stack Overflow 15 | 16 | 17 | Follow Perfect on Twitter 18 | 19 | 20 | Join the Perfect Slack 21 | 22 |

23 | 24 |

25 | 26 | Swift 4.0 27 | 28 | 29 | Platforms OS X | Linux 30 | 31 | 32 | License Apache 33 | 34 | 35 | PerfectlySoft Twitter 36 | 37 | 38 | Slack Status 39 | 40 |

41 | 42 | HTTP Server for Perfect 43 | 44 | This repository contains the main HTTP 1.1 & HTTP/2 server. 45 | 46 | If you are using this server for your Perfect Server-Side Swift project then this will be the main dependency for your project. 47 | 48 | ```swift 49 | .package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", from: "3.0.0") 50 | ``` 51 | 52 | If you are starting out with Perfect look at the main [Perfect](https://github.com/PerfectlySoft/Perfect) repository for details. 53 | 54 | If you are beginning a new project with Perfect look at the [PerfectTemplate](https://github.com/PerfectlySoft/PerfectTemplate) project for starter instructions. 55 | 56 | When building on Linux, OpenSSL 1.0.2+ is required for this package. On Ubuntu 14 or some Debian distributions you will need to update your OpenSSL before this package will build. 57 | 58 | ### HTTP/2 59 | 60 | As of version 2.2.6, experimental HTTP/2 server support is available but is disabled by default. To enable HTTP/2, add "alpnSupport" to your server's TLSConfiguration struct: 61 | 62 | ```swift 63 | let securePort = 8181 64 | let tls = TLSConfiguration(certPath: "my.cert.pem", 65 | alpnSupport: [.http2, .http11]) 66 | 67 | try HTTPServer.launch( 68 | .secureServer(tls, 69 | name: "servername", 70 | port: securePort, 71 | routes: secureRoutes)) 72 | ``` 73 | 74 | This will enable HTTP/2 to be used over secure connections if the client supports it. If the client does not support HTTP/2 then the server will use HTTP 1.x. HTTP/2 support is only offered over secure connections. Setting the global `http2Debug` variable to true will have the HTTP/2 server print much debugging information to the console while in use. 75 | 76 | Please contact us if you experience any problems or incompatibilities while experimenting with HTTP/2 support. 77 | 78 | ## QuickStart 79 | 80 | Add the dependency to your Package.swift 81 | 82 | `.package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", from: "3.0.0")` 83 | 84 | In your app, launch one or more servers. 85 | ``` 86 | // start a single server serving static files 87 | try HTTPServer.launch(name: "localhost", port: 8080, documentRoot: "/path/to/webroot") 88 | 89 | // start two servers. have one serve static files and the other handle API requests 90 | let apiRoutes = Route(method: .get, uri: "/foo/bar", handler: { 91 | req, resp in 92 | //do stuff 93 | }) 94 | try HTTPServer.launch( 95 | .server(name: "localhost", port: 8080, documentRoot: "/path/to/webroot"), 96 | .server(name: "localhost", port: 8181, routes: [apiRoutes])) 97 | 98 | // start a single server which handles API and static files 99 | try HTTPServer.launch(name: "localhost", port: 8080, routes: [ 100 | Route(method: .get, uri: "/foo/bar", handler: { 101 | req, resp in 102 | //do stuff 103 | }), 104 | Route(method: .get, uri: "/foo/bar", handler: 105 | HTTPHandler.staticFiles(documentRoot: "/path/to/webroot")) 106 | ]) 107 | 108 | let apiRoutes = Route(method: .get, uri: "/foo/bar", handler: { 109 | req, resp in 110 | //do stuff 111 | }) 112 | // start a secure server 113 | try HTTPServer.launch(.secureServer(TLSConfiguration(certPath: "/path/to/cert"), name: "localhost", port: 8080, routes: [apiRoutes])) 114 | ``` 115 | 116 | ## Documentation 117 | 118 | For further information, please visit [perfect.org](http://www.perfect.org/docs/HTTPServer.html). 119 | 120 | -------------------------------------------------------------------------------- /README.zh_CN.md: -------------------------------------------------------------------------------- 1 | # Perfect-HTTPServer 服务器[English](README.md) 2 | 3 |

4 | 5 | Get Involed with Perfect! 6 | 7 |

8 | 9 |

10 | 11 | Star Perfect On Github 12 | 13 | 14 | Stack Overflow 15 | 16 | 17 | Follow Perfect on Twitter 18 | 19 | 20 | Join the Perfect Slack 21 | 22 |

23 | 24 |

25 | 26 | Swift 3.0 27 | 28 | 29 | Platforms OS X | Linux 30 | 31 | 32 | License Apache 33 | 34 | 35 | PerfectlySoft Twitter 36 | 37 | 38 | Slack Status 39 | 40 |

41 | 42 | Perfect软件框架:HTTP 1.1 服务器 43 | 44 | 本代码资源库包括了一个 HTTP 1.1 服务器,并包含了一些 HTTP 2.0 的基础支持。 45 | 46 | 如果您在使用 Perfect 开发 Swift Web服务器应用,则请将下列依存关系增加到您的 Package.swift 程序中: 47 | 48 | ``` swift 49 | .Package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", majorVersion: 2, minor: 0) 50 | ``` 51 | 52 | 如果您刚刚开始使用 Perfect,请不妨先看一下官网主页 [Perfect](https://github.com/PerfectlySoft/Perfect)。 53 | 54 | 另外,PerfectTemplate是一个非常好的 HTTP 服务器模板项目,请查看这里:[PerfectTemplate](https://github.com/PerfectlySoft/PerfectTemplate)。 55 | 56 | ## 快速上手 57 | 58 | 请使用下列命令快速开始项目: 59 | 60 | ``` swift 61 | git clone https://github.com/PerfectlySoft/PerfectTemplate.git 62 | cd PerfectTemplate 63 | ``` 64 | 然后请检查 Package.swift 中的依存关系: 65 | 66 | ``` swift 67 | let package = Package( 68 | name: "PerfectTemplate", 69 | targets: [], 70 | dependencies: [ 71 | .Package(url:"https://github.com/PerfectlySoft/Perfect-HTTPServer.git", majorVersion: 2, minor: 0) 72 | ] 73 | ) 74 | ``` 75 | 76 | Package.swift 文件中 package 对象的名字属性决定了最终项目的可执行目标。 77 | 78 | 创建 Xcode 项目: 79 | ``` swift 80 | swift package generate-xcodeproj 81 | ``` 82 | 83 | SPM 软件包管理器会下载所有依存关系并创建为一个 Xcode 工程文件。 84 | 85 | 创建完成之后,您可以用 Xcode 打开这个名为 PerfectTemplate.xcodeproj 的工程文件。 86 | 87 | 打开之后即可编译并运行服务器,端口为 8181. 88 | 89 | 注意:如果您修改了Package.swift文件,必须重新运行``` swift package generate-xcodeproj ```命令,否则可能无法在Xcode中编译;而之前在Xcode中修改的项目配置都会被覆盖。 90 | 91 | ## 创建 HTTP 服务器并注册 webroot 根目录 92 | 93 | 打开 main.swift 并进行如下修改: 94 | 95 | 注意 ``` import ``` 语句,用于引用函数库 96 | 97 | ``` swift 98 | import PerfectLib 99 | import PerfectHTTP 100 | import PerfectHTTPServer 101 | ``` 102 | 103 | 创建 HTTP 实例并增加路由: 104 | 105 | ``` swift 106 | // 创建 HTTP 服务器 107 | let server = HTTPServer() 108 | 109 | // 注册路由并登记路由处理句柄 110 | var routes = Routes() 111 | routes.add(method: .get, uri: "/", handler: { 112 | request, response in 113 | response.appendBody(string: "你好,世界!你好,世界!") 114 | response.completed() 115 | } 116 | ) 117 | 118 | // 将路由信息写入服务器 119 | server.addRoutes(routes) 120 | ``` 121 | 122 | 检查服务器配置: 123 | ``` swift 124 | // 设置监听端口为8181 125 | server.serverPort = 8181 126 | 127 | // 设置文档根目录 128 | // 这是可选的,如果您有静态文件要使用,请按照下列步骤编程: 129 | // 静态文件设置完成后会将所有默认路由通配符 /** 都指向静态文件 130 | server.documentRoot = "./webroot" 131 | ``` 132 | 133 | 从命令行和配置文件中配置服务器 134 | 从服务器运行 --help 选项就可以看到受支持的配置清单。 135 | 您可以根据需要自行决定配置的内容。 136 | 137 | 配置文件 arguments.swift 和命令行的作用是一样的。在真正启动服务器之前,请调用下面的代码。 138 | 139 | ``` swift 140 | configureServer(server) 141 | ``` 142 | 143 | 下列操作会阻塞服务器进程。请注意 ``` server.start() ```之后的任何程序都是无法执行的。 144 | ``` swift 145 | do { 146 | // 启动 HTTP 服务器 147 | try server.start() 148 | 149 | } catch PerfectError.networkError(let err, let msg) { 150 | print("网络异常: \(err) \(msg)") 151 | } 152 | ``` 153 | 154 | 在 Xcode 中,请在运行服务器之前选择可执行文件的运行方式(executable scheme),而且这个选择操作在每次您从命令行生成 Xcode 项目时都必须执行。选择运行方式之后,还需要选择工作目录,并将工作目录设置为项目根目录,否则服务器无法正确调用上面的文档根目录。请注意每次从命令行生成 Xcode 项目后,这些操作都需要重新执行一遍。 155 | 156 | 上述设置完成之后就可以编译并运行服务器了。用浏览器测试 ```http://0.0.0.0:8181``` 应该可以看到结果。服务器还支持您的静态文件用作页面。 157 | 158 | 159 | 160 | ======= 161 | ## 问题汇报 162 | 163 | 我们现在正在转移至 JIRA 工作流处理系统,因此 github 上的问题汇报功能就被关闭了。 164 | 165 | 如果您有任何意见或建议,请在我们的JIRA管理平台上报告 [http://jira.perfect.org:8080/servicedesk/customer/portal/1](http://jira.perfect.org:8080/servicedesk/customer/portal/1)。 166 | 167 | 目前本项目详细的问题清单请参考 [http://jira.perfect.org:8080/projects/ISS/issues](http://jira.perfect.org:8080/projects/ISS/issues) 168 | 169 | -------------------------------------------------------------------------------- /Sources/PerfectHTTPServer/HTTPContentCompression.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPContentCompression.swift 3 | // PerfectHTTPServer 4 | // Copyright (C) 2016 PerfectlySoft, Inc. 5 | // 6 | //===----------------------------------------------------------------------===// 7 | // 8 | // This source file is part of the Perfect.org open source project 9 | // 10 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 11 | // Licensed under Apache License v2.0 12 | // 13 | // See http://perfect.org/licensing.html for license information 14 | // 15 | //===----------------------------------------------------------------------===// 16 | // 17 | 18 | import PerfectHTTP 19 | import PerfectCZlib 20 | 21 | class ZlibStream { 22 | var stream = z_stream() 23 | var closed = false 24 | 25 | init?() { 26 | stream.zalloc = nil 27 | stream.zfree = nil 28 | stream.opaque = nil 29 | 30 | let err = deflateInit_(&stream, Z_DEFAULT_COMPRESSION, ZLIB_VERSION, Int32(MemoryLayout.size)) 31 | guard Z_OK == err else { 32 | return nil 33 | } 34 | } 35 | 36 | deinit { 37 | if !closed { 38 | close() 39 | } 40 | } 41 | 42 | func compress(_ bytes: [UInt8], flush: Bool) -> [UInt8] { 43 | if bytes.isEmpty && !flush { 44 | return [] 45 | } 46 | let needed = Int(compressBound(UInt(bytes.count))) 47 | let dest = UnsafeMutablePointer.allocate(capacity: needed) 48 | defer { 49 | dest.deallocate() 50 | } 51 | if !bytes.isEmpty { 52 | stream.next_in = UnsafeMutablePointer(mutating: bytes) 53 | stream.avail_in = uInt(bytes.count) 54 | } else { 55 | stream.next_in = nil 56 | stream.avail_in = 0 57 | } 58 | var out = [UInt8]() 59 | repeat { 60 | stream.next_out = dest 61 | stream.avail_out = uInt(needed) 62 | let err = deflate(&stream, flush ? Z_FINISH : Z_NO_FLUSH) 63 | guard err != Z_STREAM_ERROR else { 64 | break 65 | } 66 | let have = uInt(needed) - stream.avail_out 67 | let b2 = UnsafeRawBufferPointer(start: dest, count: Int(have)) 68 | out.append(contentsOf: b2.map { $0 }) 69 | } while stream.avail_out == 0 70 | return out 71 | } 72 | 73 | func close() { 74 | if !closed { 75 | closed = true 76 | deflateEnd(&stream) 77 | } 78 | } 79 | } 80 | 81 | private let responseMinSizeNoCompression = 24 82 | 83 | public extension HTTPFilter { 84 | /// Response filter which provides content compression. 85 | /// Mime types which will be encoded or ignored can be specified with the "compressTypes" and 86 | /// "ignoreTypes" keys, respectively. The values for these keys should be an array of String 87 | /// containing either the full mime type or the the main type with a * wildcard. e.g. text/* 88 | /// The default values for the compressTypes key are: "*/*" 89 | /// The default values for the ignoreTypes key are: "image/*", "video/*", "audio/*" 90 | static func contentCompression(data: [String:Any]) throws -> HTTPResponseFilter { 91 | let inCompressTypes = data["compressTypes"] as? [String] ?? ["*/*"] 92 | let inIgnoreTypes = data["ignoreTypes"] as? [String] ?? ["image/*", "video/*", "audio/*"] 93 | 94 | struct CompressResponse: HTTPResponseFilter { 95 | let compressTypes: [MimeType] 96 | let ignoreTypes: [MimeType] 97 | 98 | func filterHeaders(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 99 | let req = response.request 100 | if case .head = req.method { 101 | return callback(.continue) 102 | } 103 | if case .notModified = response.status { 104 | return callback(.continue) 105 | } 106 | if !response.isStreaming && response.bodyBytes.count < responseMinSizeNoCompression { 107 | return callback(.continue) 108 | } 109 | if let acceptEncoding = req.header(.acceptEncoding), 110 | let contentType = contentType(response: response), 111 | clientWantsCompression(acceptEncoding: acceptEncoding), 112 | shouldCompress(mimeType: contentType) { 113 | 114 | let skipCheck = response.request.scratchPad["no-compression"] as? Bool ?? false 115 | if !skipCheck, let stream = ZlibStream() { 116 | response.setHeader(.contentEncoding, value: "deflate") 117 | if response.isStreaming { 118 | response.request.scratchPad["zlib-stream"] = stream 119 | } else { 120 | let old = response.bodyBytes 121 | let new = stream.compress(old, flush: true) 122 | response.bodyBytes = new 123 | stream.close() 124 | response.setHeader(.contentLength, value: "\(new.count)") 125 | } 126 | } 127 | } 128 | return callback(.continue) 129 | } 130 | 131 | func filterBody(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 132 | guard response.isStreaming, let stream = response.request.scratchPad["zlib-stream"] as? ZlibStream else { 133 | return callback(.continue) 134 | } 135 | 136 | let flush = response.request.scratchPad["_flushing_"] as? Bool ?? false 137 | response.bodyBytes = stream.compress(response.bodyBytes, flush: flush) 138 | return callback(.continue) 139 | } 140 | 141 | private func contentType(response: HTTPResponse) -> String? { 142 | if let contentType = response.header(.contentType) { 143 | return contentType 144 | } 145 | let path = response.request.path 146 | return MimeType.forExtension(path.lastFilePathComponent.filePathExtension) 147 | } 148 | 149 | private func clientWantsCompression(acceptEncoding: String) -> Bool { 150 | return acceptEncoding.contains("deflate") 151 | } 152 | 153 | private func shouldCompress(mimeType: String) -> Bool { 154 | let mime = MimeType(mimeType) 155 | return compressTypes.contains(mime) && !ignoreTypes.contains(mime) 156 | } 157 | } 158 | return CompressResponse(compressTypes: inCompressTypes.map { MimeType($0) }, 159 | ignoreTypes: inIgnoreTypes.map { MimeType($0) }) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /Sources/PerfectHTTPServer/HTTP11/HTTP11Response.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTP11Response.swift 3 | // PerfectLib 4 | // 5 | // Created by Kyle Jessup on 2016-06-21. 6 | // Copyright (C) 2016 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | #if os(Linux) 21 | import SwiftGlibc 22 | #else 23 | import Darwin 24 | #endif 25 | 26 | import PerfectNet 27 | import PerfectThread 28 | import PerfectHTTP 29 | 30 | class HTTP11Response: HTTPResponse { 31 | var status = HTTPResponseStatus.ok 32 | var headerStore = Array<(HTTPResponseHeader.Name, String)>() 33 | var bodyBytes = [UInt8]() 34 | var bodyPrefix = [UInt8]() 35 | var headers: AnyIterator<(HTTPResponseHeader.Name, String)> { 36 | var g = headerStore.makeIterator() 37 | return AnyIterator<(HTTPResponseHeader.Name, String)> { 38 | g.next() 39 | } 40 | } 41 | 42 | var connection: NetTCP { 43 | return request.connection 44 | } 45 | 46 | var isStreaming = false 47 | var wroteHeaders = false 48 | var contentLengthSet = false 49 | var completedCallback: (() -> ())? 50 | let request: HTTPRequest 51 | var handlers: IndexingIterator<[RequestHandler]>? 52 | 53 | lazy var isKeepAlive: Bool = { 54 | // http 1.1 is keep-alive unless otherwise noted 55 | // http 1.0 is keep-alive if specifically noted 56 | // check header first 57 | if let connection = request.header(.connection) { 58 | if connection.lowercased() == "keep-alive" { 59 | return true 60 | } 61 | return false 62 | } 63 | return isHTTP11 64 | }() 65 | 66 | var isHTTP11: Bool { 67 | let version = request.protocolVersion 68 | return version.0 == 1 && version.1 == 1 69 | } 70 | 71 | let filters: IndexingIterator<[[HTTPResponseFilter]]>? 72 | 73 | init(request: HTTPRequest, filters: IndexingIterator<[[HTTPResponseFilter]]>? = nil) { 74 | self.request = request 75 | self.filters = filters 76 | let net = request.connection 77 | completedCallback = { 78 | net.close() 79 | } 80 | } 81 | 82 | func completed() { 83 | if let cb = completedCallback { 84 | cb() 85 | } 86 | } 87 | 88 | func next() { 89 | if let n = handlers?.next() { 90 | n(request, self) 91 | } else { 92 | completed() 93 | } 94 | } 95 | 96 | func abort() { 97 | completedCallback = nil 98 | connection.close() 99 | } 100 | 101 | func header(_ named: HTTPResponseHeader.Name) -> String? { 102 | for (n, v) in headerStore where n == named { 103 | return v 104 | } 105 | return nil 106 | } 107 | 108 | @discardableResult 109 | func addHeader(_ name: HTTPResponseHeader.Name, value: String) -> Self { 110 | headerStore.append((name, value)) 111 | if case .contentLength = name { 112 | contentLengthSet = true 113 | } 114 | return self 115 | } 116 | 117 | @discardableResult 118 | func setHeader(_ name: HTTPResponseHeader.Name, value: String) -> Self { 119 | var fi = [Int]() 120 | for i in 0.. ()) { 134 | if let _ = filters { 135 | // !FIX! this needs an API change for response filters to let them know 136 | // when a call is the last 137 | request.scratchPad["_flushing_"] = true 138 | } 139 | push { 140 | ok in 141 | guard ok else { 142 | return callback(false) 143 | } 144 | if self.isStreaming { 145 | self.pushNonStreamed(bytes: Array("0\r\n\r\n".utf8), callback: callback) 146 | } else { 147 | callback(true) 148 | } 149 | } 150 | } 151 | 152 | func pushHeaders(callback: @escaping (Bool) -> ()) { 153 | wroteHeaders = true 154 | if isKeepAlive { 155 | addHeader(.connection, value: "keep-alive") 156 | } 157 | if isStreaming { 158 | addHeader(.transferEncoding, value: "chunked") 159 | } else if !contentLengthSet { 160 | addHeader(.contentLength, value: "\(bodyBytes.count)") 161 | } 162 | if let filters = self.filters { 163 | return filterHeaders(allFilters: filters, callback: callback) 164 | } 165 | finishPushHeaders(callback: callback) 166 | } 167 | 168 | func filterHeaders(allFilters: IndexingIterator<[[HTTPResponseFilter]]>, callback: @escaping (Bool) -> ()) { 169 | var allFilters = allFilters 170 | if let prioFilters = allFilters.next() { 171 | return filterHeaders(allFilters: allFilters, prioFilters: prioFilters.makeIterator(), callback: callback) 172 | } 173 | finishPushHeaders(callback: callback) 174 | } 175 | 176 | func filterHeaders(allFilters: IndexingIterator<[[HTTPResponseFilter]]>, 177 | prioFilters: IndexingIterator<[HTTPResponseFilter]>, 178 | callback: @escaping (Bool) -> ()) { 179 | var prioFilters = prioFilters 180 | guard let filter = prioFilters.next() else { 181 | return filterHeaders(allFilters: allFilters, callback: callback) 182 | } 183 | filter.filterHeaders(response: self) { 184 | result in 185 | switch result { 186 | case .continue: 187 | self.filterHeaders(allFilters: allFilters, prioFilters: prioFilters, callback: callback) 188 | case .done: 189 | self.finishPushHeaders(callback: callback) 190 | case .halt: 191 | self.abort() 192 | } 193 | } 194 | } 195 | 196 | func finishPushHeaders(callback: @escaping (Bool) -> ()) { 197 | let eol = [13, 10] as [UInt8] 198 | if case .ok = status, request.protocolVersion.1 == 1 { 199 | bodyPrefix = [72, 84, 84, 80, 47, 49, 46, 49, 32, 50, 48, 48, 32, 79, 75, 13, 10] 200 | } else { 201 | bodyPrefix = Array("HTTP/\(request.protocolVersion.0).\(request.protocolVersion.1) \(status)\r\n".utf8) 202 | } 203 | for (n, v) in headers { 204 | bodyPrefix.append(contentsOf: Array((n.standardName + ": " + v).utf8)) 205 | bodyPrefix.append(contentsOf: eol) 206 | } 207 | bodyPrefix.append(contentsOf: eol) 208 | push(callback: callback) 209 | } 210 | 211 | func filterBodyBytes(allFilters: IndexingIterator<[[HTTPResponseFilter]]>, callback: ([UInt8]) -> ()) { 212 | var allFilters = allFilters 213 | if let prioFilters = allFilters.next() { 214 | return filterBodyBytes(allFilters: allFilters, prioFilters: prioFilters.makeIterator(), callback: callback) 215 | } 216 | finishFilterBodyBytes(callback: callback) 217 | } 218 | 219 | func filterBodyBytes(allFilters: IndexingIterator<[[HTTPResponseFilter]]>, 220 | prioFilters: IndexingIterator<[HTTPResponseFilter]>, 221 | callback: ([UInt8]) -> ()) { 222 | var prioFilters = prioFilters 223 | guard let filter = prioFilters.next() else { 224 | return filterBodyBytes(allFilters: allFilters, callback: callback) 225 | } 226 | filter.filterBody(response: self) { 227 | result in 228 | switch result { 229 | case .continue: 230 | self.filterBodyBytes(allFilters: allFilters, prioFilters: prioFilters, callback: callback) 231 | case .done: 232 | self.finishFilterBodyBytes(callback: callback) 233 | case .halt: 234 | self.abort() 235 | } 236 | } 237 | } 238 | 239 | func finishFilterBodyBytes(callback: (_ bodyBytes: [UInt8]) -> ()) { 240 | let bytes = bodyBytes 241 | bodyBytes = [] 242 | callback(bytes) 243 | } 244 | 245 | func filteredBodyBytes(callback: (_ bodyBytes: [UInt8]) -> ()) { 246 | if let filters = self.filters { 247 | return filterBodyBytes(allFilters: filters, callback: callback) 248 | } 249 | finishFilterBodyBytes(callback: callback) 250 | } 251 | 252 | func push(callback: @escaping (Bool) -> ()) { 253 | if !wroteHeaders { 254 | return pushHeaders(callback: callback) 255 | } 256 | filteredBodyBytes { 257 | bytes in 258 | if self.isStreaming { 259 | return self.pushStreamed(bytes: bytes, callback: callback) 260 | } 261 | self.pushNonStreamed(bytes: bytes, callback: callback) 262 | } 263 | } 264 | 265 | func pushStreamed(bytes: [UInt8], callback: @escaping (Bool) -> ()) { 266 | let bodyCount = bytes.count 267 | guard bodyCount > 0 else { 268 | return callback(true) 269 | } 270 | let hexString = "\(String(bodyCount, radix: 16, uppercase: true))\r\n" 271 | let sendA = Array(hexString.utf8) 272 | pushNonStreamed(bytes: sendA) { 273 | ok in 274 | guard ok else { 275 | return self.abort() 276 | } 277 | self.pushNonStreamed(bytes: bytes) { 278 | ok in 279 | guard ok else { 280 | return self.abort() 281 | } 282 | self.pushNonStreamed(bytes: Array("\r\n".utf8), callback: callback) 283 | } 284 | } 285 | } 286 | 287 | func pushNonStreamed(bytes: [UInt8], callback: @escaping (Bool) -> ()) { 288 | if !bodyPrefix.isEmpty { 289 | let newBytes = bodyPrefix + bytes 290 | bodyPrefix = [] 291 | return pushNonStreamed(bytes: newBytes, callback: callback) 292 | } 293 | let bodyCount = bytes.count 294 | guard bodyCount > 0 else { 295 | return callback(true) 296 | } 297 | connection.write(bytes: bytes) { 298 | sent in 299 | guard bodyCount == sent else { 300 | return self.abort() 301 | } 302 | netHandleQueue.async { 303 | callback(true) 304 | } 305 | } 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Sources/PerfectHTTPServer/HTTP2/HTTP2Response.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTP2.swift 3 | // PerfectLib 4 | // 5 | // Created by Kyle Jessup on 2016-02-18. 6 | // Copyright © 2016 PerfectlySoft. All rights reserved. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | import PerfectLib 21 | import PerfectHTTP 22 | import PerfectThread 23 | 24 | final class HTTP2Response: HTTPResponse { 25 | var request: HTTPRequest 26 | var status: HTTPResponseStatus = .ok 27 | var isStreaming = true // implicitly streamed 28 | var bodyBytes: [UInt8] = [] 29 | var headerStore = Array<(HTTPResponseHeader.Name, String)>() 30 | var encoder: HPACKEncoder { return h2Request.session!.encoder } 31 | var wroteHeaders = false 32 | var h2Request: HTTP2Request { return request as! HTTP2Request } 33 | var session: HTTP2Session? { return h2Request.session } 34 | var debug: Bool { return session?.debug ?? false } 35 | var filters: IndexingIterator<[[HTTPResponseFilter]]>? { 36 | guard let f = session?.server.responseFilters, !f.isEmpty else { 37 | return nil 38 | } 39 | return f.makeIterator() 40 | } 41 | var frameWriter: HTTP2FrameWriter? { return session?.frameWriter } 42 | var maxFrameSize: Int { 43 | return h2Request.session?.clientSettings.maxFrameSize ?? 16384 44 | } 45 | var streamId: UInt32 { return h2Request.streamId } 46 | var handlers: IndexingIterator<[RequestHandler]>? 47 | 48 | init(_ request: HTTP2Request) { 49 | self.request = request 50 | } 51 | 52 | deinit { 53 | if debug { print("~HTTP2Response \(streamId)") } 54 | } 55 | 56 | func header(_ named: HTTPResponseHeader.Name) -> String? { 57 | for (n, v) in headerStore where n == named { 58 | return v 59 | } 60 | return nil 61 | } 62 | 63 | @discardableResult 64 | func addHeader(_ name: HTTPResponseHeader.Name, value: String) -> Self { 65 | headerStore.append((name, value)) 66 | return self 67 | } 68 | 69 | @discardableResult 70 | func setHeader(_ name: HTTPResponseHeader.Name, value: String) -> Self { 71 | var fi = [Int]() 72 | for i in 0.. { 86 | var g = self.headerStore.makeIterator() 87 | return AnyIterator<(HTTPResponseHeader.Name, String)> { 88 | g.next() 89 | } 90 | } 91 | 92 | func pushHeaders(callback: @escaping (Bool) -> ()) { 93 | guard !wroteHeaders else { 94 | return callback(true) 95 | } 96 | wroteHeaders = true 97 | guard h2Request.streamState != .closed else { 98 | return callback(false) 99 | } 100 | if let filters = self.filters { 101 | filterHeaders(allFilters: filters, callback: callback) 102 | } else { 103 | finishPushHeaders(callback: callback) 104 | } 105 | } 106 | 107 | func finishPushHeaders(callback: @escaping (Bool) -> ()) { 108 | if debug { 109 | print("response header:") 110 | print("\tstream: \(streamId)") 111 | print("\t:status \(status.code)") 112 | headerStore.forEach { 113 | (arg0) in 114 | let (name, value) = arg0 115 | print("\t\(name.standardName.lowercased()): \(value)") 116 | } 117 | } 118 | let bytes = Bytes() 119 | do { 120 | session?.encoderLock.lock() 121 | defer { 122 | session?.encoderLock.unlock() 123 | } 124 | try encoder.encodeHeader(out: bytes, nameStr: ":status", valueStr: "\(status.code)") 125 | try headerStore.forEach { 126 | (arg0) in 127 | let (name, value) = arg0 128 | try encoder.encodeHeader(out: bytes, nameStr: name.standardName.lowercased(), valueStr: value) 129 | } 130 | } catch { 131 | h2Request.session?.fatalError(streamId: h2Request.streamId, error: .internalError, msg: "Error while encoding headers") 132 | return callback(false) 133 | } 134 | pushHeaderBlock(bytes.data, sendCount: 0, callback: callback) 135 | } 136 | 137 | func pushHeaderBlock(_ bytes: [UInt8], sendCount: Int, callback: @escaping (Bool) -> ()) { 138 | let maxFrameSize = session?.clientSettings.maxFrameSize ?? 16384 139 | let final = bytes.count <= maxFrameSize 140 | if final { 141 | let frame: HTTP2Frame 142 | if sendCount == 0 { 143 | frame = HTTP2Frame(type: .headers, flags: flagEndHeaders, streamId: h2Request.streamId, payload: bytes) 144 | } else { 145 | frame = HTTP2Frame(type: .continuation, flags: flagEndHeaders, streamId: h2Request.streamId, payload: bytes) 146 | } 147 | frameWriter?.enqueueFrame(frame) 148 | callback(true) 149 | } else { 150 | let thisBytes = Array(bytes[0.. Int { 164 | let a = want, b = maxFrameSize, 165 | // c = session!.connectionFlowWindows.clientWindowSize, 166 | d = h2Request.streamFlowWindows.clientWindowSize 167 | return min(a, b, d) 168 | } 169 | 170 | func pushBody(final: Bool, bodyBytes inBodyBytes: [UInt8], callback: @escaping (Bool) -> ()) { 171 | guard h2Request.streamState != .closed else { 172 | return callback(false) 173 | } 174 | guard final || !inBodyBytes.isEmpty else { 175 | return callback(true) 176 | } 177 | let sendBytes: [UInt8] // actually sending in this frame 178 | let remainingBodyBytes: [UInt8] // left over to send next frame 179 | let moreToCome: Bool 180 | let maxSize = maxSendSize(want: inBodyBytes.count) 181 | guard (final && inBodyBytes.isEmpty) || maxSize > 0 else { 182 | h2Request.unblockCallback = { 183 | if self.debug { 184 | print("response \(self.streamId) unblocked") 185 | } 186 | guard self.h2Request.streamState != .closed else { 187 | return callback(false) 188 | } 189 | self.pushBody(final: final, bodyBytes: inBodyBytes, callback: callback) 190 | } 191 | if debug { 192 | print("response \(streamId) blocked") 193 | } 194 | return 195 | } 196 | if inBodyBytes.count > maxSize { 197 | sendBytes = Array(inBodyBytes[0.. ()) { 231 | pushHeaders { 232 | ok in 233 | guard ok else { 234 | self.removeRequest() 235 | return callback(false) 236 | } 237 | if final { 238 | // !FIX! this needs an API change for response filters to let them know 239 | // when a call is the last 240 | self.request.scratchPad["_flushing_"] = true 241 | } 242 | self.filteredBodyBytes { 243 | bytes in 244 | self.pushBody(final: final, bodyBytes: bytes) { 245 | ok in 246 | guard ok else { 247 | self.removeRequest() 248 | return callback(false) 249 | } 250 | callback(true) 251 | } 252 | } 253 | } 254 | } 255 | 256 | func push(callback: @escaping (Bool) -> ()) { 257 | push(final: false, callback: callback) 258 | } 259 | 260 | func completed() { 261 | push(final: true) { 262 | ok in 263 | self.removeRequest() 264 | } 265 | } 266 | 267 | func next() { 268 | if let n = handlers?.next() { 269 | n(request, self) 270 | } else { 271 | completed() 272 | } 273 | } 274 | 275 | func abort() { 276 | h2Request.streamState = .closed 277 | let b = Bytes() 278 | b.import32Bits(from: HTTP2Error.noError.rawValue) 279 | var frame = HTTP2Frame(type: .cancelStream, flags: 0, streamId: streamId, payload: b.data) 280 | frame.sentCallback = { 281 | ok in 282 | self.removeRequest() 283 | } 284 | } 285 | 286 | func removeRequest() { 287 | let req = h2Request 288 | req.session?.removeRequest(req.streamId) 289 | } 290 | } 291 | 292 | extension HTTP2Response { 293 | 294 | func filterHeaders(allFilters: IndexingIterator<[[HTTPResponseFilter]]>, callback: @escaping (Bool) -> ()) { 295 | var allFilters = allFilters 296 | if let prioFilters = allFilters.next() { 297 | return filterHeaders(allFilters: allFilters, prioFilters: prioFilters.makeIterator(), callback: callback) 298 | } 299 | finishPushHeaders(callback: callback) 300 | } 301 | 302 | func filterHeaders(allFilters: IndexingIterator<[[HTTPResponseFilter]]>, 303 | prioFilters: IndexingIterator<[HTTPResponseFilter]>, 304 | callback: @escaping (Bool) -> ()) { 305 | var prioFilters = prioFilters 306 | guard let filter = prioFilters.next() else { 307 | return filterHeaders(allFilters: allFilters, callback: callback) 308 | } 309 | filter.filterHeaders(response: self) { 310 | result in 311 | switch result { 312 | case .continue: 313 | self.filterHeaders(allFilters: allFilters, prioFilters: prioFilters, callback: callback) 314 | case .done: 315 | self.finishPushHeaders(callback: callback) 316 | case .halt: 317 | self.abort() 318 | } 319 | } 320 | } 321 | 322 | func filterBodyBytes(allFilters: IndexingIterator<[[HTTPResponseFilter]]>, 323 | prioFilters: IndexingIterator<[HTTPResponseFilter]>, 324 | callback: ([UInt8]) -> ()) { 325 | var prioFilters = prioFilters 326 | guard let filter = prioFilters.next() else { 327 | return filterBodyBytes(allFilters: allFilters, callback: callback) 328 | } 329 | filter.filterBody(response: self) { 330 | result in 331 | switch result { 332 | case .continue: 333 | self.filterBodyBytes(allFilters: allFilters, prioFilters: prioFilters, callback: callback) 334 | case .done: 335 | self.finishFilterBodyBytes(callback: callback) 336 | case .halt: 337 | self.abort() 338 | } 339 | } 340 | } 341 | 342 | func filterBodyBytes(allFilters: IndexingIterator<[[HTTPResponseFilter]]>, callback: ([UInt8]) -> ()) { 343 | var allFilters = allFilters 344 | if let prioFilters = allFilters.next() { 345 | return filterBodyBytes(allFilters: allFilters, prioFilters: prioFilters.makeIterator(), callback: callback) 346 | } 347 | finishFilterBodyBytes(callback: callback) 348 | } 349 | 350 | func finishFilterBodyBytes(callback: (_ bodyBytes: [UInt8]) -> ()) { 351 | let bytes = bodyBytes 352 | bodyBytes = [] 353 | callback(bytes) 354 | } 355 | 356 | func filteredBodyBytes(callback: (_ bodyBytes: [UInt8]) -> ()) { 357 | if let filters = self.filters { 358 | return filterBodyBytes(allFilters: filters, callback: callback) 359 | } 360 | finishFilterBodyBytes(callback: callback) 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /Sources/PerfectHTTPServer/HTTP2/HTTP2Request.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTP2.swift 3 | // PerfectLib 4 | // 5 | // Created by Kyle Jessup on 2016-02-18. 6 | // Copyright © 2016 PerfectlySoft. All rights reserved. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | import PerfectHTTP 21 | import PerfectNet 22 | import PerfectLib 23 | import PerfectThread 24 | 25 | 26 | final class HTTP2Request: HTTPRequest, HeaderListener { 27 | var method: HTTPMethod = .get 28 | var path: String { 29 | get { 30 | var accum = "" 31 | var lastSlashExplicit = false 32 | for p in pathComponents { 33 | if p == "/" { 34 | accum += p 35 | lastSlashExplicit = true 36 | } else { 37 | if !lastSlashExplicit { 38 | accum += "/" 39 | } 40 | accum += p.stringByEncodingURL 41 | lastSlashExplicit = false 42 | } 43 | } 44 | return accum 45 | } 46 | set { 47 | let components = newValue.filePathComponents.map { $0 == "/" ? "/" : $0.stringByDecodingURL ?? "" } 48 | pathComponents = components 49 | } 50 | } 51 | var pathComponents = [String]() 52 | var queryString = "" 53 | var scheme = "" 54 | var authority = "" 55 | lazy var queryParams: [(String, String)] = { 56 | return deFormURLEncoded(string: queryString) 57 | }() 58 | var protocolVersion = (2, 0) 59 | var remoteAddress: (host: String, port: UInt16) { 60 | guard let remote = connection.remoteAddress else { 61 | return ("", 0) 62 | } 63 | return (remote.host, remote.port) 64 | } 65 | var serverAddress: (host: String, port: UInt16) { 66 | guard let local = connection.localAddress else { 67 | return ("", 0) 68 | } 69 | return (local.host, local.port) 70 | } 71 | var serverName: String { return session?.server.serverName ?? "" } 72 | var documentRoot: String { return session?.server.documentRoot ?? "./" } 73 | var connection: NetTCP 74 | var urlVariables: [String:String] = [:] 75 | var scratchPad: [String:Any] = [:] 76 | private var headerStore = Dictionary() 77 | var headers: AnyIterator<(HTTPRequestHeader.Name, String)> { 78 | var g = self.headerStore.makeIterator() 79 | return AnyIterator<(HTTPRequestHeader.Name, String)> { 80 | guard let n = g.next() else { 81 | return nil 82 | } 83 | return (n.key, UTF8Encoding.encode(bytes: n.value)) 84 | } 85 | } 86 | 87 | func header(_ named: HTTPRequestHeader.Name) -> String? { 88 | guard let v = headerStore[named] else { 89 | return nil 90 | } 91 | return UTF8Encoding.encode(bytes: v) 92 | } 93 | 94 | func addHeader(_ named: HTTPRequestHeader.Name, value: String) { 95 | guard let existing = headerStore[named] else { 96 | self.headerStore[named] = [UInt8](value.utf8) 97 | return 98 | } 99 | let valueBytes = [UInt8](value.utf8) 100 | let newValue: [UInt8] 101 | if named == .cookie { 102 | newValue = existing + "; ".utf8 + valueBytes 103 | } else { 104 | newValue = existing + ", ".utf8 + valueBytes 105 | } 106 | self.headerStore[named] = newValue 107 | } 108 | 109 | func setHeader(_ named: HTTPRequestHeader.Name, value: String) { 110 | headerStore[named] = [UInt8](value.utf8) 111 | } 112 | 113 | lazy var postParams: [(String, String)] = { 114 | 115 | if let mime = self.mimes { 116 | return mime.bodySpecs.filter { $0.file == nil }.map { ($0.fieldName, $0.fieldValue) } 117 | } else if let bodyString = self.postBodyString { 118 | return self.deFormURLEncoded(string: bodyString) 119 | } 120 | return [(String, String)]() 121 | }() 122 | var postBodyBytes: [UInt8]? = nil 123 | var postBodyString: String? { 124 | guard let bytes = postBodyBytes else { 125 | return nil 126 | } 127 | if bytes.isEmpty { 128 | return "" 129 | } 130 | return UTF8Encoding.encode(bytes: bytes) 131 | } 132 | var postFileUploads: [MimeReader.BodySpec]? { 133 | guard let mimes = self.mimes else { 134 | return nil 135 | } 136 | return mimes.bodySpecs 137 | } 138 | 139 | weak var session: HTTP2Session? 140 | var decoder: HPACKDecoder { return session!.decoder } 141 | let streamId: UInt32 142 | var streamState = HTTP2StreamState.idle 143 | var streamFlowWindows: HTTP2FlowWindows 144 | var encodedHeadersBlock = [UInt8]() 145 | var endOfHeaders = false 146 | var unblockCallback: (() -> ())? 147 | var debug: Bool { return session?.debug ?? false } 148 | var mimes: MimeReader? 149 | 150 | init(_ streamId: UInt32, session: HTTP2Session) { 151 | connection = session.net 152 | self.streamId = streamId 153 | self.session = session 154 | streamFlowWindows = HTTP2FlowWindows(serverWindowSize: session.serverSettings.initialWindowSize, 155 | clientWindowSize: session.clientSettings.initialWindowSize) 156 | } 157 | 158 | deinit { 159 | if debug { print("~HTTP2Request \(streamId)") } 160 | } 161 | 162 | func decodeHeadersBlock() { 163 | do { 164 | decoder.reset() 165 | try decoder.decode(input: Bytes(existingBytes: encodedHeadersBlock), headerListener: self) 166 | } catch { 167 | session?.fatalError(streamId: streamId, error: .compressionError, msg: "error while decoding headers \(error)") 168 | streamState = .closed 169 | } 170 | encodedHeadersBlock = [] 171 | } 172 | 173 | func headersFrame(_ frame: HTTP2Frame) { 174 | let endOfStream = (frame.flags & flagEndStream) != 0 175 | if endOfStream { 176 | streamState = .halfClosed 177 | } else { 178 | streamState = .open 179 | } 180 | endOfHeaders = (frame.flags & flagEndHeaders) != 0 181 | if debug { 182 | print("\tstream: \(streamId)") 183 | } 184 | let padded = (frame.flags & flagPadded) != 0 185 | let priority = (frame.flags & flagPriority) != 0 186 | if let ba = frame.payload, ba.count > 0 { 187 | let bytes = Bytes(existingBytes: ba) 188 | var padLength: UInt8 = 0 189 | if padded { 190 | padLength = bytes.export8Bits() 191 | bytes.data.removeLast(Int(padLength)) 192 | } 193 | if priority { 194 | let _/*streamDep*/ = bytes.export32Bits() 195 | let _/*weight*/ = bytes.export8Bits() 196 | } 197 | encodedHeadersBlock += bytes.exportBytes(count: bytes.availableExportBytes) 198 | } 199 | if endOfHeaders { 200 | decodeHeadersBlock() 201 | } 202 | if endOfHeaders && endOfStream { 203 | processRequest() 204 | } else { 205 | session?.increaseServerWindow(stream: streamId, by: receiveWindowTopOff) 206 | } 207 | } 208 | 209 | func continuationFrame(_ frame: HTTP2Frame) { 210 | guard !endOfHeaders, streamState == .open else { 211 | session?.fatalError(streamId: streamId, error: .protocolError, msg: "Invalid frame") 212 | return 213 | } 214 | let endOfStream = (frame.flags & flagEndStream) != 0 215 | if endOfStream { 216 | streamState = .halfClosed 217 | } 218 | endOfHeaders = (frame.flags & flagEndHeaders) != 0 219 | if debug { 220 | print("\tstream: \(streamId)") 221 | } 222 | if let ba = frame.payload, ba.count > 0 { 223 | encodedHeadersBlock += ba 224 | } 225 | if endOfHeaders { 226 | decodeHeadersBlock() 227 | } 228 | if endOfHeaders && endOfStream { 229 | processRequest() 230 | } else { 231 | session?.increaseServerWindow(stream: streamId, by: receiveWindowTopOff) 232 | } 233 | } 234 | 235 | // session handles window adjustments 236 | func dataFrame(_ frame: HTTP2Frame) { 237 | let endOfStream = (frame.flags & flagEndStream) != 0 238 | let bytes = frame.payload ?? [] 239 | let padded = (frame.flags & flagPadded) != 0 240 | if debug { 241 | print("request \(streamId) POST bytes: \(bytes.count), recv window: \(streamFlowWindows.serverWindowSize), EOS: \(endOfStream), padded: \(padded)") 242 | } 243 | if padded { 244 | let padSize = Int(bytes[0]) 245 | let lastIndex = bytes.count - padSize 246 | putPostData(Array(bytes[1.. [(String, String)] { 320 | return string.split(separator: "&").map(String.init).compactMap { 321 | let d = $0.split(separator: "=", maxSplits: 1).compactMap { String($0).stringByDecodingURL } 322 | if d.count == 2 { return (d[0], d[1]) } 323 | if d.count == 1 { return (d[0], "") } 324 | return nil 325 | } 326 | } 327 | // parse from workingBuffer contents 328 | func parseURI(pathBuffer: [UInt8]) -> ([String], String) { 329 | enum ParseURLState { 330 | case slash, component, query 331 | } 332 | var state = ParseURLState.slash 333 | var gen = pathBuffer.makeIterator() 334 | var decoder = UTF8() 335 | var pathComponents = ["/"] 336 | var component = "" 337 | var queryString = "" 338 | 339 | let question = UnicodeScalar(63) 340 | let slash = UnicodeScalar(47) 341 | 342 | loopy: 343 | repeat { 344 | let res = decoder.decode(&gen) 345 | switch res { 346 | case .scalarValue(let uchar): 347 | switch state { 348 | case .slash: 349 | if uchar == question { 350 | state = .query 351 | pathComponents.append("/") 352 | } else if uchar != slash { 353 | state = .component 354 | component = String(Character(uchar)) 355 | } 356 | case .component: 357 | if uchar == question { 358 | state = .query 359 | pathComponents.append(component.stringByDecodingURL ?? "") 360 | } else if uchar == slash { 361 | state = .slash 362 | pathComponents.append(component.stringByDecodingURL ?? "") 363 | } else { 364 | component.append(Character(uchar)) 365 | } 366 | case .query: 367 | queryString.append(Character(uchar)) 368 | } 369 | case .emptyInput, .error: 370 | switch state { 371 | case .slash: 372 | if pathComponents.count > 1 { 373 | pathComponents.append("/") 374 | } 375 | case .component: 376 | pathComponents.append(component.stringByDecodingURL ?? "") 377 | case .query: 378 | () 379 | } 380 | break loopy 381 | } 382 | } while true 383 | return (pathComponents, queryString) 384 | } 385 | } 386 | 387 | extension HTTP2Request { 388 | func canSend(count: Int) -> Bool { 389 | return session!.connectionFlowWindows.clientWindowSize - count > 0 && 390 | streamFlowWindows.clientWindowSize - count > 0 391 | } 392 | 393 | func canRecv(count: Int) -> Bool { 394 | return session!.connectionFlowWindows.serverWindowSize - count > 0 && 395 | streamFlowWindows.serverWindowSize - count > 0 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /Sources/PerfectHTTPServer/HTTPServerExConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPServerExConfig.swift 3 | // PerfectHTTPServer 4 | // 5 | // Created by Kyle Jessup on 2016-11-29. 6 | // 7 | 8 | import PerfectHTTP 9 | import PerfectLib 10 | import PerfectNet 11 | import Foundation 12 | import PerfectCZlib 13 | 14 | // HERE BE DRAGONS 15 | private typealias ReturnsRequestHandlerGivenData = ([String:Any]) throws -> RequestHandler 16 | private typealias ReturnsResponseFilterGivenData = ([String:Any]) throws -> HTTPResponseFilter 17 | private typealias ReturnsRequestFilterGivenData = ([String:Any]) throws -> HTTPRequestFilter 18 | 19 | private func templateWithData(data: [String:Any]) throws -> RequestHandler { 20 | fatalError("nope") 21 | } 22 | 23 | private func templateWithData(data: [String:Any]) throws -> HTTPResponseFilter { 24 | fatalError("nope") 25 | } 26 | 27 | private func templateWithData(data: [String:Any]) throws -> HTTPRequestFilter { 28 | fatalError("nope") 29 | } 30 | 31 | private struct swift_func_object { 32 | var original_type_ptr: UnsafeMutablePointer 33 | var unknown: UnsafeMutablePointer 34 | var address: uintptr_t 35 | var selfPtr: UnsafeMutablePointer 36 | } 37 | 38 | private struct swift_func_wrapper { 39 | var trampolinePtr: UnsafeMutablePointer 40 | var functionObject: UnsafeMutablePointer 41 | } 42 | 43 | //__TZFV17PerfectHTTPServer11HTTPHandler8redirectfzT4dataGVs10DictionarySSP___FTP11PerfectHTTP11HTTPRequest_PS2_12HTTPResponse__T_ 44 | private let symbolPrefixes = ["_TF", "_TFV", "_TZFC", "_TZFO", "_TZFOV", "_TZFV"] 45 | private let exeHandle = dlopen(nil, RTLD_NOW) 46 | 47 | private func findFunc(_ named: String, suffixes: [String]) -> UnsafeMutableRawPointer? { 48 | let names = named.split(separator: ".") 49 | let preName = names.map { "\($0.count)\(String($0))" }.joined() 50 | let someDataSuffixes = ["FzT4dataGVs10DictionarySSP___", "fzT4dataGVs10DictionarySSP___"] 51 | 52 | var rcheck = "" 53 | 54 | for prefix in symbolPrefixes { 55 | for dataSuffix in someDataSuffixes { 56 | for suffix in suffixes { 57 | let check = "\(prefix)\(preName)\(dataSuffix)\(suffix)" 58 | rcheck += check + "\n" 59 | if let sym = dlsym(exeHandle, check) { 60 | return sym 61 | } 62 | } 63 | } 64 | } 65 | // print(rcheck) 66 | return nil 67 | } 68 | 69 | private extension Route { 70 | init(data: [String:Any]) throws { 71 | guard let uri = data["uri"] as? String else { 72 | throw PerfectError.apiError("Route data did not contain a uri") 73 | } 74 | let handler: RequestHandler 75 | switch data["handler"] { 76 | case let handlerName as String: 77 | guard let tryHandler = try Route.lookupHandler(named: handlerName, data: data) else { 78 | throw PerfectError.apiError("Route could not find handler \(handlerName). Ensure it is spelled correctly and fully qualified with its module name.") 79 | } 80 | handler = tryHandler 81 | case let handlerFunc as ReturnsRequestHandlerGivenData: 82 | handler = try handlerFunc(data) 83 | case let handlerFunc as RequestHandler: 84 | handler = handlerFunc 85 | default: 86 | throw PerfectError.apiError("No valid handler was provided \"handler\"=\(String(describing: data["handler"]))") 87 | } 88 | if let methodStr = data["method"] as? String { 89 | self.init(method: HTTPMethod.from(string: methodStr.uppercased()), uri: uri, handler: handler) 90 | } else if let methodsAry = data["methods"] as? [String] { 91 | self.init(methods: methodsAry.map { HTTPMethod.from(string: $0.uppercased()) }, uri: uri, handler: handler) 92 | } else { 93 | self.init(uri: uri, handler: handler) 94 | } 95 | } 96 | 97 | private static func lookupHandler(named: String, data: [String:Any]) throws -> RequestHandler? { 98 | if let sym = findFunc(named, suffixes: ["FTP11PerfectHTTP11HTTPRequest_PS1_12HTTPResponse__T_", 99 | "FTP11PerfectHTTP11HTTPRequest_PS2_12HTTPResponse__T_"]) { 100 | return try callWithData(sym, data: data) 101 | } 102 | return nil 103 | } 104 | 105 | private static func callWithData(_ ptr: UnsafeMutableRawPointer?, data: [String:Any]) throws -> RequestHandler? { 106 | guard let ptr = ptr else { 107 | return nil 108 | } 109 | let fn = UnsafeMutablePointer.allocate(capacity: 1) 110 | defer { 111 | fn.deinitialize(count: 1) 112 | fn.deallocate() 113 | } 114 | fn.initialize(to: templateWithData) 115 | let p = UnsafeMutableRawPointer(fn).assumingMemoryBound(to: swift_func_wrapper.self) 116 | p.pointee.functionObject.pointee.address = UInt(bitPattern: ptr) 117 | let callMe = fn.pointee 118 | return try callMe(data) 119 | } 120 | } 121 | 122 | extension Routes { 123 | init(data: [[String:Any]]) throws { 124 | self.init(try data.map { try Route(data: $0) }) 125 | } 126 | } 127 | 128 | extension OpenSSLVerifyMode { 129 | init?(string: String) { 130 | switch string { 131 | case "none": self = .sslVerifyNone 132 | case "peer": self = .sslVerifyPeer 133 | case "failIfNoPeerCert": self = .sslVerifyFailIfNoPeerCert 134 | case "clientOnce": self = .sslVerifyClientOnce 135 | case "peerWithFailIfNoPeerCert": self = .sslVerifyPeerWithFailIfNoPeerCert 136 | case "peerClientOnce": self = .sslVerifyPeerClientOnce 137 | case "peerWithFailIfNoPeerCertClientOnce": self = .sslVerifyPeerWithFailIfNoPeerCertClientOnce 138 | default: 139 | return nil 140 | } 141 | } 142 | } 143 | 144 | extension TLSConfiguration { 145 | init?(data: [String:Any]) { 146 | guard let certPath = data["certPath"] as? String else { 147 | return nil 148 | } 149 | self.init(certPath: certPath, 150 | keyPath: data["keyPath"] as? String, 151 | caCertPath: data["caCertPath"] as? String, 152 | certVerifyMode: OpenSSLVerifyMode(string: data["verifyMode"] as? String ?? ""), 153 | cipherList: data["cipherList"] as? [String] ?? TLSConfiguration.defaultCipherList, 154 | alpnSupport: (data["alpnSupport"] as? [String] ?? []).compactMap { HTTPServer.ALPNSupport(rawValue: $0) }) 155 | } 156 | } 157 | 158 | private func findRequestFilter(_ named: String, data: [String:Any]) throws -> HTTPRequestFilter? { 159 | if let sym = findFunc(named, suffixes: ["P11PerfectHTTP17HTTPRequestFilter_"]) { 160 | let fn = UnsafeMutablePointer.allocate(capacity: 1) 161 | defer { 162 | fn.deinitialize(count: 1) 163 | fn.deallocate() 164 | } 165 | fn.initialize(to: templateWithData) 166 | let p = UnsafeMutableRawPointer(fn).assumingMemoryBound(to: swift_func_wrapper.self) 167 | p.pointee.functionObject.pointee.address = UInt(bitPattern: sym) 168 | let callMe = fn.pointee 169 | return try callMe(data) 170 | } 171 | return nil 172 | } 173 | 174 | private func findResponseFilter(_ named: String, data: [String:Any]) throws -> HTTPResponseFilter? { 175 | if let sym = findFunc(named, suffixes: ["P11PerfectHTTP18HTTPResponseFilter_"]) { 176 | let fn = UnsafeMutablePointer.allocate(capacity: 1) 177 | defer { 178 | fn.deinitialize(count: 1) 179 | fn.deallocate() 180 | } 181 | fn.initialize(to: templateWithData) 182 | let p = UnsafeMutableRawPointer(fn).assumingMemoryBound(to: swift_func_wrapper.self) 183 | p.pointee.functionObject.pointee.address = UInt(bitPattern: sym) 184 | let callMe = fn.pointee 185 | return try callMe(data) 186 | } 187 | return nil 188 | } 189 | 190 | // DRAGONS GONE ------------------------------------------- 191 | 192 | private let kv: [String:HTTPFilterPriority] = ["low":.low, "medium":.medium, "high":.high] 193 | 194 | func filtersFrom(data: [[String:Any]]) throws -> [(HTTPRequestFilter, HTTPFilterPriority)] { 195 | var ret = [(HTTPRequestFilter, HTTPFilterPriority)]() 196 | for e in data { 197 | guard let type = e["type"] as? String, type == "request" else { 198 | continue 199 | } 200 | let prio = kv[e["priority"] as? String ?? "high"] ?? .high 201 | let filterObj: HTTPRequestFilter 202 | switch e["name"] { 203 | case let name as String: 204 | guard let tryFilterObj = try findRequestFilter(name, data: e) else { 205 | throw PerfectError.apiError("The filter \(name) was not found") 206 | } 207 | filterObj = tryFilterObj 208 | case let fnc as ReturnsRequestFilterGivenData: 209 | filterObj = try fnc(e) 210 | default: 211 | throw PerfectError.apiError("The indicated filter could not be found \(String(describing: e["name"])).") 212 | } 213 | ret.append((filterObj, prio)) 214 | } 215 | return ret 216 | } 217 | 218 | func filtersFrom(data: [[String:Any]]) throws -> [(HTTPResponseFilter, HTTPFilterPriority)] { 219 | var ret = [(HTTPResponseFilter, HTTPFilterPriority)]() 220 | for e in data { 221 | guard let type = e["type"] as? String, type == "response" else { 222 | continue 223 | } 224 | let prio = kv[e["priority"] as? String ?? "high"] ?? .high 225 | let filterObj: HTTPResponseFilter 226 | switch e["name"] { 227 | case let name as String: 228 | guard let tryFilterObj = try findResponseFilter(name, data: e) else { 229 | throw PerfectError.apiError("The filter \(name) was not found") 230 | } 231 | filterObj = tryFilterObj 232 | case let fnc as ReturnsResponseFilterGivenData: 233 | filterObj = try fnc(e) 234 | default: 235 | throw PerfectError.apiError("The indicated filter could not be found \(String(describing: e["name"])).") 236 | } 237 | ret.append((filterObj, prio)) 238 | } 239 | return ret 240 | } 241 | 242 | // ------------------------------------------- 243 | 244 | public struct HTTPHandler { 245 | /// Returns a handler which will serve static files out of the indicated directory. 246 | /// If allowResponseFilters is false (which is the default) then the file will be sent in 247 | /// the most efficient way possible and output filters will be bypassed. 248 | public static func staticFiles(data: [String:Any]) throws -> RequestHandler { 249 | let documentRoot = data["documentRoot"] as? String ?? "./webroot" 250 | let allowResponseFilters = data["allowResponseFilters"] as? Bool ?? false 251 | return { 252 | req, resp in 253 | StaticFileHandler(documentRoot: documentRoot, allowResponseFilters: allowResponseFilters) 254 | .handleRequest(request: req, response: resp) 255 | } 256 | } 257 | 258 | /// Redirect any matching URI to the server indicated by "base". 259 | /// The request.uri will be appended to the base. 260 | public static func redirect(data: [String:Any]) throws -> RequestHandler { 261 | guard let base = data["base"] as? String else { 262 | fatalError("HTTPHandler.redirect(data: [String:Any]) requires a value for key \"base\".") 263 | } 264 | return { 265 | req, resp in 266 | resp.status = .movedPermanently 267 | resp.setHeader(.location, value: base + req.uri) 268 | resp.completed() 269 | } 270 | } 271 | } 272 | 273 | public struct HTTPFilter { 274 | /// Any 404 responses will have the body replaced by the indictated file's contents. 275 | public static func custom404(data: [String:Any]) throws -> HTTPResponseFilter { 276 | guard let path = data["path"] as? String else { 277 | fatalError("HTTPFilter.custom404(data: [String:Any]) requires a value for key \"path\".") 278 | } 279 | struct Filter404: HTTPResponseFilter { 280 | let path: String 281 | func filterHeaders(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 282 | if case .notFound = response.status { 283 | do { 284 | response.setBody(string: try File(path).readString()) 285 | } catch { 286 | response.setBody(string: "An error occurred but I could not find the error file. \(response.status)") 287 | } 288 | response.setHeader(.contentLength, value: "\(response.bodyBytes.count)") 289 | } 290 | return callback(.continue) 291 | } 292 | func filterBody(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 293 | callback(.continue) 294 | } 295 | } 296 | return Filter404(path: path) 297 | } 298 | 299 | public static func customReqFilter(data: [String:Any]) throws -> HTTPRequestFilter { 300 | struct ReqFilter: HTTPRequestFilter { 301 | func filter(request: HTTPRequest, response: HTTPResponse, callback: (HTTPRequestFilterResult) -> ()) { 302 | callback(.continue(request, response)) 303 | } 304 | } 305 | return ReqFilter() 306 | } 307 | } 308 | 309 | //testing 310 | //public func uniqueName(data: [String:Any]) throws -> RequestHandler { throw PerfectError.apiError("hi") } 311 | //public struct UniqueStruct { 312 | // public static func uniqueName(data: [String:Any]) throws -> RequestHandler { throw PerfectError.apiError("hi") } 313 | // public enum UniqueNestedEnum { 314 | // public static func uniqueName(data: [String:Any]) throws -> RequestHandler { throw PerfectError.apiError("hi") } 315 | // } 316 | //} 317 | //public enum UniqueEnum { 318 | // public static func uniqueName(data: [String:Any]) throws -> RequestHandler { throw PerfectError.apiError("hi") } 319 | //} 320 | //public class UniqueClass { 321 | // public static func uniqueName(data: [String:Any]) throws -> RequestHandler { throw PerfectError.apiError("hi") } 322 | //} 323 | 324 | -------------------------------------------------------------------------------- /Sources/PerfectHTTPServer/HTTPServer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPServer.swift 3 | // PerfectLib 4 | // 5 | // Created by Kyle Jessup on 2015-10-23. 6 | // Copyright (C) 2015 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | import PerfectNet 21 | import PerfectThread 22 | import PerfectLib 23 | import PerfectHTTP 24 | 25 | #if os(Linux) 26 | import SwiftGlibc 27 | import LinuxBridge 28 | #else 29 | import Darwin 30 | #endif 31 | 32 | /// Stand-alone HTTP server. 33 | public class HTTPServer: ServerInstance { 34 | public typealias certKeyPair = (sslCert: String, sslKey: String) 35 | private var net: NetTCP? 36 | /// The directory in which web documents are sought. 37 | /// Setting the document root will add a default URL route which permits 38 | /// static files to be served from within. 39 | public var documentRoot = "./webroot" { // Given a "safe" default 40 | didSet { 41 | do { 42 | let dir = Dir(documentRoot) 43 | if !dir.exists { 44 | try Dir(documentRoot).create() 45 | } 46 | self.routes.add(method: .get, uri: "/**", handler: { 47 | request, response in 48 | StaticFileHandler(documentRoot: request.documentRoot).handleRequest(request: request, response: response) 49 | }) 50 | } catch { 51 | Log.terminal(message: "The document root \(documentRoot) could not be created.") 52 | } 53 | } 54 | } 55 | /// The port on which the server is listening. 56 | public var serverPort: UInt16 = 0 57 | /// The local address on which the server is listening. The default of 0.0.0.0 indicates any address. 58 | public var serverAddress = "0.0.0.0" 59 | /// Switch to user after binding port 60 | public var runAsUser: String? 61 | /// The canonical server name. 62 | /// This is important if utilizing the `HTTPRequest.serverName` property. 63 | public var serverName = "" 64 | public var ssl: certKeyPair? 65 | public var caCert: String? 66 | public var certVerifyMode: OpenSSLVerifyMode? 67 | public var cipherList = [ 68 | "ECDHE-ECDSA-AES256-GCM-SHA384", 69 | "ECDHE-ECDSA-AES128-GCM-SHA256", 70 | "ECDHE-ECDSA-AES256-CBC-SHA384", 71 | "ECDHE-ECDSA-AES256-CBC-SHA", 72 | "ECDHE-ECDSA-AES128-CBC-SHA256", 73 | "ECDHE-ECDSA-AES128-CBC-SHA", 74 | "ECDHE-RSA-AES256-GCM-SHA384", 75 | "ECDHE-RSA-AES128-GCM-SHA256", 76 | "ECDHE-RSA-AES256-CBC-SHA384", 77 | "ECDHE-RSA-AES128-CBC-SHA256", 78 | "ECDHE-RSA-AES128-CBC-SHA", 79 | "ECDHE-RSA-AES256-SHA384", 80 | "ECDHE-ECDSA-AES256-SHA384", 81 | "ECDHE-RSA-AES256-SHA", 82 | "ECDHE-ECDSA-AES256-SHA"] 83 | 84 | var requestFilters = [[HTTPRequestFilter]]() 85 | var responseFilters = [[HTTPResponseFilter]]() 86 | 87 | /// Routing support 88 | private var routes = Routes() 89 | private var routeNavigator: RouteNavigator? 90 | 91 | public enum ALPNSupport: String { 92 | case http11 = "http/1.1", http2 = "h2" 93 | } 94 | public var alpnSupport = [ALPNSupport.http11] 95 | 96 | /// Initialize the server object. 97 | public init() {} 98 | 99 | @available(*, deprecated, message: "Set documentRoot directly") 100 | public init(documentRoot: String) { 101 | self.documentRoot = documentRoot 102 | } 103 | 104 | /// Add the Routes to this server. 105 | public func addRoutes(_ routes: Routes) { 106 | self.routes.add(routes) 107 | } 108 | 109 | /// Set the request filters. Each is provided along with its priority. 110 | /// The filters can be provided in any order. High priority filters will be sorted above lower priorities. 111 | /// Filters of equal priority will maintain the order given here. 112 | @discardableResult 113 | public func setRequestFilters(_ request: [(HTTPRequestFilter, HTTPFilterPriority)]) -> HTTPServer { 114 | let high = request.filter { $0.1 == HTTPFilterPriority.high }.map { $0.0 }, 115 | med = request.filter { $0.1 == HTTPFilterPriority.medium }.map { $0.0 }, 116 | low = request.filter { $0.1 == HTTPFilterPriority.low }.map { $0.0 } 117 | if !high.isEmpty { 118 | requestFilters.append(high) 119 | } 120 | if !med.isEmpty { 121 | requestFilters.append(med) 122 | } 123 | if !low.isEmpty { 124 | requestFilters.append(low) 125 | } 126 | return self 127 | } 128 | 129 | /// Set the response filters. Each is provided along with its priority. 130 | /// The filters can be provided in any order. High priority filters will be sorted above lower priorities. 131 | /// Filters of equal priority will maintain the order given here. 132 | @discardableResult 133 | public func setResponseFilters(_ response: [(HTTPResponseFilter, HTTPFilterPriority)]) -> HTTPServer { 134 | let high = response.filter { $0.1 == HTTPFilterPriority.high }.map { $0.0 }, 135 | med = response.filter { $0.1 == HTTPFilterPriority.medium }.map { $0.0 }, 136 | low = response.filter { $0.1 == HTTPFilterPriority.low }.map { $0.0 } 137 | if !high.isEmpty { 138 | responseFilters.append(high) 139 | } 140 | if !med.isEmpty { 141 | responseFilters.append(med) 142 | } 143 | if !low.isEmpty { 144 | responseFilters.append(low) 145 | } 146 | return self 147 | } 148 | 149 | @available(*, deprecated, message: "Set serverPort and call start()") 150 | public func start(port: UInt16, bindAddress: String = "0.0.0.0") throws { 151 | self.serverPort = port 152 | self.serverAddress = bindAddress 153 | try self.start() 154 | } 155 | 156 | @available(*, deprecated, message: "Set serverPort and ssl directly then call start()") 157 | public func start(port: UInt16, sslCert: String, sslKey: String, bindAddress: String = "0.0.0.0") throws { 158 | self.serverPort = port 159 | self.serverAddress = bindAddress 160 | self.ssl = (sslCert: sslCert, sslKey: sslKey) 161 | try self.start() 162 | } 163 | 164 | /// Bind the server to the designated address/port 165 | public func bind() throws { 166 | if let (cert, key) = ssl { 167 | let socket = NetTCPSSL() 168 | try socket.bind(port: serverPort, address: serverAddress) 169 | socket.cipherList = self.cipherList 170 | if let verifyMode = certVerifyMode, 171 | let cert = caCert, 172 | verifyMode != .sslVerifyNone { 173 | 174 | guard socket.setClientCA(path: cert, verifyMode: verifyMode) else { 175 | let code = Int32(socket.errorCode()) 176 | throw PerfectError.networkError(code, "Error setting clientCA : \(socket.errorStr(forCode: code))") 177 | } 178 | } 179 | let sourcePrefix = "-----BEGIN" 180 | if cert.hasPrefix(sourcePrefix) { 181 | guard socket.useCertificateChain(cert: cert) else { 182 | let code = Int32(socket.errorCode()) 183 | throw PerfectError.networkError(code, "Error setting certificate chain file: \(socket.errorStr(forCode: code))") 184 | } 185 | } else { 186 | guard socket.useCertificateChainFile(cert: cert) else { 187 | let code = Int32(socket.errorCode()) 188 | throw PerfectError.networkError(code, "Error setting certificate chain file: \(socket.errorStr(forCode: code))") 189 | } 190 | } 191 | if key.hasPrefix(sourcePrefix) { 192 | guard socket.usePrivateKey(cert: key) else { 193 | let code = Int32(socket.errorCode()) 194 | throw PerfectError.networkError(code, "Error setting private key file: \(socket.errorStr(forCode: code))") 195 | } 196 | } else { 197 | guard socket.usePrivateKeyFile(cert: key) else { 198 | let code = Int32(socket.errorCode()) 199 | throw PerfectError.networkError(code, "Error setting private key file: \(socket.errorStr(forCode: code))") 200 | } 201 | } 202 | guard socket.checkPrivateKey() else { 203 | let code = Int32(socket.errorCode()) 204 | throw PerfectError.networkError(code, "Error validating private key file: \(socket.errorStr(forCode: code))") 205 | } 206 | socket.enableALPN(protocols: self.alpnSupport.map { $0.rawValue }) 207 | self.net = socket 208 | } else { 209 | let net = NetTCP() 210 | try net.bind(port: serverPort, address: serverAddress) 211 | self.net = net 212 | } 213 | } 214 | 215 | /// Start the server. Does not return until the server terminates. 216 | public func start() throws { 217 | if nil == self.net { 218 | try bind() 219 | } 220 | guard let net = self.net else { 221 | throw PerfectError.networkError(-1, "The socket was not bound.") 222 | } 223 | let witess = (net is NetTCPSSL) ? "HTTPS" : "HTTP" 224 | Log.info(message: "Starting \(witess) server \(self.serverName) on \(self.serverAddress):\(self.serverPort)") 225 | try self.startInner() 226 | } 227 | 228 | func accepted(net: NetTCP) { 229 | netHandleQueue.async { 230 | self.handleConnection(net) 231 | } 232 | } 233 | 234 | private func startInner() throws { 235 | // 1.0 compatability ONLY 236 | if let compatRoutes = compatRoutes { 237 | self.addRoutes(compatRoutes) 238 | } 239 | 240 | guard let sock = self.net else { 241 | Log.terminal(message: "Server could not be started. Socket was not initialized.") 242 | } 243 | if let runAs = self.runAsUser { 244 | try PerfectServer.switchTo(userName: runAs) 245 | } 246 | sock.listen() 247 | defer { sock.close() } 248 | self.serverAddress = sock.localAddress?.host ?? "" 249 | self.routeNavigator = self.routes.navigator 250 | sock.forEachAccept { 251 | [weak self] net in 252 | guard let net = net else { 253 | return 254 | } 255 | self?.accepted(net: net) 256 | } 257 | } 258 | 259 | /// Stop the server by closing the accepting TCP socket. Calling this will cause the server to break out of the otherwise blocking `start` function. 260 | public func stop() { 261 | if let n = self.net { 262 | self.net = nil 263 | n.close() 264 | } 265 | } 266 | 267 | func handleConnection(_ net: NetTCP) { 268 | var flag = 1 269 | _ = setsockopt(net.fd.fd, Int32(IPPROTO_TCP), TCP_NODELAY, &flag, UInt32(MemoryLayout.size)) 270 | if let netSSL = net as? NetTCPSSL, let neg = netSSL.alpnNegotiated, neg == ALPNSupport.http2.rawValue { 271 | _ = HTTP2PrefaceValidator(net, timeoutSeconds: 5.0) { 272 | _ = HTTP2Session(net, server: self) 273 | } 274 | return 275 | } 276 | let req = HTTP11Request(connection: net) 277 | req.serverName = self.serverName 278 | req.readRequest { [weak self] 279 | status in 280 | if case .ok = status { 281 | self?.runRequest(req) 282 | } else { 283 | net.close() 284 | } 285 | } 286 | } 287 | 288 | func runRequest(_ request: HTTP11Request) { 289 | request.documentRoot = self.documentRoot 290 | let net = request.connection 291 | let response = HTTP11Response(request: request, filters: responseFilters.isEmpty ? nil : responseFilters.makeIterator()) 292 | if response.isKeepAlive { 293 | response.completedCallback = { [weak self] in 294 | if let `self` = self { 295 | netHandleQueue.async { 296 | self.handleConnection(net) 297 | } 298 | } 299 | } 300 | } 301 | let oldCompletion = response.completedCallback 302 | response.completedCallback = { 303 | response.completedCallback = nil 304 | response.flush { 305 | ok in 306 | guard ok else { 307 | net.close() 308 | return 309 | } 310 | if let cb = oldCompletion { 311 | cb() 312 | } 313 | } 314 | } 315 | filterAndRun(request: request, response: response) 316 | } 317 | 318 | func filterAndRun(request: HTTPRequest, response: HTTPResponse) { 319 | if requestFilters.isEmpty { 320 | routeRequest(request, response: response) 321 | } else { 322 | filterRequest(request, response: response, allFilters: requestFilters.makeIterator()) 323 | } 324 | } 325 | 326 | private func filterRequest(_ request: HTTPRequest, response: HTTPResponse, allFilters: IndexingIterator<[[HTTPRequestFilter]]>) { 327 | var filters = allFilters 328 | if let prioFilters = filters.next() { 329 | filterRequest(request, response: response, allFilters: filters, prioFilters: prioFilters.makeIterator()) 330 | } else { 331 | routeRequest(request, response: response) 332 | } 333 | } 334 | 335 | private func filterRequest(_ request: HTTPRequest, response: HTTPResponse, 336 | allFilters: IndexingIterator<[[HTTPRequestFilter]]>, 337 | prioFilters: IndexingIterator<[HTTPRequestFilter]>) { 338 | var prioFilters = prioFilters 339 | guard let filter = prioFilters.next() else { 340 | return filterRequest(request, response: response, allFilters: allFilters) 341 | } 342 | filter.filter(request: request, response: response) { 343 | result in 344 | switch result { 345 | case .continue(let req, let res): 346 | self.filterRequest(req, response: res, allFilters: allFilters, prioFilters: prioFilters) 347 | case .execute(let req, let res): 348 | self.filterRequest(req, response: res, allFilters: allFilters) 349 | case .halt(_, let res): 350 | res.completed() 351 | } 352 | } 353 | } 354 | 355 | private func routeRequest(_ request: HTTPRequest, response: HTTPResponse) { 356 | if let nav = routeNavigator, 357 | let handlers = nav.findHandlers(pathComponents: request.pathComponents, webRequest: request) { 358 | // cheating 359 | if let resp = response as? HTTP2Response { 360 | resp.handlers = handlers.makeIterator() 361 | resp.next() 362 | } else if let resp = response as? HTTP11Response { 363 | resp.handlers = handlers.makeIterator() 364 | resp.next() 365 | } else { 366 | handlers.last?(request, response) 367 | } 368 | } else { 369 | response.status = .notFound 370 | response.appendBody(string: "The file \(request.path) was not found.") 371 | response.completed() 372 | } 373 | } 374 | } 375 | 376 | #if swift(>=4.1) 377 | #else 378 | // Added for Swift 4.0/4.1 compat 379 | extension UnsafeMutableRawBufferPointer { 380 | static func allocate(byteCount: Int, alignment: Int) -> UnsafeMutableRawBufferPointer { 381 | return allocate(count: byteCount) 382 | } 383 | } 384 | extension UnsafeMutablePointer { 385 | func deallocate() { 386 | deallocate(capacity: 0) 387 | } 388 | } 389 | extension Collection { 390 | func compactMap(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] { 391 | return try flatMap(transform) 392 | } 393 | } 394 | #endif 395 | -------------------------------------------------------------------------------- /Sources/PerfectHTTPServer/HTTP2/HTTP2Session.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTP2Session.swift 3 | // PerfectHTTPServer 4 | // 5 | // Created by Kyle Jessup on 2017-06-20. 6 | // 7 | // 8 | 9 | import PerfectNet 10 | import PerfectLib 11 | import PerfectThread 12 | import PerfectHTTP 13 | 14 | // !FIX! need a better scheme for this 15 | let receiveWindowLowWater = 1024*10 16 | let receiveWindowTopOff: Int = 25169664//1024*1*1024 17 | 18 | 19 | // receives notification of an unexpected network shutdown 20 | protocol HTTP2NetErrorDelegate: class { 21 | func networkShutdown() 22 | } 23 | 24 | protocol HTTP2FrameReceiver: class { 25 | func receiveFrame(_ frame: HTTP2Frame) 26 | } 27 | 28 | struct HTTP2FlowWindows { 29 | var serverWindowSize: Int 30 | var clientWindowSize: Int 31 | } 32 | 33 | extension Bytes { 34 | @discardableResult 35 | func importFrame32(_ int: UInt32) -> Self { 36 | import32Bits(from: int.hostToNet) 37 | return self 38 | } 39 | 40 | @discardableResult 41 | func importFrame32(_ int: Int) -> Self { 42 | import32Bits(from: UInt32(int).hostToNet) 43 | return self 44 | } 45 | 46 | @discardableResult 47 | func importFrame16(_ int: UInt16) -> Self { 48 | import16Bits(from: int.hostToNet) 49 | return self 50 | } 51 | } 52 | 53 | // A single HTTP/2 connection handling multiple requests and responses 54 | class HTTP2Session: Hashable, HTTP2NetErrorDelegate, HTTP2FrameReceiver { 55 | 56 | enum SessionState { 57 | case setup 58 | case active 59 | } 60 | 61 | private static let pinLock = Threading.Lock() 62 | private static var pins = Set() 63 | 64 | static func ==(lhs: HTTP2Session, rhs: HTTP2Session) -> Bool { 65 | return lhs.net.fd.fd == rhs.net.fd.fd 66 | } 67 | 68 | #if swift(>=4.2) 69 | func hash(into hasher: inout Hasher) { 70 | hasher.combine(Int(net.fd.fd)) 71 | } 72 | #else 73 | var hashValue: Int { return Int(net.fd.fd) } 74 | #endif 75 | 76 | let net: NetTCP 77 | let server: HTTPServer 78 | var debug = false 79 | 80 | var frameReader: HTTP2FrameReader? 81 | var frameWriter: HTTP2FrameWriter? 82 | 83 | var clientSettings = HTTP2SessionSettings() 84 | var serverSettings = HTTP2SessionSettings() 85 | var connectionFlowWindows: HTTP2FlowWindows 86 | var state = SessionState.setup 87 | 88 | let decoder = HPACKDecoder() 89 | let encoder = HPACKEncoder() 90 | let encoderLock = Threading.Lock() 91 | 92 | fileprivate let streamsLock = Threading.Lock() 93 | fileprivate var streams = [UInt32:HTTP2Request]() 94 | 95 | init(_ net: NetTCP, 96 | server: HTTPServer, 97 | debug: Bool = http2Debug) { 98 | self.net = net 99 | self.server = server 100 | self.debug = debug 101 | self.connectionFlowWindows = HTTP2FlowWindows(serverWindowSize: 65535, clientWindowSize: 65535) 102 | frameReader = HTTP2FrameReader(net, frameReceiver: self, errorDelegate: self) 103 | frameWriter = HTTP2FrameWriter(net, errorDelegate: self) 104 | pinSelf() 105 | sendInitialSettings() 106 | } 107 | 108 | deinit { 109 | if debug { 110 | print("~HTTP2Session") 111 | } 112 | } 113 | 114 | private func pinSelf() { 115 | HTTP2Session.pinLock.lock() 116 | HTTP2Session.pins.insert(self) 117 | HTTP2Session.pinLock.unlock() 118 | } 119 | 120 | private func unpinSelf() { 121 | HTTP2Session.pinLock.lock() 122 | HTTP2Session.pins.remove(self) 123 | HTTP2Session.pinLock.unlock() 124 | } 125 | 126 | func sendInitialSettings() { 127 | // !FIX! need to make this configurable 128 | 129 | serverSettings.headerTableSize = 4096 130 | serverSettings.maxConcurrentStreams = 100 131 | serverSettings.initialWindowSize = 65535 132 | 133 | let b = Bytes() 134 | b.importFrame16(headerTableSize) 135 | .importFrame32(UInt32(serverSettings.headerTableSize)) 136 | b.importFrame16(maxConcurrentStreams) 137 | .importFrame32(UInt32(serverSettings.maxConcurrentStreams)) 138 | b.importFrame16(initialWindowSize) 139 | .importFrame32(UInt32(serverSettings.initialWindowSize)) 140 | do { 141 | let frame = HTTP2Frame(type: .settings, payload: b.data) 142 | frameWriter?.enqueueFrame(frame) 143 | if debug { 144 | print("server settings:\n\theaderTableSize: \(serverSettings.headerTableSize)\n\tenablePush: \(serverSettings.enablePush)\n\tmaxConcurrentStreams: \(serverSettings.maxConcurrentStreams)\n\tinitialWindowSize: \(serverSettings.initialWindowSize)\n\tmaxFrameSize: \(serverSettings.maxFrameSize)\n\tmaxHeaderListSize: \(serverSettings.maxHeaderListSize)") 145 | } 146 | } 147 | } 148 | 149 | func networkShutdown() { 150 | net.shutdown() 151 | unpinSelf() 152 | } 153 | 154 | func fatalError(streamId: UInt32 = 0, error: HTTP2Error, msg: String) { 155 | if streamId != 0 { 156 | removeRequest(streamId) 157 | } 158 | let bytes = Bytes() 159 | bytes.importFrame32(UInt32(streamId)) 160 | .importFrame32(error.rawValue) 161 | .importBytes(from: Array(msg.utf8)) 162 | let frame = HTTP2Frame(type: .goAway, payload: bytes.data) 163 | frameWriter?.enqueueFrame(frame) 164 | frameWriter?.waitUntilEmpty { 165 | self.networkShutdown() 166 | } 167 | } 168 | } 169 | 170 | extension HTTP2Session { 171 | func getRequest(_ streamId: UInt32) -> HTTP2Request? { 172 | streamsLock.lock() 173 | defer { 174 | streamsLock.unlock() 175 | } 176 | return streams[streamId] 177 | } 178 | 179 | func putRequest(_ request: HTTP2Request) -> Bool { 180 | streamsLock.lock() 181 | defer { 182 | streamsLock.unlock() 183 | } 184 | if streams.count >= serverSettings.maxConcurrentStreams { 185 | return false 186 | } 187 | streams[request.streamId] = request 188 | return true 189 | } 190 | 191 | func removeRequest(_ streamId: UInt32) { 192 | streamsLock.lock() 193 | defer { 194 | streamsLock.unlock() 195 | } 196 | streams.removeValue(forKey: streamId) 197 | } 198 | } 199 | 200 | extension HTTP2Session { 201 | // this is called on the main frame reading thread 202 | func receiveFrame(_ frame: HTTP2Frame) { 203 | if debug { 204 | print("recv frame: \(frame.type)") 205 | } 206 | if state == .setup && frame.type != .settings { 207 | fatalError(error: .protocolError, msg: "Settings expected") 208 | return 209 | } 210 | if frame.streamId == 0 { 211 | switch frame.type { 212 | case .settings: 213 | settingsFrame(frame) 214 | case .ping: 215 | pingFrame(frame) 216 | case .goAway: 217 | goAwayFrame(frame) 218 | case .windowUpdate: 219 | windowUpdateFrame(frame) 220 | default: 221 | fatalError(error: .protocolError, msg: "Frame requires stream id") 222 | } 223 | } else { 224 | switch frame.type { 225 | case .headers: 226 | headersFrame(frame) 227 | case .continuation: 228 | continuationFrame(frame) 229 | case .data: 230 | dataFrame(frame) 231 | case .priority: 232 | priorityFrame(frame) 233 | case .cancelStream: 234 | cancelStreamFrame(frame) 235 | case .windowUpdate: 236 | windowUpdateFrame(frame) 237 | default: 238 | fatalError(error: .protocolError, msg: "Invalid frame with stream id") 239 | } 240 | } 241 | } 242 | 243 | func settingsFrame(_ frame: HTTP2Frame) { 244 | let isAck = (frame.flags & flagSettingsAck) != 0 245 | if !isAck { // ACK settings receipt 246 | state = .active 247 | if let payload = frame.payload { 248 | processSettingsPayload(Bytes(existingBytes: payload)) 249 | } 250 | let response = HTTP2Frame(type: HTTP2FrameType.settings, 251 | flags: flagSettingsAck) 252 | frameWriter?.enqueueFrame(response) 253 | } else { 254 | if debug { 255 | print("\tack") 256 | } 257 | increaseServerConnectionWindow(by: receiveWindowTopOff) 258 | } 259 | } 260 | 261 | func windowUpdateFrame(_ frame: HTTP2Frame) { 262 | guard let b = frame.payload, b.count == 4 else { 263 | return fatalError(error: .protocolError, msg: "Invalid frame") 264 | } 265 | let bytes = Bytes(existingBytes: b) 266 | let windowSize = Int(bytes.export32Bits().netToHost) 267 | guard windowSize > 0 else { 268 | return fatalError(error: .protocolError, msg: "Received window size of zero") 269 | } 270 | if frame.streamId == 0 { 271 | increaseClientConnectionWindow(by: windowSize) 272 | } else { 273 | increaseClientWindow(stream: frame.streamId, by: windowSize) 274 | } 275 | } 276 | 277 | func headersFrame(_ frame: HTTP2Frame) { 278 | let streamId = frame.streamId 279 | let request = HTTP2Request(streamId, session: self) 280 | if putRequest(request) { 281 | request.headersFrame(frame) 282 | } else { 283 | let frame = HTTP2Frame(type: .cancelStream, streamId: frame.streamId, payload: Bytes().importFrame32(HTTP2Error.refusedStream.rawValue).data) 284 | frameWriter?.enqueueFrame(frame) 285 | } 286 | } 287 | 288 | func continuationFrame(_ frame: HTTP2Frame) { 289 | let streamId = frame.streamId 290 | guard let request = getRequest(streamId) else { 291 | return fatalError(error: .streamClosed, msg: "Invalid stream id") 292 | } 293 | request.continuationFrame(frame) 294 | } 295 | 296 | func dataFrame(_ frame: HTTP2Frame) { 297 | let streamId = frame.streamId 298 | guard let request = getRequest(streamId) else { 299 | return fatalError(error: .streamClosed, msg: "Invalid stream id") 300 | } 301 | let count = frame.payload?.count ?? 0 302 | if connectionFlowWindows.serverWindowSize - count < receiveWindowLowWater { 303 | increaseServerConnectionWindow(by: receiveWindowTopOff) 304 | } 305 | if request.streamFlowWindows.serverWindowSize - count < receiveWindowLowWater { 306 | increaseServerWindow(stream: request.streamId, by: receiveWindowTopOff) 307 | } 308 | connectionFlowWindows.serverWindowSize -= count 309 | request.streamFlowWindows.serverWindowSize -= count 310 | request.dataFrame(frame) 311 | } 312 | 313 | func priorityFrame(_ frame: HTTP2Frame) { 314 | let streamId = frame.streamId 315 | guard let request = getRequest(streamId) else { 316 | // Firefox will send this before HEADERS, with the new stream id 317 | return //fatalError(error: .streamClosed, msg: "Invalid stream id") 318 | } 319 | request.priorityFrame(frame) 320 | } 321 | 322 | func cancelStreamFrame(_ frame: HTTP2Frame) { 323 | let streamId = frame.streamId 324 | guard let request = getRequest(streamId) else { 325 | return //fatalError(error: .streamClosed, msg: "Invalid stream id") 326 | } 327 | request.cancelStreamFrame(frame) 328 | if debug { 329 | print("\t\(streamId)") 330 | } 331 | } 332 | 333 | func pingFrame(_ frame: HTTP2Frame) { 334 | guard frame.streamId == 0 else { 335 | fatalError(error: .protocolError, msg: "Ping contained stream id") 336 | return 337 | } 338 | let frame = HTTP2Frame(type: .ping, flags: flagPingAck, streamId: 0, payload: frame.payload) 339 | frameWriter?.enqueueFrame(frame, highPriority: true) 340 | } 341 | 342 | func goAwayFrame(_ frame: HTTP2Frame) { 343 | if let bytes = frame.payload { 344 | let b = Bytes(existingBytes: bytes) 345 | let lastStreamId = b.export32Bits().netToHost 346 | let errorCode = b.export32Bits().netToHost 347 | let remainingBytes = b.exportBytes(count: b.availableExportBytes) 348 | let errorStr = String(validatingUTF8: remainingBytes) 349 | if debug { 350 | print("Bye: last stream: \(lastStreamId) \(HTTP2Error(rawValue: errorCode)?.rawValue ?? 0) \(errorStr ?? "")") 351 | } 352 | } 353 | networkShutdown() 354 | } 355 | } 356 | 357 | extension HTTP2Session { 358 | // send a WINDOW_UPDATE stream 0 359 | func increaseServerConnectionWindow(by: Int) { 360 | let frame = HTTP2Frame(type: .windowUpdate, payload: Bytes().importFrame32(receiveWindowTopOff).data) 361 | frameWriter?.enqueueFrame(frame) 362 | connectionFlowWindows.serverWindowSize += by 363 | if debug { 364 | print("send frame: windowUpdate \t+\(by) for connection = \(connectionFlowWindows.serverWindowSize)") 365 | } 366 | } 367 | 368 | // received a WINDOW_UPDATE stream 0 369 | func increaseClientConnectionWindow(by: Int) { 370 | connectionFlowWindows.clientWindowSize += by 371 | if debug { 372 | print("\t+\(by) for connection = \(connectionFlowWindows.clientWindowSize)") 373 | } 374 | // unblock any stalled requests 375 | streamsLock.lock() 376 | defer { 377 | streamsLock.unlock() 378 | } 379 | streams.forEach { 380 | tup in 381 | if let u = tup.value.unblockCallback { 382 | tup.value.unblockCallback = nil 383 | u() 384 | } 385 | } 386 | } 387 | 388 | // received a WINDOW_UPDATE for stream x 389 | func increaseClientWindow(stream: UInt32, by: Int) { 390 | guard let request = getRequest(stream) else { 391 | return 392 | } 393 | request.streamFlowWindows.clientWindowSize += by 394 | if let u = request.unblockCallback { 395 | request.unblockCallback = nil 396 | u() 397 | } 398 | if debug { 399 | print("\t+\(by) for stream \(stream) = \(request.streamFlowWindows.clientWindowSize)") 400 | } 401 | } 402 | 403 | // send a WINDOW_UPDATE for stream x 404 | func increaseServerWindow(stream: UInt32, by: Int) { 405 | guard let request = getRequest(stream) else { 406 | return 407 | } 408 | let frame = HTTP2Frame(type: .windowUpdate, streamId: stream, payload: Bytes().importFrame32(receiveWindowTopOff).data) 409 | frameWriter?.enqueueFrame(frame) 410 | request.streamFlowWindows.serverWindowSize += by 411 | if debug { 412 | print("send frame: windowUpdate \t+\(by) for stream \(stream) = \(request.streamFlowWindows.serverWindowSize)") 413 | } 414 | } 415 | 416 | // send a WINDOW_UPDATE for stream x 417 | func decreaseClientWindow(stream: UInt32, by: Int) { 418 | guard let request = getRequest(stream) else { 419 | return 420 | } 421 | request.streamFlowWindows.clientWindowSize -= by 422 | connectionFlowWindows.clientWindowSize -= by 423 | } 424 | } 425 | 426 | extension HTTP2Session { 427 | func processSettingsPayload(_ b: Bytes) { 428 | while b.availableExportBytes >= 6 { 429 | let identifier = b.export16Bits().netToHost 430 | let value = Int(b.export32Bits().netToHost) 431 | switch identifier { 432 | case settingsHeaderTableSize: 433 | clientSettings.headerTableSize = Int(value) 434 | decoder.setMaxHeaderTableSize(maxHeaderTableSize: Int(value)) 435 | case settingsEnablePush: 436 | clientSettings.enablePush = value == 1 437 | case settingsMaxConcurrentStreams: 438 | clientSettings.maxConcurrentStreams = value 439 | case settingsInitialWindowSize: 440 | clientSettings.initialWindowSize = value 441 | // !FIX! need to update all active streams by the difference between new and old values 442 | case settingsMaxFrameSize: 443 | guard value <= 16777215 else { 444 | fatalError(error: .protocolError, msg: "Max frame size too large") 445 | return 446 | } 447 | clientSettings.maxFrameSize = value 448 | case settingsMaxHeaderListSize: 449 | clientSettings.maxHeaderListSize = value 450 | default: 451 | () // must ignore unrecognized settings 452 | } 453 | } 454 | if debug { 455 | print("client settings:") 456 | print("\theaderTableSize: \(clientSettings.headerTableSize)") 457 | print("\tenablePush: \(clientSettings.enablePush)") 458 | print("\tmaxConcurrentStreams: \(clientSettings.maxConcurrentStreams)") 459 | print("\tinitialWindowSize: \(clientSettings.initialWindowSize)") 460 | print("\tmaxFrameSize: \(clientSettings.maxFrameSize)") 461 | print("\tmaxHeaderListSize: \(clientSettings.maxHeaderListSize)") 462 | } 463 | } 464 | } 465 | -------------------------------------------------------------------------------- /Sources/PerfectCHTTPParser/include/http_parser.h: -------------------------------------------------------------------------------- 1 | /* Copyright Joyent, Inc. and other Node contributors. All rights reserved. 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to 5 | * deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | * sell copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | * IN THE SOFTWARE. 20 | */ 21 | #ifndef http_parser_h 22 | #define http_parser_h 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | /* Also update SONAME in the Makefile whenever you change these. */ 28 | #define HTTP_PARSER_VERSION_MAJOR 2 29 | #define HTTP_PARSER_VERSION_MINOR 7 30 | #define HTTP_PARSER_VERSION_PATCH 1 31 | 32 | #include 33 | #if defined(_WIN32) && !defined(__MINGW32__) && \ 34 | (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__) 35 | #include 36 | #include 37 | typedef __int8 int8_t; 38 | typedef unsigned __int8 uint8_t; 39 | typedef __int16 int16_t; 40 | typedef unsigned __int16 uint16_t; 41 | typedef __int32 int32_t; 42 | typedef unsigned __int32 uint32_t; 43 | typedef __int64 int64_t; 44 | typedef unsigned __int64 uint64_t; 45 | #else 46 | #include 47 | #endif 48 | 49 | /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run 50 | * faster 51 | */ 52 | #ifndef HTTP_PARSER_STRICT 53 | # define HTTP_PARSER_STRICT 1 54 | #endif 55 | 56 | /* Maximium header size allowed. If the macro is not defined 57 | * before including this header then the default is used. To 58 | * change the maximum header size, define the macro in the build 59 | * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove 60 | * the effective limit on the size of the header, define the macro 61 | * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) 62 | */ 63 | #ifndef HTTP_MAX_HEADER_SIZE 64 | # define HTTP_MAX_HEADER_SIZE (80*1024) 65 | #endif 66 | 67 | typedef struct http_parser http_parser; 68 | typedef struct http_parser_settings http_parser_settings; 69 | 70 | 71 | /* Callbacks should return non-zero to indicate an error. The parser will 72 | * then halt execution. 73 | * 74 | * The one exception is on_headers_complete. In a HTTP_RESPONSE parser 75 | * returning '1' from on_headers_complete will tell the parser that it 76 | * should not expect a body. This is used when receiving a response to a 77 | * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: 78 | * chunked' headers that indicate the presence of a body. 79 | * 80 | * Returning `2` from on_headers_complete will tell parser that it should not 81 | * expect neither a body nor any futher responses on this connection. This is 82 | * useful for handling responses to a CONNECT request which may not contain 83 | * `Upgrade` or `Connection: upgrade` headers. 84 | * 85 | * http_data_cb does not return data chunks. It will be called arbitrarily 86 | * many times for each string. E.G. you might get 10 callbacks for "on_url" 87 | * each providing just a few characters more data. 88 | */ 89 | typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); 90 | typedef int (*http_cb) (http_parser*); 91 | 92 | 93 | /* Request Methods */ 94 | #define HTTP_METHOD_MAP(XX) \ 95 | XX(0, DELETE, DELETE) \ 96 | XX(1, GET, GET) \ 97 | XX(2, HEAD, HEAD) \ 98 | XX(3, POST, POST) \ 99 | XX(4, PUT, PUT) \ 100 | /* pathological */ \ 101 | XX(5, CONNECT, CONNECT) \ 102 | XX(6, OPTIONS, OPTIONS) \ 103 | XX(7, TRACE, TRACE) \ 104 | /* WebDAV */ \ 105 | XX(8, COPY, COPY) \ 106 | XX(9, LOCK, LOCK) \ 107 | XX(10, MKCOL, MKCOL) \ 108 | XX(11, MOVE, MOVE) \ 109 | XX(12, PROPFIND, PROPFIND) \ 110 | XX(13, PROPPATCH, PROPPATCH) \ 111 | XX(14, SEARCH, SEARCH) \ 112 | XX(15, UNLOCK, UNLOCK) \ 113 | XX(16, BIND, BIND) \ 114 | XX(17, REBIND, REBIND) \ 115 | XX(18, UNBIND, UNBIND) \ 116 | XX(19, ACL, ACL) \ 117 | /* subversion */ \ 118 | XX(20, REPORT, REPORT) \ 119 | XX(21, MKACTIVITY, MKACTIVITY) \ 120 | XX(22, CHECKOUT, CHECKOUT) \ 121 | XX(23, MERGE, MERGE) \ 122 | /* upnp */ \ 123 | XX(24, MSEARCH, M-SEARCH) \ 124 | XX(25, NOTIFY, NOTIFY) \ 125 | XX(26, SUBSCRIBE, SUBSCRIBE) \ 126 | XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ 127 | /* RFC-5789 */ \ 128 | XX(28, PATCH, PATCH) \ 129 | XX(29, PURGE, PURGE) \ 130 | /* CalDAV */ \ 131 | XX(30, MKCALENDAR, MKCALENDAR) \ 132 | /* RFC-2068, section 19.6.1.2 */ \ 133 | XX(31, LINK, LINK) \ 134 | XX(32, UNLINK, UNLINK) \ 135 | 136 | enum http_method 137 | { 138 | #define XX(num, name, string) HTTP_##name = num, 139 | HTTP_METHOD_MAP(XX) 140 | #undef XX 141 | }; 142 | 143 | 144 | enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; 145 | 146 | 147 | /* Flag values for http_parser.flags field */ 148 | enum flags 149 | { F_CHUNKED = 1 << 0 150 | , F_CONNECTION_KEEP_ALIVE = 1 << 1 151 | , F_CONNECTION_CLOSE = 1 << 2 152 | , F_CONNECTION_UPGRADE = 1 << 3 153 | , F_TRAILING = 1 << 4 154 | , F_UPGRADE = 1 << 5 155 | , F_SKIPBODY = 1 << 6 156 | , F_CONTENTLENGTH = 1 << 7 157 | }; 158 | 159 | 160 | /* Map for errno-related constants 161 | * 162 | * The provided argument should be a macro that takes 2 arguments. 163 | */ 164 | #define HTTP_ERRNO_MAP(XX) \ 165 | /* No error */ \ 166 | XX(OK, "success") \ 167 | \ 168 | /* Callback-related errors */ \ 169 | XX(CB_message_begin, "the on_message_begin callback failed") \ 170 | XX(CB_url, "the on_url callback failed") \ 171 | XX(CB_header_field, "the on_header_field callback failed") \ 172 | XX(CB_header_value, "the on_header_value callback failed") \ 173 | XX(CB_headers_complete, "the on_headers_complete callback failed") \ 174 | XX(CB_body, "the on_body callback failed") \ 175 | XX(CB_message_complete, "the on_message_complete callback failed") \ 176 | XX(CB_status, "the on_status callback failed") \ 177 | XX(CB_chunk_header, "the on_chunk_header callback failed") \ 178 | XX(CB_chunk_complete, "the on_chunk_complete callback failed") \ 179 | \ 180 | /* Parsing-related errors */ \ 181 | XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ 182 | XX(HEADER_OVERFLOW, \ 183 | "too many header bytes seen; overflow detected") \ 184 | XX(CLOSED_CONNECTION, \ 185 | "data received after completed connection: close message") \ 186 | XX(INVALID_VERSION, "invalid HTTP version") \ 187 | XX(INVALID_STATUS, "invalid HTTP status code") \ 188 | XX(INVALID_METHOD, "invalid HTTP method") \ 189 | XX(INVALID_URL, "invalid URL") \ 190 | XX(INVALID_HOST, "invalid host") \ 191 | XX(INVALID_PORT, "invalid port") \ 192 | XX(INVALID_PATH, "invalid path") \ 193 | XX(INVALID_QUERY_STRING, "invalid query string") \ 194 | XX(INVALID_FRAGMENT, "invalid fragment") \ 195 | XX(LF_EXPECTED, "LF character expected") \ 196 | XX(INVALID_HEADER_TOKEN, "invalid character in header") \ 197 | XX(INVALID_CONTENT_LENGTH, \ 198 | "invalid character in content-length header") \ 199 | XX(UNEXPECTED_CONTENT_LENGTH, \ 200 | "unexpected content-length header") \ 201 | XX(INVALID_CHUNK_SIZE, \ 202 | "invalid character in chunk size header") \ 203 | XX(INVALID_CONSTANT, "invalid constant string") \ 204 | XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ 205 | XX(STRICT, "strict mode assertion failed") \ 206 | XX(PAUSED, "parser is paused") \ 207 | XX(UNKNOWN, "an unknown error occurred") 208 | 209 | 210 | /* Define HPE_* values for each errno value above */ 211 | #define HTTP_ERRNO_GEN(n, s) HPE_##n, 212 | enum http_errno { 213 | HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) 214 | }; 215 | #undef HTTP_ERRNO_GEN 216 | 217 | 218 | /* Get an http_errno value from an http_parser */ 219 | #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) 220 | 221 | 222 | struct http_parser { 223 | /** PRIVATE **/ 224 | unsigned int type : 2; /* enum http_parser_type */ 225 | unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */ 226 | unsigned int state : 7; /* enum state from http_parser.c */ 227 | unsigned int header_state : 7; /* enum header_state from http_parser.c */ 228 | unsigned int index : 7; /* index into current matcher */ 229 | unsigned int lenient_http_headers : 1; 230 | 231 | uint32_t nread; /* # bytes read in various scenarios */ 232 | uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ 233 | 234 | /** READ-ONLY **/ 235 | unsigned short http_major; 236 | unsigned short http_minor; 237 | unsigned int status_code : 16; /* responses only */ 238 | unsigned int method : 8; /* requests only */ 239 | unsigned int http_errno : 7; 240 | 241 | /* 1 = Upgrade header was present and the parser has exited because of that. 242 | * 0 = No upgrade header present. 243 | * Should be checked when http_parser_execute() returns in addition to 244 | * error checking. 245 | */ 246 | unsigned int upgrade : 1; 247 | 248 | /** PUBLIC **/ 249 | void *data; /* A pointer to get hook to the "connection" or "socket" object */ 250 | }; 251 | 252 | 253 | struct http_parser_settings { 254 | http_cb on_message_begin; 255 | http_data_cb on_url; 256 | http_data_cb on_status; 257 | http_data_cb on_header_field; 258 | http_data_cb on_header_value; 259 | http_cb on_headers_complete; 260 | http_data_cb on_body; 261 | http_cb on_message_complete; 262 | /* When on_chunk_header is called, the current chunk length is stored 263 | * in parser->content_length. 264 | */ 265 | http_cb on_chunk_header; 266 | http_cb on_chunk_complete; 267 | }; 268 | 269 | 270 | enum http_parser_url_fields 271 | { UF_SCHEMA = 0 272 | , UF_HOST = 1 273 | , UF_PORT = 2 274 | , UF_PATH = 3 275 | , UF_QUERY = 4 276 | , UF_FRAGMENT = 5 277 | , UF_USERINFO = 6 278 | , UF_MAX = 7 279 | }; 280 | 281 | 282 | /* Result structure for http_parser_parse_url(). 283 | * 284 | * Callers should index into field_data[] with UF_* values iff field_set 285 | * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and 286 | * because we probably have padding left over), we convert any port to 287 | * a uint16_t. 288 | */ 289 | struct http_parser_url { 290 | uint16_t field_set; /* Bitmask of (1 << UF_*) values */ 291 | uint16_t port; /* Converted UF_PORT string */ 292 | 293 | struct { 294 | uint16_t off; /* Offset into buffer in which field starts */ 295 | uint16_t len; /* Length of run in buffer */ 296 | } field_data[UF_MAX]; 297 | }; 298 | 299 | 300 | /* Returns the library version. Bits 16-23 contain the major version number, 301 | * bits 8-15 the minor version number and bits 0-7 the patch level. 302 | * Usage example: 303 | * 304 | * unsigned long version = http_parser_version(); 305 | * unsigned major = (version >> 16) & 255; 306 | * unsigned minor = (version >> 8) & 255; 307 | * unsigned patch = version & 255; 308 | * printf("http_parser v%u.%u.%u\n", major, minor, patch); 309 | */ 310 | unsigned long http_parser_version(void); 311 | 312 | void http_parser_init(http_parser *parser, enum http_parser_type type); 313 | 314 | 315 | /* Initialize http_parser_settings members to 0 316 | */ 317 | void http_parser_settings_init(http_parser_settings *settings); 318 | 319 | 320 | /* Executes the parser. Returns number of parsed bytes. Sets 321 | * `parser->http_errno` on error. */ 322 | size_t http_parser_execute(http_parser *parser, 323 | const http_parser_settings *settings, 324 | const char *data, 325 | size_t len); 326 | 327 | 328 | /* If http_should_keep_alive() in the on_headers_complete or 329 | * on_message_complete callback returns 0, then this should be 330 | * the last message on the connection. 331 | * If you are the server, respond with the "Connection: close" header. 332 | * If you are the client, close the connection. 333 | */ 334 | int http_should_keep_alive(const http_parser *parser); 335 | 336 | /* Returns a string version of the HTTP method. */ 337 | const char *http_method_str(enum http_method m); 338 | 339 | /* Return a string name of the given error */ 340 | const char *http_errno_name(enum http_errno err); 341 | 342 | /* Return a string description of the given error */ 343 | const char *http_errno_description(enum http_errno err); 344 | 345 | /* Initialize all http_parser_url members to 0 */ 346 | void http_parser_url_init(struct http_parser_url *u); 347 | 348 | /* Parse a URL; return nonzero on failure */ 349 | int http_parser_parse_url(const char *buf, size_t buflen, 350 | int is_connect, 351 | struct http_parser_url *u); 352 | 353 | /* Pause or un-pause the parser; a nonzero value pauses */ 354 | void http_parser_pause(http_parser *parser, int paused); 355 | 356 | /* Checks if this is the final chunk of the body. */ 357 | int http_body_is_final(const http_parser *parser); 358 | 359 | #ifdef __cplusplus 360 | } 361 | #endif 362 | #endif 363 | -------------------------------------------------------------------------------- /Sources/PerfectHTTPServer/HTTP11/HTTP11Request.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTP11Request.swift 3 | // PerfectLib 4 | // 5 | // Created by Kyle Jessup on 2016-06-21. 6 | // Copyright (C) 2016 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | import PerfectNet 21 | import PerfectThread 22 | import PerfectLib 23 | import PerfectHTTP 24 | import PerfectCHTTPParser 25 | 26 | private let httpMaxHeadersSize = 1024 * 8 27 | 28 | private let characterCR: Character = "\r" 29 | private let characterLF: Character = "\n" 30 | private let characterCRLF: Character = "\r\n" 31 | private let characterSP: Character = " " 32 | private let characterHT: Character = "\t" 33 | private let characterColon: Character = ":" 34 | 35 | private let httpReadSize = 1024 * 8 36 | private let httpReadTimeout = 5.0 37 | 38 | let httpLF: UInt8 = 10 39 | let httpCR: UInt8 = 13 40 | 41 | private let httpSpace = UnicodeScalar(32) 42 | private let httpQuestion = UnicodeScalar(63) 43 | 44 | 45 | class HTTP11Request: HTTPRequest { 46 | var method: HTTPMethod = .get 47 | var path: String { 48 | get { 49 | var accum = "" 50 | var lastSlashExplicit = false 51 | for p in pathComponents { 52 | if p == "/" { 53 | accum += p 54 | lastSlashExplicit = true 55 | } else { 56 | if !lastSlashExplicit { 57 | accum += "/" 58 | } 59 | accum += p.stringByEncodingURL 60 | lastSlashExplicit = false 61 | } 62 | } 63 | return accum 64 | } 65 | set { 66 | let components = newValue.filePathComponents.map { $0 == "/" ? "/" : $0.stringByDecodingURL ?? "" } 67 | pathComponents = components 68 | } 69 | } 70 | var pathComponents = [String]() 71 | var queryString = "" 72 | 73 | lazy var queryParams: [(String, String)] = { 74 | return deFormURLEncoded(string: queryString) 75 | }() 76 | 77 | var protocolVersion = (1, 0) 78 | var remoteAddress: (host: String, port: UInt16) { 79 | guard let remote = connection.remoteAddress else { 80 | return ("", 0) 81 | } 82 | return (remote.host, remote.port) 83 | } 84 | var serverAddress: (host: String, port: UInt16) { 85 | guard let local = connection.localAddress else { 86 | return ("", 0) 87 | } 88 | return (local.host, local.port) 89 | } 90 | var serverName = "" 91 | var documentRoot = "./webroot" 92 | var urlVariables = [String:String]() 93 | var scratchPad = [String:Any]() 94 | 95 | private var headerStore = Dictionary() 96 | 97 | var headers: AnyIterator<(HTTPRequestHeader.Name, String)> { 98 | var g = headerStore.makeIterator() 99 | return AnyIterator<(HTTPRequestHeader.Name, String)> { 100 | guard let n = g.next() else { 101 | return nil 102 | } 103 | return (n.key, UTF8Encoding.encode(bytes: n.value)) 104 | } 105 | } 106 | 107 | lazy var postParams: [(String, String)] = { 108 | 109 | if let mime = mimes { 110 | return mime.bodySpecs.filter { $0.file == nil }.map { ($0.fieldName, $0.fieldValue) } 111 | } else if let bodyString = postBodyString { 112 | return deFormURLEncoded(string: bodyString) 113 | } 114 | return [(String, String)]() 115 | }() 116 | 117 | var postBodyBytes: [UInt8]? { 118 | get { 119 | if let _ = mimes { 120 | return nil 121 | } 122 | return workingBuffer 123 | } 124 | set { 125 | if let nv = newValue { 126 | workingBuffer = nv 127 | } else { 128 | workingBuffer.removeAll() 129 | } 130 | } 131 | } 132 | var postBodyString: String? { 133 | guard let bytes = postBodyBytes else { 134 | return nil 135 | } 136 | if bytes.isEmpty { 137 | return "" 138 | } 139 | return UTF8Encoding.encode(bytes: bytes) 140 | } 141 | var postFileUploads: [MimeReader.BodySpec]? { 142 | guard let mimes = self.mimes else { 143 | return nil 144 | } 145 | return mimes.bodySpecs 146 | } 147 | 148 | var connection: NetTCP 149 | var workingBuffer = [UInt8]() 150 | var workingBufferOffset = 0 151 | 152 | var mimes: MimeReader? 153 | 154 | var contentType: String? { 155 | guard let v = headerStore[.contentType] else { 156 | return nil 157 | } 158 | return UTF8Encoding.encode(bytes: v) 159 | } 160 | 161 | lazy var contentLength: Int = { 162 | guard let cl = headerStore[.contentLength] else { 163 | return 0 164 | } 165 | let conv = UTF8Encoding.encode(bytes: cl) 166 | return Int(conv) ?? 0 167 | }() 168 | 169 | typealias StatusCallback = (HTTPResponseStatus) -> () 170 | 171 | var parser = http_parser() 172 | var parserSettings = http_parser_settings() 173 | 174 | enum State { 175 | case none, messageBegin, messageComplete, headersComplete, headerField, headerValue, body, url 176 | } 177 | 178 | var state = State.none 179 | var lastHeaderName: String? 180 | 181 | static func getSelf(parser: UnsafeMutablePointer) -> HTTP11Request? { 182 | guard let d = parser.pointee.data else { return nil } 183 | return Unmanaged.fromOpaque(d).takeUnretainedValue() 184 | } 185 | 186 | init(connection: NetTCP) { 187 | self.connection = connection 188 | 189 | parserSettings.on_message_begin = { 190 | parser -> Int32 in 191 | guard let parser = parser else { return 0 } 192 | return Int32(HTTP11Request.getSelf(parser: parser)?.parserMessageBegin(parser) ?? 0) 193 | } 194 | 195 | parserSettings.on_message_complete = { 196 | parser -> Int32 in 197 | guard let parser = parser else { return 0 } 198 | return Int32(HTTP11Request.getSelf(parser: parser)?.parserMessageComplete(parser) ?? 0) 199 | } 200 | 201 | parserSettings.on_headers_complete = { 202 | parser -> Int32 in 203 | guard let parser = parser else { return 0 } 204 | return Int32(HTTP11Request.getSelf(parser: parser)?.parserHeadersComplete(parser) ?? 0) 205 | } 206 | 207 | parserSettings.on_header_field = { 208 | (parser, chunk, length) -> Int32 in 209 | guard let parser = parser else { return 0 } 210 | return Int32(HTTP11Request.getSelf(parser: parser)?.parserHeaderField(parser, data: chunk, length: length) ?? 0) 211 | } 212 | 213 | parserSettings.on_header_value = { 214 | (parser, chunk, length) -> Int32 in 215 | guard let parser = parser else { return 0 } 216 | return Int32(HTTP11Request.getSelf(parser: parser)?.parserHeaderValue(parser, data: chunk, length: length) ?? 0) 217 | } 218 | 219 | parserSettings.on_body = { 220 | (parser, chunk, length) -> Int32 in 221 | guard let parser = parser else { return 0 } 222 | return Int32(HTTP11Request.getSelf(parser: parser)?.parserBody(parser, data: chunk, length: length) ?? 0) 223 | } 224 | 225 | parserSettings.on_url = { 226 | (parser, chunk, length) -> Int32 in 227 | guard let parser = parser else { return 0 } 228 | return Int32(HTTP11Request.getSelf(parser: parser)?.parserURL(parser, data: chunk, length: length) ?? 0) 229 | } 230 | http_parser_init(&parser, HTTP_REQUEST) 231 | 232 | parser.data = Unmanaged.passUnretained(self).toOpaque() 233 | } 234 | 235 | func parserMessageBegin(_ parser: UnsafePointer) -> Int { 236 | enteringState(parser, .messageBegin, data: nil, length: 0) 237 | return 0 238 | } 239 | 240 | func parserMessageComplete(_ parser: UnsafePointer) -> Int { 241 | enteringState(parser, .messageComplete, data: nil, length: 0) 242 | return 0 243 | } 244 | 245 | func parserHeadersComplete(_ parser: UnsafePointer) -> Int { 246 | enteringState(parser, .headersComplete, data: nil, length: 0) 247 | return 0 248 | } 249 | 250 | func parserURL(_ parser: UnsafePointer, data: UnsafePointer?, length: Int) -> Int { 251 | enteringState(parser, .url, data: data, length: length) 252 | return 0 253 | } 254 | 255 | func parserHeaderField(_ parser: UnsafePointer, data: UnsafePointer?, length: Int) -> Int { 256 | enteringState(parser, .headerField, data: data, length: length) 257 | return 0 258 | } 259 | 260 | func parserHeaderValue(_ parser: UnsafePointer, data: UnsafePointer?, length: Int) -> Int { 261 | enteringState(parser, .headerValue, data: data, length: length) 262 | return 0 263 | } 264 | 265 | func parserBody(_ parser: UnsafePointer, data: UnsafePointer?, length: Int) -> Int { 266 | if state != .body { 267 | leavingState(parser) 268 | state = .body 269 | } 270 | if workingBuffer.count == 0 && mimes == nil { 271 | if let contentType = contentType, 272 | contentType.starts(with: "multipart/form-data") { 273 | mimes = MimeReader(contentType) 274 | } 275 | } 276 | data?.withMemoryRebound(to: UInt8.self, capacity: length) { 277 | data in 278 | for i in 0.., _ state: State, data: UnsafePointer?, length: Int) { 292 | if self.state != state { 293 | leavingState(parser) 294 | self.state = state 295 | if case .headersComplete = state, 296 | let expect = header(.expect), 297 | expect.lowercased() == "100-continue" { 298 | // TODO: Should let headers be passed to filters and let them 299 | // determine if request should continue or not 300 | _ = connection.writeFully(bytes: Array("HTTP/1.1 100 Continue\r\n\r\n".utf8)) 301 | } 302 | } 303 | data?.withMemoryRebound(to: UInt8.self, capacity: length) { 304 | data in 305 | for i in 0.. ([String], String) { 313 | enum ParseURLState { 314 | case slash, component, query 315 | } 316 | var state = ParseURLState.slash 317 | var gen = workingBuffer.makeIterator() 318 | var decoder = UTF8() 319 | var pathComponents = ["/"] 320 | var component = "" 321 | var queryString = "" 322 | 323 | let question = UnicodeScalar(63) 324 | let slash = UnicodeScalar(47) 325 | 326 | loopy: 327 | repeat { 328 | let res = decoder.decode(&gen) 329 | switch res { 330 | case .scalarValue(let uchar): 331 | switch state { 332 | case .slash: 333 | if uchar == question { 334 | state = .query 335 | if pathComponents.count > 1 { 336 | pathComponents.append("/") 337 | } 338 | } else if uchar != slash { 339 | state = .component 340 | component = String(Character(uchar)) 341 | } 342 | case .component: 343 | if uchar == question { 344 | state = .query 345 | pathComponents.append(component.stringByDecodingURL ?? "") 346 | } else if uchar == slash { 347 | state = .slash 348 | pathComponents.append(component.stringByDecodingURL ?? "") 349 | } else { 350 | component.append(Character(uchar)) 351 | } 352 | case .query: 353 | queryString.append(Character(uchar)) 354 | } 355 | case .emptyInput, .error: 356 | switch state { 357 | case .slash: 358 | if pathComponents.count > 1 { 359 | pathComponents.append("/") 360 | } 361 | case .component: 362 | pathComponents.append(component.stringByDecodingURL ?? "") 363 | case .query: 364 | () 365 | } 366 | break loopy 367 | } 368 | } while true 369 | return (pathComponents, queryString) 370 | } 371 | 372 | func leavingState(_ parser: UnsafePointer) { 373 | switch state { 374 | case .url: 375 | (pathComponents, queryString) = parseURI() 376 | workingBuffer.removeAll() 377 | case .headersComplete: 378 | let methodId = parser.pointee.method 379 | if let methodName = http_method_str(http_method(rawValue: methodId)) { 380 | method = HTTPMethod.from(string: String(validatingUTF8: methodName) ?? "GET") 381 | } 382 | protocolVersion = (Int(parser.pointee.http_major), Int(parser.pointee.http_minor)) 383 | workingBuffer.removeAll() 384 | case .headerField: 385 | workingBuffer.append(0) 386 | lastHeaderName = String(validatingUTF8: UnsafeMutableRawPointer(mutating: workingBuffer).assumingMemoryBound(to: Int8.self)) 387 | workingBuffer.removeAll() 388 | case .headerValue: 389 | if let name = lastHeaderName { 390 | setHeader(named: name, value: workingBuffer) 391 | } 392 | lastHeaderName = nil 393 | workingBuffer.removeAll() 394 | case .body: 395 | () 396 | case .messageComplete: 397 | () 398 | case .messageBegin, .none: 399 | () 400 | } 401 | } 402 | 403 | func header(_ named: HTTPRequestHeader.Name) -> String? { 404 | guard let v = headerStore[named] else { 405 | return nil 406 | } 407 | return UTF8Encoding.encode(bytes: v) 408 | } 409 | 410 | func addHeader(_ named: HTTPRequestHeader.Name, value: String) { 411 | guard let existing = headerStore[named] else { 412 | headerStore[named] = [UInt8](value.utf8) 413 | return 414 | } 415 | let valueBytes = [UInt8](value.utf8) 416 | let newValue: [UInt8] 417 | if named == .cookie { 418 | newValue = existing + "; ".utf8 + valueBytes 419 | } else { 420 | newValue = existing + ", ".utf8 + valueBytes 421 | } 422 | headerStore[named] = newValue 423 | } 424 | 425 | func setHeader(_ named: HTTPRequestHeader.Name, value: String) { 426 | headerStore[named] = [UInt8](value.utf8) 427 | } 428 | 429 | func setHeader(named: String, value: [UInt8]) { 430 | headerStore[HTTPRequestHeader.Name.fromStandard(name: named)] = value 431 | } 432 | 433 | func readRequest(callback: @escaping StatusCallback) { 434 | connection.readSomeBytes(count: httpReadSize) { 435 | b in 436 | guard let b = b else { // disconnection while reading 437 | return callback(.requestTimeout) 438 | } 439 | if !b.isEmpty { 440 | if self.didReadSomeBytes(b, callback: callback) { 441 | if b.count == httpReadSize { 442 | netHandleQueue.async { 443 | self.readRequest(callback: callback) 444 | } 445 | } else { 446 | self.readRequest(callback: callback) 447 | } 448 | } 449 | } else { 450 | self.connection.readBytesFully(count: 1, timeoutSeconds: httpReadTimeout) { 451 | b in 452 | guard let b = b else { 453 | return callback(.requestTimeout) 454 | } 455 | if self.didReadSomeBytes(b, callback: callback) { 456 | self.readRequest(callback: callback) 457 | } 458 | } 459 | } 460 | } 461 | } 462 | 463 | // a true return value indicates that we should keep reading data 464 | // false indicates that the request either was fully read and is being processed or that the request failed 465 | // either way no further action should be taken 466 | func didReadSomeBytes(_ b: [UInt8], callback: @escaping StatusCallback) -> Bool { 467 | _ = UnsafePointer(b).withMemoryRebound(to: Int8.self, capacity: b.count) { 468 | http_parser_execute(&parser, &parserSettings, $0, b.count) 469 | } 470 | 471 | let http_errno = parser.http_errno 472 | guard HPE_HEADER_OVERFLOW.rawValue != http_errno else { 473 | callback(.requestEntityTooLarge) 474 | return false 475 | } 476 | guard http_errno == 0 || http_errno == HPE_CLOSED_CONNECTION.rawValue else { 477 | callback(.badRequest) 478 | return false 479 | } 480 | if state == .messageComplete { 481 | callback(.ok) 482 | return false 483 | } 484 | return true 485 | } 486 | 487 | func putPostData(_ b: [UInt8]) { 488 | if workingBuffer.count == 0 && mimes == nil { 489 | if let contentType = contentType, 490 | contentType.starts(with: "multipart/form-data") { 491 | mimes = MimeReader(contentType) 492 | } 493 | } 494 | if let mimes = self.mimes { 495 | return mimes.addToBuffer(bytes: b) 496 | } else { 497 | workingBuffer.append(contentsOf: b) 498 | } 499 | } 500 | 501 | func deFormURLEncoded(string: String) -> [(String, String)] { 502 | return string.split(separator: "&").map(String.init).compactMap { 503 | let d = $0.split(separator: "=", maxSplits: 1).compactMap { String($0).stringByDecodingURL } 504 | if d.count == 2 { return (d[0], d[1]) } 505 | if d.count == 1 { return (d[0], "") } 506 | return nil 507 | } 508 | } 509 | } 510 | -------------------------------------------------------------------------------- /Sources/PerfectHTTPServer/HTTPServerEx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPServerEx.swift 3 | // PerfectHTTPServer 4 | // 5 | // Created by Kyle Jessup on 2016-11-14. 6 | // 7 | // 8 | 9 | import PerfectThread 10 | import PerfectHTTP 11 | import PerfectNet 12 | import PerfectLib 13 | import Foundation 14 | 15 | public struct TLSConfiguration { 16 | public static var defaultCipherList = [ 17 | "ECDHE-ECDSA-AES256-GCM-SHA384", 18 | "ECDHE-ECDSA-AES128-GCM-SHA256", 19 | "ECDHE-ECDSA-AES256-CBC-SHA384", 20 | "ECDHE-ECDSA-AES256-CBC-SHA", 21 | "ECDHE-ECDSA-AES128-CBC-SHA256", 22 | "ECDHE-ECDSA-AES128-CBC-SHA", 23 | "ECDHE-RSA-AES256-GCM-SHA384", 24 | "ECDHE-RSA-AES128-GCM-SHA256", 25 | "ECDHE-RSA-AES256-CBC-SHA384", 26 | "ECDHE-RSA-AES128-CBC-SHA256", 27 | "ECDHE-RSA-AES128-CBC-SHA", 28 | "ECDHE-RSA-AES256-SHA384", 29 | "ECDHE-ECDSA-AES256-SHA384", 30 | "ECDHE-RSA-AES256-SHA", 31 | "ECDHE-ECDSA-AES256-SHA"] 32 | 33 | public var certPath: String { return cert } 34 | public var keyPath: String? { return key } 35 | public let cert: String 36 | public let key: String? 37 | public let caCertPath: String? 38 | public let certVerifyMode: OpenSSLVerifyMode? 39 | public let cipherList: [String] 40 | public let alpnSupport: [HTTPServer.ALPNSupport] 41 | 42 | /// Initialize a new struct with the desired TLS settings. 43 | /// The `cert` and `key` parameters can be either a file path or the raw PEM data. 44 | public init(cert: String, key: String? = nil, 45 | caCertPath: String? = nil, certVerifyMode: OpenSSLVerifyMode? = nil, 46 | cipherList: [String] = TLSConfiguration.defaultCipherList, 47 | alpnSupport: [HTTPServer.ALPNSupport] = [.http11]) { 48 | self.cert = cert 49 | self.key = key 50 | self.caCertPath = caCertPath 51 | self.certVerifyMode = certVerifyMode 52 | self.cipherList = cipherList 53 | self.alpnSupport = alpnSupport 54 | } 55 | 56 | public init(certPath: String, keyPath: String? = nil, 57 | caCertPath: String? = nil, certVerifyMode: OpenSSLVerifyMode? = nil, 58 | cipherList: [String] = TLSConfiguration.defaultCipherList, 59 | alpnSupport: [HTTPServer.ALPNSupport] = [.http11]) { 60 | self.init(cert: certPath, 61 | key: keyPath, 62 | caCertPath: caCertPath, 63 | certVerifyMode: certVerifyMode, 64 | cipherList: cipherList, 65 | alpnSupport: alpnSupport) 66 | } 67 | } 68 | 69 | private var processRunAs: String? 70 | 71 | public extension HTTPServer { 72 | 73 | static func runAs(_ user: String) -> HTTPServer.Type { 74 | processRunAs = user 75 | return self 76 | } 77 | 78 | struct Server { 79 | public let name: String 80 | public let port: Int 81 | public let address: String 82 | public let routes: Routes 83 | public let requestFilters: [(HTTPRequestFilter, HTTPFilterPriority)] 84 | public let responseFilters: [(HTTPResponseFilter, HTTPFilterPriority)] 85 | public let tlsConfig: TLSConfiguration? 86 | 87 | var server: HTTPServer { 88 | let http = HTTPServer() 89 | http.serverName = name 90 | http.serverPort = UInt16(port) 91 | http.serverAddress = address 92 | http.addRoutes(routes) 93 | http.setRequestFilters(requestFilters) 94 | http.setResponseFilters(responseFilters) 95 | if let tls = tlsConfig { 96 | http.ssl = (tls.certPath, tls.keyPath ?? tls.certPath) 97 | http.caCert = tls.caCertPath 98 | http.certVerifyMode = tls.certVerifyMode 99 | http.cipherList = tls.cipherList 100 | http.alpnSupport = tls.alpnSupport 101 | } 102 | return http 103 | } 104 | 105 | init() { 106 | name = "" 107 | address = "" 108 | port = 0 109 | routes = .init() 110 | requestFilters = [] 111 | responseFilters = [] 112 | tlsConfig = nil 113 | } 114 | 115 | public init(name: String, address: String, port: Int, routes: Routes, 116 | requestFilters: [(HTTPRequestFilter, HTTPFilterPriority)] = [], 117 | responseFilters: [(HTTPResponseFilter, HTTPFilterPriority)] = []) { 118 | self.name = name 119 | self.address = address 120 | self.port = port 121 | self.routes = routes 122 | self.requestFilters = requestFilters 123 | self.responseFilters = responseFilters 124 | self.tlsConfig = nil 125 | } 126 | 127 | public init(tlsConfig: TLSConfiguration, name: String, address: String, port: Int, routes: Routes, 128 | requestFilters: [(HTTPRequestFilter, HTTPFilterPriority)] = [], 129 | responseFilters: [(HTTPResponseFilter, HTTPFilterPriority)] = []) { 130 | self.name = name 131 | self.address = address 132 | self.port = port 133 | self.routes = routes 134 | self.requestFilters = requestFilters 135 | self.responseFilters = responseFilters 136 | self.tlsConfig = tlsConfig 137 | 138 | } 139 | 140 | public init(name: String, port: Int, routes: Routes, 141 | requestFilters: [(HTTPRequestFilter, HTTPFilterPriority)] = [], 142 | responseFilters: [(HTTPResponseFilter, HTTPFilterPriority)] = []) { 143 | self.init(name: name, address: "::", port: port, routes: routes, requestFilters: requestFilters, responseFilters: responseFilters) 144 | } 145 | 146 | public init(tlsConfig: TLSConfiguration, name: String, port: Int, routes: Routes, 147 | requestFilters: [(HTTPRequestFilter, HTTPFilterPriority)] = [], 148 | responseFilters: [(HTTPResponseFilter, HTTPFilterPriority)] = []) { 149 | self.init(tlsConfig: tlsConfig, name: name, address: "::", port: port, routes: routes, requestFilters: requestFilters, responseFilters: responseFilters) 150 | } 151 | 152 | public static func server(name: String, port: Int, routes: Routes, 153 | requestFilters: [(HTTPRequestFilter, HTTPFilterPriority)] = [], 154 | responseFilters: [(HTTPResponseFilter, HTTPFilterPriority)] = []) -> Server { 155 | return HTTPServer.Server(name: name, port: port, routes: routes, requestFilters: requestFilters, responseFilters: responseFilters) 156 | } 157 | 158 | public static func server(name: String, port: Int, routes: [Route], 159 | requestFilters: [(HTTPRequestFilter, HTTPFilterPriority)] = [], 160 | responseFilters: [(HTTPResponseFilter, HTTPFilterPriority)] = []) -> Server { 161 | return HTTPServer.Server(name: name, port: port, routes: Routes(routes), requestFilters: requestFilters, responseFilters: responseFilters) 162 | } 163 | 164 | public static func server(name: String, port: Int, documentRoot root: String, 165 | requestFilters: [(HTTPRequestFilter, HTTPFilterPriority)] = [], 166 | responseFilters: [(HTTPResponseFilter, HTTPFilterPriority)] = []) -> Server { 167 | let sfh = StaticFileHandler(documentRoot: root, allowResponseFilters: 0 < (requestFilters.count + responseFilters.count)) 168 | let routes = Routes([ 169 | .init(method: .get, uri: "/**", handler: sfh.handleRequest), 170 | .init(method: .head, uri: "/**", handler: sfh.handleRequest) 171 | ]) 172 | return HTTPServer.Server(name: name, port: port, routes: routes, requestFilters: requestFilters, responseFilters: responseFilters) 173 | } 174 | 175 | public static func secureServer(_ tlsConfig: TLSConfiguration, name: String, port: Int, routes: [Route], 176 | requestFilters: [(HTTPRequestFilter, HTTPFilterPriority)] = [], 177 | responseFilters: [(HTTPResponseFilter, HTTPFilterPriority)] = []) -> Server { 178 | return HTTPServer.Server(tlsConfig: tlsConfig, name: name, port: port, routes: Routes(routes), requestFilters: requestFilters, responseFilters: responseFilters) 179 | } 180 | 181 | public static func secureServer(_ tlsConfig: TLSConfiguration, name: String, port: Int, routes: Routes, 182 | requestFilters: [(HTTPRequestFilter, HTTPFilterPriority)] = [], 183 | responseFilters: [(HTTPResponseFilter, HTTPFilterPriority)] = []) -> Server { 184 | return HTTPServer.Server(tlsConfig: tlsConfig, name: name, port: port, routes: routes, requestFilters: requestFilters, responseFilters: responseFilters) 185 | } 186 | } 187 | } 188 | 189 | protocol ServerInstance { 190 | func start() throws 191 | func bind() throws 192 | func stop() 193 | } 194 | 195 | public extension HTTPServer { 196 | struct LaunchFailure: Error { 197 | let message: String 198 | let configuration: Server 199 | } 200 | 201 | class LaunchContext { 202 | private let event = Threading.Event() 203 | var error: Error? 204 | public var terminated = false 205 | public let server: Server 206 | var httpServer: ServerInstance? 207 | 208 | var id: String { return "\(server.name):\(server.port)" } 209 | 210 | init(_ server: Server) { 211 | self.server = server 212 | } 213 | 214 | init(_ server: HTTPMultiplexer) { 215 | self.httpServer = server 216 | self.server = server.synthServer 217 | } 218 | 219 | @discardableResult 220 | public func terminate() -> LaunchContext { 221 | if !terminated, let httpServer = self.httpServer { 222 | httpServer.stop() 223 | } 224 | return self 225 | } 226 | 227 | // if the ctx indicated an error then we translate it into a throw 228 | @discardableResult 229 | public func wait(seconds: Double = Threading.noTimeout) throws -> Bool { 230 | event.lock() 231 | defer { 232 | event.unlock() 233 | } 234 | if !terminated { 235 | _ = event.wait(seconds: seconds) 236 | } 237 | if terminated, let error = self.error { 238 | switch error { 239 | case PerfectNetError.networkError(let code, let msg): 240 | switch code { 241 | case 53: 242 | () // socket was closed. not an error 243 | case 48: 244 | throw LaunchFailure(message: "Server \(id) - Another server was already listening on the requested port \(server.port)", configuration: server) 245 | default: 246 | throw LaunchFailure(message: "Server \(id) - \(code):\(msg)", configuration: server) 247 | } 248 | default: 249 | throw LaunchFailure(message: "Server \(id) - \(error)", configuration: server) 250 | } 251 | } 252 | return terminated 253 | } 254 | 255 | func bindServer() throws { 256 | if nil == httpServer { 257 | httpServer = server.server 258 | } 259 | guard let httpServer = self.httpServer else { 260 | throw LaunchFailure(message: "Could not get HTTPServer", configuration: server) 261 | } 262 | do { 263 | try httpServer.bind() 264 | } catch PerfectNetError.networkError(let code, let msg) { 265 | switch code { 266 | case 48: 267 | throw LaunchFailure(message: "Server \(id) - Another server was already listening on the requested port \(server.port)", configuration: server) 268 | default: 269 | throw LaunchFailure(message: "Server \(id) - \(code):\(msg)", configuration: server) 270 | } 271 | } catch { 272 | throw LaunchFailure(message: "Server \(id) - \(error)", configuration: server) 273 | } 274 | } 275 | 276 | @discardableResult 277 | func launchServer() throws -> LaunchContext { 278 | guard let httpServer = self.httpServer else { 279 | throw LaunchFailure(message: "Could not get HTTPServer", configuration: server) 280 | } 281 | let q = Threading.getQueue(name: "Server \(id) \(Foundation.UUID().uuidString)", type: .serial) 282 | q.dispatch { 283 | do { 284 | try httpServer.start() 285 | } catch { 286 | self.error = error 287 | } 288 | self.event.lock() 289 | defer { 290 | self.event.unlock() 291 | } 292 | self.terminated = true 293 | self.event.signal() 294 | } 295 | return self 296 | } 297 | } 298 | } 299 | 300 | public extension HTTPServer { 301 | 302 | @discardableResult 303 | static func launch(wait: Bool = true, _ server: Server, _ servers: Server...) throws -> [LaunchContext] { 304 | return try launch(wait: wait, [server] + servers) 305 | } 306 | 307 | @discardableResult 308 | static func launch(wait: Bool = true, name: String, port: Int, routes: [Route], 309 | requestFilters: [(HTTPRequestFilter, HTTPFilterPriority)] = [], 310 | responseFilters: [(HTTPResponseFilter, HTTPFilterPriority)] = []) throws -> LaunchContext { 311 | return try launch(wait: wait, name: name, port: port, routes: Routes(routes), requestFilters: requestFilters, responseFilters: responseFilters) 312 | } 313 | 314 | static func getLaunchContexts(_ servers: [Server]) throws -> [LaunchContext] { 315 | var notSSL = [Server]() 316 | var singleServers = [String:Server]() 317 | var multiplexers = [String:HTTPMultiplexer]() 318 | for server in servers { 319 | if let _ = server.tlsConfig { 320 | let id = "\(server.address):\(server.port)" 321 | if let existingMulti = multiplexers[id] { 322 | try existingMulti.addServer(server.server) 323 | } else if let existingSingle = singleServers[id] { 324 | singleServers.removeValue(forKey: id) 325 | let multi = HTTPMultiplexer() 326 | try multi.addServer(existingSingle.server) 327 | try multi.addServer(server.server) 328 | multiplexers[id] = multi 329 | } else { 330 | singleServers[id] = server 331 | } 332 | } else { 333 | notSSL.append(server) 334 | } 335 | } 336 | return (notSSL + singleServers.values).map { LaunchContext($0) } + multiplexers.values.map { LaunchContext($0) } 337 | } 338 | 339 | // launch with array 340 | @discardableResult 341 | static func launch(wait: Bool = true, _ servers: [Server]) throws -> [LaunchContext] { 342 | let ctx = try getLaunchContexts(servers) 343 | try ctx.forEach { try $0.bindServer() } 344 | try switchUser() 345 | try ctx.forEach { try $0.launchServer() } 346 | try ctx.forEach { try $0.wait(seconds: 1.0) } 347 | if wait { 348 | try ctx.forEach { 349 | repeat { 350 | () 351 | } while try $0.wait() == false 352 | } 353 | } 354 | return ctx 355 | } 356 | 357 | // launch one 358 | @discardableResult 359 | static func launch(wait: Bool = true, name: String, port: Int, routes: Routes, 360 | requestFilters: [(HTTPRequestFilter, HTTPFilterPriority)] = [], 361 | responseFilters: [(HTTPResponseFilter, HTTPFilterPriority)] = []) throws -> LaunchContext { 362 | return try launch(wait: wait, [.server(name: name, port: port, routes: routes, requestFilters: requestFilters, responseFilters: responseFilters)])[0] 363 | } 364 | 365 | // launch one with document root 366 | @discardableResult 367 | static func launch(wait: Bool = true, name: String, port: Int, documentRoot root: String, 368 | requestFilters: [(HTTPRequestFilter, HTTPFilterPriority)] = [], 369 | responseFilters: [(HTTPResponseFilter, HTTPFilterPriority)] = []) throws -> LaunchContext { 370 | return try launch(wait: wait, [.server(name: name, port: port, documentRoot: root, requestFilters: requestFilters, responseFilters: responseFilters)])[0] 371 | } 372 | 373 | private static func switchUser() throws { 374 | guard let runAs = processRunAs else { 375 | return 376 | } 377 | try PerfectServer.switchTo(userName: runAs) 378 | } 379 | } 380 | 381 | private extension HTTPServer.Server { 382 | init(data: [String:Any]) throws { 383 | guard let name = data["name"] as? String else { 384 | throw PerfectError.apiError("Server data did not contain a name") 385 | } 386 | guard let port = data["port"] as? Int else { 387 | throw PerfectError.apiError("Server data did not contain an integer port") 388 | } 389 | self.name = name 390 | self.port = port 391 | self.address = data["address"] as? String ?? "0.0.0.0" 392 | self.routes = try Routes(data: data["routes"] as? [[String:Any]] ?? []) 393 | 394 | let filters = data["filters"] as? [[String:Any]] ?? [] 395 | self.requestFilters = try filtersFrom(data: filters) 396 | self.responseFilters = try filtersFrom(data: filters) 397 | 398 | self.tlsConfig = TLSConfiguration(data: data["tlsConfig"] as? [String:Any] ?? [:]) 399 | } 400 | } 401 | 402 | public extension HTTPServer { 403 | @discardableResult 404 | static func launch(wait: Bool = true, configurationPath path: String) throws -> [LaunchContext] { 405 | return try launch(wait: wait, configurationFile: File(path)) 406 | } 407 | @discardableResult 408 | static func launch(wait: Bool = true, configurationFile file: File) throws -> [LaunchContext] { 409 | let string = try file.readString() 410 | guard let jsonData = try string.jsonDecode() as? [String:Any] else { 411 | throw PerfectError.apiError("Data in \(file.path) could not convert to [String:Any]") 412 | } 413 | return try launch(wait: wait, configurationData: jsonData) 414 | } 415 | @discardableResult 416 | static func launch(wait: Bool = true, configurationData data: [String:Any]) throws -> [LaunchContext] { 417 | processRunAs = data["runAs"] as? String 418 | guard let servers = data["servers"] as? [[String:Any]] else { 419 | return [] 420 | } 421 | let serversObjs = try servers.map { try HTTPServer.Server(data: $0) } 422 | return try launch(wait: wait, serversObjs) 423 | } 424 | } 425 | 426 | -------------------------------------------------------------------------------- /Sources/PerfectHTTPServer/HTTP2/HTTP2Client.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTP2.swift 3 | // PerfectLib 4 | // 5 | // Created by Kyle Jessup on 2016-02-18. 6 | // Copyright © 2016 PerfectlySoft. All rights reserved. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | // NOTE: This HTTP/2 client is competent enough to operate with Apple's push notification service, but 21 | // still lacks some functionality to make it general purpose. Consider it a work in-progress. 22 | 23 | import PerfectNet 24 | import PerfectThread 25 | import PerfectLib 26 | import PerfectHTTP 27 | 28 | #if os(Linux) 29 | import SwiftGlibc 30 | #endif 31 | 32 | private class HTTP2ClientRequest: HTTP11Request { 33 | 34 | } 35 | 36 | final class HTTP2ClientResponse: HTTP11Response, HeaderListener { 37 | func addHeader(name nam: [UInt8], value: [UInt8], sensitive: Bool) { 38 | let n = UTF8Encoding.encode(bytes: nam) 39 | let v = UTF8Encoding.encode(bytes: value) 40 | switch n { 41 | case ":status": 42 | status = HTTPResponseStatus.statusFrom(code: Int(v) ?? 200) 43 | default: 44 | headerStore.append((HTTPResponseHeader.Name.fromStandard(name: n), v)) 45 | } 46 | } 47 | } 48 | 49 | open class HTTP2Client { 50 | 51 | enum StreamState { 52 | case none, idle, reservedLocal, reservedRemote, open, halfClosedRemote, halfClosedLocal, closed 53 | } 54 | 55 | public let net = NetTCPSSL() 56 | var host = "" 57 | var timeoutSeconds = 10.0 58 | var ssl = true 59 | var streams = [UInt32:StreamState]() 60 | var streamCounter = UInt32(1) 61 | 62 | let encoder = HPACKEncoder() 63 | let decoder = HPACKDecoder() 64 | 65 | let closeLock = Threading.Lock() 66 | 67 | let frameReadEvent = Threading.Event() 68 | var frameQueue = [HTTP2Frame]() 69 | var frameReadOK = false 70 | 71 | var newStreamId: UInt32 { 72 | streams[streamCounter] = StreamState.none 73 | let s = streamCounter 74 | streamCounter += 2 75 | return s 76 | } 77 | 78 | public init() { 79 | 80 | } 81 | 82 | func dequeueFrame(timeoutSeconds timeout: Double) -> HTTP2Frame? { 83 | var frame: HTTP2Frame? = nil 84 | frameReadEvent.doWithLock { 85 | if self.frameQueue.count == 0 { 86 | let _ = self.frameReadEvent.wait(seconds: timeout) 87 | } 88 | if self.frameQueue.count > 0 { 89 | frame = self.frameQueue.removeFirst() 90 | } 91 | } 92 | return frame 93 | } 94 | 95 | func dequeueFrame(timeoutSeconds timeout: Double, streamId: UInt32) -> HTTP2Frame? { 96 | var frame: HTTP2Frame? = nil 97 | frameReadEvent.doWithLock { 98 | if self.frameQueue.count == 0 { 99 | let _ = self.frameReadEvent.wait(seconds: timeout) 100 | } 101 | if self.frameQueue.count > 0 { 102 | for i in 0..= 6 { 117 | let identifier = b.export16Bits().netToHost 118 | // let value = b.export32Bits().netToHost 119 | 120 | // print("Setting \(identifier) \(value)") 121 | 122 | switch identifier { 123 | // case SETTINGS_HEADER_TABLE_SIZE: 124 | // ()//self.encoder = HPACKEncoder(maxCapacity: Int(value)) 125 | // case SETTINGS_ENABLE_PUSH: 126 | // () 127 | // case SETTINGS_MAX_CONCURRENT_STREAMS: 128 | // () 129 | // case SETTINGS_INITIAL_WINDOW_SIZE: 130 | // () 131 | // case SETTINGS_MAX_FRAME_SIZE: 132 | // () 133 | // case SETTINGS_MAX_HEADER_LIST_SIZE: 134 | // () 135 | default: 136 | () 137 | } 138 | } 139 | } 140 | 141 | func readOneFrame() { 142 | Threading.dispatch { 143 | self.readHTTP2Frame(timeout: -1) { [weak self] 144 | f in 145 | if let frame = f { 146 | // print("Read frame \(frame.typeStr) \(frame.flagsStr) \(frame.streamId)") 147 | // if frame.length > 0 { 148 | // print("Read frame payload \(frame.length) \(UTF8Encoding.encode(bytes: frame.payload!))") 149 | // } 150 | self?.frameReadEvent.doWithLock { 151 | switch frame.type { 152 | case .settings: 153 | let endStream = (frame.flags & flagSettingsAck) != 0 154 | if !endStream { // ACK settings receipt 155 | if let payload = frame.payload { 156 | self?.processSettingsPayload(Bytes(existingBytes: payload)) 157 | } 158 | let response = HTTP2Frame(length: 0, 159 | type: HTTP2FrameType.settings.rawValue, 160 | flags: flagSettingsAck, 161 | streamId: 0, 162 | payload: nil) 163 | self?.writeHTTP2Frame(response) { 164 | b in 165 | self?.readOneFrame() 166 | } 167 | } else { // ACK of our settings frame 168 | self?.readOneFrame() 169 | } 170 | case .ping: 171 | let endStream = (frame.flags & flagPingAck) != 0 172 | if !endStream { // ACK ping receipt 173 | if let payload = frame.payload { 174 | self?.processSettingsPayload(Bytes(existingBytes: payload)) 175 | } 176 | let response = HTTP2Frame(length: frame.length, 177 | type: HTTP2FrameType.ping.rawValue, 178 | flags: flagPingAck, 179 | streamId: 0, 180 | payload: frame.payload) 181 | self?.writeHTTP2Frame(response) { 182 | b in 183 | self?.readOneFrame() 184 | } 185 | } else { // ACK of our ping frame 186 | fallthrough 187 | } 188 | default: 189 | self?.frameQueue.append(frame) 190 | self?.frameReadOK = true 191 | _ = self?.frameReadEvent.broadcast() 192 | } 193 | } 194 | } else { // network error 195 | self?.frameReadEvent.doWithLock { 196 | self?.close() 197 | self?.frameReadOK = false 198 | let _ = self?.frameReadEvent.broadcast() 199 | } 200 | } 201 | } 202 | } 203 | } 204 | 205 | func startReadThread() { 206 | Threading.dispatch { [weak self] in 207 | // dbg 208 | defer { 209 | print("~HTTP2Client.startReadThread") 210 | } 211 | guard let net = self?.net else { 212 | return 213 | } 214 | while net.isValid { 215 | guard let s = self else { 216 | net.close() 217 | break 218 | } 219 | s.frameReadEvent.doWithLock { 220 | s.frameReadOK = false 221 | s.readOneFrame() 222 | if !s.frameReadOK && net.isValid { 223 | _ = s.frameReadEvent.wait() 224 | } 225 | } 226 | if !s.frameReadOK { 227 | s.close() 228 | break 229 | } 230 | } 231 | } 232 | } 233 | 234 | public func close() { 235 | closeLock.doWithLock { 236 | self.net.shutdown() 237 | } 238 | } 239 | 240 | public var isConnected: Bool { 241 | return net.isValid 242 | } 243 | 244 | public func connect(host hst: String, port: UInt16, ssl: Bool, timeoutSeconds: Double, callback: @escaping (Bool) -> ()) { 245 | self.host = hst 246 | self.ssl = ssl 247 | self.timeoutSeconds = timeoutSeconds 248 | do { 249 | try net.connect(address: hst, port: port, timeoutSeconds: timeoutSeconds) { 250 | n in 251 | if let net = n as? NetTCPSSL { 252 | net.fd.switchToNonBlocking() 253 | net.fd.switchToBlocking() // !FIX! 254 | self.completeConnect(callback) 255 | } else { 256 | callback(false) 257 | } 258 | } 259 | } catch { 260 | callback(false) 261 | } 262 | } 263 | 264 | public func createRequest() -> HTTPRequest { 265 | return HTTP2ClientRequest(connection: net) 266 | } 267 | 268 | func awaitResponse(streamId stream: UInt32, request: HTTPRequest, callback: (HTTPResponse?, String?) -> ()) { 269 | let response = HTTP2ClientResponse(request: request) 270 | var streamOpen = true 271 | while streamOpen { 272 | let f = dequeueFrame(timeoutSeconds: timeoutSeconds, streamId: stream) 273 | if let frame = f { 274 | switch frame.type { 275 | case .goAway: 276 | let bytes = Bytes(existingBytes: frame.payload!) 277 | let streamId = bytes.export32Bits().netToHost 278 | let errorCode = bytes.export32Bits().netToHost 279 | var message = "" 280 | if bytes.availableExportBytes > 0 { 281 | message = UTF8Encoding.encode(bytes: bytes.exportBytes(count: bytes.availableExportBytes)) 282 | } 283 | 284 | let bytes2 = Bytes() 285 | let _ = bytes2.import32Bits(from: streamId.hostToNet) 286 | .import32Bits(from: 0) 287 | let frame2 = HTTP2Frame(length: 8, 288 | type: HTTP2FrameType.goAway.rawValue, 289 | flags: 0, 290 | streamId: streamId, 291 | payload: bytes2.data) 292 | self.writeHTTP2Frame(frame2) { 293 | b in 294 | 295 | self.close() 296 | } 297 | streamOpen = false 298 | callback(nil, "\(errorCode) \(message)") 299 | case .headers: 300 | let padded = (frame.flags & flagPadded) != 0 301 | // let priority = (frame.flags & HTTP2_PRIORITY) != 0 302 | // let end = (frame.flags & HTTP2_END_HEADERS) != 0 303 | 304 | if let ba = frame.payload, ba.count > 0 { 305 | let bytes = Bytes(existingBytes: ba) 306 | var padLength: UInt8 = 0 307 | // var streamDep = UInt32(0) 308 | // var weight = UInt8(0) 309 | 310 | if padded { 311 | padLength = bytes.export8Bits() 312 | } 313 | // if priority { 314 | // streamDep = bytes.export32Bits() 315 | // weight = bytes.export8Bits() 316 | // } 317 | self.decodeHeaders(from: bytes, endPosition: ba.count - Int(padLength), listener: response) 318 | } 319 | streamOpen = (frame.flags & flagEndStream) == 0 320 | if !streamOpen { 321 | callback(response, nil) 322 | } 323 | case .data: 324 | if let payload = frame.payload, frame.length > 0 { 325 | response.appendBody(bytes: payload) 326 | } 327 | streamOpen = (frame.flags & flagEndStream) == 0 328 | if !streamOpen { 329 | callback(response, nil) 330 | } 331 | default: 332 | streamOpen = false 333 | callback(nil, "Unexpected frame type \(frame.typeStr)") 334 | } 335 | 336 | } else { 337 | close() 338 | streamOpen = false 339 | callback(nil, "Connection dropped") 340 | } 341 | } 342 | } 343 | 344 | public func sendPing(callback: @escaping (Bool) -> ()) { 345 | let frame = HTTP2Frame(type: .ping, flags: 0, streamId: 0, payload: [0, 0, 0, 0, 0, 0, 0, 0]) 346 | writeHTTP2Frame(frame) { 347 | ok in 348 | guard ok, 349 | let response = self.dequeueFrame(timeoutSeconds: timeoutSeconds, streamId: 0), 350 | response.type == .ping else { 351 | return callback(false) 352 | } 353 | return callback(true) 354 | } 355 | } 356 | 357 | public func sendRequest(_ request: HTTPRequest, callback: @escaping (HTTPResponse?, String?) -> ()) { 358 | let streamId = newStreamId 359 | streams[streamId] = .idle 360 | 361 | let headerBytes = Bytes() 362 | let method = request.method 363 | let scheme = ssl ? "https" : "http" 364 | let path = request.uri 365 | 366 | do { 367 | let encoder = HPACKEncoder() 368 | try encoder.encodeHeader(out: headerBytes, nameStr: ":method", valueStr: method.description) 369 | try encoder.encodeHeader(out: headerBytes, nameStr: ":scheme", valueStr: scheme) 370 | try encoder.encodeHeader(out: headerBytes, nameStr: ":path", valueStr: path, sensitive: false, incrementalIndexing: false) 371 | try encoder.encodeHeader(out: headerBytes, nameStr: "host", valueStr: self.host) 372 | try encoder.encodeHeader(out: headerBytes, nameStr: "content-length", valueStr: "\(request.postBodyBytes?.count ?? 0)") 373 | for (name, value) in request.headers { 374 | let lowered = name.standardName.lowercased() 375 | var inc = true 376 | // this is APNS specific in that Apple wants the apns-id and apns-expiration headers to be indexed on the first request but not indexed on subsequent requests 377 | // !FIX! need to enable the caller to indicate policies such as this 378 | let n = UTF8Encoding.decode(string: lowered) 379 | let v = UTF8Encoding.decode(string: value) 380 | if streamId > 1 { // at least the second request 381 | inc = !(lowered == "apns-id" || lowered == "apns-expiration") 382 | } 383 | try encoder.encodeHeader(out: headerBytes, name: n, value: v, sensitive: false, incrementalIndexing: inc) 384 | } 385 | } catch { 386 | return callback(nil, "Header encoding exception \(error)") 387 | } 388 | let hasData = nil != request.postBodyBytes && request.postBodyBytes!.count > 0 389 | let frame = HTTP2Frame(length: UInt32(headerBytes.data.count), 390 | type: HTTP2FrameType.headers.rawValue, 391 | flags: flagEndHeaders | (hasData ? 0 : flagEndStream), 392 | streamId: streamId, 393 | payload: headerBytes.data) 394 | writeHTTP2Frame(frame) { [weak self] 395 | b in 396 | guard b else { 397 | return callback(nil, "Unable to write frame") 398 | } 399 | guard let s = self else { 400 | return callback(nil, nil) 401 | } 402 | s.streams[streamId] = .open 403 | if hasData { 404 | let frame2 = HTTP2Frame(length: UInt32(request.postBodyBytes?.count ?? 0), 405 | type: HTTP2FrameType.data.rawValue, 406 | flags: flagEndStream, 407 | streamId: streamId, 408 | payload: request.postBodyBytes) 409 | s.writeHTTP2Frame(frame2) { [weak self] 410 | b in 411 | guard let s = self else { 412 | return callback(nil, nil) 413 | } 414 | s.awaitResponse(streamId: streamId, request: request, callback: callback) 415 | } 416 | } else { 417 | s.awaitResponse(streamId: streamId, request: request, callback: callback) 418 | } 419 | } 420 | } 421 | 422 | func completeConnect(_ callback: @escaping (Bool) -> ()) { 423 | net.write(string: http2ConnectionPreface) { 424 | wrote in 425 | let settings = HTTP2Frame(length: 0, 426 | type: HTTP2FrameType.settings.rawValue, 427 | flags: 0, 428 | streamId: 0, 429 | payload: nil) 430 | self.writeHTTP2Frame(settings) { [weak self] 431 | b in 432 | if b { 433 | self?.startReadThread() 434 | } 435 | callback(b) 436 | } 437 | } 438 | } 439 | 440 | func bytesToHeader(_ b: [UInt8]) -> HTTP2Frame { 441 | let payloadLength = (UInt32(b[0]) << 16) + (UInt32(b[1]) << 8) + UInt32(b[2]) 442 | 443 | let type = b[3] 444 | let flags = b[4] 445 | var sid: UInt32 = UInt32(b[5]) 446 | sid <<= 8 447 | sid += UInt32(b[6]) 448 | sid <<= 8 449 | sid += UInt32(b[7]) 450 | sid <<= 8 451 | sid += UInt32(b[8]) 452 | 453 | sid &= ~0x80000000 454 | 455 | return HTTP2Frame(length: payloadLength, type: type, flags: flags, streamId: sid, payload: nil) 456 | } 457 | 458 | func readHTTP2Frame(timeout time: Double, callback: @escaping (HTTP2Frame?) -> ()) { 459 | let net = self.net 460 | net.readBytesFully(count: 9, timeoutSeconds: time) { 461 | bytes in 462 | if let b = bytes { 463 | var header = self.bytesToHeader(b) 464 | if header.length > 0 { 465 | net.readBytesFully(count: Int(header.length), timeoutSeconds: time) { 466 | bytes in 467 | header.payload = bytes 468 | callback(header) 469 | } 470 | } else { 471 | callback(header) 472 | } 473 | 474 | } else { 475 | callback(nil) 476 | } 477 | } 478 | } 479 | 480 | func writeHTTP2Frame(_ frame: HTTP2Frame, callback: (Bool) -> ()) { 481 | if !net.isValid { 482 | callback(false) 483 | } else if !net.writeFully(bytes: frame.headerBytes()) { 484 | callback(false) 485 | } else { 486 | if let p = frame.payload { 487 | callback(net.writeFully(bytes: p)) 488 | } else { 489 | callback(true) 490 | } 491 | } 492 | } 493 | 494 | func encodeHeaders(headers: [(String, String)]) -> Bytes { 495 | let b = Bytes() 496 | for header in headers { 497 | let n = UTF8Encoding.decode(string: header.0) 498 | let v = UTF8Encoding.decode(string: header.1) 499 | do { 500 | try encoder.encodeHeader(out: b, name: n, value: v, sensitive: false) 501 | } catch { 502 | self.close() 503 | break 504 | } 505 | } 506 | return b 507 | } 508 | 509 | func decodeHeaders(from frm: Bytes, endPosition: Int, listener: HeaderListener) { 510 | do { 511 | try decoder.decode(input: frm, headerListener: listener) 512 | } catch { 513 | print("error while decoding headers \(error)") 514 | self.close() 515 | } 516 | } 517 | } 518 | -------------------------------------------------------------------------------- /Tests/PerfectHTTPServerTests/PerfectHTTPServerTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import PerfectLib 3 | import PerfectNet 4 | import PerfectThread 5 | import PerfectHTTP 6 | @testable import PerfectHTTPServer 7 | 8 | func ShimHTTPRequest() -> HTTP11Request { 9 | return HTTP11Request(connection: NetTCP()) 10 | } 11 | 12 | class PerfectHTTPServerTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | compatRoutes = nil 17 | } 18 | 19 | func testHPACKEncode() { 20 | 21 | let encoder = HPACKEncoder() 22 | let b = Bytes() 23 | 24 | let headers = [ 25 | (":method", "POST"), 26 | (":scheme", "https"), 27 | (":path", "/3/device/00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0"), 28 | ("host", "api.development.push.apple.com"), 29 | ("apns-id", "eabeae54-14a8-11e5-b60b-1697f925ec7b"), 30 | ("apns-expiration", "0"), 31 | ("apns-priority", "10"), 32 | ("content-length", "33"), 33 | ("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4")] 34 | do { 35 | for (n, v) in headers { 36 | try encoder.encodeHeader(out: b, name: UTF8Encoding.decode(string: n), value: UTF8Encoding.decode(string: v), sensitive: false) 37 | } 38 | 39 | class Listener: HeaderListener { 40 | var headers = [(String, String)]() 41 | func addHeader(name nam: [UInt8], value: [UInt8], sensitive: Bool) { 42 | self.headers.append((UTF8Encoding.encode(bytes: nam), UTF8Encoding.encode(bytes: value))) 43 | } 44 | } 45 | 46 | let decoder = HPACKDecoder() 47 | let l = Listener() 48 | try decoder.decode(input: b, headerListener: l) 49 | 50 | XCTAssert(l.headers.count == headers.count) 51 | 52 | for i in 0.. 0 else { 283 | XCTFail("Could not read bytes from server") 284 | return endClient() 285 | } 286 | 287 | let str = UTF8Encoding.encode(bytes: bytes) 288 | let splitted = str.split(separator: "\r\n").map(String.init) 289 | 290 | XCTAssertEqual(splitted.last, msg) 291 | 292 | endClient() 293 | } 294 | } 295 | } 296 | } catch { 297 | XCTFail("Error thrown: \(error)") 298 | endClient() 299 | } 300 | waitForExpectations(timeout: 10000) { 301 | _ in 302 | 303 | } 304 | } 305 | 306 | func testSimpleStreamingHandler() { 307 | let port = 8282 308 | var routes = Routes() 309 | routes.add(method: .get, uri: "/", handler: { 310 | request, response in 311 | response.addHeader(.contentType, value: "text/plain") 312 | response.isStreaming = true 313 | response.appendBody(string: "A") 314 | response.push { 315 | ok in 316 | XCTAssert(ok, "Failed in .push") 317 | response.appendBody(string: "BC") 318 | response.completed() 319 | } 320 | } 321 | ) 322 | 323 | let clientExpectation = self.expectation(description: "client") 324 | let config: HTTPServer.LaunchContext 325 | do { 326 | config = try HTTPServer.launch(wait: false, name: "localhost", port: port, routes: routes) 327 | } catch { 328 | return XCTFail("Error: \(error)") 329 | } 330 | func endClient() { 331 | config.terminate() 332 | clientExpectation.fulfill() 333 | } 334 | 335 | let clientNet = NetTCP() 336 | do { 337 | try clientNet.connect(address: "127.0.0.1", port: UInt16(port), timeoutSeconds: 5.0) { 338 | net in 339 | 340 | guard let net = net else { 341 | XCTFail("Could not connect to server") 342 | return endClient() 343 | } 344 | let reqStr = "GET / HTTP/1.0\r\nHost: localhost:\(port)\r\nFrom: me@host.com\r\n\r\n" 345 | net.write(string: reqStr) { 346 | count in 347 | 348 | guard count == reqStr.utf8.count else { 349 | XCTFail("Could not write request \(count) != \(reqStr.utf8.count)") 350 | return endClient() 351 | } 352 | 353 | Threading.sleep(seconds: 2.0) 354 | net.readSomeBytes(count: 2048) { 355 | bytes in 356 | 357 | guard let bytes = bytes, bytes.count > 0 else { 358 | XCTFail("Could not read bytes from server") 359 | return endClient() 360 | } 361 | 362 | let str = UTF8Encoding.encode(bytes: bytes) 363 | let splitted = str.split(separator: "\r\n", omittingEmptySubsequences: false).map(String.init) 364 | let compare = ["HTTP/1.0 200 OK", 365 | "Content-Type: text/plain", 366 | "Transfer-Encoding: chunked", 367 | "", 368 | "1", 369 | "A", 370 | "2", 371 | "BC", 372 | "0", 373 | "", 374 | ""] 375 | XCTAssert(splitted.count == compare.count) 376 | for (a, b) in zip(splitted, compare) { 377 | XCTAssert(a == b, "\(splitted) != \(compare)") 378 | } 379 | 380 | endClient() 381 | } 382 | } 383 | } 384 | } catch { 385 | XCTFail("Error thrown: \(error)") 386 | endClient() 387 | } 388 | waitForExpectations(timeout: 10000) { 389 | _ in 390 | 391 | } 392 | } 393 | 394 | func testSlowClient() { 395 | let port = 8282 396 | let msg = "Hello, world!" 397 | var routes = Routes() 398 | routes.add(method: .get, uri: "/", handler: { 399 | request, response in 400 | response.addHeader(.contentType, value: "text/plain") 401 | response.appendBody(string: msg) 402 | response.completed() 403 | } 404 | ) 405 | let clientExpectation = self.expectation(description: "client") 406 | let config: HTTPServer.LaunchContext 407 | do { 408 | config = try HTTPServer.launch(wait: false, name: "localhost", port: port, routes: routes) 409 | } catch { 410 | return XCTFail("Error: \(error)") 411 | } 412 | func endClient() { 413 | config.terminate() 414 | clientExpectation.fulfill() 415 | } 416 | let clientNet = NetTCP() 417 | do { 418 | try clientNet.connect(address: "127.0.0.1", port: UInt16(port), timeoutSeconds: 5.0) { 419 | net in 420 | 421 | guard let net = net else { 422 | XCTFail("Could not connect to server") 423 | return endClient() 424 | } 425 | var reqIt = Array("GET / HTTP/1.0\r\nHost: localhost:\(port)\r\nFrom: me@host.com\r\n\r\n".utf8).makeIterator() 426 | func pushChar() { 427 | if let b = reqIt.next() { 428 | let a = [b] 429 | net.write(bytes: a) { 430 | wrote in 431 | guard 1 == wrote else { 432 | XCTFail("Could not write request \(wrote) != \(1)") 433 | return endClient() 434 | } 435 | Threading.sleep(seconds: 0.5) 436 | Threading.dispatch { 437 | pushChar() 438 | } 439 | } 440 | } else { 441 | Threading.sleep(seconds: 2.0) 442 | net.readSomeBytes(count: 1024) { 443 | bytes in 444 | guard let bytes = bytes, bytes.count > 0 else { 445 | XCTFail("Could not read bytes from server") 446 | return endClient() 447 | } 448 | let str = UTF8Encoding.encode(bytes: bytes) 449 | let splitted = str.split(separator: "\r\n").map(String.init) 450 | XCTAssert(splitted.last == msg) 451 | endClient() 452 | } 453 | } 454 | } 455 | pushChar() 456 | } 457 | } catch { 458 | XCTFail("Error thrown: \(error)") 459 | endClient() 460 | } 461 | waitForExpectations(timeout: 20000) { 462 | _ in 463 | 464 | } 465 | } 466 | 467 | func testDiscoClient() { 468 | let port = 8282 469 | let msg = "Hello, world!" 470 | var routes = Routes() 471 | routes.add(method: .get, uri: "/") { 472 | request, response in 473 | response.addHeader(.contentType, value: "text/plain") 474 | response.appendBody(string: msg) 475 | response.completed() 476 | } 477 | let clientExpectation = self.expectation(description: "client") 478 | let config: HTTPServer.LaunchContext 479 | do { 480 | config = try HTTPServer.launch(wait: false, name: "localhost", port: port, routes: routes) 481 | } catch { 482 | return XCTFail("Error: \(error)") 483 | } 484 | func endClient() { 485 | config.terminate() 486 | clientExpectation.fulfill() 487 | } 488 | let clientNet = NetTCP() 489 | do { 490 | try clientNet.connect(address: "127.0.0.1", port: UInt16(port), timeoutSeconds: 5.0) { 491 | net in 492 | guard let net = net else { 493 | XCTFail("Could not connect to server") 494 | return endClient() 495 | } 496 | let partial = "GET / HTTP/1.0\r\nHost: localhost:\(port)\r" 497 | _ = net.writeFully(bytes: Array(partial.utf8)) 498 | net.close() 499 | endClient() 500 | } 501 | } catch { 502 | XCTFail("Error thrown: \(error)") 503 | endClient() 504 | } 505 | waitForExpectations(timeout: 20000) { 506 | _ in 507 | 508 | } 509 | } 510 | 511 | static var oneSet = false, twoSet = false, threeSet = false 512 | 513 | func testRequestFilters() { 514 | let port = 8282 515 | let msg = "Hello, world!" 516 | 517 | PerfectHTTPServerTests.oneSet = false 518 | PerfectHTTPServerTests.twoSet = false 519 | PerfectHTTPServerTests.threeSet = false 520 | 521 | struct Filter1: HTTPRequestFilter { 522 | func filter(request: HTTPRequest, response: HTTPResponse, callback: (HTTPRequestFilterResult) -> ()) { 523 | PerfectHTTPServerTests.oneSet = true 524 | callback(.continue(request, response)) 525 | } 526 | } 527 | struct Filter2: HTTPRequestFilter { 528 | func filter(request: HTTPRequest, response: HTTPResponse, callback: (HTTPRequestFilterResult) -> ()) { 529 | XCTAssert(PerfectHTTPServerTests.oneSet) 530 | XCTAssert(!PerfectHTTPServerTests.twoSet && !PerfectHTTPServerTests.threeSet) 531 | PerfectHTTPServerTests.twoSet = true 532 | callback(.execute(request, response)) 533 | } 534 | } 535 | struct Filter3: HTTPRequestFilter { 536 | func filter(request: HTTPRequest, response: HTTPResponse, callback: (HTTPRequestFilterResult) -> ()) { 537 | XCTFail("This filter should be skipped") 538 | callback(.continue(request, response)) 539 | } 540 | } 541 | struct Filter4: HTTPRequestFilter { 542 | func filter(request: HTTPRequest, response: HTTPResponse, callback: (HTTPRequestFilterResult) -> ()) { 543 | XCTAssert(PerfectHTTPServerTests.oneSet && PerfectHTTPServerTests.twoSet) 544 | XCTAssert(!PerfectHTTPServerTests.threeSet) 545 | PerfectHTTPServerTests.threeSet = true 546 | callback(.halt(request, response)) 547 | } 548 | } 549 | 550 | let requestFilters: [(HTTPRequestFilter, HTTPFilterPriority)] = [(Filter1(), HTTPFilterPriority.high), (Filter2(), HTTPFilterPriority.medium), (Filter3(), HTTPFilterPriority.medium), (Filter4(), HTTPFilterPriority.low)] 551 | var routes = Routes() 552 | routes.add(method: .get, uri: "/", handler: { 553 | request, response in 554 | XCTFail("This handler should not execute") 555 | response.addHeader(.contentType, value: "text/plain") 556 | response.appendBody(string: msg) 557 | response.completed() 558 | } 559 | ) 560 | let clientExpectation = self.expectation(description: "client") 561 | let config: HTTPServer.LaunchContext 562 | do { 563 | config = try HTTPServer.launch(wait: false, name: "localhost", port: port, routes: routes, requestFilters: requestFilters) 564 | } catch { 565 | return XCTFail("Error: \(error)") 566 | } 567 | func endClient() { 568 | config.terminate() 569 | clientExpectation.fulfill() 570 | } 571 | 572 | let clientNet = NetTCP() 573 | do { 574 | try clientNet.connect(address: "127.0.0.1", port: UInt16(port), timeoutSeconds: 5.0) { 575 | net in 576 | 577 | guard let net = net else { 578 | XCTFail("Could not connect to server") 579 | return endClient() 580 | } 581 | let reqStr = "GET / HTTP/1.0\r\nHost: localhost:\(port)\r\nFrom: me@host.com\r\n\r\n" 582 | net.write(string: reqStr) { 583 | count in 584 | 585 | guard count == reqStr.utf8.count else { 586 | XCTFail("Could not write request \(count) != \(reqStr.utf8.count)") 587 | return endClient() 588 | } 589 | 590 | Threading.sleep(seconds: 3.0) 591 | net.readSomeBytes(count: 1024) { 592 | bytes in 593 | 594 | guard let bytes = bytes, bytes.count > 0 else { 595 | XCTFail("Could not read bytes from server") 596 | return endClient() 597 | } 598 | 599 | endClient() 600 | } 601 | } 602 | } 603 | } catch { 604 | XCTFail("Error thrown: \(error)") 605 | endClient() 606 | } 607 | waitForExpectations(timeout: 10000) { 608 | _ in 609 | XCTAssert(PerfectHTTPServerTests.oneSet && PerfectHTTPServerTests.twoSet && PerfectHTTPServerTests.threeSet) 610 | } 611 | } 612 | 613 | func testResponseFilters() { 614 | let port = 8282 615 | 616 | struct Filter1: HTTPResponseFilter { 617 | func filterHeaders(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 618 | response.setHeader(.custom(name: "X-Custom"), value: "Value") 619 | callback(.continue) 620 | } 621 | func filterBody(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 622 | callback(.continue) 623 | } 624 | } 625 | 626 | struct Filter2: HTTPResponseFilter { 627 | func filterHeaders(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 628 | callback(.continue) 629 | } 630 | func filterBody(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 631 | var b = response.bodyBytes 632 | b = b.map { $0 == 65 ? 97 : $0 } 633 | response.bodyBytes = b 634 | callback(.continue) 635 | } 636 | } 637 | 638 | struct Filter3: HTTPResponseFilter { 639 | func filterHeaders(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 640 | callback(.continue) 641 | } 642 | func filterBody(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 643 | var b = response.bodyBytes 644 | b = b.map { $0 == 66 ? 98 : $0 } 645 | response.bodyBytes = b 646 | callback(.done) 647 | } 648 | } 649 | 650 | struct Filter4: HTTPResponseFilter { 651 | func filterHeaders(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 652 | callback(.continue) 653 | } 654 | func filterBody(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 655 | XCTFail("This should not execute") 656 | callback(.done) 657 | } 658 | } 659 | 660 | let responseFilters: [(HTTPResponseFilter, HTTPFilterPriority)] = [ 661 | (Filter1(), HTTPFilterPriority.high), 662 | (Filter2(), HTTPFilterPriority.medium), 663 | (Filter3(), HTTPFilterPriority.low), 664 | (Filter4(), HTTPFilterPriority.low) 665 | ] 666 | 667 | var routes = Routes() 668 | routes.add(method: .get, uri: "/", handler: { 669 | request, response in 670 | response.addHeader(.contentType, value: "text/plain") 671 | response.appendBody(string: "ABZABZ") 672 | response.completed() 673 | } 674 | ) 675 | let clientExpectation = self.expectation(description: "client") 676 | let config: HTTPServer.LaunchContext 677 | do { 678 | config = try HTTPServer.launch(wait: false, name: "localhost", port: port, routes: routes, responseFilters: responseFilters) 679 | } catch { 680 | return XCTFail("Error: \(error)") 681 | } 682 | func endClient() { 683 | config.terminate() 684 | clientExpectation.fulfill() 685 | } 686 | 687 | let clientNet = NetTCP() 688 | do { 689 | try clientNet.connect(address: "127.0.0.1", port: UInt16(port), timeoutSeconds: 5.0) { 690 | net in 691 | 692 | guard let net = net else { 693 | XCTFail("Could not connect to server") 694 | return endClient() 695 | } 696 | let reqStr = "GET / HTTP/1.0\r\nHost: localhost:\(port)\r\nFrom: me@host.com\r\n\r\n" 697 | net.write(string: reqStr) { 698 | count in 699 | 700 | guard count == reqStr.utf8.count else { 701 | XCTFail("Could not write request \(count) != \(reqStr.utf8.count)") 702 | return endClient() 703 | } 704 | 705 | Threading.sleep(seconds: 3.0) 706 | net.readSomeBytes(count: 2048) { 707 | bytes in 708 | 709 | guard let bytes = bytes, bytes.count > 0 else { 710 | XCTFail("Could not read bytes from server") 711 | return endClient() 712 | } 713 | 714 | let str = UTF8Encoding.encode(bytes: bytes) 715 | let splitted = str.split(separator: "\r\n", omittingEmptySubsequences: false).map(String.init) 716 | let compare = ["HTTP/1.0 200 OK", 717 | "Content-Type: text/plain", 718 | "Content-Length: 6", 719 | "X-Custom: Value", 720 | "", 721 | "abZabZ"] 722 | XCTAssert(splitted.count == compare.count) 723 | for (a, b) in zip(splitted, compare) { 724 | XCTAssert(a == b, "\(splitted) != \(compare)") 725 | } 726 | 727 | endClient() 728 | } 729 | } 730 | } 731 | } catch { 732 | XCTFail("Error thrown: \(error)") 733 | endClient() 734 | } 735 | waitForExpectations(timeout: 10000) { 736 | _ in 737 | } 738 | } 739 | 740 | func testStreamingResponseFilters() { 741 | let port = 8282 742 | 743 | struct Filter1: HTTPResponseFilter { 744 | func filterHeaders(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 745 | response.setHeader(.custom(name: "X-Custom"), value: "Value") 746 | callback(.continue) 747 | } 748 | func filterBody(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 749 | callback(.continue) 750 | } 751 | } 752 | 753 | struct Filter2: HTTPResponseFilter { 754 | func filterHeaders(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 755 | callback(.continue) 756 | } 757 | func filterBody(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 758 | var b = response.bodyBytes 759 | b = b.map { $0 == 65 ? 97 : $0 } 760 | response.bodyBytes = b 761 | callback(.continue) 762 | } 763 | } 764 | 765 | struct Filter3: HTTPResponseFilter { 766 | func filterHeaders(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 767 | callback(.continue) 768 | } 769 | func filterBody(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 770 | var b = response.bodyBytes 771 | b = b.map { $0 == 66 ? 98 : $0 } 772 | response.bodyBytes = b 773 | callback(.done) 774 | } 775 | } 776 | 777 | struct Filter4: HTTPResponseFilter { 778 | func filterHeaders(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 779 | callback(.continue) 780 | } 781 | func filterBody(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) { 782 | XCTFail("This should not execute") 783 | callback(.done) 784 | } 785 | } 786 | 787 | let responseFilters: [(HTTPResponseFilter, HTTPFilterPriority)] = [ 788 | (Filter1(), HTTPFilterPriority.high), 789 | (Filter2(), HTTPFilterPriority.medium), 790 | (Filter3(), HTTPFilterPriority.low), 791 | (Filter4(), HTTPFilterPriority.low) 792 | ] 793 | 794 | var routes = Routes() 795 | routes.add(method: .get, uri: "/", handler: { 796 | request, response in 797 | response.addHeader(.contentType, value: "text/plain") 798 | response.isStreaming = true 799 | response.appendBody(string: "ABZ") 800 | response.push { 801 | _ in 802 | response.appendBody(string: "ABZ") 803 | response.completed() 804 | } 805 | } 806 | ) 807 | let clientExpectation = self.expectation(description: "client") 808 | 809 | let config: HTTPServer.LaunchContext 810 | do { 811 | config = try HTTPServer.launch(wait: false, name: "localhost", port: port, routes: routes, responseFilters: responseFilters) 812 | } catch { 813 | return XCTFail("Error: \(error)") 814 | } 815 | 816 | func endClient() { 817 | config.terminate() 818 | clientExpectation.fulfill() 819 | } 820 | 821 | let clientNet = NetTCP() 822 | do { 823 | try clientNet.connect(address: "127.0.0.1", port: UInt16(port), timeoutSeconds: 5.0) { 824 | net in 825 | 826 | guard let net = net else { 827 | XCTFail("Could not connect to server") 828 | return endClient() 829 | } 830 | let reqStr = "GET / HTTP/1.0\r\nHost: localhost:\(port)\r\nFrom: me@host.com\r\n\r\n" 831 | net.write(string: reqStr) { 832 | count in 833 | 834 | guard count == reqStr.utf8.count else { 835 | XCTFail("Could not write request \(count) != \(reqStr.utf8.count)") 836 | return endClient() 837 | } 838 | 839 | Threading.sleep(seconds: 3.0) 840 | net.readSomeBytes(count: 2048) { 841 | bytes in 842 | 843 | guard let bytes = bytes, bytes.count > 0 else { 844 | XCTFail("Could not read bytes from server") 845 | return endClient() 846 | } 847 | 848 | let str = UTF8Encoding.encode(bytes: bytes) 849 | let splitted = str.split(separator: "\r\n", omittingEmptySubsequences: false).map(String.init) 850 | let compare = ["HTTP/1.0 200 OK", 851 | "Content-Type: text/plain", 852 | "Transfer-Encoding: chunked", 853 | "X-Custom: Value", 854 | "", 855 | "3", 856 | "abZ", 857 | "3", 858 | "abZ", 859 | "0", 860 | "", 861 | ""] 862 | XCTAssert(splitted.count == compare.count) 863 | for (a, b) in zip(splitted, compare) { 864 | XCTAssert(a == b, "\(splitted) != \(compare)") 865 | } 866 | 867 | endClient() 868 | } 869 | } 870 | } 871 | } catch { 872 | XCTFail("Error thrown: \(error)") 873 | endClient() 874 | } 875 | 876 | self.waitForExpectations(timeout: 10000, handler: { 877 | _ in 878 | }) 879 | } 880 | 881 | func testServerConf1() { 882 | 883 | let port = 8282 884 | 885 | func handler(data: [String:Any]) throws -> RequestHandler { 886 | return handler2 887 | } 888 | 889 | func handler2(request: HTTPRequest, response: HTTPResponse) { 890 | // Respond with a simple message. 891 | response.setHeader(.contentType, value: "text/html") 892 | response.appendBody(string: "Hello, world!Hello, world!") 893 | // Ensure that response.completed() is called when your processing is done. 894 | response.completed() 895 | } 896 | 897 | let confData = [ 898 | "servers": [ 899 | [ 900 | "name":"localhost", 901 | "address":"0.0.0.0", 902 | "port":port, 903 | "routes":[ 904 | ["method":"get", "uri":"/test.html", "handler":handler], 905 | ["method":"get", "uri":"/test.png", "handler":handler2] 906 | ], 907 | "filters":[ 908 | [ 909 | "type":"response", 910 | "priority":"high", 911 | "name":PerfectHTTPServer.HTTPFilter.contentCompression, 912 | ] 913 | ] 914 | ] 915 | ] 916 | ] 917 | let configs: [HTTPServer.LaunchContext] 918 | do { 919 | configs = try HTTPServer.launch(wait: false, configurationData: confData) 920 | } catch { 921 | return XCTFail("Error: \(error)") 922 | } 923 | 924 | let clientExpectation = self.expectation(description: "client") 925 | do { 926 | let client = NetTCP() 927 | try client.connect(address: "127.0.0.1", port: UInt16(port), timeoutSeconds: 5.0) { 928 | net in 929 | 930 | guard let net = net else { 931 | XCTFail("Could not connect to server") 932 | return clientExpectation.fulfill() 933 | } 934 | let reqStr = "GET /test.html HTTP/1.1\r\nHost: localhost:\(port)\r\nAccept-Encoding: gzip, deflate\r\n\r\n" 935 | net.write(string: reqStr) { 936 | count in 937 | 938 | guard count == reqStr.utf8.count else { 939 | XCTFail("Could not write request \(count) != \(reqStr.utf8.count) \(String(validatingUTF8: strerror(errno)) ?? "no error msg")") 940 | return clientExpectation.fulfill() 941 | } 942 | 943 | Threading.sleep(seconds: 2.0) 944 | net.readSomeBytes(count: 1024) { 945 | bytes in 946 | guard let bytes = bytes, bytes.count > 0 else { 947 | XCTFail("Could not read bytes from server") 948 | return clientExpectation.fulfill() 949 | } 950 | clientExpectation.fulfill() 951 | } 952 | } 953 | } 954 | } catch { 955 | XCTFail("Error thrown: \(error)") 956 | clientExpectation.fulfill() 957 | } 958 | 959 | waitForExpectations(timeout: 10000) { 960 | _ in 961 | configs.forEach { _ = try? $0.terminate().wait() } 962 | } 963 | } 964 | 965 | func testRoutingTrailingSlash() { 966 | let port = 8282 967 | var routes = Routes() 968 | let badHandler = { 969 | (_:HTTPRequest, resp:HTTPResponse) in 970 | resp.completed(status: .internalServerError) 971 | } 972 | let goodHandler = { 973 | (_:HTTPRequest, resp:HTTPResponse) in 974 | resp.completed(status: .notFound) 975 | } 976 | routes.add(method: .get, uri: "/", handler: { _, _ in }) 977 | routes.add(method: .get, uri: "/test/", handler: goodHandler) 978 | routes.add(method: .get, uri: "/**", handler: badHandler) 979 | 980 | let clientExpectation = self.expectation(description: "client") 981 | let config: HTTPServer.LaunchContext 982 | do { 983 | config = try HTTPServer.launch(wait: false, name: "localhost", port: port, routes: routes) 984 | } catch { 985 | return XCTFail("Error: \(error)") 986 | } 987 | 988 | func endClient() { 989 | config.terminate() 990 | clientExpectation.fulfill() 991 | } 992 | 993 | let clientNet = NetTCP() 994 | do { 995 | try clientNet.connect(address: "127.0.0.1", port: UInt16(port), timeoutSeconds: 5.0) { 996 | net in 997 | guard let net = net else { 998 | XCTFail("Could not connect to server") 999 | return endClient() 1000 | } 1001 | let reqStr = "GET /test/ HTTP/1.0\r\nHost: localhost:\(port)\r\nFrom: me@host.com\r\n\r\n" 1002 | net.write(string: reqStr) { 1003 | count in 1004 | guard count == reqStr.utf8.count else { 1005 | XCTFail("Could not write request \(count) != \(reqStr.utf8.count)") 1006 | return endClient() 1007 | } 1008 | Threading.sleep(seconds: 2.0) 1009 | net.readSomeBytes(count: 1024) { 1010 | bytes in 1011 | 1012 | guard let bytes = bytes, bytes.count > 0 else { 1013 | XCTFail("Could not read bytes from server") 1014 | return endClient() 1015 | } 1016 | let str = UTF8Encoding.encode(bytes: bytes) 1017 | let splitted = str.split(separator: "\r\n", omittingEmptySubsequences: false).map(String.init) 1018 | let compare = "HTTP/1.0 404 Not Found" 1019 | XCTAssertEqual(splitted.first, compare) 1020 | endClient() 1021 | } 1022 | } 1023 | } 1024 | } catch { 1025 | XCTFail("Error thrown: \(error)") 1026 | endClient() 1027 | } 1028 | waitForExpectations(timeout: 10000) { 1029 | _ in 1030 | } 1031 | } 1032 | 1033 | static var allTests : [(String, (PerfectHTTPServerTests) -> () throws -> Void)] { 1034 | return [ 1035 | ("testHPACKEncode", testHPACKEncode), 1036 | ("testWebConnectionHeadersWellFormed", testWebConnectionHeadersWellFormed), 1037 | ("testWebConnectionHeadersLF", testWebConnectionHeadersLF), 1038 | ("testWebConnectionHeadersMalormed", testWebConnectionHeadersMalormed), 1039 | ("testWebConnectionHeadersFolded", testWebConnectionHeadersFolded), 1040 | ("testWebConnectionHeadersTooLarge", testWebConnectionHeadersTooLarge), 1041 | ("testWebRequestQueryParam", testWebRequestQueryParam), 1042 | ("testWebRequestPostParam", testWebRequestPostParam), 1043 | ("testWebRequestCookie", testWebRequestCookie), 1044 | ("testWebRequestPath1", testWebRequestPath1), 1045 | ("testWebRequestPath2", testWebRequestPath2), 1046 | ("testWebRequestPath3", testWebRequestPath3), 1047 | ("testWebRequestPath4", testWebRequestPath4), 1048 | ("testSimpleHandler", testSimpleHandler), 1049 | ("testSimpleStreamingHandler", testSimpleStreamingHandler), 1050 | ("testSlowClient", testSlowClient), 1051 | ("testDiscoClient", testDiscoClient), 1052 | ("testRequestFilters", testRequestFilters), 1053 | ("testResponseFilters", testResponseFilters), 1054 | ("testStreamingResponseFilters", testStreamingResponseFilters), 1055 | ("testServerConf1", testServerConf1), 1056 | ("testRoutingTrailingSlash", testRoutingTrailingSlash) 1057 | ] 1058 | } 1059 | } 1060 | --------------------------------------------------------------------------------