概要
SWIFTを使って、Alamofireなどのライブラリを使わないAPI通信を構築しています。
Swiftで始める仮想通貨システムトレード入門
こちらのサイトを参考にAPI全体の構成はBuildエラーが出ないところまで進めました。
そこで、以下のエラーが出て解決策が見出せずにいるので教えて頂きたく投稿しました。
###エラー内容
Build Succeeded->Runでsimulatorを立ち上げ、リクエストを送信するトリガーになる
ボタンを押すと、デバッグコンソールに以下のように出力されます。
通信先からのResponseをString表示しています。
Optional("{\"error\":{\"message\":\"Authorization Required\",\"name\":\"HTTPError\"}}")
認証が必要とのエラー内容だと認識したので、デバッグコンソールでブレイクポイント作りながら
リクエスト内容を見ると、プログラム上認証作成するフローはしっかり動いており、リクエストURLは
できているように見えます。
以下はブレイクポイントを作りデバッグコンソールにて確認したurlRequestの値です。
アクセス先 : https://testnet.bitmex.com/api/
Printing description of urlRequest: ▿ https:/(アクセス先).com/api/v1/order?orderType=Limit&symbol=XBTUSD&orderQty=1&price=590 ▿ url : Optional<URL> ▿ some : https:/(アクセス先).com/api/v1/order?orderType=Limit&symbol=XBTUSD&orderQty=1&price=590 - _url : https:/(アクセス先).com/api/v1/order?orderType=Limit&symbol=XBTUSD&orderQty=1&price=590 - cachePolicy : 0 - timeoutInterval : 60.0 - mainDocumentURL : nil - networkServiceType : __C.NSURLRequestNetworkServiceType - allowsCellularAccess : true ▿ httpMethod : Optional<String> - some : "POST" ▿ allHTTPHeaderFields : Optional<Dictionary<String, String>> ▿ some : 4 elements ▿ 0 : 2 elements - key : "Content-Type" - value : "application/json" ▿ 1 : 2 elements - key : "ACCESS-SIGN" - value : "b2dce06bcb67b6db3418b5018b45cf9f4442986ed05b76fc91bfb4303788332f" ▿ 2 : 2 elements - key : "ACCESS-TIMESTAMP" - value : "1575546" ▿ 3 : 2 elements - key : "ACCESS-KEY" - value : "アクセスキーが出力されています" - httpBody : nil - httpBodyStream : nil - httpShouldHandleCookies : true - httpShouldUsePipelining : false
###認証周りコード
プログラム実行の流れ
0. ユーザーアクション(ボタン押下)
0. Sessionクラス呼び出し
0. buildURLRequestメソッド(RequestAPI内のextension Requestable)実行
0. buildURLRequest内でisAuthorizedRequestがtrueなら認証ヘッダを作成開始(コンソールでtrueであることは確認済)
0. makeAccessSignWithにてHMAC SHA256認証のhashを作成
0. buildURLRequestに戻り、全部を詰め込んでurlRequestとして完成
0. Sessionクラスに戻り、URLSessionを使ってtask.resume()にて実行
RequestAPI
1enum Result<T, E: Error> { 2 case success(T) 3 case failure(E) 4} 5enum ResponseError: Error { 6 case unexpectedResponse 7} 8//プロトコル定義 9protocol Requestable { 10 (省略) 11} 12extension Requestable { 13 func buildURLRequest() -> URLRequest { 14 let url = Self.baseURL.appendingPathComponent(Self.path) 15 var urlRequest = URLRequest(url: url) 16 var header: [String: String] = Self.headerField 17 urlRequest.httpMethod = Self.httpMethod 18 if Self.isAuthorizedRequest { 19 header["ACCESS-KEY"] = "アクセスキーを入力してあります" 20 let now = (Date().timeIntervalSince1970 / 1000) + 60 21 let timeStamp = String(Int(now.rounded()) 22 header["ACCESS-TIMESTAMP"] = timeStamp 23 header["ACCESS-SIGN"] = makeAccessSignWith(accessKey: "アクセスキーを入力してあります", 24 timeStamp: timeStamp, 25 method: Self.httpMethod, 26 path: Self.path, 27 queryParams: Self.queryParameters, 28 body: Self.httpBody) 29 header["Content-Type"] = "application/json" 30 } 31 header.forEach { key, value in 32 urlRequest.addValue(value, forHTTPHeaderField: key) 33 } 34 if let body = Self.httpBody { 35 urlRequest.httpBody = body 36 } 37 guard var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true) else { 38 return urlRequest 39 } 40 urlComponents.query = Self.queryParameters 41 .map { "($0.key)=($0.value)" } 42 .joined(separator: "&") 43 44 urlRequest.url = urlComponents.url 45 46 return urlRequest 47 } 48}
order
1final class Session { 2 static let shared = Session() 3 private init() {} 4 func send<T: Requestable>(_ request: T, closure: @escaping (Result<T.Response, ResponseError>) -> Void) { 5 let urlRequest = request.buildURLRequest() 6 let task = URLSession.shared.dataTask(with: urlRequest) { (data, rawResponse, error) in 7 // If an error is occurred. 8 if error != nil { 9 closure(.failure(.unexpectedResponse)) 10 return 11 } 12 // If the data is empty. 13 guard let data = data else { 14 closure(.failure(.unexpectedResponse)) 15 return 16 } 17 // Decode the value. 18 do { 19 let decoder = JSONDecoder() 20 let result = try decoder.decode(T.Response.self, from: data) 21 closure(.success(result)) 22 } catch { 23 let errorResult = String(data: data, encoding: .utf8) //デバッグ用String化して中身見る 24 print(errorResult as Any) 25 } 26 } 27 task.resume() 28 } 29}
makeAccessSign
1 2private func makeAccessSignWith(accessKey: String, timeStamp: String, method: String, path: String, queryParams: [String: Any], body: Data?) -> String? { 3 4 let key = "シークレットキーを入力しています" 5 var bytes: [UInt8] = [] 6 bytes += timeStamp.bytes 7 bytes += method.bytes 8 bytes += path.bytes 9 func makeSign (queryParams: [String: Any]) -> String { 10 let requestBody = "?" + queryParams 11 .map { "($0.key)=($0.value)" } 12 .joined(separator: "&") 13 return requestBody 14 } 15 16 if !queryParams.isEmpty { 17 let str = makeSign(queryParams: ViewController.PostNewOrderRequest.queryParameters) 18 let data: Data? = str.data(using: .utf8) 19 let hexStr:String = data!.map {String(format: "%.2hhx", $0)}.joined() 20 var bytes = [UInt8]() 21 for char in hexStr.utf8{ 22 bytes += [char] 23 } 24 } 25 26 if body?.isEmpty == false { 27 if let bodyParameter = body, 28 let bodyString = String(data: bodyParameter, encoding: .utf8) { 29 bytes += bodyString.utf8 30 } 31 } 32 let signedString = try! HMAC(key: key, variant: .sha256).authenticate(bytes) 33 return signedString.toHexString() 34}
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/12/05 19:20