やりたかったこと
・SwiftからBASIC認証がかかったサイトのコンテンツを取得する。
・それをiOSアプリ上=UILabelのテキストで表示する(※)
※表示に関してはもちろん非同期で表示させたい。(ネットワーク利用してて同期処理をさせるのは明らかにダメだと思う)
Apacheで作られたサーバー上のページにBASIC認証をかける
突然本題から外れてしまっているのですが、ここらへん構築しててちょっとだけ詰まったので自分用も兼ねてメモ書き。(ここは飛ばしてもOKです。)
僕のサイトですが、現在Apache2でWebサーバーが立っています。
そして、もちろんraspberry pi2(OS:raspbian)でそれを動かしています。
で、それを踏まえてApache2で認証かけようと思ったら詰まりました。
主に詰まった点は
・.htaccessファイルがない
です。
Apache2 BASIC認証でググると結構.htaccessファイルがどうのこうのという解説が結構ありまして、俺のApacheにはそんなのないじゃん!!ってなってたというわけです。
ここら辺をちょっと解説します。
・.htaccessファイルがない を解決する
次に.htaccess ファイル ですが、これは「分散設定ファイル」であり、ディレクトリ毎に設定を変更する方法を提供します。
つまり特定のページに対してだけBASIC認証かけたいという場合はこちらをいじればいいということですね。
これがraspbian上では
/etc/apache2/conf.d/security
にあたります。
例えば
http://example.com/index.html には認証をかけたくないけど、
http://example.com/html/ には認証をかけたい場合は
<Directory "/var/www/html/">
AuthType Basic
AuthName "Member Only"
AuthGroupFile /dev/null
AuthUserFile /パスワードファイルが入ってるディレクトリ/.htpasswd
Require valid-user
order deny,allow
</Directory>
というようなものを追加すればいいですね。
.htpasswdの書き方は ( http://promamo.com/?p=2976 )を参考にして下さい。
まとめると、BASIC認証には
/etc/apache2/conf.d/securityに任意のディレクトリに対するアクセス制御文を追加すればいいということですね。
これでOK
ここから本題にうつります。
・SwiftからBASIC認証がかかったサイトのコンテンツを取得する。
といっても実は前回の記事のカメラ画像を取得した時と状況は変わりません。だってApacheでもmjpg-streamerでも認証方法は同じBASIC認証ですからね。
今回は事情によりコンテンツをテキストファイルと定義します。
(次回の記事でこれが活かされる予定です)
というわけで以下のような手順を踏んで取得してゆきます。
今回(Apacheのサイトのコンテンツを取得する)の手順としては
①まずBASIC認証を通すNSURLRequestを作る
②NSURLConnection.sendAsynchronousRequestを用いて非同期のリクエストを送信する
③そのリクエストからコンテンツを取得する。
となります。
mjpg-streamerの時も
①まずBASIC認証を通すNSURLRequestを作る
②リクエストを送信する
③リクエスト結果をUIWebViewにロードする
でしたから、ほとんど手順が変わらないことがわかっていただけるかとおもいます。
以下では、例によって
目標のコンテンツがあるサイトURLをexample.com/html/target.txt
として
それにかかっているBASIC認証を通過するユーザ名とパスワードをそれぞれuserid,passとします。
コンテンツを取得するための処理が書かれている関数をManager.swift
実際のViewを扱っているViewControllerをViewController.swiftとします。
(参考:https://sites.google.com/a/gclue.jp/swift-docs/ni-yinki100-ios/13-http/fei-tong-qihttp)
↑のサイトをものすごく参考にさせてもらいました。ありがとうございます
①まずBASIC認証を通すNSURLRequestを作る
func makeWebRequest() -> NSURLRequest{
var url_with_basic_auth = "http://example.com/html/target.txt"
var url = NSURL(string: url_with_basic_auth)
var req = NSMutableURLRequest(URL: url!)
//Authorizationヘッダーの作成
var username = "userid"
var password = "pass"
var authStr = "\(username):\(password)"
var data = authStr.dataUsingEncoding(NSUTF8StringEncoding)
var authData = data!.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.allZeros)
var authValue = "Basic \(authData)"
//作成したAuthorizationヘッダーの付与
req.setValue(authValue, forHTTPHeaderField: "Authorization")
return req
}
いつものという感じですね。返り値にNSURLRequestを返す関数を作ることによってリクエストを取得するようにします。
②NSURLConnection.sendAsynchronousRequestを用いて非同期のリクエストを送信する
func sendRequest(){
var myRequest : NSURLRequest = makeWebRequest()
NSURLConnection.sendAsynchronousRequest(myRequest, queue: NSOperationQueue.mainQueue(), completionHandler: self.getFile)
}
NSURLConnection.sendAsynchronousRequestの第三引数であるself.getFile
に関しては次で述べます。
先程作ったリクエストを使って非同期でリクエストを送信しています。
③そのリクエストからコンテンツを取得する。
NSURLConnection.sendAsynchronousRequestの第三引数であるself.getFile
には、取得が完了した際に呼ばれる関数ということで、実行結果である(reponse, data, error)が渡されます。
つまりgetFile関数でこの取得結果をいじったりするということですね。
func getFile(res:NSURLResponse?,data:NSData?,error:NSError?){
var myData:NSString = NSString(data: data!, encoding: NSUTF8StringEncoding)!
var valStr = myData as String
println(valStr)
}
これで、
http://example.com/html/target.txt のファイルの中身を標準出力することができました。
UILabelのテキストを取得してきたテキストにする
もちろんgetFile関数をどうのこうのします。
UIなどの画面の更新はメインスレッドで行われています。先程のsendAsynchronousRequestですが、結果は実はメインスレッドで扱われるようなので、そんなに大変ではありません。
実は、この章を書く瞬間までgetFile関数がサブスレッドで動いていると思っていたのでちょっと前置きがおかしなことになっているかもです。ご了承ください。
結論からいうとProtocol(デリゲート)をつかいます。使わなくてもできるけど、コードがわけわかんないことになるので使ったほうが絶対にいいということです。
デリゲートが使えると何が良いかと言いますと、
・関数が呼び出されるタイミングだけを定義できて、処理自体は他のクラスで書くことができる。
・ManagerクラスにLabelがあるクラスのインスタンスを持ってこなくていい
の2点だと僕は思っています。
まずManager.swiftに
・デリゲートの宣言(protpcol~~ のところ)
・どのタイミングで呼ばれるか(getFile関数の中にあるdelegate.~~)
を書きます。
import UIKit
protocol ManagerDelegate {
func changeValue(mes : String)
}
class Manager {
var delegate : ManagerDelegate!
func makeWebRequest() -> NSURLRequest{
//↑に書いてあるので省略
}
func sendRequest(){
//↑に書いてあるので省略
}
func getFile(res:NSURLResponse?,data:NSData?,error:NSError?){
var myData:NSString = NSString(data: data!, encoding: NSUTF8StringEncoding)!
var valStr = myData as String
delegate.changeValue(valStr)
}
}
次にViewController.swiftでは
・Managerのデリゲートにアクセスするためにインスタンス化
・delegate=selfとすることで、処理をこのクラスに委任する
・実際の処理を書く
ということをしています。
class ViewController: UIViewController,ManagerDelegate
のようにdelegateを宣言するのを忘れないようにしてください。
import UIKit
class ViewController: UIViewController,ManagerDelegate {
@IBOutlet weak var tLabel: UILabel!
var m = Manager()
override func viewDidLoad() {
super.viewDidLoad()
m.delegate = self
m.sendRequest()
}
func changeValue(mes: String) {
tLabel.text = mes
}
}
こうすると、
Managerクラスの非同期のコンテンツ取得処理が完了し、getFile関数の処理が行われるタイミング
で
ViewControllerクラスで、ラベルに対してそのtarget.txtの内容が描画される
ということを実装することができました。デリゲート最高。
これにて一件落着ということです。
おまけ
func getFile(res:NSURLResponse?,data:NSData?,error:NSError?){
var myData:NSString = NSString(data: data!, encoding: NSUTF8StringEncoding)!
var valStr = myData as String
delegate.changeValue(valStr)
}
の
delegate.changeValue(valStr)
をViewControllerのインスタンスを使って、
viewController.changeValue(valStr)としても動くと思うんですけど、インスタンスをManagerに持ってくるのアレだし、修正があったらいろいろ手間増えると思うしで明らかにコード汚くなるしで絶対delegate使ったほうがいい。delegate最高、ということです。
この記事めちゃくちゃ長くて疲れた