社内のLTに発表者として参加した

発表者として参加したので振り返り。

悪かった点

言いたいことは 1 つに絞る

言いたいことが2つあったので、両方とも盛り込んだ。振り返ってみるとどちらとも中途半端な説明で終わっていたような。

LTということで持ち時間も限られていて、詳しく説明できないのはわかっているので、興味があったら後で個別に聞きに来てね、的なスタンスでもいいのかもしれないけれど。 

よかった点

聞いてくれている人の方を向けた

前回はスライドやPCの画面ばかり見てしゃべってしまっていた。今回は、少しではあるが聞き手の様子を見ながらしゃべることができた。

ただ、見る回数はそれほど多くなく。 見たとしても聞き手の様子を探って自分の話し方を工夫するとまではいかなかった。

感想

発表者になると、LTと言えど適当なことは話せないので、発表内容についてより深く考えるようになるので勉強になるな、と。

また機会があれば発表者として参加したい。

小説家という職業 を読んで

「小説家という職業」という本を読んだ(小説家になりたいと考えているわけではない)。

小説家という職業 (集英社新書)

小説家という職業 (集英社新書)

この本は小説家になりたい人だけではなく、プログラマーにになりたい人にも当てはめられる部分が多々あると思う。

特に、最後の段落、ここは強く共感する。

HTTPって意外に難しい

長い間Webサービスの開発に携わっているのだけれど、HTTP について全然理解していなかったんだ、と思わされる出来事があった。

そのこととは、名前ベースのバーチャルホストのこと。今までは、ドメイン名を変えれば同じサーバ上に複数のサービスを稼働させられるあれのことね、という程度の認識で、仕組みまでは理解していなかった。

仕組みを理解していなかったために、問題の解決に時間がかかってしまった。いや、理解していれば問題は発生していなかったはず。

問題

問題はプロキシ的な機能(以下プロキシ)を作っているときに発生した。

プロキシを簡単に説明すると、クライアントからリクエストを受け取り、簡単な変換とチェックを行い、その先の各サービスにリクエストを送信する役割。プロキシと各サービスは同じサーバ上で開発していて、URLはサブドメインで分けられている(バーチャルホスト)。

で、問題発生。プロキシを通すと期待しているサービスにリクエストが届かない。

解決

設定とか調査するんだけれど、悪いところは見当たらない。コードでも調査して、HTTPクライアント(cURL)に渡すURLは正しいことが確認できた。コードや設定を見つめていても原因がわからなかったので、tcpdump でどんな通信をしているか覗いてみることにした。

そしたら、Host ヘッダの所に各サービスのホストではなくプロキシのホストが設定されていたのがわかった。

ただ、わかった瞬間は別におかしいとは思わなくて、しばらくしてから、バーチャルホストってどういう仕組みで動いているのかを考えたときに、原因に気付いた。

名前ベースのバーチャルホストは HTTP ヘッダの Host の値をもとにリクエストの振り分け先を決定している。

なので、プロキシから各サービスに送信するには、Host に各サービスのドメインを設定するのが正しいが、実際はクライアントから受信した Host の値を設定していしまっていた(というか、無理に設定しなければ HTTPクライアントがよきに計らってくれる)。

反省

反省点は、どんなヘッダがあるのかろくに調べもせず全部丸投げにすればいいだろうと安易に考えてしまったこと、だろうか。

Content-Length とかも気をつけないとなー。

というか、どのヘッダを送らない(ブラックリスト)方式ではなく、どのヘッダは送る(ホワイトリスト)方式にした方がよさそう。

はてな エンジニアブロガー祭りに参加して

ここ最近ブログを書いていなかったけれど(確認したら 2013年8月8日が最後だった)、書かかないのは損なんんじゃないのか、と思った。

なので、具体的な目標はないけれど、できるだけ書いてみようと思う。

その一歩目がこれ。

Aeson でオプション値を含む JSON を扱う.

JSON の要素が全部が必須であれば簡単だけれども、そうでない場合が往々にしてある。公式のドキュメントにはサンプルがなかったので、ここにメモしておく。

値なし

Person の middleName がオプション値。型は Maybe Text。パース時に (.:?) を使用する。middleName が存在しなかった場合は、当然 Nothing だ。

