react-nativeからalibaba cloud OSSにファイルをアップロードする
react-nativeからOSSにファイルをアップロードする方法を調べました。
イメージしやすいようにアップロード画面のスクショを貼っておきます。Sendボタンを押したらアップロードされます。
なお、画像の選択にはreact-native-image-pickerを使っています。
選択肢
react-nativeからOSSにファイルをアップロードする手法の選択肢は4つあります。
ali-oss
を使うaliyun-oss-react-native
を使う- function compute + API Gatewayを使う
- function compute + HTTPトリガーを使う
最終的にfunction compute + HTTPトリガーの画像アップロードサービスを作成して実現しました。
ali-ossを使う
OSSの公式のnode.js用SDKとしてali-ossがありますが、依存関係の問題でreact-native上では直接使えないっぽいので無理でした。
aliyun-oss-react-nativeを使う
公式のライブラリとしてaliyun-oss-react-nativeがあるのでこれを使います。
// this.state.imageにはreact-image-pickerで選択した画像が入っています。 import AliyunOSS from 'aliyun-oss-react-native' onSendPress () { const credentials = { accessKeyId: 'xxxxxxxxxxx', accessKeySecret: 'xxxxxxxxxxxxxxxxxxxxxxxxx', endPoint: 'oss-ap-northeast-1.aliyuncs.com' }; const configuration = { maxRetryCount: 3, timeoutIntervalForRequest: 30, timeoutIntervalForResource: 24 * 60 * 60 }; AliyunOSS.initWithPlainTextAccessKey(credentials.accessKeyId, credentials.accessKeySecret, credentials.endPoint, configuration) AliyunOSS.asyncUpload('my bucket name', this.state.image.fileName, `file://${this.state.image.path}`).then((res) => { console.log(res) }).catch((error)=>{ console.log(error) }) }
これでアップロードは可能なのですが、2.5MB程度の画像を送ろうとするとアップロードに20秒、レスポンスが返ってくるまで40秒くらいかかりました。めちゃくちゃ遅い。断念。ってかなんでだろう。。。
function compute + API Gatewayを使う
react-nativeからOSSに直接ファイルを送るのはやめて、一旦こちらで用意したアップロードサービスに画像を送ってそいつにアップロードを任せるようにします。
以下のコードをfunction computeにデプロイします。
const atob = require('atob'); const OSS = require('ali-oss'); module.exports.handler = (event, context, callback) => { const client = new OSS({ region: 'oss-ap-northeast-1', accessKeyId: 'xxxxxxxxxxxx', accessKeySecret: 'xxxxxxxxxxxxxxxxxxxx', }); const request = JSON.parse(event.toString('utf8')); const parameters = JSON.parse(atob(request["body"])); client.useBucket('my bucket name'); client.put(parameters['fileName'], new Buffer(parameters['data'], 'base64'), {mime: parameters['type']}).then((response) => { if (response.res.statusCode === 200) { callback(null, { statusCode: 200, body: {url: response.url}}); } else { callback(null, { statusCode: 400, body: {msg: 'bad request'}}); } }); };
これならいけると思ったのですが、API Gatewayにはリクエストの最大長が2MBという制限(公式ドキュメント)があるので2MB以上のデータを送信しようとすると無情にも503が返ってきてしまうので断念。
function compute + HTTPトリガーを使う
次にHTTPトリガーを試します。こっちは6MBまでのデータを送信できます。
以下のコードをfunction computeにデプロイします。API GatewayとHTTP Triggerでインターフェイスが違うの、結構めんどくさいです。
const OSS = require('ali-oss') const getRawBody = require('raw-body') module.exports.handler = function(request, response, context) { getRawBody(request, (err, data) => { const client = new OSS({ region: 'oss-ap-northeast-1', accessKeyId: 'xxxxxxxxxxxx', accessKeySecret: 'xxxxxxxxxxxxxxxxx', }); client.useBucket('my_bucket_name'); const requestBody = JSON.parse(data.toString('utf8')); console.log(requestBody) client.put(requestBody.fileName, new Buffer(requestBody.data, 'base64'), {mime: requestBody.type}).then((ossRes) => { if (ossRes.res.statusCode === 200) { const respBody = new Buffer(JSON.stringify({url: ossRes.url})) response.setStatusCode(200) response.setHeader('content-type', 'application/json') response.send(respBody) } else { const respBody = new Buffer(JSON.stringify({msg: 'bad request'})) response.setStatusCode(400) response.setHeader('content-type', 'application/json') response.send(respBody) } }); }) }
bodyにいろいろ詰め込んで投げると画像が保存できます。レスポンスも早いです。
$ curl -i -H "Content-Type: application/json" -X POST -d ' {"fileName": "greate2.jpg", "data": "iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAACNklEQVQ4EeWTz0tUURTHP+/xnN7kZAjZkIYllpX0uxYtLBAiqIWioVAglIt+yeAigvAfaNOuRW6UIkiCIFwEQWmrCMqiCEm0smZQMcmZ9+bnc957J96bZiIIW+TOC/fecy/n+73nfM+5SjqdFlZwqCvI5VOtQkLNVvQVlXEVavj/KVtLEE+AFP5HgXBiksyRoyiOg2LbyOMnkLP+WSz34SPMhq2Y+xtJbqtHPoxTitBNp3wCcYXklW7I5ZYnjMZIXe0hNHif9Z+iBC/1kj5z9jehh5aZWVIHD/tE5oE9yJt3sLiI1X0Zc0s1Zl0tzq1+cF3IZNGbTqI2H0PKNLRTJ5B8DhIjL8V4+1Fm6veKEZ2X+OADiYVrJN5/T4zpOZk/3i7zza1ijI1LfGhYYlVhSbyfFCPjlKZpWDK3r0kWzkVEtZ+NllJTKitR21tRgxWo7S0Q3kigtwd94DY07kRt2I6i6fB9oYTxipG7GME1fhC4eQPN/fK1kELRxbYLlmVBKIRStYFMWyf27FTRA9SS9BCNYY0MU/HiNawNogb6rv3pUIRpZb5OqY7T6JEeX/iKqW+o66qKHqU92NYF1Zv8s+o8HYViVN6V6yBOHiYmIZsFO4+yuQZxHNyBu7jJBfCi/zXETKKEysHrR0DVznchuo5aHvIvlDU6gd2HMDtb4PM0wQsRkl0dmDvqyI88R6vdRfZ6n9+zHsB9NUZ26A5k0gW8Vy3fWm7xXvdaJaj7RLKU9+2/QX4CzuX6MmaoX4UAAAAASUVORK5CYII=", "type": "image/jpg"} ' https://xxxxxxxx.ap-northeast-1.fc.aliyuncs.com/2016-08-15/proxy/image_service/upload/ HTTP/1.1 200 OK Access-Control-Expose-Headers: Date,x-fc-request-id,x-fc-error-type,x-fc-code-checksum,x-fc-invocation-duration,x-fc-max-memory-usage,x-fc-log-result,x-fc-invocation-code-version Content-Length: 71 Content-Type: application/json X-Fc-Code-Checksum: 17416432931740708308 X-Fc-Invocation-Duration: 55 X-Fc-Max-Memory-Usage: 25.38 Date: Wed, 05 Sep 2018 02:02:53 GMT {"url":"http://xxxxxxxxx.oss-ap-northeast-1.aliyuncs.com/greate2.jpg"}
さて、react-nativeから画像を投げるようにします。
// this.state.imageにはreact-image-pickerで選択した画像が入っています。 onSendPress () { const body = JSON.stringify({ type: this.state.image.type, fileName: this.state.image.fileName, data: this.state.image.data }) console.log(body) fetch('https://xxxxxxxxx.ap-northeast-1.fc.aliyuncs.com/2016-08-15/proxy/image_service/upload/', { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, body: body }).then(response => { const jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FSkNORVpGTkRJeFJEVTBSa1F6TkRORE56aERNakZFTTBKQlJUWkZOelJHT1RNNE1EZzJSUSJ9.eyJpc3MiOiJodHRwczovL2t1bW9ub3RhbWkuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDViOGIyNGZlYjk3NTFiMjAyOTQyMDkxMSIsImF1ZCI6IjBBcll6OVZqQkR1Mk43M1JSUVhNWVR6S1duNDd6N1E1IiwiaWF0IjoxNTM2MDI3NDMxLCJleHAiOjE1MzYwNjM0MzEsImF0X2hhc2giOiI4UVB4X3NsVWpNc09wZHdDUHV5ZlZnIiwibm9uY2UiOiJQT2M5MjM3NXRWNGFtOEpadlhtNHZGOG93NTRLeG05WSJ9.KKgNaeG7YTp7CP3hHA1v4yRT8NacWTqliGhziTu3IFtX6l7FoXiT5SVri0zYfYrNMRyGVBt5y9t6th8ttTKjVGW6_6Ad8r3OaMezDU_OnKXlqu5JOAKXIgLeVtvkfXkc7gcs2_hB--ZAqQfeM7lbm-86zZUdnTuhZSGzdKaPmuzowUEXSqpB2-HEQqKTVm6jKQqoDxamwLrrHuuUFq7U4-9l_Qx40ScUtwLzlk85aoMoI7au3oUvWdyJdY4x_BAShXsgzqoVXXUREG-nAYIJ6TzbJLOSe-3A3Rf9q4YfUX_WK-cGD4ROBIOfX6GUR5AHYEOL6Xh9p0dvQelb2-pN4w" }).catch(err => { console.log(err) }) }
できました。3MBくらいの画像もすんなりアップロードできます。6MB以上のファイルのアップロードはできないようにしましょう。
まとめ
react-nativeからOSSに画像をアップロードするならimage upload service
を別に立てましょう。