Árvore de páginas

A utilização do recurso de @annotations na linguagem TL++ possibilitou a simplificação da escrita para o desenvolvimento de API´s REST, mas ainda é possível desenvolver aplicações sem utilização deste recurso no modo tradicional, trataremos disto nesta documentação.

Inicialização de um serviço REST

Para poder utilizar aplicações REST sem o uso das @annotations, obrigatoriamente é necessário criar um serviço Http por meio de uma função fazendo uso de um objeto JSon. Veremos no exemplo abaixo como fazer isto:

#include "tlpp-core.th"

Function u_fInitService()
 Local oVdrCtrl := VdrCtrl():New() as object
 Local cAppPath := "/examples" as character
 Local nResult := -1 as integer
 Local jConfig := JsonObject():New() as Json
 // Obtem o ambiente atual
 Local cEnv     := GetEnvServer() as character

 //Definição das configurações do HttpServer
 jConfig['HTTPSERVER']                             := JsonObject():New()
 jConfig['HTTPSERVER']['Enable']                   := .T.
 jConfig['HTTPSERVER']['Log']                     := .F.
 jConfig['HTTPSERVER']['Charset']                 := "ISO-8859-1"
 jConfig['HTTPSERVER']['Servers']                 := {"INIT_HTTP_REST"}

 jConfig['INIT_HTTP_REST']                         := JsonObject():New()
 jConfig['INIT_HTTP_REST']['Port']                 := 9995
 jConfig['INIT_HTTP_REST']['HostName']             := "TLPP_REST_SERVER"
 jConfig['INIT_HTTP_REST']['Locations']           := {"HTTP_ROOT_01"}
 jConfig['INIT_HTTP_REST']['ContentTypes']         := "INIT_ContentTypes"

 jConfig['HTTP_ROOT_01']                           := JsonObject():new()
 jConfig['HTTP_ROOT_01']['Path']                   := cAppPath
 jConfig['HTTP_ROOT_01']['RootPath']               := "C:\tlppCore\bin\root\web"
 jConfig['HTTP_ROOT_01']['DefaultPage']           := {"index.html"}
 jConfig['HTTP_ROOT_01']['ThreadPool']             := "INIT_THREAD_POOL_01"

 jConfig['INIT_THREAD_POOL_01']                   := JsonObject():new()
 jConfig['INIT_THREAD_POOL_01']['Environment']     := cEnv
 jConfig['INIT_THREAD_POOL_01']['MinThreads']     := 1
 jConfig['INIT_THREAD_POOL_01']['MaxThreads']     := 4
 jConfig['INIT_THREAD_POOL_01']['MinFreeThreads'] := 0
 jConfig['INIT_THREAD_POOL_01']['GrowthFactor']   := 1
 jConfig['INIT_THREAD_POOL_01']['AcceptTimeout']   := 10000
 jConfig['INIT_THREAD_POOL_01']['InactiveTimeout'] := 30000
 jConfig['INIT_THREAD_POOL_01']['ActiveTimeout']   := 120000
 
 jConfig['INIT_THREAD_POOL_01']['Slaves']         := {"TP_AUX_1", "TP_AUX_2"}
 
 jConfig['TP_AUX_1']                               := JsonObject():new()
 jConfig['TP_AUX_1']['Environment']               := cEnv
 jConfig['TP_AUX_1']['MinThreads']                 := 2
 jConfig['TP_AUX_1']['MaxThreads']                 := 4
 jConfig['TP_AUX_1']['MinFreeThreads']             := 0
 jConfig['TP_AUX_1']['GrowthFactor']               := 1
 jConfig['TP_AUX_1']['AcceptTimeout']             := 10000
 jConfig['TP_AUX_1']['InactiveTimeout']           := 30000
 jConfig['TP_AUX_1']['ActiveTimeout']             := 120000
 
 jConfig['TP_AUX_2']                               := JsonObject():new()
 jConfig['TP_AUX_2']['Environment']               := cEnv
 jConfig['TP_AUX_2']['MinThreads']                 := 2
 jConfig['TP_AUX_2']['MaxThreads']                 := 4
 jConfig['TP_AUX_2']['MinFreeThreads']             := 0
 jConfig['TP_AUX_2']['GrowthFactor']               := 1
 jConfig['TP_AUX_2']['AcceptTimeout']             := 10000
 jConfig['TP_AUX_2']['InactiveTimeout']           := 30000
 jConfig['TP_AUX_2']['ActiveTimeout']             := 120000

 jConfig['INIT_ContentTypes']                     := JsonObject():new()
 jConfig['INIT_ContentTypes']['htm']               := "text/html"
 jConfig['INIT_ContentTypes']['html']             := "text/html"
 jConfig['INIT_ContentTypes']['stm']               := "text/html"
 jConfig['INIT_ContentTypes']['tsp']               := "text/html"
 jConfig['INIT_ContentTypes']['js']               := "text/javascript"
 jConfig['INIT_ContentTypes']['json']             := "application/json;charset=utf-8"
 jConfig['INIT_ContentTypes']['*']                 := "application/octet-stream"

 /* -----------------------------------------------------------
 Aqui é feita a chamada para a função responsável por criar o
 vinculo entre URN´s e as aplicações
 ----------------------------------------------------------- */
 jConfig['INIT_HTTP_REST']['LoadURNs']             := JsonObject():new()
 if !( sLoadURNs(@jConfig['INIT_HTTP_REST']['LoadURNs']) )
   return Nil
 endif
 /*------------------------------------------------------------*/

 nResult := oVdrCtrl:Start(jConfig)
 if ( ValType(nResult) == 'N' .AND. nResult == 0 )
   conout("### Servidor HTTP inicializado com sucesso!")
 else
   conout("### Erro ao iniciar HTTP Server - ret: " + cValToChar(nResult) + " - err: " + cValToChar(oVdrCtrl:nErr) + " desc: '" + cValToChar(oVdrCtrl:cErr) + "'" )
 endif
