From 7137ef38746c864c0b575e22b1913fefc4f14de4 Mon Sep 17 00:00:00 2001 From: Dan Loman Date: Wed, 1 Jun 2022 19:52:09 -0700 Subject: [PATCH 1/9] Update Package.swift; support only macOS 10.15+ --- Package.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 66fde6e1..40ac2821 100644 --- a/Package.swift +++ b/Package.swift @@ -1,10 +1,12 @@ -// swift-tools-version:5.0 +// swift-tools-version:5.6 import PackageDescription let package = Package( name: "Swifter", + platforms: [.macOS(.v10_15)], + products: [ .library(name: "Swifter", targets: ["Swifter"]), .executable(name: "SwifterExample", targets: ["SwifterExample"]) @@ -19,7 +21,7 @@ let package = Package( path: "Xcode/Sources" ), - .target( + .executableTarget( name: "SwifterExample", dependencies: [ "Swifter" From 7f8c15288becbf0254abd620f7aec0eb51fdfff5 Mon Sep 17 00:00:00 2001 From: Dan Loman Date: Wed, 1 Jun 2022 19:53:23 -0700 Subject: [PATCH 2/9] Update files to fix navigating through dirs --- Xcode/Sources/Files.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Xcode/Sources/Files.swift b/Xcode/Sources/Files.swift index a45e2c5b..9ab473ac 100644 --- a/Xcode/Sources/Files.swift +++ b/Xcode/Sources/Files.swift @@ -81,7 +81,7 @@ public func directoryBrowser(_ dir: String) -> ((HttpRequest) -> HttpResponse) { tr { td { a { - href = request.path + "/" + file + href = request.path + "%2F" + file inner = file } } From b5f01d96c422d84127b1f16c689ed58fc0e95290 Mon Sep 17 00:00:00 2001 From: Dan Loman Date: Wed, 1 Jun 2022 19:55:35 -0700 Subject: [PATCH 3/9] Update Router/Server/ServerIO --- Xcode/Sources/HttpRouter.swift | 8 +++--- Xcode/Sources/HttpServer.swift | 6 ++--- Xcode/Sources/HttpServerIO.swift | 45 ++++++++++++++++---------------- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/Xcode/Sources/HttpRouter.swift b/Xcode/Sources/HttpRouter.swift index 1429627e..73fe728c 100644 --- a/Xcode/Sources/HttpRouter.swift +++ b/Xcode/Sources/HttpRouter.swift @@ -20,7 +20,7 @@ open class HttpRouter { var isEndOfRoute: Bool = false /// The closure to handle the route - var handler: ((HttpRequest) -> HttpResponse)? + var handler: ((HttpRequest) async -> HttpResponse)? } private var rootNode = Node() @@ -47,7 +47,7 @@ open class HttpRouter { return result } - public func register(_ method: String?, path: String, handler: ((HttpRequest) -> HttpResponse)?) { + public func register(_ method: String?, path: String, handler: ((HttpRequest) async -> HttpResponse)?) { var pathSegments = stripQuery(path).split("/") if let method = method { pathSegments.insert(method, at: 0) @@ -58,7 +58,7 @@ open class HttpRouter { inflate(&rootNode, generator: &pathSegmentsGenerator).handler = handler } - public func route(_ method: String?, path: String) -> ([String: String], (HttpRequest) -> HttpResponse)? { + public func route(_ method: String?, path: String) -> ([String: String], (HttpRequest) async -> HttpResponse)? { return queue.sync { if let method = method { @@ -98,7 +98,7 @@ open class HttpRouter { return currentNode } - private func findHandler(_ node: inout Node, params: inout [String: String], generator: inout IndexingIterator<[String]>) -> ((HttpRequest) -> HttpResponse)? { + private func findHandler(_ node: inout Node, params: inout [String: String], generator: inout IndexingIterator<[String]>) -> ((HttpRequest) async -> HttpResponse)? { var matchedRoutes = [Node]() let pattern = generator.map { $0 } diff --git a/Xcode/Sources/HttpServer.swift b/Xcode/Sources/HttpServer.swift index b8d56790..4e4c95c1 100644 --- a/Xcode/Sources/HttpServer.swift +++ b/Xcode/Sources/HttpServer.swift @@ -41,7 +41,7 @@ open class HttpServer: HttpServerIO { public var DELETE, PATCH, HEAD, POST, GET, PUT: MethodRoute public var delete, patch, head, post, get, put: MethodRoute - public subscript(path: String) -> ((HttpRequest) -> HttpResponse)? { + public subscript(path: String) -> ((HttpRequest) async -> HttpResponse)? { get { return nil } set { router.register(nil, path: path, handler: newValue) @@ -56,7 +56,7 @@ open class HttpServer: HttpServerIO { public var middleware = [(HttpRequest) -> HttpResponse?]() - override open func dispatch(_ request: HttpRequest) -> ([String: String], (HttpRequest) -> HttpResponse) { + override open func dispatch(_ request: HttpRequest) -> ([String: String], (HttpRequest) async -> HttpResponse) { for layer in middleware { if let response = layer(request) { return ([:], { _ in response }) @@ -74,7 +74,7 @@ open class HttpServer: HttpServerIO { public struct MethodRoute { public let method: String public let router: HttpRouter - public subscript(path: String) -> ((HttpRequest) -> HttpResponse)? { + public subscript(path: String) -> ((HttpRequest) async -> HttpResponse)? { get { return nil } set { router.register(method, path: path, handler: newValue) diff --git a/Xcode/Sources/HttpServerIO.swift b/Xcode/Sources/HttpServerIO.swift index 65c6e95a..52caca15 100644 --- a/Xcode/Sources/HttpServerIO.swift +++ b/Xcode/Sources/HttpServerIO.swift @@ -67,7 +67,6 @@ open class HttpServerIO { stop() } - @available(macOS 10.10, *) public func start(_ port: in_port_t = 8080, forceIPv4: Bool = false, priority: DispatchQoS.QoSClass = DispatchQoS.QoSClass.background) throws { guard !self.operating else { return } stop() @@ -111,34 +110,36 @@ open class HttpServerIO { self.state = .stopped } - open func dispatch(_ request: HttpRequest) -> ([String: String], (HttpRequest) -> HttpResponse) { + open func dispatch(_ request: HttpRequest) -> ([String: String], (HttpRequest) async -> HttpResponse) { return ([:], { _ in HttpResponse.notFound(nil) }) } private func handleConnection(_ socket: Socket) { - let parser = HttpParser() - while self.operating, let request = try? parser.readHttpRequest(socket) { - let request = request - request.address = try? socket.peername() - let (params, handler) = self.dispatch(request) - request.params = params - let response = handler(request) - var keepConnection = parser.supportsKeepAlive(request.headers) - do { - if self.operating { - keepConnection = try self.respond(socket, response: response, keepAlive: keepConnection) + Task { + let parser = HttpParser() + while self.operating, let request = try? parser.readHttpRequest(socket) { + let request = request + request.address = try? socket.peername() + let (params, handler) = self.dispatch(request) + request.params = params + let response = await handler(request) + var keepConnection = parser.supportsKeepAlive(request.headers) + do { + if self.operating { + keepConnection = try self.respond(socket, response: response, keepAlive: keepConnection) + } + } catch { + print("Failed to send response: \(error)") } - } catch { - print("Failed to send response: \(error)") - } - if let session = response.socketSession() { - delegate?.socketConnectionReceived(socket) - session(socket) - break + if let session = response.socketSession() { + delegate?.socketConnectionReceived(socket) + session(socket) + break + } + if !keepConnection { break } } - if !keepConnection { break } + socket.close() } - socket.close() } private struct InnerWriteContext: HttpResponseBodyWriter { From 4324a05d4b9e816bbf934f6e2b47879584b10c57 Mon Sep 17 00:00:00 2001 From: Dan Loman Date: Wed, 1 Jun 2022 19:58:57 -0700 Subject: [PATCH 4/9] Fix test compilation --- Xcode/Tests/FilesTests.swift | 6 ++--- Xcode/Tests/SwifterTestsHttpRouter.swift | 34 ++++++++++++------------ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Xcode/Tests/FilesTests.swift b/Xcode/Tests/FilesTests.swift index 1f884844..2c82e5d8 100644 --- a/Xcode/Tests/FilesTests.swift +++ b/Xcode/Tests/FilesTests.swift @@ -55,7 +55,7 @@ class FilesTests: XCTestCase { let closure = shareFile(temporaryDirectoryURL.appendingPathComponent("does_not_exist").path) let result = closure(request) - XCTAssert(result == .notFound) + XCTAssert(result == .notFound()) } func testShareFilesFromDirectory() { @@ -77,7 +77,7 @@ class FilesTests: XCTestCase { let closure = shareFilesFromDirectory(temporaryDirectoryURL.path) let result = closure(request) - XCTAssert(result == .notFound) + XCTAssert(result == .notFound()) } func testDirectoryBrowser() { @@ -95,6 +95,6 @@ class FilesTests: XCTestCase { let closure = directoryBrowser(temporaryDirectoryURL.path) let result = closure(request) - XCTAssert(result == .notFound) + XCTAssert(result == .notFound()) } } diff --git a/Xcode/Tests/SwifterTestsHttpRouter.swift b/Xcode/Tests/SwifterTestsHttpRouter.swift index c4b06df5..e47a0962 100644 --- a/Xcode/Tests/SwifterTestsHttpRouter.swift +++ b/Xcode/Tests/SwifterTestsHttpRouter.swift @@ -128,7 +128,7 @@ class SwifterTestsHttpRouter: XCTestCase { XCTAssertNotNil(router.route(nil, path: "/a/%3C%3E/%5E")) } - func testHttpRouterHandlesOverlappingPaths() { + func testHttpRouterHandlesOverlappingPaths() async { let request = HttpRequest() @@ -151,19 +151,19 @@ class SwifterTestsHttpRouter: XCTestCase { let staticRouteResult = router.route("GET", path: "a/b") let staticRouterHandler = staticRouteResult?.1 XCTAssertNotNil(staticRouteResult) - _ = staticRouterHandler?(request) + _ = await staticRouterHandler?(request) let variableRouteResult = router.route("GET", path: "a/b/c") let variableRouterHandler = variableRouteResult?.1 XCTAssertNotNil(variableRouteResult) - _ = variableRouterHandler?(request) + _ = await variableRouterHandler?(request) - waitForExpectations(timeout: 10, handler: nil) + await waitForExpectations(timeout: 10, handler: nil) XCTAssertTrue(foundStaticRoute) XCTAssertTrue(foundVariableRoute) } - func testHttpRouterHandlesOverlappingPathsInDynamicRoutes() { + func testHttpRouterHandlesOverlappingPathsInDynamicRoutes() async { let request = HttpRequest() @@ -186,19 +186,19 @@ class SwifterTestsHttpRouter: XCTestCase { let firstRouteResult = router.route("GET", path: "a/b") let firstRouterHandler = firstRouteResult?.1 XCTAssertNotNil(firstRouteResult) - _ = firstRouterHandler?(request) + _ = await firstRouterHandler?(request) let secondRouteResult = router.route("GET", path: "a/b/c") let secondRouterHandler = secondRouteResult?.1 XCTAssertNotNil(secondRouteResult) - _ = secondRouterHandler?(request) + _ = await secondRouterHandler?(request) - waitForExpectations(timeout: 10, handler: nil) + await waitForExpectations(timeout: 10, handler: nil) XCTAssertTrue(foundFirstVariableRoute) XCTAssertTrue(foundSecondVariableRoute) } - func testHttpRouterShouldHandleOverlappingRoutesInTrail() { + func testHttpRouterShouldHandleOverlappingRoutesInTrail() async { let request = HttpRequest() @@ -229,25 +229,25 @@ class SwifterTestsHttpRouter: XCTestCase { let firstRouteResult = router.route("GET", path: "/a") let firstRouterHandler = firstRouteResult?.1 XCTAssertNotNil(firstRouteResult) - _ = firstRouterHandler?(request) + _ = await firstRouterHandler?(request) let secondRouteResult = router.route("GET", path: "/a/b") let secondRouterHandler = secondRouteResult?.1 XCTAssertNotNil(secondRouteResult) - _ = secondRouterHandler?(request) + _ = await secondRouterHandler?(request) let thirdRouteResult = router.route("GET", path: "/a/b/b") let thirdRouterHandler = thirdRouteResult?.1 XCTAssertNotNil(thirdRouteResult) - _ = thirdRouterHandler?(request) + _ = await thirdRouterHandler?(request) - waitForExpectations(timeout: 10, handler: nil) + await waitForExpectations(timeout: 10, handler: nil) XCTAssertTrue(foundFirstVariableRoute) XCTAssertTrue(foundSecondVariableRoute) XCTAssertTrue(foundThirdVariableRoute) } - func testHttpRouterHandlesOverlappingPathsInDynamicRoutesInTheMiddle() { + func testHttpRouterHandlesOverlappingPathsInDynamicRoutesInTheMiddle() async { let request = HttpRequest() @@ -270,14 +270,14 @@ class SwifterTestsHttpRouter: XCTestCase { let firstRouteResult = router.route("GET", path: "/a/b/c/d/e") let firstRouterHandler = firstRouteResult?.1 XCTAssertNotNil(firstRouteResult) - _ = firstRouterHandler?(request) + _ = await firstRouterHandler?(request) let secondRouteResult = router.route("GET", path: "/a/b/f/g") let secondRouterHandler = secondRouteResult?.1 XCTAssertNotNil(secondRouteResult) - _ = secondRouterHandler?(request) + _ = await secondRouterHandler?(request) - waitForExpectations(timeout: 10, handler: nil) + await waitForExpectations(timeout: 10, handler: nil) XCTAssertTrue(foundFirstVariableRoute) XCTAssertTrue(foundSecondVariableRoute) } From 86cfa2006c326fca66d2a401739ac3670ef0a335 Mon Sep 17 00:00:00 2001 From: Dan Loman Date: Wed, 1 Jun 2022 23:17:34 -0700 Subject: [PATCH 5/9] Excluce DemoServer from Swifter package --- Package.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 40ac2821..128cf60b 100644 --- a/Package.swift +++ b/Package.swift @@ -18,8 +18,9 @@ let package = Package( .target( name: "Swifter", dependencies: [], - path: "Xcode/Sources" - ), + path: "Xcode/Sources", + exclude: ["Xcode/Sources/DemoServer.swift"] + ), .executableTarget( name: "SwifterExample", From cb8d7cf91977b028f32185a34ea1e5dee9ea271d Mon Sep 17 00:00:00 2001 From: Dan Loman Date: Sun, 5 Jun 2022 23:23:08 -0700 Subject: [PATCH 6/9] Add Controller protocol and convenience subscript --- Xcode/Sources/Controller.swift | 3 +++ Xcode/Sources/HttpServer.swift | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 Xcode/Sources/Controller.swift diff --git a/Xcode/Sources/Controller.swift b/Xcode/Sources/Controller.swift new file mode 100644 index 00000000..47a19bc7 --- /dev/null +++ b/Xcode/Sources/Controller.swift @@ -0,0 +1,3 @@ +public protocol Controller { + init(request: HttpRequest) +} diff --git a/Xcode/Sources/HttpServer.swift b/Xcode/Sources/HttpServer.swift index 4e4c95c1..b9facc26 100644 --- a/Xcode/Sources/HttpServer.swift +++ b/Xcode/Sources/HttpServer.swift @@ -80,5 +80,14 @@ open class HttpServer: HttpServerIO { router.register(method, path: path, handler: newValue) } } + public subscript(path: String) -> ((T) -> () async -> HttpResponse)? { + get { nil } + set { + guard let newValue = newValue else { return } + router.register(method, path: path) { request in + await newValue(T(request: request))() + } + } + } } } From b9953ce954b8d5b64ac75937518c8d48bbf1ea34 Mon Sep 17 00:00:00 2001 From: Dan Loman Date: Thu, 30 Jun 2022 13:05:01 -0700 Subject: [PATCH 7/9] Allow multiple paths to be registered w/ same handler --- Xcode/Sources/HttpServer.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Xcode/Sources/HttpServer.swift b/Xcode/Sources/HttpServer.swift index b9facc26..26465026 100644 --- a/Xcode/Sources/HttpServer.swift +++ b/Xcode/Sources/HttpServer.swift @@ -74,18 +74,20 @@ open class HttpServer: HttpServerIO { public struct MethodRoute { public let method: String public let router: HttpRouter - public subscript(path: String) -> ((HttpRequest) async -> HttpResponse)? { + public subscript(paths: String...) -> ((HttpRequest) async -> HttpResponse)? { get { return nil } set { - router.register(method, path: path, handler: newValue) + paths.forEach { router.register(method, path: $0, handler: newValue) } } } - public subscript(path: String) -> ((T) -> () async -> HttpResponse)? { + public subscript(paths: String...) -> ((T) -> () async -> HttpResponse)? { get { nil } set { guard let newValue = newValue else { return } - router.register(method, path: path) { request in - await newValue(T(request: request))() + paths.forEach { path in + router.register(method, path: path) { request in + await newValue(T(request: request))() + } } } } From 3ff32c046a654259b5eda827158be254db99cb88 Mon Sep 17 00:00:00 2001 From: Dan Loman Date: Sun, 31 Jul 2022 10:13:24 -0700 Subject: [PATCH 8/9] Drop ":" from variable/param name --- Xcode/Sources/HttpRouter.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Xcode/Sources/HttpRouter.swift b/Xcode/Sources/HttpRouter.swift index 73fe728c..51784cf7 100644 --- a/Xcode/Sources/HttpRouter.swift +++ b/Xcode/Sources/HttpRouter.swift @@ -125,20 +125,21 @@ open class HttpRouter { var currentIndex = index + 1 let variableNodes = node.nodes.filter { $0.0.first == ":" } if let variableNode = variableNodes.first { + let paramName = String(variableNode.0.dropFirst()) if currentIndex == count && variableNode.1.isEndOfRoute { // if it's the last element of the pattern and it's a variable, stop the search and // append a tail as a value for the variable. let tail = pattern[currentIndex.. 0 { - params[variableNode.0] = pathToken + "/" + tail + params[paramName] = pathToken + "/" + tail } else { - params[variableNode.0] = pathToken + params[paramName] = pathToken } matchedNodes.append(variableNode.value) return } - params[variableNode.0] = pathToken + params[paramName] = pathToken findHandler(&node.nodes[variableNode.0]!, params: ¶ms, pattern: pattern, matchedNodes: &matchedNodes, index: currentIndex, count: count) } From a51ae97214d6d24e18aa9e824eea5f1f4021d171 Mon Sep 17 00:00:00 2001 From: Dan Loman Date: Sun, 31 Jul 2022 10:17:37 -0700 Subject: [PATCH 9/9] Update Xcode and Swift versions --- .github/workflows/linux-tests.yml | 4 ++-- .github/workflows/macos-tests.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linux-tests.yml b/.github/workflows/linux-tests.yml index 67f4ed5a..aaafe4f9 100644 --- a/.github/workflows/linux-tests.yml +++ b/.github/workflows/linux-tests.yml @@ -8,9 +8,9 @@ jobs: Test-Linux-Platform: runs-on: ubuntu-latest container: - image: swift:5.2 + image: swift:5.6 options: --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --security-opt apparmor=unconfined steps: - uses: actions/checkout@v2 - name: Run Unit Tests - run: swift test \ No newline at end of file + run: swift test diff --git a/.github/workflows/macos-tests.yml b/.github/workflows/macos-tests.yml index 9989f63f..6c31bcde 100644 --- a/.github/workflows/macos-tests.yml +++ b/.github/workflows/macos-tests.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: maxim-lobanov/setup-xcode@v1.1 with: - xcode-version: 12.0 + xcode-version: 13.2 - uses: actions/checkout@v2 - name: Create Test Result Directory run: | @@ -39,4 +39,4 @@ jobs: uses: actions/upload-artifact@v1 with: name: test-results - path: tmp/test-results \ No newline at end of file + path: tmp/test-results