2017年2月28日火曜日

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.

概要

iOS 開発中 http に通信するシーンが何度もありその度にタイトルのエラーに悩まされては設定していたので忘れないようにメモしておきます

環境

  • Xcode 7.3.1
  • iOS 9.3.1

Xcode の Info.plist に NSAppTransportSecurity の項目を追加する

Info.plist を開いて項目を設定していきます

1 - Information Property List の横のプラスボタンをクリックする

set_ats_in_infoplist_1.png

2 - 新しく追加された行に NSAppTransportSecurity を入力します

set_ats_in_infoplist_2.png

3 - 追加した NSAppTransportSecurity 配下に更に NSAllowsArbitraryLoads を追加します

set_ats_in_infoplist_3.png

まず追加した NSAppTransportSecurity という項目は勝手に「App Transport Security Settings」という名前に変わります
その左の三角ボタンを下向きにします
次にその状態で「App Transport Security Settings」の右にあるプラスボタンをクリックします
最後に入れ子として追加された項目に NSAllowsArbitraryLoads を入力します

入れ子として項目を追加した段階で「App Transport Security Settings」の Type は自動的に Dictionary に変わります

4 - Type を「Boolean」Value を「YES」に設定します

set_ats_in_infoplist_4.png

追加した NSAllowsArbitraryLoads という名前はまた勝手に「Allow Arbitrary Loads」という名前に変わります
気にせずその横にある Type と Value を変更すれば OK です

最後に

本当は http としてアクセスするドメインごとに管理したほうがセキュリティ的にいいのですが、どうしても不特定多数のドメインにアクセスするアプリだとこうせざるを得ません

これでもう忘れることはないと思います、たぶん

swagger ui のスタイルを変更してみた

概要

swagger ui にスタイルを当ててみました
使用するアプリケーションはこれまでに作成した TODO アプリを使用します

環境

  • CentOS 6.7 64bit
  • go-swagger dev
  • golang 1.6
  • swagger ui v2.2.10

事前準備

Apache Httpd をインストールする

  • yum -y install httpd
  • service httpd start

TODO アプリの CORS を有効にする

  • go get github.com/rs/cors
  • cd $GOPATH/src/github.com/hawksnowlog/todo-list
  • emacs restapi/configure_todo_list.go
import "github.com/rs/cors"
func setupGlobalMiddleware(handler http.Handler) http.Handler {
        handleCORS := cors.Default().Handler
        return handleCORS(handler)
}

修正できたらビルドしてアプリを起動します

  • go fmt restapi/configure_todo_list.go && go install ./cmd/todo-list-server/
  • todo-list-server –host=0.0.0.0 –port=18080

swagger ui のインストールと起動

swagger ui の dist 配下に index.html やら必要な静的ファイルがあるのでこれを Apache の DocumentRoot 配下に配置するだけです
配置できたら http://xxx.xxx.xxx.xxx/dist/ にアクセスしてみましょう

すると、swagger ui が表示されるので、ヘッダにあるフィールドに http://xxx.xxx.xxx.xxx:18080/swagger.json を入力し「Explore」を選択します
でとりあえず自分のアプリでデフォルトの swagger ui が表示されると思います
swagger_ui1.png

スタイルの設定

今回はすでに公開されているスタイルを使ってみたいと思います

<link href="css/theme-feeling-blue.css" media="screen" rel="stylesheet" id="sut" type="text/css">

を head タグの <!-- Some basic translations --> の直下にコピペします
そして再度 http://xxx.xxx.xxx.xxx/dist/ にアクセスするとスタイルが変わっていると思います

最後に

swagger ui でスタイルを変更する方法を紹介しました
公式の swagger ui は go-swagger で表示させた ui のスタイルとは全く違うデザインになっていました

今回はすでに公開されているスタイルを使いましたが、直接 css や html を変更しても問題ないと思います

参考サイト

2017年2月27日月曜日

go-swagger でリクエスト情報をロギングしてみた

概要

どんな Web アプリでもリクエストされた情報はロギングしたいと思います
今回は go-swagger でリクエスト情報をロギングしてみたいと思います
ロギングする情報はリクエストメソッド、パス、ボディになります
使用するアプリケーションはこれまでに作成した TODO アプリを使用します

環境

  • CentOS 6.7 64bit
  • go-swagger dev
  • golang 1.6
  • swagger ui v2.2.10

ソース修正

  • cd $GOPATH/src/github.com/hawksnowlog/todo-list
  • vim restapi/configure_todo_list.go

import の追加

import (
        "bytes"
        "io/ioutil"
        "log"
)

既存の import に追記してください

ロギング用ハンドラメソッドの追加

func addLogging(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                buf, _ := ioutil.ReadAll(r.Body)
                rdr1 := ioutil.NopCloser(bytes.NewBuffer(buf))
                rdr2 := ioutil.NopCloser(bytes.NewBuffer(buf))
                r.Body = rdr2
                bufbody := new(bytes.Buffer)
                bufbody.ReadFrom(rdr1)
                body := bufbody.String()
                log.Println("received request:", r.Method, r.URL, body)
                next.ServeHTTP(w, r)
        })
}

ここでポイントですが、r.Body の情報を 2 つのバッファに分割しています
これは r.Body をそのまま ReadFrom にかけてしまうと r.Body の情報が失われてしまい、この後の go-swagger 側の処理で body がないと言われエラーになってしまうからです
なので、一度分割して使っていない方を再度 r.Body に設定しています

ロギング処理のコール

func setupGlobalMiddleware(handler http.Handler) http.Handler {
        handleCORS := cors.Default().Handler
        return handleCORS(addLogging(handler))
}

swagger ui に対応するために CORS の処理を入れたので更にその処理にロギング処理を追加します
こんな感じで go-swagger はハンドラを何個も挟むことで、別の処理を追加することができます

動作確認

  • cd $GOPATH/src/github.com/hawksnowlog/todo-list
  • go fmt restapi/configure_todo_list.go
  • go install ./cmd/todo-list-server/
  • todo-list-server --host=0.0.0.0 --port=18080

でアプリを起動して

  • curl -XPOST -H "Content-Type: application/io.goswagger.examples.todo-list.v1+json" "http://127.0.0.1:18080/v1/" -d '{"description":"test2", "completed":true}'

でアクセスすると

2017/02/23 14:57:10 received request: POST /v1/ {"description":"test2", "completed":true}

こんな感じのログが出力されると思います
ログファイルに出力したい場合はコード内でファイルに出力するようにしても良いですし todo-list-server --host=0.0.0.0 --port=18080 >> log 2>&1 こんな感じで出力をリダイレクトしても良いと思います

参考サイト

UITableViewCell 内の UILabel のテキストを改行する方法

概要

UITableViewCell 内にある UILabel を自動で改行する際にちょっとハマったの紹介します
UI は Storyboard で作成し IBOutlet を使ってコード連携している環境を想定しています

環境

  • Xcode 7.3.1
  • iOS 9.3.1
  • Swift 2.2

状況

Storyboard 上の構成は以下のような感じです
uilabel_storyboard.png

この状況で普通に UILabel を改行させようとしたら普通は Storyboard の Lines を「0」にすると思います
こんな感じ
uilabel_configration_lines.png

自分はこの設定でうまく改行できない状況が発生しました

対応方法

原因は UITableViewCell のテキストの設定方法に間違いがあっため改行できない状況が発生していました
UILabel のテキストの設定はコード側で行っています

func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) {
    let item = self.items[indexPath.row] as MWFeedItem
    cell.textLabel?.text = item.title
}

これだと UITableViewCell に設定できるテキストに直接テキストを設定してしまうことになり、セル内にある UILabel にテキストを設定していないことになります
なので以下のように書き換えました

func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) {
    let item = self.items[indexPath.row] as MWFeedItem
    let label = cell.viewWithTag(2) as! UILabel
    label.text = item.title
}

cell というオブジェクトから viewWithTag で Storyboard 上で割り当てた UILabel のタグ情報を元に UILabel のオブジェクトを取得して、そのオブジェクトのテキストに対して設定することで無事改行させることができました

最後に

よくよく考えれば当たり前のミスをしていました
本来設定するべきオブジェクトに設定しないで全然別のオブジェクトに設定していたので、、、

当初は Storyboard 上の Lines の設定がおかしいと思い 0 以外の値にしたり、UILabel の高さが低いから改行されないで表示されているのではといろいろと疑いました
が、結局は Swift のコードが側が悪いということに気が付きました

2017年2月26日日曜日

go-swagger + redismq を試してみた

概要

go-swagger + redismq を試してみました
イメージとしてはある API をコールしたらバックエンドの redis のキューに値を入れる感じです
go-swagger のアプリケーションは前回 までに作成している TODO アプリを使っています

環境

  • CentOS 6.7 64bit
  • go-swagger dev
  • golang 1.8
  • redis-server 3.0.2

go 1.8 のインストール

go version go1.8 linux/amd64

インストール

  • go get “github.com/adjust/redismq”

コーディング

  • cd $GOPATH/src/github.com/hawksnowlog/todo-list
  • vim restapi/configure_todo_list.go
import "github.com/adjust/redismq"
var testQueue = redismq.CreateQueue("localhost", "6379", "", 9, "clicks")
api.TodosAddOneHandler = todos.AddOneHandlerFunc(func(params todos.AddOneParams) middleware.Responder {
        fmt.Println("TodosAddOneHandler")
        if err := addItem(params.Body); err != nil {
                return todos.NewAddOneDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
        }
        testQueue.Put("testpayload") // -> ここを追記
        return todos.NewAddOneCreated().WithPayload(params.Body)
})

という感じです
やっている処理はヒジョに簡単です

ビルド

  • go fmt restapi/configure_todo_list.go
  • go install ./cmd/todo-list-server/
  • todo-list-server –host=0.0.0.0 –port=18080

動作確認

まずはリクエストを送ります

  • curl -XPOST -H "Content-Type: application/io.goswagger.examples.todo-list.v1+json" "http://127.0.0.1:18080/v1/" -d '{"description":"test", "completed":false}'

今回は redis-cli で確認します
DB は 9 を選択しているので SELECT コマンドで切り替えてから操作します
redis-cli するときに -n 9 でも OK です

  • redis-cli
  • redis> SELECT 9
  • redis> keys *
1) "redismq::clicks::failed::size::1487755650"
2) "redismq::clicks"
3) "redismq::clicks::size::1487755650"
4) "redismq::clicks::rate::1487755084"

で、こんな key が入っていれば OK です
それぞれタイプは上から string, list, string, string なのでデータにアクセスする場合は

  • redis> GET redismq::clicks::failed::size::1487755650
  • redis> LRANGE redismq::clicks 0 -1 LRANGE redismq::clicks 0 -1
  • redis> GET redismq::clicks::size::1487755650
  • redis> GET redismq::clicks::rate::1487755084

になります
実際に送信したペイロード情報は redismq::clicks に入っています

Consumer を作成してみる

  • vim consumer.go
package main

import (
        "fmt"
        "github.com/adjust/redismq"
)

func main() {
        testQueue := redismq.CreateQueue("localhost", "6379", "", 9, "clicks")
        consumer, err := testQueue.AddConsumer("testconsumer")
        if err != nil {
                panic(err)
        }
        for {
                p, err := consumer.Get()
                if err != nil {
                        fmt.Println(err)
                        continue
                }
                //fmt.Println(p.CreatedAt)
                fmt.Println(p)
                err = p.Ack()
                if err != nil {
                        fmt.Println(err)
                }
        }
}
  • go run consumer.go

で待ち状態になるので、これで curl を実行してみると値が表示されるのが確認できると思います

最後に

go-swagger で redismq というライブラリを試してみました
所謂 Message Queue なので Producker と Consumer が登場します
また、pubsub モデルっぽいですが内部では redis の PUBLISH と SUBSCRIBE を使っているわけではないです
https://redis.io/topics/pubsub

内部では紹介したとおり list を使っていてかつ list にあるメタ情報を別の key で管理している感じです
list には ttl がないですが各 key には ttl があるので勝手に消えますが、ttl が過ぎるまで key が大量に増えるのは少し嫌かもしれません
デフォルトだと 7200 秒 (2時間) で設定されているようです

参考サイト

Chrome に Google Cast エクステンションをインストールしてみた

概要

Chrome のブラウザ情報を ChromeCast に連携できるエクステンション「Google Cast」をインストールしてみました
インストール後実際にテレビにキャストして動作確認まで行いました

環境

  • Mac OS X 10.11.5
  • Chrome 50.0.2661.102

Google Cast をインストール

Chrome に Google Cast をインストールします
Chrome を開いてダウンロードサイトにアクセスします
google_cast_install.png
「CHROME に追加」を選択します

google_cast_install_cofirm.png
確認画面が表示されるので「拡張機能を追加」を選択します

google_cast_tool_bat_notification.png
ツールバーに Google Cast のアイコンが追加されるのを確認します

google_cast_agreement.png
利用状況の送信に協力する場合はチェックして OK してください
どちらでも OK です

これでインストールは完了です

Google Cast の設定

Chrome Cast の電源がすでに ON になっており、かつ Chrome Cast と同じ LAN 上にいる場合は特に設定しなくてもすでに Chrome Cast が登録されていると思います
google_cast_added.png

追加されていない場合は以下を試してみてください

1 - Chrome Cast の電源が ON になっていることを確認する

google_cast_power_on.jpg

ランプがちゃんと白になっていることを確認してください
テレビの電源が入っていないと白にならないことがあります

2 - キャストを探す

オプション -> 新しいキャスト デバイスをセットアップ

でキャストを探すことができます
google_cast_searching_cast.png

これで新規のキャストを探して追加してみてください
見つからない大抵の場合は違うネットワークにいるか Chrome Cast の電源が ON になっていません

動作確認

キャストが追加できたら動作確認してみましょう
ツールバーから「このタブのキャスト先…」で追加したキャストを選択しましょう

Chrome Cast にこんな感じでキャストされれば OK です
google_cast_result.jpg

Chrome にも同じ情報が表示されているのでそっちで操作すると Chrome Cast 側にも情報が反映されます
もちろん動画ではなく Google の検索結果やサイトの閲覧もキャストすることができます
その場合は現在キャストしているタブのキャストを停止して別のタブに移動してそのタブをキャストしてください

最後に

おそらくブラウザの情報をキャストできるのは Chrome だけだと思います
Firefox や IE もアドオンを作ればできると思いますが探した限りではなかったような気がします
スマホのアプリでは結構対応しているアプリがあるので、そっちを使うのも手だと思います

ブラウザのタグをそのままキャストできるので普段ブラウザで作業する機会が多い人は HDMI 等のケーブルがなくてもモニタが使えるのは嬉しい気がします
ただ、1 タブ分しかキャストできないのでそこが辛い部分かもしれません

P.S 20160604
実際に使っていて気づいたのですが、使っていると CPU 使用率が跳ね上がって PC が唸りを上げ始めました
長時間の利用は避けたほうがいいかもしれません

2017年2月25日土曜日

Google Custom Search API を使って画像検索をしてみた

概要

Google の画像検索 API が 2011/5 に使えなくなってから画像を検索をする場合は Google Custom Search API を使うことになりました
今回は Google Custom Search API を使った画像検索の方法を紹介します
クライアントは curl を使って直接 API をコールしているのでプログラムから使いたい場合は各言語の HTTP クライアントなどを使ってください

環境

  • Mac OS X 10.11.5
  • Google Custom Search API 2016/06/05 時点
  • Google CSE 2016/06/05 時点

Google Custom Search API の有効化

https://console.developers.google.com/iam-admin/projects
にアクセスして Google Custom Search API を有効化します

try_gsa_site_access.png
「プロジェクトを作成」を選択して新規でプロジェクトを作成しましょう

try_gsa_input_project_name.png
まずプロジェクト名を入力します
ここは何でも OK です

try_gsa_select_api.png
プロジェクトを作成すると API の一覧画面になるのでここから「Custom Search API」を選択しましょう

try_gsa_enable_api.png
「有効にする」ボタンがあるので選択し API を使えるようにします

try_gsa_setting_auth.png
すると認証情報を作成する旨の警告が出るのでそのまま「認証情報に進む」を選択して認証情報を作成しましょう

try_gsa_setting_auth_type.png
まず認証情報の種類を設定します
使用する API は「Custom Search API」を選択します
API を呼び出す場所は何でも OK ですが、とりあえず「ウェブサーバー (node.js, Tomcat など)」を選択しましょう
選択したら「必要な認証情報」を選択して次に進みます

try_gsa_setting_auth_name.png
作成する認証情報の名前を設定します
今回は「test-server-key」という名前にします
「リクエストを受け入れるサーバー IP アドレス (省略可)」は何も入力しないで OK です
問題なければ「API キーを作成する」を選択して API キーを作成しましょう

try_gsa_created_key.png
API キーが作成できたことを確認したら完了しましょう
認証情報の一覧画面に移動します

これで Google Custom Search API の有効化は完了です
次に Google Custom Search Engine (CSE) の作成をします

Google Custom Search Engine の作成

Google CSE では API を叩くために必要なパラメータである「検索エンジン ID」の取得を行います

https://cse.google.com/cse/all
にアクセスして Google CSE を作成します

try_gsa_add_cse.png
検索エンジンの一覧画面から「Add」で新規に検索エンジンを作成します

try_gsa_input_engine_info.png
新規で検索エンジンを作成する画面になるので必要な情報を入力します

  • 検索するサイト・・・www.google.co.jp/*
  • 言語・・・日本語
  • 検索エンジンの名前・・・test-engine

という感じで入力しましょう
最後の検索エンジンの名前は何でも OK です
入力に問題がなければ「作成」を選択します

try_gsa_edit_engine.png
作成が完了したら早速編集します
「コントロールパネル」を選択しましょう

try_gsa_get_engine_id.png
まず肝心の「検索エンジン ID」を取得します
画面中段くらいにある詳細の「検索エンジン ID」ボタンをクリックしましょう
ポップアップが出て検索エンジン ID が表示されるので、これを使いましょう

try_gsa_enable_image_search.png
次に画像検索を有効にします
「画像検索」の項目のトグルボタンで設定を有効にします
また、「検索するサイト」という項目が少し下にあるのでここの項目を「追加したサイトを重視して、ウェブ全体を検索する」に変更しましょう
上の画像では「音声入力」もオンになっていますが、今回は音声入力は使わないのでオフにしても OK です

try_gsa_test_cse.png
一応この状態で CSE は動作するはずです
念のため動作確認したい場合は右側にあるテスト用の検索窓を使うと設定した CSE がちゃんと動作しているか確認することができます

動作確認

ここまでで

  • API キー・・・Google Custom Search API から取得
  • 検索エンジン ID・・・ Google CSE から取得

することができました
これらの情報を使って実際に画像検索 API をコールしてみます

今回は curl 使ってコールします
コールする URL は以下の通りです

https://www.googleapis.com/customsearch/v1?key=[API キー]&cx=[検索エンジン ID]&searchType=image&q=hawksnow-log

という感じになります
API キーと検索エンジン ID は取得したものをそれぞれ入力してください
「searchType」は画像検索なので「image」を設定します
「q」には好きな検索クエリを指定してください

これを curl でコールすると以下のような Json で結果が返ってくると思います
( 実際の結果は、kind, url, queries, nextPage, context, searchInformation などの情報も含まれていますが、結果が長いので一部を記載しています )

 "items": [
  {
   "kind": "customsearch#result",
   "title": "hawksnowlog (@hawksnowlog) | Twitter",
   "htmlTitle": "\u003cb\u003ehawksnowlog\u003c/b\u003e (@\u003cb\u003ehawksnowlog\u003c/b\u003e) | Twitter",
   "link": "https://pbs.twimg.com/profile_images/712848447569661952/ayfI9-77.jpg",
   "displayLink": "twitter.com",
   "snippet": "hawksnowlog (@hawksnowlog) |",
   "htmlSnippet": "\u003cb\u003ehawksnowlog\u003c/b\u003e (@\u003cb\u003ehawksnowlog\u003c/b\u003e) |",
   "mime": "image/jpeg",
   "image": {
    "contextLink": "https://twitter.com/hawksnowlog",
    "height": 256,
    "width": 256,
    "byteSize": 24875,
    "thumbnailLink": "https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcQ3-Q1N6LqmggIC_Hejxcm4PHfIFtNJP7kGvQT1APE2AiT53ptBp-FZ0A",
    "thumbnailHeight": 111,
    "thumbnailWidth": 111
   }
  }

という感じです
実際に指定できるパラメータは他にもたくさんあるので API エクスプローラで指定できるパラメータを見ながらパラメータを調整するといいと思います

例えば「num」というパラメータは一回の検索結果で取得できる数を指定できるパラメータですが、これを 1 とすると先頭の一件のみを取得することができるようになります
( ちなみに num はデフォルトで 10 で 10 以上は指定できないようです )

リクエスト数の確認

おまけですが API のリクエスト数は Google Custom Search API のダッシュボードから確認することができます

try_gsa_check_api_count.png

有効な API -> Custom Search API -> 使用量

で確認することができます
リアルタイムにデータは更新されるので、リクエスト数の限界のエラーなどが出る場合はここで確認するといいと思います

最後に

紹介は以上です

実はこの Google Custom Search API は有料のサービスです
執筆時点での公式を見ると

For CSE users, the API provides 100 search queries per day for free

とあり 1 日 100 request しか出来ないようです
なので、大量にページネイトする処理や大量に検索リクエストをするような処理があるとすぐに使えなくなってしまうと思います

無料の範囲で使う場合はキャッシュなどを使ってあまりリクエストさせないような仕組みを検討する必要がありそうです

P.S 20160607

Google Custom Search API と Google 本家で検索した結果は必ずしも一緒になることはないらしいので、その点は注意が必要です
https://support.google.com/customsearch/answer/4513751?hl=ja&ref_topic=4513742

go-swagger で swagger ui を使ってみた

概要

前回 go-swagger を使って単純な TODO アプリを作ってみました
今回は swagger の特徴の一つである swagger ui を試してみました
go-swagger でも swagger ui が実装されているので使えます

環境

  • CentOS 6.7 64bit
  • go-swagger dev
  • golang 1.6

事前準備

事前に TODO アプリを起動しておきましょう

  • todo-list-server --port=18080

swagger ui の起動

ローカルで立ち上げます
同一ローカル上でアプリが動いていることが前提です

  • swagger serve "http://127.0.0.1:18080/swagger.json" --port=28080

動作確認

ブラウザで http://xxx.xxx.xxx.xxx:28080/docs にアクセスすると以下のような UI が表示されると思います
go-swagger-ui.png

最後に

go-swagger で実装されている swagger ui を試してみました
ポイントは /swagger.json を明示的に指定する必要がある点でした

おそらく go-swagger で実装されている ui のデザインは変更することができないと思います
プライベートで使う分にはそんなに変なデザインではないので問題かなと思います

とは言えスタイルを当てたいというケースは多いと思うのでちょっと方法を考えてみたいと思います
たぶん標準の swagger ui を使ってかつテーマを当てればできるんだと思います
http://stackoverflow.com/questions/28033075/why-there-are-no-themes-for-swagger-ui

2017年2月24日金曜日

Bebop2 を Mac で制御できる環境を構築してみた

概要

前回 node-bebop という Nodejs 製のライブラリを使って Bebop2 を制御してみました
今回は Nodejs を返さず直接 Mac から制御してみました

環境

  • Mac OS X 10.10.5
  • repo 1.12.33
  • Bebop2

repo コマンドのインストール

repo コマンドは git を管理するためのツールです
環境構築に必要なのでインストールしましょう

で OK です
repo --version などしてバージョンが表示されることを確認してください

ソースの取得

Mac で Bebop2 を制御するためのツールはソースから直接インストールします
まずは先ほどインストールした repo コマンドを使ってソースコードを取得しましょう

とするとリポジトリを初期化する処理が開始されます

warning: gpg (GnuPG) is not available.
warning: Installing it is strongly encouraged.

Get https://gerrit.googlesource.com/git-repo/clone.bundle
Get https://gerrit.googlesource.com/git-repo
Get https://github.com/Parrot-Developers/arsdk_manifests.git
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
curl: (22) The requested URL returned error: 404 Not Found
Server does not provide clone.bundle; ignoring.
remote: Counting objects: 62, done.        
remote: Total 62 (delta 0), reused 0 (delta 0), pack-reused 62        
From https://github.com/Parrot-Developers/arsdk_manifests
 * [new branch]      master     -> origin/master
 * [new tag]         ARSDK3_version_3_8 -> ARSDK3_version_3_8
 * [new tag]         ARSDK3_version_3_8_1 -> ARSDK3_version_3_8_1
 * [new tag]         ARSDK3_version_3_8_2 -> ARSDK3_version_3_8_2
 * [new tag]         ARSDK3_version_3_8_3 -> ARSDK3_version_3_8_3
 * [new tag]         ARSDK3_version_3_9_0 -> ARSDK3_version_3_9_0
 * [new tag]         ARSDK3_version_3_9_1 -> ARSDK3_version_3_9_1

Your Name  [hawksnowlog]: 
Your Email [hawksnowlog@localhost.local]: 

Your identity is: hawksnowlog <hawksnowlog@localhost.local>
is this correct [y/N]? y

repo has been initialized in /Users/hawksnowlog/Documents/work/ardrone3

途中でリポジトリにコミットする Author の設定を入力する必要があるので、適当に入力しましょう
これだけではソースコード自体は取得できていないので、sync してソースコードを取得しましょう

  • repo sync

とするとズラーっとログが流れてソースコードの取得が始まります
ログは長いので省略します

最終的に以下のようになっていれば OK です

  • ls -1
build
build.sh
out
packages
products
track-branch.sh

ビルド

ダウンロードしたソースコードをビルドしましょう
ビルド方法はダウンロードした中にある「build.sh」を実行すれば OK です

コマンドについて

実行には引数が必要で今回は

  • ./build.sh -p arsdk-native -t build-sdk -j
  • ./build.sh -p arsdk-native -t build-sample -j

という感じで実行します

「-p」オプションはプロダクトを指定するオプションで今回は「arsdk-native」を指定しています
他には android , ios, ios-sim というプロダクトを指定することができます
今回は Mac から直接制御したいので arsdk-native を指定します
「-t」というオプションでタスクを指定できます
代わりに「-A」というオプションを使用することで alchemy というタスクが実行され、これを実行することで必要なものは全部ビルドされます
が、今回は SDK と Sample だけ必要なので、そのタスクだけ実行します

ちなみにタスクの一覧を表示するには

  • ./build.sh -p native -t

を実行してみてください
あとわからないことは「./build.sh -h」でヘルプを表示することができます

実行

実行するとおそらくいろいろとモジュールが足りないと言われてエラーになると思います
その場合は逐一 homebrew などで足りないモジュールをインストールすれば OK です
ちなみに自分は以下のエラーが出たので

  • brew install libtool

で対応しました

Can't exec "glibtoolize": No such file or directory at /usr/local/Cellar/autoconf/2.69/share/autoconf/Autom4te/FileUtils.pm line 345, <GEN3> line 5.
autoreconf: failed to run glibtoolize: No such file or directory
autoreconf: glibtoolize is needed because this package uses Libtool

それ以外のエラーは特に出ませんでした
公式を見ると以下がインストールされている必要があるみたいです

  • git
  • build-essential (only for Linux)
  • autoconf
  • libtool
  • libavahi-client-dev
  • libavcodec-dev
  • libavformat-dev
  • libswscale-dev
  • libncurses5-dev
  • mplayer

最終的にビルド結果が以下のようになれば OK です

[I] Finished task 'build-sdk'
[I] Finished task 'build-sample'

動作確認

今回は実際に飛ばさないでビデオストリームを使ってカメラの画像を Mac 上にリアルタイムに表示してみます

サンプルの C スクリプトを使うのですが、Mac 上での動画の描画は mplayer を使うみたいなので事前にインストールしておきます

  • brew install mplayer

インストールできたら実行してみます
実行するときは Bebop2 を起動して Mac と Wifi で接続してください

  • /path/to/Documents/work/ardrone3
  • LD_LIBRARY_PATH=./out/arsdk-native/staging/usr/lib
  • echo $LD_LIBRARY_PATH
  • ./out/Unix-base/staging/native-darwin-wrapper.sh ./out/arsdk-native/staging/usr/bin/BebopDroneReceiveStream

で実行できるはずです
やっていることは単純でラッパーのシェルを使ってビルドしてできたバイナリを実行している感じです
実行直後のデバッグログは以下の通りです

[INF] BebopDroneReceiveStream | 14:49:46:749 | main:307 - -- Bebop Drone Receive Video Stream --
[INF] BebopDroneReceiveStream | 14:49:46:750 | main:317 - -- Starting --
MPlayer 1.3.0-4.2.1 (C) 2000-2016 MPlayer Team
Can't init Apple Remote.

Playing /tmp/arsdk_wz0b7G/arsdk_fifo.
libavformat version 57.25.100 (internal)
[INF] BebopDroneReceiveStream | 14:49:46:763 | ardiscoveryConnect:473 - - ARDiscovery Connection
[INF] BebopDroneReceiveStream | 14:49:46:933 | startNetwork:505 - - Start ARNetwork
[INF] BebopDroneReceiveStream | 14:49:46:933 | startVideo:611 - - Start ARStream
[INF] BebopDroneReceiveStream | 14:49:46:933 | sendBeginStream:730 - - Send Streaming Begin
[ERR] ARNETWORKAL_WifiNetwork | 14:49:46:957 | ARNETWORKAL_WifiNetwork_GetAvailableSendSize:1154 - [0x7ff759d000c0] Error $
uring ioctl 6 (Device not configured)
[INF] ARNETWORKAL_WifiNetwork | 14:49:46:957 | ARNETWORKAL_WifiNetwork_GetAvailableSendSize:1159 - [0x7ff759d000c0] ioctl f
ailed with error ENXIO, stop trying to get available socket buffer size

...

しばらくすると Bebop2 のカメラの映像が Mac にも表示されると思います
bebop2_with_unix_commnds.png

最後に

紹介は以上です
2015年くらいの記事を参考に実施しようとすると ARSDKBuildUtils をクローンしてビルドする手順が出てくるのですが、どうやらこれは非推奨になったらしく今回の手順が正規の手順になったようです

あとは実行シェル (native-darwin-wrapper.sh) とサンプルファイル (BebopDroneReceiveStream.c) を見れば何とか作れるんじゃないかなと思います
独自のサンプルもできたら紹介したいと思います

参考サイト

go-swagger を使って生成したコードに独自のロジックを実装してみた

概要

前回 go-swagger のインストールと簡単なサーバの生成と起動まで実施しました
今回は生成されたコードを修正し実際のロジックまで作成してみました

環境

  • CentOS 6.7 64bit
  • go-swagger dev
  • golang 1.6

swagger.yml 編集

TODO リストに必要な REST API を追加します
前回の swagger.yml から追記する必要がある差分は以下の通りです
基本的には paths の「/」に post 命令を追加するのと新規の paths「/{id}」に対して put と delete の定義を追加しています
TODO アプリに必要な CRUD 機能を追加してる感じです

65,118d65
<     post:
<       tags:
<         - todos
<       operationId: addOne
<       parameters:
<         - name: body
<           in: body
<           schema:
<             $ref: "#/definitions/item"
<       responses:
<         201:
<           description: Created
<           schema:
<             $ref: "#/definitions/item"
<         default:
<           description: error
<           schema:
<             $ref: "#/definitions/error"
<   /{id}:
<     parameters:
<       - type: integer
<         format: int64
<         name: id
<         in: path
<         required: true
<     put:
<       tags:
<         - todos
<       operationId: updateOne
<       parameters:
<         - name: body
<           in: body
<           schema:
<             $ref: "#/definitions/item"
<       responses:
<         200:
<           description: OK
<           schema:
<             $ref: "#/definitions/item"
<         default:
<           description: error
<           schema:
<             $ref: "#/definitions/error"
<     delete:
<       tags:
<         - todos
<       operationId: destroyOne
<       responses:
<         204:
<           description: Deleted
<         default:
<           description: error
<           schema:
<             $ref: "#/definitions/error"

追記できたら validation して再生成します

  • swagger validate swagger.yml
  • swagger generate server -A TodoList -f swagger.yml

で再度 .go ファイルが生成されます

restapi/configure_todo_list.go 編集

では、実際に TODO アプリに必要な機能を実装してみます
編集する箇所がやや多いのでポイントごとに紹介します

import

import (
        "crypto/tls"
        "fmt"
        "net/http"
        "sync"
        "sync/atomic"

        errors "github.com/go-openapi/errors"
        runtime "github.com/go-openapi/runtime"
        middleware "github.com/go-openapi/runtime/middleware"
        "github.com/go-openapi/swag"
        graceful "github.com/tylerb/graceful"

        "github.com/hawksnowlog/todo-list/models"
        "github.com/hawksnowlog/todo-list/restapi/operations"
        "github.com/hawksnowlog/todo-list/restapi/operations/todos"
)

既存 import にいくつかライブラリを追加しています
足りない部分を追加すれば基本は OK です
sync や swag, モデルを管理するための models が追加になっていると思います

ロジック

ちょっと長いです
が、これが TODO アプリのコアの機能の部分になっています

var items = make(map[int64]*models.Item)
var lastID int64

var itemsLock = &sync.Mutex{}

func newItemID() int64 {
        return atomic.AddInt64(&lastID, 1)
}

まずは TODO を保存するを定義します
TODO にはインクリメントな ID が振られるため、それを生成するための関数を定義します
次に各 CRUD 処理のメインとなる関数をそれぞれ準備します

func addItem(item *models.Item) error {
        if item == nil {
                return errors.New(500, "item must be present")
        }

        itemsLock.Lock()
        defer itemsLock.Unlock()

        newID := newItemID()
        item.ID = newID
        items[newID] = item

        return nil
}

func updateItem(id int64, item *models.Item) error {
        if item == nil {
                return errors.New(500, "item must be present")
        }

        itemsLock.Lock()
        defer itemsLock.Unlock()

        _, exists := items[id]
        if !exists {
                return errors.NotFound("not found: item %d", id)
        }

        item.ID = id
        items[id] = item
        return nil
}

func deleteItem(id int64) error {
        itemsLock.Lock()
        defer itemsLock.Unlock()

        _, exists := items[id]
        if !exists {
                return errors.NotFound("not found: item %d", id)
        }

        delete(items, id)
        return nil
}

func allItems(since int64, limit int32) (result []*models.Item) {
        result = make([]*models.Item, 0)
        for id, item := range items {
                if len(result) >= int(limit) {
                        return
                }
                if since == 0 || id > since {
                        result = append(result, item)
                }
        }
        return
}

関数の名前の通りなのでそれほど読み解くのは難しくないと思います
先程定義した items という変数に対して値を追加したり削除したり更新したりする処理をそれぞれの関数で行っているだけです

またこのロジックは // This file is safe to edit. Once it exists it will not be overwritten というコメントがあるので、その直下に記載してください

ハンドラで各ロジックをコールする

実装したロジックをハンドラ側でコールします
configureAPI というメソッドがあるのでその中のハンドラを修正します

api.TodosAddOneHandler = todos.AddOneHandlerFunc(func(params todos.AddOneParams) middleware.Responder {
        fmt.Println("TodosAddOneHandler")
        if err := addItem(params.Body); err != nil {
                return todos.NewAddOneDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
        }
        return todos.NewAddOneCreated().WithPayload(params.Body)
})
api.TodosDestroyOneHandler = todos.DestroyOneHandlerFunc(func(params todos.DestroyOneParams) middleware.Responder {
        fmt.Println("TodosDestroyOneHandler")
        if err := deleteItem(params.ID); err != nil {
                return todos.NewDestroyOneDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
        }
        return todos.NewDestroyOneNoContent()
})
api.TodosFindTodosHandler = todos.FindTodosHandlerFunc(func(params todos.FindTodosParams) middleware.Responder {
        fmt.Println("TodosFindTodosHandler")
        mergedParams := todos.NewFindTodosParams()
        mergedParams.Since = swag.Int64(0)
        if params.Since != nil {
                mergedParams.Since = params.Since
        }
        if params.Limit != nil {
                mergedParams.Limit = params.Limit
        }
        return todos.NewFindTodosOK().WithPayload(allItems(*mergedParams.Since, *mergedParams.Limit))
})
api.TodosUpdateOneHandler = todos.UpdateOneHandlerFunc(func(params todos.UpdateOneParams) middleware.Responder {
        fmt.Println("TodosUpdateOneHandler")
        if err := updateItem(params.ID, params.Body); err != nil {
                return todos.NewUpdateOneDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
        }
        return todos.NewUpdateOneOK().WithPayload(params.Body)
})

デバッグ用に fmt していますが必須ではないので不要であれば削除してください
基本は先程定義したロジックをコールしてその結果を見て成功 or 失敗のレスポンス情報を返却してます
レスポンスを返却するようの関数はすでに swagger が生成してくれているのでそれを素直に使います

記載できたらフォーマットしてインストールしましょう
go install でビルドもされるのでバイナリが新規に作成されます

  • go fmt restapi/configure_todo_list.go && go install ./cmd/todo-list-server/

バイナリが生成できたら起動します

  • todo-list-server --host 0.0.0.0 --port=18080

P.S 20190206 解説追記

api.TodosFindTodosHandler で引数の params todos.FindTodosParams をそのまま参照せず、なんでわざわざ todos.NewFindTodosParams() し直しているかというと Since パラメータに default の定義がないからです
もしそのまま params.Since という感じでポインタ参照すると invalid memory address or nil pointer dereference になります
なので swagger.yml で

paths:
  /:
    get:
      tags:
        - todos
      operationId: findTodos
      parameters:
        - name: since
          in: query
          type: integer
          format: int64
          default: 0
        - name: limit
          in: query
          type: integer
          format: int32
          default: 20

という感じで since に default:0 を追加して swagger generate server -A TodoList -f swagger.yml し直してあげると以下のように直接 params を参照してもエラーになりません

api.TodosFindTodosHandler = todos.FindTodosHandlerFunc(func(params todos.FindTodosParams) middleware.Responder {
    return todos.NewFindTodosOK().WithPayload(allItems(*params.Since, *params.Limit))
})

go-swagger はこんな感じで引数やロジック側からのレスポンスをわざわざ正しい構造体に変換してから扱わなければいけない箇所が多いような気がします、、、

動作確認

それぞれ curl を叩けば OK です
なぞの Content-Type ヘッダがありますが、今回の swagger ファイルだとこの Content-Type が必須になります

  • curl -XPOST -H "Content-Type: application/io.goswagger.examples.todo-list.v1+json" "http://127.0.0.1:18080/v1/" -d '{"description":"test", "completed":false}'
{"description":"test","id":1}
  • curl -XGET "http://127.0.0.1:18080/v1"
[{"description":"test","id":1}]
  • curl -XPOST -H "Content-Type: application/io.goswagger.examples.todo-list.v1+json" "http://127.0.0.1:18080/v1/" -d '{"description":"test2", "completed":true}'
{"completed":true,"description":"test2","id":2}
  • curl -XGET "http://127.0.0.1:18080/v1"
[{"description":"test","id":1},{"completed":true,"description":"test2","id":2}]
  • curl -XPUT -H "Content-Type: application/io.goswagger.examples.todo-list.v1+json" "http://127.0.0.1:18080/v1/1" -d '{"description":"put test", "completed":true}'
{"completed":true,"description":"put test","id":1}
  • curl -XGET "http://127.0.0.1:18080/v1"
[{"completed":true,"description":"put test","id":1},{"completed":true,"description":"test2","id":2}]
  • curl -XDELETE -H "Content-Type: application/io.goswagger.examples.todo-list.v1+json" "http://127.0.0.1:18080/v1/1"

  • curl -XGET "http://127.0.0.1:18080/v1"

[{"completed":true,"description":"test2","id":2}]

こんな感じになれば OK です

最後に

go-swagger で実際にロジック部分を実装してみました
生成されるコードのほとんどは基本触れないでメインとなる部分だけいじればいいので簡単です
逆に言うと生成されたコードの部分は何しているさっぱりになるので、swagger の内容の理解を深めるためにコードを追ってみてもいいかもしれません

今回の実装したロジックは単純なオンメモリの情報なのでサーバを停止すると情報は消えてしまいます
なので、本来あれば DB を使ったりして実装します

その場合でも基本的な実装の流れは変わらないかなと思います

今回コードの紹介は全部だと長いので一部分とさせていただきました
基本は以下の参考サイトにあるコードを元にして作成しているので、以下を参考にするとコードの全容をイメージしやすくなるかなと思います

参考サイト

2017年2月23日木曜日

Mac で Nodejs を使って Bebop2 を制御してみる

概要

node-bebop を使って Nodejs から Bebop2 を制御してみました

環境

  • Mac OS X 10.11.4
  • Nodejs 5.9.1
  • npm 3.7.3
  • node-bebop 0.6.0

node-bebop のインストール

  • cd /path/to/work
  • npm install node-bebop

Bebop2 の組み立てと接続

ここでは詳細は説明しません
プロペラを付けて、バッテリーを充電したら Bebop2 を起動してください

Bebop2 を起動すると Bebop2 用の Wifi のアクセスポイントが出現します
first_bebop2_connect_bebop.png

Mac とこのアクセスポイントを接続しましょう
デフォルトだとパスワード等も特になく名前もこんな感じで Bebop2-xxxxxxx で表示されます
接続したらスクリプトを実行してみましょう

サンプルのスクリプト

とりあえずテイクオフして 3 秒後にランディングするスクリプトを作成してみます

  • cd /path/to/work
  • vim first_bebop.js
var bebop = require('node-bebop');

var drone = bebop.createClient();

drone.connect(function() {
  drone.takeOff();

  setTimeout(function() {
    drone.land();
  }, 5000);
});
  • node first_bebop.js

これで実行するとテイクオフして 5 秒後にランディングすると思います
もちろん実行するときは Bebop2 と接続した状態で行ってください
Bebop2 と接続していない状態で実行すると以下のようなエラーになると思います

events.js:154
      throw er; // Unhandled 'error' event
      ^

Error: connect ETIMEDOUT 192.168.42.1:44444
    at Object.exports._errnoException (util.js:890:11)
    at exports._exceptionWithHostPort (util.js:913:20)
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1061:14)

最後に

Bebop2 を Nodejs で動かす Getting Started 的なことをやってみました
公式 だと iOS や Android 用の SDK がメインなので Nodejs で動かせるとサーバ処理と連携したりできるので応用性が増しそうです

Bebop2 と接続しているときは当たり前ですが、インターネットには接続できません
なので、インターネットに接続しながら Bebop2 も制御したいとなると Mac に Wifi 用の USB ドングルをもう一つ接続する必要があります

Dozens を使って Blogger にカスタムドメインを設定する方法

概要

Dozens を使って Blogger にカスタムドメインを設定する方法を紹介します
ドメインは事前に取得しておくことが前提です

環境

  • Blogger 2017/02/15
  • Dozens 2017/02/15

方法

Blogger の管理画面にアクセスします
設定 -> 基本 -> 公開で「ブログのサードパーティ URL を設定」を選択します
blogger_custom_domain1.png

今回は blog. で始めるカスタムドメインを設定します
入力して保存すると CNAME を 2 つ入れるように言われます
blogger_custom_domain2.png

Dozens で言われたとおりに CNAME レコードを 2 つ追加します
blogger_custom_domain3.png

blogger_custom_domain4.png

こんな感じで追加されれば OK です
blogger_custom_domain5.png

Blogger に戻って再度保存すればエラーが出ないで保存できると思います

トップレベルドメインもブログにリダレクトする

blog. がなくてもブログにアクセスするように設定できます

Blogger の管理画面にアクセスします
設定 -> 基本 -> 公開でリダイレクトするためのチェックボタンにチェックを入れます
保存します
blogger_custom_domain6.png

Dozens 側に A レコードを 4 つ追加します

  • 216.239.32.21
  • 216.239.34.21
  • 216.239.36.21
  • 216.239.38.21

blogger_custom_domain7.png

RECORD NAME はトップレベルドメインなので設定するときに何も入力しなければ OK です

最後に

Blogger でカスタムドメインを設定する方法を紹介しました
トップレベルドメインのリダイレクトが結構時間がかかるっぽいです

CNAME は速攻で反映されました

が、なんと

現在リダイレクトの設定に問題があるようで、独自ドメインにリダイレクトするときになぜかリダイレクトの確認ページが表示されてしまうと
いう問題があります
https://productforums.google.com/forum/?utm_medium=email&utm_source=footer#!msg/blogger/gPRRPcPTkro/yH0aEE30CwAJ

現状回避する方法がないらしく Blogger 運営側の対応を待つしかないようです
スレを追うとわかりますが、皆さん激おこですw

参考サイト

2017年2月22日水曜日

WARNING ITMS-90080: "The executable 'Payload/myapp.app/Frameworks/NCMB.framework' is not a Position Independent Executable

概要

Xcode で iTunesConnect にビルドしてできたファイルをアップロードする際にタイトルの警告が発生しました
対処方法を紹介したいと思います

環境

  • Mac OS X 10.11.5
  • Xcode 7.3.1
  • Cocoapods 0.39.0
  • iTunes Connect 2016/06/07 時点

警告詳細

WARNING ITMS-90080: “The executable ‘Payload/myapp.app/Frameworks/NCMB.framework’ is not a Position Independent Executable. Please ensure that your build settings are configured to create PIE executables. For more information refer to Technical Q&A QA1788 - Building a Position Independent Executable in the iOS Developer Library.”

が全容です
Framework のところはどんな Framework でも出ると思います
この警告が Xcode で Product -> Archive としてビルドしたあとに iTunes Connect にアップロードするときに発生しました

出る時と出ない時の違い

実は今回は警告は初めは出ませんでした
あることをしたら出るようになったのですが、そのあることとは

Deployment Target を 9.3 から 8.0 に変更した

になります
これをやると警告が出るようになりました

対処方法

で、結局どうやって対応したかですが結論としては

  • 何もしていない

になります

警告文をよく調べてみると PIE ( Position Independent Executable ) が有効になっているせいで警告が出ているケースが主な原因であることが判明しました
その対応方法は公式にも出ていて

  1. In Xcode, select your target in the “Targets” section, then click the “Build Settings” tab to view its settings.
  2. For iOS apps, set iOS Deployment Target to iOS 4.3 or later. For Mac apps, set OS X Deployment Target to OS X 10.7 or later.
  3. Verify that Generate Position-Dependent Code is set at its default value of NO.
  4. Verify that Don’t Create Position Independent Executables is set at its default value of NO.

を設定するみたいです
しかし、自分の場合すべてクリアしているにも関わらず該当の警告が発生し続けました

更に調べてみると Cocoapods の以下の issue にたどり着きました

でここでは結局「Apple 側のバグでしょ、いづれ対応してくれるよ」という結論で Close されています ( 一応まだ別の場所で議論はされているみたいですが )

ここで自分も気づいたのですが、確かに警告をみると自分のプロジェクトが PIE として怒られているわけではなく Cocoapods でインストールしらライブラリが警告の対象になっていることがわかりました
なので、自分のアプリは PIE の対象外だと判断してそのまま iTunes Connect で審査に提出しました

結果的には

審査は問題なく通過しました
ということで今回と同じケースに遭遇した方は基本何もしないで OK です
ただ、自分のアプリの PIE 自体は対応しなければいけない問題なので、自分のアプリで PIE が有効になっている場合は Build Settings を No に変更してビルドし直してください

go-swagger を使ってみた

概要

swagger は REST API を開発するためのフレームワークで YAML ファイルに定義した内容からコードを生成してくれるものです
今回はその golang 版である go-swagger というものがあるので使ってみました
とりあえず YAML ファイルを定義してコードを生成して、サーバを起動するところまでになります

環境

  • CentOS 6.7 64bit
  • go-swagger dev
  • golang 1.6

インストール

  • go get -u github.com/go-swagger/go-swagger/cmd/swagger

GOPATH の設定と PATH に GOPATH/bin を追加しておいてください
インストールが完了すると swagger コマンドが使えるようになっています
あとから PATH に追加した場合は一度ログアウトして再度ログインするとコマンドが使えるようになっていると思います

プロジェクトの作成

  • mkdir -p $GOPATH/src/github.com/hawksnowlog/todo-list
  • cd $GOPATH/src/github.com/hawksnowlog/todo-list
  • swagger init spec --title "A To Do list application" --description "The product of a tutorial on goswagger.io" --version 1.0.0 --scheme http --consumes application/io.goswagger.examples.todo-list.v1+json --produces application/io.goswagger.examples.todo-list.v1+json
    • -> 2017/02/21 15:14:19 creating specification document in root/swagger.yml
  • swagger validate swagger.yml
    • -> The swagger spec at “swagger.yml” is valid against swagger specification 2.0

swagger init で雛形となる swagger.yml ファイルを作成することができます
オプションをいろいろと設定することでデフォルトの YAML ファイルに記載する内容を指定できます
作成したファイルを swagger validate でフォーマットの確認ができます
今回は作成したばかりなので特にエラーにはならないと思います

設定を追記する

swagger.yml に REST API に必要となる設定を追記していきます
追記の内容ごとに説明しており最後に YAML ファイルの全容があるので、全体はそちらを確認してください

basePath

  • emacs swagger.yml
basePath: /v1

まず初めに basePath を設定します
基本はここで設定したパス配下に対していろいろな API を生やしていきます

definitions

  • emacs swagger.yml
definitions:
  item:
    type: object
    required:
      - description
    properties:
      id:
        type: integer
        format: int64
        readOnly: true
      description:
        type: string
        minLength: 1
      completed:
        type: boolean

definitions でレスポンスに含まれるフィールド情報などを定義します
item というオブジェクトには id, description, completed というフィールドが含まれています
それぞれには型やフォーマットを設定することができます
生成された swagger.yml の下に追記すれば OK です

paths

  • emacs swagger.yml
paths:
  /:
    get:
      tags:
        - todos
      operationId: findTodos
      responses:
        200:
          description: list the todo operations
          schema:
            type: array
            items:
              $ref: "#/definitions/item"

次に paths を追記します
paths は「このパスに来たら」「このオブジェクトを返します」という定義をすることができます
上記の場合「/」に GET でアクセスが来たら 200 を返すという定義をしています
そしてレスポンス情報に先程定義した #/definitions/item を配列に突っ込んで返却するようにしています
tags は公式を見ると後々幸せになれるらしいので今回はおまじないとして付与しておきましょう

paths -> parameters

  • emacs swagger.yml
paths:
  /:
    get:
      tags:
        - todos
      operationId: findTodos
      parameters:
        - name: since
          in: query
          type: integer
          format: int64
        - name: limit
          in: query
          type: integer
          format: int32
          default: 20
      responses:
        200:
          description: list the todo operations
          schema:
            type: array
            items:
              $ref: "#/definitions/item"

先程の paths に parameters という項目を追記します
これはユーザから送信される所謂クエリパラメータ or リクエストボティ情報になります
今回追記したのはページネイト用のパラメータで「ここから何件取得する」というパラメータになります

definitions -> error

  • emacs swagger.yml
definitions:
  item:
    type: object
    required:
      - description
    properties:
      id:
        type: integer
        format: int64
        readOnly: true
      description:
        type: string
        minLength: 1
      completed:
        type: boolean
  error:
    type: object
    required:
      - message
    properties:
      code:
        type: integer
        format: int64
      message:
        type: string

definitions にエラー用のレスポンス定義を追記します
definitions に error という項目を追加します
error はステータスコード (code) とエラーメッセージ (message) をレスポンスの内容に含めるように定義します

paths -> responses -> default

  • emacs swagger.yml
paths:
  /:
    get:
      tags:
        - todos
      operationId: findTodos
      parameters:
        - name: since
          in: query
          type: integer
          format: int64
        - name: limit
          in: query
          type: integer
          format: int32
          default: 20
      responses:
        200:
          description: list the todo operations
          schema:
            type: array
            items:
              $ref: "#/definitions/item"
        default:
          description: generic error response
          schema:
            $ref: "#/definitions/error"

「/」にアクセスしたときに default でエラーを返却するように追加します
返却するエラーオブジェクトは先程追記した #/definitions/error になります

編集後の swagger.yml

全体として以下のようになれば OK です (ちょっと長いですが)

consumes:
- application/io.goswagger.examples.todo-list.v1+json
info:
  description: The product of a tutorial on goswagger.io
  title: A To Do list application
  version: 1.0.0
produces:
- application/io.goswagger.examples.todo-list.v1+json
schemes:
- http
swagger: "2.0"
basePath: /v1
definitions:
  item:
    type: object
    required:
      - description
    properties:
      id:
        type: integer
        format: int64
        readOnly: true
      description:
        type: string
        minLength: 1
      completed:
        type: boolean
  error:
    type: object
    required:
      - message
    properties:
      code:
        type: integer
        format: int64
      message:
        type: string
paths:
  /:
    get:
      tags:
        - todos
      operationId: findTodos
      parameters:
        - name: since
          in: query
          type: integer
          format: int64
        - name: limit
          in: query
          type: integer
          format: int32
          default: 20
      responses:
        200:
          description: list the todo operations
          schema:
            type: array
            items:
              $ref: "#/definitions/item"
        default:
          description: generic error response
          schema:
            $ref: "#/definitions/error"

サーバを作成する

記載した swagger.yml を元にサーバを生成します

  • swagger generate server -A TodoList -f swagger.yml

成功すると以下のファイル郡が生成されます

  • tree -a .
.
├── cmd
│   └── todo-list-server
│       └── main.go
├── models
│   ├── error.go
│   └── item.go
├── restapi
│   ├── configure_todo_list.go
│   ├── doc.go
│   ├── embedded_spec.go
│   ├── operations
│   │   ├── todo_list_api.go
│   │   └── todos
│   │       ├── get.go
│   │       ├── get_parameters.go
│   │       ├── get_responses.go
│   │       └── get_urlbuilder.go
│   └── server.go
└── swagger.yml

インストールする

サーバコードの生成が成功すると動作させたのでバイナリも作成しましょう
依存するパッケージがないといろいろとエラーになったので go get でインストールします

  • go get github.com/go-openapi/runtime
  • go get github.com/tylerb/graceful
  • go get github.com/jessevdk/go-flags
  • go get golang.org/x/net/context
  • go get github.com/docker/go-units
  • go get github.com/go-openapi/analysis
  • go get github.com/go-openapi/spec
  • go get github.com/go-openapi/validate
  • go get github.com/gorilla/context

それぞれ個別でインストールしても OK ですが面倒な場合は go-swagger リポジトリにある Gopkg.toml を使いましょう

  • wget https://raw.githubusercontent.com/go-swagger/go-swagger/master/Gopkg.toml
  • dep ensure

依存ライブラリがインストールできたらバイナリを生成しましょう

  • go install ./cmd/todo-list-server/

インストールに成功したら todo-list-server を実行すればサーバが起動します
デフォルトだと localhost からのアクセスしか受け付けていないのでホスト名やポートを指定したい場合には

  • todo-list-server --host 0.0.0.0 --port 18080

とします
起動したらルートにアクセスしてみると

  • curl localhost:18080
{"code":404,"message":"path / was not found"}

というエラーが返ってくると思います
また basePath で指定したパスにアクセスすると

  • curl localhost:18080/v1
"operation todos.Get has not yet been implemented"

が返ってきます
これを返しているのが restapi/configure_todo_list.go というファイルになっておりここを編集することでレスポンスを制御できるようになっています

最後に

go-swagger を使ってとりあえずサーバが起動するところまでやってみました
まだまだ swagger の良さを体験できるレベルではないですが、これだけでも簡単な JSON API を作れることは体験できたので、その片鱗は見れたかなと思います

次は実際にコードをいじって、動作させるところまでやってみます

参考サイト