フロント（React）で受け取った画像ファイルをapi（Golang）に渡して最終的にS3に保存するまで

# 概要
フロント:`React`、サーバーサイド：`golang`で記事投稿サイトを作っていました。
画像投稿機能の実装をまとめます。
web frameworkにはそれぞれ、`axios`と`gin`を用いています。

# 実装
## フロント(React)

```react:post.tsx
interface ArticleState {
  files: File[];
}

class Post extends React.Component<{}, ArticleState> {
  constructor(props: {}) {
    super(props);
    this.state = {
      files: [],
    };
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleOnDrop = this.handleOnDrop.bind(this);
  }


  handleOnDrop(acceptedFiles: File[]) {
    this.setState({files: this.state.files.concat(acceptedFiles)});
  }

  async handleSubmit() {
    const formData = new FormData();
    for (var i in this.state.files) {
      formData.append('images[]', this.state.files[i]);
    }

    const postArticleImage = postArticleImageFactory();
    const resImageNames = await postArticleImage(formData);
  }

  render() {
    return (
          <Dropzone accept="image/*" onDrop={this.handleOnDrop}>
          //省略
          <Form.Button content="Submit" onClick={this.handleSubmit} />
    );
  }
}
```
 - 画像投稿フォーム（ここでは`Dropzone`）から画像を投稿します。
 - 投稿ボタンがクリックされると、`formData`に画像を詰めてapiにrequestを飛ばすメソッドを呼びます。


```react:articleAPI.tsx
export const postArticleImageFactory = () => {
  const postArticleImage = async (formData: FormData) => {
    try {
      const response = await axios.post('/api/post/image', formData, {
        headers: {'Content-Type': 'multipart/form-data'},
      });

      if (response.status !== 200) {
        throw new Error('Server Error');
      }

      return response.data;
    } catch (err) {
      throw err;
    }
  };
  return postArticleImage;
};
```
 - `headers: {'Content-Type': 'multipart/form-data'}`を設定しないとapi通信でエラーが発生するので、設定しています。

## バックエンド(Golang)
```go:handler.go

	router := gin.Default()

	router.Use(cors.New(cors.Config{
		AllowOrigins:     []string{"*"},
		AllowMethods:     []string{"GET", "POST", "OPTIONS"},
		AllowHeaders:     []string{"Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization", "accept", "origin", "Cache-Control", "X-Requested-With"},
		ExposeHeaders:    []string{"Content-Length"},
		AllowCredentials: true,
		AllowOriginFunc: func(origin string) bool {
			return true
		},
		MaxAge: 15 * time.Second,
	}))

	api := router.Group("/api")
	{
		api.POST("/post/image", func(c *gin.Context) {
			cntlr.PostImageController(c)
		})
	}
```
 - `gin`を利用してrequestを受け取っています。

```go:service.go
func (s Service) PostImageService(c *gin.Context) []util.ImageName {

	form, _ := c.MultipartForm()
	files := form.File["images[]"]

	var imageNames []util.ImageName
	imageName := util.ImageName{}

	for _, file := range files {

		u, err := uuid.NewRandom()
		if err != nil {
			fmt.Println(err)
		}
		uu := u.String()

		s.dao.PostImageToS3Dao(file, uu)

		imageName.NAME = uu

		imageNames = append(imageNames, imageName)
	}

	return imageNames
}
```
 - `gin.Context`から画像ファイルを取り出しています。
 - S3に保存する画像名を一意にする為、uuidを設定しています。（万が一画像名が同じだとバケットで上書きされる為）

```go:s3.go
func (objs *S3) PostImageToS3(file *multipart.FileHeader, imageName string) error {
	creds := credentials.NewStaticCredentials(objs.APPID, objs.SECRET, "")

	cfg := aws.NewConfig().WithRegion("ap-northeast-1").WithCredentials(creds)
	svc := s3.New(session.New(), cfg)

	f, err := file.Open()

	if err != nil {
		log.Println(err)
	}

	defer f.Close()

	size := file.Size
	buffer := make([]byte, size)

	f.Read(buffer)
	fileBytes := bytes.NewReader(buffer)
	fileType := http.DetectContentType(buffer)
	path := "/media/" + imageName
	params := &s3.PutObjectInput{
		Bucket:        aws.String("<Bucket Name>"),
		Key:           aws.String(path),
		Body:          fileBytes,
		ContentLength: aws.Int64(size),
		ContentType:   aws.String(fileType),
	}
	resp, err := svc.PutObject(params)

	fmt.Printf("response %s", awsutil.StringValue(resp))

	return err
}
```
 - S3に保存するメソッドです。
 - テストしやすいようにS3に保存する目的だけのメソッドです（ゆえに返り値はerror）

---
ソースコード全貌は以下のgithubを参照してください
https://github.com/jpskgc/article