如何通过react / axios和golang / gin将图像上传到S3

如何通过react / axios和golang / gin将图像上传到S3

问题描述:

I want to make functionality to post image to S3 in following steps.

  1. user upload image(s) on the screen.
  2. image files are sent to server after submitting
  3. images are uploaded to S3 in server side(golang).

Now issue is at 3."images are uploaded to S3 in server side(golang)."

There is nil error at server side code.

debug shows nil value at this method.

form, _ := c.MultipartForm()

Here is the codes.

//import

interface ArticleState {
  title: string;
  content: string;
  redirect: boolean;
  files: File[];
}

class Post extends React.Component<{}, ArticleState> {
  constructor(props: {}) {
    super(props);
    this.state = {
      title: '',
      content: '',
      redirect: false,
      files: [],
    };
    this.handleChangeTitle = this.handleChangeTitle.bind(this);
    this.handleChangeContent = this.handleChangeContent.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.renderRedirect = this.renderRedirect.bind(this);
    this.handleOnDrop = this.handleOnDrop.bind(this);
  }

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

  handleChangeTitle(e: React.FormEvent<HTMLInputElement>) {
    this.setState({title: e.currentTarget.value});
  }

  handleChangeContent(e: React.FormEvent<HTMLInputElement>) {
    this.setState({content: e.currentTarget.value});
  }

  handleSubmit() {
    this.setState({
      redirect: true,
    });
    const data = {
      title: this.state.title,
      content: this.state.content,
      files: this.state.files,
    };
    axios.post('http://localhost:4000/api/post', data).then(res => {
      console.log(res);
    });
  }

  renderRedirect = () => {
    if (this.state.redirect) {
      return <Redirect to="/post/finish" />;
    }
  };

  render() {
    return (
      <Container text style={{marginTop: '3em'}}>
        <Form>
          <Form.Input
            label="Title"
            placeholder=""
            name="title"
            value={this.state.title}
            onChange={this.handleChangeTitle}
          />
          <Form.Field
            label="Content"
            placeholder=""
            name="content"
            value={this.state.content}
            rows="20"
            control="textarea"
            onChange={this.handleChangeContent}
          />
          {this.renderRedirect()}
          <input type="file" id="file" hidden /> */}
          <Dropzone accept="image/*" onDrop={this.handleOnDrop}>
            {({getRootProps, getInputProps, open}) => (
              <section>
                <div {...getRootProps()} style={{margin: '20px auto'}}>
                  <input {...getInputProps()} />
                  <p>Drag 'n' drop some files here, or click to select files</p>
                  <button type="button" onClick={open}>
                    Open File Dialog
                  </button>
                </div>
              </section>
            )}
          </Dropzone>
          <Form.Button content="Submit" onClick={this.handleSubmit} />
        </Form>
      </Container>
    );
  }
}

export default Post;
package main

//import

type Article struct {
    ID      int    `json:"id"`
    TITLE   string `json:"title"`
    CONTENT string `json:"content"`
}

var articles []Article

type Param struct {
    Bucket      string
    Key         string
    Expires     string
    ContentType string
}

func main() {

    awsAccessKeyID := "Insert Key Here"
    awsSecretAccessKey := "Insert Secret Here"
    token := ""

    //some codes for mysql connection

    router.Use(
    // some codes
    )

    api := router.Group("/api")
    {
        api.POST("/post", func(c *gin.Context) {
            //some codes for inserting article data in mysql

            creds := credentials.NewStaticCredentials(awsAccessKeyID, awsSecretAccessKey, token)

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

            //Here form value is nill.
            form, _ := c.MultipartForm()
            files := form.File["upload[]"]

            for _, file := range files {
                f, err := file.Open()

                defer f.Close()
                fileInfo, _ := f.(*os.File).Stat()
                size := fileInfo.Size()
                buffer := make([]byte, size)

                f.Read(buffer)
                fileBytes := bytes.NewReader(buffer)
                fileType := http.DetectContentType(buffer)
                path := "/media/" + f.(*os.File).Name()
                params := &s3.PutObjectInput{
                    Bucket:        aws.String("bucketname"),
                    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))
            }

            c.JSON(http.StatusOK, gin.H{"status": "ok"})

        })
    }

    router.Run(":4000")
}

I expected image(s) are uploaded to S3, but actually not uploaded with server error.

The following is error messages.

