Skip to content

Commit

Permalink
requests now use SIMD processing
Browse files Browse the repository at this point in the history
- added `SocketProtocol` `loadRequest` function
- removed two now obsolete function requirements for `SocketProtocol`
- `DynamicMiddlewareProtocol` now takes a mutating `Request` instead of borrowing one
- SIMD extension logic fixes
- updated `SIMDTests`
  • Loading branch information
RandomHashTags committed Nov 8, 2024
1 parent b126248 commit 0d13cf8
Show file tree
Hide file tree
Showing 11 changed files with 200 additions and 133 deletions.
32 changes: 14 additions & 18 deletions Sources/Destiny/Server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,44 +147,40 @@ enum ClientProcessing {
shutdown(client, 2) // shutdown read and write (https://www.gnu.org/software/libc/manual/html_node/Closing-a-Socket.html)
close(client)
}
//let request:Request = try client_socket.loadRequest()
let token:DestinyRoutePathType = try client_socket.readLineSIMD()
if let responder:StaticRouteResponseProtocol = static_responses[token] {
var request:Request = try client_socket.loadRequest()
if let responder:StaticRouteResponseProtocol = static_responses[request.startLine] {
if responder.isAsync {
try await responder.respondAsync(to: client_socket)
} else {
try responder.respond(to: client_socket)
}
} else if let (start_line, route):(HTTPStartLine, DynamicRouteResponseProtocol) = dynamic_responses[token] {
let headers:[String:String] = try client_socket.readHeaders()
let request:Request = Request(token: token, method: start_line.method, path: start_line.path, version: start_line.version, headers: headers, body: "")

var response:DynamicResponseProtocol = route.defaultResponse
for index in route.parameterPathIndexes {
response.parameters[route.path[index].value] = start_line.path[index]
} else if let responder:DynamicRouteResponseProtocol = dynamic_responses.responder(for: &request) {
var response:DynamicResponseProtocol = responder.defaultResponse
for index in responder.parameterPathIndexes {
response.parameters[responder.path[index].value] = request.path[index]
}
for middleware in dynamic_middleware {
if middleware.shouldHandle(request: request, response: response) {
if middleware.shouldHandle(request: &request, response: response) {
do {
if middleware.isAsync {
try await middleware.handleAsync(request: request, response: &response)
try await middleware.handleAsync(request: &request, response: &response)
} else {
try middleware.handle(request: request, response: &response)
try middleware.handle(request: &request, response: &response)
}
} catch {
if middleware.isAsync {
await middleware.onErrorAsync(request: request, response: &response, error: error)
await middleware.onErrorAsync(request: &request, response: &response, error: error)
} else {
middleware.onError(request: request, response: &response, error: error)
middleware.onError(request: &request, response: &response, error: error)
}
break
}
}
}
if route.isAsync {
try await route.respondAsync(to: client_socket, request: request, response: &response)
if responder.isAsync {
try await responder.respondAsync(to: client_socket, request: request, response: &response)
} else {
try route.respond(to: client_socket, request: request, response: &response)
try responder.respond(to: client_socket, request: request, response: &response)
}
} else {
var err:Swift.Error? = nil
Expand Down
36 changes: 3 additions & 33 deletions Sources/Destiny/Socket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,15 @@ public extension Socket {
return result
}

/// Reads multiple bytes and loads them into a SIMD vector.
@inlinable
func readBytesSIMD<T: SIMD>() throws -> T {
var result:T = T()
try withUnsafeMutablePointer(to: &result, { p in
let bytes_read:Int = recv(fileDescriptor, p, T.scalarCount, 0)
if bytes_read < 0 {
throw SocketError.readSingleByteFailed()
}
})
return result
}

@inlinable
func readLine() throws -> String {
var line:String = ""
var index:UInt8 = 0
while true {
index = try self.readByte()
if index == 10 {
if index == 10 { // line feed (\n)
break
} else if index == 13 {
} else if index == 13 { // carriage return (\r)
continue
}
line.append(Character(UnicodeScalar(index)))
Expand All @@ -73,34 +60,17 @@ public extension Socket {
func loadRequest() throws -> Request {
var test:[SIMD64<UInt8>] = []
test.reserveCapacity(10) // maximum of 640 bytes; decent starting point
var head_count:Int = 0
var token:DestinyRoutePathType = .init()
while true {
let (line, read):(SIMD64<UInt8>, Int) = try readLineSIMD2(length: 64)
if head_count == 0 {
token = line.lowHalf
}
if read == 0 {
break
}
test.append(line)
head_count += read
if read < 64 {
break
}
}
print("test strings=\(test.map({ $0.string() }))")
return Request(token: token, method: .get, path: ["dynamic", "rekt"], version: "HTTP/1.1", headers: [:], body: "")
}

/// Reads `scalarCount` characters and loads them into the target SIMD.
@inlinable
func readLineSIMD<T : SIMD>() throws -> T where T.Scalar == UInt8 { // read just the method, path & http version
var string:T = T()
let _:Int = try withUnsafeMutableBytes(of: &string) { p in
return try readBuffer(into: p.baseAddress!, length: T.scalarCount)
}
return string
return Request(tokens: test)
}

@inlinable
Expand Down
40 changes: 20 additions & 20 deletions Sources/DestinyUtilities/DynamicMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,28 @@ import SwiftSyntax

/// The default Dynamic Middleware that powers Destiny's dynamic middleware which handles requests to dynamic routes.
public struct DynamicMiddleware : DynamicMiddlewareProtocol {
public static let defaultOnError:@Sendable (_ request: borrowing Request, _ response: inout DynamicResponseProtocol, _ error: Error) -> Void = { request, response, error in
public static let defaultOnError:@Sendable (_ request: inout Request, _ response: inout DynamicResponseProtocol, _ error: Error) -> Void = { request, response, error in
response.status = .internalServerError
response.headers[HTTPField.Name.contentType.rawName] = HTTPMediaType.Application.json.rawValue
response.result = .string("{\"error\":true,\"reason\":\"\(error)\"}")
}

public let async:Bool
public let shouldHandleLogic:@Sendable (_ request: borrowing Request, _ response: borrowing DynamicResponseProtocol) -> Bool
public let handleLogic:(@Sendable (_ request: borrowing Request, _ response: inout DynamicResponseProtocol) throws -> Void)?
public let handleLogicAsync:(@Sendable (_ request: borrowing Request, _ response: inout DynamicResponseProtocol) async throws -> Void)?
public let onError:@Sendable (_ request: borrowing Request, _ response: inout DynamicResponseProtocol, _ error: Error) -> Void
public let onErrorAsync:@Sendable (_ request: borrowing Request, _ response: inout DynamicResponseProtocol, _ error: Error) async -> Void
public let shouldHandleLogic:@Sendable (_ request: inout Request, _ response: borrowing DynamicResponseProtocol) -> Bool
public let handleLogic:(@Sendable (_ request: inout Request, _ response: inout DynamicResponseProtocol) throws -> Void)?
public let handleLogicAsync:(@Sendable (_ request: inout Request, _ response: inout DynamicResponseProtocol) async throws -> Void)?
public let onError:@Sendable (_ request: inout Request, _ response: inout DynamicResponseProtocol, _ error: Error) -> Void
public let onErrorAsync:@Sendable (_ request: inout Request, _ response: inout DynamicResponseProtocol, _ error: Error) async -> Void

fileprivate var logic:String = ""

public init(
async: Bool,
shouldHandleLogic: @escaping @Sendable (_ request: borrowing Request, _ response: borrowing DynamicResponseProtocol) -> Bool,
handleLogic: (@Sendable (_ request: borrowing Request, _ response: inout DynamicResponseProtocol) throws -> Void)?,
handleLogicAsync: (@Sendable (_ request: borrowing Request, _ response: inout DynamicResponseProtocol) async throws -> Void)?,
onError: @escaping @Sendable (_ request: borrowing Request, _ response: inout DynamicResponseProtocol, _ error: Error) -> Void = DynamicMiddleware.defaultOnError,
onErrorAsync: @escaping @Sendable (_ request: borrowing Request, _ response: inout DynamicResponseProtocol, _ error: Error) async -> Void = DynamicMiddleware.defaultOnError
shouldHandleLogic: @escaping @Sendable (_ request: inout Request, _ response: borrowing DynamicResponseProtocol) -> Bool,
handleLogic: (@Sendable (_ request: inout Request, _ response: inout DynamicResponseProtocol) throws -> Void)?,
handleLogicAsync: (@Sendable (_ request: inout Request, _ response: inout DynamicResponseProtocol) async throws -> Void)?,
onError: @escaping @Sendable (_ request: inout Request, _ response: inout DynamicResponseProtocol, _ error: Error) -> Void = DynamicMiddleware.defaultOnError,
onErrorAsync: @escaping @Sendable (_ request: inout Request, _ response: inout DynamicResponseProtocol, _ error: Error) async -> Void = DynamicMiddleware.defaultOnError
) {
self.async = async
self.shouldHandleLogic = shouldHandleLogic
Expand All @@ -43,21 +43,21 @@ public struct DynamicMiddleware : DynamicMiddlewareProtocol {

public var isAsync: Bool { async }

public func shouldHandle(request: borrowing Request, response: borrowing DynamicResponseProtocol) -> Bool { shouldHandleLogic(request, response) }
public func shouldHandle(request: inout Request, response: borrowing DynamicResponseProtocol) -> Bool { shouldHandleLogic(&request, response) }

public func handle(request: borrowing Request, response: inout DynamicResponseProtocol) throws {
try handleLogic!(request, &response)
public func handle(request: inout Request, response: inout DynamicResponseProtocol) throws {
try handleLogic!(&request, &response)
}

public func handleAsync(request: borrowing Request, response: inout DynamicResponseProtocol) async throws {
try await handleLogicAsync!(request, &response)
public func handleAsync(request: inout Request, response: inout DynamicResponseProtocol) async throws {
try await handleLogicAsync!(&request, &response)
}

public func onError(request: borrowing Request, response: inout DynamicResponseProtocol, error: Error) {
onError(request, &response, error)
public func onError(request: inout Request, response: inout DynamicResponseProtocol, error: Error) {
onError(&request, &response, error)
}
public func onErrorAsync(request: borrowing Request, response: inout DynamicResponseProtocol, error: any Error) async {
await onErrorAsync(request, &response, error)
public func onErrorAsync(request: inout Request, response: inout DynamicResponseProtocol, error: any Error) async {
await onErrorAsync(&request, &response, error)
}

public var debugDescription : String {
Expand Down
15 changes: 5 additions & 10 deletions Sources/DestinyUtilities/DynamicResponses.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,11 @@ public struct DynamicResponses : Sendable {
}
}

public subscript(_ token: DestinyRoutePathType) -> (HTTPStartLine, DynamicRouteResponseProtocol)? {
let spaced:[DestinyRoutePathType] = token.splitSIMD(separator: 32) // 32 = space
guard let version:String = spaced.get(2)?.string(), let method:HTTPRequest.Method = HTTPRequest.Method.parse(spaced[0].string()) else {
return nil
}
let values:[String] = spaced[1].splitSIMD(separator: 47).map({ $0.string() }) // 1 = the target route path; 47 = slash
let start_line:HTTPStartLine = HTTPStartLine(method: method, path: values, version: version)
if let responder:DynamicRouteResponseProtocol = parameterless[token] {
return (start_line, responder)
public func responder(for request: inout Request) -> DynamicRouteResponseProtocol? {
if let responder:DynamicRouteResponseProtocol = parameterless[request.startLine] {
return responder
}
let values:[String] = request.path
guard let routes:[DynamicRouteResponseProtocol] = parameterized.get(values.count) else { return nil }
for route in routes {
var found:Bool = true
Expand All @@ -55,7 +50,7 @@ public struct DynamicResponses : Sendable {
}
}
if found {
return (start_line, route)
return route
}
}
return nil
Expand Down
24 changes: 24 additions & 0 deletions Sources/DestinyUtilities/HTTPRequestMethodParse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,28 @@ public extension HTTPRequest.Method {
default: return .init(key)
}
}
// MARK: Parse by SIMD key
static func parse(_ key: StackString8) -> Self? {
switch key {
case Self.getSIMD: return .get
case Self.headSIMD: return .head
case Self.postSIMD: return .post
case Self.putSIMD: return .put
case Self.deleteSIMD: return .delete
case Self.connectSIMD: return .connect
case Self.optionsSIMD: return .options
case Self.traceSIMD: return .trace
case Self.patchSIMD: return .patch
default: return .init(key.stringSIMD())
}
}
static let getSIMD:StackString8 = StackString8(71, 69, 84, 0, 0, 0, 0, 0)
static let headSIMD:StackString8 = StackString8(72, 69, 65, 68, 0, 0, 0, 0)
static let postSIMD:StackString8 = StackString8(80, 79, 83, 84, 0, 0, 0, 0)
static let putSIMD:StackString8 = StackString8(80, 85, 84, 0, 0, 0, 0, 0)
static let deleteSIMD:StackString8 = StackString8(68, 69, 76, 69, 84, 69, 0, 0)
static let connectSIMD:StackString8 = StackString8(67, 79, 78, 78, 69, 67, 84, 0)
static let optionsSIMD:StackString8 = StackString8(79, 80, 84, 73, 79, 78, 83, 0)
static let traceSIMD:StackString8 = StackString8(84, 82, 65, 67, 69, 0, 0, 0)
static let patchSIMD:StackString8 = StackString8(80, 65, 84, 67, 72, 0, 0, 0)
}
39 changes: 39 additions & 0 deletions Sources/DestinyUtilities/HTTPVersion.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// HTTPVersion.swift
//
//
// Created by Evan Anderson on 11/8/24.
//

public struct HTTPVersion : Sendable, ~Copyable {
public let token:StackString8

init(_ token: StackString8) {
self.token = token
}
public init(_ path: DestinyRoutePathType) {
token = path.lowHalf.lowHalf
}
}

public extension HTTPVersion {
private static func get(major: UInt8, minor: UInt8) -> HTTPVersion {
var token:StackString8 = StackString8()
token[0] = 72 // H
token[1] = 84 // T
token[2] = 84 // T
token[3] = 80 // P
token[4] = 47 // /
token[5] = 48 + major // major
token[6] = 46 // .
token[7] = 48 + minor // minor
return HTTPVersion(token)
}

static let v0_9:HTTPVersion = get(major: 0, minor: 9)
static let v1_0:HTTPVersion = get(major: 1, minor: 0)
static let v1_1:HTTPVersion = get(major: 1, minor: 1)
static let v1_2:HTTPVersion = get(major: 1, minor: 2)
static let v2_0:HTTPVersion = get(major: 2, minor: 0)
static let v3_0:HTTPVersion = get(major: 3, minor: 0)
}
10 changes: 5 additions & 5 deletions Sources/DestinyUtilities/Middleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ public protocol DynamicMiddlewareProtocol : MiddlewareProtocol {
var isAsync : Bool { get }

/// Whether or not this middleware should handle a request.
@inlinable func shouldHandle(request: borrowing Request, response: borrowing DynamicResponseProtocol) -> Bool
@inlinable func shouldHandle(request: inout Request, response: borrowing DynamicResponseProtocol) -> Bool

@inlinable func handle(request: borrowing Request, response: inout DynamicResponseProtocol) throws
@inlinable func handleAsync(request: borrowing Request, response: inout DynamicResponseProtocol) async throws
@inlinable func handle(request: inout Request, response: inout DynamicResponseProtocol) throws
@inlinable func handleAsync(request: inout Request, response: inout DynamicResponseProtocol) async throws

@inlinable func onError(request: borrowing Request, response: inout DynamicResponseProtocol, error: Error)
@inlinable func onErrorAsync(request: borrowing Request, response: inout DynamicResponseProtocol, error: Error) async
@inlinable func onError(request: inout Request, response: inout DynamicResponseProtocol, error: Error)
@inlinable func onErrorAsync(request: inout Request, response: inout DynamicResponseProtocol, error: Error) async
}

// MARK: StaticMiddlewareProtocol
Expand Down
3 changes: 0 additions & 3 deletions Sources/DestinyUtilities/SocketProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ public protocol SocketProtocol : ~Copyable {

@inlinable func loadRequest() throws -> Request

@inlinable func readLineSIMD<T: SIMD>() throws -> T where T.Scalar == UInt8
@inlinable func readHeaders() throws -> [String:String] // TODO: make faster (replace with a SIMD/StackString equivalent)

@inlinable func readBuffer(into baseAddress: UnsafeMutablePointer<UInt8>, length: Int) throws -> Int
/// Writes a buffer to the socket.
@inlinable func writeBuffer(_ pointer: UnsafeRawPointer, length: Int) throws
Expand Down
Loading

0 comments on commit 0d13cf8

Please sign in to comment.