質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

Amazon S3

Amazon S3 (Simple Storage Service)とはアマゾン・ウェブ・サービスが提供するオンラインストレージサービスです。

AWS(Amazon Web Services)

Amazon Web Services (AWS)は、仮想空間を機軸とした、クラスター状のコンピュータ・ネットワーク・データベース・ストーレッジ・サポートツールをAWSというインフラから提供する商用サービスです。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

2回答

2440閲覧

react/axiosとgolang/ginを使ってS3に画像をアップロードしたいです

jpskgc

総合スコア19

Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

Amazon S3

Amazon S3 (Simple Storage Service)とはアマゾン・ウェブ・サービスが提供するオンラインストレージサービスです。

AWS(Amazon Web Services)

Amazon Web Services (AWS)は、仮想空間を機軸とした、クラスター状のコンピュータ・ネットワーク・データベース・ストーレッジ・サポートツールをAWSというインフラから提供する商用サービスです。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2019/07/18 00:16

編集2019/07/18 01:25

前提・実現したいこと

ローカル環境で動く画像投稿機能を作成しています。

具体的には、以下のプロセスで画像をS3にアップロードしたいです。

  1. ユーザーが画面上から画像をアップロード(複数画像可能)
  2. 投稿ボタンを押すと、画像ファイルがサーバーに渡される
  3. サーバーで画像がS3に渡される処理が実行される

現在問題として、「3. サーバーで画像がS3に渡される処理が実行される」時点でnilエラーが発生しています。

フロントエンドにはReact、バックエンドサーバーにはGolangを利用しています。
フロントからサーバーへの通信にはaxios/ginをそれぞれ用いています。
また、画像アップロードのライブラリとしてdropzoneを使っています。

発生している問題・エラーメッセージ

client

1POST http://localhost:4000/api/post 500 (Internal Server Error) 2createError.js:17 Uncaught (in promise) Error: Request failed with status code 500 3 at createError (createError.js:17) 4 at settle (settle.js:19) 5 at XMLHttpRequest.handleLoad (xhr.js:60)

server

