Category Archives: gae

send grid app engine

send grid アカウント作成

https://sendgrid.kke.co.jp/about/

App Engine設定

APIキー作成

配信

import(
    "google.golang.org/appengine/urlfetch"
    "gopkg.in/sendgrid/sendgrid-go.v2"
)
func handler(w http.ResponseWriter, r *http.Request) {
    sg := sendgrid.NewSendGridClientWithApiKey("{APIキー}")
        ctx := appengine.NewContext(r)

        // Set http.Client to use the App Engine urlfetch client
        sg.Client = urlfetch.Client(ctx)

        message := sendgrid.NewMail()
        message.AddTo("example@email.com")
        message.SetSubject("Email From SendGrid")
        message.SetHTML("Through AppEngine")
        message.SetFrom("sendgrid@appengine.com")
        sg.Send(message)
}

備考

fromのアドレスには自由に設定することができるが、なりすましの場合は、
スパム判定される。

事前にSPFレコードの登録などが必要
https://mxtoolbox.com/spf.aspx

参考

ドキュメント
https://sendgrid.kke.co.jp/blog/?p=1391

godotenv

パッケージダウンロード

go get github.com/joho/godotenv

kintone.env

app.yamlと同階層に配置

KINTONE_DOMAIN="{値をセット}"
KINTONE_USER="{値をセット}"
KINTONE_PASSWORD="{値をセット}"

ファイル読込

func init() {
    err := godotenv.Load("kintone.env")
    if err != nil {
        log.Fatal("Error loading .env file")
    }
    log.Print(os.Getenv("KINTONE_DOMAIN")) // 値の読み込みが可能
}

参考

https://github.com/joho/godotenv

gae datastore 開発環境

goapp serve

の後、

http://localhost:8080

の開発環境のWEBアプリケーションからレコードを作成して、
以下からレコードの内容を確認することができる

http://localhost:8000/datastore

gae go gin エラーハンドリング

以下のようにエラーの詳細を出すことができる

// Show ...
func (f Form) Show(c *gin.Context) {
    appID, err := strconv.ParseUint(c.Param("appID"), 10, 64)
    if err != nil {
        c.String(400, "appIDが間違っています")
        return
    }
}

app engine プロジェクト名を変更した際のエラー

現象

goapp deploy の際に、

 You do not have permission to modify this app.

というエラーがでる

原因

cookieに保存されているアプリケーションIDとapp.yamlに記述されたアプリケーションIDが異なっていることが原因です。

対応

ログイン情報ためてるキャッシュファイルを削除

rm ~/.appcfg_*

参考

http://otiai10.hatenablog.com/entry/2016/04/25/004711

ファイルのmultipart送信

リクエスト生成

data, header, err := c.Request.FormFile("upload")
if err != nil {
    panic(err)
}
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", fileName)
if err != nil {
    panic(err)
}
if _, err := io.Copy(part, data); err != nil {
    panic(err)
}

h := make(textproto.MIMEHeader)
h.Set("Content-Disposition",
    fmt.Sprintf(`form-data; name="file"; filename="%s"`,
        escapeQuotes(fileName)))
h.Set("Content-Type", contentType)

fw, err := writer.CreatePart(h)
if err != nil {
    panic(err)
}
if _, err = io.Copy(fw, data); err != nil {
    panic(err)
}
if err = writer.Close(); err != nil {
    panic(err)
}

req, err := app.newRequest("POST", "file", body, "")
if err != nil {
    panic(err)
}

req.Header.Set("Content-Type", writer.FormDataContentType())

blobstoreへの画像保存

ルーティング

init.go

r := gin.New()
r.GET("/image/new", controllers.Image{}.New)
r.POST("/image/upload", controllers.Image{}.Upload)
r.GET("/image", controllers.Image{}.Show)
http.Handle("/", r)

コントローラー

controllers/image.go

// Image ...
type Image struct{}

// New ...
func (Image) New(c *gin.Context) {
    ctx := appengine.NewContext(c.Request)
    uploadURL, err := blobstore.UploadURL(ctx, "/image/upload", nil)
    if err != nil {
        panic(err)
    }
    t, err := template.ParseFiles("views/image/new.tmpl", "views/application.tmpl")
    if err != nil {
        panic(err)
    }
    t.ExecuteTemplate(c.Writer, "base", uploadURL)
}

// Upload ...
func (Image) Upload(c *gin.Context) {
    blobs, _, err := blobstore.ParseUpload(c.Request)
    if err != nil {
        panic(err)
    }
    file := blobs["file"]
    http.Redirect(c.Writer, c.Request, "/image?blobKey="+string(file[0].BlobKey), http.StatusFound)
}

// Show ...
func (Image) Show(c *gin.Context) {
    blobstore.Send(c.Writer, appengine.BlobKey(c.Request.FormValue("blobKey")))
}

View

application.tmpl

{{define "base"}}
<html>
<head>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</head>
<body>
  <div class="container">
    <h3>Kintone</h3>
    {{template "content" .}}
  </div>
  <script src="/js/bootstrap.min.js"></script>
</body>
</html>
{{end}}

new.tmpl

{{define "content"}}
<form action="{{.}}" method="POST" enctype="multipart/form-data">
  <input type="file" name="file" class="form-control">
  <br>
  <input type="submit" value="create" class="btn btn-primary">
</form>
{{end}}

gae go 静的ファイル

ファイル構成

// 動的テンプレート
gae/views/test.tmpl

// 静的ファイル
gae/static/img/gopher.png
gae/static/js/test.js

gae/app.yaml

動的テンプレートはapp.yamlでの定義不要

- url: /js
  static_dir: static/js
- url: /img
  static_dir: static/img
- url: /.*
  script: _go_app
  secure: always

ケース2