Utilizando XML-RPC com C#

Introdução

O XML-RPC é um protocolo de chamada de procedimento remoto (RPC) que utiliza XML para codificar suas chamadas e HTTP como um mecanismo de transporte.

É um protocolo simples, definido com poucas linhas de códigos em oposição com a maioria dos sistemas de RPC, onde os documentos padrões são freqüentemente com milhares de páginas e exige apoio de softwares para serem usados. Fonte: http://pt.wikipedia.org/wiki/XML-RPC

O XML-RPC é um protocolo criado em 1989 por Dave Winer na UserLand Software. Ele é o protocolo de WebService utilizado por diversos sistemas. Atualmente ele não é o padrão recomendado para implementações de  soluções WebService, sendo o SOAP 1.2 o padrão.

Para maiores informações sobre o SOAP 1.2 recomendo a leitura da sua especificação no W3C disponível em http://www.w3.org/TR/soap/ e do livro Programming Web Services with SOAP de Doug Tidwell, James Snell e Pavel Kulchenko (O’Reilly).

Buscando um pouco na internet encontrei uma biblioteca de XML-RPC para C#, porém achei pouca informação sobre como utilizá-la e explorar todas suas possibilidades. Desta forma resolvi criar este post para quem está iniciando neste mundo de WebService.

Para quem deseja se aprofundar  no protocolo XML-RPC recomento a leitura do livro Programming Web Services with XMLRPC de Simon St. Laurent, Joe Johnston e Edd Dumbill (O’Reilly).

Biblioteca

Como base deste aplicativo foi utilizada a biblioteca XML-RPC.NET criada por Charles Cook.

Recursos

  • Listagem dos métodos disponíveis no WebService;
  • Listagem das estruturas do WebService;
  • Chamada de um método do WebService.

Métodos básicos do XML-RPC

O método system.listMethods geralmente está presente nas implementações de servidor WebService. Este método é utilizado pata listar todos os métodos presentes no WebService.

system.listMethods

Na execução deste método há como retorno uma lista contendo o nome de todos os métodos disponíveis no servidor.

Nome:

system.listMethods.

 Parametros:

Não há parâmetros

 Resultado:

Retorna uma XML-RPC array de Strings que representa o nome dos métodos implementados pelo servidor.

Cada elemento da lista é único, ou seja, não poderá haver duplicidade de nomes.

Não é obrigatório que o servidor retorne todos os métodos implementados pelo servidor. Um exemplo disso é quando deseja-se que um determinado método seja privado.

Exemplo:

Chamada:

<methodCall>
<methodName>system.listMethods</methodName>
<params></params>
</methodCall>

Resposta:

<methodResponse>
<params>
<param>
<value><array><data>
<value>
<string>system.listMethods</string>
</value>
<value>
<string>system.methodSignature</string>
</value>
</data></array></value>
</param>
</params>
</methodResponse>

Implementação

Vamos a implementação da API de consulta.

A Primeira coisa a ser criada é a interface se conexão com o Server.

using System;
using CookComputing.XmlRpc;
namespace WebServiceAPI
{
   public interface IStateName : IXmlRpcProxy
   {
      [XmlRpcMethod("system.listMethods")]
      String[] ListMethods();
   }
}

Com a interface criada agora podemos realizar a consulta, em meu projeto criarei uma classe chamada APIBase com o seguinte código:

using System;
using System.Collections.Generic;
using CookComputing.XmlRpc;
using System.Text;
namespace WebServiceAPI
{
   public class APIBase
   {
      internal IStateName proxy;
      public APIBase(Uri uri) : this(uri, false){}
      public APIBase(Uri uri, Boolean ignoreCertificateErrors)
      {
          if (ignoreCertificateErrors)
               System.Net.ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true);

           proxy = (IStateName)XmlRpcProxyGen.Create(typeof(IStateName));
           proxy.Url = uri.AbsoluteUri;
       }

       public String[] ListMethods()
       {
           return proxy.ListMethods();
       }

   }
}

Como métodos de instanciamento da classe temos 2 formas, a primeira somente com a URL do WebService (aceitando HTTP e HTTPS), e a segunda com a URL e se serão ignorados os erros de certificado HTTPS inválido.

Nesta classe também há o método ListMethods que por sua vez chama o ListMethods da interface com o WebService.

Vamos ao teste. Instancie a classe de API e execute o método ListMethods conforme demonstrado no código abaixo:

APIBase xmlRpcApi = new APIBase(new Uri("http://servidor_wordpress/xmlrpc.php"), true);
String[] WSMethods = xmlRpcApi.ListMethods();
foreach(String methodName in WSMethods)
    Console.WriteLine(methodName);

