Última atualização em 4 de setembro de 2023 por Willian Tuttoilmondo
Recapitulando
Como vimos na parte I deste artigo, o processo para criação de APIs RESTful com Delphi e DataSnap é um processo simples, mas extenso. Como já iniciamos o projeto, definimos quais eram os detalhes da estrutura, incluindo a estrutura de diretórios. Sendo assim, seguiremos com as implementações que serão utilizadas em nossa API. Como já disse antes, a base para essa implementação é um artigo já escrito anteriormente, onde utilizamos padrões de projeto. Aqui faremos o mesmo, mas teremos uma pequena diferença entre a implementação daquele artigo e essa nova implementação.
Ao longo do artigo veremos a diferença e entenderemos o porquê dela. Mas, como o tempo urge e a Sapucaí é grande, vamos logo ao que interessa.
APIs RESTful com Delphi e DataSnap – Implementações
Para começarmos nossa implementação, vamos lembrar da classe TOperação que vimos no outro artigo. Nela havíamos estabelecido as bases para as demais classes que seriam utilizadas no projeto. Sendo assim, seu conteúdo pode ser visto abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | unit Classes.Operacao; interface type TOperacao = class abstract strict private FB: Extended; FA: Extended; procedure SetA(const Value: Extended); procedure SetB(const Value: Extended); public constructor Create; virtual; function Efetuar: Extended; virtual; abstract; property A: Extended read FA write SetA; property B: Extended read FB write SetB; end; TOperacaoClass = class of TOperacao; implementation { TOperacao } constructor TOperacao.Create; begin FA := 0; FB := 0; end; procedure TOperacao.SetA(const Value: Extended); begin if FA = Value then begin Exit; end; FA := Value; end; procedure TOperacao.SetB(const Value: Extended); begin if FB = Value then begin Exit; end; FB := Value; end; end. |
Ao contrário da versão anterior, esta classe possui apenas um método público além de seu construtor: Efetuar. Além disso, esse método, que anteriormente possuía um retorno do tipo Integer, agora possui o retorno Extended, já que todas as operações da API retornarão um valor Extended. Com isso, as classes TSoma, TSubtracao, TMultiplicacao e TDivisao também terão apenas o método Efetuar como público e retornando uma valor Extended, como pode ser visto abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | unit Classes.Soma; interface uses Classes.Operacao; type TSoma = class(TOperacao) public function Efetuar: Extended; override; end; implementation uses Classes.Factory; { TSoma } function TSoma.Efetuar: Extended; begin Result := A + B; end; initialization TFactory.Instance.RegisterClass('soma', TSoma); end. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | unit Classes.Subtracao; interface uses Classes.Operacao; type TSubtracao = class(TOperacao) public function Efetuar: Extended; override; end; implementation uses Classes.Factory; { TSubtracao } function TSubtracao.Efetuar: Extended; begin Result := A - B; end; initialization TFactory.Instance.RegisterClass('subtracao', TSubtracao); end. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | unit Classes.Multiplicacao; interface uses Classes.Operacao; type TMultiplicacao = class(TOperacao) public function Efetuar: Extended; override; end; implementation uses Classes.Factory; { TMultiplicacao } function TMultiplicacao.Efetuar: Extended; begin Result := A * B; end; initialization TFactory.Instance.RegisterClass('multiplicacao', TMultiplicacao); end. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | unit Classes.Divisao; interface uses Classes.Operacao; type TDivisao = class(TOperacao) public function Efetuar: Extended; override; end; implementation uses Classes.Factory; { TDivisao } function TDivisao.Efetuar: Extended; begin Result := A / B; end; initialization TFactory.Instance.RegisterClass('divisao', TDivisao); end. |
E, como não poderia faltar, temos o Singleton da nossa Abstract Factory, a qual fará a instância do objeto da operação selecionada para que possamos efetuar nossa operação matemática. Esse código, em relação ao anterior, não sofreu alterações, mas é importante colocá-lo aqui, como visto abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | unit Classes.Factory; interface uses System.Generics.Collections, Classes.Operacao; type TFactory = class strict private FDictionary: TObjectDictionary<string, TOperacaoClass>; public constructor Create; destructor Destroy; class function Instance: TFactory; procedure RegisterClass(const AKey: string; const AClass: TOperacaoClass); function GetOperacao(const AKey: string): TOperacao; property Operacoes: TObjectDictionary<string, TOperacaoClass> read FDictionary; end; implementation uses System.SysUtils; var FFactory: TFactory; { TFactory } constructor TFactory.Create; begin FDictionary := TObjectDictionary<string, TOperacaoClass>.Create; end; destructor TFactory.Destroy; begin FreeAndNil(FDictionary); end; function TFactory.GetOperacao(const AKey: string): TOperacao; var classOperacao: TOperacaoClass; begin Result := nil; if not (FDictionary.TryGetValue(AKey, classOperacao) and Assigned(classOperacao)) then begin Exit; end; Result := classOperacao.Create; end; class function TFactory.Instance: TFactory; begin if not Assigned(FFactory) then begin FFactory := TFactory.Create; end; Result := FFactory; end; procedure TFactory.RegisterClass(const AKey: string; const AClass: TOperacaoClass); begin if FDictionary.ContainsKey(AKey) then begin Exit; end; FDictionary.Add(AKey, AClass); end; initialization FFactory := nil; finalization FreeAndNil(FFactory); end. |
Com esta etapa da implementação fechada, podemos partir para as implementações que estabelecerão a comunicação com nossa API. É sempre bom lembrar que, para que tudo isso funcione, é necessária uma versão Enterprise ou Archtect do Delphi, a qual pode ser obtida aqui para um teste de 30 dias. Até o momento do fechamento desta série de artigos, a versão disponibilizada era a 11 (Alexandria). Quem quiser testar esse nosso projeto nesta versão e nos contar os resultados, ficaremos muito felizes em ouvir.
Sendo assim, vamos para a próxima etapa.
APIs RESTful com Delphi e DataSnap – Preparando a API
Como já implementamos nossas classes que serão consumidas através da API, chegou a hora de implementar a API em si. o Primeiro passo é remover tudo aquilo que não precisaremos no nosso WebModule. Depois de uma faxina básica, seu código ficou desta maneira:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | unit WebModule.Container; interface uses System.SysUtils, System.Classes, Web.HTTPApp, IPPeerServer, Datasnap.DSCommonServer, Datasnap.DSHTTP, Datasnap.DSHTTPWebBroker, Datasnap.DSServer; type TdmDSCalculator = class(TWebModule) hwdDSCalculator: TDSHTTPWebDispatcher; dssDSCalculator: TDSServer; procedure WebModule1DefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); private { Private declarations } public { Public declarations } end; var WebModuleClass: TComponentClass = TdmDSCalculator; implementation {%CLASSGROUP 'System.Classes.TPersistent'} {$R *.dfm} uses Web.WebReq; procedure TdmDSCalculator.WebModule1DefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin end; initialization finalization Web.WebReq.FreeWebModules; end. |
Como parte da especificação RESTful, alguns aspectos da normalização das rotas são necessários quando planejamos a distribuição de nossa API. Um deles é o nome do “concentrador” ou “serviço” que, no nosso caso, é dscalculator. Além dele, outro aspecto é a versão da API, a qual deve constar na rota de acesso. Como estamos trabalhando com nossa primeira versão, esta será a v1. E para indicarmos que estamos acessando uma API, vamos colocar o termo api no início da rota. Sendo assim, nossa rota completa fica estabelecida como /api/dscalculator/v1/[classe], onde [classe] indica qual classe acessaremos. Quando montarmos nosso ambiente de testes falaremos mais sobre as rotas e o porquê dessa especificação mas, por enquanto, nos atenhamos a isso.
Para iniciar nossa implementação, vamos alterar mais um pouco nossa unit do WebModule. A princípio, mudaremos a seção uses da interface, removendo a unit System.SysUtils de lá é levando-a para a seção uses da implementação. Além disso, vamos adicionar mais algumas units a esta seção, o que vai nos permitir trabalhar com mais liberdade em alguns aspectos, deixando esta seção como vemos abaixo:
1 2 3 4 5 6 7 8 9 10 | uses System.SysUtils, Web.WebReq, System.JSON, Helpers.JSONObject, Helpers.JSONValue, Helpers.Str, Helpers.Integer, Classes.Factory, Classes.Operacao; |
E com isso, podemos implementar a primeira verificação de nosso método WebModule1DefaultHandlerAction.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | procedure TdmDSCalculator.WebModule1DefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var strPathInfo: string; bufPathInfo: TArray<string>; begin // Recebendo o PathInfo constante na Request strPathInfo := Request.InternalPathInfo; // Eliminando o primeiro caracter / do PathInfo Delete(strPathInfo, 1, 1); // Dividindo o PathInfo em partes para idenificar a classe solicitada bufPathInfo := strPathInfo.Split('/'); // Atribuindo o Content-Type da resposta como padrão para todas elas Response.ContentType := 'application/json'; // Verificando se o comprimento do caminho está correto // Senão estiver, emita uma mensagem de erro e saia if not bufPathInfo.Count.Equals(3) then begin with TJSONObject.Create do begin AddBooleanPair('sucesso', False); AddStringPair('mensagem', 'Caminho para a solicitação inválido.'); Response.Content := Minify; Response.StatusCode := 400; // Bad Request Free; Exit; end; end; end; |
Nesse ponto, começamos a avaliar nossa requisição, a qual vem através do parâmetro Request. A primeira coisa que precisamos checar é se a rota solicitada é válida. Quando chamamos a rota https://localhost/api/dscalculator/v1/soma?a=20&b=30 para acesso à API, parte dessa rota vem através da propriedade InternaPathInfo do parâmetro Request. Sendo assim, e para poder manipulá-lo posteriormente, vamos atribuí-lo à variável local strPathInfo.
Feito isso, precisamos excluir o primeiro caractere / que consta na variável. Isso facilitará nosso processo de quebra da rota em um array de strings, o qual será usado para validar essa mesma rota. A partir daí, dividimos a string em um array de strings utilizado um record helper, sobre o qual já discutimos aqui, e devemos atribuir ao cabeçalho Content-Type da resposta o valor application/json, já que todas as respostas serão um objeto JSON. Depois, verificamos se a quantidade de termos nele é diferente de três, uma vez que a propriedade InternalPathInfo retorna o valor /dscalculator/v1/soma.
Caso nossa verificação confirme que a rota não contém três termos, criaremos um objeto JSON para ser usado como resposta, adicionando a ele um nó booleano indicando falha (“sucesso”: false) uma mensagem explicativa e amigável do erro. Para completar a resposta, devemos atribuir à ela o StatusCode 400, indicando que a requisição é inválida. Ao corpo da resposta atribuímos o conteúdo do objeto JSON e, assim como liberamos o objeto JSON da memória, efetuamos a saída do método.
Em seguida, implementamos a verificação da versão da API e a existência da classe registrada em nossa factory. Algumas refatorações foram feitas de forma a deixar o código mais limpo, então o método completo pode ser visto abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | procedure TdmDSCalculator.WebModule1DefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var strPathInfo: string; bufPathInfo: TArray<string>; strClass : string; procedure SetErrorResponse(const AMessage: string; const AStatusCode: Integer = 400 {Bad Request}); begin with TJSONObject.Create do begin AddBooleanPair('sucesso', False); AddStringPair('mensagem', AMessage); Response.Content := Minify; Response.StatusCode := AStatusCode; Free; end; end; begin // Recebendo o PathInfo constante na Request strPathInfo := Request.InternalPathInfo; // Eliminando o primeiro caracter / do PathInfo Delete(strPathInfo, 1, 1); // Dividindo o PathInfo em partes para idenificar a classe solicitada bufPathInfo := strPathInfo.Split('/'); // Atribuindo o Content-Type da resposta como padrão para todas elas Response.ContentType := 'application/json'; // Verificando se o comprimento do caminho está correto // Se não estiver, emita uma mensagem de erro e saia if not bufPathInfo.Count.Equals(3) then begin SetErrorResponse('Caminho para a solicitação inválido.'); Exit; end; // Verificando se a versão da API é a mesma da constante na rota // Se não for, emita uma mensagem de erro e saia if not bufPathInfo[1].Equals('v1') then begin SetErrorResponse('Versão inválida da API.'); Exit; end; strClass := bufPathInfo[2]; // Verificando se a classe chamada na rota está registrada na factory // Se não estiver, emita uma mensagem de erro e saia if not TFactory.Instance.Operacoes.Keys.ToArray.Contains(strClass) then begin SetErrorResponse('Classe %s não encontrada.'.Format([strClass])); Exit; end; end; |
Agora, vamos implementar o consumo das classes que já codificamos anteriormente. Isso será acessado através da factory, o que nos garante uma versatilidade maior na hora de fazer implementações mais extensas em nosso projeto. E como precisamos verificar o método de requisição (inicialmente utilizaremos o método GET), faremos essa verificação no momento de invocar o método Efetuar do objeto selecionado.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | procedure TdmDSCalculator.WebModule1DefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var strPathInfo: string; bufPathInfo: TArray<string>; strClass : string; objOperacao: TOperacao; procedure SetErrorResponse(const AMessage: string; const AStatusCode: Integer = 400 {Bad Request}); begin with TJSONObject.Create do begin AddBooleanPair('sucesso', False); AddStringPair('mensagem', AMessage); Response.Content := Minify; Response.StatusCode := AStatusCode; Free; end; end; function GetQueryField(const AField: string): string; var strField: string; bufField: TArray<string>; i : Integer; begin Result.Clear; for i := 0 to Request.QueryFields.Count.Pred do begin strField := Request.QueryFields[i]; if strField.Pos(AField + '=') <> 1 then begin Continue; end; bufField := strField.Split('='); Result := bufField[1]; Break; end; end; procedure DoOperation; var strA: string; strB: string; begin // Verifica se os parâmetros foram passados corretamente if Request.QueryFields.Text.IsEmpty then begin SetErrorResponse('Nenhum parâmetro foi atribuído na rota.'); Exit; end; // Lendo os campos na Query String da rota strA := GetQueryField('a'); strB := GetQueryField('b'); // Verificando se os campos estão vazios ou não foram encontrados if strA.IsEmpty or strB.IsEmpty then begin SetErrorResponse('Parâmetro a ou b não encontrado.'); Exit; end; // Verificando se os campos são valores numéricos if not ((strA.IsNumbersOnly or strA.IsFloat) and (strB.IsNumbersOnly or strB.IsFloat)) then begin SetErrorResponse('Parâmetros a e b devem conter valores numéricos.'); Exit; end; // Atribuindo valores às propriedades do objeto objOperacao.A := strA.ToFloat; objOperacao.B := strB.ToFloat; // Criando o resultado with TJSONObject.Create do try try AddFloatPair('result', objOperacao.Efetuar); Response.Content := Minify; Response.StatusCode := 200; except on E: Exception do begin case E is EZeroDivide of True : raise Exception.Create('O valor de b não pode ser 0 em uma divisão.'); False: raise; end; end; end; finally Free; end; end; begin // Recebendo o PathInfo constante na Request strPathInfo := Request.InternalPathInfo; // Eliminando o primeiro caracter / do PathInfo Delete(strPathInfo, 1, 1); // Dividindo o PathInfo em partes para idenificar a classe solicitada bufPathInfo := strPathInfo.Split('/'); // Atribuindo o Content-Type da resposta como padrão para todas elas Response.ContentType := 'application/json'; // Verificando se o comprimento do caminho está correto // Se não estiver, emita uma mensagem de erro e saia if not bufPathInfo.Count.Equals(3) then begin SetErrorResponse('Caminho para a solicitação inválido.'); Exit; end; // Verificando se a versão da API é a mesma da constante na rota // Se não for, emita uma mensagem de erro e saia if not bufPathInfo[1].Equals('v1') then begin SetErrorResponse('Versão inválida da API.'); Exit; end; strClass := bufPathInfo[2]; // Verificando se a classe chamada na rota está registrada na factory // Se não estiver, emita uma mensagem de erro e saia if not TFactory.Instance.Operacoes.Keys.ToArray.Contains(strClass) then begin SetErrorResponse('Classe %s não encontrada.'.Format([strClass])); Exit; end; // Instanciando um objeto TOperação com base na solicitação objOperacao := TFactory.Instance.GetOperacao(strClass); try try // Verificando o método da requisição case Request.MethodType of // Executando operações com método GET mtGet: DoOperation; else // Enviando resposta caso outro método seja solicitado SetErrorResponse('Método não suportado.', 405 {Method not allowed}); end; except // Em caso de erro... on E: Exception do begin SetErrorResponse(E.Message, 500); end; end; finally objOperacao.DisposeOf; end; end; |
Nessa implementação, é possível notar que desenvolvemos três métodos restritos ao método WebModule1DefaultHandlerAction: SetErrorResponse, que tem a função de formatar respostas de erro da API, GetQueryField, que lê o campo solicitado em uma Query String em uma requisição GET, e DoOperation, que, como o nome já propõe, efetua a operação em si. Com isso, nosso código final ficaria desta forma, tanto para o projeto quanto para o WebModule:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | library mod_dscalculator; uses {$IFDEF MSWINDOWS} Winapi.ActiveX, System.Win.ComObj, {$ENDIF } Web.WebBroker, Web.ApacheApp, Web.HTTPD24Impl, Data.DBXCommon, Datasnap.DSSession, WebModule.Container in '..\src\WebModule.Container.pas' {dmDSCalculator: TWebModule}, Classes.Divisao in '..\src\Classes.Divisao.pas', Classes.Multiplicacao in '..\src\Classes.Multiplicacao.pas', Classes.Soma in '..\src\Classes.Soma.pas', Classes.Subtracao in '..\src\Classes.Subtracao.pas'; {$R *.res} // httpd.conf entries: // (* LoadModule dscalculator_module modules/mod_dscalculator.so <Location /api/dscalculator/v1> SetHandler mod_dscalculator-handler </Location> *) // // These entries assume that the output directory for this project is the apache/modules directory. // // httpd.conf entries should be different if the project is changed in these ways: // 1. The TApacheModuleData variable name is changed. // 2. The project is renamed. // 3. The output directory is not the apache/modules directory. // 4. The dynamic library extension depends on a platform. Use .dll on Windows and .so on Linux. // // Declare exported variable so that Apache can access this module. var GModuleData: TApacheModuleData; exports GModuleData name 'dscalculator_module'; procedure TerminateThreads; begin TDSSessionManager.Instance.Free; Data.DBXCommon.TDBXScheduler.Instance.Free; end; begin {$IFDEF MSWINDOWS} CoInitFlags := COINIT_MULTITHREADED; {$ENDIF} Web.ApacheApp.InitApplication(@GModuleData); Application.Initialize; Application.WebModuleClass := WebModuleClass; TApacheApplication(Application).OnTerminate := TerminateThreads; Application.Run; end. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | unit WebModule.Container; interface uses System.Classes, Web.HTTPApp, IPPeerServer, Datasnap.DSCommonServer, Datasnap.DSHTTP, Datasnap.DSHTTPWebBroker, Datasnap.DSServer; type TdmDSCalculator = class(TWebModule) hwdDSCalculator: TDSHTTPWebDispatcher; dssDSCalculator: TDSServer; procedure WebModule1DefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); private { Private declarations } public { Public declarations } end; var WebModuleClass: TComponentClass = TdmDSCalculator; implementation {%CLASSGROUP 'System.Classes.TPersistent'} {$R *.dfm} uses System.SysUtils, Web.WebReq, System.JSON, Helpers.JSONObject, Helpers.Str, Helpers.Integer, Classes.Factory, Classes.Operacao; procedure TdmDSCalculator.WebModule1DefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var strPathInfo: string; bufPathInfo: TArray<string>; strClass : string; objOperacao: TOperacao; procedure SetErrorResponse(const AMessage: string; const AStatusCode: Integer = 400 {Bad Request}); begin with TJSONObject.Create do begin AddBooleanPair('sucesso', False); AddStringPair('mensagem', AMessage); Response.Content := Minify; Response.StatusCode := AStatusCode; Free; end; end; function GetQueryField(const AField: string): string; var strField: string; bufField: TArray<string>; i : Integer; begin Result.Clear; for i := 0 to Request.QueryFields.Count.Pred do begin strField := Request.QueryFields[i]; if strField.Pos(AField + '=') <> 1 then begin Continue; end; bufField := strField.Split('='); Result := bufField[1]; Break; end; end; procedure DoOperation; var strA: string; strB: string; begin // Verifica se os parÂmetros foram passados corretamente if Request.QueryFields.Text.IsEmpty then begin SetErrorResponse('Nenhum parâmetro foi atribuído na rota.'); Exit; end; // Lendo os campos na Query String da rota strA := GetQueryField('a'); strB := GetQueryField('b'); // Verificando se os campos estão vazios ou não foram encontrados if strA.IsEmpty or strB.IsEmpty then begin SetErrorResponse('Parâmetro a ou b não encontrado.'); Exit; end; // Verificando se os campos são valores numéricos if not ((strA.IsNumbersOnly or strA.IsFloat) and (strB.IsNumbersOnly or strB.IsFloat)) then begin SetErrorResponse('Parâmetros a e b devem conter valores numéricos.'); Exit; end; // Atribuindo valores às propriedades do objeto objOperacao.A := strA.ToFloat; objOperacao.B := strB.ToFloat; // Criando o resultado with TJSONObject.Create do try try AddFloatPair('result', objOperacao.Efetuar); Response.Content := Minify; Response.StatusCode := 200; except on E: Exception do begin case E is EZeroDivide of True : raise Exception.Create('O valor de b não pode ser 0 em uma divisão.'); False: raise; end; end; end; finally Free; end; end; begin // Recebendo o PathInfo constante na Request strPathInfo := Request.InternalPathInfo; // Eliminando o primeiro caracter / do PathInfo Delete(strPathInfo, 1, 1); // Dividindo o PathInfo em partes para idenificar a classe solicitada bufPathInfo := strPathInfo.Split('/'); // Atribuindo o Content-Type da resposta como padrão para todas elas Response.ContentType := 'application/json'; // Verificando se o comprimento do caminho está correto // Se não estiver, emita uma mensagem de erro e saia if not bufPathInfo.Count.Equals(3) then begin SetErrorResponse('Caminho para a solicitação inválido.'); Exit; end; // Verificando se a versão da API é a mesma da constante na rota // Se não for, emita uma mensagem de erro e saia if not bufPathInfo[1].Equals('v1') then begin SetErrorResponse('Versão inválida da API.'); Exit; end; strClass := bufPathInfo[2]; // Verificando se a classe chamada na rota está registrada na factory // Se não estiver, emita uma mensagem de erro e saia if not TFactory.Instance.Operacoes.Keys.ToArray.Contains(strClass) then begin SetErrorResponse('Classe %s não encontrada.'.Format([strClass])); Exit; end; // Instanciando um objeto TOperação com base na solicitação objOperacao := TFactory.Instance.GetOperacao(strClass); try try // Verificando o método da requisição case Request.MethodType of // Executando operações com método GET mtGet: DoOperation; else // Enviando resposta caso outro método seja solicitado SetErrorResponse('Método não suportado.', 405 {Method not allowed}); end; except // Em caso de erro... on E: Exception do begin SetErrorResponse(E.Message, 500); end; end; finally objOperacao.DisposeOf; end; end; initialization finalization Web.WebReq.FreeWebModules; end. |
Compilando…
Após toda a implementação, basta compilar o projeto para poder utilizá-lo em um ambiente de testes. O módulo, quando compilado na opção Debug para a plataforma Windows 64-bit, poderá ser localizado no diretório C:\Projetos\Delphi\Datasnap\DSCalculator\bin\Win64\Debug, se mantidas as orientações dadas ao longo do artigo anterior. Mas, fica aqui minha dica: já façam a compilação em modo Release. Nesta técnica de desenvolvimento não há como efetuar o debug do módulo Apache. Caso haja a necessidade de debug, podemos utilizar duas técnicas: pontos de debug que geram um arquivo de log para analisarmos a aplicação e seu comportamento ou construir uma outra aplicação que possamos debugar nossas classes, uma vez que não será necessário debugar o módulo em si.
Em uma oportunidade futura podemos discutir sobre essas técnicas mas, por ora, basta saber que não é possível debugar um módulo Apache.
Tudo compilado. E agora?
Agora que já compilamos nosso módulo Apache, precisamos montar nosso ambiente de testes. Como esse processo é extenso, vamos deixá-lo para a próxima parte do nosso artigo. De qualquer forma, até o momento, já pudemos perceber o quão simples é montar APIs RESTful com Delphi e DataSnap. Nesta técnica conseguimos contornar, inclusive, uma questão técnica do próprio DataSnap, que não nos permitia a utilização de Query Strings para a execução de métodos GET, assim como estabelecia rotas que levam em consideração o nome das classes de métodos utilizadas para o desenvolvimento.
Ah, um arquivo .zip contendo os Class Helpers utilizados no artigo pode ser encontrado aqui.
Então, por ora, ficamos por aqui. Logo mais a parte III do nosso artigo estará no ar. Até mais!
Faça um comentário