// // AsyncHTTPClient+HTTP.swift // // // Created by Kabir Oberai on 05/05/21. // #if os(Linux) import Foundation import AsyncHTTPClient import NIO import NIOHTTP1 import NIOSSL import NIOFoundationCompat import WebSocketKit import OpenAPIRuntime import OpenAPIAsyncHTTPClient import Dependencies extension HTTPClientDependencyKey: DependencyKey { public static let liveValue: HTTPClientProtocol = Client() } private struct Client: HTTPClientProtocol { private static var proxyConfiguration: HTTPClient.Configuration.Proxy? { // e.g. `XTL_HTTP_PROXY=localhost:8080` guard let value = ProcessInfo.processInfo.environment["XTL_HTTP_PROXY"], let separator = value.lastIndex(of: ":"), let port = Int(value[value.index(after: separator)...]) else { return nil } let host = String(value[.. any WebSocketSession { let (stream, continuation) = AsyncStream.makeStream(of: WebSocketSessionWrapper.self) async let value = stream.first(where: { _ in true }) // must be after the `async let` so that we finish if connect throws defer { continuation.finish() } // we can't use the async overload because we need to immediately subscribe // to onText/onBinary in the same EventLoop tick that the WebSocket is created. // This is also why we create the SessionWrapper inside the closure. let future = WebSocket.connect(to: url, on: client.eventLoopGroup) { continuation.yield(WebSocketSessionWrapper(webSocket: $0)) } try await future.get() guard let webSocket = await value else { throw Errors.connectFailed } return webSocket } private enum Errors: Error { case connectFailed } var asOpenAPITransport: any ClientTransport { AsyncHTTPClientTransport(configuration: .init(client: client)) } func withEphemeralClient( perform: (any HTTPClientProtocol) async throws -> T ) async throws -> T { let ephemeralClient = Client() let result = await Result { try await perform(ephemeralClient) } try? await ephemeralClient.client.shutdown() return try result.get() } } private final class WebSocketSessionWrapper: WebSocketSession { let webSocket: WebSocket let stream: AsyncStream private let finishStream: @Sendable () -> Void init(webSocket: WebSocket) { self.webSocket = webSocket let (stream, continuation) = AsyncStream.makeStream(bufferingPolicy: .unbounded) self.stream = stream self.finishStream = { continuation.finish() } webSocket.onText { _, text in continuation.yield(.text(text)) } webSocket.onBinary { _, data in continuation.yield(.data(Data(data.readableBytesView))) } } deinit { close() } func close() { finishStream() Task { [webSocket] in try? await webSocket.close() } } func send(_ message: WebSocketMessage) async throws { switch message { case .text(let text): try await webSocket.send(text) case .data(let data): try await webSocket.send(.init(data)) } } func receive() async throws -> WebSocketMessage { await stream.first(where: { _ in true }) ?? .data(Data()) } } private let appleRootPEM = """ -----BEGIN CERTIFICATE----- MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzET MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0 MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBw bGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx FjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg+ +FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1 XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9w tj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IW q6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKM aLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8E BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3 R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAE ggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93 d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNl IG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0 YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBj b25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZp Y2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBc NplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQP y3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7 R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4Fg xhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oP IQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AX UKqK1drk/NAJBzewdXUh -----END CERTIFICATE----- """ #endif