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

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

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

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

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

Express

ExpressはNode.jsのWebアプリケーションフレームワークです。 マルチページを構築するための機能セットおよびハイブリッドのWebアプリケーションを提供します。

React.js

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

Q&A

解決済

2回答

2327閲覧

React + Express + WebAssembly(Go) Go側で画像ファイルをbase64エンコード

tomoZQ

総合スコア14

Go

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

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

Express

ExpressはNode.jsのWebアプリケーションフレームワークです。 マルチページを構築するための機能セットおよびハイブリッドのWebアプリケーションを提供します。

React.js

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

0グッド

0クリップ

投稿2022/09/12 05:32

前提

React + Expressで画像・動画投稿ができるシステムを作っております。
純粋にフロント側で画像をbase64に変換し、サーバー側へポストする方法で実装したのですが
base64への変換に時間がかかりすぎるためwasmを使ってみました。
wasmに繋いで適当にコンソール出力できるところまではできています。

実現したいこと

Reactから画像ファイルをwasmに渡し、wasmでbase64への変換を行いその変換結果をReactに返すということを行いたいです。

解決したいこと①Go側で画像ファイルが渡されているのかを確認できる方法を知りたい
解決したいこと②もし画像ファイルが渡されている場合どのようにエンコードすれば良いか。(os.Openに何を渡せば良いかが分かればなんとか行けそうな気がしています。)

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

wasm側の関数を呼び出すことはできているがファイルが渡せているのか確認する方法がわからない。
ファイルを引数として受けれているのならGoでこのように受けたファイルをどのようにエンコードできるのかわからない。

該当のソースコード

wasm.js

javascript

1const go = new window.Go() 2 3export default go

Form.jsx

javascript

