Integrando C# e C++ via serviço REST

Uma das coisas que gosto de trabalhar é na integração de tecnologias diferentes, geralmente C++ e .Net e nos próxmis artigos irei abordar uma maneira de integrar essas duas linguagens utilizando um serviço WCF REST com e um cliente C++ utilizando a libcurl. Neste primeiro artigo irei demostrar a criação de um serviço e um cliente ambos bem simples.

O Serviço

O serviço que irei demonstrar será um WCF e evitar a necessidade de configurar o IIS sendo que este será bem simples o WCF será um self hosted, ou seja, ele mesmo será responsável por manter o serviço no ar e para isso utilizarei uma aplicação console do windows.

Criação de aplicação console

Com o projeto criado, o primeiro passo para a criação do WCF é a definição de um contrato e para o este caso é bem simples, onde serão definidos apenas dois métodos simulando uma consulta a uma livraria.

1
2
3
4
5
6
7
8
9
10
11
namespace WcfServer
{
[ServiceContract]
public interface IBooks
{
[OperationContract]
List<string> GetSections();
[OperationContract]
List<string> GetBooks(string section);
}
}

Tendo o contrato do serviço definido, com os métodos e seus atributos é necessário criar uma classe com as suas implementações.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class LibraryService : IBooks
{
[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json, UriTemplate = "sections/")]
public List<string> GetSections()
{
return new List<string>() { "ScienceFiction", "Fantasy" };
}

[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json, UriTemplate = "items/{section}")]
public List<string> GetBooks(string section)
{
if (section.Equals("ScienceFiction", StringComparison.CurrentCultureIgnoreCase))
{
return new List<string>() { "I Robot", "Old Man's War" };
}
else if (section.Equals("Fantasy", StringComparison.CurrentCultureIgnoreCase))
{
return new List<string>() { "The Hobbit", "A Game of Thrones" };
}
return new List<string>();
}
}

Aqui o atributo WebInvoke para define o comportamento de cada método do serviço, configurando o modelo de chamada para GET na propriedade Method, o endereço de cada chamada dentro da URL do  serviço com a propriedade UriTemplate e  o formato da resposta para json com a propriedade ResponseFormat
Desta maneira uma chamada para o endereço http://localhost/sections sem nenhuma querystring  nos retornará uma lista de strings serializada para json. Já a chamada no endpoint http://localhost/items/{_section_} espera uma string que será utilizada como parâmetro da função. Neste caso a informação é passada sem utilizar uma variável de  querystring, sendo colocado o valor diretamente após o endereço do endpoint, mas isso também é possível e basta ver a documentação da propriedade UriTemplate. Com esses dois passos concluídos é possível criar o serviço.

1
2
3
4
5
6
7
8
9
10
11
static void Main(string[] args)
{
Uri httpUrl = new Uri("http://localhost:8090/library");
var host = new WebServiceHost(typeof(LibraryService), httpUrl);
var binding = new WebHttpBinding();
host.AddServiceEndpoint(typeof(IBooks), binding, "books");
host.Open();
Console.WriteLine("press any key to quit");
Console.ReadLine();
host.Close();
}

A criação do WCF é feita de maneira bem parecida de um WCF tradicional notando apenas que utilizamos o WebServiceHost e WebHttpBinding que são implementações do próprio .Net Framework. Onde o WebServiceHost é um host específico para REST e o WebHttpBinding configura o host para expor as interfaces via HTTP e não via SOAP que é o padrão para o WCF. Como o serviço precisa ficar rodando indefinidamente para atender as chamadas o Console.ReadLine faz com que a aplicação aguarde o pressionamento da tecla _ para finalizar.__
_

Serviço WCF sendo executado
_
_


### O Cliente

O cliente será desenvolvido C++ e para conectar ao servidor utilizaremos a libcurl que é uma biblioteca que abstrai boa parte da criação da conexão e serve para uma grande diversidade de protocolos, mas no escopo do cliente é utilizado apenas o suporte a HTTP. Outra vantagem da libcurl é que ela esta disponível para varias plataformas, no caso desse cliente que estamos desenvolvendo estamos utilizando a sua versão para linux. Aqui é possível ver todas as funcionalidades desta biblioteca.
A utilização da libcurl é bem simples e existe uma boa documentação no site. Vou explicar aqui apenas o que foi utilizado para criar o cliente. Como o nosso programa é bem simples utilizaremos o easy interface da libcurl, que é mais simples e síncrona, para processos assíncronos a libcurl disponibiliza a multi interface.
O primeiro passo é inicializar a libcurl e para fazer isso na easy interface utiliza-se o método _curl_easy_init_ que retorna um handle para a sessão curl que será utilizada.
1
CURL *curl = curl_easy_init();


Com a libcurl inicializada já é possível configurar a nossa requisição ao servidor, que está definido responderá no formato json no servidor WCF. A configuração é feita pela função _curl_easy_setopt_ passando uma lista de strings com os valores que serão adicionados ao header da requisição.
1
2
3
4
5
struct curl_slist *headers = NULL;
curl_slist_append(headers, "Accept: application/json");
curl_slist_append(headers, "Content-Type: application/json");
curl_slist_append(headers, "charsets: utf-8");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);


A função _curl_easy_setopt_ não é utilizada apenas para definir o header e sim para configurar qualquer aspecto que a libcurl permite ser configurado. Nos próximos essa função é utilizada para definirmos qual a URL as ser chamada, qual função irá tratar os dados de retorno e qual objeto irá armazenar essa informação.
1
2
3
4
5
6
7
8
// define o caminho a ser solicitado
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
// função que irá tratar os dados de retorno
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, this-&gt;OnReceiveData);
// objeto que será passada como parâmetro para a funcão definida anteriormente
std::string buffer;
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &amp;buffer);
// faz o get de maneira blocante


Para cada passo de configuração a _curl_easy_setopt_ recebe uma constante identificando o tipo de informação que está sendo definida. No site da libcurl existe uma lista com todas essas opções. Neste cao a opção que precisa de mais atenção nesse momento é a CURLOPT_WRITEFUNCTION, que espera um callback, que será chamado com os dados obtidos pelo chamada ao endereço especificado.
1
size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);


Neste cliente iremos apenas atribuir o valor recebido para uma string e escrever no terminal, sendo que a string utilizada é a mesma definida com a opção _CURLOPT_WRITEDATA_. Neste ponto já está configurada a chamada com header, endereço, função para tratar dados e objeto para armazenamento. E basta fazer a chamada para os métodos do serviço.
1
2
3
RestClient client;
auto data = client.DownloadData("http://localhost:8090/library/books/sections/");
std::cout &lt;&lt; data &lt;&lt; std::endl;


E rodando o cliente teremos:

Resultado do cliente

Continuando

No próximo artigo irei mostrar como lidar de maneira mais útil com objetos json mais complexos no C++ utilizando uma biblioteca específica para isso.

O código deste artigo pode ser obtido no Bitbucket