2016/02/24


golang で Web アプリを作ってると画像のアップロード処理を書くことって意外と多くて、その度にググったり過去の自分の実装を調べたりして、みたいな事を繰り返してましたが go-imageupload を使うとかなり端折れる事になりそうです。

GitHub - olahol/go-imageupload: Gracefully handle image uploading and thumbnail creation.
https://github.com/olahol/go-imageupload

実装は簡素ですが、毎回自分でこれを書いてたと思うと時間が勿体ないですね。使い方も簡単で README.md から転用すると 300x300 のサムネイル画像を生成するのはこれだけになります。

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/olahol/go-imageupload"
)

func main() {
    r := gin.Default()

    r.GET("/"func(c *gin.Context) {
        c.File("index.html")
    })

    r.POST("/upload"func(c *gin.Context) {
        img, err := imageupload.Process(c.Request, "file")

        if err != nil {
            panic(err)
        }

        thumb, err := imageupload.ThumbnailPNG(img, 300300)

        if err != nil {
            panic(err)
        }

        thumb.Write(c.Writer)
    })

    r.Run(":5000")
}

サムネイルとは書いてますが、サイズが指定出来るので例えばアップロード画像のサイズを均一にしたい場合にも使えます。ブラウザ側の処理も dropzone.js を使えば簡単にアップロード画面が出来上がります。

まずはサーバ側のコード。

package main

import (
    "crypto/sha1"
    "fmt"
    "time"

    "github.com/gin-gonic/contrib/static"
    "github.com/gin-gonic/gin"
    "github.com/mattn/go-colorable"
    "github.com/olahol/go-imageupload"
)

func main() {
    gin.DefaultWriter = colorable.NewColorableStdout()
    r := gin.Default()

    r.Use(static.Serve("/", static.LocalFile("./assets"true)))

    r.POST("/upload"func(c *gin.Context) {
        img, err := imageupload.Process(c.Request, "file")
        if err != nil {
            panic(err)
        }
        thumb, err := imageupload.ThumbnailPNG(img, 300300)
        if err != nil {
            panic(err)
        }
        h := sha1.Sum(thumb.Data)
        thumb.Save(fmt.Sprintf("files/%s_%x.png",
            time.Now().Format("20060102150405"), h[:4]))
    })

    r.Run(":5000")
}

アップロードされた画像を 300x300 にリサイズして、日付と sha1 のファイル名付けて保存しているだけです。files というディレクトリに保存されます。クライアント側も dropzone.js 様様です。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>あぷろだ</title>
    <link rel="stylesheet" href="dropzone.css" media="all">
    <link rel="stylesheet" href="app.css" media="all">
    <script src="dropzone.js"></script>
</head>
<body>
<form action="/upload" class="dropzone" id="dropzone"></form>
</body>
</html>

一応、マウスホバーで色変えちゃうよ的な。

#dropzone {
  background-color#afa;
  bordersolid 3px #5a5;
  color#585;
  padding20px;
}

#dropzone.dropover {
  background-color#cfc;
  color#9c9;
}
upload

便利。

Go言語によるWebアプリケーション開発 Go言語によるWebアプリケーション開発
Mat Ryer, 鵜飼 文敏, 牧野 聡
オライリージャパン 大型本 / ¥3,520 (2016年01月22日)
 
発送可能時間:

Posted at by



2015/12/12


golang の defer は後処理のキューの登録です。コードを見ていないので分かりませんが、おそらくこういうコードを書いたのだと推測します。

package main

import (
    "fmt"
)

type foo struct {
    n int
}

func Create(n int) *foo {
    fmt.Printf("%v を作成\n", n)
    return &foo{n}
}

func Delete(f *foo) {
    fmt.Printf("%v を削除\n", f.n)
}

func main() {
    fmt.Println("開始")
    for i := 1; i <= 10; i++ {
        f := Create(i)
        defer Delete(f)
    }
    fmt.Println("終了")
}

この処理、実際には作成者の意図に反して以下の様に動作します。

開始
1 を作成
2 を作成
3 を作成
4 を作成
5 を作成
6 を作成
7 を作成
8 を作成
9 を作成
10 を作成
終了
10 を削除
9 を削除
8 を削除
7 を削除
6 を削除
5 を削除
4 を削除
3 を削除
2 を削除
1 を削除

つまり後処理を LIFO に登録し、関数スコープを抜けたタイミングで最後に登録したキューから取り出して実行します。ですので大量のループを実行するとキューが爆発します。さらに defer はその瞬間の変数をキャプチャします。

package main

import (
    "os"
)

func doSomething() {
    f, err := os.Open("test1.dat")
    if err != nil {
        return
    }
    defer f.Close() // test1.dat の Close()

    f, err = os.Open("test2.dat")
    if err != nil {
        return
    }
    defer f.Close() // test2.dat の Close()

    f, err = os.Open("test3.dat")
    if err != nil {
        return
    }
    defer f.Close() // test3.dat の Close()
}

func main() {
    doSomething()
}

つまり test1.dat の f も test2.dat の f も test3.dat の f もキューに乗っかります。それを確認する為にこのコードを以下の様に修正します。

package main

import (
    "fmt"
    "os"
)

func closeFile(f *os.File) {
    fmt.Printf("%v を Close() します\n", f.Name())
    f.Close()
}

func doSomething() {
    f, err := os.Open("test1.dat")
    if err != nil {
        return
    }
    defer closeFile(f) // test1.dat の Close()

    f, err = os.Open("test2.dat")
    if err != nil {
        return
    }
    defer closeFile(f) // test2.dat の Close()

    f, err = os.Open("test3.dat")
    if err != nil {
        return
    }
    defer closeFile(f) // test3.dat の Close()
}

func main() {
    doSomething()
}

実行すると以下の様に出力されます(ファイルは存在しているものとします)。

test3.dat を Close() します
test2.dat を Close() します
test1.dat を Close() します

ですのでループの中で defer を使ってはいけません。ただしループの中で処理するステートメントが多く、defer を使って簡素化したい場合は、以下の様に関数スコープを作ってあげる必要があります。

package main

import (
    "fmt"
)

type foo struct {
    n int
}

func Create(n int) *foo {
    fmt.Printf("%v を作成\n", n)
    return &foo{n}
}

func Delete(f *foo) {
    fmt.Printf("%v を削除\n", f.n)
}

func main() {
    fmt.Println("開始")
    for i := 1; i <= 10; i++ {
        func() {
            f := Create(i)
            defer Delete(f)
            // ... 色んな処理
        }()
    }
    fmt.Println("終了")
}

もちろんこの場合、途中で return しても大域脱出にならないので注意が必要です。

Posted at by



2015/12/01


この記事は Go Advent Calendar 2015 の記事ではありません。

ccat は cat やソースに色が付けられて非常に便利なので結構使っていた。

ccat

ふと「これ、ブログ書く時なんかに HTML として出力される機能があたら便利じゃね?」と思ったので ccat に html 出力機能を書いて pull-request を送った。

Htmlize by mattn - Pull Request #35 - jingweno/ccat - GitHub
https://github.com/jingweno/ccat/pull/35

マージされ、新しいバージョンがリリースされたので試したい人はダウンロードするか自分でビルドするといいと思います。ちなみにこんなソースが出力されます。

Posted at by