{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import Control.Applicative
import Control.Monad
import Data.Text
import Data.ByteString.Lazy.Char8

data Person = Person
     { firstName  :: Text
     , middleName :: Maybe Text
     , lastName   :: Text
     } deriving Show

instance FromJSON Person where
    parseJSON (Object v) = Person <$>
                           v .:  "firstName"  <*>
                           v .:? "middleName" <*>
                           v .:  "lastName"
    parseJSON _          = mzero

instance ToJSON Person where
    toJSON (Person fname mname lname) = object [ "firstName"  .= fname
                                               , "middleName" .= mname
                                               , "lastName"   .= lname
                                               ]

decodeWithMiddleName :: Maybe Person
decodeWithMiddleName = decode "{\"firstName\":\"John\", \"middleName\":\"Lambda\", \"lastName\":\"Doe\"}"

decodeWithoutMiddleName :: Maybe Person
decodeWithoutMiddleName = decode "{\"firstName\":\"John\", \"lastName\":\"Doe\"}"

encodeWithMiddleName :: ByteString
encodeWithMiddleName = encode $ Person "John" (Just "Lambda") "Doe"

encodeWithoutMiddleName :: ByteString
encodeWithoutMiddleName = encode $ Person "John" Nothing "Doe"

実行結果。

*Main> encodeWithoutMiddleName
"{\"middleName\":null,\"firstName\":\"John\",\"lastName\":\"Doe\"}"
*Main> decodeWithMiddleName
Just (Person {firstName = "John", middleName = Just "Lambda", lastName = "Doe"})
*Main> decodeWithoutMiddleName
Just (Person {firstName = "John", middleName = Nothing, lastName = "Doe"})
*Main> encodeWithMiddleName
"{\"middleName\":\"Lambda\",\"firstName\":\"John\",\"lastName\":\"Doe\"}"
*Main> encodeWithoutMiddleName
"{\"middleName\":null,\"firstName\":\"John\",\"lastName\":\"Doe\"}"

デフォルト値

パース時に値がなかった場合、デフォルト値を設定したい場合は (.!=) を使う。

{-# LANGUAGE OverloadedStrings #-}
import Data.Aeson
import Control.Applicative
import Control.Monad
import Data.Text

data Person = Person
     { firstName  :: Text
     , middleName :: Text -- Maybe じゃない
     , lastName   :: Text
     } deriving Show

instance FromJSON Person where
    parseJSON (Object v) = Person <$>
                           v .:  "firstName"  <*>
                           v .:? "middleName" .!= "Haskell" <*> -- デフォルト値 "Haskell"
                           v .:  "lastName"
    parseJSON _          = mzero

decodeWithMiddleName :: Maybe Person
decodeWithMiddleName = decode "{\"firstName\":\"John\", \"middleName\":\"Lambda\", \"lastName\":\"Doe\"}"

decodeWithoutMiddleName :: Maybe Person
decodeWithoutMiddleName = decode "{\"firstName\":\"John\", \"lastName\":\"Doe\"}"

結果

*Main> decodeWithMiddleName
Just (Person {firstName = "John", middleName = "Lambda", lastName = "Doe"})
*Main> decodeWithoutMiddleName
Just (Person {firstName = "John", middleName = "Haskell", lastName = "Doe"})

($) と (.)

($) と (.) のことをよくわかっていないので調べる。

ghci で見てみる。

Prelude> :i ($)
($) :: (a -> b) -> a -> b   -- Defined in `GHC.Base'
infixr 0 $

Prelude> :i (.)
(.) :: (b -> c) -> (a -> b) -> a -> c   -- Defined in `GHC.Base'
infixr 9 .

($)は引数に関数をひとつ取るが、(.) は二つ取る。関数合成だから当然か。 結合の優先順位は ($) は一番弱く、 (.) が一番強い。

実装はどうなっているかというと、base/GHC/Base.lhs を見てみる。

($)   :: (a -> b) -> a -> b
f $ x =  f x

(.)    :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)

($) は、一見意味ないんじゃないかと思うけど、結合性を右結合にするところに意味があるのか?右結合にすることによって、カッコを省略できる、と。

(.) f g がとる引数はひとつだけ。\x y -> f (g x y) のようにできるとよく勘違いしてしまう。

参考

ping

Hakell で ping を作った。Haskellping を作ろうと思ってから2年以上も経ってしまった

コードは Gist にある。

ping を作る中で一番苦労したところは、送信するパケットを作成ところ。String (Char) でどうやってデータを作ればいいのかわからなかった。最終的には Data.Binary を使うことで解決した。

というか、初めて使うライブラリばかりで全体的に大変だった。が、調べてみると意外にできるもんだな、と思った次第。

同じByteString でも、Data.ByteString と Data.ByteString.Lazy 同士で直接計算できないのはちょっと面倒な気もするが、まあ、そういうものだし。