Closed UyttenhoveSimon closed 3 years ago
Sorry but I don't have any server example, only client-side.
@LordVeovis , thanks for your reply. I ended up mixing one code I saw online: https://github.com/daluu/sharprobotremoteserver/blob/master/robotremoteserver.cs And your code. You can reuse it in your example if you want.
using Horizon.XmlRpc.AspNetCore;
using Horizon.XmlRpc.Core;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Xml.XPath;
namespace RobotListenerCore3
{
public interface IAddService
{
[XmlRpcMethod()]
int AddNumbers(int numberA, int numberB);
[XmlRpcMethod]
string[] get_keyword_names();
[XmlRpcMethod]
XmlRpcStruct run_keyword(string keyword, object[] args);
[XmlRpcMethod]
string[] get_keyword_arguments(string keyword);
[XmlRpcMethod]
string get_keyword_documentation(string keyword);
}
public class RobotListener : XmlRpcService, IAddService
{
public static bool enableStopServer = true;
private Assembly library;
private string libraryClass;
private XPathDocument doc;
//I/O management components
private TextWriter libout;
private TextWriter liberrs;
//.NET reflection components to handle the .NET library being served
private Type classType;
private object libObj;
private List<string> listToAvoid = new List<string>() { "GetType", "InitializeLifetimeService", "GetLifetimeService", "System__Method__Help___", "System__Method__Signature___", "System__List__Methods___", "GetHashCode", "Equals", "ToString", "HandleHttpRequest", "HandleHttpRequestAsync", "Invoke" };
public RobotListener()
{
//library = Assembly.GetAssembly(this.GetType());
classType = this.GetType();
libout = new StringWriter();
liberrs = new StringWriter();
}
public int AddNumbers(int numberA, int numberB)
{
return numberA + numberB;
}
public string[] get_keyword_arguments(string keyword)
{
if (keyword == "stop_remote_server") return new String[0];
return classType.GetMethod(keyword).GetParameters().Select(parameter => parameter.Name).ToArray();
}
public string get_keyword_documentation(string keyword)
{
string retval = ""; //start off with no documentation, in case keyword is not documented
if (keyword == "stop_remote_server")
{
retval = "Remotely shut down remote server/library w/ Robot Framework keyword.\n\n";
retval += "If server is configured to not allow remote shutdown, keyword 'request' is ignored by server.\n\n";
retval += "Always returns status of PASS with return value of 1. Output value contains helpful info and may indicate whether remote shut down is allowed or not.";
return retval;
}
if (doc == null)
{
return retval; //no XML documentation provided, return blank doc
}//else return keyword (class method) documentation from XML file
XPathNavigator docFinder;
XPathNodeIterator docCol;
try
{
docFinder = doc.CreateNavigator();
}
catch
{
docFinder = null; //failed to load XML documentation file, set null
}
string branch = "/doc/members/member[starts-with(@name,'M:" + libraryClass + "." + keyword + "')]/summary";
try
{
retval = docFinder.SelectSingleNode(branch).Value + System.Environment.NewLine + System.Environment.NewLine;
}
catch
{
//no summary info provided for .NET class method
}
try
{
branch = "/doc/members/member[starts-with(@name,'M:" + libraryClass + "." + keyword + "')]/param";
docCol = docFinder.Select(branch);
while (docCol.MoveNext())
{
retval = retval + docCol.Current.GetAttribute("name", "") + ": " + docCol.Current.Value + System.Environment.NewLine;
};
retval = retval + System.Environment.NewLine;
}
catch
{
//no parameter info provided or some parameter info missing for .NET class method
}
try
{
branch = "/doc/members/member[starts-with(@name,'M:" + libraryClass + "." + keyword + "')]/returns";
retval = retval + "Returns: " + docFinder.SelectSingleNode(branch).Value;
}
catch
{
//.NET class method either does not return a value (e.g. void) or documentation not provided
}
return retval; //return whatever documentation was found for the keyword
}
public string[] get_keyword_names()
{
//MethodInfo[] mis = classType.GetMethods(BindingFlags.Public | BindingFlags.Static);
//seem to have issue when trying to only get public & static methods, so get all instead
var methods = classType.GetMethods();
methods = methods.Where(method => !listToAvoid.Contains(method.Name)).ToArray();
//add one more for stop server that's part of the server
var keyword_names = methods.Select(method => method.Name).ToList();
keyword_names.Add("stop_remote_server");
return keyword_names.ToArray();
}
public XmlRpcStruct run_keyword(string keyword, object[] args)
{
XmlRpcStruct kr = new XmlRpcStruct();
if (keyword == "stop_remote_server")
{
if (RobotListener.enableStopServer)
{
//reset output back to stdout
StreamWriter stdout = new StreamWriter(Console.OpenStandardOutput());
stdout.AutoFlush = true;
Console.SetOut(stdout);
//spawn new thread to do a delayed server shutdown
//and return XML-RPC response before delay is over
new Thread(stop_remote_server).Start();
Console.WriteLine("Shutting down remote server/library in 5 seconds, from Robot Framework remote");
Console.WriteLine("library/XML-RPC request.");
Console.WriteLine("");
kr.Add("output", "NOTE: remote server shutting/shut down.");
}
else
{
kr.Add("output", "NOTE: remote server not configured to allow remote shutdowns. Your request has been ignored.");
//in case RF spec changes to report failure in this case in future
//kr.Add("status","FAIL");
//kr.Add("error","NOTE: remote server not configured to allow remote shutdowns. Your request has been ignored.");
}
kr.Add("return", 1);
kr.Add("status", "PASS");
kr.Add("error", "");
kr.Add("traceback", "");
return kr;
}
//redirect output from test library to send back to Robot Framework
Console.SetOut(libout); //comment out when debugging
Console.SetError(liberrs);
MethodInfo mi = classType.GetMethod(keyword);
try
{
/* we let XML-RPC.NET library handle the data type conversion
* hopefully, the test library returns one of the supported XML-RPC data types:
* http://xml-rpc.net/faq/xmlrpcnetfaq-2-5-0.html#1.9
* http://xml-rpc.net/faq/xmlrpcnetfaq-2-5-0.html#1.12
* Otherwise, an error may occur.
*
* FYI, and this is the spec for Robot Framework remote library keyword argument and return type
* http://robotframework.googlecode.com/svn/tags/robotframework-2.5.6/doc/userguide/RobotFrameworkUserGuide.html#supported-argument-and-return-value-types
* on how data types should map, particularly for the non-native-supported types.
* Hopefully XML-RPC.NET converts them closely to those types, otherwise, you will have to make some
* changes in the remote server code here to adjust return type according to Robot Framework spec.
* Or change the test library being served by the remote server to use simpler data structures (e.g. primitives)
*/
libObj = Activator.CreateInstance(classType);
if (mi.ReturnType == typeof(void))
{
mi.Invoke(libObj, args);
kr.Add("return", "");
}
else
{
kr.Add("return", mi.Invoke(libObj, args));
}
kr.Add("status", "PASS");
kr.Add("output", libout.ToString());
libout.Flush();
kr.Add("error", liberrs.ToString());
liberrs.Flush();
kr.Add("traceback", "");
return kr;
}
catch (TargetInvocationException iex)
{
//exception message is probably more useful at this point than standard error?
liberrs.Flush();
kr.Add("traceback", iex.InnerException.StackTrace);
kr.Add("error", iex.InnerException.Message);
kr.Add("output", libout.ToString());
libout.Flush();
kr.Add("status", "FAIL");
kr.Add("return", "");
return kr;
}
catch (System.Exception ex)
{
//to catch all other exceptions that are not nested or from target invocation (i.e. reflection)
//exception message is probably more useful at this point than standard error?
liberrs.Flush();
kr.Add("traceback", ex.StackTrace);
kr.Add("error", ex.Message);
kr.Add("output", libout.ToString());
libout.Flush();
kr.Add("status", "FAIL");
kr.Add("return", "");
return kr;
}
}
private static void stop_remote_server()
{
//delay shutdown for some time so can return XML-RPC response
int delay = 5000; //let's arbitrarily set delay at 5 seconds
Thread.Sleep(delay);
Console.WriteLine("Remote server/library shut down at {0}", System.DateTime.Now.ToString());
System.Environment.Exit(0);
}
}
}
You launch it from a ASP Net Core server. Startup.cs looks like this:
using Horizon.XmlRpc.AspNetCore.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
namespace RobotListenerCore3
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddXmlRpc();
services.Configure<KestrelServerOptions>(options =>
{
options.AllowSynchronousIO = true;
options.ListenAnyIP(5678);
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseXmlRpc(config => config.MapService<RobotListener>("/RPC2"));
}
}
}
Hello,
Context: I am trying to reuse some c# code and interact with robot-framework via xml-rpc.
I was wondering if there was one example with a xml-rpc server? All http:/ references are within !FX1_0 regions or removed within .csproj files.
What am I missing ?