XML-RPCを使って別プロセスからApricotのキャラクターを喋らせる方法
HTTP経由でXML-RPCを使って、別プロセスからApricotを操作するIPC (Inter Process Communication, プロセス間通信)を行えるようにPythonで「XML-RPC.py」を書いてみました。これは機能拡張なので、「XML-RPC.py」をScriptsフォルダにコピーすれば使用できます。
仕組みとしては、(デフォルトでは)localhostの65535番にHTTPサーバを起動してます。ここにHTTP POSTでXML-RPCで定義されているXMLを送信すると、IronPython経由でApricotのメソッドを呼び出してます。
XML-RPCで実際に送信する型は、第一引数にstructのarrayをとります。structには、Resource、Title、Authorなどのnameが指定でき、Resourceには対象のURIを設定したstring型、Titleにはタイトルを設定したstring型、Authorには著作者を設定したstring型などを指定できます。
ちなみに、http://localhost:65535/にブラウザなどでアクセスするとApricotのバージョン情報が表示されると、HTTPサーバが正常に起動しているのを確認することが出来ます。
XML-RPC 仕様書:
http://lowlife.jp/yasusii/stories/9.html
XML-RPCの呼び出しを行うサンプル(クライアント側):
WebClient client = new WebClient(); client.Headers.Add(HttpRequestHeader.ContentType, "text/xml"); Console.WriteLine(client.UploadString(new Uri("http://localhost:65535/"), xml));
XML-RPCのサンプル:
<methodCall> <methodName>Script.Alert</methodName> <params> <param> <value> <array> <data> <value> <struct> <member> <name>Resource</name> <value> <string>http://d.hatena.ne.jp/kawatan/</string> </value> </member> <member> <name>Title</name> <value> <string>かわたんのにっき</string> </value> </member> <member> <name>Author</name> <value> <string>kawatan</string> </value> </member> </struct> </value> </data> </array> </value> </param> </params> </methodCall>
XML-RPC.py:
# -*- encoding: utf-8 -*- # XML-RPC.py import clr clr.AddReferenceByPartialName("System.Xml") clr.AddReferenceByPartialName("Apricot") from System import Byte, String, Uri, IAsyncResult from System.IO import Stream, StreamReader, StreamWriter, MemoryStream, SeekOrigin from System.Collections.Generic import List from System.Net import HttpListener, HttpListenerContext, HttpListenerRequest from System.Reflection import Assembly from System.Text.RegularExpressions import Regex, RegexOptions, Match from System.Xml import XmlDocument from Apricot import Entry prefix = "http://localhost:65535/" def post(s): try: ms = MemoryStream() entryList = List[Entry]() doc = XmlDocument() doc.Load(s) methodNameXmlNode = doc.SelectSingleNode("/methodCall/methodName") if methodNameXmlNode != None: if methodNameXmlNode.InnerText.Equals("Script.Alert"): for paramXmlNode in doc.SelectNodes("/methodCall/params/param"): for paramValueXmlNode in paramXmlNode.ChildNodes: if paramValueXmlNode.Name.Equals("value"): for arrayXmlNode in paramValueXmlNode.ChildNodes: if arrayXmlNode.Name.Equals("array"): for dataXmlNode in arrayXmlNode.ChildNodes: if dataXmlNode.Name.Equals("data"): for dataValueXmlNode in dataXmlNode.ChildNodes: if dataValueXmlNode.Name.Equals("value"): for structXmlNode in dataValueXmlNode.ChildNodes: if structXmlNode.Name.Equals("struct"): entry = Entry() for memberXmlNode in structXmlNode.ChildNodes: if memberXmlNode.Name.Equals("member"): name = String.Empty for xmlNode in memberXmlNode.ChildNodes: if xmlNode.Name.Equals("name"): name = xmlNode.InnerText if not String.IsNullOrEmpty(name): for xmlNode in memberXmlNode.ChildNodes: if xmlNode.Name.Equals("value"): if name.Equals("Resource"): for stringXmlNode in xmlNode.ChildNodes: if stringXmlNode.Name.Equals("string"): entry.Resource = Uri(stringXmlNode.InnerText) elif name.Equals("Title"): for stringXmlNode in xmlNode.ChildNodes: if stringXmlNode.Name.Equals("string"): entry.Title = stringXmlNode.InnerText elif name.Equals("Author"): for stringXmlNode in xmlNode.ChildNodes: if stringXmlNode.Name.Equals("string"): entry.Author = stringXmlNode.InnerText elif name.Equals("Description"): for stringXmlNode in xmlNode.ChildNodes: if stringXmlNode.Name.Equals("string"): entry.Description = stringXmlNode.InnerText elif name.Equals("ImageUri"): for stringXmlNode in xmlNode.ChildNodes: if stringXmlNode.Name.Equals("string"): entry.ImageUri = Uri(stringXmlNode.InnerText) elif name.Equals("Created"): for dateTimeXmlNode in xmlNode.ChildNodes: if dateTimeXmlNode.Name.Equals("dateTime.iso8601"): entry.Created = DateTime.Parse(dateTimeXmlNode.InnerText) elif name.Equals("Modified"): for dateTimeXmlNode in xmlNode.ChildNodes: if dateTimeXmlNode.Name.Equals("dateTime.iso8601"): entry.Modified = DateTime.Parse(dateTimeXmlNode.InnerText) if entry.Resource != None and String.IsNullOrEmpty(entry.Title) == False: if entry.Modified.Ticks == 0: entry.Modified = entry.Created if entry.Created > entry.Modified: entry.Modified = entry.Created entryList.Add(entry) if entryList.Count > 0: Script.Alert(entryList) isActivated = False for entry in entryList: wordList = List[String]() for word in Script.Words: if Regex.IsMatch(entry.Title, Regex.Escape(word.Name), RegexOptions.IgnoreCase) and wordList.Contains(word.Name) == False: wordList.Add(word.Name) if wordList.Count > 0 and isActivated == False: isActivated = Script.TryEnqueue(Script.Prepare(entry, wordList, None, True)) methodResponseXmlNode = doc.CreateElement("methodResponse") doc.ReplaceChild(methodResponseXmlNode, doc.DocumentElement) doc.Save(ms) ms.Seek(0, SeekOrigin.Begin) except: return None return ms def onGetContext(result): try: if result.AsyncState.IsListening: context = result.AsyncState.EndGetContext(result) httpListener.BeginGetContext(onGetContext, httpListener) request = context.Request if request.HttpMethod.Equals("POST"): if request.HasEntityBody and request.ContentType.Equals("text/xml"): s = request.InputStream sr = StreamReader(s, request.ContentEncoding) ms = post(sr) if ms != None: context.Response.ContentType = request.ContentType output = context.Response.OutputStream; buffer = ms.ToArray() output.Write(buffer, 0, buffer.Length); output.Close() ms.Close() sr.Close() s.Close() elif request.HttpMethod.Equals("GET"): if request.RawUrl.Equals("/"): output = context.Response.OutputStream; sw = StreamWriter(output) sw.Write(Assembly.GetEntryAssembly().GetName().ToString()); sw.Flush() sw.Close() output.Close() context.Response.Close() except: return def onStart(s, e): global httpListener if not httpListener.IsListening: httpListener.Prefixes.Add(prefix) httpListener.Start() httpListener.BeginGetContext(onGetContext, httpListener) def onStop(s, e): global httpListener if httpListener.IsListening: httpListener.Stop() if String.IsNullOrEmpty(prefix) == False and HttpListener.IsSupported == True: httpListener = HttpListener() Script.Start += onStart Script.Stop += onStop