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