Esse mini tutorial tem como objetivo mostar os fundamentos da criação de uma aplicação RESTful com Qt, como cliente e como servidor com o auxilio do Cutelyst.
Serviços com APIs REST se tornaram muito populares nos ultimos anos, e interagir com elas pode ser necessário para integrar com serviços e manter sua aplicação relevante, assim como pode ser interessante substituir seu protocolo próprio com uma implementação REST.
REST é muito associado a JSON, porém, para um serviço se caracterizar REST não é necessário utilizar JSON, a forma como os dados são trocados é escolhida por quem define a API, ou seja é possível ter REST trocando mensagens em XML ou outro formato. Iremos utilizar JSON por sua popularidade, simplicidade e devido a classe QJsonDocument ser presente no módulo Core do Qt.
Um serviço REST é caracterizado principalmente por fazer uso dos métodos e cabeçalhos (headers) pouco utilizados do HTTP, navegadores utilizam basicamente GET para obter dados e POST para enviar dados de formulários e arquivos, porém clientes REST farão uso de métodos como DELETE, PUT e HEAD, se tratando de cabeçalhos muitas APIs definem cabeçalhos para autenticação, por exemplo X-Applicativo-Token pode conter uma chave gerada apenas para o aplicativo de um usuário X, de forma que se esse cabeçalho não conter os dados corretos ele não terá acesso aos dados.
Vamos começar definindo a API do servidor:
Por motivos de simplicidade iremos armazenar os dados utilizando QSettings, não recomendo para aplicações reais, mas foge do escopo utilizar Sql ou algo do tipo. Também assumimos que já tem o Qt e o Cutelyst instalados, o código estará disponível em https://github.com/ceciletti/example-qt-cutelyst-rest
Primeiramente criamos a aplicação do servidor:
$ cutelyst --create-app ServidorREST
E então iremos criar o Controller que terá os metodos da API:
$ cutelyst --controller ApiV1
Feito isso a nova classe deve ser instanciada em serverrest.cpp, no método init() com,
#include "apiv1.h"
bool ServerREST::init() {
new ApiV1(this);
...
Adicione os seguintes métodos ao arquivo "apiv1.h"
C_ATTR(usuarios, :Local :AutoArgs :ActionClass(REST))
void usuarios(Context *c);C_ATTR(usuarios_GET, :Private)
void usuarios_GET(Context *c);C_ATTR(usuarios_POST, :Private)
void usuarios_POST(Context *c);C_ATTR(usuarios_uuid, :Path('usuarios') :AutoArgs :ActionClass(REST))
void usuarios_uuid(Context *c, const QString &uuid);C_ATTR(usuarios_uuid_GET, :Private)
void usuarios_uuid_GET(Context *c, const QString &uuid);C_ATTR(usuarios_uuid_PUT, :Private)
void usuarios_uuid_PUT(Context *c, const QString &uuid);C_ATTR(usuarios_uuid_DELETE, :Private)
void usuarios_uuid_DELETE(Context *c, const QString &uuid);
A macro C_ATTR é usada para adicionar metadados sobre a classe que o MOC ira guardar, dessa forma o Cutelyst sabe como mapear as URLs a essas funções.
Pronto isso já é suficiente para termos um mapeamento automático dependendo do método HTTP para cada função, é importante notar que a primeira função (sem _MÉTODO) é sempre executada, para mais informações veja a API do ActionREST
Por brevidade vou mostrar apenas o código do GET de usuários, o restante pode ser visto no GitHub:
void ApiV1::usuarios_GET(Context *c)
{
QSettings s;
const QStringList uuids = s.childGroups();QJsonArray array;
for (const QString &uuid : uuids) {
array.append(uuid);
}c->response()->setJsonBody(array);
}
Após todos os métodos implementados inicie o servidor:
cutelyst -r --server
Para testar a API pode testar um POST com o curl:
curl -H "Content-Type: application/json" -X POST -d '{"nome":"fulano","idade":32}' http://localhost:3000/api/v1/usuarios
Pronto, você já tem uma aplicação servidor REST, feita em Qt, com uma das mais respostas mais rápidas do velho oeste :)
Não, é serio, confira os benchmarks.
Agora vamos para parte 2, que é criar a aplicação cliente que ira consumir essa API.
Primeiramente crie um projeto QWidgets com uma QMainWindow, o objetivo aqui é apenas ver como criar as solicitações REST a partir de código Qt, então assumimos que já tem familiaridade em criar interfaces gráficas com o mesmo.
Nossa interface será composta:
Desenhada a interface, nossa sub-classe do QMainWindow precisa ter um ponteiro para o QNetworkAccessManager, essa é a classe responsável por cuidar da comunicação com serviços de rede como HTTP e FTP. Essa classe trabalha de forma assíncrona, ela tem o funcionamento similar ao de um navegador que irá criar até 6 conexões simultâneas a um mesmo servidor, caso você tenha feito mais requisições ao mesmo tempo ela ira colocar as mesmas em uma fila.
Crie então um ponteiro QNetworkAccessManager *m_nam; como membro de sua classe para podermos reutiliza-lo. A nossa requisição para obter a lista de usuários será bastante simples:
QNetworkRequest request(QUrl("http://localhost:3000/api/v1/usuarios"));
QNetworkReply *reply = m_nam->get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply] () {
reply->deleteLater();
const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());
const QJsonArray array = doc.array();for (const QJsonValue &value : array) {
ui->uuidCB->addItem(value.toString());
}
});
Pronto já preenchemos com os dados via GET do servidor nosso QComboBox, agora vamos ver o código do cadastramento que é um pouco mais complexo:
QNetworkRequest request(QUrl("http://localhost:3000/api/v1/usuarios"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");QJsonObject obj{
{"nome", ui->nomeLE->text()},
{"idade", ui->idadeSP->value()}
};QNetworkReply *reply = m_nam->post(request, QJsonDocument(obj).toJson());
connect(reply, &QNetworkReply::finished, this, [this, reply] () {
reply->deleteLater();
const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());
const QJsonObject obj = doc.object();if (obj.value("status").toString() == "ok") {
ui->uuidCB->addItem(obj.value("uuid").toString());
} else {
qWarning() << "ERROR" << obj.value("error").toString();
}
});
Com o código acima enviamos uma requisição HTTP usando o método POST, esse por sua vez assim como PUT aceita enviar dados para o servidor. É fundamental informar ao servidor com que tipo de dados ele estará lidando, para isso o cabeçalho "Content-Type" esta definido para "application/json", o Qt emite um aviso no terminal caso o tipo de conteúdo não tenha sido definido. Assim que o servidor responde nós adicionamos o novo UUID no combobox para que ele fique atualizado sem precisar obter todos os UUID novamente.
Como demonstrado QNetworkAccessManager já tem métodos prontos para as ações REST mais comuns, porém caso queria enviar um pedido do tipo OPTIONS por exemplo terá que criar um pedido do tipo CustomOperation:
m_nam->sendCustomRequest("OPTIONS", request);
Gostou do artigo? Ajude dando uma estrelinha para o Cutelyst https://github.com/cutelyst/cutelyst
2012-2020 © Ceciletti - Software out of the box