Swift-NIO で http クライアントの書き方。
以前失敗してたけど、リベンジしたのでその記録。
以前失敗した記事
Package.swift
は以下の通り
// swift-tools-version:4.0 import PackageDescription let package = Package( name: "HTTP-CLIENT", products: [ .executable(name: "HTTP-CLIENT", targets: ["HTTP-CLIENT"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-nio.git", from: "1.7.2") ,.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "1.0.0") ], targets: [ .target( name: "HTTP-CLIENT", dependencies: ["NIO", "NIOHTTP1", "NIOOpenSSL"]), .testTarget( name: "HTTP-CLIENTTests", dependencies: ["HTTP-CLIENT"]), ] )
ハンドラーはごくごく簡単なもの
import NIO import NIOHTTP1 import NIOOpenSSL private final class HTTPResponseHandler: ChannelInboundHandler { let promise: EventLoopPromise<Void> init(_ promise: EventLoopPromise<Void>) { self.promise = promise } typealias InboundIn = HTTPClientResponsePart func channelRead(ctx: ChannelHandlerContext, data: NIOAny) { let httpResponsePart = unwrapInboundIn(data) switch httpResponsePart { case .head(let httpResponseHeader): print("\(httpResponseHeader.version) \(httpResponseHeader.status.code) \(httpResponseHeader.status.reasonPhrase)") for (name, value) in httpResponseHeader.headers { print("\(name): \(value)") } case .body(var byteBuffer): if let responseBody = byteBuffer.readString(length: byteBuffer.readableBytes) { print(responseBody) } case .end(_): break } _ = ctx.channel.close() promise.succeed(result: ()) } func errorCaught(ctx: ChannelHandlerContext, error: Error) { print("Error: ", error) _ = ctx.channel.close() promise.succeed(result: ()) } }
EventLoopGroup
と同期のために EventLoopPromise
を作る(main.swift
)
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) let promise: EventLoopPromise<Void> = eventLoopGroup.next().newPromise() defer { try! promise.futureResult.wait() try! eventLoopGroup.syncShutdownGracefully() }
次に OpenSSLClientHandler
を作る(main.swift
)
前回やったときは、 OpenSSLClientHandler
のイニシャライザーに serverHostname
を指定してないためにエラーが発生したようだった。
let tlsConfiguration = TLSConfiguration.forClient() let sslContext = try! SSLContext(configuration: tlsConfiguration) let openSslHandler = try! OpenSSLClientHandler(context: sslContext, serverHostname: "httpbin.org")
Bootstrap
を組み立てる。(main.swift
)
let bootstrap = ClientBootstrap(group: eventLoopGroup) .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) .channelInitializer { channel in _ = channel.pipeline.add(handler: openSslHandler) _ = channel.pipeline.addHTTPClientHandlers() return channel.pipeline.add(handler: HTTPResponseHandler(promise)) }
接続後にリクエストを書き込むための関数を作る。(main.swift
)
func sendRequest(_ channel: Channel) { var request = HTTPRequestHead(version: HTTPVersion(major: 1, minor: 1), method: HTTPMethod.GET, uri: "https://httpbin.org/get?query=param") request.headers = HTTPHeaders([ ("Host", "httpbin.org"), ("User-Agent", "swift-nio"), ("Accept", "application/json") ]) _ = channel.write(HTTPClientRequestPart.head(request)) _ = channel.writeAndFlush(HTTPClientRequestPart.end(nil)) }
最後にサーバーにつないで、リクエストを送信します(main.swift
)
bootstrap.connect(host: "httpbin.org", port: 443) .whenSuccess { sendRequest($0) }
すると、このような感じで表示されるはず…
HTTP/1.1 200 OK Connection: keep-alive Server: gunicorn/19.9.0 Date: Sat, 01 Sep 2018 14:18:51 GMT Content-Type: application/json Content-Length: 260 Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true Via: 1.1 vegur { "args": { "query": "param" }, "headers": { "Accept": "application/json", "Connection": "close", "Host": "httpbin.org", "User-Agent": "swift-nio" }, "origin": "192.168.1.2", "url": "https://httpbin.org/get?query=param" }