Dockerを用いてSwaggerを試す 〜組み込んでみる〜

ども、iOSエンジニアの徳光です。 さらに前回からの続きになります。 前回は、Swagger-Editorを軽…

ども、iOSエンジニアの徳光です。
さらに前回からの続きになります。
前回は、Swagger-Editorを軽くさわり、go言語でのサーバの雛形を出力してみました。
今回は、クライアント側のソースを出力して、実際に組み込んでみようと思います。

◆Swagger

前提条件として、前回生成させたgo-serverを8080ポートで立ち上げておきます。

$ go run main.go

シミュレータのSafariで「http://localhost:8080/v2/pet/findByStatus?status=sold」として結果が表示されることを確認しておきます。
(前回はAcceptタイプで返却値を振り分けてみましたが、それはやめて必ずJSONのテスト値を返却させた方が確認しやすいかも…)

▼クライアントのコードを生成する

Swagger-Editor上部のメニュー帯、「Generate Client」を展開すると「swift3」を選び、コード生成したzipファイルの内容を確認してみます。
「swift3-client」の中に【SwaggerClient.podspec】と【Cartfile】が含まれているので、CocoaPodsやCarthageで組み込むことができると。
Xcodeを起動して、新規プロジェクト「testSwagger」を作成する。

▽CocoaPodsで組み込む

展開した「swift3-client」を「~/proj/_localPods/」に配置する
ちなみに、「testSwagger」は「~/proj/testSwagger/」になります
「$ pod init」を実行して、【Podfile】を生成する
【Podfile】を以下のように編集する

platform :ios, '10.0'
target 'testSwagger' do
    use_frameworks!
    # Pods for testSwagger
    pod 'SwaggerClient', :path => '~/proj/_localPods/swift3-client'
end

「pod install」して、SwaggerClientを組み込む…とエラーがでました

$ pod install
Analyzing dependencies
Fetching podspec for `SwaggerClient` from `~/proj/_localPods/swift3-client`
[!] The `SwaggerClient` pod failed to validate due to 2 errors:
    - ERROR | attributes: Missing required attribute `homepage`.
    - ERROR | attributes: Missing required attribute `summary`.
    - WARN  | source: The version should be included in the Git tag.
    - WARN  | source: Git SSH URLs will NOT work for people behind firewalls configured to only allow HTTP, therefore HTTPS is preferred.

「~/proj/_localPods/swift3-client」の【SwaggerClient.podspec】を編集し、項目を追加する

Pod::Spec.new do |s|
  s.name = 'SwaggerClient'
  s.ios.deployment_target = '9.0'
  s.osx.deployment_target = '10.11'
  s.tvos.deployment_target = '9.0'
  s.version = '0.0.1'
  s.source = { :git => 'git@github.com:swagger-api/swagger-mustache.git', :tag => 'v1.0.0' }
  s.authors = 'Swagger Codegen'
  s.license = 'Proprietary'
  s.source_files = 'SwaggerClient/Classes/**/*.swift'
  s.dependency 'Alamofire', '~> 4.5.0'
  ##=== 追加 ===
  s.summary          = '.'
  s.homepage         = 'http://example.com/SwaggerClient'
end

あらためて「pod install」して、SwaggerClientを組み込む

$ pod install
Analyzing dependencies
Fetching podspec for `SwaggerClient` from `~/proj/_localPods/swift3-client`
Downloading dependencies
Installing Alamofire (4.5.1)
Installing SwaggerClient (0.0.1)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `testSwagger.xcworkspace` for this project from now on.
Sending stats
Pod installation complete! There is 1 dependency from the Podfile and 2 total pods installed.

「~/proj/_localPods/swift3-client」の【testSwagger.xcworkspace】でプロジェクトを開く
とりあえず、Swaggerで生成したPet型の内容確認のため、descriptionを用意しておきます。

import SwaggerClient
extension Pet {
    var description: String {
        let _id = self.id ?? 0
        let _name = self.name ?? "---"
        let _categoryId = self.category?.id ?? 0
        let _categoryName = self.category?.name ?? "---"
        var bufTags: String = ""
        if let _tags = self.tags {
            for _tag in _tags {
                bufTags += "{\(_tag.id ?? 0): \(_tag.name ?? "---")}"
            }
        }
        return "[\(_id): \(_name)]  <\(_categoryId): \(_categoryName)>  Tags: \(bufTags)"
    }
}

そして、Storyboardに適当にボタンとテキストビューを配置して、ボタンを押したらAPIを叩いて結果を表示するようにしてみます。

