例えば3000x1500の画像を400x200にして返すようにします。
イメージ処理から新しいスタイルを作成します。
固定幅とオートフィット高さを選択したら可変高さとなります。
これだけです。あとは画像のURLにパラメーターとしてドメイン名/sample.jpg?x-oss-process=style/asshuku
を追加すれば取得できます。
例えば3000x1500の画像を400x200にして返すようにします。
イメージ処理から新しいスタイルを作成します。
固定幅とオートフィット高さを選択したら可変高さとなります。
これだけです。あとは画像のURLにパラメーターとしてドメイン名/sample.jpg?x-oss-process=style/asshuku
を追加すれば取得できます。
react-nativeからOSSにファイルをアップロードする方法を調べました。
イメージしやすいようにアップロード画面のスクショを貼っておきます。Sendボタンを押したらアップロードされます。
なお、画像の選択にはreact-native-image-pickerを使っています。
react-nativeからOSSにファイルをアップロードする手法の選択肢は4つあります。
ali-oss
を使うaliyun-oss-react-native
を使う最終的にfunction compute + HTTPトリガーの画像アップロードサービスを作成して実現しました。
OSSの公式のnode.js用SDKとしてali-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秒くらいかかりました。めちゃくちゃ遅い。断念。ってかなんでだろう。。。
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が返ってきてしまうので断念。
次に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
を別に立てましょう。
これは20x20ピクセルの正方形画像をbase64エンコードしたものです。
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=
ちなみにこんなの↓。適当なスクショ。
上の画像をali-oss
の.put()
メソッドを使ってOSSに送信します。
// index.js const OSS = require('ali-oss') const client = new OSS({ region: 'oss-ap-northeast-1', accessKeyId: 'xxxxxxxxxx', accessKeySecret: 'xxxxxxxxxxxxxxx', }); client.useBucket('myBucket'); const base64EncodedImage = "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="; client.put('great.jpeg', new Buffer(base64EncodedImage, 'base64'), {mime: 'image/jpeg'}).then((r) => { console.log(r) })
できました。
$ node index.js { name: 'great.jpeg', url: 'http://xxxxxxx.oss-ap-northeast-1.aliyuncs.com/great.jpeg', res: { status: 200, statusCode: 200, headers: { server: 'AliyunOSS', date: 'Tue, 04 Sep 2018 10:46:21 GMT', 'content-length': '0', connection: 'keep-alive', 'x-oss-hash-crc64ecma': '4336817615697645645', 'content-md5': 'hgOG0apst+UHhylCs8lorQ==', 'x-oss-server-time': '19' }, size: 0, aborted: false, rt: 146, keepAliveSocket: false, data: <Buffer >, requestUrls: [ 'http://xxxxxxxxxx.oss-ap-northeast-1.aliyuncs.com/great.jpeg' ], timing: null, remotePort: 80, socketHandledRequests: 1, socketHandledResponses: 1 } }
react-native + auth0を使ったときログイン画面に飛ばずにSomething went wrong
とだけ表示されてログにも何も出ない現象で困りました。
アプリのパッケージ名にアンダースコア(_
)が入っているとダメみたいです。作り直したら無事できるようになりました。
react-native-navigationをビルドした時に失敗したのでその解決法をメモします。
/Users/asmsuechan/src/my_mobile/android/app/src/main/java/com/my_mobile/MainApplication.java:6: error: cannot find symbol import com.reactnativenavigation.NavigationReactPackage; ^ symbol: class NavigationReactPackage location: package com.reactnativenavigation 1 error :app:compileDebugJavaWithJavac FAILED
import法が変わっているみたいで、import com.reactnativenavigation.NavigationReactPackage;
ではなくimport com.reactnativenavigation.bridge.NavigationReactPackage;
としたら解決しました。
FAILURE: Build failed with an exception. · Issue #1242 · wix/react-native-navigation · GitHub
Pinterestの2カラムで高さ可変のUIをreact-nativeで作ります。
このレイアウトはMansory
といいます。
Mansoryレイアウトを実現するJSのライブラリはいくつかあって、Packery、Mansory.js、Bricks.jsなどが有名です。しかしこれらはそのままreact-nativeでは使うことができないのでreact-native用のコンポーネントを探しました。
すると以下の2つが見つかりました。
まずスター数が多いreact-native-masonryの方を試したのですが、これが結構曲者でなかなか期待通りに動いてくれないしバグってるしなんかオーナー修正するつもりなさそうだしで諦めました。
次に試したのがreact-native-masonry-layoutです。今回はこちらを使用しました。
これも正直スター数が少なかったりインタフェースが微妙だったりといろいろ不穏な雰囲気はあるのですが、動くのでよしとしました。
実際に使ったコードはこちらです。
import React, {Component} from 'react'; import {Platform, StyleSheet, Text, View, Image, TouchableHighlight, Dimensions} from 'react-native'; import Masonry from 'react-native-masonry-layout'; type Props = {}; export default class App extends Component<Props> { constructor() { super(); this.state = { }; } componentDidMount() { const { width } = Dimensions.get( "window" ); const columnWidth = ( width - 10 ) / 2 - 10; const data2 = [ { image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSPL2GTXDuOzwuX5X7Mgwc3Vc9ZIhiMmZUhp3s1wg0oHPzSP7qC', text: 'wwwww', key: Math.random().toString(36).slice(-8), height: columnWidth / 400 * 500 }, { image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQGYfU5N8lsJepQyoAigiijX8bcdpahei_XqRWBzZLbxcsuqtiH', text: 'wwwww', key: Math.random().toString(36).slice(-8), height: columnWidth / 400 * 600 }, { image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQGYfU5N8lsJepQyoAigiijX8bcdpahei_XqRWBzZLbxcsuqtiH', text: 'wwwww', key: Math.random().toString(36).slice(-8), height: columnWidth / 400 * 700 }, { image: 'https://img.buzzfeed.com/buzzfeed-static/static/2017-03/30/12/asset/buzzfeed-prod-fastlane-01/sub-buzz-24597-1490890739-1.jpg', text: 'wwwww', key: Math.random().toString(36).slice(-8), height: columnWidth / 400 * 600 }, { image: 'https://img.buzzfeed.com/buzzfeed-static/static/2017-03/30/12/asset/buzzfeed-prod-fastlane-01/sub-buzz-24597-1490890739-1.jpg', text: 'wwwww', key: Math.random().toString(36).slice(-8), height: columnWidth / 400 * 300 }, { image: 'https://img.buzzfeed.com/buzzfeed-static/static/2017-03/30/12/asset/buzzfeed-prod-fastlane-01/sub-buzz-24597-1490890739-1.jpg', text: 'wwwww', key: Math.random().toString(36).slice(-8), height: columnWidth / 400 * 400 }, { image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSPL2GTXDuOzwuX5X7Mgwc3Vc9ZIhiMmZUhp3s1wg0oHPzSP7qC', text: 'wwwww', key: Math.random().toString(36).slice(-8), height: columnWidth / 400 * 500 }, { image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSPL2GTXDuOzwuX5X7Mgwc3Vc9ZIhiMmZUhp3s1wg0oHPzSP7qC', text: 'wwwww', key: Math.random().toString(36).slice(-8), height: columnWidth / 400 * 500 }, ] this.refs.list.addItems( data2 ); } render() { return ( <View style={{ flex:1 }}> <View style={{flex: 1, flexGrow: 10, padding: 5}}> <Masonry ref="list" columns={2} renderItem={(item)=><View style={{ margin: 5, backgroundColor: "#fff", borderRadius: 5, overflow: "hidden", }}> <Image source={{ uri: item.image }} style={{ height: item.height }}/> <Text>some text</Text> </View>} /> </View> </View> ); } } const styles = StyleSheet.create({ });
$ react-native --version
react-native-cli: 2.0.1
react-native: 0.56.0
8/29にあった Alibaba Cloud Developers Meetup #6に参加しました。
togetterにまとめがあります。これを見たら雰囲気はわかると思います。 8/29 AliEaters Meetup#6 - Togetter
会場は霞が関にあるWeWork日比谷パークフロントです。行く途中に国会議事堂があったのですけど、初めて見ました。国会議事堂。
会場には想像以上に人がいたのですが、WeWorkのドロップインメンバーもいたのでそう見えたみたいです。
そしてなんと会場はビール飲み放題でした。最高です。めちゃくちゃ飲みました。ごちそうさまです。
今回は15分のセッションが5つあって、
でした。私はスマートロックの話をしました。あんなに酒飲んで発表したのは初めてです
会場の雰囲気はちょっとザワザワしていて(コワーキングスペースだから)発表するにはちょっと楽です。傾注!って感じで聞かれるとドキドキしてしまいますからね。
それぞれめちゃくちゃ簡単にまとめます。
SBクラウドのSissyさんの発表です。
中国のBAT(Baidu, Alibaba, Tencent)に関する情報や独身の日の爆買いなど、中国のインターネット企業に関することを概要的に説明してくれました。
独身の日の購入額が楽天の1年の流通額に近いって話があったんですが、すごいですね。
そして上の画像にあるCreate@TOKYOが10月9日に開催されます。これはビジコンで、優勝賞金はAlibaba Cloudのクーポン300万円分!x.largeのインスタンスを立てまくれます。日本を勝ち抜けば中国に行けるみたいです。
さらにAlibabaはAI/機械学習のコンテストもやっていて、優勝賞金は300万円くらいです。すごい。
Sissyさん写真提供ありがとうございます!
スターティアの石橋さんの発表です。
Aliexpressはいわゆる中華スマホやガジェットを安く見つけることができて、シャオミのスマホも楽に(?)手に入るそうです。
ちなみに私はEssential Phoneを使っています(布教)
次デバイス系の何かを探すときはAliexpressを当たってみることにします。
SBクラウドのWangzzさんの発表です。
今回のミートアップで唯一のガッツリとテクノロジーな発表でした。ブルーグリーンデプロイメントとは何か、の話から始まって実演動画(ターミナルのスクショ動画)に終わるよい発表でした。
たぶんAlibaba Cloud Kubernetesに関する知見は日本語だとどこにもないからとてもありがたいですね。
k8sやっていかねば。。。
不肖私の発表です。
内容はただスマートロックを作ったよ、という話でIoT Platformをゆるふわに布教したといった感じです。
短い発表でしたが終始ゴキゲンな感じに進んだのでなかなか良かったように思っています。
この発表で私が言いたかったことはこれだけです。
アイフォーカスの小野さんの発表です。
前々から地味に話題になりつつもパンピの私はなかなかお目にかかることがなかった農業IoTの話です。聞けて良かったです。
こういう大きな事業は動くお金も大きそうです。
IoT+AIによる農業の専門家のための農業クラウドサービス開始 | クレバアグリ株式会社
AliEatersはこんな人にオススメです。
来月(9月)あるみたいなのでまた参加したいです!