- 投稿日:2020-01-01T22:30:36+09:00
眺めて覚えるGo言語 その8 PythonからGO言語を呼ぶ1(引数文字列)
pythonからGo サブルーチンへの引数 文字列
byte列をポインターで渡す。
pcgo1.pyfrom ctypes import * lib = cdll.LoadLibrary("./golib.so") a=lib.fun1(c_char_p(b"hello world"))golib.gopackage main import "C" import "fmt" //export fun1 func fun1(a *C.char) int { fmt.Println(C.GoString(a)) return 0 } func main() {} コンパイルと実行 >go build -o golib.so -buildmode=c-shared golib.go >python pcgo1.py hello world
- c_char_p(b"hello world")は、バイト列ポインター扱い
- C.GoString(a)は、受け取ったバイト列ポインターをGO stringへ
文字列の受け渡し
基本的に文字列の受け渡しは、バイト列で行う。
py2go.pyfrom ctypes import * #パラメータの準備 class go_string(Structure): _fields_ = [("p", c_char_p),("n", c_int)] #GO言語用string変換 def GoString(s): u=s.encode('utf-8') return go_string(c_char_p(u), len(u)) lib = cdll.LoadLibrary("./golib.so") lib.fun1.restype=c_char_p a=lib.fun1(c_char_p("こんにちは".encode('utf-8'))) lib.fun2.restype=c_char_p b=lib.fun2(GoString("こんにちは")) print("a=",a.decode('utf-8')) print("b=",b.decode('utf-8'))
- encode/decodeは、それぞれバイト列変換です。
golib.gopackage main import "C" import "fmt" //export fun1 func fun1(a *C.char)*C.char{ fmt.Println("fun1",C.GoString(a)+" 日本") return C.CString(C.GoString(a)+" 日本") } //export fun2 func fun2( a string)*C.char{ fmt.Println("fun2",a+" 日本") return C.CString(a+" 日本") } func main() {} コンパイルと実行結果 >go build -o golib.so -buildmode=c-shared golib.go >python py2go.py fun1 こんにちは 日本 fun2 こんにちは 日本 a= こんにちは 日本 b= こんにちは 日本
- fun1は、C言語ポインターで受け取りC言語ポインターで返す。
- fun2は、Go言語stringで受け取りC言語ポインターで返す。
- 投稿日:2020-01-01T22:00:47+09:00
アプリ音痴の両親のためにLINEで共有した写真をデジタルフォトフレーム風にするアプリを作った話
この記事はぷりぷりあぷりけーしょんず Advent Calendar 2019の22日目の記事です。
はじめに
大遅刻スミマセン。、
まさかの年越してからアドベントカレンダーに登録するという最早アドベントカレンダーとはなんぞやな状況です。
師走の風に流され、忙しさのあまり年内に完成させ記事にする予定のアプリが新年初コミットとともに完成に至る形になりました。さて、年を越したと同時にエンジニアに転職して丸3年が経ちました。
1年半ほど前は丸3年経つまでにサーバー・ネットワークの設定からアプリの開発、何かしらのサービスの一般公開をしたい
というのを目標に業務とは別に個人学習、開発を行なっていました。
結果的にサーバー・ネットワークの設定
,何かしらのサービスの一般公開
というのは叶うことはなかったわけですが個人的に家族のための使うことができる
レベルのアプリを作成することが叶いました。作ったもの
私の両親はまあアプリ音痴でインスタグラムはもちろんのこと、LINEがギリギリ使えるレベルです。その割に父親はガジェット好きでiPadはいっちょ前に4台持っています。
いっちょ前にリビングに立て掛けてあるiPadを有効活用してもらいたいと思いLINEだけで完結するデジタルフォトフレームアプリfamiphoto
(famip○rtからパクったとかは言わない)を作成しました。アーキテクチャ
ざっくり上記のようになっており、アプリのフローとしては以下の通りです。
- LINEグループにMessagingAPIで作成したbotを招待
UUIDを作成し、グループIDをキーにDynamoDBに保存、S3にUUIDのディレクトリを作成- 今まで通り画像を共有(ここ重要)
グループIDからUUIDを取得し、画像をUUIDのディレクトリに保存famiphoto url
とメッセージを送るとデジタルフォトフレームのurlを返す
urlはhttps://***firebase.app/family/{uuid}
- urlにアクセスするとデジタルフォトフレームとして起動しリビングに飾る
裏では{uuid}
をリクエストパラメータにAPIGatewayからLambdaを起動し、S3の画像URL一覧を取得してる- PWAなのでホームに落としてあげれば、普通にiPad使いたいときは普通に使ってもらい、飾っておくときはアプリを起動して置くだけで良い
使用している技術は
- AWSLambda Runtime-Go
- DynamoDB
- S3
- Firebase hosting
- Vue.js
- PWAAWS側のインフラ周りはCDKで管理しておりTypescriptで記述しています。
実装
実装の面で特別記述するような話はないのですが、初めて挑戦したような箇所もありそれぞれでハマった所、苦労した所を自分へのメモとして残しておきます。
もし参考になれば幸いです。CDK
APIGateway, Lambdaを使ったCDKは何度か構築したことがありますが今回初めて
クエリパラメータ
使うことになりましたstack.ts// API Gateway const api = new apigateway.RestApi(this, `api`, { restApiName: `api`, }) // Lambda const lambdaHandler = new lambda.Function(this, `api`, { code: lambda.Code.fromAsset("../bin"), handler: "main", runtime: lambda.Runtime.GO_1_X, }) const lambdaHandlerIntegration = new apigateway.LambdaIntegration(lambdaHandler, {proxy: true}) api.root.addResource(`api`) api.root.addMethod("GET", lambdaHandlerIntegration, { requestParameters: { "method.request.querystring.{parameter_name}": true } })一部を適当に抜粋していますが大体上記の通り設定していきます。
apigateway.root.addMethod
のoptionにrequestParameters
を定義し、"method.request.querystring.{parameter_name}"
でクエリパラメータを設定できます。
今回自分の場合はuuid
で処理を行っていくので"method.request.querystring.uuid"
となります。lambda側で取得する際は以下の通りです
lambda.gofunc handler(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { uuid = req.QueryStringParameters["uuid"]) .... }Lambda × Go
LambdaでのGoというより、Lambdaでのline-sdk-goの話ですがこれは以前の記事でも書かせていただきました。
APIGatewayProxyRequestをline-bot-sdk-go/linebot.Eventにパース
それ以外にもBodyを返す際にちょいと苦労(ドキュメント読めば良い話だったわけですが)したので何をしてるかそのまま転載します。
controller.gotype ContentsResponse struct { Body []string `json:"contents"` } func handler(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { contents, err := c.photo.GetContentURLs(req.QueryStringParameters["uuid"]) if err != nil { err = fmt.Errorf("Failed get contents: %v ", err) return errorResponse(err), err } body, err := json.Marshal(&ContentsResponse{ Body: contents, }) if err != nil { return events.APIGatewayProxyResponse{}, err } return events.APIGatewayProxyResponse{ StatusCode: 200, Body: string(body), IsBase64Encoded: false, }, nil }PWA
フロントの知識がほぼ皆無な私が今回PWAを選定した理由はアプリのフローであげている
今まで通り画像を共有(ここ重要)
してもらい普通にiPad使いたいときは普通に使ってもらい、飾っておくときはアプリを起動して置くだけで良い
ようにしたかったからです。PWAのアプリをFirebaseでhostingする記事はググればたくさん出てくるのでハマることは特にありませんでした。(ありがとうございます)
firebaseにhostingする際はbuild時に生成される
dist
を参照するように設定されているわけですがURLは先述の通りhttps://***firebase.app/family/{uuid}
となります。
これはuuidで画像のURLのリストを取るためなのですがアプリごとにuuidを切り替えられるようにしたいためPWAとして起動する際のstart_url
を動的に変更する必要があります。PWAのmanifest.jsonを動的に生成する
↑こちらの記事を参考にさせていただき、ほぼそのまま使わせていただきました。
しかしこれだとbuildするたびにdist/index.html
が毎回書き変わってしまい、<link rel=manifest>
にidを付与する必要ができるため下記のようなスクリプトを用意してbuild時はこれを使うようにしています。build.sh#bin/bash npm run build sed -i 's/href=\/manifest.json/id="my-manifest"/g' ./dist/index.html終わりに
個人開発というとどうしても途中で投げ出してしまったり、アイデアで出しで設計終わったらそれっきりだったり、設計拘ってたら飽きたりと色々続かないことが多いです。
今回の開発で思ったのは
- 初挑戦は1つ〜2つくらい(規模によると思う)
- 今までの知識のアップデートを中心に
- 時間のかかりそうな箇所は得意分野で
- わからなそうな領域はググってコピペでいいから終わらせる
というのがアプリを作る際のモチベーション維持の要になるように思います。
今回の私のアプリでいうと
- PWA
- Go, Vue.js
- CDK, Firebase
- 画像のフルスクリーン表示やスライドショーぽくするところ
といったところでしょうか。
エンジニア丸3年経過時の目標へのコミットは残念ながら届きませんでしたが全くの未経験から相談なしにエンジニアへの転職をしてやって見せてやることはできたかと思います。(父親は令和の世を生きる人間とは思えない程固い人間)
ここまで作ることができ、あとはやる気次第だと思うので2020年のアドベントカレンダーでは一般公開したサービスの記事を書けるように変わらずマイペースに個人開発をしていきたいと思います。
参考
Vue CLI 3 で PWA チュートリアル(Service Workers / Add to Home Screen / Push Notifications)
とっても簡単!Vueでフルスクリーン機能を実装する
- 投稿日:2020-01-01T18:40:22+09:00
gRPCの中身ってどんな?feat. Go
はじめに
Goが大好きで、一通り勉強した後何やろうかなと思い、gRPCに手を出してしまいました。。
と言うのも、WEB+DB PRESSのvol.114でkyashの特集をやっており、Goのアーキテクチャなどが紹介され、マイクロサービス 化を実践してみたくて、gRPCに興味が湧きました!そこで、よく分かるgRPCの良書があったのでそちらをまとめて行きたいと思います。
gRPCとは?
言わずもがなGoogleが開発しているRPC用のフレームワーク
RPCとは異なる場所にあるプログラムを呼び出すための仕組みを指します。
イメージとしては外部APIとHTTP通信でJSONを用いてやり取りする様な物です。
- HTTP/2の仕組みを用いた高速な通信
- Protocol Buffersを使ったRPCの定義とコード生成(レビューも出来ちゃう!)
もうここまで言うとマイクロサービスにドチャクソ向いてるのは分かりますよね!?
動かしてみる
色んな記事にある様に、チュートリアルしてもらうのが一番早く、確実です。
https://grpc.io/docs/quickstart/go/
ここではコードの書き方を何となく理解できればグッドです!
gRPCの仕組み
RPC
gRPCの単一の呼び出しはRPCと言います。
RPCには種類が4つあります。
- Unary RPC
- Server streaming RPC
- Client streaming RPC
- Bidirectional streaming RPC
これはよくあるgRPCの記事で言うそれぞれ上から
- 1リクエスト1レスポンス
- 1リクエストNレスポンス
- Nリクエスト1レスポンス
- NリクエストNレスポンス
と言うやつの正式名称ですね!
チャットとかの実装だとBidirectional streaming RPCを使います。
Channel (goのchannelとは別)
gRPCの裏側を支える代表的な要素がこのChannel(goではClientConn)
gRPCの通信を行うための経路の様な物Channelはいくつかの状態を持ちます。
最後に切断するまで状態が切り替わっていく様になっています。
- CONNECTING
- READY
- TRANSIENT_FAILURE
- IDLE
- SHUTDOWN
Channel作成直後はCONNECTINGから始まり、使い終わるとSHUTDOWNになります。
gRPCとHTTP/2
gRPCはHTTP/2の元、通信を行っています。
HTTP/2に関してはそろそろ知っておきたいHTTP/2の話で概要が掴めるかと思います。個人的に一番「うオ!!」っと思ったのは
今まで1リクエスト1レスポンだったものが Streamが導入され、複数の通信を行える様になった と言う点です!(すごい。。)
- Connection
- Stream
- Frame
このワードを押さえてください。
ひとつのRPCに大してひとつのHTTP/2 Streamが対応しています。
そしてそれら(Connection, Stream)を管理しているのがgRPCのChannel(goではClientConn)です。
- 投稿日:2020-01-01T18:16:54+09:00
眺めて覚えるGo言語 その8 PythonからGO言語を呼ぶ
pythonからGo サブルーチンへの引数 文字列
byte列をポインターで渡す。
pcgo1.pyfrom ctypes import * lib = cdll.LoadLibrary("./golib.so") a=lib.fun1(c_char_p(b"hello world"))golib.gopackage main import "C" import "fmt" //export fun1 func fun1(a *C.char) int { fmt.Println(C.GoString(a)) return 0 } func main() {} コンパイルと実行 >go build -o golib.so -buildmode=c-shared golib.go >python pcgo1.py hello world
- c_char_p(b"hello world")は、バイト列ポインター扱い
- C.GoString(a)は、受け取ったバイト列ポインターをGO stringへ
文字列の受け渡し
基本的に文字列の受け渡しは、バイト列で行う。
py2go.pyfrom ctypes import * #パラメータの準備 class go_string(Structure): _fields_ = [("p", c_char_p),("n", c_int)] #GO言語用string変換 def GoString(s): u=s.encode('utf-8') return go_string(c_char_p(u), len(u)) lib = cdll.LoadLibrary("./golib.so") lib.fun1.restype=c_char_p a=lib.fun1(c_char_p("こんにちは".encode('utf-8'))) lib.fun2.restype=c_char_p b=lib.fun2(GoString("こんにちは")) print("a=",a.decode('utf-8')) print("b=",b.decode('utf-8'))
- encode/decodeは、それぞれバイト列変換です。
golib.gopackage main import "C" import "fmt" //export fun1 func fun1(a *C.char)*C.char{ fmt.Println("fun1",C.GoString(a)+" 日本") return C.CString(C.GoString(a)+" 日本") } //export fun2 func fun2( a string)*C.char{ fmt.Println("fun2",a+" 日本") return C.CString(a+" 日本") } func main() {} コンパイルと実行結果 >go build -o golib.so -buildmode=c-shared golib.go >python py2go.py fun1 こんにちは 日本 fun2 こんにちは 日本 a= こんにちは 日本 b= こんにちは 日本
- fun1は、C言語ポインターで受け取りC言語ポインターで返す。
- fun2は、Go言語stringで受け取りC言語ポインターで返す。
- 投稿日:2020-01-01T14:41:02+09:00
眺めて覚えるGo言語 その8 PythonからGO言語を呼ぶ
今回は、PythonからGO言語を呼んでpythonの弱点である速度向上させるアプローチです。
Pythonを語ったとき「コンパイラ」ではない。で炎上しました。
javaでも「コンパイラ」ではないし、C#もコンパイラではないのです。
中間言語に変換して実行するのは、スピード遅いし、第一電池がすぐ減るのです。
GOは、本物っぽいコンパイラーです。
pythonから見ると計算部分を高速化できる可能性があります。
フィボナッチ数を参考にしました。
- 1つがいの兎は、産まれて2か月後から毎月1つがいずつの兎を産む。
- 兎が死ぬことはない。
- この条件のもとで、産まれたばかりの1つがいの兎は1年の間に何つがいの兎になるか?
fib_go.pyfrom ctypes import * lib = cdll.LoadLibrary("./fib.so") for i in range(40): print ("fib %d %d" %(i, lib.fib(i)))lib.fib(i)で下記の関数呼ぶだけ。
fib.gopackage main import "C" //export fib func fib(n int) int { if n <= 1 { return n } return fib(n-1) + fib(n-2) } func main() {}Goで書いた関数をコンパイルする。
> go build -o fib.so -buildmode=c-shared fib.go実行する
>python fib_go.py fib 0 0 fib 1 1 fib 2 1 fib 3 2 fib 4 3 fib 5 5 fib 6 8 fib 7 13 fib 8 21 fib 9 34 fib 10 55 fib 11 89 fib 12 144 fib 13 233 fib 14 377 fib 15 610 fib 16 987 fib 17 1597 fib 18 2584 fib 19 4181 fib 20 6765 fib 21 10946 fib 22 17711 fib 23 28657 fib 24 46368 fib 25 75025 fib 26 121393 fib 27 196418 fib 28 317811 fib 29 514229 fib 30 832040 fib 31 1346269 fib 32 2178309 fib 33 3524578 fib 34 5702887 fib 35 9227465 fib 36 14930352 fib 37 24157817 fib 38 39088169 fib 39 63245986 fib 40 10233415510倍以上速くなる。
- 投稿日:2020-01-01T09:46:12+09:00
ginを最速でマスターしよう
初めに
ginとは何でしょうか
ginはGo(Golang)で書かれたWebフレームワークです。 httprouterのおかげで、最大40倍高速なパフォーマンスを備えたmartiniのようなAPIを備えています。パフォーマンスと優れた生産性が必要な場合は、Ginを好きになるでしょう。--公式ドキュメント
パフォーマンスが良いのが売りらしいですが、他のGo言語のwebフレームワークと比較してみます。
人気ランキング
githubのスターの多い順
Name Stars Forks Issues Open Issues Closed Birth Year Latest Update Author Gin 34,231 3,900 183 1,115 2014 2019-12-27 @manucorporat Beego 22,890 4,600 737 1,900 2012 2019-12-27 @astaxie Iris 17,133 1,906 4 467 2016 2019-12-27 @kataras Echo 16,058 1,541 26 902 2015 2019-12-27 @vishr また、国内に案件が存在するのは、現在GinとEchoだけです
準備
作業PCにGo語言が入ってることをまず確認してください。
go version go version go1.13ginをgetしましょう、新規プロジェクトを作る際にgomodを使用することをお勧めします。
gomodについてよくわからない方はこちらの記事を参考にしてください。
GOMODULE--Goのパッケージ管理新規フォルダを作ってください、フォルダ名は任意で結構です。
mkdir gin_test && cd gin_testgo modを初期化します。
go mod init gin_test初期化完了したら、ginのパッケージを取得します
go get -u github.com/gin-gonic/ginginの基礎
ginはlaravelやDjangoのようなフルスタックフレームワークと違って非常にライトなフレームワークになります、
最初の実例を書いてみよう。main.gopackage main import "github.com/gin-gonic/gin" import "net/http" func main() { engine:= gin.Default() engine.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "hello world", }) }) engine.Run(":3000") }
go run main.go
でサーバー立ち上げてlocalhost:3000
にアクセスしてみると
Jsonメッセージの{"message":"hello world"}
が確認できます。もしginを使わずに素のGoで同じサーバーを作る場合、以下のようになります。
main.gopackage main import ( "encoding/json" "net/http" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, q *http.Request) { message := map[string]string{ "message": "hello world", } jsonMessage, err := json.Marshal(message) if err != nil { panic(err.Error()) } w.Write(jsonMessage) }) http.ListenAndServe("127.0.0.1:3000", mux) }コードの量はほぼ倍になりました。
では、ginは実際何をしたのでしょうか、ginのソースコード見てみます。context.gotype Context struct { writermem responseWriter Request *http.Request Writer ResponseWriter Params Params handlers HandlersChain index int8 fullPath string engine *Engine // Keys is a key/value pair exclusively for the context of each request. Keys map[string]interface{} // Errors is a list of errors attached to all the handlers/middlewares who used this context. Errors errorMsgs // Accepted defines a list of manually accepted formats for content negotiation. Accepted []string // queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query() queryCache url.Values // formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH, // or PUT body parameters. formCache url.Values }素のGoで
HandleFunc
を書く際に、パラメタとして存在するwritermem responseWriter
やRequest *http.Request
、ginのContext
のstruct
の構成要素になってます。gin.gofunc (engine *Engine) Run(addr ...string) (err error) { defer func() { debugPrintError(err) }() address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) err = http.ListenAndServe(address, engine) return }
engin.Run(":3000")
も実際http.ListenAndServe(address, engine)
を中身で呼んでます。
Goのhttpサーバーのコードを活かして、便利な処理を追加するのがginです。
素のGoでhttpサーバー書いたことがあれば、ginのソースコードは非常にわかやすいはずですミドルウェアを使ってみよう
ginはリクエストに対する処理は基本下記のようになります
Request -> Route Parser -> Middleware -> Route Handler -> Middleware -> Response実際処理する関数に到達する前に、必ずミドルウェアを通る必要があります、
簡単な例として、アクセスするユーザーのUser-Agent
を取得するミドルウェアを作ってみましょう。main.gopackage main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { engine:= gin.Default() ua := "" // ミドルウェアを使用 engine.Use(func(c *gin.Context) { ua = c.GetHeader("User-Agent") c.Next() }) engine.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "hello world", "User-Agent": ua, }) }) engine.Run(":3000") }
http://localhost:3000/
にアクセスすれば、以下のメッセージが確認できるはずです。{ User-Agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36", message: "hello world" }HTMLや静的ファイルを扱う
HTMLの扱い
mkdir templates && cd templates && touch index.htmlindex.htmlの中身は以下のように
index.html<h1 style="color: rebeccapurple;">{{.message}}</h1>main.goを修正
main.gopackage main import "github.com/gin-gonic/gin" import "net/http" func main() { engine:= gin.Default() // htmlのディレクトリを指定 engine.LoadHTMLGlob("templates/*") engine.GET("/", func(c *gin.Context) { c.HTML(http.StatusOK, "index.html", gin.H{ // htmlに渡す変数を定義 "message": "hello gin", }) }) engine.Run(":3000") }サーバー立ち上げて
localhost:3000
にアクセルすると、以下の内容が確認できるはずです静的ファイルの扱う
mkdir static
任意の画像をstaticフォルダに入れます。
main.goを以下のように修正します。main.gopackage main import "github.com/gin-gonic/gin" func main() { engine:= gin.Default() engine.Static("/static", "./static") engine.Run(":3000") }サーバーを立ち上げて、
http://localhost:3000/static/gin.png
をアクセスすると、画像が表示されるはずです。ファイルのアップロード
まず画像保存用のフォルダを作っておきます。
mkdir imagesmain.gopackage main import ( "github.com/gin-gonic/gin" "io" "log" "net/http" "os" ) func main(){ engine := gin.Default() engine.POST("/upload", func(c *gin.Context) { file,header, err := c.Request.FormFile("image") if err != nil { c.String(http.StatusBadRequest, "Bad request") return } fileName := header.Filename dir, _ := os.Getwd() out, err := os.Create(dir+"\\images\\"+fileName) if err != nil { log.Fatal(err) } defer out.Close() _, err = io.Copy(out, file) if err != nil { log.Fatal(err) } c.JSON(http.StatusOK, gin.H{ "status": "ok", }) }) engine.Run(":3000") }フロントエンド、vue.jsを使ってます。 urlが
/api/upload
になってるのはproxy
の設定に/api
で始まるurlのtarget
をginサーバーにリダイレクトしています。設定の仕方に疑問のある方はコメントくださいApp.vue<template> <div id="app"> <input type="file" id="people-export" ref="input"> <button type="submit" @click="fileUpload">提出</button> </div> </template> <script> import axios from 'axios' export default { name: 'app', components: { HelloWorld }, methods: { fileUpload(){ let file = this.$refs.input; let formData = new FormData(); formData.append('image', file.files[0]); axios({ method: 'post', url :'/api/upload', data : formData, header :{ 'Content-Type': 'multipart/form-data', } }).then(( res )=>{ console.log(res.data) }) } } } </script> <style> </style>ginで簡単なREST風のAPIサーバーを作ってみよう
実装機能、書籍についての
- 新規追加
- データ一覧
- データ修正
- 削除
ディレクトリ構成
qiita |- controller |- |- book.go |- middleware |- |- bookMiddleware.go |- model |- |- book.go |- serice |- |- book.go |- |- init.go |- go.mod |- main.go必要なパッケージをgetします。
go get github.com/gin-gonic/gin go get github.com/go-sql-driver/mysql go get github.com/go-xorm/xorm go get go.uber.org/zapmain.go
役割はREST風のAPIを実装とmysql用のパッケージの初期化します。
main.gopackage main import ( "github.com/gin-gonic/gin" _ "github.com/go-sql-driver/mysql" "qiita/controller" "qiita/middleware" ) func main(){ engine := gin.Default() // ミドルウェア engine.Use(middleware.RecordUaAndTime) // CRUD 書籍 bookEngine := engine.Group("/book") { v1 := bookEngine.Group("/v1") { v1.POST("/add", controller.BookAdd) v1.GET("/list", controller.BookList) v1.PUT("/update", controller.BookUpdate) v1.DELETE("/delete", controller.BookDelete) } } engine.Run(":3000") }controller/book.go
機能としてはmain.goから振られたリクエストをserviceにハンドルし、レスポンスを返します。
controller/book.gopackage controller import ( "github.com/gin-gonic/gin" "net/http" "qiita/model" "qiita/service" "strconv" ) func BookAdd(c *gin.Context) { book := model.Book{} err := c.Bind(&book) if err != nil{ c.String(http.StatusBadRequest, "Bad request") return } bookService :=service.BookService{} err = bookService.SetBook(&book) if err != nil{ c.String(http.StatusInternalServerError, "Server Error") return } c.JSON(http.StatusCreated, gin.H{ "status": "ok", }) } func BookList(c *gin.Context){ bookService :=service.BookService{} BookLists := bookService.GetBookList() c.JSONP(http.StatusOK, gin.H{ "message": "ok", "data": BookLists, }) } func BookUpdate(c *gin.Context){ book := model.Book{} err := c.Bind(&book) if err != nil{ c.String(http.StatusBadRequest, "Bad request") return } bookService :=service.BookService{} err = bookService.UpdateBook(&book) if err != nil{ c.String(http.StatusInternalServerError, "Server Error") return } c.JSON(http.StatusCreated, gin.H{ "status": "ok", }) } func BookDelete(c *gin.Context){ id := c.PostForm("id") intId, err := strconv.ParseInt(id, 10, 0) if err != nil{ c.String(http.StatusBadRequest, "Bad request") return } bookService :=service.BookService{} err = bookService.DeleteBook(int(intId)) if err != nil{ c.String(http.StatusInternalServerError, "Server Error") return } c.JSON(http.StatusCreated, gin.H{ "status": "ok", }) }middleware/bookMiddleware.go
リクエストのlogを記録します。
go.uber.org/zap
パッケージを使用しています、出前で有名なUberさんのオープンソースらしいです。middleware/bookMiddleware.gopackage middleware import ( "github.com/gin-gonic/gin" "go.uber.org/zap" "log" "time" ) func RecordUaAndTime(c *gin.Context){ logger, err := zap.NewProduction() if err != nil{ log.Fatal(err.Error()) } oldTime := time.Now() ua := c.GetHeader("User-Agent") c.Next() logger.Info("incoming request", zap.String("path", c.Request.URL.Path), zap.String("Ua", ua), zap.Int("status", c.Writer.Status()), zap.Duration("elapsed", time.Now().Sub(oldTime)), ) }model/book.go
bookの構造体を定義してます。
xorm
パッケージ使用して、テーブルの初期化でも使用します。model/book.gopackage model type Book struct { Id int64 `xorm:"pk autoincr int(64)" form:"id" json:"id"` Title string `xorm:"varchar(40)" json:"title" form:"title"` Content string `xorm:"varchar(40)" json:"content" form:"content"` }service/init.go
データベースへの接続とテーブルの初期化を実装します。
service/init.gopackage service import ( "errors" "fmt" "github.com/go-xorm/xorm" "qiita/model" "log" ) var DbEngine *xorm.Engine func init() { driverName := "mysql" DsName := "root:root@(192.168.99.100:3306)/gin?charset=utf8" err := errors.New("") DbEngine, err = xorm.NewEngine(driverName,DsName) if err != nil && err.Error() != ""{ log.Fatal(err.Error()) } DbEngine.ShowSQL(true) DbEngine.SetMaxOpenConns(2) DbEngine.Sync2(new(model.Book)) fmt.Println("init data base ok") }service/book.go
機能としてはコントローラから振られたdb操作を引き受け、結果を返します。
service/book.gopackage service import ( "qiita/model" ) type BookService struct {} func (BookService) SetBook(book *model.Book) error { _, err := DbEngine.Insert(book) if err!= nil{ return err } return nil } func (BookService) GetBookList() []model.Book { tests := make([]model.Book, 0) err := DbEngine.Distinct("id", "title", "content").Limit(10, 0).Find(&tests) if err != nil { panic(err) } return tests } func (BookService) UpdateBook(newBook *model.Book) error { _, err := DbEngine.Id(newBook.Id).Update(newBook) if err != nil { return err } return nil } func (BookService) DeleteBook(id int) error { book := new(model.Book) _, err := DbEngine.Id(id).Delete(book) if err != nil{ return err } return nil }サーバを立ち上げて、APIの動作を見てみます。
go run main.go起動後、以下のlogも確認できるはずです。
内容はdbの初期化完了と実装されたAPI情報です。[xorm] [info] 2020/01/01 00:40:53.621508 [SQL] SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT`, `TABLE_COMMENT` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE _SCHEMA`=? AND (`ENGINE`='MyISAM' OR `ENGINE` = 'InnoDB' OR `ENGINE` = 'TokuDB') [gin] [xorm] [info] 2020/01/01 00:40:53.655668 [SQL] SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`, `COLUMN_KEY`, `EXTRA`,`COLUMN_COMMENT` FROM `INFORMATION _SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? [gin book] [xorm] [info] 2020/01/01 00:40:53.656645 [SQL] SELECT `INDEX_NAME`, `NON_UNIQUE`, `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NA ME` = ? [gin book] init data base ok [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] POST /book/v1/add --> qiita/controller.BookAdd (4 handlers) [GIN-debug] GET /book/v1/list --> qiita/controller.BookList (4 handlers) [GIN-debug] PUT /book/v1/update --> qiita/controller.BookUpdate (4 handlers) [GIN-debug] DELETE /book/v1/delete --> qiita/controller.BookDelete (4 handlers) [GIN-debug] Listening and serving HTTP on :3000APIのテストは
postman
を使います。新規追加 POST /book/v1/add
データ一覧 GET /book/v1/list
データ修正 PUT /book/v1/update
データの削除 DELETE /book/v1/delete
長くなりましたが、以上となります。
ORMに関してはxorm
パッケージを使用してます、ドキュメントは公式リポジトリを参考にしてください -- リンク最後に
最後まで読んで下さり、ありがとうございます。
実際ginを使ってみて、ライトな感覚に結構惹かれています。
フレームワークを使用してるけれど、過依存することもなく、素のGoの感覚でコーディングできます。
- 投稿日:2020-01-01T03:18:36+09:00
GO言語のGUIライブラリ「Fyne」のインストール方法
はじめに
参考書を一通り読み終わり、
「さぁこれから本格的にコードを書いて勉強だ!」
「どうせなら何か適当にGUIのライブラリでも入れて楽しく勉強しよう!」
と思い、紆余曲折ありながらFyneというライブラリをubuntuに入れました。入れるのに3日もかかってしまい、正直めっちゃ辛かったです笑
だって、GO言語の日本語の記事全然ないんだもん。
※私が色々試して実行できた方法なのでもしかしたら必要のない工程があるかもしれません。前提条件
・vmware (virtualboxよりも滑らかに動くと思う、個人的に)
・ubuntu-ja-18.04.3 (https://www.ubuntulinux.jp/download/ja-remix)
※メモリは2G以上、プロセッサ数2 に設定1.アップデートや必要なものを入れる
$sudo apt-get -y update $sudo apt-get -y upgrade $sudo apt -y install git $sudo apt -y install vim2.goのインストール
・(https://golang.org/dl) からリンクをコピー
$wget https://dl.google.com/go/go1.13.5.linux-amd64.tar.gz・解凍
$sudo tar -C /usr/local -zxf go1.13.5.linux-amd64.tar.gz・末尾にPATH=$PATH:/usr/local/go/binを追記
$vim .bashrc・環境変数を適用
$source .bashrc・goのversion確認
$go version go version go1.13.5 linux/amd643.Fyneを入れる準備
$sudo apt install build-essential $sudo apt-get - y install libasound2-dev libglu1-mesa-dev freeglut3-dev mesa-common-dev xorg-dev libgl1-mesa-dev git-all $sudo apt-get -y install libegl1-mesa-dev libgles2-mesa-dev libx11-dev $sudo apt -y install libglfw3-devここでいったん再起動
4.ようやくFyneのインストール!
・Fyneのインストール (https://github.com/fyne-io/fyne)
$go get -v fyne.io/fyne/...・ディレクトリ作成&サンプルダウンロード
$cd $mkdir golang $cd golang $git clone https://github.com/fyne-io/examples.git・サンプル実行
$cd examples $go run main.go・実行結果!
Examplesウィンドウから項目を選択することでサンプルが実行される。終わりに
ということでubuntuにFyneをインストールしました。
これで楽しくGO言語の勉強ができます!間違っているところがありましたらご指摘お願いします。
ここまで読んでいただきありがとうございました!!