zappaが想定しないAWSアカウントにデプロイされて焦った話し
世間は、最近サーバレスが盛んで業務でlambdaを使用するメンバも増えてきましたが自分は なかなか使う機会がなかったのですが、PyConJPで弊社ブースを出すに当たってなにか展示物を作る 必要があったので、Zappa(GitHub - Miserlou/Zappa: Serverless Python Web Services)を使ってみようと思いました。
Zappaとは
Python/flaskベースのAWSの Serverless frameworkでAPI Gateway + lambda + S3を使用するようです。
テストアプリを作ってみた
zappa で hello world するまで - Qiitaを参考にさせてもらいながら もりもり作成して、デプロイしたわけです。
こんな感じで設定ファイルを書いて
{ "dev": { "app_function": "test.app", "aws_region": "ap-northeast-1", "profile_name": "default", "s3_bucket": "zappa-xyzxyzxyzxyz" } }
そしてデプロイしたわけです。
$ zappa deploy dev Calling deploy for stage dev.. Creating demo-zappa-dev-ZappaLambdaExecutionRole IAM Role.. Creating zappa-permissions policy on demo-zappa-dev-ZappaLambdaExecutionRole IAM Role. Warning! Your project and virtualenv have the same name! You may want to re-create your venv with a new name, or explicitly define a 'project_name', as this may cause errors. Downloading and installing dependencies.. Packaging project as zip.. Uploading demo-zappa-dev-1498726098.zip (7.6MiB).. 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 7.94M/7.94M [00:01<00:00, 4.31MB/s] Scheduling.. Scheduled demo-zappa-dev-zappa-keep-warm-handler.keep_warm_callback with expression rate(4 minutes)! Uploading demo-zappa-dev-template-1498726129.json (1.6KiB).. 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1.64K/1.64K [00:00<00:00, 5.09KB/s] Waiting for stack demo-zappa-dev to create (this can take a bit).. 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 4/4 [00:15<00:00, 5.71s/res] Deploying API Gateway.. Deployment complete!: https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev
すげーデプロイできた!!。そして、実行。
$ curl -l https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev hello from Flask!
おお、楽すぎるーと感動しつつ・・。さて、マネージメントコンソールでどんな感じなのかなと 見てみました・・・。
何故か、該当するFunctionが見当たらない・・。
profileを確認してみる。
zappa_settings.jsonでは、defaultをしていました。
$ cat ~/.aws/config [default] region = ap-northeast-1 [profile xxxxxx] output = json region = ap-northeast-1 [profile yyyyyy] region = ap-northeast-1 output = json # @@@@は伏せ字 $ cat ~/.aws/credentials [xxxxxx] aws_access_key_id = @@@@ aws_secret_access_key = @@@@ [yyyyyy] aws_access_key_id = @@@@ aws_secret_access_key = @@@@
???。defaultのaccess_keyとsecret_access_key が定義されていないのに、なぜかデプロイされちゃってます・・・。
調査する。
$ aws lambda list-functions { "Functions": [ { "FunctionName": "demo-zappa-dev", "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXX:function:demo-zappa-dev", "Runtime": "python3.6", "Role": "arn:aws:iam::[デプロイ先のAWSアカウント]:role/demo-zappa-dev-ZappaLambdaExecutionRole", "Handler": "handler.lambda_handler", "CodeSize": 7939825, "Description": "Zappa Deployment", "Timeout": 30, "MemorySize": 512, "LastModified": "2017-06-29T09:17:27.435+0000", "CodeSha256": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "Version": "$LATEST" } ] }
デプロイされたlambda functionのAWSアカウントが分かりました。 しかし、いまいち覚えがないアカウント・・・。
$ aws sts get-caller-identity { "UserId": [デプロイ先のAWSアカウント], "Account": [デプロイ先のAWSアカウント], "Arn": "arn:aws:iam::[デプロイ先のAWSアカウント]:root" }
うーん、これも同じ。~/.aws/configは使用していない模様。
$ aws configure list Name Value Type Location ---- ----- ---- -------- profile <not set> None None access_key ****************XXXX boto-config secret_key ****************4YT1 boto-config region ap-northeast-1 config-file ~/.aws/config
configの一覧が確認できるようなので、試すとboto-config というキーワードがあります。それで思い出しました。以前、botoを使用するために /etc/boto.cfgを作成したことを・・
試しにboto.cfgを削除して、デプロイすると見事にエラーとなりました。
$ zappa deploy dev Calling deploy for stage dev.. Oh no! An error occurred! :( ============== Traceback (most recent call last): ・・・ ============== Need help? Found a bug? Let us know! :D File bug reports on GitHub here: https://github.com/Miserlou/Zappa And join our Slack channel here: https://slack.zappa.io Love!, ~ Team Zappa!
まとめ
とりあえず、原因は分かりましたが、zappaのデプロイ時にzappa_settings.json に該当するprofileが無かった場合はせめてエラーメッセージ出力してデプロイしないで欲しいなあとも思ったのですが botoの挙動なのかもしれないので、ドキュメントを確認しました。
これ見る限り、 botoの仕様ぽいですね。良い勉強になりました。
The mechanism in which boto3 looks for credentials is to search through a list of possible locations and stop as soon as it finds credentials. The order in which Boto3 searches for credentials is: Passing credentials as parameters in the boto.client() method Passing credentials as parameters when creating a Session object Environment variables Shared credential file (~/.aws/credentials) AWS config file (~/.aws/config) Assume Role provider Boto2 config file (/etc/boto.cfg and ~/.boto) Instance metadata service on an Amazon EC2 instance that has an IAM role configured.
実践AWS Lambda ~「サーバレス」を実現する新しいアプリケーションのプラットフォーム~
- 作者: 西谷圭介
- 出版社/メーカー: マイナビ出版
- 発売日: 2017/06/09
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
サーバーレスシングルページアプリケーション ―S3、AWS Lambda、API Gateway、DynamoDB、Cognitoで構築するスケーラブルなWebサービス
- 作者: Ben Rady,吉田真吾,笹井崇司
- 出版社/メーカー: オライリージャパン
- 発売日: 2017/06/23
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
AWS Lambda: A Guide to Serverless Microservices (English Edition)
- 作者: Matthew Fuller
- 発売日: 2016/01/11
- メディア: Kindle版
- この商品を含むブログを見る
Amazon Web Services クラウドネイティブ・アプリケーション開発技法 一番大切な知識と技術が身につく
- 作者: 佐々木拓郎,佐藤瞬,石川修,高柳怜士,佐藤雄也,岸本勇貴
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2016/04/20
- メディア: Kindle版
- この商品を含むブログを見る
docker for mac + nginxのログをrsyslogコンテナで出力してみた
docker for mac + nginx + オレオレ証明書でローカルSSL環境を作ったメモ - katekichiのゆるブログ
前回の続きで、ログの集中管理する的なことをやってみたかったので rsyslogコンテナに吐き出してみることにしました。
ログの集中管理というと、「fluentd」(http://www.fluentd.org/)が主流だと思いますが 今回は、基本的なところを抑えたかったのでrsyslogにしました。
rsyslogサーバーを立てる
http://d.hatena.ne.jp/tmatsuu/20140603/1401811893 の記事を参考に立てました。
Dockerfile作成
FROM centos:latest RUN yum install -y rsyslog ADD remote.conf /etc/rsyslog.d/ EXPOSE 514 CMD ["/sbin/rsyslogd", "-n"]
remote.conf作成(rsyslog.conf)
$ModLoad imudp $UDPServerRun 514 $AllowedSender UDP, 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 local1.* /var/log/nginx_access_log local2.* /var/log/nginx_error_log
イメージビルド
$ docker build --tag simple-rsyslog .
コンテナ起動
$ docker run -d -p 514:514/udp -h "logserver" simple-rsyslog
テストしてみる
loggerコマンドを使用して、以前構築したnginxコンテナからログを送信してみます。
rsyslogコンテナのIPは、以下で調べました。
$ docker inspect -format="{{ .NetworkSettings.IPAddress }}" コンテナID
nginxコンテナからログ送信
$ docker exec -it [nginxのコンテナID] /bin/bash # コンテナに入る # logger -n[コンテナのIP] -p local1.info testtesttest # logger -n[コンテナのIP] -p local2.error errorerrorerrorerror
rsyslogコンテナでログの確認
$ docker exec -it [rsyslogのコンテナID] /bin/bash # コンテナに入る # tail -f /var/log/nginx_access_log Jun 27 10:07:16 172.17.0.3 <someone>: testtesttest # tail -f /var/log/nginx_error_log Jun 27 10:12:01 172.17.0.3 <someone>: errorerror12345678
ログフォーマット等の調整は必要そうですが、出力することはできました。
nginxイメージの修正
nginx.confのログ設定を変更します。
access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; ↓ access_log syslog:server=logserver,facility=local1 main; error_log syslog:server=logserver,facility=local2 notice;
イメージビルドと起動(--add-host
で「logserver」を指定)
$ docker build --tag webapp-syslog . $ docker run -d --name webapp-syslogp -p 443:443 -p 80:80 --add-host logserver:[rsyslogのIP] webapp-syslog
rsyslogコンテナでログ確認
$ docker exec -it [rsyslogのコンテナID] /bin/bash # コンテナに入る # tail -f /var/log/nginx_access_log Jun 27 11:08:12 f4809369ff7e nginx: 172.17.0.1 - - [27/Jun/2017:11:08:12 +0000] "GET / HTTP/1.1" 200 132 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36" "-" Jun 27 11:08:22 f4809369ff7e nginx: 172.17.0.1 - - [27/Jun/2017:11:08:22 +0000] "GET / HTTP/1.1" 200 132 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36" "-" Jun 27 11:08:24 f4809369ff7e nginx: 172.17.0.1 - - [27/Jun/2017:11:08:24 +0000] "GET / HTTP/1.1" 200 132 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36" "-"
まとめ
思いつきでやってみましたが、rsyslogの知見がそもそも無いのに、Docker上で構築するってのが 少し敷居が高かったです(そもそもDockerのオペレーションも怪しい・・)。
何事もステップ・バイ・ステップですね。良い勉強になりました。
記事読まれている方で、この辺り詳しい方、是非アドバイス頂けると嬉しいです。
- 作者: 鶴長鎮一
- 出版社/メーカー: 技術評論社
- 発売日: 2011/04/22
- メディア: 大型本
- 購入: 2人 クリック: 27回
- この商品を含むブログ (1件) を見る
docker for mac + nginx + オレオレ証明書でローカルSSL環境を作ったメモ
nginxのローカルSSL環境が必要になり、docker for macとオレオレ証明書で作ったので備忘録としてまとめました。
以下の記事を参考に構築してみました。
docker の nginx イメージの設定ファイルを眺めながら、独自ページを表示します。 - Qiita
nginxのdokcerイメージを取得
Docker Hubから最新のnginxイメージを取得する
詳細は、以下の記事を参照
$ docker pull nginx:latest
docker コマンド 基本のキ(nginx のコンテナを実行してみる) - Qiita
環境構築
$ mkdir nginx; cd $ _ $ touch Dockerfile $ mkdir html; cd $_ $ touch index.html $ tree . ├── Dockerfile └── html └── index.html
index.html
<html> <head> <meta charset="UTF-8"> <title>SSL Test</title> </head> <body> <h1>SSL Test</h1> </body> </html>
DockerFile作成
FROM nginx COPY ./html /usr/share/nginx/html
Dockerイメージの作成とコンテナ立ち上げ
$ docker build --tag ssl-nginx . Sending build context to Docker daemon 4.096 kB Step 1/2 : FROM nginx ---> cc1b61406712 Step 2/2 : COPY ./html /usr/share/nginx/html ---> Using cache ---> 2cc22a506a12 Successfully built 2cc22a506a12 $ docker run -d --name ssl-nginx-container -p 80:80 ssl-nginx 651eb9b5642d13274aed51f535d7014754bb001bdcfdf90d7e194bcd5c188b74
ブラウザで確認(http://localhost/)
SSL対応するためにオレオレ証明書の作成をします。 この辺りは、こちらを参考にしました。 DockerでNginxのコンテナを作成し、https化してWebページやサービスを公開する方法 | Black Everyday Company
$ brew list openssl $ mkdir keys; cd $ _ ### 秘密鍵を暗号化してしまうと、コンテナ起動時(Nginx起動時)に 「Enter PEM pass phrase」とパスワードが求められ起動できないようなので暗号化はしない。### $ openssl genrsa 2048 > server.key ### XXXXは適当に入力 ### $ openssl req -new -key server.key > server.csr You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:JP State or Province Name (full name) [Some-State]:XXXX Locality Name (eg, city) []:XXXX Organization Name (eg, company) [Internet Widgits Pty Ltd]:XXXX Organizational Unit Name (eg, section) []:XXXX Common Name (e.g. server FQDN or YOUR name) []:example.com Email Address []:hoge@example.com Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: <--- パスワードは空白でOK An optional company name []: # 有効期限はざっくり100日で $ openssl x509 -in server.csr -days 100 -req -signkey server.key > server.crt # 現状の構成 $ tree . ├── Dockerfile ├── html │ └── index.html └── keys ├── server.crt ├── server.csr └── server.key
nginx.confの設定
HTTP(ポート80)で受けたリクエストはHTTPS(ポート443)にリダイレクトする設定にしておきます。
server { listen 80; server_name localhost; return 301 https://$host$request_uri; } server { server_tokens off; access_log /var/logs/access.log; error_log /var/logs/error.log; listen 443; server_name localhost; ssl on; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_certificate /etc/nginx/server.crt; ssl_certificate_key /etc/nginx/server.key; location / { alias /var/www/; } }
DockerFileの変更
FROM nginx ADD ./html /var/www/html ADD ./app.conf /etc/nginx/conf.d/app.conf ADD ./keys/server.crt /etc/nginx/server.crt ADD ./keys/server.key /etc/nginx/server.key RUN chmod 755 -R /var RUN chmod 400 /etc/nginx/server.key EXPOSE 443 CMD ["nginx", "-g", "daemon off;"]
Dockerイメージの作成とコンテナ立ち上げ
$ docker build --tag webapp . Sending build context to Docker daemon 12.29 kB Step 1/9 : FROM nginx ---> cc1b61406712 Step 2/9 : ADD ./html /var/www/html ---> Using cache ---> 102243bc267d Step 3/9 : ADD ./app.conf /etc/nginx/conf.d/app.conf ---> Using cache ---> 00668c5641b7 Step 4/9 : ADD ./keys/server.crt /etc/nginx/server.crt ---> Using cache ---> 85ee3c0da89a Step 5/9 : ADD ./keys/server.key /etc/nginx/server.key ---> Using cache ---> f3095cadf8a3 Step 6/9 : RUN chmod 755 -R /var ---> Running in 8eab56aefa1c ---> 565d1f1e9f07 Removing intermediate container 8eab56aefa1c Step 7/9 : RUN chmod 400 /etc/nginx/server.key ---> Running in fe5c00f05409 ---> 02c65246caec Removing intermediate container fe5c00f05409 Step 8/9 : EXPOSE 443 ---> Running in 1e67a2a92994 ---> d95cc7a21aa0 Removing intermediate container 1e67a2a92994 Step 9/9 : CMD nginx -g daemon off; ---> Running in 0efbccec8be8 ---> 437f35b60db0 Removing intermediate container 0efbccec8be8 Successfully built 437f35b60db0 $ docker run -d --name webapp -p 443:443 -p 80:80 webapp 2e389797c84eb2162c0f67ab13eb865966ea70ecead1077a61055eadd869e952
ブラウザで確認(https://localhost/)
とりあえずできました。
最後にログは、コンテナ外に出したいのでその辺を試します。
ホストのディレクトリにマウントする
以下のように-v でホストのディレクトリにマウントするのが まずは、手っ取り早い方法かなと思いした。
$ docker run -d -p 443:443 -p 80:80 -v /tmp/sslnginx-log:/var/log/nginx webapp
Syslogで吐き出す方法もありますが、こちらは別の機会に試そうと思います。 nginx-1.7.1でsyslogにログを飛ばせるようになったので試してみた - Dマイナー志向
まとめ
最近、手を動かすことが少なくなってきているように感じたのでローカルの SSLテスト環境を作ろうと思ってやってみましたが、Dockerの理解がまだ浅かったので 良い勉強になりました。
あと普段の業務では、SSL証明書をAWSのELBに配置してしまっているため、直接nginxに 設定する良い機会になりました。
今回作成した環境は、githubにあります。 GitHub - y-nakazawa/docker-sslnginx
- 作者: 久保達彦,道井俊介
- 出版社/メーカー: 技術評論社
- 発売日: 2016/01/16
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (4件) を見る
nginx実践ガイド impress top gearシリーズ
- 作者: 渡辺高志
- 出版社/メーカー: インプレス
- 発売日: 2017/02/16
- メディア: Kindle版
- この商品を含むブログを見る
- 作者: Clement Nedelcu,長尾高弘
- 出版社/メーカー: アスキー・メディアワークス
- 発売日: 2011/04/21
- メディア: 大型本
- 購入: 2人 クリック: 714回
- この商品を含むブログ (23件) を見る
- 作者: Dimitri Aivaliotis,高橋基信
- 出版社/メーカー: オライリージャパン
- 発売日: 2013/10/26
- メディア: 大型本
- この商品を含むブログ (7件) を見る
- 作者: 鶴長鎮一,馬場俊彰
- 出版社/メーカー: 技術評論社
- 発売日: 2015/09/26
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
「Electronではじめるアプリ開発」を写経してみた ⑥
またかなり間が空いてしまったのと、写経したものの期待通りの動きではなく調査しましたが 以下の理由からかなりハマってしまっていました。
Electron上でのReactのDebug方法を確立できていなかった。
ES2016と、Promiseの理解不足から原因の特定に時間が掛かった。
そもそもFirebaseのお作法が分かってなく、原因の特定に時間が掛かった。
※1.に関しては、以下を参考にしてDebug環境の構築をしたいと思います。 qiita.com
※2. 3.の課題については別途調査してまとめようと思います。
今回は、チャットルーム一覧の実装から
src/renderer/Rooms.jsx
import React from "react"; import { hashHistory } from "react-router"; import RoomItem from "./RoomItem"; import firebase from "firebase/firebase-browser"; const ICON_CHAT_STYLE = { fontSize: 120, color: "#DDD" }; const FORM_STYLE = { display: "flex" }; const BUTTON_STYLE = { marginLeft: 10 }; export default class Rooms extends React.Component { constructor(props) { super(props); this.state = { roomName: "", rooms: [] }; this.db = firebase.database(); this.handleOnChangeRootName = this.handleOnChangeRootName.bind(this); this.handleOnSubmit = this.handleOnSubmit.bind(this); } componentDidMount() { this.fetchRooms(); } handleOnChangeRootName(e) { this.setState({ roomName: e.target.value }); } handleOnSubmit(e) { const { roomName } = this.state; e.preventDefault(); if (!roomName.length) { return; } // Make new Chatroom for a Firebase const newRoomRef = this.db.ref("/chatrooms").push(); const newRoom = { description: roomName }; newRoomRef.update(newRoom).then(() => { this.setState({ roomName: "" }); return this.fetchRooms().then(() => { hashHistory.push(`/rooms/${newRoomRef.key}`); }); }); } fetchRooms() { return this.db.ref("/chatrooms").limitToLast(20).once("value").then(snapshot => { const rooms = []; snapshot.forEach(item => { rooms.push(Object.assign({ key: item.key }, item.val())); }); this.setState({ rooms }); }); } // Drawing the left pane renderRoomList() { const { roomId } = this.props.params; const { rooms, roomName } = this.state; return ( <div className="list-group"> { rooms.map(r => <RoomItem room={r} key={r.key} selected={r.key == roomId} />)} <div className="list-group-header"> <form style={FORM_STYLE} onSubmit={this.handleOnSubmit} > <input type="test" className="form-control" placeholder="New room" value={roomName} onChange={this.handleOnChangeRootName} /> <button className="btn btn-default" style={BUTTON_STYLE}> <span className="icon icon-plus" /> </button> </form> </div> </div> ); } // Drawing the right pane renderRoom() { if (this.props.children) { return this.props.children; } else { return ( <div className="text-center"> <div style={ICON_CHAT_STYLE}> <span className="icon icon-chat" /> </div> <p> Join a chat room from the sidebar or create your chat room. </p> </div> ); } } render() { return ( <div className="pane-group"> <div className="pane-sm slider">{this.renderRoomList()}</div> <div className="pane">{this.renderRoom()}</div> </div> ); } }
以下は、チャットルーム一覧の個々のアイテムの実装
src/renderer/RoomItem.jsx
import React from "react"; import { Link } from "react-router"; const LINK_STYLE = { color: "inherit", textDecoration: "none" }; export default function RoomItem(props) { const { selected } = props; const { description, key } = props.room; return( <div className={selected ? "list-group-item selected" : "list-group-item"}> <Link to={`/rooms/${key}`} style={LINK_STYLE}> <div className="media-body"> <strong>{description}</strong> </div> </Link> </div> ); }
ルームを追加すると一覧に登録されることが確認できました。
Firebaseのchatroomsにも登録されました。
今回はここまで!!
Electronではじめるアプリ開発 ~JavaScript/HTML/CSSでデスクトップアプリを作ろう
- 作者: 野口将人,倉見洋輔
- 出版社/メーカー: 技術評論社
- 発売日: 2017/03/28
- メディア: 大型本
- この商品を含むブログ (1件) を見る
Reactビギナーズガイド ―コンポーネントベースのフロントエンド開発入門
- 作者: Stoyan Stefanov,牧野聡
- 出版社/メーカー: オライリージャパン
- 発売日: 2017/03/11
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
入門 React ―コンポーネントベースのWebフロントエンド開発
- 作者: Frankie Bagnardi,Jonathan Beebe,Richard Feldman,Tom Hallett,Simon HØjberg,Karl Mikkelsen,宮崎空
- 出版社/メーカー: オライリージャパン
- 発売日: 2015/04/03
- メディア: 大型本
- この商品を含むブログ (2件) を見る
- 作者: 掌田津耶乃
- 出版社/メーカー: 秀和システム
- 発売日: 2016/09/16
- メディア: 単行本
- この商品を含むブログを見る
JavaScriptエンジニアが手っ取り早くReactの基礎を理解するための「超」入門書
- 作者: 天田士郎
- 発売日: 2017/02/05
- メディア: Kindle版
- この商品を含むブログを見る
「Electronではじめるアプリ開発」を写経してみた ⑤
諸々立て込んでいて停滞していた写経再開しました。今回は、Firebaseの設定から実装します。
目次:
Firebaseのセットアップ
上記のサイトでアカウント生成等をして取得した接続情報 を src/render
er/app.jsに貼ります。
import React from "react"; import { render } from "react-dom"; import { Router, Route, hashHistory } from "react-router"; import Login from "./Login"; import Signup from "./Signup"; import Rooms from "./Rooms"; import Room from "./Room"; import firebase from "firebase/firebase-browser"; const appRouting = ( <Router history={hashHistory}> <Route path="/"> <Route path="login" component={Login} /> <Route path="signup" component={Signup} /> <Route path="rooms" component={Rooms}> <Route path=":roomId" component={Room} /> </Route> </Route> </Router> ); if (!location.hash.length) { location.hash = "#/login"; } // Firebaseの初期化 ←ココ追加 var config = { apiKey: "xxxxxxx", authDomain: "electron-chat-cyyyyyy.firebaseapp.com", databaseURL: "https://electron-chat-cyyyyyy.firebaseio.com", projectId: "electron-chat-cyyyyyy", storageBucket: "electron-chat-cyyyyyy.appspot.com", messagingSenderId: "xxxxxxxx" }; firebase.initializeApp(config); render(appRouting, document.getElementById("app"));
ログイン画面の実装
src/renderer/Login.jsxにゴリゴリ実装します。 Validationはもう少し後々、工夫できそうです
import React from "react"; import { Link, hashHistory } from "react-router"; import Errors from "./Errors"; import firebase from "firebase/firebase-browser"; const FORM_STYLE = { margin: "0, auto", padding: 30 }; const SIGNUP_LINK_STYLE = { display: "inline-block", marginLeft: 10 }; export default class Login extends React.Component { constructor(props) { super(props); this.state = { email: localStorage.userEmail || "", password: localStorage.userPassword || "", errors: [] }; this.handleOnChangeEmail = this.handleOnChangeEmail.bind(this); this.handleOnChangePassword = this.handleOnChangePassword.bind(this); this.handleOnSubmit = this.handleOnSubmit.bind(this); } handleOnChangeEmail(e) { this.setState({ email: e.target.value }); } handleOnChangePassword(e) { this.setState({ password: e.target.value }); } handleOnSubmit(e) { const { email, password } = this.state; const errors = []; let isValid = true; e.preventDefault(); if (!email.length) { isValid = false; errors.push("Email address can't be blank."); } if (!password.length) { isValid = false; errors.push("Password can't be blank."); } if (!isValid) { this.setState({ errors }); return; } firebase.auth().signInWithEmailAndPassword(email, password).then(() => { localStorage.userEmail = email; localStorage.userPassword = password; hashHistory.push("/rooms"); }).catch(() => { this.setState({ errors: ["Incorrect email or password."] }); }); } render() { return ( <form style={FORM_STYLE} onSubmit={this.handleOnSubmit}> <Errors errorMessages={this.state.errors} /> <div className="form-group"> <label>Email address*</label> <input type="email" className="form-control" placeholder="email" value={this.state.email} onChange={this.handleOnChangeEmail} /> </div> <div className="form-group"> <label>Password</label> <input type="password" className="form-control" placeholder="password" value={this.state.password} onChange={this.handleOnChangePassword} /> </div> <div className="form-group"> <button className="btn btn-large btn-default">Login</button> <div style={SIGNUP_LINK_STYLE}> <Link to="/signup">create new account</Link> </div> </div> </form> ); } }
実行するとこんな感じになりました。
サインアップ画面の実装
import React from "react"; import { Link, hashHistory } from "react-router"; import Errors from "./Errors"; import firebase from "firebase/firebase-browser"; const SIGNUP_FORM_STYLE = { margin: "0, auto", padding: 30 }; const CANCEL_BUTTON_STYLE = { marginLeft: 10 }; export default class Login extends React.Component { constructor(props) { super(props); this.state = { email: "", password: "", name: "", photoURL: "", errors: [] }; this.handleOnChangeEmail = this.handleOnChangeEmail.bind(this); this.handleOnChangePassword = this.handleOnChangePassword.bind(this); this.handleOnChangeName = this.handleOnChangeName.bind(this); this.handleOnChangePhotoURL = this.handleOnChangePhotoURL.bind(this); this.handleOnSubmit = this.handleOnSubmit.bind(this); } handleOnChangeEmail(e) { this.setState({ email: e.target.value }); } handleOnChangePassword(e) { this.setState({ password: e.target.value }); } handleOnChangeName(e) { this.setState({ name: e.target.value }); } handleOnChangePhotoURL(e) { this.setState({ photoURL: e.target.value }); } handleOnSubmit(e) { const { email, password, name, photoURL } = this.state; const errors = []; let isValid = true; e.preventDefault(); if (!email.length) { isValid = false; errors.push("Email address can't be blank."); } if (!password.length) { isValid = false; errors.push("Password can't be blank."); } if (!name.length) { isValid = false; errors.push("Name can't be blank."); } if (!isValid) { this.setState({ errors }); return; } firebase.auth().createUserWithEmailAndPassword(email, password).then(newUser => { return newUser.updateProfile({ displayName: name, photoURL }); }).then(() => { hashHistory.push("/rooms"); }).catch(err => { this.setState({ errors: [err.message] }); }); } render() { return ( <form style={SIGNUP_FORM_STYLE} onSubmit={this.handleOnSubmit}> <Errors errorMessages={this.state.errors} /> <div className="form-group"> <label>Email address*</label> <input type="email" className="form-control" placeholder="email" value={this.state.email} onChange={this.handleOnChangeEmail} /> </div> <div className="form-group"> <label>Password</label> <input type="password" className="form-control" placeholder="password" value={this.state.password} onChange={this.handleOnChangePassword} /> </div> <div className="form-group"> <label>User name*</label> <input type="text" className="form-control" placeholder="user name" value={this.state.name} onChange={this.handleOnChangeName} /> </div> <div className="form-group"> <label>Photo URL</label> <input type="text" className="form-control" placeholder="photo URL" value={this.state.photoURL} onChange={this.handleOnChangePhotoURL} /> </div> <div className="form-group"> <button className="btn btn-large btn-primary">Create new account</button> <Link to="/login"> <button type="button" style={CANCEL_BUTTON_STYLE} className="btn btn-large btn-default">Cancel</button> </Link> </div> </form> ); } }
ユーザ作成を実際に作成するとFirebase上にユーザが作られました。
今回は、ここまで!!
Electronではじめるアプリ開発 ~JavaScript/HTML/CSSでデスクトップアプリを作ろう
- 作者: 野口将人,倉見洋輔
- 出版社/メーカー: 技術評論社
- 発売日: 2017/03/28
- メディア: 大型本
- この商品を含むブログ (1件) を見る
Reactビギナーズガイド ―コンポーネントベースのフロントエンド開発入門
- 作者: Stoyan Stefanov,牧野聡
- 出版社/メーカー: オライリージャパン
- 発売日: 2017/03/11
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
入門 React ―コンポーネントベースのWebフロントエンド開発
- 作者: Frankie Bagnardi,Jonathan Beebe,Richard Feldman,Tom Hallett,Simon HØjberg,Karl Mikkelsen,宮崎空
- 出版社/メーカー: オライリージャパン
- 発売日: 2015/04/03
- メディア: 大型本
- この商品を含むブログ (2件) を見る
「Electronではじめるアプリ開発」を写経してみた ④
G.W.は、何もしないで終わってしまったので、また写経を再開しました。チャットアプリ の続きをやります。
目次:
npmスクリプトの登録
package.jsonにnpmスクリプト(scripts)を記述すると コマンドのエイリアス登録をできます。
"scripts": { "watch": "babel --out-dir .tmp src --watch", "start": "electron .", },
$ npm start で electron . と同様となります。
ルーティングの実装
react と react-router で各画面とルーティングを実装していきます。
src/main/Login.jsx(ログイン画面)
import React from "react"; import { Link } from "react-router"; export default class Login extends React.Component { render() { return ( <div> <h2>Login</h2> <Link to="/rooms">Login</Link> <br /> <Link to="/signup">Create new account</Link> </div> ); } }
src/main/SignUp.jsx(サインアップ画面)
import React from "react"; import { Link } from "react-router"; export default class Login extends React.Component { render() { return ( <div> <h2>Signup</h2> <Link to="/rooms">Login</Link> <br /> <Link to="/login">cancel</Link> </div> ); } }
src/main/Rooms.jsx(チャットルーム一覧画面)
import React from "react"; import { Link } from "react-router"; export default class Login extends React.Component { render() { return ( <div> <h2>Rooms</h2> <ul> <li><Link to="/rooms/1">Room 1</Link></li> <li><Link to="/rooms/2">Room 2</Link></li> </ul> <div>{this.props.children}</div> </div> ); } }
src/main/Room.jsx(チャットルーム詳細画面)
import React from "react"; export default class Login extends React.Component { render() { return ( <div> <h3>Room</h3> </div> ); } }
src/renderer/app.jsx(ルーティングの実装)
import React from "react"; import { render } from "react-dom"; import { Router, Route, hashHistory } from "react-router"; import Login from "./Login"; import Signup from "./Signup"; import Rooms from "./Rooms"; import Room from "./Room"; const appRouting = ( <Router history={hashHistory}> <Route path="/"> <Route path="login" component={Login} /> <Route path="signup" component={Signup} /> <Route path="rooms" component={Rooms}> <Route path=":roomId" component={Room} /> </Route> </Route> </Router> ); if (!location.hash.length) { location.hash = "#/login"; } render(appRouting, document.getElementById("app"));
メニューの作成
もろもろメニュー項目を追加します。 単純作業で地味にツラったです・・
src/main/setAppMenu.jsx(ルーティングの実装)
import { app, Menu } from "electron"; import createWindow from "./createWindow"; function setAppMenu() { const template = [ { label: "File", submenu: [ { label: "New Window", accelerator: "CmdOrCtrl+N", click: createWindow }, { type: "separator" }, { label: "Close", accelerator: "CmdOrCtrl+W", role: "close" }, ], }, { label: "Edit", submenu: [ { label: "Copy", accelerator: "CmdOrCtrl+C", role: "copy" }, { label: "Paste", accelerator: "CmdOrCtrl+V", role: "paste" }, { label: "Cut", accelerator: "CmdOrCtrl+X", role: "cut" }, { label: "Select All", accelerator: "CmdOrCtrl+A", role: "selectall" } ], }, { label: "View", submenu: [ { label: "Reload", accelerator: "CmdOrCtrl+R", click: (item, focusedWindow) => focusedWindow && focusedWindow.reload() }, ], }, { label: "Toggle DevTools", submenu: [ { label: "Reload", accelerator: process.platform === "darwin" ? "Alt+Command+I" : "Ctrl+Shift+I", click: (item, focusedWindow) => focusedWindow && focusedWindow.toggleDevTools() }, ], }, ]; if (process.platform === "darwin") { template.unshift({ label: app.getName(), submenu: [ { role: "about" }, { type: "separator" }, { role: "services", submenu:[] }, { type: "separator" }, { role: "hide" }, { role: "hideothers" }, { role: "unhide" }, { type: "separator" }, { role: "quit" } ], }); } template.push({ role: "window", submenu: [ { role: "minimize" }, { role: "zoom" }, ] }); const appMenu = Menu.buildFromTemplate(template); Menu.setApplicationMenu(appMenu); } export default setAppMenu;
src/main/index.js(アプリケーションメニューを追加)
import { app } from "electron"; import createWindow from "./createWindow"; import setAppMenu from "./setAppMenu"; app.on("ready", () => { setAppMenu(); createWindow(); }); app.on("window-all-closed", () => { if(process.platform !== "darwin") { app.quit(); } }); app.on("activate", (_e, hasVisibleWindows) => { if(!hasVisibleWindows) { createWindow(); } });
こんな感じになりました。
今回は、メニュー作成するのが少々単純作業で辛かったです・・。 次回は、Firebaseを使用するので、テクニカルな面白味はありそうです。
「Electronではじめるアプリ開発」を写経してみた ③
少し間が空きましたが、ぼちぼち写経しました。チャットアプリの続きをやります。
目次:
3-3 開発プロジェクトの作成
package.jsonの作成と必要なモジュールをインストールする
npm init npm install electron@1.6.1 --save-dev npm install connors/photon --save npm install react@15.4.2 react-dom@15.4.2 react-router@3.0,0 --save npm install babel-cli@6.18.0 babel-preset-es2015@6.18.0 babel-preset-react@6.16.0 --save-dev
src/main/index.js(mainプロセス実装)
import { app } from "electron"; import createWindow from "./createWindow"; app.on("ready", () => { createWindow(); }); app.on("window-all-closed", () => { if(process.platform !== "darwin") { app.quit(); } }); app.on("activate", (_e, hasVisibleWindows) => { if(!hasVisibleWindows) { createWindow(); } });
src/main/createWindow.js(Window生成)
import { BrowserWindow } from "electron"; let win; function createWindow() { win = new BrowserWindow(); win.loadURL(`file://${__dirname}/../../index.html`); win.on("closed",() => { win = null; }); } export default createWindow;
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Electron chat</title> <link rel="stylesheet" href="node_modules/photon/dist/css/photon.css"> </head> <body> <div class="window"> <div id="app" class="window-content"></div> </div> <script>require("./.tmp/renderer/app.js")</script> </body> </html>
src/renderer/app.jsx(renderプロセス実装)
import React from "react"; import { render } from "react-dom"; render(<div>Hello, Electron and React JSX</div>, document.getElementById("app"));
.babelrc(Babelの設定。ES2015、React JSX形式のファイルをトランスパイルする)
{ "presets":["es2015", "react"] }
Babelの実行(srcディレクトリ下層の各jsとjsxを.tmp配下にトランスパイルしたjsとして配置) –watch 指定でファイル変更を自動検知する
$ ./node_modules/.bin/babel --out-dir .tmp src --watch
package.jsonのエンドポイントを変更
"main": "main.js", ↓ "main": ".tmp/main/index.js",
実行
$ ./node_modules/.bin/electron .
こんな感じ
今回は、React JSXの初歩的な書き方と、Babelを使用したトランスパイルでした。 次回は、ルーティングの実装から