Help with remote xmlrpc server

734 views
Skip to first unread message

Mark Nelson

unread,
May 16, 2013, 5:57:13 AM5/16/13
to robotframe...@googlegroups.com
Hi All,

I am very new to Robot Framework, so please excuse if this is a stupid question, but I have searched all over the internet and cannot find an answer.

I am trying to write a remote server using Apache xmlrpc.  I have a simple test case right now to see how it works.

Here is my remote server class:

package com.mark;

public class Robot {
 
  public String[] get_keyword_names() {
    return new String[] { "ping" };
  }

  public String ping(String input) {
    return "pong " + input;
  }

}

And I have an XmlRpcServer.properties which says:

Robot=com.mark.Robot

I have this deployed on an app server, and it is at http://server:port/myRobot/xmlrpc

I wrote a python client to test it and that works fine:

import xmlrpclib

# Create an object to represent our server.
server_url = 'http://server:port/myRobot/xmlrpc';
server = xmlrpclib.Server(server_url);

# Call the server and get our result.
result = server.Robot.get_keyword_names()
print result

# test ping
result = server.Robot.ping('frank')
print result

It outputs:

['ping']
pong frank

So it seems the server is working from a purely xmlrpc point of view.

Now I have a test like this:

*** Settings ***
Library  Remote  http://server:port/myRobot/xmlrpc  WITH NAME  Robot

*** Test Cases ***

Ping Test
   ${pong} =                 Robot.ping     bob
   Strings Should Be Equal   ${pong}        pong bob

But this is giving me an error that it can't find get_keyword_names:

==============================================================================
Acceptance                                                                   
==============================================================================

[ ERROR ] Unexpected error: Connecting remote server at http://server:port/myRobot/xmlrpc failed: <Fault 0: 'No such handler: get_keyword_names'>
Traceback (most recent call last):
  File "/home/hudson/.m2/repository/org/robotframework/robotframework/2.7.7/robotframework-2.7.7.jar/Lib/robot/libraries/Remote$py.class", line 43, in get_keyword_names


I have tried various alternatives:
- removing the WITH NAME Robot
- putting /Robot on the end of the URL
- using ping instead of Robot.ping

But I just cant seem to get it to work, and I can't really find an example that shows both sides - they all show only the server or the client/test, but don't put it all together.

Would really appreciate any help or advice.

Thanks in advance,
Mark Nelson




Kevin O.

unread,
May 16, 2013, 8:33:18 AM5/16/13
to robotframe...@googlegroups.com
The problem is that the remote library API protocol requires the handlers to have no prefix and you are using prefix "Robot". IOTW Remote library is trying to invoke get_keyword_names and you have Robot.get_keyword_names.
Out of the box, Apache XMLRPC server does not have a way to add a handler without a prefix (also known as a default handler).
I solved this by extending AbstractReflectiveHandlerMapping and providing a method to remove all the prefixes (e.g. move from /foo.runKeyword to /runKeyword) when designing jrobotremoteserver.
In RemoteApplications, this was solved by implementing a full custom XmlRpcHandler.
Another solution would be to use existing code.
jrobotremoteserver is servlet based and you should be able to add it to your app/web server using org.robotframework.remoteserver.servlet.RemoteServerServlet.

So your test code should like like this:
result = server.get_keyword_names() 
instead of:
result = server.Robot.get_keyword_names() 

Kevin

David

unread,
May 16, 2013, 10:21:13 PM5/16/13
to robotframe...@googlegroups.com
That's insightful info Kevin, thanks. I never had to use (and thus not really tested) remote libraries with aliasing/prefixing library keywords. So my Perl and .NET/C# remote servers may have this problem as well, I'll have to look into them sometime.

Mark Nelson

unread,
May 16, 2013, 10:52:10 PM5/16/13
to robotframe...@googlegroups.com
Thanks a lot for your help Kevin.  I got my library working.

In case anyone else has a similar interest, here is what I did:

my test:


*** Settings ***
Library  Remote  http://server:port/myRobot/xmlrpc 

*** Test Cases ***

Ping Test
   ${pong} =                    ping           bob
   Should Be Equal as Strings   ${pong}        pong bob

The web.xml for the web app:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" >

  <session-config>
    <session-timeout>30</session-timeout>
  </session-config>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>
  <servlet>
    <servlet-name>XmlRpcServlet</servlet-name>
    <servlet-class>com.mark.MyXmlRpcServlet</servlet-class>
    <init-param>
      <param-name>enabledForExtensions</param-name>
      <param-value>true</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>XmlRpcServlet</servlet-name>
    <url-pattern>/xmlrpc</url-pattern>
  </servlet-mapping>
</web-app>

My remote library class:

package com.mark;

import java.util.Map;
import java.util.HashMap;

public class Robot {
 
  public String ping(String input) {
    System.out.println("[MARK] Robot.ping()");
    return "pong " + input;
  }

  //
  // methods required by the robotframework remote library protocol follow
  //
  public String[] get_keyword_names() {
    //System.out.println("[MARK] Robot.get_keyword_names()");
    return new String[] { "ping" };
  }

  public Map<String, Object> run_keyword(String keyword, Object[] args) {
    //System.out.println("[MARK] Robot.run_keyword()");
    HashMap<String, Object> kr = new HashMap<String, Object>();
    try {
      kr.put("status", "PASS");
      kr.put("error", "");
      kr.put("traceback", "");
      Object retObj = "";
      // run the right method
      if (keyword.equalsIgnoreCase("ping")) {
        retObj = ping((String)args[0]);
      } else {
        kr.put("status", "FAIL");
        kr.put("return", "");
        kr.put("error", "");
        kr.put("traceback", "");
      }
      kr.put("return", retObj);
    } catch (Throwable e) {
      e.printStackTrace(System.out);
    }
    return kr;
  }

}


my custom handler:

/*
 * Adapted from https://github.com/ombre42/jrobotremoteserver/blob/master/src/main/java/org/robotframework/remoteserver/xmlrpc/ReflectiveHandlerMapping.java
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.mark;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.server.AbstractReflectiveHandlerMapping;

public class HandlerMapping extends AbstractReflectiveHandlerMapping {

    /**
     * Removes the prefixes from all keys in this handler mapping assuming a
     * String was used as the key and period was
     * used as a separator. Example: Robot.getInvoice -> getInvoice
     */
    @SuppressWarnings("unchecked")
    public void removePrefixes() {
      //System.out.println("[MARK] entering HandlerMapping.removePrefixes()");
      Map<String, Object> newHandlerMap = new HashMap<String, Object>();
      for (Entry<String, Object> entry : (Set<Entry<String, Object>>) this.handlerMap.entrySet()) {
        String newKey = (String) entry.getKey();
        if (entry.getKey() instanceof String) {
          String key = (String) entry.getKey();
          if (key.contains(".")) {
            newKey = key.substring(key.lastIndexOf(".") + 1);
          }
        }
        newHandlerMap.put(newKey, entry.getValue());
      }
      this.handlerMap = newHandlerMap;
    }

my custom servlet:

    /**
     * Adds handlers for the given object to the mapping. The handlers are build by invoking
     * {@link #registerPublicMethods(String, Class)}.
     *
     * @param pKey
     *            The class key, which is passed to {@link #registerPublicMethods(String, Class)}.
     * @param pClass
     *            Class, which is responsible for handling the request.
     */
    public void addHandler(String pKey, Class<?> pClass) throws XmlRpcException {
      //System.out.println("[MARK] entering HandlerMapping.addHandler()");
      registerPublicMethods(pKey, pClass);
    }
}

package com.mark;

import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.server.XmlRpcHandlerMapping;
import org.apache.xmlrpc.webserver.XmlRpcServlet;

public class MyXmlRpcServlet extends XmlRpcServlet  {

  @Override
  protected XmlRpcHandlerMapping newXmlRpcHandlerMapping() throws XmlRpcException {
    //System.out.println("[MARK] entering MyXmlRpcServlet.newXmlRpcHandlerMapping()");
    HandlerMapping mapping = new HandlerMapping();
    mapping.addHandler(com.mark.Robot.class.getName(), com.mark.Robot.class);
    mapping.removePrefixes();
    return mapping;
  }

}


Out of interest, I did not want to run the server and fire up a new listener/thread on another port.  I wanted to have the servlet run just in the normal container.  This way, i can take advantage of the container/app server's tuning, thread and workload management, which I need to do in this particular exercise.

Thanks again for your help.

Best Regards,

Mark Nelson

David

unread,
May 17, 2013, 3:30:52 AM5/17/13
to robotframe...@googlegroups.com
Also, just curious if this handler prefix issue applied to the Python and Ruby remote servers and if so, how it was addressed for them.


On Thursday, May 16, 2013 5:33:18 AM UTC-7, Kevin O. wrote:

Kevin O.

unread,
May 17, 2013, 12:03:10 PM5/17/13
to robotframe...@googlegroups.com
Glad to hear you got it working and thanks for sharing.
When I enhanced the Java server to support multiple libraries, I did not realize you could use a URL with a path, so I used different ports instead.
This was pretty dumb and big mistakes can happen when you are the solo developer.
In the next release I will remove all the multiple port functionality and switch to multiple paths.
I have also put in an enhancement to make the servlet more portable for situations like yours.

@David
Neither the C# or Perl servers appear to have this issue.
Maybe you are thinking of the fact that the Perl server binds to path /RPC2 instead of /.
That is a different issue.
The method prefix issue has to do with how the contents of the methodCall/methodName element inside the request body/XML are mapped to a method.
As usual the Java folks made it complicated and the Python/Ruby folks made something that "just works" :P
Even the Redstone server required prefixes until recent versions.
Reply all
Reply to author
Forward
0 new messages