agentleFS
replybrreplybr/plugadvpl/skills/advpl-webservice/SKILL.md

advpl-webservice

Skill1 starsChanged 6 months agoMITView on GitHub

Web Services em ADVPL — REST (WSRESTFUL, @Get/@Post) e SOAP (WSSERVICE/WSMETHOD/WSDATA). NUNCA RpcSetEnv em REST (regra SEC crítica). Use ao criar API ou integração HTTP no Protheus.

The file

Files
SKILL.md

advpl-webservice — REST e SOAP no Protheus

ADVPL/TLPP suporta duas formas de Web Service:

  • REST — preferida hoje. Sintaxe declarativa com WSRESTFUL + annotations @Get/@Post/@Put/@Delete. Em TLPP, mais limpo ainda com Class ... Inherit FWRest.
  • SOAP — legado, mas vivo. WSSERVICE + WSDATA + WSMETHOD + WSSTRUCT.

O AppServer Protheus expõe ambos via porta HTTP configurada em appserver.ini ([HTTPSERVER], [HTTPV11] para REST).

Quando usar

  • Criar API REST/SOAP no Protheus.
  • Edit em fontes com WSRESTFUL, WSSERVICE, WSMETHOD, WSDATA, WSSTRUCT, @Get, @Post.
  • Integração de Protheus com aplicação externa (mobile, e-commerce, ERP terceiro).
  • Revisão de segurança em endpoint exposto (SEC-001, SEC-002).

REST com WSRESTFUL (TLPP moderno)

#include "tlpp-core.th"
#include "tlpp-rest.th"

@Get(endpoint="/v1/cliente/:codigo", description="Busca cliente por código")
User Function GetCliente()
    Local oResp  := JsonObject():New()
    Local cCod   := oRest:GetUrlParam("codigo")
    Local cError := ""

    If Empty(cCod)
        oRest:SetStatusCode(400)
        oResp["error"] := "Codigo obrigatorio"
        oRest:SetResponse(oResp:ToJson())
        Return .F.
    EndIf

    DbSelectArea("SA1")
    SA1->(DbSetOrder(1))
    If SA1->(DbSeek(xFilial("SA1") + cCod))
        oResp["codigo"] := SA1->A1_COD
        oResp["nome"]   := AllTrim(SA1->A1_NOME)
        oResp["cnpj"]   := SA1->A1_CGC
        oRest:SetStatusCode(200)
    Else
        oRest:SetStatusCode(404)
        oResp["error"] := "Cliente nao encontrado"
    EndIf

    oRest:SetResponse(oResp:ToJson())
Return .T.

REST com WSRESTFUL (estilo classic)

WSRESTFUL XYZCli DESCRIPTION "API de Clientes"

    WSMETHOD GET   DESCRIPTION "Lista clientes"   WSSYNTAX "/api/cli"
    WSMETHOD GET   DESCRIPTION "Busca por codigo" WSSYNTAX "/api/cli/{codigo}"
    WSMETHOD POST  DESCRIPTION "Cria cliente"     WSSYNTAX "/api/cli"

END WSRESTFUL

WSMETHOD GET WSSERVICE XYZCli
    Local cBody := ""
    // ... lógica
    ::SetContentType("application/json")
    ::SetResponse(cBody)
Return .T.

WSMETHOD POST WSSERVICE XYZCli
    Local cInput := ::GetContent()  // ATENÇÃO SEC-002
    // ... lógica com validação
Return .T.

SOAP com WSSERVICE

WSSERVICE XYZSrv DESCRIPTION "Servico XYZ"
    WSDATA cCodCliente AS STRING
    WSDATA oCliente    AS XYZCliRet

    WSMETHOD BuscaCliente DESCRIPTION "Busca cliente por codigo"
END WSSERVICE

WSSTRUCT XYZCliRet
    WSDATA cCodigo AS STRING
    WSDATA cNome   AS STRING
    WSDATA nLimite AS NUMERIC
END WSSTRUCT

WSMETHOD BuscaCliente WSRECEIVE cCodCliente WSSEND oCliente WSSERVICE XYZSrv
    ::oCliente := WSClassNew("XYZCliRet")
    // ... popula
Return .T.

Regra crítica — NUNCA RpcSetEnv em REST (SEC-001 família)

RpcSetEnv é usado para abrir ambiente Protheus (empresa, filial, usuário) em JOB/RPC. Em REST, o framework já entrega o ambiente via autenticação HTTP (token, basic auth) configurada no appserver.ini.

// ERRADO — risco crítico (SEC-001 família)
@Post(endpoint="/v1/pedido")
User Function CriaPedido()
    RpcSetEnv("01", "0101", "admin", "totvs")  // BLOQUEAR
    // ...
Return .T.

// CORRETO — ambiente já vem da requisição autenticada
@Post(endpoint="/v1/pedido")
User Function CriaPedido()
    Local cEmp := cEmpAnt  // já preenchido pelo framework
    Local cFil := cFilAnt  // já preenchido pelo framework
    // ... lógica
Return .T.