1[GIN] 2019/07/18 - 08:47:34 | 204 | 33.322µs | ::1 | OPTIONS /api/post 2 32019/07/18 08:47:34 [Recovery] 2019/07/18 - 08:47:34 panic recovered: 4POST /api/post HTTP/1.1 5Host: localhost:4000 6Accept: application/json, text/plain, */* 7Accept-Encoding: gzip, deflate, br 8Accept-Language: en-US,en;q=0.9 9Connection: keep-alive 10Content-Length: 68 11Content-Type: application/json;charset=UTF-8 12Origin: http://localhost:3000 13Referer: http://localhost:3000/post/finish 14User-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 15 16 17runtime error: invalid memory address or nil pointer dereference 18/usr/local/go/src/runtime/panic.go:82 (0x10426f0) 19 panicmem: panic(memoryError) 20/usr/local/go/src/runtime/signal_unix.go:390 (0x104251f) 21 sigpanic: panicmem() 22/Users/jpskgc/go/src/github.com/gin-gonic/gin/context.go:532 (0x17122f5) 23 (*Context).MultipartForm: return c.Request.MultipartForm, err 24/Users/jpskgc/go/src/github.com/gin-gonic/gin/context.go:147 (0x14fdea9) 25 (*Context).Next: c.handlers[c.index](c) 26/Users/jpskgc/go/src/github.com/gin-gonic/gin/recovery.go:83 (0x1511939) 27 RecoveryWithWriter.func1: c.Next() 28/Users/jpskgc/go/src/github.com/gin-gonic/gin/context.go:147 (0x14fdea9) 29 (*Context).Next: c.handlers[c.index](c) 30/Users/jpskgc/go/src/github.com/gin-gonic/gin/logger.go:240 (0x15109e0) 31 LoggerWithConfig.func1: c.Next() 32/Users/jpskgc/go/src/github.com/gin-gonic/gin/context.go:147 (0x14fdea9) 33 (*Context).Next: c.handlers[c.index](c) 34/Users/jpskgc/go/src/github.com/gin-gonic/gin/gin.go:391 (0x1507da9) 35 (*Engine).handleHTTPRequest: c.Next() 36/Users/jpskgc/go/src/github.com/gin-gonic/gin/gin.go:352 (0x150749d) 37 (*Engine).ServeHTTP: engine.handleHTTPRequest(c) 38/usr/local/go/src/net/http/server.go:2774 (0x12e2207) 39 serverHandler.ServeHTTP: handler.ServeHTTP(rw, req) 40/usr/local/go/src/net/http/server.go:1878 (0x12dddf0) 41 (*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req) 42/usr/local/go/src/runtime/asm_amd64.s:1337 (0x1059ed0) 43 goexit: BYTE $0x90 // NOP 44 45[GIN] 2019/07/18 - 08:47:34 | 500 | 119.560492ms | ::1 | POST /api/post

該当のソースコード

React

1//import 2 3interface ArticleState { 4 title: string; 5 content: string; 6 redirect: boolean; 7 files: File[]; 8} 9 10class Post extends React.Component<{}, ArticleState> { 11 constructor(props: {}) { 12 super(props); 13 this.state = { 14 title: '', 15 content: '', 16 redirect: false, 17 files: [], 18 }; 19 this.handleChangeTitle = this.handleChangeTitle.bind(this); 20 this.handleChangeContent = this.handleChangeContent.bind(this); 21 this.handleSubmit = this.handleSubmit.bind(this); 22 this.renderRedirect = this.renderRedirect.bind(this); 23 this.handleOnDrop = this.handleOnDrop.bind(this); 24 } 25 26 handleOnDrop(acceptedFiles: File[]) { 27 this.setState({files: this.state.files.concat(acceptedFiles)}); 28 } 29 30 handleChangeTitle(e: React.FormEvent<HTMLInputElement>) { 31 this.setState({title: e.currentTarget.value}); 32 } 33 34 handleChangeContent(e: React.FormEvent<HTMLInputElement>) { 35 this.setState({content: e.currentTarget.value}); 36 } 37 38 handleSubmit() { 39 this.setState({ 40 redirect: true, 41 }); 42 const data = { 43 title: this.state.title, 44 content: this.state.content, 45 files: this.state.files, 46 }; 47 axios.post('http://localhost:4000/api/post', data).then(res => { 48 console.log(res); 49 }); 50 } 51 52 renderRedirect = () => { 53 if (this.state.redirect) { 54 return <Redirect to="/post/finish" />; 55 } 56 }; 57 58 render() { 59 return ( 60 <Container text style={{marginTop: '3em'}}> 61 <Form> 62 <Form.Input 63 label="Title" 64 placeholder="" 65 name="title" 66 value={this.state.title} 67 onChange={this.handleChangeTitle} 68 /> 69 <Form.Field 70 label="Content" 71 placeholder="" 72 name="content" 73 value={this.state.content} 74 rows="20" 75 control="textarea" 76 onChange={this.handleChangeContent} 77 /> 78 {this.renderRedirect()} 79 <input type="file" id="file" hidden /> */} 80 <Dropzone accept="image/*" onDrop={this.handleOnDrop}> 81 {({getRootProps, getInputProps, open}) => ( 82 <section> 83 <div {...getRootProps()} style={{margin: '20px auto'}}> 84 <input {...getInputProps()} /> 85 <p>Drag 'n' drop some files here, or click to select files</p> 86 <button type="button" onClick={open}> 87 Open File Dialog 88 </button> 89 </div> 90 </section> 91 )} 92 </Dropzone> 93 <Form.Button content="Submit" onClick={this.handleSubmit} /> 94 </Form> 95 </Container> 96 ); 97 } 98} 99 100export default Post;

Golang

1package main 2 3//import 4 5type Article struct { 6 ID int `json:"id"` 7 TITLE string `json:"title"` 8 CONTENT string `json:"content"` 9} 10 11var articles []Article 12 13type Param struct { 14 Bucket string 15 Key string 16 Expires string 17 ContentType string 18} 19 20func main() { 21 22 awsAccessKeyID := "Insert Key Here" 23 awsSecretAccessKey := "Insert Secret Here" 24 token := "" 25 26 //mysql 27 db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/article") 28 if err != nil { 29 panic(err.Error()) 30 } 31 defer db.Close() 32 33 err = db.Ping() 34 if err != nil { 35 panic(err.Error()) 36 } 37 38 router := gin.Default() 39 40 router.Use(cors.New(cors.Config{ 41 AllowOrigins: []string{"*"}, 42 AllowMethods: []string{"GET", "POST", "OPTIONS"}, 43 AllowHeaders: []string{"Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization", "accept", "origin", "Cache-Control", "X-Requested-With"}, 44 ExposeHeaders: []string{"Content-Length"}, 45 AllowCredentials: true, 46 AllowOriginFunc: func(origin string) bool { 47 return true 48 }, 49 MaxAge: 15 * time.Second, 50 })) 51 52 api := router.Group("/api") 53 { 54 api.POST("/post", func(c *gin.Context) { 55 //mysql 56 var article Article 57 c.BindJSON(&article) 58 ins, err := db.Prepare("INSERT INTO articles(title,content) VALUES(?,?)") 59 if err != nil { 60 log.Fatal(err) 61 } 62 ins.Exec(article.TITLE, article.CONTENT) 63 64 //aws 65 creds := credentials.NewStaticCredentials(awsAccessKeyID, awsSecretAccessKey, token) 66 67 cfg := aws.NewConfig().WithRegion("ap-northeast-1").WithCredentials(creds) 68 svc := s3.New(session.New(), cfg) 69 70 //ここでformがnilになって落ちてしまいます 71 form, _ := c.MultipartForm() 72 files := form.File["upload[]"] 73 74 for _, file := range files { 75 f, err := file.Open() 76 77 defer f.Close() 78 fileInfo, _ := f.(*os.File).Stat() 79 size := fileInfo.Size() 80 buffer := make([]byte, size) 81 82 f.Read(buffer) 83 fileBytes := bytes.NewReader(buffer) 84 fileType := http.DetectContentType(buffer) 85 path := "/media/" + f.(*os.File).Name() 86 params := &s3.PutObjectInput{ 87 Bucket: aws.String("bucketname"), 88 Key: aws.String(path), 89 Body: fileBytes, 90 ContentLength: aws.Int64(size), 91 ContentType: aws.String(fileType), 92 } 93 resp, err := svc.PutObject(params) 94 95 fmt.Printf("response %s", awsutil.StringValue(resp)) 96 } 97 98 c.JSON(http.StatusOK, gin.H{"status": "ok"}) 99 100 }) 101 } 102 103 router.Run(":4000") 104}