1import React, { useState } from 'react' 2import { TextField, Button, Typography, Paper } from '@material-ui/core'; 3import { useDispatch } from 'react-redux' 4import { createChat } from '../../../actions/chats'; 5import useStyles from './styles' 6 7import go from '../../../actions/wasm' 8 9const Form = ({ gossipId }) => { 10 const classes = useStyles() 11 const [chatData, setChatData] = useState({message: '', selectedFile: ''}) 12 const user = JSON.parse(localStorage.getItem('profile')) 13 const dispatch = useDispatch() 14 const [ imageFile, setImageFile ] = useState({identify: '', file: null}) 15 const reader = new FileReader 16 17 const uploadFile = async () => { 18 let val = null 19 let sample = null 20 reader.readAsDataURL(imageFile.file) 21 reader.addEventListener('load', async () => { 22 await WebAssembly.instantiateStreaming(fetch("/wasm/main.wasm"), go.importObject).then((result) => { 23 go.run(result.instance) 24 sample = window.test(imageFile.file) 25 }).catch((err) => { 26 console.log(err) 27 }) 28 29 val = reader.result.replace(/data:.*\/.*base64,/,'') 30 dispatch(createChat({ 31 ...chatData, 32 name: user?.result?.name, 33 gossip: gossipId, 34 userId: user.result._id ? user.result._id : user.result.googleId, 35 file: val, 36 identify: imageFile.identify, 37 test: sample 38 })) 39 }) 40 } 41 42 const handleSubmit = async (e) => { 43 console.log('submit clicked') 44 e.preventDefault() 45 if (imageFile.file) { 46 uploadFile() 47 }else{ 48 dispatch(createChat({ ...chatData, name: user?.result?.name, gossip: gossipId, userId: user.result._id ? user.result._id : user.result.googleId})) 49 } 50 clear() 51 } 52 53 const clear = () => { 54 setChatData({message: '', selectedFile: ''}) 55 setImageFile({identify: '', file: null}) 56 } 57 58 const fileSet = (e) => { 59 const files = e.target.files 60 const file = files[0] 61 const time = new Date().getTime() 62 const identify = `chats/${user?.result?._id ? user?.result?._id : user?.result?.googleId}_${time}_${file.name}` 63 64 setImageFile({identify: identify, file: file}) 65 setChatData({ ...chatData, selectedFile: identify }) 66 } 67 68 if(!user?.result?.name){ 69 return ( 70 <Paper className={classes.chatFormPaper}> 71 <Typography variant='h6' align='center'> 72 Please Sign In to Create Your Own Chat and Like Other's Chat 73 </Typography> 74 </Paper> 75 ) 76 } 77 return ( 78 <Paper className={classes.chatFormPaper}> 79 <form className={classes.form} autoComplete='off' noValidate onSubmit={ handleSubmit }> 80 <Typography className={classes.formTitle} variant='h6' align='center'>Let's exchange opinions</Typography> 81 <TextField 82 className={classes.messageForm} 83 name='message' 84 variant='outlined' 85 label='Message' 86 multiline 87 rows={3} 88 fullWidth 89 value={chatData.message} 90 onChange={e => setChatData({ ...chatData, message: e.target.value })} 91 /> 92 <div className={classes.imageForm}> 93 <input type='file' multiple={false} onChange={(e) => fileSet(e)}/> 94 </div> 95 <Button variant='contained' color='primary' size="large" type="submit" fullWidth>Submit</Button> 96 </form> 97 </Paper> 98 ) 99} 100 101export default Form

main.go

go

1// コメントアウト部分が実行できればと考えています。 2package main 3 4import ( 5 "syscall/js" 6 "fmt" 7 // "os" 8 // "io/ioutil" 9 // "encoding/base64" 10) 11 12func test(this js.Value, vs []js.Value) interface{}{ 13 fmt.Println(this) 14 fmt.Println(vs) 15 fmt.Println(vs[0]) 16 // f, err := os.Open(vs[0].String()) 17 // if err != nil { 18 // panic(err) 19 // } 20 // defer f.Close() 21 22 // b, _ := ioutil.ReadAll(f) 23 // out, _ := os.Create("out") 24 // base64.NewEncoder(base64.StdEncoding, out).Write(b) 25 return "hogehoge" 26} 27 28func setFuncs(){ 29 js.Global().Set("test", js.FuncOf(test)) 30} 31 32func main () { 33 fmt.Println("App assembly process start") 34 setFuncs() 35 done := make(chan struct{}, 0) 36 <-done 37}

試したこと

wasm側で受けとった引数のコンソール出力
イメージ説明

実際に上記main.goのコメントアウトを外して動作を確認
main.go

go

1package main 2 3import ( 4 "syscall/js" 5 "fmt" 6 "os" 7 "io/ioutil" 8 "encoding/base64" 9) 10 11type General interface {} 12 13func test(this js.Value, vs []js.Value) interface{}{ 14 // fmt.Println(this) 15 // fmt.Println(vs) 16 // fmt.Println(vs[0]) 17 f, err := os.Open(vs[0].String()) 18 if err != nil { 19 panic(err) 20 } 21 defer f.Close() 22 23 b, _ := ioutil.ReadAll(f) 24 out, _ := os.Create("out") 25 base64.NewEncoder(base64.StdEncoding, out).Write(b) 26 return "hogehoge" 27} 28 29func setFuncs(){ 30 js.Global().Set("test", js.FuncOf(test)) 31} 32 33func main () { 34 fmt.Println("App assembly process start") 35 setFuncs() 36 done := make(chan struct{}, 0) 37 <-done 38}

結果

イメージ説明

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

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

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

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

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

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

tomoZQ

2022/09/19 10:00 編集

前提の base64への変換に時間がかかりすぎるためwasmを使ってみました。 →base64への変換に時間がかかっていたわけではありませんでした。 画像ファイルを渡すのではなくbase64エンコードされた文字列を渡し、文字列の加工をwasmで行う方法で実装しました。
guest

回答2

0

自己解決

最終的には下記のようにすることで納得いく速度での実装ができました。

javascript

1Form.jsx uploadFile関数 2 3const uploadFile = async () => { 4 let val = null 5 reader.readAsDataURL(imageFile.file) 6 reader.addEventListener('load', async () => { 7 await WebAssembly.instantiateStreaming(fetch("/wasm/main.wasm"), go.importObject).then((result) => { 8 go.run(result.instance) 9 val = window.replaceString(reader.result) 10 }).catch((err) => { 11 console.log(err) 12 }) 13 14 dispatch(createChat({ 15 ...chatData, 16 name: user?.result?.name, 17 gossip: gossipId, 18 userId: user.result._id ? user.result._id : user.result.googleId, 19 file: val, 20 identify: imageFile.identify, 21 })) 22 }) 23 }

go

1package main 2 3import ( 4 "syscall/js" 5 "fmt" 6 "regexp" 7) 8 9func replaceString (this js.Value, s []js.Value) interface{} { 10 re := regexp.MustCompile(`^data:\w+/\w+;base64,`) 11 result := re.ReplaceAllString(s[0].String(), "") 12 return result 13} 14 15func setFuncs(){ 16 js.Global().Set("replaceString", js.FuncOf(replaceString)) 17} 18 19func main () { 20 fmt.Println("App assembly process start") 21 setFuncs() 22 done := make(chan struct{}, 0) 23 <-done 24}

投稿2022/09/19 09:55

tomoZQ

総合スコア14

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

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

0

WASM環境でosパッケージのほとんどは動作しません。
また、ブラウザ上ではいろんな制約があります。

なので、フロントエンド側で読み込んだ画像データなどをWASMの関数に直接渡すなどの手法を試すことをお勧めします。

go

1func uint8array2bytes(dv js.Value) []byte { 2 b := make([]byte, dv.Get("byteLength").Int()) 3 js.CopyBytesToGo(b, global.Get("Uint8Array").New(dv.Get("buffer"))) 4 return b 5} 6 7func image2base64(img js.Value) string { 8 return base64.StdEncoding.EncodeToString(uint8array2bytes(img)) 9}

画像をUInt8Arrayにする方法はJavaScript側で考えてみてください。

追記

base64変換が遅いというのは小さな単位で処理しているからかもしれません。
以下のようにJavascriptに備えられた変換を利用するというのも検討してみてください。
WASMは小さな単位の繰り返しにおいてJSよりも早い結果が得られますが、
JS埋め込みの機能利用だとJSのほうが早いことが多いです。

html

1<html> 2<h2>Example of converting image to string</h2> 3<body> 4<input type="file" onchange="enc(this)" /> 5<script> 6function enc(element) { 7 let reader = new FileReader(); 8 let f = element.files[0]; 9 reader.onloadend = function() { 10 document.write('String Output: ', reader.result); 11 } 12 reader.readAsDataURL(f); 13} 14</script> 15</body></html>

この場合「data:image/png;base64,iViejk...==」という結果が得られますので、「data:image/png;base64,」という部分を取り除くとBASE64に変換した結果を得ることができます。

投稿2022/09/12 10:09

編集2022/09/14 00:51
nobonobo

総合スコア3367

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

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

nobonobo

2022/09/12 10:19 編集

動画などでオンメモリに置くことが難しいという話であれば、ストリーミング技術なども検討する必要がありそうですが、GoとJS側とで相互にストリーミング処理する機能は用意されていません。どちらかが他方のやり方に合わせる必要があります。JSのストリームオブジェクトをGo側から利用するか、Goのio.Reader、io.Writerの操作をJS側から行うなど。
tomoZQ

2022/09/13 12:55

ありがとうございます。 JSで画像ファイルをUint8Arrayに変換してからwasm側に渡すということですね。 まだJS側でUint8Arrayに変換する方法を調査中ですので 完了し、動作が確認できしだいご報告させていただきベストアンサーとさせていただきます。
tomoZQ

2022/09/19 09:49 編集

何度も回答の方編集いただき、さまざまご教示いただきありがとうございました。 私はこの質問を出させていただいた通り、ファイルのエンコードに時間がかかっているとばかり思っていました。 なのでwasm側にファイルを渡して...という方法を検討していたのですが、nobonobo様のご教示によりosパッケージがブラウザ上で機能しないのでそれは難しいとのことがわかりました。 この質問を出させていただく前はnobonoboさんがおっしゃる下記のやり方でやっていたのですが >> この場合「data:image/png;base64,iViejk...==」という結果が得られますので、「data:image/png;base64,」という部分を取り除くとBASE64に変換した結果を得ることができます。 その後いろいろな部分で処理にかかった時間をコンソール出力してみたところエンコードの部分ではなくエンコードした後の文字列加工に時間がかかっていることがわかりました。 具体的には >> data:image/png;base64 これを取り除く処理でreplaceを使用していたのですが replace(/data:.*\/.*base64,/,'') としてしまっており、これに時間がかかっていたことがわかりました。 ここからはせっかくなのでWebAssemblyを使用してみたいということでbase64エンコードした文字列をwasmに渡し、加工したものを返す方法で実装するということをしてしまったので憶測になりますが replaceの第一引数での表現が不味かったので文字列内全てで「data:image/png;base64,」を検索し時間がかかってしまっていたものと思われます。 replace(^/data:.*\/.*base64,/,'') として文字列の先頭であることだけ指定してあげればよかった話かもしれません。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.37%

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

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

質問する

関連した質問