20200101のGoに関する記事は7件です。

眺めて覚えるGo言語 その8 PythonからGO言語を呼ぶ1(引数文字列)

pythonからGo サブルーチンへの引数 文字列

byte列をポインターで渡す。

pcgo1.py
from ctypes import *
lib = cdll.LoadLibrary("./golib.so")
a=lib.fun1(c_char_p(b"hello world"))
golib.go
package 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.py
from 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.go
package 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言語ポインターで返す。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アプリ音痴の両親のためにLINEで共有した写真をデジタルフォトフレーム風にするアプリを作った話

この記事はぷりぷりあぷりけーしょんず Advent Calendar 2019の22日目の記事です。

はじめに

大遅刻スミマセン。、
まさかの年越してからアドベントカレンダーに登録するという最早アドベントカレンダーとはなんぞやな状況です。
師走の風に流され、忙しさのあまり年内に完成させ記事にする予定のアプリが新年初コミットとともに完成に至る形になりました。

さて、年を越したと同時にエンジニアに転職して丸3年が経ちました。
1年半ほど前は丸3年経つまでにサーバー・ネットワークの設定からアプリの開発、何かしらのサービスの一般公開をしたいというのを目標に業務とは別に個人学習、開発を行なっていました。
結果的にサーバー・ネットワークの設定,何かしらのサービスの一般公開というのは叶うことはなかったわけですが個人的に家族のための使うことができるレベルのアプリを作成することが叶いました。

作ったもの

私の両親はまあアプリ音痴でインスタグラムはもちろんのこと、LINEがギリギリ使えるレベルです。その割に父親はガジェット好きでiPadはいっちょ前に4台持っています。
いっちょ前にリビングに立て掛けてあるiPadを有効活用してもらいたいと思いLINEだけで完結するデジタルフォトフレームアプリfamiphoto(famip○rtからパクったとかは言わない)を作成しました。

DSC_2667.JPG

アーキテクチャ

Untitled Diagram (1).jpg

ざっくり上記のようになっており、アプリのフローとしては以下の通りです。

  1. LINEグループにMessagingAPIで作成したbotを招待
    UUIDを作成し、グループIDをキーにDynamoDBに保存、S3にUUIDのディレクトリを作成
  2. 今まで通り画像を共有(ここ重要)
    グループIDからUUIDを取得し、画像をUUIDのディレクトリに保存
  3. famiphoto urlとメッセージを送るとデジタルフォトフレームのurlを返す
    urlはhttps://***firebase.app/family/{uuid}
  4. urlにアクセスするとデジタルフォトフレームとして起動しリビングに飾る
    裏では{uuid}をリクエストパラメータにAPIGatewayからLambdaを起動し、S3の画像URL一覧を取得してる
  5. PWAなのでホームに落としてあげれば、普通にiPad使いたいときは普通に使ってもらい、飾っておくときはアプリを起動して置くだけで良い

使用している技術は
- AWSLambda Runtime-Go
- DynamoDB
- S3
- Firebase hosting
- Vue.js
- PWA

AWS側のインフラ周りは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.go
func 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.go
type 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. 初挑戦は1つ〜2つくらい(規模によると思う)
  2. 今までの知識のアップデートを中心に
  3. 時間のかかりそうな箇所は得意分野で
  4. わからなそうな領域はググってコピペでいいから終わらせる

というのがアプリを作る際のモチベーション維持の要になるように思います。
今回の私のアプリでいうと

  1. PWA
  2. Go, Vue.js
  3. CDK, Firebase
  4. 画像のフルスクリーン表示やスライドショーぽくするところ

といったところでしょうか。

エンジニア丸3年経過時の目標へのコミットは残念ながら届きませんでしたが全くの未経験から相談なしにエンジニアへの転職をしてやって見せてやることはできたかと思います。(父親は令和の世を生きる人間とは思えない程固い人間)

ここまで作ることができ、あとはやる気次第だと思うので2020年のアドベントカレンダーでは一般公開したサービスの記事を書けるように変わらずマイペースに個人開発をしていきたいと思います。

参考

Vue CLI 3 で PWA チュートリアル(Service Workers / Add to Home Screen / Push Notifications)
とっても簡単!Vueでフルスクリーン機能を実装する

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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)です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

眺めて覚えるGo言語 その8 PythonからGO言語を呼ぶ

pythonからGo サブルーチンへの引数 文字列

