Shogo's Blog

Mar 14, 2016 - 2 minute read - go golang websocket

ngrokみたいなHTTPプロキシを書いてみた

開発中のWebアプリをみんなに試してほしいけど、 サーバなんてなくて開発環境がローカルにしか無くて公開できないということは、 開発初期段階だとよくあることだと思います。 もちろん本格的にやるならテスト用にサーバを建てるべきですが、 小さなものならngrokを使うと簡単です。 ngrokの公開サーバへのHTTPリクエストをローカルにリレーして、 ローカルのサーバをお手がるに公開できるサービスです。 びっくりするほど簡単に公開できて便利ですが、 一応oAuthで制限とかかけたいなーとかカスタマイズしてみたくなってきたので、 似たようなものを自作できないかといろいろ遊んでみました。 その結果、HTTP2 over Websocketみたいな謎なものが出来上がってしまったというお話です。 HTTP2 over Websocketというアイデア ngrokっぽいものを実現するためには、 サーバが受け取ったHTTPリクエストをローカルの環境に転送する必要があります。 ご存知のとおり通常のHTTPではサーバ側からのプッシュ配信が難しいので、Websocketを使うのが良さそうです。 しかし、複数のコネクションで並列にやってくるHTTPリクエストを、一本のWebsocketに束ねる必要があり、 上手く制御するのは大変そうです。 さて、HTTP2は一つのTCPコネクションで複数のリクエストを並行処理する仕様があります。 「複数のリクエストを一本に束ねる」という点ではなんか似ているので、なんだか流用できそうな気がしてきました。 Golangならきっと上手いことinterfaceを実装すれば、なんとかできるのではとやってみました。 実装 HTTP2は暗号化や複雑なフロー制御を行っていますが、 外から見ればnet.Connインターフェースに読み書きしている何かに過ぎません。 そして、websocket.Connもnet.Connを実装しているので、そのままHTTP2のライブラリに渡せるはずです。 そうしてできたのが以下のサーバです。 package main import ( "errors" "log" "net/http" "net/http/httputil" "sync" "golang.org/x/net/http2" "golang.org/x/net/websocket" ) type transport struct { m sync.Mutex t http.RoundTripper closed chan struct{} } var t *transport func main() { t = &transport{} s := websocket.Server{Handler: websocket.Handler(Handler)} http.Handle("/", s) go http.ListenAndServe(":3000", nil) http.

Mar 10, 2016 - 1 minute read - go golang

nginx-omniauth-adapterのGolangポート作った

「nginx で omniauth を利用してアクセス制御を行う」という記事で、 ngx_http_auth_request_moduleの存在を知ったので、 Golangでnginx_omniauth_adapterと似たようなものを作ってみました。 shogo82148/go-nginx-oauth2-adapter 背景 typester/gateは単体でも動くようになっていますが、 例えばIP制限などちょっと高度なことをしたい場合には結局nginxを前段に置く必要があります。 nginxとgateの設定を同時にいじる必要があって煩雑だと感じていました。 そんな中「nginx で omniauth を利用してアクセス制御を行う」という記事で、 ngx_http_auth_request_moduleの存在を知りました。 gateが認証+Proxyをやってしまうのに対して、認証だけRubyのomniauthモジュールで行いProxyはnginxに任せるという方法です。 以前から記事の存在は知っていたのですが、Rubyの実行環境をそろえるのが億劫で手を出せずにいました。 小さなアプリなので自分の慣れた言語で実装しても大したことないのではと思い、Goで実装してみることにしました。 使い方 go getで落として来れます。 最低限client_idとclient_secretの指定が必要です。 nginx_omniauth_adapterと同じ環境変数名で設定できるほか、YAML形式の設定ファイルを読みこませることができます。 YAMLの形式はREADMEを参照してください。 $ go get github.com/shogo82148/go-nginx-oauth2-adapter/cli/go-nginx-oauth2-adapter $ export NGX_OMNIAUTH_GOOGLE_KEY=YOUR_CLIENT_ID $ export NGX_OMNIAUTH_GOOGLE_SECRET=YOUR_CLIENT_SECRET $ go-nginx-oauth2-adapter $ go-nginx-oauth2-adapter -c conf.yaml # 設定ファイルでの指定も可能 PerlでHTTPサーバ書いているひとにはおなじみのServer::Starterにも対応しているので、 それ経由で立ち上げておくと設定の更新・プログラム自身の更新等が楽になると思います。 start_server --port 18081 -- go-nginx-oauth2-adapter -c conf.yaml nginx側の設定はexamplesディレクトリを参照してください。 ヘッダ名・パス名等を合わせてあるので、nginx_omniauth_adapterと同じ設定で動くはずです。 また、h2oの設定はプログラマブルだからh2oでもちゃんと設定ファイルを書けば動くのではと考え、 h2oの設定も書いてみました。 mrubyからproxyに渡るリクエストを書き換える方法がない(?)っぽいので、アプリ側で認証情報をとることはできないですが、一応制限はできます。 basic認証の実装を見る限りremote-userヘッダだけは渡せるようなので、これを使えばなんとかなるかもしれないですが、未確認です。 (Ruby慣れてないからってGoで実装したけど、結局Rubyを書いていて面白い) nginx_omniauth_adapterとの違い 厳密に同じ挙動を実装するのが面倒だったため、挙動に若干の違いがあります。 一番大きなものは認証後のリダイレクト先です。 nginx_omniauth_adapterは認証後、一度adapterのURLにリダイレクトしてから、アプリサーバの/_auth/callbackにリダイレクトします。 それに対してgo-nginx-oauth2-adapterは認証後、アプリサーバの/_auth/callbackに直接リダイレクトします。 この違いのため、Google Developers Consoleの「承認済みのリダイレクト URI」に設定するべきURIが異なることに注意してください。 nginx_omniauth_adapterはadapter自身のURI、go-nginx-oauth2-adapterはアプリサーバの/_auth/callbakを指定します。 この挙動のため、go-nginx-oauth2-adapterはアプリの追加のたびにnginxの設定に加え「承認済みのリダイレクト URI」に正しいURIを追加する必要があります。 もちろん設定箇所がGoogle Developers Consoleではないだけで、nginx_omniauth_adapterもリダイレクト先の設定は必要です。 GoogleでもFacebookでも認証できるようにしたいという場合、nginx_omniauth_adapterは設定を一箇所変えればOKですが、go-nginx-oauth2-adapterは各サービスに登録し直す必要があります。 現状、認証に使うサービスをユーザが選ぶ仕組みがないので、そのまま放置してあります。

Mar 8, 2016 - 1 minute read -

転職して一週間がたちました

転職して1週間がたち,新しい生活サイクルにも慣れてきましたので近況報告をします. 面白法人を卒業し、3月1日から Fuller 株式会社で働き始めました! アプリの視聴率調査のApp Ape Analyticsの提供を中心に、スマフォアプリの開発・分析をやってる会社です。 Pythonの会社なのか? 私も入るまでみんなPythonを使っている会社だと思っていたのですが、 実際はPythonとjavascript半々くらいで使われています。(若干javascript勢の方が多いかも?) 最近は一部Goが導入されつつあるようでが、残念ながらPerlは影も形もありません。 折角Perlな会社にいたので、Perlの布教活動に勤しみたいと思っています。 業務の感じ チームみんなで改善点を話し合って、みんなで解決していくような感じです。 慣れないツールばっかりで苦労してますが、頑張ります。 会社の雰囲気 ひとことで言うと大学の研究室みたいな感じです。(こう言えば多くの人に伝わるんじゃないかなと) 社員の高専卒の割合が非常に高く僕自身も高専の出身なので、懐かしい感じです。 最後に一言 TLを追ってなかったので全然気が付かなかったけど、退職と転職のタイミングがamacbee氏と完全に一致していてびっくりした。 僕も26日退社、1日入社だったのです。 転職して一週間がたちました 退職します 折角なので、記事の中身もamacbee氏に合わせてみました。

Feb 27, 2016 - 1 minute read -

グロンギ語翻訳辞書をアップデートしました

やることがたくさんあるときに限ってどうでもいいことが捗ってこまっているいっちーです。 先日、挑戦状を受け取ったので、グロンギ語翻訳の品質改善に挑戦しました。 《緊急告知》2月26日(金)、何かが起こる!!「仮面ライダークウガ」にまつわる新商品のようですが・・・。ページに書かれているのは、グロンギ語?お客様の中で、リントの言葉に翻訳できる方はいらっしゃいますか~? https://t.co/hMDQCST6Tz — プレミアムバンダイ (@p_bandai) 2016年2月17日 仮面ライダークウガより衝撃の新アイテム登場 ボンジ・ジュグギゾ・ガギバギ・グスと判明!(投げやり) お手軽に試せるページも作ったので、こちらでお試し下さい。 グロンギ語翻訳 変換の仕組み 変換の仕組みの詳細は以前書いた記事をどうぞ。 概略だけ書いておくと、 日本語からグロンギ語への変換はMeCabを使った読み・品詞推定の結果もとに、 変換ルールを適用して翻訳しています。 グロンギ語から日本語への翻訳は、この翻訳問題が実は仮名漢字変換と同じ問題だということを利用して、 IMEの辞書をグロンギ語対応したものを使っています。 変換ロジックの修正 旧版の問題点 ボンジジュグギゾガギバギグス: 紺地重視を再開する ゲゲルンギバブゾロヅボパザセザ: ゲームのしなくっ持つのはだれだ ゲゲルゾザジレスゾ: ゲームを始めるぞ 「この日」は「ボンジ」が正しいのですが、「ボボジ」と変換していたため正しく認識できていませんでした。 「の」は通常「ガ」になるのですが、助詞として現れたときは「ン」になります。 さらに連体詞の一部として出てきたときも「ン」になるのですが、こちらのルールが抜けていました。 さらなる改良 旧版はmecab-skkdicを元にした辞書を使っていましたが、 mozcベースに変更しました。 mozcの辞書はクラスタリングや語彙化のような粒度調整が行われており、変換精度の向上が期待できます。 どのようが調整が行われたかはMozcソースコード徹底解説 や 言語処理学会でのMozcの資料を見るとよいと思います。 mozcの変換エンジンをそのまま使えると良かったのですが、すごく面倒なことがわかったのでギブアップしました。 (依存モジュールの関係で32bit版しかビルドできず64bitのプログラムからは直接呼び出せないとか、C++とかC++とかC++とか) mozcとMeCabの辞書構造は非常に似ているので、MeCabの辞書形式に変換して利用しています。 mozcには共起辞書を使った補正機能(例えば同じ「かいたい」という読みでも、「猫を飼いたい」「マグロを解体」を出し分ける機能)など、 MeCabにはない機能も入っているので、そのうち挑戦してみたいですね。 ただし、mozcには機能だけ組み込まれていて辞書が入っていないので、mozcを使っただけだと大差ないかもしれません。 改良の結果 ボンジジュグギゾガギバギグス: この日重視を再開する ゲゲルンギバブゾロヅボパザセザ: ゲームの資格を持つ子は誰だ ゲゲルゾザジレスゾ: ゲームを始めるぞ だいぶ近くなりました。 「重視」と「遊戯」はグロンギ語で同じ音なので、難しいですね。 変換サーバの実装 ライブラリはPythonで書いてあるので、 PythonのWebフレームワークであるPyramidを使ってAPI化してみました。 デプロイ時のファイル置き換えをアトミックにする sakuraのVPS上でdrootを使って起動しています。 kazuhoさんの「server-starter が SIGHUP 受け取ると pull 型のデプロイツールが起動して、そいつが新しいディレクトリにイメージを展開して、そこに chroot してアプリケーションが動き出すスタイル」を実践してみたくなったので、以下のようなスクリプトを書いてみました。 CONTAINER_DIR=/var/containers/hogehoge-$$ tar zfx hogehoge.tar.gz -C $CONTAINER_DIR droot run --root $CONTAINER_DIR exec gunicorn server:application & CHILD=$!

Feb 11, 2016 - 2 minute read - mecab go golang

MeCabのGolangバインディングを書いてみた

GolangからMeCabを呼び出すライブラリ探せばあるにはあるのですが、 なんだかどれもメモリ管理がちょっと怪しいんですよね・・・。 GolangでMeCabを使う。 yukihir0/mecab-go Go言語から mecab を使う - Qiita rerofumi/mecab Go で Mecab を使ってみた メモリ管理はbluele/mecab-golangが一番しっかりしているっぽいですが、 libmecabの一番高機能だけど面倒な使い方しか対応していなくて、ちょっとカジュアルに遊ぶにはつらい。 というわけで、カジュアルな用途から高度な使い方まで対応したWrapperを書いてみました。 shogo82148/go-mecab 使い方 READMEとgodocのexamplesからのコピペになってしまいますが、 簡単に使い方の紹介です。 インストール go getで取ってくることはできますが、事前にlibmecabとリンクするための設定が必要です。 $ export CGO_LDFLAGS="-L/path/to/lib -lmecab -lstdc++" $ export CGO_CFLAGS="-I/path/to/include" $ go get github.com/shogo82148/go-mecab mecabコマンドと一緒にmecab-configがインストールされているはずなので、 それを使うのが楽でしょう。 $ export CGO_LDFLAGS="`mecab-config --libs`" $ export CGO_FLAGS="`mecab-config --inc-dir`" $ go get github.com/shogo82148/go-mecab MeCabはデフォルトで/usr/local/以下に入るので、他の実装では決め打ちしている例が多いですが、 100%とは言い切れないので面倒ですが都度指定にしてあります。 cgoはpkg-configに対応しているで、MeCab側が対応してくれると環境変数の設定が不要になってもっと楽なんですけどね。 カジュアルに使う Parseを使うとmecabコマンドと同等の結果を文字列として受け取れます。 tagger, err := mecab.New(map[string]string{}) if err != nil { panic(err) } defer tagger.Destroy() result, err := tagger.

Feb 10, 2016 - 2 minute read - aws-lambda python mecab

AWS Lambda で MeCab を動かす(改)

MeCabのPythonバインディングをいじってた関係で、MeCabについてインターネットをさまよっていたら、 AWS Lambda で MeCab を動かすという記事を見つけました。 Lambdaの計算リソースで形態素解析できるのは楽しいですねー。 ただ実装にまだまだ改善できそうな部分があったので修正してみました。 2017/12/06追記 Norio Kimuraさんのコメントを受けて、MeCabをAWS Lambdaで動かす(2017年版)を書きました。 以下の手順でも動きますが、少し簡単に出来るようになっています。 問題点 第一に**「外部プロセスを起動しているので遅い」**という点です。 外部プロセスの起動は非常に重くて数百msかかります。 MeCabは非常に高速で数msもあれば解析が終わるのに、もったいないですよね。 第二に**「OSコマンドインジェクションの危険性がある」**という点です。 解析対象の文字列をコマンドライン引数として渡しており、この際シェルを経由しています。 そのため、{"sentence": "$(ls)"}のような文字列を渡すと、シェルがコマンドとして実行してしまいます。 API Gatewayなどで外部に公開した場合、第三者が何でもし放題な状態になってしまいます。 頑張ってMeCabをライブラリとして呼ぶ 全ての元凶は外部プロセス起動にあるので、頑張ってMeCabをライブラリとして呼んでみましょう。 そもそもなんで外部プロセス起動をしていたかというと、 LD_LIBRARY_PATHが正しく設定されていないためimport MeCab時にlibmecab.soを発見できないからです。 なんとかならないものかと探したところ、Stack Overflowにそれっぽい記事がありました。 Setting LD_LIBRARY_PATH from inside Python 「環境変数を設定してから自分自身をexecし直す方法」と「ctypesを使って絶対パス指定で読み込む方法」が紹介されています。 前者の方がvoteは多いですがLambdaでこれをやるのは大変そうなので、後者で試してみます。 # preload libmecab import os import ctypes libdir = os.path.join(os.getcwd(), 'local', 'lib') libmecab = ctypes.cdll.LoadLibrary(os.path.join(libdir, 'libmecab.so')) 一度読み込んでしまったライブラリは再利用されるため、 import MeCabはここで読み込んだライブラリにリンクされます(importの順番が重要なの闇な感じがする)。 LD_LIBRARY_PATHが正しく設定されている必要はありません。 さて、これでlambda_function.pyとtokenizer.pyが分かれている必要がなくなったので、二つを合体してみましょう。 # coding=utf-8 import os import settings import logging logger = logging.getLogger(__name__) logger.setLevel(settings.LOG_LEVEL) # preload libmecab import ctypes libdir = os.

Feb 6, 2016 - 2 minute read - perl redis

Redisでスコアを複数設定できるランキングを作ってみた

ランキングを作っているとスコアを複数設定したいことがよくあると思います。 例えば「得点が同じだったら早くその得点を出した人優先」とか「勝ち点が同じだったら得失点差が大きい方優先」とかのように、 最初の基準で順位を決められなかった場合の第二基準が欲しいみたいな場合です。 ランキングを作るのにはRedisのSorted Setを使うのが便利ですが、残念ながらSorted Setはひとつしかスコアを設定できません。 少し前にどうやったら実装できるかと社内チャットで話題に上ったので、試しにRedis::LeaderBoardMulti(仮名)という名前で書いてみました。 shogo82148/p5-Redis-LeaderBoardMulti 使い方 メソッドの名前はRedis::LeaderBoardにあわせてありますが、 スコアが複数指定できるようになった関係でちょっと変わってます。 use Redis; use Redis::LeaderBoard; my $redis = Redis->new; my $lb = Redis::LeaderBoardMulti->new( redis => $redis, key => 'leader_board:1', order => ['asc', 'desc'], # asc/desc, desc as default ); $lb->set_score('one' => 100, time); # 第二基準は時間=得点が同じだったら早くその得点を出した人優先 $lb->set_score('two' => 50, time); my ($rank, $score, $time) = $lb->get_rank_with_score('one'); set_scoreの第二引数以降はすべてスコアとして扱われます。(そのためRedis::LeaderBoardと互換性はない) 上の例では「得点が同じだったら早くその得点を出した人優先」になってます。 制限事項 実装の都合により、以下のような制限があります。 スコアはすべて64bit符号付き整数です Redis::LeaderBoardのスコアは倍精度浮動小数点型なので小数も扱えるが、Redis::LeaderBoardMultiは整数だけ Redis 2.8.9以降のみで動きます 実装の仕組み Sorted Setの同じスコアを持つメンバーは辞書順にソートされます(zaddの同じスコアを持つ要素の項を参照)。 例えば以下の様にメンバー「a」「b」「c」を追加すると、必ず「abc」の順番になることが保証されています。 127.0.0.1:6379> ZADD ranking 0 "a" 0 "b" 0 "c" (integer) 3 127.

Jan 23, 2016 - 1 minute read - perl redis

Redis::Fast 0.17 をリリースしました

Redis::Fast 0.17 をリリースしました。 主な変更点は以下のとおりです。 I/Oの待ち合わせに使うシステムコールをselect(2)からpoll(2)に変更 hiredisをv0.13.3にアップデート macでテストが終わらない問題がありましたが、この変更によって修正されています。 hiredisはconnect(2)をnonblokingモードで呼び出しています。 nonblockingなので接続が未完了であってもすぐに制御を返し、errnoにEINPROGRESSが設定されます。 この場合、manにあるようにselect(2)で書き込み可能になるのを待つことで、接続完了を検知できます。 select(2) で書き込み可能になった後に、 getsockopt(2) を使って SOL_SOCKET レベルで SO_ERROR オプションを読み出すこ とにより、 connect() が成功したか、失敗したかを判断できる。 linuxの場合はこれで上手く動くのですが、macだと何故かselect(2)が永遠に制御を返さない場合があるようです。 接続先が存在しない場合に起こるのですが、制御を返す場合もあるので謎です。 いろいろ調べてはみたのですがselect(2)だとどうやっても上手く動かなかったので、poll(2)に変更しました。 poll(2)変更版でテストしてみると、接続先が存在しない場合にPOLLOUTを返すケースとPOLLHUPを返すケースがあるようです。 どうやらPOLLHUPにあたるイベントが来た時の挙動がlinuxとmacとで違うらしい? 謎です。

Dec 22, 2015 - 1 minute read - unity git

UnityのBitmapフォントの収録文字のdiffを取る

Unityで文字を描画するには 「BMFont(Bitmap Font Generator)でビットマップフォントを作る方法」等にあるように ビットマップフォントを自分で作ってあげないといけないらしいです。 (ダイナミックフォントというものもあるらしいけど、まだ安定性が検証ができていないので使ってない。) フォントに入っている全部の文字を収録するとでかくなりすぎるので、一部の文字だけ収録するのが一般的だと思います。 入れる文字は自分で選ぶわけですが、フォントファイルを更新する際に、以前は使えた文字が入っていなくてつらい思いをしたので、 gitで差分をみれるようにしてみました。 gitのいろんなファイル形式の差分を見やすくする方法は Git Diffでcsvの差分を見やすく表示するを参照。 csvのときと同じ要領で、まずはfntファイルをdiffを取りやすい形式に変換するスクリプト(fnt2txt)を用意し #!/bin/bash grep 'char id=' $1 | cut -d' ' -f2 | cut -d= -f2 | perl -MEncode -ne 'printf "%04x: %s\n", $_, encode_utf8 chr($_) if $_ >= 32' fnt2txtを使う設定を.git/configに設定します。 [diff "fnt"] textconv = fnt2txt 最後に拡張子.fntに対してだけこの設定が反映されるようにすればOKです。 *.fnt diff=fnt こんな感じでdiffが見れます。 diff --git a/foo.fnt b/foo.fnt index 79391c0..e262b2d 100755 --- a/foo.fnt +++ b/foo.fnt @@ -93,6 +93,7 @@ 007c: | 007d: } 007e: ~ +00a0: 00a1: ¡ 00a2: ¢ 00a3: £ 事故防止に是非ご利用ください。

Dec 20, 2015 - 1 minute read - python mecab

MeCabをPython3から使う(続報)

Python3からMeCabを扱おうとして挫折していたのですが (MeCabをPython3から使う(中間報告))、 改めて調査して、上手くいかなかった原因が分かったのでご報告します。 おさらい Python3で以下のようにMeCabを使おうとすると import MeCab tagger = MeCab.Tagger('') text = u'MeCabで遊んでみよう!' node = tagger.parseToNode(text) while node: print(node.surface + '\t' + node.feature) node = node.next surfaceが全く読み取れないという現象に遭遇していました。 BOS/EOS,*,*,*,*,*,*,*,* 名詞,一般,*,*,*,*,* 助詞,格助詞,一般,*,*,*,で,デ,デ 動詞,自立,*,*,五段・バ行,連用タ接続,遊ぶ,アソン,アソン 助詞,接続助詞,*,*,*,*,で,デ,デ Traceback (most recent call last): File "m.py", line 10, in <module> print( node.surface + '\t' + node.feature ) UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa3 in position 1: invalid start byte 解決策 詳しい原因なんてどうでもいいからMeCabを使いたい人向けに、最初に解決方法を書いておきます。 以下のように本当に解析したい対象を解析する前に、一度parseをしておけばOKです。 import MeCab tagger = MeCab.