Razão: RpcSetEnv hardcoded vaza credenciais e bypassa o login do usuário REST. Auditoria perde rastreio.

Validação de input — SEC-002

::GetContent() (classic) / oRest:GetBodyRequest() (TLPP) retorna string raw do body. Toda lógica que recebe input externo precisa validar:

@Post(endpoint="/v1/cliente")
User Function CriaCli()
    Local cBody := oRest:GetBodyRequest()
    Local oReq  := JsonObject():New()
    Local cErr  := oReq:FromJson(cBody)

    If !Empty(cErr)
        oRest:SetStatusCode(400)
        oRest:SetResponse('{"error":"JSON invalido"}')
        Return .F.
    EndIf

    // Valida campos obrigatórios + tipos
    If Empty(oReq["codigo"]) .Or. ValType(oReq["codigo"]) != "C"
        oRest:SetStatusCode(422)
        oRest:SetResponse('{"error":"campo codigo obrigatorio"}')
        Return .F.
    EndIf

    // Valida tamanho/range
    If Len(AllTrim(oReq["codigo"])) > TamSX3("A1_COD")[1]
        oRest:SetStatusCode(422)
        oRest:SetResponse('{"error":"codigo excede tamanho"}')
        Return .F.
    EndIf

    // ... agora usa cCod com segurança
Return .T.

Violação dispara SEC-002 (crítico).

Content-Type e CORS

oRest:SetContentType("application/json; charset=utf-8")
oRest:SetHeader("Access-Control-Allow-Origin", "*")          // ajuste conforme política
oRest:SetHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
oRest:SetHeader("Access-Control-Allow-Headers", "Content-Type,Authorization")

Endpoints com upload binário usam application/octet-stream. JSON é o default.

Retorno de erro padronizado

Convenção: sempre devolva JSON com error + code:

{
  "error": "Cliente nao encontrado",
  "code": "CLI_NOT_FOUND",
  "details": {"codigo": "999999"}
}

Mapeie status HTTP:

StatusQuando
200OK
201Criado (POST com sucesso)
204OK sem body (DELETE)
400Body malformado (JSON invalido)
401Não autenticado
403Autenticado mas sem permissão
404Recurso não encontrado
409Conflito (registro já existe)
422Validação de negócio falhou
500Erro interno (logge stack, NUNCA exponha em resposta)

Configuração appserver.ini (resumo)

[HTTPV11]
ENABLE=1
PORT=8080
PATH=...

[HTTPURI]
URL=/rest
PrepareIn=99,01
Instances=1,5
CORSEnable=1
AllowOrigin=*
Security=1

Security=1 ativa autenticação obrigatória. Sempre ative em produção.

Anti-padrões

  • RpcSetEnv em REST → SEC-001 família, vaza credenciais.
  • Usar GetContent() direto em SQL sem validar → SEC-002 crítico.
  • Devolver stack trace na response (Errorblock, Try/Catch que vaza interno) → vaza estrutura.
  • Endpoint sem Begin Sequence/Recover em operações críticas → 500 sem log estruturado.
  • Hardcode de credenciais no fonte (basic auth, token, conn string) → SEC-004 warning.
  • Log de body cru com PII em ConOut → SEC-003 warning.
  • Falta de oRest:SetStatusCode → cliente recebe 200 com erro no body (confunde monitoring).

Referência rápida

FuncionalidadeTLPP @annotation / Classic WSMETHOD
GET@Get(endpoint="...") / WSMETHOD GET
POST@Post(endpoint="...") / WSMETHOD POST
PUT@Put / WSMETHOD PUT
DELETE@Delete / WSMETHOD DELETE
URL paramoRest:GetUrlParam("nome")
Query stringoRest:GetQueryParam("nome")
BodyoRest:GetBodyRequest() / ::GetContent()
Header readoRest:GetHeaderRequest("Auth")
Header writeoRest:SetHeader("X-Foo","bar")
StatusoRest:SetStatusCode(404)
Response bodyoRest:SetResponse(cJson)
Content-TypeoRest:SetContentType("application/json")

Comandos plugadvpl relacionados

  • /plugadvpl:find function <WS> — localiza WSRESTFUL/WSSERVICE.
  • A tabela ws_services + ws_structures no índice cataloga endpoints.
  • /plugadvpl:lint <arq> — verifica SEC-001, SEC-002 e regras correlatas.

Referência profunda

Para detalhes completos (~1.5k linhas), consulte reference.md ao lado deste arquivo:

  • Anatomia completa de WSRESTFUL/WSSERVICE/WSMETHOD/WSDATA/WSSTRUCT.
  • Configuração detalhada de appserver.ini para REST + SOAP (HTTPV11, HTTPURI, Security, CORS).
  • Autenticação Basic/Bearer/OAuth2 e integração com RestAuthenticator do framework.
  • Catálogo de métodos de oRest (TLPP moderno) e ::Self (classic) com exemplos.
  • Padrões de paginação, streaming, upload/download binário em REST.
  • Documentação automática via annotations + geração de OpenAPI/Swagger.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.