Category:LSL XML-RPC/ja

From Second Life Wiki
Jump to: navigation, search

XML-RPC は外部システムへのプロシージャコール (機能呼び出し) 送信に使われるひとつの標準です。そこでは、外部システムが扱う XML データが HTTP でやり取りされます。

LSL は XML-RPC 要求を受け取ると、それを指定のプリムへ渡します。プリムはコネクションを確立しないかもしれませんが、応答は可能であり、サーバとの双方向通信を維持します。これらの応答は (EmailHTTP通信 要求に比べると) Second Life 外との大規模なデータのやりとりを可能にしてくれるようです。

注意:

KBcaution.png 重要 XML-RPC 要求は、フロントエンド サーバが過負荷となるため、しばしばタイムアウトします。LL (Linden Lab) は定期的にサーバのハードウェアを更新し続けていますが、未だに信頼性には欠けます。(サーバが 1 つだけというボトルネックゆえに) XML-RPC の設計に拡張性が無く、そのサービスは "推奨されない" と LL の開発者は勧告してきました。彼らは HTTP ポーリング を代替とするよう提案しています。XML-RPC 要求がタイムアウトした場合、そのスクリプトの remote_data イベントは発生するかもしれませんし、発生しないかもしれません。(後者の場合、スクリプトは無反応になります。)

実装に関する重要な注意:

現在の XML-RPC の実装では、フロントエンド サーバ (xmlrpc.secondlife.com) で同時にキューに入ることができる要求はただ 1 個だけです。同じデータ チャネルに新しい要求があった場合、それは既存のものを上書きします。これは XML-RPC 通信の設計にとって深刻な問題です。なぜなら XML-RPC 通信では in-world のオブジェクトが応答可能になる前に要求を受け取る可能性があるからです。加えて、llRemoteDataReply 関数の 3 秒間の遅延が問題をより深刻にします。

分かっている問題は以下のとおりです: XML-RPC を介して in-world のオブジェクト (何らかの処理を行なって llRemoteDataReply 関数で応答を返すスクリプトが入っている) に複数の要求を立て続けに送ると、先に送られた要求ほど (remote_data イベントを発生させなければならないのに) フロントエンド サーバで取りこぼされる可能性があります。その結果、先の要求に対して、後の要求に関する応答を返してしまいます。本来ならば先の要求に関して、外部アプリケーションに対しタイムアウトが返るところです。

結局のところ、XML-RPC を使って何らかの重要な処理を行なおうとするならば、各 RPC チャネルに対する全ての要求に連番をつけて管理するよう、外部のクライアント アプリケーションを設計しなければならないでしょう。すなわちこれは、次の要求を送る前に、前の要求に関する応答が帰ってくるのを待たなけれならない事を意味します。応答の受け取りを気にしないで済むならば、これは問題ではありません。なぜならキューの問題がどうであろうと、全ての要求はスクリプトへ渡されたように見えるからです。

なお llRemoteDataReply 関数の 3 秒間の遅延から逃れる方法はありません。例えば multiple-slave-comm-script といった、 llMessageLinked 関数を使い複数のスクリプトに同じ発生源からの返答を並行で処理させる手法は使えません。XML-RPC チャネルはスクリプト固有のものであり、オブジェクト固有のものではないからです。

これ以上の情報については公式フォーラムのこのスレッドこのスレッドを参照してください。

参考資料

php

外部サーバとして XML-RPC を初期化するには、ある種の Web アプリケーションが必要です。 Web アプリケーションを作成する言語の一つが PHP です。 PHP を使い Web サーバから SL 内のスクリプトへ XML-RPC メッセージをどのように送信するかの例を以下に示します:

<?php
	echo '<pre>';
	$channel = ""; //Fill in the channel you are using (key)
	$intvalue = ""; //Fill in the intvalue you are using (integer)
	$strvalue = ""; //Fill in the strvalue you are using (string)
	$xmldata = "<?xml version=\"1.0\"?><methodCall><methodName>llRemoteData</methodName>
<params><param><value><struct>
<member><name>Channel</name><value><string>".$channel."</string></value></member>
<member><name>IntValue</name><value><int>".$intvalue."</int></value></member>
<member><name>StringValue</name><value><string>".$strvalue."</string></value></member>
</struct></value></param></params></methodCall>";
	echo sendToHost("xmlrpc.secondlife.com", "POST", "/cgi-bin/xmlrpc.cgi", $xmldata);
	echo '</pre>';
 
	function sendToHost($host,$method,$path,$data,$useragent=0)
	{ 
		$buf="";
		// Supply a default method of GET if the one passed was empty 
		if (empty($method)) 
			$method = 'GET'; 
		$method = strtoupper($method); 
 
		$fp = fsockopen($host, 80, $errno, $errstr, 30);
 
		if( !$fp )
		{
			$buf = "$errstr ($errno)<br />\n";
		}else
		{
			if ($method == 'GET') 
			$path .= '?' . $data; 
			fputs($fp, "$method $path HTTP/1.1\r\n"); 
			fputs($fp, "Host: $host\r\n"); 
			fputs($fp, "Content-type: text/xml\r\n"); 
			fputs($fp, "Content-length: " . strlen($data) . "\r\n"); 
			if ($useragent) 
				fputs($fp, "User-Agent: MSIE\r\n"); 
			fputs($fp, "Connection: close\r\n\r\n"); 
			if ($method == 'POST') 
				fputs($fp, $data); 
			while (!feof($fp)) 
				$buf .= fgets($fp,128); 
			fclose($fp); 
		}
		return $buf; 
	} 
?>

perl

Second Life 内のオブジェクトにアクセスするための perl コード、及びそれに対応する LSL のコードは以下のとおりです。perl コードは RPC::XML モジュールを使います。この perl コードはコマンドラインで実行できます。それは RPC 関連の perl オブジェクトを作成し、グリッド内に rez された SL オブジェクトへメッセージを送信します。perl コードに、SL オブジェクトの UUID を与えなければならない点に注意してください。

この perl コードは "Message to pass" という文字列と "2007" という数字を含んだメッセージを送信します。LSL コードは "I got it" という文字列と "2008" という数字を応答として返します。

#!/usr/bin/perl
# 
 
use RPC::XML;
use RPC::XML::Parser;
use RPC::XML::Client;
 
my $llURL = "http://xmlrpc.secondlife.com/cgi-bin/xmlrpc.cgi";
$P = RPC::XML::Parser->new();
 
my $cli = RPC::XML::Client->new($llURL);
my $req = RPC::XML::request->new(
        "llRemoteData",
        {'Channel' => RPC::XML::string->new("UUID for the open channel from the object GOES HERE!"),
        'IntValue' => RPC::XML::int->new(2007),
        'StringValue' => RPC::XML::string->new("message to pass")});
 
#  Print out the message to send
print "ref(req): ",ref($req),"\n";
$xml = $req->as_string();
print "Length: ",$req->length,"\n\n",$xml,"\n\n";
 
$res = $P->parse($xml);
print ref($res),"\n";
if (ref($res)) {
    %h = %{$res->args->[0]->value};
    foreach $lupe (keys %h) {
       print "$lupe: $h{$lupe}\n";
    }
}
 
#  Submit the request to send the information above.
my $resp = $cli->send_request($req);
 
 
#  Print out the response
print "\n\n\nResponse\n";
print "ref(resp): ",ref($resp),"\n";
$xml = $resp->as_string();
print "Length: ",$resp->length,"\n\n",$xml,"\n\n";
 
$res = $P->parse($xml);
print ref($res),"\n";
if (ref($res)) {
    %h = %{$res->value};
    foreach $lupe (keys %h) {
       $val =  $h{$lupe};
       print "$lupe: $val\n";
    }
}


Java

この Java コードは the org.apache.xmlrpc ライブラリを使っています。これに関する jar ファイルは http://www.perisic.com/xmlrpc/cis69mc.jar にあります。(これを使って以下のコードはテストされました。) 必要ならば Java と XMLRPC に関する簡単なイントロダクションを http://perisic.com/xmlrpc で参照してください。

  • SLClient.java という名前のファイルにあるコードを保存し、上記の URL から cis69mc.jar という jar ファイルをダウンロードしてください。
  • '<add channel id here!!!>' とある場所にチャネル ID を追加してください。結果的にその行は theData.put("Channel", "24a2c834-c984-9209-78df-20608d4c8ade"); といった具合になります。
  • 次のコマンドでコードをコンパイルします: javac -classpath "cis69mc.jar;." SLClient.jar (未チェックあるいは安全でない操作という警告が出るかもしれませんが無視してください。)
  • 次のコマンドでコードを実行してください: java -classpath "cis69mc.jar;." SLClient
import java.util.*;
import org.apache.xmlrpc.*;
 
public class SLClient {
 public static void main (String [] args) {
  try {
   Hashtable theData = new Hashtable(); 
   XmlRpcClient server = new XmlRpcClient("http://xmlrpc.secondlife.com/cgi-bin/xmlrpc.cgi"); //
   theData.put("Channel", "<add channel id here!!!>"); 
   theData.put("IntValue", 2483); 
   theData.put("StringValue", "The date is: "+ (new Date()).toString() ); 
   Vector params = new Vector(); 
   params.add(theData); 
 
   Object result = server.execute("llRemoteData", params ); 
 
  } catch (Exception exception) {
   System.err.println("SL_Client: " + exception);
   exception.printStackTrace(); 
   }
  }
}

Visual Basic 6

この Visual Basic 6 コードは Microsoft WinSock 6.0 コントロールを使います。プロジェクトを新規作成し、フォームに Winsock コントロールを配置してください。

Dim Channel As String
Dim IntValue As Integer
Dim StrValue As String
Dim XMLData As String
 
Private Sub Form_Load()
    Channel = "<add channel id here!!!>"
    IntValue = 2007
    StrValue = "This is a test string."
    XMLData = "<?xml version=""1.0""?><methodCall><methodName>llRemoteData</methodName><params><param><value><struct>"
    XMLData = XMLData & "<member><name>Channel</name><value><string>" & Channel & "</string></value></member>"
    XMLData = XMLData & "<member><name>IntValue</name><value><int>" & CStr(IntValue) & "</int></value></member>"
    XMLData = XMLData & "<member><name>StringValue</name><value><string>" & StrValue & "</string></value></member>"
    XMLData = XMLData & "</struct></value></param></params></methodCall>"
 
    Winsock1.Connect "xmlrpc.secondlife.com", 80
End Sub
 
Private Sub Winsock1_Connect()
    WinsockSend "POST /cgi-bin/xmlrpc.cgi HTTP/1.1" & vbCrLf
    WinsockSend "Host: xmlrpc.secondlife.com" & vbCrLf
    WinsockSend "Content-type: text/xml" & vbCrLf
    WinsockSend "Content-length: " & CStr(Len(XMLData)) & vbCrLf
    WinsockSend "Connection: close" & vbCrLf & vbCrLf
    WinsockSend XMLData & vbCrLf
End Sub
 
Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
    Dim TempStr As String
    Winsock1.GetData TempStr
    MsgBox TempStr
End Sub
 
Sub WinsockSend(str As String)
    Winsock1.SendData str
End Sub

Command line curl

コマンドラインで XML-RPC をデバッグしたいならば、"curl" が使えます。rpc.xml という名前のファイルに以下の内容を保存してください。その際、UUID をスクリプトのチャネル ID に置き換えてください:

<?xml version="1.0"?>
<methodCall>
    <methodName>llRemoteData</methodName>
    <params>
        <param>
            <value>
                <struct>
                    <member>
                        <name>Channel</name>
                        <value><string>1ee246fc-e60d-2fd3-6242-f5fc5d19850f</string></value>
                    </member>
                    <member>
                        <name>IntValue</name>
                        <value><int>42</int></value>
                    </member>
                    <member>
                        <name>StringValue</name>
                        <value><string>Hello, world!</string></value>
                    </member>
                </struct>
            </value>
        </param>
    </params>
</methodCall>

その上で、curl を以下のように実行し、XMLRPC サーバに POST します。

curl --verbose --data "@rpc.xml" http://xmlrpc.secondlife.com/cgi-bin/xmlrpc.cgi

LSL Server Code

(上記の例に) 対応する LSL コードは以下の通りです。(オブジェクト内のスクリプトにコピー&ペーストし、保存し、rez してください。チャネル ID およびその他の情報がメッセージとして表示されます。) :

key remoteChannel;
init() {
    llOpenRemoteDataChannel(); // create an XML-RPC channel
    llOwnerSay("My key is " + (string)llGetKey());
}
 
default {
    state_entry() {
        init();
    }
 
    state_exit() {
        return;
    }
 
    on_rez(integer param) {
        llResetScript();        
    }
 
    remote_data(integer type, key channel, key message_id, string sender, integer ival, string sval) {
         if (type == REMOTE_DATA_CHANNEL) { // channel created
             llSay(DEBUG_CHANNEL,"Channel opened for REMOTE_DATA_CHANNEL" + 
                (string)channel + " " + (string)message_id + " " + (string)sender + " " +                         
                (string)ival + " " + (string)sval);
             remoteChannel = channel;
             llOwnerSay("Ready to receive requests on channel \"" + (string)channel + "\"");                        
             state receiving; // start handling requests
         } else {
             llSay(DEBUG_CHANNEL,"Unexpected event type"); 
         }                      
     }                 
}                     
 
 
state receiving {
 
    state_entry() {
        llOwnerSay("Ready to receive information from outside SL");
    }  
 
    state_exit() {
        llOwnerSay("No longer receiving information from outside SL.");
        llCloseRemoteDataChannel(remoteChannel);
    }
 
    on_rez(integer param) {
        llResetScript();
    }
 
    remote_data(integer type, key channel, key message_id, string sender, integer ival, string sval) {
        if (type == REMOTE_DATA_REQUEST) { // handle requests sent to us
             llSay(DEBUG_CHANNEL,"Request received for REMOTE_DATA_REQUEST " + (string)channel + " " +
                (string)message_id + " " + (string)sender + " " + (string)ival + " " + (string)sval);
            llRemoteDataReply(channel,NULL_KEY,"I got it",2008);
            llOwnerSay("I just received data in "+ llGetRegionName() + 
                        " at position " + (string)llGetPos() + "\n" +
                       "The string was " +  sval + "\nThe number was " + (string)ival + ".");
        }
    }
}