見出し画像

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℃は寒いのかもしれません。

開発のための諸経費(機材、Appleの開発者、サーバー運用)に利用します。 ソフトウェアのマニュアルをnoteの記事で提供しています。 サポートによりnoteの運営にも貢献できるのでよろしくお願います。