iOSアプリ上からBASIC認証がかかったサイトのコンテンツを非同期処理で取得して表示する話

この記事は最終更新日から3年以上が経過しています。

やりたかったこと
・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/ には認証をかけたい場合は

/etc/apache2/conf.d/security
<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を作る

Manager.swift
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を用いて非同期のリクエストを送信する

Manager.swift
 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関数でこの取得結果をいじったりするということですね。

Manager.swift
    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.~~)
を書きます。

Manager.swift
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を宣言するのを忘れないようにしてください。

ViewController.swift
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の内容が描画される

ということを実装することができました。デリゲート最高。
これにて一件落着ということです。


おまけ

Manager.swift
    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最高、ということです。

この記事めちゃくちゃ長くて疲れた

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント
この記事にコメントはありません。
あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした