初めまして。普段NLPを触っている海老原です。今回はGoでAPIサーバーを作り、herokuへデプロイし、iosのショートカットからアクセスしてみた一連の流れと詰まった個所を記述します。
製作物データ
作ったもの
pdfのurlを受け取り、ダウンロードした後Dropboxに送信するサーバー
githubへのリンク
今回のサイトへのリンク
製作期間
丸1日
制作者レベル
- チュートリアル終了レベル
- Qiita初投稿
モチベーション
- 通信量的な問題で論文のpdf(大きい)をサーバー上でクラウドへ上げてwifi接続時に端末にインスコしたかった
- Goのチュートリアルが終わった
- 就活がやばい(アウトプットォ...)
やったこと
- ginを使ったpost, get通信
- DropboxのAPIを使ったpdfのアップロード
- godepを使ったvendor管理
- herokuへのアップロードと起動
本編
APIサーバー作成
環境構築
まず初めに goをインストールし、go get github.com/tools/godep
、go get github.com/gin-gonic/gin
を行います。godep周辺を知りたい方はここを参照してください。また私の環境はwindowsなのですが、デフォルトGOPATHがわけわからん所(Go的には正解?)に通されていたので使いやすい所に設定しなおしました。GOPATHを通し、go getを実行した段階でGOPATH以下にsrc, bin, pkg...のファイル群が生成されました。ここのsrc/github.com/(自分のgithub)/(アプリ)というファイル構成で開発するのが正解っぽいです。
ginを利用した開発
ソースコードはgithubにありますが、mainから見ていこうと思います。(vendor以下は普通gitignoreで排除するっぽい)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "send token and url and filename to /load use post")
})
router.POST("/load", Load)
port := os.Getenv("PORT")
if port == "" {
port = "3000"
}
router.Run(":"+port)
}
ginすごいですねgin.Default()とrouter.Run()さえあれば動きますよ、詳しくはここ。POST通信をしているLoadが今回のメインの関数です。また、今回はherokuを利用するので空いているポートが分かりませんでした。なので空いてるポートを探して、無かったら3000番で立ち上げる様にしてあります。
func Load(c *gin.Context) {
filename, err := get_pdf(c.PostForm("url"), c.PostForm("filename"))
if err != nil {
c.String(http.StatusInternalServerError, "server error")
}
err = send_pdf(c.PostForm("token"), filename)
if err != nil {
c.String(http.StatusInternalServerError, "server error")
}
err = del_file(filename)
if err != nil {
c.String(http.StatusInternalServerError, "server error")
}
c.String(http.StatusOK, filename+" is done")
}
基本的に受け取ったcontextにステータスやエラーを詰めて返しています。またここではフォーム形式の入力を必要な部分だけ次の関数へ渡しています。
func get_pdf(URL,name string) (string, error){
client := &http.Client{}
url := URL
token := c.PostForm("token")
filename := name+".pdf"
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
res, err := client.Do(req)
if err != nil {
os.Exit(1)
return "", err
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
os.Exit(1)
return "", err
}
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
return "", err
}
defer func() {
file.Close()
}()
file.Write(body)
return filename, nil
}
上記は受け取ったURLを開き、ローカルにfilenameを名前として保存しています。この時ファイルはos.File形式なのですが、os.Fileをreturnすることができなかったのでローカルに保存しファイル名のみを返しています。
func send_pdf(token, filename string) (err error){
type Send_json struct {
Path string `json:"path"`
Mode string `json:"mode"`
}
client := &http.Client{}
url := "https://content.dropboxapi.com/2/files/upload"
param, err := json.Marshal(Send_json{Path:"/"+filename, Mode:"add"})
if err != nil {
return
}
file, err := ioutil.ReadFile(filename)
if err != nil {
return
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(file))
if err != nil {
return
}
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Dropbox-API-Arg", string(param))
req.Header.Set("Content-Type", "application/octet-stream")
_, err := client.Do(req)
return err
}
セキュリティに自信がなかったのでトークンは随時入力を求め、保留しないようにしています。また、ここではdropboxのapiを利用してファイルをアップロードしています。golangにはdropbox用のapiライブラリがあり、それを利用すれば簡単にアップロード等できるのですが非公式で本家の方が信頼できるので直接公式apiを叩いています。apiの場合150MBまでしか送れない(?)様なので150MB以上のファイルを送信しようとする場合上手くいかないかもしれません。パラメータですが、一旦jsonにしてstringに直すというとても助長的な事をしていますがご容赦を...。参考元。
func del_file(filename string) (err error){
err = os.Remove(filename)
return
}
最後にファイルを削除して終了です。
ここまでで半日です、ここからがGo感マシマシで大変でした。。
herokuへのアップロード
始めはgithubとの連携でアップロードしていました。ですがherokuでgoを動かす為にはvendor/が必要でvendor以下には依存パッケージが含まれるのでgithub連携でなく、CLIでアップロードした方がよさそうです。詳しくはここ。というわけでvendor以下を用意する為に今回はgodepを利用していきます。
godepを利用したvendor
godep save
をソースコードが置いてあるディレクトリで行うと、vendor/とGodeps/が生成され、依存パッケージがグローバルからそのプロジェクトディレクトリにコピーされます。Godeps.jsonをvendor以下にコピーします(これはいらなかったかも)。Procfileを作成してweb: EssayForDropbox
、go特有の過程は終了です。(Procfileのweb:は実行されるコマンドではなく、アプリディレクトリ名だという事に気づかず半ギレでやってました)
herokuへのアップロード(続)
こことherokuの公式ページを参照してgitとherokuを連携した後、全てをアップロードします。この際postmanでテストしたのですが、H10とH13エラーが起きました。H13はサーバーが何も返さない、H10はサーバーが落ちているエラーです。H13が起きた際、入力を失敗していたのでプログラムのエラーでクラッシュしていました。またH10が起きた際はheroku restart
でappを再起動出来るのでそれで解消できます。できない場合。cannot find package "github.com/gin-gonic/gin"
というエラーが起きていたので色々調べたのですが、どうもgitでginがファイルとして認識されていた様でgit ls-files
で分かりました。なのでいったん消した git rm --cached vendor/github.com/gin-gonic/gin
後、もう一回追加 git add .
したら直りました。
iosからの接続
今回はiosのショートカットを利用してアプリ化したいと思います。具体的にはクリップボードへコピーしたurlとファイル名(.pdf無し)、dropboxのトークンをpost通信で送信します。Dropboxのuploadトークンはここ。参考ページを参照してPOST通信のアプリを作成しました。利用例を以下に示します。
- pdfのurlをクリップボードにコピーする
展望
- tokenをユーザーに用意させるの心苦しいから自動取得とかやりたい
- いつでも落ちますwelcomeって感じだからもっと入力チェックとかできるようにしないと
- dockerで運用したい