POST http://localhost:4000/api/post 500 (Internal Server Error)
createError.js:17 Uncaught (in promise) Error: Request failed with status code 500
    at createError (createError.js:17)
    at settle (settle.js:19)
    at XMLHttpRequest.handleLoad (xhr.js:60)
[GIN] 2019/07/18 - 08:47:34 | 204 |      33.322µs |             ::1 | OPTIONS  /api/post

2019/07/18 08:47:34 [Recovery] 2019/07/18 - 08:47:34 panic recovered:
POST /api/post HTTP/1.1
Host: localhost:4000
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Content-Length: 68
Content-Type: application/json;charset=UTF-8
Origin: http://localhost:3000
Referer: http://localhost:3000/post/finish
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36


runtime error: invalid memory address or nil pointer dereference
/usr/local/go/src/runtime/panic.go:82 (0x10426f0)
        panicmem: panic(memoryError)
/usr/local/go/src/runtime/signal_unix.go:390 (0x104251f)
        sigpanic: panicmem()
/Users/jpskgc/go/src/github.com/gin-gonic/gin/context.go:532 (0x17122f5)
        (*Context).MultipartForm: return c.Request.MultipartForm, err
/Users/jpskgc/go/src/github.com/gin-gonic/gin/context.go:147 (0x14fdea9)
        (*Context).Next: c.handlers[c.index](c)
/Users/jpskgc/go/src/github.com/gin-gonic/gin/recovery.go:83 (0x1511939)
        RecoveryWithWriter.func1: c.Next()
/Users/jpskgc/go/src/github.com/gin-gonic/gin/context.go:147 (0x14fdea9)
        (*Context).Next: c.handlers[c.index](c)
/Users/jpskgc/go/src/github.com/gin-gonic/gin/logger.go:240 (0x15109e0)
        LoggerWithConfig.func1: c.Next()
/Users/jpskgc/go/src/github.com/gin-gonic/gin/context.go:147 (0x14fdea9)
        (*Context).Next: c.handlers[c.index](c)
/Users/jpskgc/go/src/github.com/gin-gonic/gin/gin.go:391 (0x1507da9)
        (*Engine).handleHTTPRequest: c.Next()
/Users/jpskgc/go/src/github.com/gin-gonic/gin/gin.go:352 (0x150749d)
        (*Engine).ServeHTTP: engine.handleHTTPRequest(c)
/usr/local/go/src/net/http/server.go:2774 (0x12e2207)
        serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
/usr/local/go/src/net/http/server.go:1878 (0x12dddf0)
        (*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
/usr/local/go/src/runtime/asm_amd64.s:1337 (0x1059ed0)
        goexit: BYTE    $0x90   // NOP

[GIN] 2019/07/18 - 08:47:34 | 500 |  119.560492ms |             ::1 | POST     /api/post
  • frontend: react/axios
  • backend: golang/gin
  • upload library: dropzone
  • storage: amazon s3

I resolved this issue by separating requests. Following is the codes I fixed.

//React
    const data = {
      title: this.state.title,
      content: this.state.content,
    };

    const res = await axios.post('http://localhost:2345/api/post', data);

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

    const resImageNames = await axios.post(
      'http://localhost:2345/api/post/image',
      formData,
      {
        headers: {'Content-Type': 'multipart/form-data'},
      }
    );
  }
//Golang
        api.POST("/post", func(c *gin.Context) {
            u, err := uuid.NewRandom()
            if err != nil {
                fmt.Println(err)
                return
            }
            uu := u.String()
            var article Article
            c.BindJSON(&article)
            ins, err := db.Prepare("INSERT INTO articles(uuid, title,content) VALUES(?,?,?)")
            if err != nil {
                log.Fatal(err)
            }
            ins.Exec(uu, article.TITLE, article.CONTENT)

            c.JSON(http.StatusOK, gin.H{"uuid": uu})

        })
        api.POST("/post/image", func(c *gin.Context) {
            creds := credentials.NewStaticCredentials(awsAccessKeyID, awsSecretAccessKey, token)

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

            form, _ := c.MultipartForm()

            files := form.File["images[]"]

            var imageNames []ImageName
            imageName := ImageName{}

            for _, file := range files {

                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/" + file.Filename
                params := &s3.PutObjectInput{
                    Bucket:        aws.String("article-s3-jpskgc"),
                    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))

                imageName.NAME = file.Filename

                imageNames = append(imageNames, imageName)
            }

            c.JSON(http.StatusOK, imageNames)
        })