GAEでバッチ処理
2018/11/07
はじめに Google App Engine(GAE)を使って、バッチ処理が実行できたのでメモです。 GAEではcron.yamlに設定を記載することで時間を指定した処理を行うことができます。 サンプル 今回は、slackにメッセージを送信するアプリを定期的に実行させてみます。 slackにメッセージを送る部分は以下のサイトに詳しく記載されているので省略します。 CrossBridge Lab | GolangでSlackの特定のチャンネルにメッセージを送る 今回はGAE上でGolangを動かしてみましたよの構成を元に、GAEのプロジェクトを構築します。 ルートディレクトリ(app.yamlと同じ階層)にcrom.yamlを作成します。 cron.yaml cron.yamlには、以下のように開始時刻や間隔で実行するタイミングを指定することが出来ます。 cron: - description: "毎朝7時(日本時間)に実行" url: /morning timezone: Asia/Tokyo schedule: every day 7:00 - description: "毎週日曜8時(日本時間)に実行" url: /morning timezone: Asia/Tokyo schedule: every sunday 8:00 - description: "毎日0時を起点とし、1時間単位で実行" url: /morning timezone: Asia/Tokyo schedule: every 1 hours その他のスケジュール指定方法や、HTTPリクエスト失敗時のリトライ回数は公式のドキュメントに詳しく記載されています。 注意点 GAEではdev_server.pyでローカルにサーバを立ち上げて、動作確認をすることができます。しかし、ローカル環境では、cron.yamlは動作しません。 localhost:8000 からGAEローカル環境の管理画面を開くことができます。 管理画面にcron.yamlで設定したスケジュールが一覧で表示されるので、Run nowをクリックすることで手動で実行することができます。 Cronの設定を本番環境にデプロイするときは、gcloud app deploy cron.yamlのようにcrom.yamlを指定します。 指定しないと、crom.yamlが読み込まれないため、いつまで待っても処理が実行されません。 Cronが設定されているかどうかは、コンソール画面のcronジョブから確認することが出来ます。 仮に他のユーザからHTTPでのリクエストがあった場合、想定していない時間に処理が実行される可能性があります。 そのため、GAEのcronサービスからのHTTPアクセス以外からのアクセスは除外する必要があります。 GAEのcronサービスからのHTTPアクセスにはHTTPヘッダにX-Appengine-Cron: trueが含まれているので、HTTPリクエストを受け取ったタイミングで、チェックする必要があります 例えば、GolangでGAEのcronサービス以外からのHTTPリクエストを弾くには、以下のように書きます。 (弾いた後の処理がお粗末ですが…) // cron以外からのアクセスは弾く if r.…
GAEでfaviconを設定
2018/11/06
はじめに GoogleAppEngine(GAE)上で提供するWebページにfaviconを設定するさいに、少し迷ったのでメモに残しておきます。 faviconとは faviconとは、ブラウザのタブやブックマークに表示されてるアレです。 最近は、スマートフォンでWebページのショートカットをホーム画面や、Windows10のスタートメニューで見かけることも増えてきました。 faviconについては、以下の資料に詳しくまとめられています。 WEBサイトが「できた」と安心する前に最終チェックすること favicon準備 faviconの元にするイラストはこちらです。 このイラストをいい感じにfaviconとして使っていきます。 今回は、スライドで紹介されているFavicon Generatorを使ってfaviconを用意していきます。 faviconを生成すると、favicon_package_v0.16.zipがダウンロードできます。 favicon_package_v0.16.zipは以下のような中身になっています。 favicon_package_v0.16 ├── android-chrome-192x192.png ├── android-chrome-384x384.png ├── apple-touch-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── mstile-150x150.png ├── safari-pinned-tab.svg └── site.webmanifest この中ので拡張子がpng、icoは実際に表示されるイメージファイルです。 browserconfig.xmlはWindows10のスタート画面にWebページをピン留めしたときに表示される画像の設定情報です。このファイルは明示的に読み込まなくても、ルートディレクトリに置いておけば、勝手に読み込んでくれます。 safari-pinned-tab.svgはベクターというフォーマットの画像ファイルです。safariで表示するfaviconはこの画像を使っているようです。 site.webmanifestはAndroidでWebページをホーム画面に追加したときのアイコンや、タイトルを設定するファイルです。 今回はGAE上でGolangを動かしてみましたよの構成を元に、faviconを設定します。 browserconfig.xmlとsite.webmanifestはルートディレクトリに配置し、その他の画像ファイルはルートディレクトリ直下のimagesに配置します。 プロジェクトのディレクトリ構成は以下の通りです。 . ├── app.yaml ├── gae_sample.iml ├── hello.go ├── index.yaml └── static ├── browserconfig.xml ├── site.webmanifest ├── images │ ├── android-chrome-192x192.png │ ├── android-chrome-384x384.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.…
祝日のカウントダウンタイマー
2018/10/30
はじめに GoogleAppEngine(GAE)、Golangの勉強のために、祝日までのカウントダウンタイマーを表示するWebページを作ってみました。 https://shukujitsu.info 処理の内容 内閣府が祝日の情報をCSV形式で公開しているので、定期的に取得して、加工して、CloudStorageにアップロードしています。 GAEにアクセスがあると、CloudStorageから祝日情報を読み込んで、いい感じに加工して表示しています。 使ってる技術 GoogleAppEngine インフラとしてGAEを使いました。 GAEに対応していない関数とかもあるので、ドキュメントを見ながら覚えていくしかなさそうです。 たとえば、GolangでHTTPのリクエストを行うときにhttp.Get()を使いますが、GAEでは対応していないのでurlfetch.Clientを使う必要があります。 他にも、Javaでは時刻を扱うjava.time.LocalDateというライブラリがあるのですが、同様にGAEでは使うことができません。 GAE使うなら言語に依らず注意する必要があります。 Golang ネット上に公開されているCSVファイルを読み込んで、Cloud Storageにアップロードする部分をやってます。 もともと、Golangの勉強を兼ねたプロジェクトだったんですが、あまりGolangを使いませんでした。 Vue.js JavaScriptで変数を書き換えると、いい感じにHTMLで表示される内容も変更されます。 まだVue.jsを使いこなせてはいませんが、低い学習コストでさっくり使うことができます。 Scrollify マウススクロールでいい感じに画面を切り替える処理はScrollifyを使いました。 これを使うだけで今風な感じがでます。 今後の課題 データ保管場所 情報をCloudStorageに保存してるので、NoSQLを触ってみようかと考え中です。 日付と祝日名を格納するだけなのでRDBでも問題ないんですけど… 監視、ログ なんらかのエラーが起きた場合はソーリー画面を出し、StackDriver上にログを出すようにしていますが、能動的にチェックしないとエラーに気が付きません。 エラーを検知したら通知する仕組みがほしいですね、LINEかSlackに飛ばすのが今風でしょうか… パッケージ管理 使用しているライブラリのバージョン管理とかをしていないので、一般的に使われている(らしい)、depを使ってみようかと考え中。 次バージョンのGolangではmodulesという新たなパッケージ管理機能が正式リリース予定なので、最終的にはそっちに映る感じでしょうか。 リファクタリング ディレクトリ階層がぐちゃぐちゃで、1つ1つのファイルも肥大化しているのでキレイにしたいです。 ある程度キレイになったらリポジトリを公開するのもアリですね。 CIツール導入 現在、dev_appserver.pyでローカル環境を立ち上げて、動作チェックが済んだらgcloud app deployで本番環境を更新する、という雑な運用をしています。 雑すぎるので、CircleCIとかを使って、PR投げたらステージング環境でテストが実行されてー、というのを導入したいです。 AltJS 「素のJavaScriptを書くのがツラい」という話を聞くことが増えてきました。 この規模なら素のJavaScriptでも問題ないですが、今後のことを考えるとTypeScriptかCoffeeScriptあたりを勉強して置き換えることを考え中です。 WPA化 WPAであれば、オンライン状態でアクセスしておけば、オフライン状態でもそのWebサイトにアクセスができるようです。 地下鉄などの電波状況が悪い場所でも祝日カウンターが使えれば素敵ですね。 見た目 レイアウト気にし始めるとキリがないので一旦リリースしてしまいました。 運用しながら徐々に変更を加える予定です。 感想 本当はcoinhive仕込みたかったけど神奈川県警に捕まったらいやなので我慢しました。 認証も、DBへのアクセスもないページですが、1人でインフラ、サーバサイド、フロントエンドの構築を行ったのでいろいろと勉強になりました。 細々としたTipsはブログに残していきたいです。 課題もたくさん出てきたので、徐々に直していきます。 欲しい機能とか、気になる箇所があれば、お気軽に@foresukecomまでご連絡下さい。 その他 ここ数年、ウィルキンソンの炭酸水をAmazonで定期購入してます。炭酸水のおかげで、ビールを飲む量がだいぶ減りました。 でも、炭酸水はウイスキーのチェイサーに最適なんですよね…割れば簡単にハイボールも作れるし… 悩ましい飲み物です。
ReactNativeを触ったお話
2018/10/28
はじめに お仕事でReactNativeを使うことになったので、触ってみました。 導入手順について本記事内では説明をしないので、ReactNativeの公式サイトのGetting Startedをご参照ください。 ちなみに、ReactNativeのバージョンは0.57.3です。 サンプル 今回はRESAS-APIから都道府県一覧を取得してリスト表示してみます。 react-native init projectNameを実行直後のディレクトリ構成は以下の通りになっています。 projectName ├── android ├── ios ├── node_modules ├── App.js ├── app.json ├── index.js ├── package-lock.json └── package.json 今回はApp.jsのみを以下のように変更を加えていきます。 RESAS-APIを使うには事前に公式サイトから利用登録する必要があります。 X-API-KEYはRESAS-API登録時に発行されるキーを指定して下さい。 感想 ReactNativeは活発に開発が行われていますが、毎週のように最新バージョンが更新されているため、開発環境の構築が面倒ですね。 今回は使わかなかったから影響ないですが、ReactNativeの0.57.3ではButtonコンポーネントを使うとAndroidシミュレータで以下のエラーが発生します。 https://stackoverflow.com/questions/52784633/i-have-some-error-when-add-button-to-my-react-native-app もう少し安定した(悪く言えば枯れた)技術を使った方が開発も運用も楽なんですが、最近はそんなことを言っていたら取り残されちゃうから、日々勉強するしかないですね。 でも…せめてLTSがあるといいな… 開発環境の構築が終われば、シミュレータ上でポンポンとテキストなりボタンなりを配置できるから面白いです。 その他 最近、フラジャイルを読み返しました。 医療漫画はいいですね。Dr.コトー診療所を読んだ時もそうですが、泣いてしまいます。特に3巻、4巻のアミノ製薬の話が好きです、火箱ちゃん可愛い。 フラジャイルはドラマ化しているみたいなので、そのうち観てみたいです。
JavaScriptでクロージャを触ったお話
2018/10/14
はじめに クロージャの存在は知っていましたが、ちゃんと使ったことがなかったんで使ってみました。 クロージャの詳しい説明はmozillaが公開している以下のドキュメントにわかりやすく載っているので省略します。 MDN web docs サンプル 1から6までの整数を返す関数に追加機能として、連続して同じ値が出ないようにしてみます。 連続して同じ値が出なくなったので、ランダム感を演出できるようになりました。 実際には結果を操作してるのでランダムとは言えなくなりましたが ^^; まずはクロージャを使わない例です。 前回の結果を保持する変数prevNumをグローバル関数として定義しているので、関数dice()以外からもアクセスが出来てしまいます。 次がクロージャを使った例です。 こちらではprevNumをdice()内で定義しているので、dice()の外ではprevNumが使えなくなりました。 感想 クロージャを使わなくても同じ機能は実装できますが、グローバル変数の使用を抑えられるのは嬉しいところです。 今回のサンプルのような短いプログラムではありがたみも薄くなりますが、 前回と違う乱数を生成する箇所はfor、ifを使わずに、剰余演算を使ってワンライナーできなかと一晩悩みましたが思いつきませんでした。 もっといい感じの計算式をご存知の方がいたら教えてください。 その他 最近、地元の静岡が舞台になっているローカル女子の遠吠えの4巻が発売されました。 作者の瀬戸口みづき先生も静岡出身ということで、思わず膝を連打するネタが多いです。 ただ…この漫画を読むと静岡に帰りたくなるので、転職や引越しなどの重大イベントを控えたタイミングでは読まないほうがいいでしょう。 あー、亀まんじゅう食べたい。げんこつハンバーグ食べたい。
Vue.jsで配列を扱ったお話
2018/08/21
はじめに Vue.jsを使っていて、配列の中身を変更したのに画面上の値が変わらないなー、となって困ったのでメモです。 本文 ボタンを押すたびに配列に格納された数値を倍にし、リアルタイムで画面上にも表示するサンプルプログラムです。 以下のように、配列の中身を変更する箇所を修正したら想定通りに動くようになりました。 具体的には以下のように、要素番号を指定して配列の中身を変更しても画面上の値は変化しませんが、Vue.jsに用意されているメソッドを使えは配列の中身に応じて画面上の値も変化します。 /*失敗例*/ this.num_list[i] = this.num_list[i] * 2; /*成功例*/ this.num_list.splice(i, 1, this.num_list[i] * 2) 公式ドキュメントにも注意事項としてちゃんと書かれていました。 JavaScriptの制限じゃ仕方ないですね(´・ω・`) JavaScript の制限のため、Vue は配列で以下の変更を検出することはできません: 1.インデックスでアイテムを直接設定するとき。例: vm.items[indexOfItem] = newValue 2.配列の長さを変更するとき。例: vm.items.length = newLength まとめ 公式ドキュメントを読みましょう。 内容とはまったく関係ないですが、記事の中にソースコードを埋め込みたかったのでCODEPENを初めて使ってみました。 ソースコードと実行内容をまとめて埋め込めるのは便利ですね。 その他 最近はRocket Leagueにハマっています。 車でサッカーをするという天才的発想。 FPSほどの精密な操作は求められないので、お酒を飲みながら遊ぶにはピッタリです。 ワイルドスピードやバットマンに登場する車が使えるようになるDLCも多数用意されているので、自分の気に入った車で走り回ることもできます。
GAE上でGolangを動かしてみましたよ
2018/08/13
はじめに GoogleAppEngine上で動くwebサービスをgolangで作りはじめました。 作り始めでつまずくことが多いので自分用の最小構成メモです。 ここで説明するwebサービスは、Ajaxで非同期通信し、Golangが返した文字列を表示するだけのものです。 環境の準備 まずは、GCPの公式ドキュメントを参考にGolang、Cloud SDKをダウンロードします。 以下のようにバージョンが表示されればOKです。 $ goapp version go version 1.9.4 (appengine-1.9.74) darwin/amd64 ちなみに、開発にはmacOS High Sierra(10.13.6)を使用しています。 構成説明 ディレクトリ構成は以下の通りです。 ├── app.yaml ├── hello.go └── static ├── js │ └── main.js ├── css │ └── main.css └── index.html この中でGAE特有のファイルはapp.yamlになります。 app.yamlリファレンスに各パラメータの説明が載ってますが、最低限のパラメータだけ書いておきます。 runtime: go #Golangを使用する場合はgoを指定 api_version: go1 #最新のバージョンを使用する場合はgo1を指定 handlers: #リクエストと実行する処理をURLパターン毎に指定 - url: / static_files: static/index.html upload: static/index.html - url: /js static_dir: static/js - url: /css static_dir: static/css - url: /.* script: _go_app その他のファイルについては、特別なことはしていないので説明は省略します。…
英語漫画のススメ
2018/05/20
はじめに 最近、海外ドラマを観たりオンライン英会話を受けたりして英語を勉強しています。 しかし学生時代に英語の勉強をサボっていたので、わからない単語が多すぎて会話に付いていけず苦労しています。 単語帳なんかも買ってみたのですがすぐに飽きてしまったので、英語版HUNTER×HUNTERをKindleで買って試してみました。 とりあえず飽きずに10巻まで読み進められたので、メリット/デメリットをまとめてみます。 メリット なんとなく読める 僕は日本語版のHUNTER×HUNTERを何度も読み返しているので、英語版でも100%理解しなくても読み進めることができます。 すぐに買える 大型書店でないと英語版の漫画は売っていないと思います。(少なくとも僕は売っているのを見たことがありません。) 読みたい時にすぐ買えるのは電子書籍ならではです。 (紙版の本と比べると)安い 1冊だいたい850円ぐらいなので、日本語版と比べれば割高になります。 しかし、英語版HUNTER×HUNTERは紙の本では1冊1,000円ぐらいします。 翻訳コストがかかるので日本語版よりも高くなるのは仕方がありませんが、紙の本と比べて1〜2割引で買えるのはありがたい限りです。 デメリット 翻訳機能が使えない Kindleでは漫画を画像として取り込んでいるので、セリフの検索や翻訳ができません。 しかし先日、Google I/O 2018で写真に写っているモノをAIが認識するGoogleLensというサービスが発表されました。 KindleはAmazonのサービスなので、Googleのサービスを使うとは思えませんが、こういった技術が発展すれば漫画の中の文字も翻訳対象として扱えるようになると思います。 最終的にはNetflixの音声や字幕のように、1冊の本を買ったら自由に言語を切り替えて読める世界になるのでしょうか。 英語がすべて大文字 理由は諸説あるようですが、アメリカンコミックではセリフが大文字で書かれていることが多いようです。 英語版HUNTER×HUNTERでは作者コメントは大文字と小文字が混ざっているのですが、セリフや説明分はすべて大文字で書かれています。 慣れの問題かもしれませんが、なかなか読みにくいものです。 今後 HUNTER×HUNTERを読み終えたら、ハガレンかドラゴンボールに挑戦予定です。 ハガレンも日本語で何度も読み返しているから大丈夫でしょう。 実はドラゴンボールをしっかりと読んだことがないので、せっかくなので教養として読んでおこうかと。 3月のライオン、よつばと、ヨルムンガンドあたりもKindleで翻訳版が出てほしいのです。
Go言語でJSONを作成したときのイージーミス
2018/05/11
はじめに Go言語を使って構造体をJSON形式に変換しようとしたら詰まったのでメモです。 JSON形式への変換は標準パッケージに含まれているjson.Marshalを使っています。 失敗例 こんな感じで構造体のスライスをJSONにしようとしたところ、作成されたJSONの中身が空になっていました。 成功例 原因がわからなかったので色々と試行錯誤した結果、下記のコードで想定した動きを確認することができました。 失敗例では10〜12行目の構造体のフィード名の先頭文字を小文字にしていましたが、成功例では先頭文字を大文字に変えています。 原因 ここまで書けば原因がわかる方も多いと思いますが… Go言語では変数や関数のスコープが先頭文字列が小文字か、大文字かで判定されます。 先頭文字が大文字の場合は他パッケージからの参照が可能(いわゆるpublic)、先頭文字が小文字の場合は他パッケージから参照不可(いわゆるprivate)となります。 このルールは構造体のフィードにも適用されます。 そのため、失敗例では構造体のフィードに対してパッケージ外からアクセスすることができず、作成されたJSONが空っぽになっていました。 ちなみに 作成されるJSONのKeyを先頭小文字にしたい場合、以下のようにフィードに対してタグをつけることで任意の名前を指定することができます。 おわりに 初歩的なミスでしたが原因に気づくまで時間がかかってしまいました。 前に触ったことがあるはずなんですが…先頭文字でスコープが変化するのは慣れないと忘れちゃいますね。 コンパイルエラーとかで教えてくれないかなぁ。
JavaからRESAS APIを使ってみましたよ
2018/02/03
お久しぶりです。 巷で話題のビッグデータを使って何かやってみようかと思い立ったので、RESASから情報を取得してみます。 RESASとは地域の活性を目的とし、市町村名一覧や人口構成、観光資源などの多くの情報を公開しているWebサイトです。 APIも公開されているので、かんたんに情報を取得することができます。 地域経済分析システム(RESAS:リーサス)は、地方創生の様々な取り組みを情報面から支援するために、経済産業省と内閣官房(まち・ひと・しごと創生本部事務局)が提供しています。 自治体職員の方や、地域の活性化に関心を持つ様々な分野の方によって、効果的な施策の立案・実行・検証のためなどに広く利用されています。 引用元: RESAS 地域経済分析システム とりあえず今回は勉強がてら、Javaを使って都道府県の一覧を取得してみます。 ※RESASのAPIを使うには利用登録をしてAPIキーを発行してもらう必要があります。利用登録は公式サイトから行えます。 RESAS-API利用登録 以下のように結果が返ってきます。 JSON形式なので簡単に加工が出来そうです。 { "message": null, "result": [ { "prefCode": 1, "prefName": "北海道" }, { "prefCode": 2, "prefName": "青森県" }, { "prefCode": 3, "prefName": "岩手県" }, { "prefCode": 4, "prefName": "宮城県" }, { "prefCode": 5, "prefName": "秋田県" }, { "prefCode": 6, "prefName": "山形県" }, { "prefCode": 7, "prefName": "福島県" }, { "prefCode": 8, "prefName": "茨城県" }, { "prefCode": 9, "prefName": "栃木県" }, { "prefCode": 10, "prefName": "群馬県" }, { "prefCode": 11, "prefName": "埼玉県" }, { "prefCode": 12, "prefName": "千葉県" }, { "prefCode": 13, "prefName": "東京都" }, { "prefCode": 14, "prefName": "神奈川県" }, { "prefCode": 15, "prefName": "新潟県" }, { "prefCode": 16, "prefName": "富山県" }, { "prefCode": 17, "prefName": "石川県" }, { "prefCode": 18, "prefName": "福井県" }, { "prefCode": 19, "prefName": "山梨県" }, { "prefCode": 20, "prefName": "長野県" }, { "prefCode": 21, "prefName": "岐阜県" }, { "prefCode": 22, "prefName": "静岡県" }, { "prefCode": 23, "prefName": "愛知県" }, { "prefCode": 24, "prefName": "三重県" }, { "prefCode": 25, "prefName": "滋賀県" }, { "prefCode": 26, "prefName": "京都府" }, { "prefCode": 27, "prefName": "大阪府" }, { "prefCode": 28, "prefName": "兵庫県" }, { "prefCode": 29, "prefName": "奈良県" }, { "prefCode": 30, "prefName": "和歌山県" }, { "prefCode": 31, "prefName": "鳥取県" }, { "prefCode": 32, "prefName": "島根県" }, { "prefCode": 33, "prefName": "岡山県" }, { "prefCode": 34, "prefName": "広島県" }, { "prefCode": 35, "prefName": "山口県" }, { "prefCode": 36, "prefName": "徳島県" }, { "prefCode": 37, "prefName": "香川県" }, { "prefCode": 38, "prefName": "愛媛県" }, { "prefCode": 39, "prefName": "高知県" }, { "prefCode": 40, "prefName": "福岡県" }, { "prefCode": 41, "prefName": "佐賀県" }, { "prefCode": 42, "prefName": "長崎県" }, { "prefCode": 43, "prefName": "熊本県" }, { "prefCode": 44, "prefName": "大分県" }, { "prefCode": 45, "prefName": "宮崎県" }, { "prefCode": 46, "prefName": "鹿児島県" }, { "prefCode": 47, "prefName": "沖縄県" } ] } 備考 モンハンワールドが面白すぎて…進捗ダメです。