1章 WebSocketを使ったチャットアプリケーション(シンプルなウェブサーバー)
(Mac OS X El Capitanを基本に書いています)
最初のプログラムとしてブラウザを使ったチャットアプリケーションを作ってみよう。 ブラウザとサーバーはWebSocketを使って通信することにします。 WebSocketっていうのはウェブサーバー/ブラウザ間の双方向通信を行う技術です。 通信がつながると、ブラウザ起点の通信だけでなく、サーバー起点の通信も可能になります。 詳しくはWikipediaを見てね。
これは書籍「Go言語によるWebアプリケーション開発」を使って Haskellの勉強をしようというシリーズです。 Haskellは書籍「関数プログラミング実践入門 ──簡潔で、正しいコードを書くために (WEB+DB PRESS plus)」を読んで勉強中。
表記について
以降の説明では次の表記を使います。
コマンド
$ echo これはコマンド実行です
これはコマンド実行です
$
始まりの行が入力したコマンド行で、それ以外は出力された行か補足です。
出力が多い時は:
で省略します。
なおrootユーザーでのコマンドは#
始まりで表記します。
ソースコード
-- 入門プログラム main = putStrLn "Hello, World!"
使用パッケージの選定
Haskellには多くのウェブフレームワークが用意されています。
今回はウェブフレームワークのひとつYesodで使われているWAI
、WARP
、WAI WebSocketsと、WebSockets、HTTP types、あとGinger
を使うことにしました。
WAI
WAI
はHaskellのウェブサーバー/フレームワークの共通インタフェースを提供します。
CGIやWSGIみたいなものでしょうか。
Warp
Warp
はWAIを使ったHaskellのHTTPサーバーライブラリです。
RubyのWEBrickみたいなものでしょうか。
WAI WebSockets
WAI WebSockets
はWAI上でWebSocketsを使うためのライブラリです。
WebSockets
WebSockets
はHaskellでWebSocketサーバーを実装するためのライブラリです。
Ginger
Ginger
はHaskellのテンプレートエンジンです。
PythonのJinja2をHaskellで実装したもののようです。
シンプルなウェブサーバー
まずはパッケージの準備を行ってシンプルなウェブサーバーを作ってみましょう。
プロジェクト用ディレクトリ作成
プロジェクトを作りたいディレクトリに移動し、stack new
でプロジェクトディレクトリを作ります。
今回は~/proj/にchatプロジェクトを作ってそこで作業をします。
$ cd ~/proj
$ stack new chat
Downloading template "new-template" to create project "chat" in chat/ ...
The following parameters were needed by the template but not provided: author-email, author-name, authorName, category, copyright, github-username
You can provide them in /Users/big/.stack/config.yaml, like this:
templates:
params:
author-email: value
author-name: value
:
Or you can pass each one as parameters like this:
stack new chat new-template -p "author-email:value" -p "author-name:value" -p "authorName:value" -p "category:value" -p "copyright:value" -p "github-username:value"Using cabal packages:
- chat/chat.cabal
:
Writing configuration to file: chat/stack.yaml
All done.
$cd chat
この出力ではパラメータ指定がされていないと出ています。 今回はサンプルですので省略しましたが、実際のプロジェクト時には作者やメールアドレスなどを指定します。(いつか調べたい)
Cabalファイル編集
まず作られたchatディレクトリにあるchat.cabal
ファイルを編集します。
Library
セクションのbuild-depends:
に使用するパッケージを追加します。
: library hs-source-dirs: src exposed-modules: Lib build-depends: base >= 4.7 && < 5 -- ここに使用パッケージ追記 , http-types , wai , warp default-language: Haskell2010 :
ソースファイル編集
次にchat/src/Lib.hs
ファイルを編集しましょう。
とりあえずsomeFunc
関数を書き換えていきます。
今回は短いので全て引用しておきます。
参考ページ
{-# LANGUAGE OverloadedStrings #-} module Lib ( someFunc ) where import Network.HTTP.Types (status200) import Network.HTTP.Types.Header (hContentType) import Network.Wai (Application, responseLBS) import Network.Wai.Handler.Warp (run) someFunc :: IO() someFunc = do let port = 3000 putStrLn $ "Listening on port " ++ show port run port app app :: Application app req f = f $ responseLBS status200 [(hContentType, "text/plain")] "Hello world!"
1行目 {-# LANGUAGE OverloadedStrings #-}
文字列リテラルである"〇〇"の扱いを変更します。
今回使用するパッケージが[Char]
ではなく、ByteString
を要求するためです。
3〜5行目 module
外部に提供するものを書きます。
雛形ではLibモジュール
としてsomeFunc
を提供しています。
7〜10行目 import
使用するパッケージを書きます。
今回は何がどのパッケージかわかるように使う要素も(〇〇)
で指定しています。
13〜16行目 run port app
Warpを利用してポート番号とApplication
を渡してサーバーを起動しています。
18〜20行目 app :: Application
WAIのApplication
型を定義しています。
リクエストに対してレスポンスとしてstatus:200
、context-type:text/plain
と内容Hello world!
を返すように定義しています。
アプリビルド
chat
ディレクトリでstack build
してみましょう。
初回はCabalファイルに追加したパッケージのインストールが行われるため、かなり時間がかかります。
$ stack build
:
chat-0.1.0.0: build
Preprocessing library chat-0.1.0.0...
:
アプリ実行
ビルドが成功したら、実行してみましょう。
$ stack exec chat-exe
Listening on port 3000
ブラウザでhttp://localhost:3000
にアクセスすると、Hello world!
メッセージが表示されれば成功です。
パチパチパチ〜
使われている基礎構文の説明
Haskell 2010 言語報告書
ちゃんと言語報告書を読んだわけではなく、実際のコードから推測したりググって調べたものを書いています。まだHaskellに慣れていないので勘違いがあったらごめんなさい。
コメント
--
から改行まではコメントとして扱われます。
ただし、他の構文に使われる字句の一部の場合はそっちが優先されます。
例えば-->
や|--
はコメントとしては扱われないです。
なので慣習的に前後に空白を置いて書かれることが多いようです。
コメントが複数行になる場合は{-
と-}
で囲むと改行を含めてコメントとして扱われます。
この形式のコメントは入れ子にできるので、ネストコメントと呼びます。
以下がコメント例です。
-- これは1行コメント {- これは複数行コメント 改行もOK {- ネストできます -} ここもコメントです -} -- {- ここは1行コメントです this_is_NOT_comment = "コメントアウトの切り替え" -- -}
最後のコメントがネストコメントのようにも見えますが、コメントアウトのイディオムです。
ネストコメントの終了を-- -}
としておくと、開始部分を1行コメントにするかどうかで、コメントアウトの切り替えができます。
また、ネストコメントはコンパイラへのプラグマ指定としても使われます。
今回のコードでは{-# LANGUAGE OverloadedStrings #-}
が使われています。
次回予定の記事で扱いますが、ソースコードとその中のコメントからドキュメントを自動生成するhaddock
というツールもあります。
-- |
や{- |
、-- ^
や{- ^
始まりのコメントがドキュメント化されます。
識別子
- 変数識別子:小文字アルファベット始まりで、英字・数字・シングルクォートが0個以上続くもの
- 構成子識別子:大文字アルファベット始まりで、英字・数字・シングルクォートが0個以上続くもの
識別子は小文字、大文字を区別し、アンダースコア_
は小文字とみなされます。
また、_
始まりの識別子はコンパイラの未使用警告を抑制するようです。
型推論
Haskellには型推論機能があります。 わざわざ書かなくても自明なものはコンパイラが自動で推測してくれます。 ただ、自明じゃないものや人に向けたドキュメントとして型を指定することもできます。
::
で型を指定します。
someFunc :: IO()
とapp :: Application
が型指定です。
左側の小文字始まりのものが変数名(関数名)で、右側の大文字始まりのものが型になります。
IO()
の()
が何を指しているかは勉強不足です。。。
let式 / where節
let
で式中でのみ有効な一時的な定義を行うことができます。
似たような構文としてwhere
節があります。
where
は式中ではなく、宣言全体で有効な一時的な定義を行うことができるようです。
きっとmodule
のwhere
もそういうものなんでしょう。
演算子
Haskellでは演算子を自分で定義することができます。
用意されている演算子は+
,-
,*
,/
などよくあるものや、$
、++
など見かけないものがあります。
(私は普段C++
を使っています)
$
は関数適用を行います。通常は左結合の所を右結合にできます。関数の引数として別の関数の結果を使いたい場合に()
を省略できて便利です。++
はリストの結合です。文字列リテラルは文字のリストなので、++
を使って結合します。