如何通过react / axios和golang / gin将图像上传到S3
问题描述:
I want to make functionality to post image to S3 in following steps.
- user upload image(s) on the screen.
- image files are sent to server after submitting
- 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)
})