試したこと

デバッグしたところ、サーバーサイドのメソッドでnilが原因で落ちていることがわかりました

form, _ := c.MultipartForm()

以上、お手数ですがよろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

サーバーログのここを見ると
Content-Type: application/json;charset=UTF-8
ここがmultipart/form-dataにならないとc.MultipartForm()は失敗するような気がします。

axiosでContent-Typeを指定する必要があるかも。

(蛇足かもしれませんが、c.MultipartForm()が返すerror値はハンドリングしたほうが良いでしょう。)

投稿2019/07/18 05:28

編集2019/07/18 05:35
nobonobo

総合スコア3367

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

自己解決

Content-Type: application/json

Content-Type: multipart/form-data
を一緒に送信しようとして詰まったので、
テキストと画像イメージを別々に送信することで一旦解決しました。
以下対応部分のコードを記載しておきます。

React

1 const data = { 2 title: this.state.title, 3 content: this.state.content, 4 }; 5 6 const res = await axios.post('http://localhost:2345/api/post', data); 7 8 const formData = new FormData(); 9 for (var i in this.state.files) { 10 formData.append('images[]', this.state.files[i]); 11 } 12 13 const resImageNames = await axios.post( 14 'http://localhost:2345/api/post/image', 15 formData, 16 { 17 headers: {'Content-Type': 'multipart/form-data'}, 18 } 19 ); 20 }

golang

1 api.POST("/post", func(c *gin.Context) { 2 u, err := uuid.NewRandom() 3 if err != nil { 4 fmt.Println(err) 5 return 6 } 7 uu := u.String() 8 var article Article 9 c.BindJSON(&article) 10 ins, err := db.Prepare("INSERT INTO articles(uuid, title,content) VALUES(?,?,?)") 11 if err != nil { 12 log.Fatal(err) 13 } 14 ins.Exec(uu, article.TITLE, article.CONTENT) 15 16 c.JSON(http.StatusOK, gin.H{"uuid": uu}) 17 18 }) 19 api.POST("/post/image", func(c *gin.Context) { 20 creds := credentials.NewStaticCredentials(awsAccessKeyID, awsSecretAccessKey, token) 21 22 cfg := aws.NewConfig().WithRegion("ap-northeast-1").WithCredentials(creds) 23 svc := s3.New(session.New(), cfg) 24 25 form, _ := c.MultipartForm() 26 27 files := form.File["images[]"] 28 29 var imageNames []ImageName 30 imageName := ImageName{} 31 32 for _, file := range files { 33 34 f, err := file.Open() 35 36 if err != nil { 37 log.Println(err) 38 } 39 40 defer f.Close() 41 42 size := file.Size 43 buffer := make([]byte, size) 44 45 f.Read(buffer) 46 fileBytes := bytes.NewReader(buffer) 47 fileType := http.DetectContentType(buffer) 48 path := "/media/" + file.Filename 49 params := &s3.PutObjectInput{ 50 Bucket: aws.String("article-s3-jpskgc"), 51 Key: aws.String(path), 52 Body: fileBytes, 53 ContentLength: aws.Int64(size), 54 ContentType: aws.String(fileType), 55 } 56 resp, err := svc.PutObject(params) 57 58 fmt.Printf("response %s", awsutil.StringValue(resp)) 59 60 imageName.NAME = file.Filename 61 62 imageNames = append(imageNames, imageName) 63 } 64 65 c.JSON(http.StatusOK, imageNames) 66 })

投稿2019/07/22 02:27

編集2019/07/22 02:29
jpskgc

総合スコア19

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問