TWSNMPのiOS連携アプリ開発のための学習: Web APIにアクセスする
毎朝、猫に3時台に起こされiOSアプリ開発の学習をしています。昨日と今日、iOSアプリからTWSNMPのWeb APIにアクセスするサンプルと作った時に悪戦苦闘した話です。
作りたいサンプルプログラム
TWSNMPのWeb API
にiOSのアプリからアクセスする処理を作ります。基本的にHTTPのGETリクエストを送信して応答を取得できればよいのでGoogleに"Swift HTTP GET”と聞いてみれば多くの人のサンプルコードが見つかります。それらを応用すれば楽勝と思っていました。
サーバー証明書の落とし穴
Playgroundで簡単なサンプル
import UIKit
let url = URL(string: "https://192.168.1.250:8192/api/mapstaus")
let request = URLRequest(url: url!)
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let error = error {
print("error: \(error.localizedDescription) \n")
return
}
if let data = data, let response = response as? HTTPURLResponse {
// HTTPヘッダの取得
print("Content-Type: \(response.allHeaderFields["Content-Type"] ?? "")")
// HTTPステータスコード
print("statusCode: \(response.statusCode)")
print(String(data: data, encoding: String.Encoding.utf8) ?? "")
}
}.resume()
を作って楽勝と思って試してみました。
あれ!
error: The certificate for this server is invalid. You might be connecting to a server that is pretending to be “192.168.1.250” which could put your confidential information at risk.
サーバー証明書が正しくないというエラーでアクセスできません。TWSNMPのWeb APIのサーバー証明書は自己署名証明書(いわいるオレオレ証明書)なので正しくないと言われても仕方ないです。
困った。GO言語だと
var insecureTransport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
var insecureClient = &http.Client{Transport: insecureTransport}
のようにInsecureSkipVerifyというパラメータ一発で解決します。同じように簡単にできるかと思いましたが、そうでもありません。
いろいろ調べた結果、 サーバー証明をチェックする処理をdelegateによって自分で作る必要があることがわかりました。その処理の中でサーバー証明書をチェックしないようにすればエラーはなくなりました。
class AllowsSelfSignedCertificateDelegate: NSObject, URLSessionDelegate {
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let protectionSpace = challenge.protectionSpace
guard protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
let serverTrust = protectionSpace.serverTrust else {
// デフォルトのハンドリングを行う
completionHandler(.performDefaultHandling, nil)
return
}
// 証明書を何でも信じる
completionHandler(.useCredential, URLCredential(trust: serverTrust))
}
}
// 作った証明書チェックの処理を適用する。
let session = URLSession(configuration: .default, delegate: AllowsSelfSignedCertificateDelegate(), delegateQueue: nil)
のような方法で解決しました。
Basic認証
証明書の問題は解決しましたが、TWSNMPのWeb APIは、ユーザー名、パスワードでBasic認証する必要があります。この部分もGoogleに"Swift http basic auth"と聞いてサンプルを見つけました。組み込んだ結果が
let session = URLSession(configuration: .default, delegate: AllowsSelfSignedCertificateDelegate(), delegateQueue: nil)
let url = URL(string: "https://192.168.1.250:8192/api/mapstatus")!
var request = URLRequest(url: url)
// Basci認証
let username = "a"
let password = "a"
let credentialData = "\(username):\(password)".data(using: String.Encoding.utf8)!
let credential = credentialData.base64EncodedString(options: [])
let basicData = "Basic \(credential)"
request.setValue(basicData, forHTTPHeaderField: "Authorization")
let task = session.dataTask(with: request) { data, response, error in
if let error = error {
print("err: \(error.localizedDescription) \n")
return
}
guard let data = data, let response = response as? HTTPURLResponse else {
print("no data or no response")
return
}
if response.statusCode == 200 {
let str = String(data: data, encoding: .utf8)
print(str!)
} else {
print("err: \(response.statusCode)\n")
}
}
task.resume()
です。実行すると
{"High":0,"Low":1,"Warn":4,"Normal":12,"Repair":11,"Unknown":1,"DBSize":12320952320,"DBSizeStr":"12 GB","State":"low"}
応答が取得できました。
パッケージの存在を知る
TWSNMPのWeb APIにアクセスするためのサンプルプログラムを作るためにサーバー証明書やBasic認証の方法をいろいろ調べました。その過程で、SwiftにもGO言語と同じような便利なパッケージがあることを知りました。
(iOSアプリ開発初心者なので勘弁してください。)
HTTPのアクセスは、どうやら
が良いらしいので、さっそくこれを使ってサンプルプログラムを作ってみました。
let configuration = URLSessionConfiguration.af.default
let sesstion = Session(configuration: configuration,
serverTrustManager: ServerTrustManager(allHostsMustBeEvaluated: false,
evaluators: ["192.168.1.250": DisabledTrustEvaluator()]))
func getTWSNMP() {
let user = "a"
let password = "a"
let headers: HTTPHeaders = [.authorization(username: user, password: password)]
sesstion.request("https://192.168.1.250:8192/api/mapstatus",headers: headers)
.responseJSON { response in
debugPrint(response)
}
}
スッキリしています。サーバー証明書をチェックしないことやBasic認証も、これだけで解決しています。
結果だけ書くと簡単に解決したように見えますが、Playgroundでパッケージが使えないとか、Basic認証がマニュアル通りに動作しないとか、いろいろな問題を乗り越えた結果です。ちょっとうれしい。
何故か猫が騒いでいます。室温が22℃は寒いのかもしれません。