- 投稿日:2020-01-16T23:41:00+09:00
【Golang】mockgen で生成した *_mock.go のせいで Build がコケる場合の対処法
対象読者
- Go の mockgen で生成した *_mock.go ファイルのせいで Build がコケている人
- 自分
忙しい人のための結論
Build failed 時に、怒られているコード内の該当箇所を消せばよい。
事例
REST API を golang で書いていた。
テストコードを書くべく、mockgen でモックを生成したところ、それらのファイルにいちゃもんをつけられて Build が通らなくなった。# hoge/fuga fuga | hoge/fuga_mock.go:**:*: m.ctrl.T undefined (type *gomock.Contr oller has no field or method T)m.ctrl.T で怒られるのは gomock の code generation が悪いからではないかと推測される。下記リンクによると gomock の version が関係していそう。
参考:https://github.com/openshift/openshift-azure/issues/870下記リンクによると、同様の問題(m.ctrl.T undefined)が発生したが、gomock を v1.2.0 にしたところ動いたという。原因は構造体のフィールド t の表記を T と変更したことのようだ。
https://github.com/golang/mock/issues/255では、go get で落とした gomock, mockgen の version を変更するには?
Q. go get で落としたパッケージの version を変更する場合には?
A. $GOPATH 以下の src/ で、以下を実行し、使用するバージョン情報のブランチにチェックアウトする。
$ git tag -l #各バージョンのブランチが表示(tags/v1.2.0 など) $ git checkout tags/v1.2.0 #v1.2.0 に切り替える場合または glide などでパッケージ管理するといいらしい(Go version 1.5 以上)。
参考:https://qiita.com/usk81/items/8e192e68d6b18bec3b4aだが、今回の問題に関してはより簡単な方法があった。。。
改めて結論
以下のようなメッセージが表示される箇所はコメントアウトしたところ、問題なく Build が成功した。下記の場合では、m.ctrl.T を含む行をコメントアウトした。
*****-*** | hoge/fuga_mock.go:37:8: m.ctrl.T undefined (type *gomock.Controller has no field or method T)mockgen のバージョンを最新にすると通ったという声もある(詳細は未確認)。
余談:GOPATH が 2 つ以上指定されている場合
$GOPATH に指定した全ての箇所が有効なのは、 Go コンパイラが必要な外部のパッケージを探す場合のみ。逆に 1 つ目の GOPATH しか有効にならないのは、go get により外部のパッケージをダウンロード・インストールする場合らしい。
参考:https://qiita.com/chano2/items/ea76cc503e651f07bfb0余談:mockgen で生成されたファイル一覧を表示
ついでに mockgen で生成したファイルの一覧を取得した。
日付表示でラフにスクリーニングし($7>15
)、ファイル名に mock を含むことを要求($9 ~ /.*mock.*/
)した。誰かもっとスマートな方法を教えてください。$ ls -ulR $(find . -type f) | awk '{if($7>15 && $9 ~ /.*mock.*/){print $6" "$7" "$8" "$9}}’余談の余談:GOPATH, GOROOT の表示
$ echo ${GOPATH} $ echo ${GOROOT}ではなく、
go env GOPATH go env GOROOTを用いた方がいい。デフォルト(環境変数として設定していない)の場合は echo では表示されないからだ。
参考:https://qiita.com/YumaInaura/items/3372186e2c11a45c65d8
- 投稿日:2020-01-16T18:14:46+09:00
Goで超簡易版Twitterを作って見ました(初心者向け)
はじめまして
最近Goの勉強を始めたばかりで何か開発してみようと思い、簡単なアプリを開発しました。
twitterもやってます@khm_itac見た目
出来る事
・フォームから投稿する事が出来ます
・投稿をデータベースに保存する事が出来ます
・保存されている投稿を表示してくれます
・投稿を消す事も出来ますはい、それだけです。
めちゃくちゃ低レベルですが、UIからサーバーにデータを送りDBへ保存、DBのデータをビューに表示するというのは全てのアプリの基本的な部分だと思いますので、これが出来るとようやくエンジニアレベル1達成です!
僕と同じようにこれからGoの勉強を始める方の少しでも役に立てればと思い記事を投稿しました。
僕も初心者ですので間違ってる部分や至らない点があるかと思いますがご了承ください。解説
Goの導入や基礎文法やVSCodeの使い方などは解説しません、僕より詳しい人がYouTube等で動画を出していますのでそちらで勉強した方がいいと思います(あとProgateとか)
Macでの開発ですのでwindowsの方はうまく動かない可能性もありますまずmain.goを作ります
メインディレクトリの直下でいいと思います。
この画面上でF5を押すとスタートさせる事が出来ます、その後http://localhost:8080
にアクセスすると
この画面になります
これでサーバーを建てられました。
Goでは起動させると func main() が動き出します。
なのでmain関数の中に書いてあるhttp.ListenAndServe(":8080", nil)が起動するという事ですね。
8080という数字はお好みで大丈夫です、他の数字でも動きます。
ちなみにimport "net/http"も書いてあげないと動きません。
Goでは使用したい機能(パッケージ)をimportしてあげる必要があります。
ではサーバーを起動する事が出来たのでHTMLを表示させたいと思います。メインディレクトリの下にviewsディレクトリを作成し、その中にindex.htmlを作ります
そして適当に文字を書いてみます
そしてこのHTMLファイルをmain.goから呼び出す記述を書いてみます
この記述後に再起動してみて、http://localhost:8080
をリロードしてみてください
このようにHTMLの文字が画面に表示されると成功です解説
http.HandleFuncは第一引数でURLのパスを指定し、第二引数で動かしたい関数を指定します。
今回第一引数は "/" なのでルートパスがリクエストされた時にindexHandlerという関数を呼び出しているという事です。
次にindexHandlerの中身ですが、10行目でtemplate.ParseFilesにHTMLファイルを指定しその中身を tとerrという変数に代入しています。11行目で、もしtemplate.ParseFilesに渡したHTMLが存在しなければerr変数にエラーが代入されエラーを出してくれます。
この if err != nil { log.Fatalln(err) }という記述はGoではめちゃめちゃ出てきますので、とりあえずお決まりの文という認識でいいと思います。(エラーが出た時に内容を教えてくれるよーというものです)
で、HTMLが存在すればその t変数に対してExecuteメソッドを使用するとHTMLを表示する事が出来ます。
Executeに渡している引数ですが、第一引数にResponseWriterを渡しています(これもお決まり文という認識で大丈夫です)第二引数にmain.goからHTMLに渡すデータを指定してあげる事が出来ます。
今回は何も渡さないのでnilにしておきます。とりあえずHTMLを完成させます、こんな感じにしました
次にCSSファイルを作成します。
メインディレクトリの下にresourcesディレクトリを作り、さらにcssディレクトリを作ります。その中にview.cssを作ります。こんな感じですheader{ height:50px; line-height: 50px; border:1px solid lightgray; text-align: center; color: rgb(142, 168, 184); background-color: rgb(249, 252, 252); } .tweet_view{ float: left; height: 100vh; width: 50vw; background-color: rgb(235, 243, 243); padding-top: 20px; } .tweet_message{ background-color: white; color: gray; font-size: 30px; margin: 0 20px 15px; padding: 10px; position: relative; border-radius: 15px; } button.tweet_delete_button{ background-color: rgb(130, 168, 238); border-style: none; height: 20px; width: 20px; position: absolute; right: 10px; bottom: 17px; color: rgb(255, 255, 255); line-height: 5px; padding-right: 18px; cursor: pointer; } button:focus { outline:0; } .tweet_form{ height: 100vh; width: 100vw; } textarea{ background-color: rgb(235, 243, 243); width: 450px; height: 250px; margin: 50px 0 0 120px; } input{ width: 200px; height: 50px; background-color: rgb(130, 168, 238); color: white; position: absolute; left: 68vw; top: 400px }あれ、CSSが効いてませんね。
Goでは静的ファイルを読み出す時はmain関数内で宣言をしてあげないと行けないらしいです。
ではmain.goにcssを呼び出す記述を書きますfunc main() { http.Handle("/resources/", http.StripPrefix("/resources/", http.FileServer(http.Dir("resources/")))) http.HandleFunc("/", indexHandler) http.ListenAndServe(":8080", nil) }一番上にhttp.Handleという関数を追加しました。
正直自分はコードの細部は理解出来ていなんですが、とりあえずresourcesディレクトリを呼び出してくれる関数という事らしいです(なのでコピペで大丈夫です)
この記述後もう一度リロードしてみます(cm + sf + R でスーパーリロードした方がいいかも)
今度はちゃんとCSSが効きました!次はデータベースを導入します
SQLite3というDBを導入します。導入用記事はまた今度書きます(ググればすぐ出来ます)
ではDBインストール出来たのでmain.goにインポートします
package main import ( "database/sql" _ "github.com/mattn/go-sqlite3" "log" "net/http" "text/template" ) var DbConnection *sql.DB func indexHandler(w http.ResponseWriter, r *http.Request) { t, err := template.ParseFiles("views/index.html") if err != nil { log.Fatalln(err) } t.Execute(w, nil) } func main() { http.Handle("/resources/", http.StripPrefix("/resources/", http.FileServer(http.Dir("resources/")))) http.HandleFunc("/", indexHandler) http.ListenAndServe(":8080", nil) }上記のようにimportした後、var DbConnection *sql.DBでDBを定義してあげます。
これでDbConnectionという名前でDBにアクセス出来ます。
では実際にDBにデータベースを作ってみます。今回はターミナルからSQLコマンドを打ちます。メインディレクトリでsqlite3 example.sqlと打ち込んでください。
こんな風になると思います、次にCREATE TABLE tweets (id INTEGER PRIMARY KEY,tweet STRING);と打ち込みます。
これはtweetsという名前のテーブルを作成し、idとtweetというカラムを作成するというコマンドです。
tweetは文字型なのでstring、idはintegerにします。
idのprimary keyとは、何もしなければ自動的に数字を割り振ってくれるというものです。これでtweetsテーブルは作成出来ました、一応確認してみます。ターミナルで.tabelと打ち込んでください。
tweetsテーブルが作成されているのを確認出来ました。
ではフォームに入力した値を実際にDBに入れてみます。
index.htmlの14行目に注目してください。
<form action="/tweet/" method="POST" class="tweet_form"> <textarea name="tweet" rows="20" cols="80"></textarea> <input type="submit" value="speak"> </form>actionに"/tweet/"を指定してるので、ボタンを押すとhttp://localhost:8080/tweet/
というURLが走ります、なのでmain関数にそのパスが通った時に呼び出される関数を記述すればいいという訳です。package main import ( "database/sql" _ "github.com/mattn/go-sqlite3" "log" "net/http" "text/template" ) var DbConnection *sql.DB func indexHandler(w http.ResponseWriter, r *http.Request) { t, err := template.ParseFiles("views/index.html") if err != nil { log.Fatalln(err) } t.Execute(w, nil) } func getPostTweet(w http.ResponseWriter, r *http.Request) { DbConnection, _ := sql.Open("sqlite3", "./example.sql") defer DbConnection.Close() v := r.FormValue("tweet") cmd := `INSERT INTO tweets(tweet)VALUES(?)` DbConnection.Exec(cmd, v) http.Redirect(w, r, "/", http.StatusFound) } func main() { http.Handle("/resources/", http.StripPrefix("/resources/", http.FileServer(http.Dir("resources/")))) http.HandleFunc("/", indexHandler) http.HandleFunc("/tweet/", getPostTweet) http.ListenAndServe(":8080", nil) }ますmain関数内にhttp.HandleFunc("/tweet/", getPostTweet)という関数を記述します。
これは先ほど説明しましたね、これで"/tweet/"というパスが通ればgetPostTweetという関数が呼び出されます。
ではgetPostTweet関数の中身な解説をします。
DbConnection, _ := sql.Open("sqlite3", "./example.sql")でデータベースを開いて読み込みます。
defer DbConnection.Close()で開いたら閉じましょう。
これもお決まり文という認識で大丈夫だと思います、openしたらdeferでcloseです
ビューから送信したデータはr変数(http.Request)として送られてくるので
v := r.FormValue("tweet")としてv変数に代入してあげます。
このFormValueの中の"tweet"とは<textarea name="tweet" rows="20" cols="80"></textarea>このtextareaのname属性の事ですね。
textareaに記述した投稿がtweetという名前で受け取れますよという意味ですね。今度はv変数に投稿内容が渡せているのでこれをDBに書きこみたいです。なのでデータを書き込むSQL文を記述してあげます。
cmd :=INSERT INTO tweets(tweet)VALUES(?)
それがこれですね。
tweetsテーブルのtweetカラムに値を入れてね、というSQL文です。DbConnection.Exec(cmd, v)で実際にデータを書きこむ事が出来ます。
Execの第一引数にSQL文を、そして第二引数に書き込む値を渡している訳です。
そして最後のhttp.Redirect(w, r, "/", http.StatusFound)ですが、この関数処理が終わった後に"/"パス(つまり元のページ)に戻ってきてねという関数です。
この処理を書かないとtweet.htmlの画面に飛んでしまうのですが、そんなもの作成していないので真っ白の世界に行っちゃいます。
では実際に投稿フォームから投稿してみたいと思います。
今回は Go と投稿してみます、ボタンを押すと...
またこのページがリロードされましたね。
では本当にDBへ保存されているのか確認してみます。
ターミナルでselect * from tweets;と打ち込んでください。これでtweetsテーブルの中身を確認出来ます。
1|Go と保存されているのが確認出来ました。
左のidはprimary keyを設定しているので勝手に数字が振られてます。次はこのデータを画面に表示してみます
まずはstructを作成します。
package main import ( "database/sql" _ "github.com/mattn/go-sqlite3" "log" "net/http" "text/template" ) type Tweets struct { Id int Tweet string }適当にこの辺りに作成します。
structとは他の言語でいうクラスのようなものです(間違ってたらごめんなさい)
DBから引っ張ってきたデータを一旦入れておく入れ物みたいなイメージですかね!
DBのデータをHTMLに渡したい時は一旦このstructに入れてからじゃないと渡せません。次にindexHandleを編集します
func indexHandler(w http.ResponseWriter, r *http.Request) { DbConnection, _ := sql.Open("sqlite3", "./example.sql") defer DbConnection.Close() cmd := `SELECT * FROM tweets` rows, err := DbConnection.Query(cmd) if err != nil { log.Fatalln(err) } defer rows.Close() var body []Tweets for rows.Next() { var b Tweets err := rows.Scan(&b.Id, &b.Tweet) if err != nil { log.Fatalln(err) } body = append(body, b) } t, err := template.ParseFiles("views/index.html") if err != nil { log.Fatalln(err) } t.Execute(w, body) }先ほどと同じくDBをOpenしてdeferでCloseしまして、tweetsテーブルを探すSQL文を記述してcmd変数に入れてやってるだけですね。
投稿の時と違うのは、DbConnection.ExecではなくDbConnection.Queryになっている事です。Queryメソッドでデータを引っ張り出せるんですね。
今回はrows変数にそのデータを代入してあげます。ちなみこれもdeferでcloseしてあげる必要があります。その後スライスのTweets型のbody変数を定義してます。
rows.Next()でなにをしてるかと言いますと、DBから引っ張って来たrows変数を一つづつ b変数に代入して行って、それをスライスのbodyへ追加してあげてる訳です。(これもお決まり文として覚えればいいです)
これでtweetsテーブルのデータをTweets structへ変換する事が出来ました。
ではこれをHTMLヘ渡します。Executeの第二引数でデータを渡せるんでしたね。次にHTMLも編集します
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8" /> <title>Gopher Tweets</title> <link rel="stylesheet" type="text/css" href="../resources/css/view.css"> </head> <body> <main> <header>Gopher Tweets</header> <div class="tweet_view"> {{range .}} <form action="/tweet_delete/" method="DELETE"> <p class="tweet_message">{{.Tweet}} <button type="submit" value={{.Id}} name="tweet_delete" class="tweet_delete_button">-</button> </p> </form> {{end}} </div> <form action="/tweet/" method="POST" class="tweet_form"> <textarea name="tweet" rows="20" cols="80"></textarea> <input type="submit" value="speak"> </form> </main> </body> </html>{{}}←これでデータを受け取る事が出来ます。詳しくはこの人の記事で
ではまた再起動して画面を見てみましょう
先ほど投稿した Go という投稿が表示されてますね、今度は Hello と投稿してみます。
ちゃんと投稿する事が出来ました。次は投稿を削除してみます
今投稿の横に青いボタンがありますが、今は押しても何も変化しません。このボタンを押すと投稿が削除できるようにしてみましょう。
{{range .}} <form action="/tweet_delete/" method="DELETE"> <p class="tweet_message">{{.Tweet}} <button type="submit" value={{.Id}} name="tweet_delete" class="tweet_delete_button">-</button> </p> </form> {{end}}これが投稿表示部分のhtmlですが、このボタンをおすと
/tweet_delete/ パスが走り
tweet_deleteというnameで
{{.Id}} という値が送られる訳ですね。なので先ほどと同様にこのパスのHandlefuncをmain関数に記述します。
func deleteTweet(w http.ResponseWriter, r *http.Request) { DbConnection, _ := sql.Open("sqlite3", "./example.sql") defer DbConnection.Close() cmd := "DELETE FROM Tweets WHERE id = ?" i := r.FormValue("tweet_delete") var I int I, _ = strconv.Atoi(i) DbConnection.Exec(cmd, I) http.Redirect(w, r, "/", http.StatusFound) } func main() { http.Handle("/resources/", http.StripPrefix("/resources/", http.FileServer(http.Dir("resources/")))) http.HandleFunc("/", indexHandler) http.HandleFunc("/tweet_delete/", deleteTweet) http.HandleFunc("/tweet/", getPostTweet) http.ListenAndServe(":8080", nil) }はい、先ほどの投稿の時とほとんど同じですね、削除するSQL文と削除したいIDを渡して実行しているだけです。
ただ送られて来たIdはstring型で送られてくるのでInt型に変換する為にstrconv.Atoi(i)を使用しているだけです。
ではまた再起動してから、一番上のGoという投稿のボタンをクリックしてみます。
ちゃんと消えました!このままでは寂しいのでマスコットキャラのゴーファくんを表示してあげて終了です。
resourcesディレクトリ内にimagesディレクトリを作成し、その中に画像を入れてCSSにてbackground-imageで指定するだけですね。
- 投稿日:2020-01-16T18:14:46+09:00
Goで超簡易版Twitterを作ってみました(初心者向け)
はじめまして
最近Goの勉強を始めたばかりで何か開発してみようと思い、簡単なアプリを開発しました。
twitterもやってます@khm_itac見た目
出来る事
・フォームから投稿する事が出来ます
・投稿をデータベースに保存する事が出来ます
・保存されている投稿を表示してくれます
・投稿を消す事も出来ますはい、それだけです。
めちゃくちゃ低レベルですが、UIからサーバーにデータを送りDBへ保存、DBのデータをビューに表示するというのは全てのアプリの基本的な部分だと思いますので、これが出来るとようやくエンジニアレベル1達成です!
僕と同じようにこれからGoの勉強を始める方の少しでも役に立てればと思い記事を投稿しました。
僕も初心者ですので間違ってる部分や至らない点があるかと思いますがご了承ください。解説
Goの導入や基礎文法やVSCodeの使い方などは解説しません、僕より詳しい人がYouTube等で動画を出していますのでそちらで勉強した方がいいと思います(あとProgateとか)
Macでの開発ですのでwindowsの方はうまく動かない可能性もありますまずmain.goを作ります
メインディレクトリの直下でいいと思います。
この画面上でF5を押すとスタートさせる事が出来ます、その後http://localhost:8080
にアクセスすると
この画面になります
これでサーバーを建てられました。
Goでは起動させると func main() が動き出します。
なのでmain関数の中に書いてあるhttp.ListenAndServe(":8080", nil)が起動するという事ですね。
8080という数字はお好みで大丈夫です、他の数字でも動きます。
ちなみにimport "net/http"も書いてあげないと動きません。
Goでは使用したい機能(パッケージ)をimportしてあげる必要があります。
ではサーバーを起動する事が出来たのでHTMLを表示させたいと思います。メインディレクトリの下にviewsディレクトリを作成し、その中にindex.htmlを作ります
そして適当に文字を書いてみます
そしてこのHTMLファイルをmain.goから呼び出す記述を書いてみます
この記述後に再起動してみて、http://localhost:8080
をリロードしてみてください
このようにHTMLの文字が画面に表示されると成功です解説
http.HandleFuncは第一引数でURLのパスを指定し、第二引数で動かしたい関数を指定します。
今回第一引数は "/" なのでルートパスがリクエストされた時にindexHandlerという関数を呼び出しているという事です。
次にindexHandlerの中身ですが、10行目でtemplate.ParseFilesにHTMLファイルを指定しその中身を tとerrという変数に代入しています。11行目で、もしtemplate.ParseFilesに渡したHTMLが存在しなければerr変数にエラーが代入されエラーを出してくれます。
この if err != nil { log.Fatalln(err) }という記述はGoではめちゃめちゃ出てきますので、とりあえずお決まりの文という認識でいいと思います。(エラーが出た時に内容を教えてくれるよーというものです)
で、HTMLが存在すればその t変数に対してExecuteメソッドを使用するとHTMLを表示する事が出来ます。
Executeに渡している引数ですが、第一引数にResponseWriterを渡しています(これもお決まり文という認識で大丈夫です)第二引数にmain.goからHTMLに渡すデータを指定してあげる事が出来ます。
今回は何も渡さないのでnilにしておきます。とりあえずHTMLを完成させます、こんな感じにしました
次にCSSファイルを作成します。
メインディレクトリの下にresourcesディレクトリを作り、さらにcssディレクトリを作ります。その中にview.cssを作ります。こんな感じですheader{ height:50px; line-height: 50px; border:1px solid lightgray; text-align: center; color: rgb(142, 168, 184); background-color: rgb(249, 252, 252); } .tweet_view{ float: left; height: 100vh; width: 50vw; background-color: rgb(235, 243, 243); padding-top: 20px; } .tweet_message{ background-color: white; color: gray; font-size: 30px; margin: 0 20px 15px; padding: 10px; position: relative; border-radius: 15px; } button.tweet_delete_button{ background-color: rgb(130, 168, 238); border-style: none; height: 20px; width: 20px; position: absolute; right: 10px; bottom: 17px; color: rgb(255, 255, 255); line-height: 5px; padding-right: 18px; cursor: pointer; } button:focus { outline:0; } .tweet_form{ height: 100vh; width: 100vw; } textarea{ background-color: rgb(235, 243, 243); width: 450px; height: 250px; margin: 50px 0 0 120px; } input{ width: 200px; height: 50px; background-color: rgb(130, 168, 238); color: white; position: absolute; left: 68vw; top: 400px }あれ、CSSが効いてませんね。
Goでは静的ファイルを読み出す時はmain関数内で宣言をしてあげないと行けないらしいです。
ではmain.goにcssを呼び出す記述を書きますfunc main() { http.Handle("/resources/", http.StripPrefix("/resources/", http.FileServer(http.Dir("resources/")))) http.HandleFunc("/", indexHandler) http.ListenAndServe(":8080", nil) }一番上にhttp.Handleという関数を追加しました。
正直自分はコードの細部は理解出来ていなんですが、とりあえずresourcesディレクトリを呼び出してくれる関数という事らしいです(なのでコピペで大丈夫です)
この記述後もう一度リロードしてみます(cm + sf + R でスーパーリロードした方がいいかも)
今度はちゃんとCSSが効きました!次はデータベースを導入します
SQLite3というDBを導入します。導入用記事はまた今度書きます(ググればすぐ出来ます)
ではDBインストール出来たのでmain.goにインポートします
package main import ( "database/sql" _ "github.com/mattn/go-sqlite3" "log" "net/http" "text/template" ) var DbConnection *sql.DB func indexHandler(w http.ResponseWriter, r *http.Request) { t, err := template.ParseFiles("views/index.html") if err != nil { log.Fatalln(err) } t.Execute(w, nil) } func main() { http.Handle("/resources/", http.StripPrefix("/resources/", http.FileServer(http.Dir("resources/")))) http.HandleFunc("/", indexHandler) http.ListenAndServe(":8080", nil) }上記のようにimportした後、var DbConnection *sql.DBでDBを定義してあげます。
これでDbConnectionという名前でDBにアクセス出来ます。
では実際にDBにデータベースを作ってみます。今回はターミナルからSQLコマンドを打ちます。メインディレクトリでsqlite3 example.sqlと打ち込んでください。
こんな風になると思います、次にCREATE TABLE tweets (id INTEGER PRIMARY KEY,tweet STRING);と打ち込みます。
これはtweetsという名前のテーブルを作成し、idとtweetというカラムを作成するというコマンドです。
tweetは文字型なのでstring、idはintegerにします。
idのprimary keyとは、何もしなければ自動的に数字を割り振ってくれるというものです。これでtweetsテーブルは作成出来ました、一応確認してみます。ターミナルで.tabelと打ち込んでください。
tweetsテーブルが作成されているのを確認出来ました。
ではフォームに入力した値を実際にDBに入れてみます。
index.htmlの14行目に注目してください。
<form action="/tweet/" method="POST" class="tweet_form"> <textarea name="tweet" rows="20" cols="80"></textarea> <input type="submit" value="speak"> </form>actionに"/tweet/"を指定してるので、ボタンを押すとhttp://localhost:8080/tweet/
というURLが走ります、なのでmain関数にそのパスが通った時に呼び出される関数を記述すればいいという訳です。package main import ( "database/sql" _ "github.com/mattn/go-sqlite3" "log" "net/http" "text/template" ) var DbConnection *sql.DB func indexHandler(w http.ResponseWriter, r *http.Request) { t, err := template.ParseFiles("views/index.html") if err != nil { log.Fatalln(err) } t.Execute(w, nil) } func getPostTweet(w http.ResponseWriter, r *http.Request) { DbConnection, _ := sql.Open("sqlite3", "./example.sql") defer DbConnection.Close() v := r.FormValue("tweet") cmd := `INSERT INTO tweets(tweet)VALUES(?)` DbConnection.Exec(cmd, v) http.Redirect(w, r, "/", http.StatusFound) } func main() { http.Handle("/resources/", http.StripPrefix("/resources/", http.FileServer(http.Dir("resources/")))) http.HandleFunc("/", indexHandler) http.HandleFunc("/tweet/", getPostTweet) http.ListenAndServe(":8080", nil) }ますmain関数内にhttp.HandleFunc("/tweet/", getPostTweet)という関数を記述します。
これは先ほど説明しましたね、これで"/tweet/"というパスが通ればgetPostTweetという関数が呼び出されます。
ではgetPostTweet関数の中身な解説をします。
DbConnection, _ := sql.Open("sqlite3", "./example.sql")でデータベースを開いて読み込みます。
defer DbConnection.Close()で開いたら閉じましょう。
これもお決まり文という認識で大丈夫だと思います、openしたらdeferでcloseです
ビューから送信したデータはr変数(http.Request)として送られてくるので
v := r.FormValue("tweet")としてv変数に代入してあげます。
このFormValueの中の"tweet"とは<textarea name="tweet" rows="20" cols="80"></textarea>このtextareaのname属性の事ですね。
textareaに記述した投稿がtweetという名前で受け取れますよという意味ですね。今度はv変数に投稿内容が渡せているのでこれをDBに書きこみたいです。なのでデータを書き込むSQL文を記述してあげます。
cmd :=INSERT INTO tweets(tweet)VALUES(?)
それがこれですね。
tweetsテーブルのtweetカラムに値を入れてね、というSQL文です。DbConnection.Exec(cmd, v)で実際にデータを書きこむ事が出来ます。
Execの第一引数にSQL文を、そして第二引数に書き込む値を渡している訳です。
そして最後のhttp.Redirect(w, r, "/", http.StatusFound)ですが、この関数処理が終わった後に"/"パス(つまり元のページ)に戻ってきてねという関数です。
この処理を書かないとtweet.htmlの画面に飛んでしまうのですが、そんなもの作成していないので真っ白の世界に行っちゃいます。
では実際に投稿フォームから投稿してみたいと思います。
今回は Go と投稿してみます、ボタンを押すと...
またこのページがリロードされましたね。
では本当にDBへ保存されているのか確認してみます。
ターミナルでselect * from tweets;と打ち込んでください。これでtweetsテーブルの中身を確認出来ます。
1|Go と保存されているのが確認出来ました。
左のidはprimary keyを設定しているので勝手に数字が振られてます。次はこのデータを画面に表示してみます
まずはstructを作成します。
package main import ( "database/sql" _ "github.com/mattn/go-sqlite3" "log" "net/http" "text/template" ) type Tweets struct { Id int Tweet string }適当にこの辺りに作成します。
structとは他の言語でいうクラスのようなものです(間違ってたらごめんなさい)
DBから引っ張ってきたデータを一旦入れておく入れ物みたいなイメージですかね!
DBのデータをHTMLに渡したい時は一旦このstructに入れてからじゃないと渡せません。次にindexHandleを編集します
func indexHandler(w http.ResponseWriter, r *http.Request) { DbConnection, _ := sql.Open("sqlite3", "./example.sql") defer DbConnection.Close() cmd := `SELECT * FROM tweets` rows, err := DbConnection.Query(cmd) if err != nil { log.Fatalln(err) } defer rows.Close() var body []Tweets for rows.Next() { var b Tweets err := rows.Scan(&b.Id, &b.Tweet) if err != nil { log.Fatalln(err) } body = append(body, b) } t, err := template.ParseFiles("views/index.html") if err != nil { log.Fatalln(err) } t.Execute(w, body) }先ほどと同じくDBをOpenしてdeferでCloseしまして、tweetsテーブルを探すSQL文を記述してcmd変数に入れてやってるだけですね。
投稿の時と違うのは、DbConnection.ExecではなくDbConnection.Queryになっている事です。Queryメソッドでデータを引っ張り出せるんですね。
今回はrows変数にそのデータを代入してあげます。ちなみこれもdeferでcloseしてあげる必要があります。その後スライスのTweets型のbody変数を定義してます。
rows.Next()でなにをしてるかと言いますと、DBから引っ張って来たrows変数を一つづつ b変数に代入して行って、それをスライスのbodyへ追加してあげてる訳です。(これもお決まり文として覚えればいいです)
これでtweetsテーブルのデータをTweets structへ変換する事が出来ました。
ではこれをHTMLヘ渡します。Executeの第二引数でデータを渡せるんでしたね。次にHTMLも編集します
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8" /> <title>Gopher Tweets</title> <link rel="stylesheet" type="text/css" href="../resources/css/view.css"> </head> <body> <main> <header>Gopher Tweets</header> <div class="tweet_view"> {{range .}} <form action="/tweet_delete/" method="DELETE"> <p class="tweet_message">{{.Tweet}} <button type="submit" value={{.Id}} name="tweet_delete" class="tweet_delete_button">-</button> </p> </form> {{end}} </div> <form action="/tweet/" method="POST" class="tweet_form"> <textarea name="tweet" rows="20" cols="80"></textarea> <input type="submit" value="speak"> </form> </main> </body> </html>{{}}←これでデータを受け取る事が出来ます。詳しくはこの人の記事で
ではまた再起動して画面を見てみましょう
先ほど投稿した Go という投稿が表示されてますね、今度は Hello と投稿してみます。
ちゃんと投稿する事が出来ました。次は投稿を削除してみます
今投稿の横に青いボタンがありますが、今は押しても何も変化しません。このボタンを押すと投稿が削除できるようにしてみましょう。
{{range .}} <form action="/tweet_delete/" method="DELETE"> <p class="tweet_message">{{.Tweet}} <button type="submit" value={{.Id}} name="tweet_delete" class="tweet_delete_button">-</button> </p> </form> {{end}}これが投稿表示部分のhtmlですが、このボタンをおすと
/tweet_delete/ パスが走り
tweet_deleteというnameで
{{.Id}} という値が送られる訳ですね。なので先ほどと同様にこのパスのHandlefuncをmain関数に記述します。
func deleteTweet(w http.ResponseWriter, r *http.Request) { DbConnection, _ := sql.Open("sqlite3", "./example.sql") defer DbConnection.Close() cmd := "DELETE FROM Tweets WHERE id = ?" i := r.FormValue("tweet_delete") var I int I, _ = strconv.Atoi(i) DbConnection.Exec(cmd, I) http.Redirect(w, r, "/", http.StatusFound) } func main() { http.Handle("/resources/", http.StripPrefix("/resources/", http.FileServer(http.Dir("resources/")))) http.HandleFunc("/", indexHandler) http.HandleFunc("/tweet_delete/", deleteTweet) http.HandleFunc("/tweet/", getPostTweet) http.ListenAndServe(":8080", nil) }はい、先ほどの投稿の時とほとんど同じですね、削除するSQL文と削除したいIDを渡して実行しているだけです。
ただ送られて来たIdはstring型で送られてくるのでInt型に変換する為にstrconv.Atoi(i)を使用しているだけです。
ではまた再起動してから、一番上のGoという投稿のボタンをクリックしてみます。
ちゃんと消えました!このままでは寂しいのでマスコットキャラのゴーファくんを表示してあげて終了です。
resourcesディレクトリ内にimagesディレクトリを作成し、その中に画像を入れてCSSにてbackground-imageで指定するだけですね。
- 投稿日:2020-01-16T15:05:45+09:00
gRPCメモ
自身のメモとしてgRPCをまとめてみました。
gRPCとは
protocol buffersをMessage interchange format(メッセージのI/Oに使うための形式)として使えるリモートプロシージャコールシステムです。
protocol buffersとは
IDL:Interface Definition Language (インタフェース定義言語)を用いたファイルフォーマットです。
gRPCで何ができるのか
クライアントが別のマシンにあるメソッドをまるでローカルにあるかのように使えるようになります。
下の図のように様々なプログラミング言語に対して実装できる。雑に言うと
今までjsonなどを介してAPIを叩いていたようなところを、もっと確固たる定義をもった上でより高パフォーマンスでやりとりができる…という感じでしょうか。
protocol bufferの定義の仕方
やりとりするmessageの構成は、以下のようにname-valueでfieldを定義します。
person.protomessage Person { string name = 1; int32 id = 2; bool has_ponycopter = 3; }gRPC Serverの定義はrpcメソッドを使って行います。
greeter.proto// The greeter service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings message HelloReply { string message = 1; }protoファイルののコンパイル
上のgreeter.protoをGo言語ようにコンパイルする場合は以下のようなコマンドを叩きます。
protoc --go_out=plugins=grpc:./ ./greeter.proto
このコンパイルでClient側のメソッドもServer側のメソッドも作成されます。
もし、何かのディレクトリの中にあるすべての.protoをコンパイルしたい場合は、以下のような感じでワイルドカードを使うこともできます。
protoc --go_out=plugins=grpc:./ ./*.proto
もし、どこかのディレクトリに入れたいという場合は、grpc:後のところを書き換えます。
以下の場合はprotoディレクトリの中にコンパイルされたデータが書き出されます。
protoc --go_out=plugins=grpc:proto ./*.proto
もし、どこかのディレクトリからどこかのディレクトリに書き出したい場合は、以下のような書き方になります。
protoc -I protobuf --go_out=plugins=grpc:proto protobuf/*.proto
-I が無いとprotoディレクトリ配下にprotbufディレクトリが作成され、そのなかにコンパイルされたデータが書き出されます。
-I はimportの基点となる場所をしてしてやるオプションと考えてもらえば良いかもしれません。
--go_out=plugins=grpc:
と書いているのですが、protocol buffer自体は別にgRPCのためだけのものではないので、このようにgRPCのための書き出しですよーというオプションを書いておく必要があります。Ruby用にコンパイルしたい場合は --go_outを書き換えます。(複数書けば複数の言語のファイルを同時に書き出せます。)
protoc -I protobuf --ruby_out=plugins=grpc:proto protobuf/*.proto
gRPCサーバーのリクエストとレスポンスの種類
種類は4つあります。
Unary RPC
一番シンプルな方法で、単一のメッセージのリクエストに対して、単一のメッセージをレスポンスで返す方法です。
greeter.protorpc SayHello(HelloRequest) returns (HelloResponse) { }Server streaming RPC
単一のメッセージのリクエストに対して、複数のメッセージをレスポンスで返す方法です。
例えばですが、何かのデータの一覧が欲しいなどと言うときに、この方法を使えたりします。greeter.protorpc LotsOfReplies(HelloRequest) returns (stream HelloResponse) { }Client streaming RPC
複数のメッセージのリクエストを受けた上で、単一のメッセージのレスポンスを返します。
greeter.proto
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}
Bidirectional streaming RPC
これはリクエストもレスポンスも複数のメッセージをレスポンスを扱う方法ですが、
すべてのメッセージを待って、すべてのメッセージを書き出して返すことも、一つメッセージを受け取る毎に、メッセージを書き出していき、最後にまとめてメッセージを返すことも、サーバー側の実装次第で返ることができます。greeter.protorpc BidiHello(stream HelloRequest) returns (stream HelloResponse) { }サーバー側の実装
主な処理は以下のような感じです。
main.gopackage main import ( pb "github.com/{リポジトリ名}/{デフォルトならproto}" "flag" "net" "log" "sync" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/testdata" ) var ( tls = flag.Bool("tls", false, "Connection uses TLS if true, else plain TCP") certFile = flag.String("cert_file", "", "The TLS cert file") keyFile = flag.String("key_file", "", "The TLS key file") jsonDBFile = flag.String("json_db_file", "", "A json file containing a list of features") port = flag.Int("port", 10000, "The server port") ) func main() { flag.Parse() // 引数を入れて実行したい時用にflagを使ってます。 // hostとportを設定します。 lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port)) if err != nil { log.Fatalf("failed to listen: %v", err) } // 必要であればcredentialの設定をします。 var opts []grpc.ServerOption if *tls { if *certFile == "" { *certFile = testdata.Path("server1.pem") } if *keyFile == "" { *keyFile = testdata.Path("server1.key") } creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile) if err != nil { log.Fatalf("Failed to generate credentials %v", err) } opts = []grpc.ServerOption{grpc.Creds(creds)} } // gRPCのサーバーを初期化 grpcServer := grpc.NewServer(opts...) // protocでコンパイルされたコードからサーバーを登録するメソッドを実行 pb.RegisterGreeterServer(grpcServer, newServer()) // サーバーを起動 grpcServer.Serve(lis) }上の記述で書いていないのですがnewServer()という関数が重要なのですが、Register*Serverの第二引数にはprotoのServer内で定義したメソッドを持ったinterfaceを入れる形になっています。
その為に以下のような形の定義もmainに入れておきます。(例えば、Server streaming RPCだけ定義している場合)
main.gotype greeterServer struct {} // nilかerrを返せばstreamingが終了します。 func(s *greeterServer) LotsOfReplies(req *pb.LotsOfRepliesRequest, stream pb.Greeter_LotsOfRepliesServer) error { hs := []pb.HelloReply{ pb.HelloReply{ Name: "ohayo" }, pb.HelloReply{ Name: "konnichiwa" }, pb.HelloReply{ Name: "konbanwa" }, } for _, h := range hs { if err := stream.Send(&h); err != nil { return err } } return nil } func newServer() *greeterServer { s := &greeterServer{} return s }その他
repeated
protoの定義の中でmessageの中に配列をいれたい場合はRepeatedが使えます。
greeter.protomessage HelloResponse { repeated string name = 1; }こうするとnameの配列を返す事ができます。
複数のmessageを返したい場合はstreamでmessageの中で処理できるならrepeatedというところでしょうか。oneof
messageに入るのがstringの可能性もあるし、int32の可能性もある…なんて場合に使えます。
greeter.protomessage HelloResponse { oneof name { string text = 1; int32 id = 2; } }リファレンス
- 投稿日:2020-01-16T14:02:01+09:00
フロントエンジニア(仮)がGO言語で簡単なスクレイピング処理を実装するまで
はじめまして。kenalog210と申します。
ある事情によりGo言語でのスクレイピング処理方法を色々調べてたんですが、無事お流れとなりました。
もったいないのでQiitaに上げさせていただきます。
今回は下記のサイトをサンプルとして使用させて頂きました。
https://wiki.hackerspaces.org/
使用したフレームワーク
Colly
http://go-colly.org/
https://github.com/gocolly/collyスターが1万近くついてるスゴイやつです。
リポジトリにサンプルがいっぱいあるので、基本的なケースなら自分の応用したいパターンに合わせて
サンプルをちょっといじくり回せば使えそうです。
この記事もサンプルをちょっと応用しただけのものです。
やりたかったこと
・マッピング情報を渡したらうまいことインスタンス化してくれるようなものを組みたい
・インスタンス化出来たら、JSON文字列化してコンソールで出力
・構造体とのマッピング用スクリプトはめんどいので組みたくない構造体を定義
まずは型定義を用意してやります。下記のようにフィールド定義と紐付けたい要素を簡単に定義できます。
ちなみに、Go言語は先頭文字が小文字か大文字かでPrivateかPublicかを定義するようなので
フィールド名の先頭を大文字にしてやらないとちゃんとパースしてくれません。(1敗)/* パース処理対象の構造体 型名指定の後に、JQueryと同じ要領でフィールドと紐付けたい要素や属性をセレクタで指定 */ type hackerspacesWiki struct { // 注釈 Note string `selector:"pre"` // 紹介文 Description []string `selector:"p"` // 右側のサムネ画像一覧 ThumbnailImageUrls []string `selector:".thumbimage" attr:"src"` }クローラー、パース対象の構造体のインスタンスを生成
下記のようにインスタンスを生成しました。クローリング
// クローラーインスタンスを生成 c := colly.NewCollector( // クローリングを許可するドメインを設定 colly.AllowedDomains("wiki.hackerspaces.org"), ) // パース対象のインスタンスを生成 parseTargetStruct := &hackerspacesWiki{}クローラーの各イベント処理を定義
今回はOnHTMLイベントとOnRequestイベントのみ使用しました。
/* セレクタで指定した要素が存在した場合の処理 第1引数にJQueryと同じ要領でセレクタを指定可能 めんどくさいのでルート要素を指定して一括パースをかける */ c.OnHTML(".mw-parser-output", func(e *colly.HTMLElement) { /* OnHTMLで指定したセレクタに適合した要素が引数で入ってくるため Unmarshalメソッドを使用してパース対象の型のインスタンスを渡してやれば 構造体に指定したセレクタに従って自動的にマッピングしてくれる */ e.Unmarshal(parseTargetStruct) }) // リクエスト実行時(Visitメソッド実行時)に実行される処理 c.OnRequest(func(r *colly.Request) { fmt.Println("Visiting", r.URL.String()) })スクレイピング処理開始
Visitメソッドを実行すると OnRequest → OnHTML の順で処理が流れます。
// スクレイピング処理を開始 c.Visit("https://wiki.hackerspaces.org/")JSON文字列化 → コンソール出力
// マッピング後のインスタンスをJSON文字列化 bs, err := json.Marshal(parseTargetStruct) if err != nil { // 申し訳程度のエラー処理 fmt.Println("JSON Parse Error") os.Exit(1) } // コンソールに出力 fmt.Println(string(bs))実行結果
こんな形でコンソールに出力されました。
対象要素が複数存在する場合は、構造体の方で配列として定義してやらないとうまく取得してくれないです。Visiting https://wiki.hackerspaces.org/ { "Note": "Note: We are currently updating the wiki. If you notice something you want to tell us, join #hackerspaces on Freenode IRC network.", "Description": [ "Hackerspaces are community-operated physical places, where people can meet and work on their projects.", "This website is for Anyone and Everyone who wants to share their hackerspace stories and questions with the global hackerspaces community.", "Have some free time? Help with the Hackerspace Census 2019 and find out if your local hackerspaces listed here are still active!" ], "ThumbnailImageUrls": [ "/images/e/ec/Cbase07.jpg", "/images/0/0f/NYCR2.JPG", "/images/7/73/Hackerspace_charlotte.jpg" ] }ソース全文
package main import ( "encoding/json" "fmt" "os" "github.com/gocolly/colly/v2" ) /* パース処理対象の構造体 型名指定の後に、JQueryと同じ要領でフィールドと紐付けたい要素や属性をセレクタで指定 */ type hackerspacesWiki struct { // 注釈 Note string `selector:"pre"` // 紹介文 Description []string `selector:"p"` // 右側のサムネ画像一覧 ThumbnailImageUrls []string `selector:".thumbimage" attr:"src"` } func main() { // クローラーインスタンスを生成 c := colly.NewCollector( // クローリングを許可するドメインを設定 colly.AllowedDomains("wiki.hackerspaces.org"), ) // パース対象のインスタンスを生成 parseTargetStruct := &hackerspacesWiki{} /* セレクタで指定した要素が存在した場合の処理 第1引数にJQueryと同じ要領でセレクタを指定可能 めんどくさいのでルート要素を指定して一括パースをかける */ c.OnHTML(".mw-parser-output", func(e *colly.HTMLElement) { /* OnHTMLで指定したセレクタに適合した要素が引数で入ってくるため Unmarshalメソッドを使用してパース対象の型のインスタンスを渡してやれば 構造体に指定したセレクタに従って自動的にマッピングしてくれる */ e.Unmarshal(parseTargetStruct) }) // リクエスト実行時(Visitメソッド実行時)に実行される処理 c.OnRequest(func(r *colly.Request) { fmt.Println("Visiting", r.URL.String()) }) // スクレイピング処理をスタート c.Visit("https://wiki.hackerspaces.org/") // マッピング後のインスタンスをJSON文字列化 bs, err := json.Marshal(parseTargetStruct) if err != nil { // 申し訳程度のエラー処理 fmt.Println("JSON Parse Error") os.Exit(1) } // コンソールに出力 fmt.Println(string(bs)) }感想
Collyが使いやすくてスゴイ。