byte列をポインターで渡す。

pcgo1.py
from ctypes import *
lib = cdll.LoadLibrary("./golib.so")
a=lib.fun1(c_char_p(b"hello world"))
golib.go
package 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.py
from 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.go
package 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言語ポインターで返す。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

眺めて覚えるGo言語 その8 PythonからGO言語を呼ぶ

今回は、PythonからGO言語を呼んでpythonの弱点である速度向上させるアプローチです。

Pythonを語ったとき「コンパイラ」ではない。で炎上しました。

javaでも「コンパイラ」ではないし、C#もコンパイラではないのです。

中間言語に変換して実行するのは、スピード遅いし、第一電池がすぐ減るのです。

GOは、本物っぽいコンパイラーです。

pythonから見ると計算部分を高速化できる可能性があります。

フィボナッチ数を参考にしました。

  • 1つがいの兎は、産まれて2か月後から毎月1つがいずつの兎を産む。
  • 兎が死ぬことはない。
  • この条件のもとで、産まれたばかりの1つがいの兎は1年の間に何つがいの兎になるか?
fib_go.py
from ctypes import *
lib = cdll.LoadLibrary("./fib.so")
for i in range(40):
    print ("fib %d  %d" %(i, lib.fib(i)))

lib.fib(i)で下記の関数呼ぶだけ。

fib.go
package 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  102334155

10倍以上速くなる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ginを最速でマスターしよう

初めに:sunny:

gin.png

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だけです:point_up:

準備

作業PCにGo語言が入ってることをまず確認してください。

go version
go version go1.13

ginをgetしましょう、新規プロジェクトを作る際にgomodを使用することをお勧めします。
gomodについてよくわからない方はこちらの記事を参考にしてください。
GOMODULE--Goのパッケージ管理

新規フォルダを作ってください、フォルダ名は任意で結構です。

mkdir gin_test && cd gin_test

go modを初期化します。

go mod init gin_test

初期化完了したら、ginのパッケージを取得します

go get -u github.com/gin-gonic/gin

ginの基礎

ginはlaravelやDjangoのようなフルスタックフレームワークと違って非常にライトなフレームワークになります、
最初の実例を書いてみよう。

main.go
package 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.go
package 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.go
type 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 responseWriterRequest *http.Request、ginの Contextstructの構成要素になってます。

gin.go
func (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.go
package 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.html

index.htmlの中身は以下のように

index.html
<h1 style="color: rebeccapurple;">{{.message}}</h1>

main.goを修正

main.go
package 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にアクセルすると、以下の内容が確認できるはずですUNADJUSTEDNONRAW_mini_3e.jpg

静的ファイルの扱う

mkdir static 

任意の画像をstaticフォルダに入れます。
2019-12-27 17.35のイメージ.jpg
main.goを以下のように修正します。

main.go
package 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 images
main.go
package 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/zap

main.go

役割はREST風のAPIを実装とmysql用のパッケージの初期化します。

main.go
package 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.go
package 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.go
package 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.go
package 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.go
package 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.go
package 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 :3000

APIのテストはpostmanを使います。

新規追加 POST /book/v1/add

1.PNG
データベースにもちゃんとデータが入りました。
2.PNG

データ一覧 GET /book/v1/list

3.PNG

データ修正 PUT /book/v1/update

内容を一部修正します。
4.PNG

データベースにもちゃんと反映されてます。
キャプチャ.PNG

データの削除 DELETE /book/v1/delete

キャプチャ.PNG

データベース内のデータも削除されました。
キャプチャ.PNG

長くなりましたが、以上となります。
ORMに関しては xorm パッケージを使用してます、ドキュメントは公式リポジトリを参考にしてください -- リンク

最後に

最後まで読んで下さり、ありがとうございます。

実際ginを使ってみて、ライトな感覚に結構惹かれています。
フレームワークを使用してるけれど、過依存することもなく、素のGoの感覚でコーディングできます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 vim

2.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

erve.PNG

・環境変数を適用

$source .bashrc

・goのversion確認

$go version
go version go1.13.5 linux/amd64

3.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

・実行結果!
aervawrevwr.PNG
Examplesウィンドウから項目を選択することでサンプルが実行される。

終わりに

ということでubuntuにFyneをインストールしました。
これで楽しくGO言語の勉強ができます!

間違っているところがありましたらご指摘お願いします。

ここまで読んでいただきありがとうございました!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む