Edited at

React Native にて FormData 形式でファイルをアップロードしようとして詰まった話


はじめに

自分が携わるプロジェクトでは、数100kB程度の軽い画像ファイルを通信する際に、 GraphQL multipart request で直接ファイルを転送しています。

ionic + cordova で動作している既存処理を React Native に移行する際に色々と試行錯誤しました。

かなりニッチなシチュエーションですが、同じ状況に悩む人の救いになれば、と備忘も兼ねて記事に残します。

ニッチなシチュエーション

- 動作環境:React Native

- react-native-signature-pad で生成した署名データ

- 手動で作成した HTTP Header / Body

- content-type: 'multipart/form-data'

- 仕様:GraphQL multipart request


既存のロジック(ionic)

現在動いているシステムでは、http header / body を自前で構成してサーバサイドにポストする処理が実装されていました。

参考までに、下記のような感じです。(ボディ部の定義のみ記載しています。)

file: null は意図的です。詳細はコチラをご覧ください。

const body = new FormData();

body.append(
'operations',
JSON.stringify({
operationName: 'UploadFileMutation',
variables: { file: null },
query: `mutation UploadFileMutation($file: Upload!) {
uploadFile(file: $file) {
success
}
}`
,
}),
);
body.append('map', JSON.stringify({ file: ['variables.file'] }));
body.append('file', this._fileBlob);


ハマったこと(その1)

結論を先に言うと、今回 axios の利用は断念しました。

その理由は、こちらの issue にあります。

生成したデータを axios でサーバ側に post していましたが、リクエストを見ても content-type がセットされていませんでした。

ボディ部のデータが悪いのかな?と思い、色々とトライしてみてもうまくいかず…。

上述の issue では、似たような状況に陥っている人が見受けられました。(React Native 特有の問題のよう)

結局、解決策は見つけられなかったのであるコメントを参考にして、XMLHttpRequest で通信することにしました。

その結果、正しく content-type: 'multipart/form-data' がセットされるようになりました。


ハマったこと(その2)

その1で、content-type は正しくセットされるようになりましたが、ファイルのアップロードはまだうまく処理されていませんでした。

今回のケースでは、既存の処理に倣い、ファイルは Blob で通信を試みていました。

試行錯誤する中で、気になるコメントを見つけました。


true gets stringified when sent to the server, which is interesting.


Boolean で処理したはずの true という値がサーバ側では String の 'true' になっている、という内容でした。

このコメントは axios のケースですが、axios は xhr adapter を利用しているようなので、もしかしたら xhr にも当てはまるのでは?と仮定し、 Blob での処理を断念しました。

代わりに、apollo-upload-client にある、 ReactNativeFile クラスを利用しました。

const file = new ReactNativeFile({

uri: values.signatureDataUrl,
type: mimeType,
name: 'blob',
});

Wiresharkを使ったリクエストの確認やサーバ側の受信データの確認をサボってしまったので、推測が入ってますが、ともあれこれでバックエンドに正しくファイルをアップロードできました。


おわりに

その2で導入している、apollo-upload-client を使えばもっと簡単じゃないの?って思った方もいるかもしれません。

おっしゃる通りです。

いきなり apollo-upload-client を導入しようとして色々とエラーが発生したので、ステップバイステップで既存処理のコピーをやろうとしてまたハマるというお間抜けな展開でした。

とりあえず React Native 上で既存処理と同等の処理は動作したので、apollo-upload-client の導入・リファクタリングはそのうち取り掛かります。