Hatena::ブログ(Diary)

make for h @ppy_things;

2013-02-21(Thu)

httpstatusコマンドで、HTTPのステータスコード(

一般的なWeb Programmerならば、HTTP Status codeはすべて暗記していると聞きました。

しかし、僕は初心者なので、なかなか覚えきれていないので、HTTPステータスコードをさがすのに便利なツールを用意しました。httpstatus.erlです。インストール方法は:

$ wget https://gist.github.com/takkkun/5002968/raw/569c3e3ea98d1cd8c17a9529c12ac8a88c97350a/httpstatus.erl
$ chmod +x httpstatus.erl

です。escript用に書いてあるのでコンパイルとか必要ありません。

$ ./httpstatus.erl 4
400: Bad Request
401: Unauthorized
402: Payment Required
403: Forbidden
404: Not Found
405: Method Not Allowed
406: Not Acceptable
407: Proxy Authentication Required
408: Request Timeout
409: Conflict
410: Gone
411: Length Required
412: Precondition Failed
413: Request Entity Too Large
414: Request-URI Too Large
415: Unsupported Media Type
416: Request Range Not Satisfiable
417: Expectation Failed
418: I'm a teapot
422: Unprocessable Entity
423: Locked
424: Failed Dependency
425: No code
426: Upgrade Required
428: Precondition Required
429: Too Many Requests
431: Request Header Fields Too Large
$ ./httpstatus.erl 40
400: Bad Request
401: Unauthorized
402: Payment Required
403: Forbidden
404: Not Found
405: Method Not Allowed
406: Not Acceptable
407: Proxy Authentication Required
408: Request Timeout
409: Conflict
$ ./httpstatus.erl 400
400: Bad Request
$ ./httpstatus.erl Not
304: Not Modified
404: Not Found
405: Method Not Allowed
406: Not Acceptable
416: Request Range Not Satisfiable
501: Not Implemented
505: HTTP Version Not Supported
510: Not Extended

以下コード。

#!/usr/bin/env escript
 
main([Query|_]) ->
    case matcher(Query) of
        {ok, Matcher} ->
            print(lists:filter(Matcher, http_statuses()));
        {error, {Message, Position}} ->
            io:format("Error: ~s at ~p character~n", [Message, Position])
    end;
 
main([]) ->
    print(http_statuses()).
 
matcher([C|_] = Query) when $0 =< C andalso C =< $9 ->
    case re:compile("^" ++ Query) of
        {ok, Regexp} ->
            {ok, fun({Code, _}) ->
                     re:run(Code, Regexp, [{capture, none}]) =:= match
                 end};
        {error, ErrorSpec} ->
            {error, ErrorSpec}
    end;
 
matcher(Query) ->
    {ok, fun({_, Message}) -> string:str(Message, Query) > 0 end}.
 
print(HttpStatuses) ->
    lists:foreach(fun({Code, Message}) ->
                      io:format("~s: ~s~n", [Code, Message])
                  end, HttpStatuses).
 
http_statuses() ->
    [
     {"100", "Continue"},
     {"101", "Switching Protocols"},
     {"102", "Processing"},
     {"200", "OK"},
     {"201", "Created"},
     {"202", "Accepted"},
     {"203", "Non-Authoritative Information"},
     {"204", "No Content"},
     {"205", "Reset Content"},
     {"206", "Partial Content"},
     {"207", "Multi-Status"},
     {"208", "Already Reported"},
     {"300", "Multiple Choices"},
     {"301", "Moved Permanently"},
     {"302", "Found"},
     {"303", "See Other"},
     {"304", "Not Modified"},
     {"305", "Use Proxy"},
     {"307", "Temporary Redirect"},
     {"400", "Bad Request"},
     {"401", "Unauthorized"},
     {"402", "Payment Required"},
     {"403", "Forbidden"},
     {"404", "Not Found"},
     {"405", "Method Not Allowed"},
     {"406", "Not Acceptable"},
     {"407", "Proxy Authentication Required"},
     {"408", "Request Timeout"},
     {"409", "Conflict"},
     {"410", "Gone"},
     {"411", "Length Required"},
     {"412", "Precondition Failed"},
     {"413", "Request Entity Too Large"},
     {"414", "Request-URI Too Large"},
     {"415", "Unsupported Media Type"},
     {"416", "Request Range Not Satisfiable"},
     {"417", "Expectation Failed"},
     {"418", "I'm a teapot"},
     {"422", "Unprocessable Entity"},
     {"423", "Locked"},
     {"424", "Failed Dependency"},
     {"425", "No code"},
     {"426", "Upgrade Required"},
     {"428", "Precondition Required"},
     {"429", "Too Many Requests"},
     {"431", "Request Header Fields Too Large"},
     {"449", "Retry with"},
     {"500", "Internal Server Error"},
     {"501", "Not Implemented"},
     {"502", "Bad Gateway"},
     {"503", "Service Unavailable"},
     {"504", "Gateway Timeout"},
     {"505", "HTTP Version Not Supported"},
     {"506", "Variant Also Negotiates"},
     {"507", "Insufficient Storage"},
     {"509", "Bandwidth Limit Exceeded"},
     {"510", "Not Extended"},
     {"511", "Network Authentication Required"}
    ].

2012-05-16(Wed)

最近のMeCabの使い方

MeCabC/C++で書かれた形態素解析を行うライブラリなのですが、ちょっと疑問に思うところがあって、以下のようなコードを書きました。

require 'MeCab'

mecab_options = '-Owakati'

1000.times do |n|
  begin
    tagger = MeCab::Tagger.new(mecab_options)
    # do something with tagger
  rescue => e
    raise "failed at #{n + 1} times, [#{e.class}] #{e.message}"
  end
end

マシンスペックに依りますが、大体例外が発生します。MeCab::Taggerインスタンスが破棄されているにも関わらず! 僕のMacBook Air(メモリ2GB)で試したところ、"failed at 70 times, [RuntimeError] "と、大体70回MeCab::Tagger.newを呼んだところで落ちます。例外の内容は不明。

プロセスどうなってんのと見てみると、メモリを13GBほど消費してました。ひどい。多分mallocに失敗とかそんなところでしょうか?

どうやらMeCab 0.99からマルチスレッドに対応したようで、本家のドキュメントのコード例も変わってました。要はマルチスレッドMeCab::Taggerインスタンスをもりもり作る場合はMeCab::Modelのインスタンスを作り、そこからMeCab::Taggerインスタンスを生成しろよ、ってことみたいです。もしMeCab::Taggerインスタンスを直接生成すると、そのたびにMeCab::Modelのインスタンスが生成されるので、メモリを圧迫し、死亡、という流れのようですね。

ということでMeCab 0.99をインストールしている環境では以下のように書けば大丈夫みたいです。

require 'MeCab'

mecab_options = '-Owakati'
mecab_model = MeCab::Model.create(mecab_options)

1000.times do
  tagger = mecab_model.createTagger
  # do something with tagger
end

ちなみにMeCab::Taggerをひとつしか生成しないような環境であれば、MeCab::Taggerから直接インスタンスを生成しても構いません(コード例でもそう書いてある)。

2012-03-12(Mon)

ErlangでTwitterのUserStreamを受け取る

以前もErlangでTwitter Streaming APIを使うといったエントリを書いたのですが、いかんせん情報が古すぎます。UserStreamではなく、素のStreaming APIなのはともかく、認証がベーシック認証だったりします。

その割にはどうやら最近参照されているらしい。http://naoyat.hatenablog.jp/entry/2012/01/04/220639http://d.hatena.ne.jp/siritori/20120312/1331503357には以前のエントリのURLが貼られているようで。いや、なんかすみません。

ということで、ちゃんと動くかつOTPで書き直してみました。erlang-oauthに依存しています。

-module(userstream).
-author("Takahiro Kondo <heartery@gmail.com>").

-export([start/5, start/6, start_link/5, start_link/6, stop/1]).

-behavior(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

-record(state, {id, processor}).

start(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret) ->
    start(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret, []).

start(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret, Options) ->
    Args = [Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret],
    gen_server:start(?MODULE, Args, Options).

start_link(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret) ->
    start_link(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret, []).

start_link(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret, Options) ->
    Args = [Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret],
    gen_server:start_link(?MODULE, Args, Options).

stop(Server) ->
    gen_server:cast(Server, stop).

%% callback functions

init([Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret]) ->
    Url = "https://userstream.twitter.com/2/user.json",
    Consumer = {ConsumerKey, ConsumerSecret, hmac_sha1},
    Options = [{sync, false}, {stream, self}],
    case oauth:post(Url, [], Consumer, AccessToken, AccessTokenSecret, Options) of
        {ok, Id}        -> {ok, #state{id = Id, processor = Processor}};
        {error, Reason} -> {stop, {http_error, Reason}}
    end.

handle_call(_, _, State) ->
    {noreply, State}.

handle_cast(stop, State) ->
    {stop, normal, State}.

handle_info({http, {Id, stream_start, Headers}}, #state{id = Id, processor = Processor} = State) ->
    send(Processor, {start, Headers}),
    {noreply, State};

handle_info({http, {Id, stream, <<"\r\n">>}}, #state{id = Id} = State) ->
    {noreply, State};

handle_info({http, {Id, stream, Part}}, #state{id = Id, processor = Processor} = State) ->
    send(Processor, {stream, Part}),
    {noreply, State};

handle_info({http, {Id, {error, Reason}}}, #state{id = Id, processor = Processor} = State) ->
    send(Processor, {error, Reason}),
    {stop, {http_error, Reason}, State}.

terminate(_, #state{id = Id, processor = Processor}) ->
    send(Processor, stop),
    httpc:cancel_request(Id).

code_change(_, State, _) ->
    {ok, State}.

%% private functions

send(To, Message) ->
    To ! Message.

ちゃんと動くかは確認しましたが、process_flagとかは呼んでいないのでそこらへんは適当に。gen_serverですので、ちゃんと設定すればそこまで手こずることなくsupervisor treeに組込めるかと思います。

本当はuserstreamモジュールをさらにビヘイビアにして、handle_status/2, handle_favorite/2とかで各イベントをハンドリングできるようにするといいんですが、それをやるとちょっと複雑になるので、そこまではやりません。

ちなみに使い方はこんな感じで。

-module(example).
-author("Takahiro Kondo <heartery@gmail.com>").

-export([start/0, stop/1]).

start() ->
    Processor = spawn(fun() -> process() end),
    ConsumerKey = "Your consumer key",
    ConsumerSecret = "Your consumer secret",
    AccessToken = "Your access token",
    AccessTokenSecret = "Your access token secret",
    userstream:start(Processor, ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret).

stop(Pid) ->
    userstream:stop(Pid).

process() ->
    receive
        {start, Headers} ->
            io:format("Start: ~p~n", [Headers]),
            process();
        {stream, Part} ->
            io:format("Stream: ~p~n", [Part]),
            process();
        {error, Reason} ->
            io:format("Error: ~p~n", [Reason]),
            process();
        stop ->
            io:format("Stop~n")
    end.
$ erl -s inets -s ssl
> {ok, Pid} = example:start().
ここにUserStreamからの応答が表示される(example:process/0で標準出力に吐き出してるため)
> example:stop(Pid).
Stop
>

OTPを使いつつ複雑すぎない書き方をしてみました。必ずしもOTPを使う必要はないですし、メリットばかりでもないんですが、アプリケーションがある程度複雑になってきたら使った方が良いかなと思います。それこそ書き捨てのコードでは不要でしょうが、あのプロセスが動いて、こっちであーでどーで、とかで頭のリソース割かれるならOTPを学ぶ価値はアリかなと。

余談ではありますが、余力があれば自作のtwitterモジュールGitHubにでもあげておきたいもんですね。それにはTwitterREST APIはもちろん、先述したUserStream用ビヘイビアも書いてはあるんですよ。ただ随分前からメンテナンスをサボってるので、REST APIが古過ぎるという感じで…… なんか一から書いた方が早そう。

2012-02-27(Mon)

autotest-twitterでブヒる

最近とあるgemを書きながら付随するgemを書いてて優先すべきそれが中々進まない昨今ですこんばんは。

で、恥ずかしながらテストファーストってあんまりやったことなくて、今それを実践しながらの開発をしています。使っているツールはRSpecなんですが、コマンドひとつでテストできるとは言っても、今度はそのコマンドを実行するのがめんどくさい。ひたすら怠惰な生き物ですね。

そういう生き物たちにうってつけなのが当然あって、それのひとつにautotestってのがあります。しばらくはautotest + autotest-growlで開発してたんですが、家で使ってるマシンはMac、職場で使ってるマシンはUbuntuなんですね。Macには当然Growlインストールしてるんですが、UbuntuとなるとGrowl以前の問題です。なので「Twitterにテストの結果をツイートして、あとは各OS向けのTwitterクライアントから通知すればいいんじゃね」と思い至りました。というわけでautotest-twitterです。まあ後からよく調べたらautotest-growlLinuxにも対応してましたけどね。ちくしょう。

使い方

README読めと言いたいところですが、GitHubに置いてあるのがいい加減なのでアレです。何がアレってテストを書いていないところですよね。まあとりあえずテストの対象となるアプリケーションなりライブラリが置いてあるディレクトリに.rspecを作り:

--format nested
--color

を、.autotestに:

require 'autotest-twitter'

Autotest::Twitter.configure do |config|
  # ツイートするアカウントのアクセストークンを設定
  config.consumer_key = 'your consumer key'
  config.consumer_secret = 'your consumer secret'
  config.oauth_token = 'your access token'
  config.oauth_token_secret = 'your access token secret'

  # ラベル。アプリケーションの名前とか
  config.label = 'any application'

  # テストの結果に応じてアイコンを変えられるので、そのアイコンが
  # 置いてあるディレクトリ
  #   - missing.png: テスト自体がない場合のアイコン
  #   - failed.png: テストに失敗した場合のアイコン
  #   - pending.png: ペンディングが存在する場合のアイコン
  #   - passed.png: テストに成功した場合のアイコン
  config.image_dir = 'path/to/icons'

  # テストの結果に応じたツイートの内容。$で始まるのは変数
  #   - $label: config.labelで設定した内容
  #   - $all: テストの全件数
  #   - $failed: 失敗したテストの件数
  #   - $pending: ペンディングしてるテストの件数
  config.missing_messages = ['$labelのテストが存在しないよ']
  config.failed_messages = ['$labelのテストに失敗したよ。$all件中$failed件がダメみたい']
  config.pending_messages = ['$labelのテストに$pending件のペンディングがあるよ']
  config.passed_messages = ['$labelのテストに成功したよ! $all件あったみたいだね']
end

を、Gemfileに:

source 'https://rubygems.org'

gem 'autotest'
gem 'autotest-fsevent'
gem 'autotest-twitter', :git => 'git://github.com/takkkun/autotest-twitter.git'

こう。で:

$ bundle --path vendor/bundle

でもしてautotest諸々をインストール。後は:

$ bundle exec autotest

でテストを開始。後はファイルに変更があるたびにテストが走り、結果に応じてツイートされるはずです。config.image_dirを設定してればアイコンも変わります。ちなみにRSpecでしか試していませんし、とりあえず動いてるっぽいってことしか確認してないのであしからず。

ちなみに僕は @Shinobu_DD で試していました。まるでアイコンセットのような画像(TVアニメ偽物語の一部でしょうが)があったので。でもまあ「$labelで$pending件ペンディングがあるようじゃな。お前様の生き様が垣間見えるの。かか」とか打ってると頭抱えたくなりますし、いざブヒろうにも全然テンション上がらないのであんま向いてなかったようです。ていうか元々そういうのじゃないし!

まあでもブヒろうと思えばブヒれるので、テストがコケたらツンツンされたり、テストが通ったらデレデレされたりして、「今日も開発がんばりましゅううう」とか言ってればいいんじゃないですかね。

あとさっき思い付いたんですけど、ツイートするアカウントを自分のアカウントにし、passed.pngをいつも使ってるアイコン、failed.pngをとてつもなく恥ずかしいアイコンにすると面白いんじゃないかと思います。はやくテスト通さないとエラい思いをするハメになるというマゾい開発が出来ていいんじゃないかナーーー。

2011-09-07(Wed)

Twitter彼氏の作り方と掘り方

※ この記事はフィクションです

Twitter彼氏の作り方

  1. たらしっぽい言動を男に振り撒きます
  2. ネタには果敢にノっていきます
  3. 彼氏が出来ます
  4. 充分な人数の彼氏が出来るまでそれを繰り返します

Twitter彼氏の掘り方

ググってください。ちなみにおまえ棒 ガチムチ味*1はかなりリアルらしいので、これで練習しておくと良いかもしれません。

私情

まずはじめに「どうしてこんなことになってしまったんだ」って言いたいです。そもそも僕は男性を性的な興味として捉えられませんし、まあネタとしてノることはありますが、うん…… ってそれがいけないんですよね。

とりあえず彼氏っぽい人たちを載せておきます。

  • @zya4
  • @HemusAran
  • @ne_ko_
  • @__________t_t_
  • @mitukiii

ひとりは通い妻(♂)に来るわ、ひとりは嫉妬の炎を燃やすメンヘラビッチ(♂)だわ、ひとりは慎みある淑女(♂)だわ、ひとりはキスを求めてくる性欲旺盛なヤリマン(♂)だわ、ひとりは運命感じちゃう乙女(♂)だわ、バリエーションが豊富で飽きません。

もういやだ。

*1:ガチゲイからいただきました。言っておきますが未使用です