Ao executar este código deve retornar o nome de todos os métodos.

Agora vamos implementar na classe APIBase umas funções para facilitar o trabalho no conhecimento das propriedades e métodos do WebService.  Ficando o código dessa classe conforme abaixo:

using System;
using System.Collections.Generic;
using CookComputing.XmlRpc;
using System.Text;

namespace WebServiceAPI
{
   public class APIBase
   {
      internal IStateName proxy;
      public APIBase(Uri uri) : this(uri, false){}
      public APIBase(Uri uri, Boolean ignoreCertificateErrors)
      {
         if (ignoreCertificateErrors)
            System.Net.ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true);
         proxy = (IStateName)XmlRpcProxyGen.Create(typeof(IStateName));
         proxy.Url = uri.AbsoluteUri;
      }

      public String[] ListMethods()
      {
         return proxy.ListMethods();
      }

      public static String DumpVariable(Object obj){
         return DumpVariable(obj, "");
      }

      public static String DumpVariable(Object obj, String printPrefix)
      {
         StringBuilder dumpText = new StringBuilder();
         try {
            dumpText.AppendLine(printPrefix + "type ==> " + obj.GetType());
         } catch (Exception) { }
         if (obj is XmlRpcStruct)
         {
             foreach(String key in ((XmlRpcStruct)obj).Keys){
                 dumpText.AppendLine(printPrefix + key);
                 dumpText.AppendLine(DumpVariable(((XmlRpcStruct)obj)[key], printPrefix + "\t"));
             }
         }
         else if (obj is XmlRpcStruct[])
         {
             foreach(XmlRpcStruct r1 in ((XmlRpcStruct[])obj)){
                 dumpText.AppendLine(DumpVariable(r1, printPrefix + "\t"));
             }
         }
         else if (obj is Object[])
         {
             foreach(Object t in ((Object[])obj)){
                 dumpText.AppendLine(DumpVariable(t, printPrefix + "\t   "));
             }
         }
         else if (obj is String)
         {
             if (obj != null)
                dumpText.AppendLine(printPrefix + "\t   " +obj.ToString());
         }
         else if (obj is String[])
         {
             foreach(Object t in ((String[])obj)){
                 if (t != null)
                    dumpText.AppendLine(printPrefix + "\t   " +t.ToString());
             }
         }
         else
         {
             if (obj != null)
                dumpText.AppendLine(printPrefix + "\t   " + obj.ToString());
         }
         return dumpText.ToString();
      }
   }
}

Basicamente foram criados 2 métodos nomeados DumpVariable que realizam o parse dos dados retornados pelo WebService.

Para dar os próximos passos é necessário a documentação do WebService, no tocante a parâmetros de entrada e saída dos métodos. Alguns aplicativos servidores de WebService implementam métodos de consulta da estrutura como é o caso do Webservice da Barracuda (colocarei um código de exemplo para download).

Para o webservice escolhido (WordPress) há uma página de referência dos métodos em :

http://codex.wordpress.org/XML-RPC_wp

Para que se possa utilizar o WebService do wordpress é preciso habilitar em Configurações > Escrita > Ativar os protocolos de publicação XML-RPC do WordPress, Movable Type, MetaWeblog e Blogger.

Para exemplificação escolheremos dois métodos o wp.getUsersBlogs e wp.getPageList. Segue a descrição destes:

wp.getUsersBlogs

Retorna os blogs dos usuários.

Parâmetros:

String username
String password

Retorno:

Array
   Struct
     Boolean isAdmin
     String url
     String blogid
     String blogName
     String xmlrpc

wp.getPageList

Retorna uma array com todas as páginas de um blog. Somente informações mínimas são retornadas, para maiores informações pode ser usado o método wp.getPages.

Parâmetros:

Int32 blog_id
String username
String password

Retorno:

Array
   Struct
      Int32 page_id
      String page_title
      Int32 page_parent_id
      DateTime dateCreated

Agora que conhecemos a estrutura dos nossos métodos necessitamos cria-las em nosso projeto. Até o momento criaremos somente a estrutura de retorno do método wp. getUsersBlogs, pois nos próximos passos mostrarei como utilizar as funções DumpVariables para descobrir a estrutura de retorno dos dados.

public struct getUsersBlogsResponse
{
   public Boolean isAdmin;
   public String url;
   public String blogid;
   public String blogName;
   public String xmlrpc;
}

Caso deseje não receber algum parâmetro basta incluir a instrução para ignorar conforme código abaixo. Apenas para exemplificação foi removido o parâmetro xmlrcp. Em nosso exemplo não utilizaremos essa instrução.

[XmlRpcMissingMapping(MappingAction.Ignore)]
public struct getUsersBlogsResponse
{
   public Boolean isAdmin;
   public String url;
   public String blogid;
   public String blogName;
}

Após a criação das inclua os métodos na interface XML-RPC.

[XmlRpcMethod("wp.getUsersBlogs")]
getUsersBlogsResponse[] getUsersBlogs(String username, String password);

[XmlRpcMethod("wp.getPageList")]
Object getPageList(Int32 blog_id, String username, String password);

Depois inclua as chamadas na classe APIBase

public getUsersBlogsResponse[] UsersBlogs(String username, String password)
{
   return proxy.getUsersBlogs(username, password);
}

public Object PageList(Int32 blog_id, String username, String password)
{
   return proxy.getPageList(blog_id, username, password);
}

Note que o retorno do método PageList foi definido como Object, pois como desejamos conhecer a estrutura o Object é uma forma genérica de retorno para que possamos utilizar a função DumpVariables.

Agora no código principal basta realizar as consultas.

using System;
using WebServiceAPI;

namespace Test
{
   class Program
   {
      public static void Main(string[] args)
      {
          APIBase xmlRpcApi = new APIBase(new Uri("http:// servidor_wordpress /xmlrpc.php"), true);
          String[] WSMethods = xmlRpcApi.ListMethods();
          //foreach(String methodName in WSMethods)
          //            Console.WriteLine(methodName);

          String username = "WordPressAdminUser";
          String password = "Senha";
          getUsersBlogsResponse[] Blogs = xmlRpcApi.UsersBlogs(username,password);
          foreach(getUsersBlogsResponse b in Blogs)
          {
              Object PList = xmlRpcApi.PageList(Int32.Parse(b.blogid), username,password);
              Console.WriteLine(b.blogid + " = " + b.blogName);
              Console.WriteLine(APIBase.DumpVariable(PList));
              Console.WriteLine("");
         }
         Console.Write("Press any key to continue . . . ");
         Console.ReadKey(true);
      }
   }
}

Executando este código tem-se a seguinte saída:

Pode-se observar pela imagem que o retorno da chamada PageList é um array do tipo XmlRpcStruct e que contem as seguintes variáveis:

String page_id
String page_title
DateTime dateCreated
Datetime date_created_gmt



De posse dessa informação podemos criar a struct e retornar os dados no formato desejado.

public struct getPageListResponse
{
public String page_id;
public String page_title;
public DateTime dateCreated;
public DateTime date_created_gmt;
}

Altere a interface e a API para utilizar essa struct

//Struct
[XmlRpcMethod("wp.getPageList")]
getPageListResponse[] getPageList(Int32 blog_id, String username, String password);

//API
public getPageListResponse[] PageList(Int32 blog_id, String username, String password)
{
   return proxy.getPageList(blog_id, username, password);
}

E por ultimo a aplicação principal:

using System;
using WebServiceAPI;
namespace Test
{
     class Program
     {
        public static void Main(string[] args)
        {
             APIBase xmlRpcApi = new APIBase(new Uri("http:// servidor_wordpress /xmlrpc.php"), true);
             String[] WSMethods = xmlRpcApi.ListMethods();
             //foreach(String methodName in WSMethods)
             //            Console.WriteLine(methodName);
             String username = "WordPressAdminUser";
             String password = "Senha";
             getUsersBlogsResponse[] Blogs = xmlRpcApi.UsersBlogs(username,password);
             foreach(getUsersBlogsResponse b in Blogs)
             {
                  Console.WriteLine(b.blogid + " = " + b.blogName);
                  getPageListResponse[] PList = xmlRpcApi.PageList(Int32.Parse(b.blogid), username,password);
                  foreach(getPageListResponse pl in PList)
                  {
                      Console.WriteLine("\t" + pl.page_id + ", " + pl.page_title);
                  }
                  Console.WriteLine("");
             }
             Console.Write("Press any key to continue . . . ");
             Console.ReadKey(true);
         }
     }
}

Executando este código temos essa saída na tela:

Licença

Este aplicativo pode ser livremente utilizado.

Download

Aplicativo XML-RPC padrão

Aplicativo XMLRPC-barracuda

Helvio Junior

Helvio Junior