ちなみに、Swagger-Editorで生成したクライアント用のソースでの定義、【APIs.swift】ファイルでの「SwaggerClientAPI」の「basePath」を書き換えるというのは美しくない気がするので、組み込んだあとのtestSwagger側だけで上書き定義してAPI呼び出しをしています。
(実際には生成させる時点で「basePath」に指定していると思いますが……まぁ、一時的にテスト用サーバに接続させたい場合などですかね)

    @IBOutlet weak var textVW: UITextView!
    @IBAction func actButton(_ sender: Any) {
        var bufResult: String = ""
        let status: [String] = [PetAPI.Status_findPetsByStatus.sold.rawValue]
        SwaggerClientAPI.basePath = "http://localhost:8080/v2" //サーバの接続先を変更するため
        PetAPI.findPetsByStatus(status: status) { (pets, error) in
            if let _pets = pets as? [Pet] {
                for pet in _pets {
                    bufResult += "\(pet.description)\n"
                }
            }
            self.textVW.text = bufResult
        }
    }

これで、ボタンを押すと「SOLD」状態のペットを検索した結果を表示する準備ができたわけです。

セキュリティの壁を越えろ! 〜ATS〜

まずは、シミュレータのSafariで「http://petstore.swagger.io」を表示させてみて、Swagger-Editorが表示されることを確認しておく。
ついでに、アプリで表示させるのと同じ、EditorのUIでfindByStatusでsoldを指定して結果が表示されることを確認する。
それができていたら、URLとして「http://petstore.swagger.io/v2/pet/findByStatus?status=sold」を指定して表示されてれば事前準備は完了です。
では、Xcodeで「testSwagger」プロジェクトを開き、シミュレータにビルドして動作を確認。
ボタンを押してリクエスト投げると。。。

App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
Cannot start load of Task <F0F2E878-011E-484B-B060-A2B3F99055E1>.<1> since it does not conform to ATS policy
Task <F0F2E878-011E-484B-B060-A2B3F99055E1>.<1> finished with error - code: -1022

はい、お約束のATSで怒られましたっと。
なので、プロジェクトの【Info.plist】に「App Transport Security Settings」を追加して、さらに「Allow Arbitrary Loads」を追加して値を「YES」にしておく。

	<key>NSAppTransportSecurity</key>
	<dict>
		<key>NSAllowsArbitraryLoads</key>
		<true/>
	</dict>

ただしこれだと、大雑把すぎるので、ちゃんとドメイン指定して許可するならば下記。
プロジェクトの【Info.plist】に「App Transport Security Settings」を追加する
さらに「Exception Domains」を追加して、Dictionary型に変更する
「Exception Domains」に、キー「localhost」を追加して、Dictionary型に変更する
「petstore.swagger.io」に、キー「NSTemporaryExceptionAllowsInsecureHTTPLoads」を追加して、Boolean型に変更し、値を「YES」にする

	<key>NSAppTransportSecurity</key>
	<dict>
		<key>NSExceptionDomains</key>
		<dict>
			<key>localhost</key>
			<dict>
				<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
				<true/>
			</dict>
		</dict>
	</dict>

これで、シミュレータで実行した場合に、期待通りに表示されることが確認できるかと思います。

セキュリティの壁を越えろ!! 〜実機〜

シミュレータで無事に動くことが確認できたら、実機で動作させてみることにしましょう。
…って、接続先が「localhost」で良いわけがありませんよね。
お試しサーバはMacで動かしているので、シミュレータならば同じマシンなのでlocalhostで辿れちゃうけれども、iPhoneからは見ることができるわけないのです。
しかし、MacとiPohneを同じネットワークに接続し、Macの「システム環境設定」→「共有」→「コンピュータ名」の所に表示されている「hoge.local」をホスト名として指定することで、アクセスさせることが可能になります。
ってなわけで、実機のSafariで「http://hoge.local:8080」を表示させてみて、「404 page not found」が表示されることを確認してください。
それができたら、「testSwagger」でも「localhost」ではなく「hoge.local」を参照させれば良いことになります。
てなわけで、ソースの「SwaggerClientAPI.basePath」と、ATSの設定の「localohost」を「hoge.local」に変更してシミュレータで動作確認してみます。
それができたら、実機で動作確認してみましょう。
想定どおりの結果が表示されることと思います。

[101: アイン]  <1: 屋外犬>  Tags: {0: 賢い}{1: 可愛い}
[103: 豆助]  <0: 室内犬>  Tags: {1: 可愛い}