return nResult

OBS.: Caso já exista algum serviço REST sendo criado por meio de função utilizando-se um JSon, basta incluir a chamada da static sLoadUrn como será visto no item posterior.


Vinculando a URN à API

Como não está sendo utilizado o recurso das @annotations para poder desenvolver API´s REST, é preciso utilizar outra forma para efetuar o vínculo com a URN, isto é feito via função. A mesma é chamada do ponto destacado na função do item anterior, no exemplo citado está sendo feita a chamada para uma função do tipo Static, mas, nada impede que ela seja do tipo User, desde que respeitando o tipo de retorno e as regras de implementações que serão vistas no exemplo a seguir:

//Static Function responsável por criar o nó contendo o vínculo entre as URN´s e as API´s(funções) no objeto jSon que foi recebido como parametro e será utilizado no serviço REST que será executado a partir das definições do mesmo.

Static Function sLoadURNs(jEndpoints)
 Local cDelPath     := "/documentation/noannotation/delete"
 Local cGetPath     := "/documentation/noannotation/get"
 Local cGetParamPath := "/documentation/noannotation/get/*"
 Local cGetClassPath := "/documentation/noannotation/get/class"
 Local cPatchPath   := "/documentation/noannotation/patch"
 Local cPostPath     := "/documentation/noannotation/post"
 Local cPutPath     := "/documentation/noannotation/put"

 if(ValType(jEndpoints) == 'U' .Or. ValType(jEndpoints) != 'J')
   jEndpoints := jsonObject():New()
 endIf

 // Exemplo de verbo com uso de funcao
 jEndpoints[cGetPath]                           := JsonObject():new()
 jEndpoints[cGetPath]['GET']                     := JsonObject():new()
 jEndpoints[cGetPath]['GET']['ProgramType']     := 0
 jEndpoints[cGetPath]['GET']['ClassName']       := ""
 jEndpoints[cGetPath]['GET']['Function']         := "U_getExampleNoAnnotation"
 jEndpoints[cGetPath]['GET']['EndPoint']         := {"documentation", "noannotation", "get"}

 // Para a mesma URL (cGetPath) pode ter mais de um verbo, ja tinha GET adicionando o POST
 jEndpoints[cGetPath]['POST']                   := JsonObject():new()
 jEndpoints[cGetPath]['POST']['ProgramType']     := 0
 jEndpoints[cGetPath]['POST']['ClassName']       := ""
 jEndpoints[cGetPath]['POST']['Function']       := "U_getExampleNoAnnotation"
 jEndpoints[cGetPath]['POST']['EndPoint']       := {"documentation", "noannotation", "get"}

 // Exemplo de path com parametro
 jEndpoints[cGetParamPath]                       := JsonObject():new()
 jEndpoints[cGetParamPath]['GET']               := JsonObject():new()
 jEndpoints[cGetParamPath]['GET']['ProgramType'] := 0
 jEndpoints[cGetParamPath]['GET']['ClassName']   := ""
 jEndpoints[cGetParamPath]['GET']['Function']   := "U_getParamExampleNoAnnotation"
 jEndpoints[cGetParamPath]['GET']['EndPoint']   := {"documentation", "noannotation", "get", ":myparam"}

 // Exemplo de uso com classe ao inves de funcao
 jEndpoints[cGetClassPath]                       := JsonObject():new()
 jEndpoints[cGetClassPath]['GET']               := JsonObject():new()
 // Para indicar que eh uma classe informe que o tipo de programa eh 1
 jEndpoints[cGetClassPath]['GET']['ProgramType'] := 1
 jEndpoints[cGetClassPath]['GET']['ClassName']   := "getClassExampleNoAnnotation"
 jEndpoints[cGetClassPath]['GET']['Function']   := "method_get"
 jEndpoints[cGetClassPath]['GET']['EndPoint']   := {"documentation", "noannotation", "get", "class"}

 // Exemplo com verbo DELETE
 jEndpoints[cDelPath]                           := JsonObject():new()
 jEndpoints[cDelPath]['DELETE']                 := JsonObject():new()
 jEndpoints[cDelPath]['DELETE']['ProgramType']   := 0
 jEndpoints[cDelPath]['DELETE']['ClassName']     := ""
 jEndpoints[cDelPath]['DELETE']['Function']     := "U_deleteExampleNoAnnotation"
 jEndpoints[cDelPath]['DELETE']['EndPoint']     := {"documentation", "noannotation", "delete"}

 // Exemplo com verbo PATCH
 jEndpoints[cPatchPath]                         := JsonObject():new()
 jEndpoints[cPatchPath]['PATCH']                 := JsonObject():new()
 jEndpoints[cPatchPath]['PATCH']['ProgramType'] := 0
 jEndpoints[cPatchPath]['PATCH']['ClassName']   := ""
 jEndpoints[cPatchPath]['PATCH']['Function']     := "U_patchExampleNoAnnotation"
 jEndpoints[cPatchPath]['PATCH']['EndPoint']     := {"documentation", "noannotation", "patch"}

 // Exemplo com verbo POST
 jEndpoints[cPostPath]                           := JsonObject():new()
 jEndpoints[cPostPath]['POST']                   := JsonObject():new()
 jEndpoints[cPostPath]['POST']['ProgramType']   := 0
 jEndpoints[cPostPath]['POST']['ClassName']     := ""
 jEndpoints[cPostPath]['POST']['Function']       := "U_postExampleNoAnnotation"
 jEndpoints[cPostPath]['POST']['EndPoint']       := {"documentation", "noannotation", "post"}

 // Exemplo com verbo PUT
 jEndpoints[cPutPath]                           := JsonObject():new()
 jEndpoints[cPutPath]['PUT']                     := JsonObject():new()
 jEndpoints[cPutPath]['PUT']['ProgramType']     := 0
 jEndpoints[cPutPath]['PUT']['ClassName']       := ""
 jEndpoints[cPutPath]['PUT']['Function']         := "U_putExampleNoAnnotation"
 jEndpoints[cPutPath]['PUT']['EndPoint']         := {"documentation", "noannotation", "put"}