Especialista em Segurança Ofensiva e Análise de Malwares em SafeTrend
Especialista em Segurança Ofensiva e pesquisador independente de Malwares.
Helvio Junior
3 respostas
  1. Tiago Carneiro Gesto
    Tiago Carneiro Gesto says:

    Olá meu amigo.
    Pesquisando na internet sobre HTTP/XML-RPC achei o seu site e talvez você possa sanar algumas duvidas que tenho que sou leigo em assunto de servidores. Estou querendo alugar um servidor para jogos com hospedagem do servidor nos EUA e o site que aluga os servidores me passou um documento mostrando como acessar e configurar o servidor mas tem umas coisas que não entendo. Quando voce aluga o servidor dedicado voce escolhe o numero de jogadores, localização do servidor e o nome do servidor. Efetuado o pagamento eu preciso configurar o servidor e é ai que eu não entendo o documento. Além dele estar em ingles o documento é falado de uma forma tecnica que deixa o entendimento ruim. Eu vou colocar aqui os trechos mais importantes do documento e depois vou fazer umas perguntas.

    RCON CONNECTIVITY
    Crysis 3 supports the same HTTP/XML-RPC protocol from Crysis 2. Your server provider will inform you of the IP, port and password required.

    Once started, you can use a third party HTTP/XML-RPC client (there is no internally developed HTTP/XML-RPC client available at this time).

    Crysis 3 also supports the same rcon protocol from Crysis 2.Your server provider will inform you of the IP, port and password required.

    Clients will be able to connect to the dedicated server by using the following command on their in-game console:

    rcon_connect addr:%external IP of the dedicated server% port:%port as specified on the dedicated server% pass:%password as specified on the dedicated server%

    Once connected, commands may be issued to the dedicated server by using the following command on their in-game console:rcon_command %command%
    where %command% is the remote command that they wish to execute on the dedicated server, e.g. rcon_command sv_maxplayers 8

    Clients may disconnect from the rcon server by using the following command on their in-game console:rcon_disconnect

    Setting the RCON Password:
    The rcon password can be set with rcon_password=password in your dedicated.cfg
    If rcon_password is not set, rcon will try to use http_password.
    If you’re intending to use the same password for http and rcon, then you only need to start rcon with rcon_startserver port:xxxx

    LEVEL ROTATION.XML
    Custom level rotation files can be created easily with a LevelRotation.xml file, using either built-in or custom playlists and variants. Once the LevelRotation.xml file has been created (see examples below), place it in the root of the build (the same directory as dedicated.cfg).

    Note: If you do not intend to change a setting, it doesn’t need to be in the LevelRotation.xml file.

    Note: we have provided a number of example LevelRotation.xml files for you to play with

    Firstly, using a standard playlist:
    If the name of the LevelRotation is one of the existing playlists, i.e. “TDM”,”DM “, “CRASHSPEARS”, “EXTCTF”, “HUNTER”, “ASSAULT”, “MEDLEY”, “CELLREBEL”, “DESIGN”, “TDMPRO”, “DMPRO” then it will use one of these inbuilt playlists for the map/mode combinations. Any map/mode combo’s specified in the LevelRotation.xml will be ignored. These playlists mirror the console playlists, as did Crysis 2. MEDLEY is all levels with all game modes whereas CRASHSPEARS contains a mixture of CRASHSITE and SPEARS game modes.

    As minhas duvidas são as seguinte:
    dentro do jogo existe um comando quando voce aperta a tecla aspas e crase logo acima do Tab que abre uma tela parecida com o prompt do windows onde fica aparecendo informações do software do jogo e outras coisas. Será que é essa tela que o documento que coloquei aqui chama de console ou o console é realmente um acesso pelo prompt do windows? A outra duvida é que para configurar o server como eu quero fazendo uma configuração Custom eu preciso criar um arquivo chamado level rotation. xml e coloca-lo no mesmo directorio de dedicated.cfg. Onde ficaria esses arquivos na minha maquina ou no servidor e se for no servidor como crio esses arquivos la? via console ou prompt?
    Me desculpe o volume de informações Helio!! Esse tipo de informação é tão dificil de ser achar na net e quando vi voce imaginei que pudesse me dar uma força

    Obrigado pela atenção e se possivel espero sua resposta!!

    Tiago Carneiro Gesto

    Responder
    • helvio
      helvio says:

      Tiago,

      Como não conheço o seu jogo e a estrutura que o mesmo foi desenvolvido (funcionalidades, integração e etc) fica bem complicado de lhe ajudar. Recomendo conversar com alguém que ja tenha realizado esta integração ou com o fornecedor do servidor.

      Abraços.

      Responder
  2. Luis
    Luis says:

    Cara, comecei a usar a lib mas encontrei uma dificuldade.
    Criei as structs de retorno as vezes o retorno do server pode vir diferente.
    Quando o retorno é direrente da struct da erro.
    Tem uma maneira de deixar o retorno mais genérico?
    Abraço

    Responder

Deixe uma resposta

Want to join the discussion?
Feel free to contribute!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *