皆様こんにちは。
AMDlabの松本です。
普段はWebエンジニアとして、開発に携わってます。
今回は弊社の自社サービスで使用しているgrpcでバックエンドからBFF(backend for frontend)に対してファイルを送信する方法について簡単にご紹介していきます。
まず、gRPCについて簡単な説明です。
gRPCとはGoogleが開発した RPC(Remote Procedure Call)システムで、HTTP/2を経由した関数呼び出しを行う事ができます。
protoファイルに構造化データをシリアライズするための設定を記載することで、言語やプラットフォームに関係なく関数を定義する事ができます。
また、HTTP/2を経由する必要があるので今回はReactで作成したBFFを通しています。
しかし、ファイルを送信する際には注意が必要です。
と言うのも、gRPCでは受信メッセージは4MBに制限されているからです。
今回の記事は4MB以上のデータをバックエンドからBFFにどのように送信すればよいかを書いていきます。
開発環境
開発環境は下記のようになっています。
golang v1.19.4
google.golang.org/grpc v1.45.0
react v18.2.0
grpc/grpc-js”: “1.4.4
protoファイル
まず、protoファイルの作成を行います。
1 2 3 4 5 6 7 8 9 10 11 12 |
syntax = "proto3"; package file; service FileService { rpc GetFile (GetFileRequest) returns (stream GetFileResponse) {} } message GetFileRequest { } message GetFileResponse { bytes file = 1; } |
protoファイルの設定は以上で完了です。
注意点としてはサービスのResponse側には必ずstreamと付けてください。
これをつけることで、Server streaming RPCとなり、一つのRequestに対して複数のResponseを返却できるようになります。
バックエンド側
次にバックエンド側の処理を記載します。
ここでファイルを送信する際に、1024byteにファイルを分割して送信します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import ( pbFile "github.com/AMDlab/dddbox-platform-server/pkg/proto/file" ) func (s *fileServer) GetFile(r *pbFile.GetFileRequest, stream pbFile.FileService_GetFileServer) error { f := // ファイル取得処理 // 一回の送信で送るサイズです b := 1024 // 一つの要素が1024byteのスライスを作成しています buf := make([]byte, b) for { // bufの中にバイナリーを入れています。nには読み込んだサイズが入ります。 n, err := f.Read(buf) if err == io.EOF { // 終了した場合にこの条件に入ります。終了した場合はnilを返却します。 return nil } if err != nil { return errors.ConvertBinaryError(err) } res := &pbFile.GetFileResponse{ File: buf[:n], } // 読み込んだバイナリーを送信しています。 err = stream.Send(res) if err != nil { return errors.GRPCError(err) } } } |
BFF側
最後に受け取る側のBFFの処理を書いていきます。
ここでは、1024byteで受け取ったファイルを結合して一つのファイルにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import { FileServiceClient } from '/proto/file_grpc_pb' import * as grpc from '@grpc/grpc-js' import base64js from 'base64-js' const client = new FileServiceClient( process.env.PLATFORM_SERVER_ENDPOINT as string, getCredential(), { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true, } ) const request = new GetFileRequest() let splitBinary: Uint8Array[] = [] // fileのサイズを納める変数です。 // ファイルサイズを指定する必要があるのですが、超えているとその分余計なバイナリーが入ってしまうためファイルサイズを保存しています。 let fileSize = 0 const stream: grpc.ClientReadableStream = client.getFile(request) // 送られてきたバイナリーを配列の中に格納しています。 stream.on('data', (data: GetFileResponse) => { fileSize += data.getFile_asU8().byteLength splitBinary = splitBinary.concat(data.getFile_asU8()) }) // バックエンドから終了の信号がきた時に入ります。 // ここで配列に入れたバイナリーを結合しています。 stream.on('end', () => { const binary: Uint8Array = new Uint8Array(fileSize) let size = 0 splitBinary.forEach((value) => { binary.set(value, size) size += value.byteLength }) // 変数binaryにファイルが入っているので、ファイルに対して処理を行ってください。 }) |
下記の箇所でログ出力を行った場合の実行ログを貼ります。
1024byteで分割されて送られているのと、最後は余りが送られてきています。
1 2 3 4 5 6 7 |
import { FileServiceClient } from '/proto/file_grpc_pb' // 送られてきたバイナリーを配列の中に格納しています。 stream.on('data', (data: GetFileResponse) => { console.log(data.getFile_asU8()) fileSize += data.getFile_asU8().byteLength splitBinary = splitBinary.concat(data.getFile_asU8()) }) |
おわりに
BFFにgolangや他の言語を使用して記載されている記事は見かけたのですが、JavaScriptで書かれた記事はあまり見なかったです。
BFFはフロントエンドエンジニアが管理するところが多いのかなぁと、思っているのですがフロントエンドエンジニアでもBFFはgolangを選定しているところが多いのかな? と今回記事を書きながら思いっていました。
それでもフロントエンドエンジニアの方ならJavaScriptは馴染み深いと思うので少しでもお力になれば幸いです!
COMMENTS