このページは福井県立大学の田中求之が2006年1月まで運用していた Mac のサーバ運用に関する会議室 「Web Scripter's Meeting」の記録です。情報が古くなっている可能性がありますのでご注意ください。

Server Pushでチャット

発言者:重松修
( Date Sunday, May 18, 1997 13:13:06 )


いつもお世話になっております。

今日は、変なことを思いついたのですが、知識が不足していて可能かどうか、
検討がつきません。アドバイスを戴ければと存じます。

● やりたいこと
 ホームページにチャットコーナーを開設する

● 問題点
 専用線が64Kのため、30秒おきにクライアントプルによる更新などという
低次元のものを設置するとホームページがとても重たくなってしまいます。

● 考えた対策
 クライアントプルを行うにしても、前回そのクライアントがアクセスした
ときから更新された内容のみを送る。それをJavaScriptで表示する。
 ただし、この場合でも、定期的に更新の有無にかかわらず、サーバに接続
しなければならない。
 そこで、最初に接続したときにずっとコネクションを保ったままのページ
を表示して、誰か(もちろん自分も)がメッセージを送信したら、それを
エコーする。

● わからないこと
 ずっとコネクションを保ったまま、順次データを表示することは、CGIで
可能なのでしょうか?NTTのタイタン(サーチエンジン)は検索しつつ
ヒットすると結果を返してきます。この、ヒットした結果を返すのと同じように
チャットのエコーを送れれば、リアルタイムに、会話が楽しめると思いました。
 このホームページにて、 CGIを用意すれば、一定間隔ごとにデータをクライ
アントに送ることが可能になる。(Server Push アニメーション)ということ
は、送りたいときにデータを送ることができるかもしれない、と思いました。
 また、接続したまま=普通にHTMLをサーブしているものと共有すると、
たくさんの人(ex. 10人)などがアクセスをすると、ホームページが見えなく
なる気がしますが、同じサーバでポート80を10と、ポート8080(チャット用)
を5という風に用意することはできるのでしょうか?

よろしくお願いいたします。


→  NTT TITAN

田中求之 さんからのコメント
( Sunday, May 18, 1997 14:09:27 )

Web でチャットをやろうとすると、どうしても疑似的なものになると思います。
(問題点などはご自身ですでに書かれている通りです)

>同じサーバでポート80を10と、ポート8080(チャット用)
>を5という風に用意することはできるのでしょうか?

サーバーソフトを 8080 でも走らせて、チャットはそちらへいくようにしておく
という設定は可能でしょう(ポートさえ違えておけば、同じマシンで複数の Web
サーバーソフトを走らせることは可能です)。


個人的には、チャットはやはり専用のサーバー( IRC サーバーって日本語対応のものが
あったかな??)でやるべきだとは思います。 Web (HTTP) のプロトコルとチャットが
根本的になじまないものだからです。

重松修 さんからのコメント
( Thursday, June 05, 1997 19:13:10 )

その後色々と調べました。

PerlやCのスクリプトは色々見つかりました。が、分からないことが出てきま
した。これは、どちらかというと、AppleScript、AppleEventの問題に思い
ます。

動かないサンプル
property crlf : (ASCII character 13) & (ASCII character 10)

property boundary : "---EOP---"
property http_10_header : "HTTP/1.0 200 OK" & crlf ツ
  & "MIME-Version: 1.0" & crlf ツ
  & "Content-type: multipart/x-mixed-replace;boundary=" & boundary & crlf ツ
  & crlf

on ヌevent WWWスsdocネ path_args ツ
  given ヌclass kforネ:search_args, ヌclass postネ:post_args, ヌclass userネ:username, ヌclass passネ:password
  return http_10_header
  return boundary
  repeat
    return "Content-type: text/html" & crlf & crlf
    return "Present time: "
    return (current date as string) & crlf
    return boundary
    repeat 10000 times -- 1秒ほど待たせたい
    end repeat
  end repeat
end ヌevent WWWスsdocネ

これはサーバの時間が単に表示されるだけのCGIのつもりです。
1秒おきにプッシュする予定なのですが、どうも最初のreturn文でCGI自体が
処理を終了するようです。


(参考)
#!/bin/sh 
echo "HTTP/1.0 200" 
echo "Content-type: multipart/x-mixed-replace;boundary=---EOP---" 
echo "" 
echo "---EOP---" 
while true 
do 
echo "Content-type: text/html" 
echo "" 
echo "Present time: " 
date 
echo "---EOP---" 
sleep 1 
done

重松修 さんからのコメント
( Thursday, June 05, 1997 19:20:20 )

先ほどアップした内容に誤りがありました。

property crlf : (ASCII character 13) & (ASCII character 10)

property boundary : "---EOP---" & crlf
property http_10_header : "HTTP/1.0 200 OK" & crlf ツ
  & "MIME-Version: 1.0" & crlf ツ
  & "Content-type: multipart/x-mixed-replace;boundary=" & boundary ツ
  & crlf

on ヌevent WWWスsdocネ path_args ツ
  given ヌclass kforネ:search_args, ヌclass postネ:post_args, ヌclass userネ:username, ヌclass passネ:password
  return http_10_header
  return boundary
  repeat
    return "Content-type: text/html" & crlf & crlf
    return "Present time: " & ((current date) as string) & crlf
    return boundary
    repeat 10000 times
    end repeat
  end repeat
end ヌevent WWWスsdocネ

(これも残念ながら動きませんが。)

田中求之 さんからのコメント
( Thursday, June 05, 1997 19:23:02 )

>1秒おきにプッシュする予定なのですが、どうも最初のreturn文でCGI自体が
>処理を終了するようです。

ハンドラーは最初の return の時点で終了します( return はハンドラーの
実行を終了して、呼び出し元に reply を返す=return というものですから)。

MacOS のCGIで Server Push を使って連続的なデータの送り出しを行いたい
場合には、Send Partial という機能を利用します。ただし、これはすべての
サーバーが実装している機能ではありません( WebSTAR 互換であれば大丈夫
だと思います)。

Send Partial を使ったスクリプトについては、下記のページをごらんください


→  Send Partial の使い方

重松修 さんからのコメント
( Thursday, June 05, 1997 19:45:17 )

さっそくありがとうございます。

サンプルプログラムを試そうとしましたが、残念ながら、QPQとTeleFinderでは
『send partial』の部分がコンパイルできません。用語集を見たのですが、
それらしいコマンドはリストされていなかったので、どうやら、対応していない
ようです。(T_T)

WEBSTARは高すぎて買えないので、ServerPush Chatはあきらめ見たいです。
(T^T)

重松修 さんからのコメント
( Friday, June 06, 1997 18:43:16 )

上記訂正します。
QPQは対応しているようです。

以下オンラインマニュアルより
----
Server Push

In the usual CGI model, a single AppleEvent is
sent to the CGI, which processes the request and
returns its result in the reply to the original
event. The server push feature allows you to
return data as multiple data packets. The
mechanism for this is the "Send Partial"
AppleEvent. If your CGI intends to use server
push, instead of returning its result in the reply
to the original event, it should return
"<SEND_PARTIAL>" as the result. This tells Quid
Pro Quo to keep the connection open, and send any
data that is received via "Send Partial" events. To
pass data on to the client, the CGI sends the "Send
Partial" event (class 'WWWomega' [option-Z],
event code 'SPar'). The event contains the data to
be sent to the client in the direct parameter. The
event should also contain the 'Kcid' parameter
identifying which connection the data is to be
sent to (the 'Kcid' parameter is obtained from the
original event. A third parameter, 'Kmor'
(typeBoolean) indicates whether or not there is
more data to follow. If false, Quid Pro Quo closes
the connection and expects no more data from the
CGI. If Quid Pro Quo returns an error when the
event is sent, the client has closed the connection
and no more data should be sent. The Quid Pro Quo
distribution included a sample server push CGI
written in C++ that demonstrates how to create
server-based animations.

という訳でどうやらAppleEvent(wwwスSPar)で送る必要があるようです。

重松修 さんからのコメント
( Friday, June 06, 1997 19:15:01 )

send partialでわからない点があります。

それは、例えば、リアルタイムにGIFなどをPUSHし続けるカメラとかがありま
すが、永遠に送り続ける場合、つまり終わりはクライアントが別のページに移っ
たときということになります。

つまり上の英文によりは、コネクションがきれて送るべきあいてがいない時は
エラーになる、とかいてあります。

ということは、とりあえず、AppleEventでデータを垂れ流して、エラーが起き
たらそのセッションは終了ということでしょうか。(@_@)?

partial.acgiとデータを流すparial.serverがいるとして、簡単なフローをか
くと、
・partial.acgi
 wwwスsdocでQPQから呼び出される。
 ことのときセッションIDはclass Kcidに入っている。
 partial.serverに送信開始のイベントとセッションIDを渡す。
 partial.acgiはとりあえず、return "<SEND_PARTIAL>"を返す。
 おしまい。
・partial.server
 適当な(任意の)イベントでpartial.acgiから呼び出される
 エラーに備えて
 まずは、pushであることをヘッダを付けて1回目をWWWサーバに送る。
 以降は、適宜時間を置いてデータを送るを繰り返す。
 エラーが起きたら
 終了する

基本的な疑問ですが、QPQにセッションID(connection_id)と実際のデータ
(myData)を送る方法が良くわかりません。

tell application "QPQ" to ヌevent wwwスSParネ connection_id myData

で、よいのでしょうか?classの指定はしなくてもいいのかな??そんなはずは。。。

田中求之 さんからのコメント
( Saturday, June 07, 1997 19:09:47 )

QPQ にセッション ID を渡す必要はありません。セッション ID は QPQ の方で
CGI を呼び出すときに渡されるわけですから、この渡された ID を使って処理を
行えば、QPQ の方でコネクションの管理を行ってくれます。

Partial Server から QPQ にデータを渡すときには、 send partial イベント
を使うわけですが、このときに、最初に渡された ID をパラメーターで指定すれば
よいだけですよ。

また、コネクションがユーザー側で切られたときには、send partial の reply
にエラーが返るはずですので、この時点で処理を打ち切ることになります。

WebSTAR をダウンロードして、テストモード(試用モード)で動かして(シリアルの
入力のダイアログをキャンセルすれば、2時間動かせるモードに入ります)、
スクリプトを試してみるのがよいでしょう。

ただ、もし本格的に Server Push やりたいのなら、NetEvents で Push 用の
サーバーを組んでしまったほうが、少なくともスクリプトの次元では好き勝手なこと
ができますよ(さすがに簡単とは言いませんが)。

重松修 さんからのコメント
( Sunday, June 08, 1997 00:10:28 )

田中先生、ありがとうございます。m(__)m

とりあえず、サンプルを作りましたので、アップロードします。
サーバの時間を10回、約1秒間隔でPUSHします。

プログラム1(partial.acgi.txt)
on ヌevent WWWスsdocネ given ヌclass Kcidネ:connection_id
  
  ignoring application responses
    tell application "partialserver" to ヌevent xxxxYYYYネ connection_id
  end ignoring
  
  return "<SEND_PARTIAL>"
  
end ヌevent WWWスsdocネ

プログラム2(partialserver.txt)
property crlf : (ASCII character 13) & (ASCII character 10)

property boundary : "---ThisRandomString---" & crlf

property http_10_header : "HTTP/1.0 200 OK" & crlf ツ
  & "MIME-Version: 1.0" & crlf ツ
  & "Content-type: multipart/x-mixed-replace;boundary=" & boundary ツ
  & crlf

on ヌevent xxxxYYYYネ connectionID
  waitasecond()
  sendpartial(http_10_header & boundary, connectionID, true)
  try
    repeat 9 times
      set bodydata to "Content-type: text/html" & crlf & crlf & ツ
        "Current Date: " & ((current date) as string) & crlf & boundary
      sendpartial(bodydata, connectionID, true)
      waitasecond()
    end repeat
    set bodydata to "Content-type: text/html" & crlf & crlf & ツ
      "Current Date: " & ((current date) as string) & crlf & boundary
    sendpartial(bodydata, connectionID, false)
    
  on error
    -- エラーが起きた=ユーザがコネクションを切断(ログアウト)した
    -- でも接続が切られてもエラーが起きない!どうして?(@_@)
  end try
end ヌevent xxxxYYYYネ


on sendpartial(bodydata, connectionID, morefg)
  tell application "Quid Pro Quo"
    ヌevent WWWスSParネ bodydata ツ
      given ヌclass Kcidネ:connectionID, ヌclass Kmorネ:morefg
  end tell
end sendpartial

on waitasecond()
  set aDelay to (current date) + 1
  repeat while aDelay > (current date)
  end repeat
end waitasecond

重松修 さんからのコメント
( Sunday, June 08, 1997 00:16:01 )

最終的には、好き勝ってができるのと、プッシュ=接続しているあいだ中ポート
を占有する=普通HTMLが送り出せなくなる、という理由で、先生のおっしゃる
通り、NetEventバージョンも作ろうとは思っていました。

セッションが切れたのをどう関知するかわからないので、先生のNetEvent版の
WWWを参考にプッシュ専用のWWWを作って見ようと思います。