LoginSignup
3
1

More than 3 years have passed since last update.

golang でファイルアップロードを受けるREST APIサーバを作る

Last updated at Posted at 2020-02-08

概要

goが提供する標準ライブラリを利用して,ファイルをアップロードするエンドポイントを作成した.下記の理由で標準ライブラリを使用することとなり,私のググり力が少ないのか,Qiita等で同じような記事を見つけられなかった.なので詰まったところ等をまとめておきたいと思う.

標準ライブラリ使用の理由
- githubにアクセスできない環境下であること泣
- メンテナンス性
- 自分の理解向上
環境はgo:1.13

学んだところ

詳細は次の節ソースコードを読んでください.

RESTAPIのURI設計

最初は,http://localhost:8080/updateというエンドポイントに対してPOSTでアップロードするように指定していた.しかし,URIに対して動詞を設定するのはアンチパターン.なるべくオブジェクトを一意に指定できるようにし,メソッドで対応するのがベターというアドバイスをいただいた.なので,http://localhost:8080/file というエンドポイントを指定するよう変更した.

bufの処理

メモリが溢れないための,一時的保存領域であるbufという変数は buf := make([]byte, 100) という形で宣言されている.またこれにより自動的に100の領域が確保される.次に,n, err := file.Read(buf)という部分で,ファイルからバイナリデータを読み込み,戻り値として読み込んだ数(n)とエラーの有無を返してくる.このままbufをファイルに保存する処理を書くと,保存したファイルにゴミが残ってしまった.そのため,得たn でスライスのサイズをちょうど良い数に変更することでこの問題をクリアした.

ソースコード

fileupload.go
package main

import(
       "fmt"
       "net/http"
       "encoding/json"
}

func main() {
  http.HandleFunc("/file", handler) 
  http.ListenAndServe(":8080", nil)
}

func fileHandler(w http.ResponseWriter, r *http.Request) {
    // postのみ対応させる
    if r.Method != "POST" {
        http.Error(w, "405 Method Not Allowed", http.StatusMethodNotAllowed)
        return
    }

    file, fileHeader, err := r.FormFile("json")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    uploadedFileName := fileHeader.Filename
    // ファイル名が指定したものじゃないとエラーを吐くように設定
    if uploadedFileName != "file.json" {
        http.Error(w, "Invaild file", http.StatusInternalServerError)
        return
    }

    saveJSONFile, err := os.Create("./" + uploadedFileName)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    var len int64 = 0
    for {
        buf := make([]byte, 100) // buf を用意するのはメモリ節約のため.
        n, err := file.Read(buf)
        buf = buf[:n]       // sliceをnで制限しないと,ループ終了時の処理でゴミが残りアップロード
        if err == io.EOF {    // ファイルを後で読み込むことができないエラーが発生する.
            buf = buf[:n]
        }
        if n == 0 {
            break
        }
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        saveJSONFile.WriteAt(buf,len)
        len += int64(n)
    }
    saveJSONFile.Close()
    file.Close()
    fmt.Fprintf(w, "success")
}

おわりに

golang を書き始めて4ヶ月くらい経ちましたが,いいですね〜.特にエンドポイント等が書きやすく使い勝手がいいです.昨今はマイクロサービス化して,RESTAPIでサービスを接続する流れになってきていると思いますので,一度勉強しておくと良いと思います.

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1