return .T.

function U_getExampleNoAnnotation() as logical
 oRest:updateKeyHeaderResponse("Content-Type", "application/json;charset=utf-8")
return oRest:setStatusResponse(200, '{"tlpp_test":"get"}')

function U_getParamExampleNoAnnotation() as logical
 Local jParam := oRest:getPathParamsRequest() as Json
 Local cParam := "no_param" as character
 if (ValType(jParam) == 'J')
   cParam := jParam["myparam"]
   if (ValType(cParam) != 'C')
     cParam := "invalid_param"
   endif
 endif
 oRest:updateKeyHeaderResponse("Content-Type", "application/json;charset=utf-8")
return oRest:setStatusResponse(200, '{"tlpp_test":"get with param: ' + cValToChar(cParam) + '"}')

function U_deleteExampleNoAnnotation() as logical
 oRest:updateKeyHeaderResponse("Content-Type", "application/json;charset=utf-8")
return oRest:setStatusResponse(200, '{"tlpp_test":"delete"}')

function U_patchExampleNoAnnotation() as logical
 oRest:updateKeyHeaderResponse("Content-Type", "application/json;charset=utf-8")
return oRest:setStatusResponse(200, '{"tlpp_test":"patch"}')

function U_postExampleNoAnnotation() as logical
 oRest:updateKeyHeaderResponse("Content-Type", "application/json;charset=utf-8")
return oRest:setStatusResponse(200, '{"tlpp_test":"post"}')

function U_putExampleNoAnnotation() as logical
 oRest:updateKeyHeaderResponse("Content-Type", "application/json;charset=utf-8")
return oRest:setStatusResponse(200, '{"tlpp_test":"put"}')

class getClassExampleNoAnnotation
 public method new as object
 public method method_get as logical
endclass

method new() as object class getClassExampleNoAnnotation
 ConOut("### Creating class: " + "getClassExampleNoAnnotation")
return self

method method_get() as logical class getClassExampleNoAnnotation
 oRest:updateKeyHeaderResponse("Content-Type", "application/json;charset=utf-8")
return oRest:setStatusResponse(200, '{"tlpp_test_class":"getClassExampleNoAnnotation"}')


OBSERVAÇÃO

As funções, os endpoints e as demais configurações utilizadas nos exemplos vistos aqui, devem ser criados conforme as regras e as necessidades de utilização de cada ambiente e desenvolvedor.

Atenção:

Os objetos JSON retornados por métodos de oRest, como por exemplo oRest:getQueryRequest(), são referências ao objeto existente no REST e não uma cópia.

Existem alguns motivos para ser uma referência, são eles:

  • Melhorar performance do serviço;
  • Economia de memória;
  • Evitar que seja necessário limpar o objeto na saída da implementação do serviço REST.

Portanto, é imprescindível que não se manipule diretamente o Objeto, pois isso irá refletir nas próximas requisições, causando problemas difíceis de serem detectados.

Métodos:

oRest:getPathParamsRequest()
oRest:getQueryRequest()
oRest:getHeaderRequest()
oRest:getThreadPoolTlppData()
oRest:getServerTlppData()
oRest:getThreadPoolUserData()
oRest:getThreadPoolServerUserData()
oRest:getHeaderResponse()

  • Sem rótulos