JavaのSocketクラスを使うと、ソケットによる通信を簡単に実装できます。今回は、JavaのSocketを使ってWebサーバー上のCGIに文字列を送信し、その応答を受け取ってみましょう。また、CGIの方では送信された文字列を記録してみることにします。これにより、「Javaアプレットで作成したデータをWebサーバー上のCGIに送り処理・保存、さらにCGIからの応答をJavaアプレットで取得」という一連の流れをテストしてみるわけです。この流れは、Javaアプレット対応のWebブラウザとCGI実行可能なWebサーバーさえあれば特殊な環境なしで「クライアントにダウンロードしたJavaアプレットと、サーバー上のCGIとの連携」が可能になる、という点で大きな可能性を持っていると言えるでしょう。
Socketを使用したデータ通信では、入出力ともにストリームを使うことになります。ただし、Javaの場合はString型文字列がUNICODEなので単純なバイト列としては扱えません。通常、Webサーバーが扱う文字列はEUCやShift-JISなどの1/2バイト混在バイト列でしょうから、JavaのStringとの間でどのようなバイト列を送受信すべきか、意識する必要があります。
今回は、JavaアプレットからWebサーバーへの送信にはStringオブジェクトのgetBytes()メソッドで取得したバイト列を使用し、受信にはInputStreamReaderオブジェクトを介して読み込んだバイト列を使用するようにしました。ただし、文字コードについては一切考慮しません。
CGIとの通信に使用するソケットとストリームは以下のようにして作成します。なお、stAddrはCGIがあるWebサーバのアドレスです。この処理では、アドレスstAddrのポート80番との通信を行うソケットを作成し、そのソケットの入出力ストリームを取得しています。入力ストリームに関しては、ストリームを直接扱うのではなくバイト列から文字列表現への変換を行うInputStreamReaderを介している点に注意してください。
Socket sock=null; InputStreamReader irStr=null; OutputStream osStr=null; String stReturn=new String(); try { // サーバー接続用ソケットと入出力ストリーム作成 sock=new Socket(stAddr,80); irStr=new InputStreamReader(sock.getInputStream()); osStr=sock.getOutputStream(); } catch(Exception e) { return "Error!"; }
ストリームができたら、次にCGIに送る文字列stSendを作成します。今回は、文字列stArgをPOSTするためのヘッダ・本文を作りましょう。
String stHead="POST "+stUrl+" HTTP/1.0\n"; stHead+="Content-Length:"+String.valueOf(stArg.length())+"\n\n"; String stSend=new String(stHead+stArg);
ヘッダ部分は、CGIのファイル名(今回はURLで指定)stUrlに対しPOSTを指定しています。続いて、送信する文字列stArgの文字列長をContent-Lengthに指定して空行でヘッダを終了、続いて文字列を付加すれば送信用の文字列は完成です。
文字列の送信は簡単で、ストリームにwrite()するだけ。この時、stSendのgetBytes()でバイト列表現を送信する点に注意してください。
osStr.write(stSend.getBytes());
これでCGIに文字列がPOSTされたので、CGIからの応答を受け取りましょう。今回は、InputStreamReaderのread()メソッドで1文ずつ読み取った文字を、StringBufferオブジェクトsbStrに追加していくことにしました。InputStreamReaderのread()は(関連付けられている)ストリームの末端に達するとー1を返すので、read()がー1を返すかエラーになるまで読み取りを続けます。
while (bRead) { // CGIの応答データを受け取る try { s=irStr.read(); // 1文字読み取り if (s==-1) // ストリーム終了 bRead=false; else // 読み込んだ文字を文字バッファに追加 sbStr.append((char)s); } catch(Exception e) { bRead=false; } }
これでCGIからの応答がsbStrに記録されたので、あとはsbStrのtoString()メソッドで文字列に変換するだけですね。
以上のことをまとめて、文字列stArgをアドレスstAddrのCGI(stUrl)にPOSTし、その応答を取得するメソッドsendを作っておきましょう。send()では、一連の送信・受信文字列をstReturnとして返すようにします。
private String send(String stArg) { // CGIにstArgをPOST Socket sock=null; InputStreamReader irStr=null; OutputStream osStr=null; String stReturn=new String(); try { // サーバー接続用ソケットと入出力ストリーム作成 sock=new Socket(stAddr,80); irStr=new InputStreamReader(sock.getInputStream()); osStr=sock.getOutputStream(); } catch(Exception e) { return "Error!"; } String stHead="POST "+stUrl+" HTTP/1.0\n"; stHead+="Content-Length:"+String.valueOf(stArg.length())+"\n\n"; String stSend=new String(stHead+stArg); try { // CGIに文字列をPOST osStr.write(stSend.getBytes()); } catch(Exception e) { return "Error!"; } stReturn="Send:\n"+stSend; boolean bRead=true; StringBuffer sbStr=new StringBuffer(); int s; while (bRead) { // CGIの応答データを受け取る try { s=irStr.read(); // 1文字読み取り if (s==-1) // ストリーム終了 bRead=false; else // 読み込んだ文字を文字バッファに追加 sbStr.append((char)s); } catch(Exception e) { bRead=false; } } stReturn+="\nResv:\n"+sbStr.toString(); return stReturn; }
なお、CGIの方は、以下のように受け取った文字列をそのまま出力し、さらにファイルに保存するようにしてみました。このCGIは、Webサーバーが余計なヘッダなどを付加しないようにnphスクリプトとして、CGIの出力をそのまま返すようにしてあります。
#!/usr/local/bin/perl # postされたデータを読み込む read(STDIN,$data,$ENV{'CONTENT_LENGTH'}); print "CGI Responce\n$data\n"; ($sec,$min,$hour,$day,$mon,$year,$wday,$yday)=localtime(time); $year+=1900; $mon++; $date=sprintf("%04d/%02d/%02d-%02d:%02d:%02d",$year,$mon,$day,$hour,$min,$sec); open(F,">> cgij-log.txt"); print F "$date - $data\n"; close(F);
String欄に適当な文字列を入力したらSendボタンをクリックしてください。CGIに文字列が送信され、その様子がテキストエリアに表示されます。さらにファイルにも送信日時と文字列が保存されるので、確認してみてください。