レガシーコード生産ガイド

私に教えられることなら

Squeak5.0でHTTPサーバを立ててSmalltalk環境外と通信する

パッケージWebClient-Core内にあるWebServerクラスが便利そうなので使ってみた(Client?)。いつから入ってるものなのか知らないけど、とりあえず5.0にはある。

クラスメソッドのexampleDoItなどを参考に、

  • htmlを表示
  • XHRでブラウザ側からテキストを送信
  • Squeak側でそれをTranscriptに表示

を目標にする。

立てる

まずサーバを立てる。サンプルではWebServerクラスのdefaultインスタンスを使っているけど、特にインスタンスは1つじゃないといけないとかそういう制限はないみたいだ。なので普通にnewして使える。

| server port |
port := 9999
server := WebServer new.
server listenOn: port.

落とす

server destroyでlisteningを止めることができる。

聞く

WebServer >> #addService:action:を使ってアクセスを捌ける。addService:には聞きたいパスを、action:にはアクセス時に実行するブロックを渡す。ブロックにはリクエストオブジェクトが渡され、それを使って処理する。

例えばルート(/)にGETでアクセスが来た場合単にhelloというテキストを返す、なら次のようにする。(上で作ったserverに対するメッセージ)

server addService: '/' action: [:req| req send200Response: 'hello']

(パスにマッチングなどを使う方法があるのかはまだ調べてない)

投稿を受ける

POSTで何かしら送られた場合はreq isPostRequestがtrueを返し、req contentに内容が入る。

例えば上に加えて/に送られたテキストをTranscriptに表示したい場合は次のようにする。

server addService: '/' action: [:req|
    req isPostRequest ifTrue: [ Transcript cr; show: req content ].
    req send200Response: 'hello']

ただ、req contentはByteStringなので、例えばUTF8で送られた日本語を扱いたいときにはreq content utf8ToSqueakなどとして扱う。

また、サンプルではレスポンス送信時に

req
    send200Response: (HTML内容 convertToWithConverter: UTF8TextConverter new) 
    contentType: 'text/html; charset=utf-8'.

などと変換・指定している。

XHRで送信されたテキストを受けるサンプル

簡単に、WebServerに直接クラスメソッドをつけていく。

/にGETメソッドが来たら、テキスト入力とボタン、それから送信用のJSが入ったhtmlを送る。POSTメソッドが来たら、contentをTranscriptに表示してsuccess!と送り返す。

WebServer exampleTranscriptで起動し、前者をexampleTranscriptGet、後者をexampleTranscriptPostで処理することにする。(#addService:action:に渡すブロック内部に処理を書いてしまうと、処理の変更がやや面倒だと思う)

ちなみにWebServer resetで停止できる。

まずexampleTranscript。上のサンプルの組み合わせ。WebServerクラスのdefault(クラス変数に入ったインスタンス)を使うので、serverを返す必要はない。

exampleTranscript

    | server port |
    port := 9999.
    server := WebServer reset default.
    server listenOn: port.
    server addService: '/' action:[:req|
        req isPostRequest 
            ifTrue: [
                self exampleTranscriptPost: req]
            ifFalse: [ self exampleTranscriptGet: req].
    ].

次にexampleTranscriptPost:

exampleTranscriptPost: req
    Transcript cr; cr; show: 'POST:'; cr; show: (req content utf8ToSqueak).
    req send200Response: 'success!'.

exampleTranscriptGetだけど、JSが長いので変数代わりにexampleTranscriptJSに分ける。

exampleTranscriptGet: req
    |html|
    html := String streamContents:[:s|
        s nextPutAll: '<html><head><title>at: ', req url,'</title></head><body>'.
        s nextPutAll: '<input type="text" id="fld" size=40 />'.
        s nextPutAll: '<input type="button" id="btn" value="send" />'.
        s nextPutAll: '<script type="text/javascript">'.
        s nextPutAll: self exampleTranscriptJS.
        s nextPutAll: '</script>'.
        s nextPutAll: '</body></html>'.
        s crlf].
    
    req
        send200Response: (html convertToWithConverter: UTF8TextConverter new) 
        contentType: 'text/html; charset=utf-8'.
exampleTranscriptJS
    ^ 'var xhr = new XMLHttpRequest();',
        'var btn = document.getElementById("btn");',
        'var fld = document.getElementById("fld");',
        'btn.addEventListener("click", function () { ',
        'xhr.open("POST", "/"); xhr.send(fld.value); })'

WebServer exampleTranscriptで起動し、localhost:9999にアクセス、テキスト入力に適当に入れてボタンを押すと、Transcriptにテキストが来る。フォントを適切に指定していれば日本語も表示できる。

f:id:phaendal:20151230131141p:plain

シェルスクリプトからのインターフェイスにする

#addService:action:でサーバを起動したままルートを追加していけるので、例えばカウンタを使ってルートを生成し、シェルスクリプトの標準入力を受ける窓口にしたりできる。

例えばワークスペースで以下のコードを評価して、サーバを起動してnewRouteブロックを作り

port := 9999.
server := WebServer new.
server listenOn: port.
i := 0.

newRoute :=  [:act|
        i := i + 1.
        server addService: '/', i action: act.
        Transcript cr; show: '/', i. "ここでアクセス用のルートをTranscriptに記録"
        i].

さらにワークスペースで次のコードをprintItすると、入力をTranscriptに流すAPIのアクセス用の番号が手に入る。(上でTranscriptにも記録している)

newRoute value: [:req|
    req isPostRequest ifTrue: [Transcript cr; show: (req content utf8ToSqueak)].
    req send200Response: 'success!']. "=> 1"

curlコマンドを使えば標準入力をPOSTできるので、次のようなシェルスクリプトを作り

phaendal $ cat bin/sqin 
#!/bin/sh

curl "http://localhost:9999/$1" -X POST -d @- < /dev/stdin

echo 'ヌッ!' | sqin 1などでSqueakにテキストデータを渡し、この場合Transcriptに表示できる。(ただ改行がうまくいかないので、どうにかする必要がある)

挙動が変になったら

同じポートをlistenするインスタンスが残っていて、受け取ってしまってるかもしれない。

(WebServer allInstances select: [:s| s listenerPort = 9999]) do: [:s| s destroy]などで全部停止できる。

広告を非表示にする