Re: [cf-brasil] Boas Práticas para a elaboração de Serviços REST

131 views
Skip to first unread message

Hermes Siqueira

unread,
May 17, 2021, 8:16:04 PM5/17/21
to cfbr...@googlegroups.com
Christian,
Como aprendiz de feiticeiro, fico muito grato pelo conhecimento compartilhado.
Parabéns pela iniciativa.

Em dom., 9 de mai. de 2021 às 03:35, Christian Figueiredo <scei...@gmail.com> escreveu:

Olá pessoal. Como estão?

Estava estudando sobre status HTTP e percebi que podemos aproveitar muito mais esse recurso para construir as respostas REST, principalmente quando é preciso desenvolver serviços REST pagos ou que necessitem de login, evitando exigir o famoso token por URL ou Form Field.



1) Árvore de Decisão:

É possível criar uma metodologia segura que evita entregar o conteúdo antes de verificar todas as possibilidades de autenticação. E essa resposta você aplica nas chaves status e content do RestSetResponse. Uma dica importante: não é preciso informar a descrição do status HTTP no content, pois a própria descrição do status é retornada na tela do browser, no fetch do JavaScript ou na resposta do cURL que pode ser usado no Prompt de Comando, no PowerShell ou no Prompt do Git. o cURL e o fetch do JavaScript funcionam como o CFHTTP. Se você ainda não usou o fetch, pense que é muito mais simples que o ajax do jQuery. O que você pode colocar no content é uma justificativa para esclarecer o motivo de retornar o tal status do HTTP. Esse content aparecerá na chave Filecontent do retorno da API pelo CFHTTP ou pelo fetch do JavaScript. Para entender melhor as chamadas cURL, você pode assistir esse tutorial: https://www.youtube.com/watch?v=7XUibDYw4mc. E se quiser entender melhor sobre a estrutura do HTTP, você pode assistir esse Tutorial: https://www.youtube.com/watch?v=iYM2zFP3Zn0. Se você quer entender como funciona o fetch, acesse essa dica da hcode: https://www.youtube.com/watch?v=Pi6wkdU2vR4.

+ Acesso à API

  - Se o Usuário NÃO possuir Credencial no Serviço REST, apresentar o Status 403 (Forbidden)

  + Se o Usuário possuir Credencial no Serviço REST

    + Se o Serviço REST for gratuíto

      - Se a Autenticação NÃO for sucedida, apresentar o Status 401 (Unauthorized)

      + Se a Autenticação for sucedida

        - Se o usuário estiver bloqueado, apresentar o 401 (Unauthorized)

        + Se o usuário estiver liberado

          - Se o Serviço REST NÃO tiver limite de acesso, processar a API... Fim!

          + Se o Serviço REST tiver limite de acessos

            - Se o Acesso NÃO estiver dentro do limite, apresentar o Status 429 (Too Many Requests)

            - Se o Acesso estiver dentro do limite, processar a API... Fim!

    + Se o Serviço REST tiver custo (assinatura, por exemplo)

      - Se a Autenticação NÃO for sucedida, apresentar o Status 401 (Unauthorized)

      + Se a Autenticação for sucedida

        - Se o usuário estiver bloqueado, apresentar o 401 (Unauthorized) e suspender a assinatura

        + Se o usuário estiver liberado

          - Se o pagamento NÃO estiver em dia, apresentar o Status 402 (Payment Required)

          + Se o pagamento estiver em dia

              - Se o Serviço REST NÃO tiver limite de acesso, processar a API... Fim!

              + Se o Serviço REST tiver limite de acesso

                - Se o Acesso NÃO estiver dentro do limite, apresentar o Status 429 (Too Many Requests)

                - Se o Acesso estiver dentro do limite, processar a API... Fim!

 

 

2) Efetuar o login:

É possível efetuar o login durante uma chamada REST. Quando você faz uma chamada REST usando a tag CFHTTP, é possivel informar o username e o password. A função REST recebe esses dados no escopo getHTTPRequestData(). Se você fizer um dump dessa função, verá que a struct possui 2 chaves: headers e content. No headers terá uma chave chamada Authorization. Nessa chave terá um valor escrito Basic [criptografia do login]. Removendo o texto "Basic " (é preciso tirar o espaço após o Basic também), aplique o código ToString(ToBinary("[criptografia do login]")) que você verá o login no seguinte formato [usuário]:[senha]. Caso o login não tenha êxito ou a chave Authorization não esteja em headers, você retorna o status 401 (Unauthorized ou Desautorizado) na função REST!

 


3) Receber parâmetros JSON pelo body:

Métodos POST, PUT e DELETE aceitam receber parâmetros JSON pelo Body. Os parâmetros JSON enviados pelo Body ajudam a receber um pacote de informações estruturado de forma segura (afinal, é só aplicar o DeserializeJSON para transformar em struct), em vez do solicitante enviar várias informações via URL ou Form Field. Às vezes, é preciso receber uma lista de produtos contendo peso, cor de preferência, preço promocional e alguma observação. Enviar um JSON via URL ou Form Field não deve ser muito adequado. Não é mesmo? Indignante é saber que a documentação do ColdFusion sobre REST não explica sobre como receber esse pacote de informações JSON pelo Body. E é tão simples que você vai rir do quanto nossa ferramenta de trabalho é poderosa. Essa informação está na chave content do escopo getHTTPRequestData(). Por essa você não esperava. Não é mesmo?

 

4) Pelo amor de Deus! Não use o evento onCFCRequest!!! Use o onRESTRequest se necessário...

Quando eu comecei a aprender REST, criei meu primeiro CFC, registrei o REST Service no Administrador do ColdFusion, fiz minha primeira chamada com CFHTTP e me indignei com o meu primeiro retorno: O status 204 (No Content), ou seja, NADA!!!!!

Busquei ajuda de tudo quanto é forma! Comprei a apostila do Adam Tuttle (muito boa, mas apenas conceitual), pedi ajuda ao Fabiano Pechibella e ao Robson Cabral que me indicou o Vinicius Perdigão (Fera em API REST)... Ninguém achava o maldito erro! Depois de umas semanas quebrando a cabeça, resolvi comentar todos os eventos do Application.cfc e (até que enfim) o tão sonhado retorno apareceu!!!!!

Com o ColdFusion 2018 o evento onRESTRequest funciona muito bem, mas foi extinto na edição 2021 que agora causa a mesma dor de cabeça do onCFCRequest, ou seja, retorna o status 204 (No Content) e não haja Cristo que o faça mudar de idéia!!!!!!

Se você usa o ColdFusion 2018, segue a versão CFSCRIPT do evento onRESTRequest para te ajudar a monitorar qual CFC foi chamado, sua função e os seus parâmetros. Basta você incluí-la no seu Application.cfc.

public any function onRESTRequest(required string cfcname,required string method,required struct args){

    local['s_return']=null;

    cfinvoke(component=arguments.cfcname,method=arguments.method,argumentCollection=arguments.args,returnVariable='local.s_return');

    return local['s_return'];

}

 

Se você quiser monitorar todos os erros dos seus eventos no Application.cfc, aí vai uma dica de como manipular o evento onError:

public void function onError(required any exception,required string eventName){

    local['s_event']={

        'onApplicationStart'='as'

    ,   'onRequestStart'='rs'

    ,   'onRESTRequest'='rr'

    ,   'onPageLoading'='pl'

    ,   'onRequestEnd'='re'

    ,   'onAbort'='ab'

    ,   'onApplicationEnd'='ae'

    ,   'onError'='er'

    };

    arguments['eventName']=((yesNoFormat(len(trim(arguments['eventName']))))?(arguments['eventName']):('onPageLoading'));

    local['s_timeZone']={

        'v_UTC'=duplicate(getTimeZoneInfo()['utcTotalOffset'])

    ,   'v_local'=duplicate(getTimeZoneInfo()['utcTotalOffset'])*(-1)

    };

    local['v_folder']=expandPath('/')&'debug\'&'_'&dateFormat(dateAdd('s',local['s_timeZone']['v_UTC'],now()),'yyyy-mm-dd');

    if(not(directoryExists(local['v_folder']))){

        directoryCreate(local['v_folder']);

    }

    local['q_listErrorFiles']=directoryList(path=local['v_folder'],listInfo='query');

    local['v_file']=timeFormat(dateAdd('s',local['s_timeZone']['v_UTC'],now()),'HH-mm-ss-lll');

    local['v_simultaneous']=0;

    local['s_query']={

        'v_sql'="

            select

                1

            from [local].[q_listErrorFiles]

            where name=:v_errorFileName

            ;"

    ,   's_params'={

            'v_errorFileName'={

                'cfsqltype'='cf_sql_varchar'

            ,   'value'='_'&local['v_file']&toString(numberFormat(local['v_simultaneous'],'000'))&'_'&local['s_event'][arguments['eventName']]&'.html'

            }

        }

    };

    local['q_getErrorFile']=queryExecute(sql=local['s_query']['v_sql'],params=local['s_query']['s_params'],options={'DBType'='query'});

    while(local['q_getErrorFile'].recordCount>0){

        local['v_simultaneous']=local['v_simultaneous']+1;

        local['s_query']={

            'v_sql'="

                select

                    1

                from [local].[q_listErrorFiles]

                where name=:v_errorFileName

                ;"

        ,   's_params'={

                'v_errorFileName'={

                    'cfsqltype'='cf_sql_varchar'

                ,   'value'='_'&local['v_file']&toString(numberFormat(local['v_simultaneous'],'000'))&'_'&local['s_event'][arguments['eventName']]&'.html'

                }

            }

        };

        local['q_getErrorFile']=queryExecute(sql=local['s_query']['v_sql'],params=local['s_query']['s_params'],options={'DBType'='query'});

    }

    local['v_file']=local['v_folder']&'\'&'_'&local['v_file']&toString(numberFormat(local['v_simultaneous'],'000'))&'_'&local['s_event'][arguments['eventName']]&'.html';

    fileWrite(local['v_file'],'','utf-8');

    writeDump(var=getHTTPRequestData(),label='getHTTPRequestData()',format='html',output=local['v_file']);

    writeDump(var=arguments,label='arguments',format='html',output=local['v_file']);

    if(isDefined('variables')){

        writeDump(var=variables,label='variables',format='html',output=local['v_file']);

    }

    writeDump(var=cgi,label='cgi',format='html',output=local['v_file']);

    writeDump(var=server,label='server',format='html',output=local['v_file']);

    if(isDefined('application')){

        writeDump(var=application,label='application',format='html',output=local['v_file']);

    }

    writeDump(var=cookie,label='cookie',format='html',output=local['v_file']);

    writeDump(var=url,label='url',format='html',output=local['v_file']);

    if(isDefined('form')){

        writeDump(var=form,label='form',format='html',output=local['v_file']);

    }

    return;

}

 

Dica: você pode personalizar o diretório onde essa função deve gerar os arquivos HTML na linha 17, ou seja, essa linha:

local['v_folder']=expandPath('/')&'debug\'&'_'&dateFormat(dateAdd('s',local['s_timeZone']['v_UTC'],now()),'yyyy-mm-dd');

 

Repare que eu coloquei o nome do diretório de debug.

O que essa função faz? Cria uma pasta de arquivo dentro de debug para cada dia que os erros acontecem no formato _AAAA_MM_DD. Cada HTML será nomeado com o exato momento da ocorrência do erro (no formato _ev_HH_mm_ss_lllççç.html)

ev corresponde ao evento no Application.cfc. Você pode personalizar ao seu gosto entre as linhas 3 e 10...

ççç corresponde ao complemento dos millisegundos para evitar conflito quando houver um threshold (uma geração excessiva de processos no mesmo instante). Nesse HTML você terá todos os Dumps necessários para averiguar o que aconteceu.

Outra dica interessante: essa função está preparada para gerar HTML's no fuso horário de Greenwich (GMT+00:00), pois eu tive que adaptar meu projeto hospedado na HOSTEK. Se você trabalha com servidores próprios e quer essa função em seu horário local, eu adaptei o onError para você... Lembre-se de trocar o diretório ao seu gosto como informei anteriormente (agora na linha 13):

public void function onError(required any exception,required string eventName){

    local['s_event']={

        'onApplicationStart'='as'

    ,   'onRequestStart'='rs'

    ,   'onRESTRequest'='rr'

    ,   'onPageLoading'='pl'

    ,   'onRequestEnd'='re'

    ,   'onAbort'='ab'

    ,   'onApplicationEnd'='ae'

    ,   'onError'='er'

    };

    arguments['eventName']=((yesNoFormat(len(trim(arguments['eventName']))))?(arguments['eventName']):('onPageLoading'));

    local['v_folder']=expandPath('/')&'debug\'&'_'&dateFormat(now(),'yyyy-mm-dd');

    if(not(directoryExists(local['v_folder']))){

        directoryCreate(local['v_folder']);

    }

    local['q_listErrorFiles']=directoryList(path=local['v_folder'],listInfo='query');

    local['v_file']=timeFormat(now(),'HH-mm-ss-lll');

    local['v_simultaneous']=0;

    local['s_query']={

        'v_sql'="

            select

                1

            from [local].[q_listErrorFiles]

            where name=:v_errorFileName

            ;"

    ,   's_params'={

            'v_errorFileName'={

                'cfsqltype'='cf_sql_varchar'

            ,   'value'='_'&local['v_file']&toString(numberFormat(local['v_simultaneous'],'000'))&'_'&local['s_event'][arguments['eventName']]&'.html'

            }

        }

    };

    local['q_getErrorFile']=queryExecute(sql=local['s_query']['v_sql'],params=local['s_query']['s_params'],options={'DBType'='query'});

    while(local['q_getErrorFile'].recordCount>0){

        local['v_simultaneous']=local['v_simultaneous']+1;

        local['s_query']={

            'v_sql'="

                select

                    1

                from [local].[q_listErrorFiles]

                where name=:v_errorFileName

                ;"

        ,   's_params'={

                'v_errorFileName'={

                    'cfsqltype'='cf_sql_varchar'

                ,   'value'='_'&local['v_file']&toString(numberFormat(local['v_simultaneous'],'000'))&'_'&local['s_event'][arguments['eventName']]&'.html'

                }

            }

        };

        local['q_getErrorFile']=queryExecute(sql=local['s_query']['v_sql'],params=local['s_query']['s_params'],options={'DBType'='query'});

    }

    local['v_file']=local['v_folder']&'\'&'_'&local['v_file']&toString(numberFormat(local['v_simultaneous'],'000'))&'_'&local['s_event'][arguments['eventName']]&'.html';

    fileWrite(local['v_file'],'','utf-8');

    writeDump(var=getHTTPRequestData(),label='getHTTPRequestData()',format='html',output=local['v_file']);

    writeDump(var=arguments,label='arguments',format='html',output=local['v_file']);

    if(isDefined('variables')){

        writeDump(var=variables,label='variables',format='html',output=local['v_file']);

    }

    writeDump(var=cgi,label='cgi',format='html',output=local['v_file']);

    writeDump(var=server,label='server',format='html',output=local['v_file']);

    if(isDefined('application')){

        writeDump(var=application,label='application',format='html',output=local['v_file']);

    }

    writeDump(var=cookie,label='cookie',format='html',output=local['v_file']);

    writeDump(var=url,label='url',format='html',output=local['v_file']);

    if(isDefined('form')){

        writeDump(var=form,label='form',format='html',output=local['v_file']);

    }

    return;

}

 

Juntando um com o outro, você pode incluir a linha de debug no evento onRESTRequest:

public any function onRESTRequest(required string cfcname,required string method,required struct args){

    local['s_return']=null;

    cfinvoke(component=arguments.cfcname,method=arguments.method,argumentCollection=arguments.args,returnVariable='local.s_return');

    onError(exception=duplicate(local),eventName='onRESTRequest');

    return local['s_return'];

}

 

Se você NÃO for usar o evento onRESTRequest, você pode catalogar suas próprias funções REST! Quer saber como? incluindo essa função GET no fim do seu CFC!

remote void function f_scan() httpMethod='GET' restPath='/scan' produces='application/json' displayName='Scaneando o REST desse CFC...' hint='Esta função serve para catalogar as funções REST desse Component...'{

    try{

        local.s_return={

            status=200

        ,   content=serializeJSON(data={'s_structure'=variables,'s_info'=getHTTPRequestData()})

        };

    }catch(any s_error){

        local.s_return={

            status=500

        ,   content=serializeJSON(data={'v_result'='Ocorreu um erro interno!'})

        };

    }

    restSetResponse(local.s_return);

}

 

Ao fazer a chamada usando o CFHTTP, é só fazer o DeserializeJSON e o CFDUMP do retorno para se surpreender com o resultado... Se você tentar usar essa função contendo o evento onRESTRequest, vai dar erro! Ou um, ou outro!

 

 

5) Nunca criou uma API REST na vida? Não tem problema!

Você pode criar um CFC com o REST mais basicão da história! Depois é só implementar suas idéias e seus processos... Nesse exemplo, crie seu diretório com nome rest na home do seu site, depois outro diretório dentro do diretório rest com o nome do Serviço REST que você deseja. Por fim, crie seu arquivo CFC dentro da pasta do Serviço REST com nome first.cfc para o seu primeiro Componente REST!

Recomendação: se você não tem idéia de qual nome para o Serviço REST deve criar, use como nomenclatura a sua versão! Pode ser apenas o número 1 ou o v1. Depois vem o 2, o 3 e assim por diante... Dentro dessa pasta 1, você pode colocar todos os microsserviço que você quiser! Pode ser um cadastro de clientes, a gestão de seu catálogo, a automação de suas ordens de serviço (requisições) com progresso e acompanhamento... Enfim, o que você quiser! Cada microsserviço é uma pasta dentro da pasta 1 com os arquivos CFC referentes e cada CFC contém os seus manipuladores CRUDs! Create para o POST, Read para o GET, Update para o PUT e o Delete... Delete para o que????? Ah, vá!

A nomenclatura do Serviço REST nesse modelo vai funcionar para organizar o endereço da seguinte forma: http://[subdominio.]dominio.com.br/rest/1/first/[função REST][/[identificador]]

A [função REST] será a referência à cada função do CFC a ser determinada pela sua propriedade restPath. Já o first nesse endereço será definido na propriedade restPath do componente como você verá na primeira linha do arquivo first.cfc. Tudo isso logo abaixo...

Por fim, é obrigatório colocar o /rest entre o domínio e o nome do seu Serviço REST. É convenção do ColdFusion...

Dica importante: Ao criar o seu Serviço REST usando a função restInitApplication (ao ver logo adiante), você pode definir se seu Serviço REST pode ser padrão ou não. Se você definir como padrão, encurtará sua URL, deixando-a da seguinte forma: http://[subdominio.]dominio.com.br/rest/first/[função REST][/[identificador]]

Repare que o /1 referente ao nome do Serviço REST foi removido, pois pode ser omitido por conta de ser uma aplicação padrão! Mesmo assim, pode ser incluído normalmente.

O ColdFusion permite a definição de um Serviço REST padrão POR HOST! Portanto, é preciso definir o Host na função restInitApplication. Você verá com os comentários logo abaixo.

O que você deve colocar no seu primeiro arquivo CFC chamado first.cfc? Esse código BASICÃO abaixo que tem mais comentários do que comando:

component rest=true restPath='/first'{

    pageEncoding 'utf-8';

 

    remote void function f_getFromParams(required string v_url_param restArgName='urlparam' restArgSource='query', required string v_form_param restArgName='formparam' restArgSource='form') httpMethod='GET' restPath='/' consumes='application/json' produces='application/json' diaplayName='A descrição da sua função GET.' hint='Uma explicação detalhada sobre sua função GET que recebe parâmetros da URL e do Form.'{

        try{

            local['s_return']={

                'status':200

            ,   'content':serializeJSON(data=arguments)

            };

        }catch(any s_error){

            local['s_return']={

                'status':500

            ,   'content':serializeJSON(data={'error':duplicate(s_error)})

            };

        }

        restSetResponse(local['s_return']);

    }

 

    remote void function f_getFromPath(required string v_path_param restArgName='pathparam' restArgSource='path') httpMethod='GET' restPath='/{pathparam}' consumes='application/json' produces='application/json' diaplayName='A descrição da sua função GET.' hint='Uma explicação detalhada sobre sua função GET que recebe parâmetro do path. Esse parâmetro do path é conhecido como identificador.'{

        try{

            local['s_return']={

                'status':200

            ,   'content':serializeJSON(data=arguments)

            };

        }catch(any s_error){

            local['s_return']={

                'status':500

            ,   'content':serializeJSON(data={'error':duplicate(s_error)})

            };

        }

        restSetResponse(local['s_return']);

    }

 

    remote void function f_postWithParams(required string v_url_param restArgName='urlparam' restArgSource='query', required string v_form_param restArgName='formparam' restArgSource='form') httpMethod='POST' restPath='/parametrizado' consumes='application/json' produces='application/json' diaplayName='A descrição da sua função POST.' hint='Uma explicação detalhada sobre sua função POST com parâmetros de URL e Form (além do JSON do body) que jamais precisa de identificador, pois os dados a serem registrados são novos!'{

        try{

            local['s_content']={

                's_arguments'=duplicate(arguments)

            ,   's_content'=duplicate(getHTTPRequestData()['content'])?:{} // nesta linha coloquei um Elvis operator (?:) para assegurar que um valor seja atribuído se a chave 'content' não existir no escopo getHTTPRequestData()

            };

            local['s_return']={

                'status':200

            ,   'content':serializeJSON(data=local['s_content'])

            };

        }catch(any s_error){

            local['s_return']={

                'status':500

            ,   'content':serializeJSON(data={'error':duplicate(s_error)})

            };

        }

        restSetResponse(local['s_return']);

    }

 

    remote void function f_postWithoutParams() httpMethod='POST' restPath='/' consumes='application/json' produces='application/json' diaplayName='A descrição da sua função POST.' hint='Uma explicação detalhada sobre sua função POST apenas com o parâmetro JSON do body que jamais precisa de identificador, pois os dados a serem registrados são novos!'{

        try{

            local['s_return']={

                'status':200

            ,   'content':serializeJSON(data=duplicate(getHTTPRequestData()['content'])?:{}) // nesta linha coloquei um Elvis operator (?:) para assegurar que um valor seja atribuído se a chave 'content' não existir no escopo getHTTPRequestData()

            };

        }catch(any s_error){

            local['s_return']={

                'status':500

            ,   'content':serializeJSON(data={'error':duplicate(s_error)})

            };

        }

        restSetResponse(local['s_return']);

    }

 

    remote void function f_putWithParams(required string v_path_param restArgName='pathparam' restArgSource='path', required string v_url_param restArgName='urlparam' restArgSource='query', required string v_form_param restArgName='formparam' restArgSource='form') httpMethod='PUT' restPath='/{pathparam}/parametrizado' consumes='application/json' produces='application/json' diaplayName='A descrição da sua função PUT.' hint='Uma explicação detalhada sobre sua função PUT com parâmetros do URL e do FORM (além do JSON do body) que sempre precisa de identificador, pois é preciso referenciar onde os dados a serão atualizados...'{

        try{

            local['s_content']={

                's_arguments'=duplicate(arguments)

            ,   's_content'=duplicate(getHTTPRequestData()['content'])?:{} // nesta linha coloquei um Elvis operator (?:) para assegurar que um valor seja atribuído se a chave 'content' não existir no escopo getHTTPRequestData()

            };

            local['s_return']={

                'status':200

            ,   'content':serializeJSON(data=local['s_content'])

            };

        }catch(any s_error){

            local['s_return']={

                'status':500

            ,   'content':serializeJSON(data={'error':duplicate(s_error)})

            };

        }

        restSetResponse(local['s_return']);

    }

 

    remote void function f_putWithoutParams(required string v_path_param restArgName='pathparam' restArgSource='path') httpMethod='PUT' restPath='/{pathparam}' consumes='application/json' produces='application/json' diaplayName='A descrição da sua função PUT.' hint='Uma explicação detalhada sobre sua função PUT apenas com o parâmetro JSON do body que sempre precisa de identificador, pois é preciso referenciar onde os dados a serão atualizados...'{

        try{

            local['s_content']={

                's_arguments'=duplicate(arguments)

            ,   's_content'=duplicate(getHTTPRequestData()['content'])?:{} // nesta linha coloquei um Elvis operator (?:) para assegurar que um valor seja atribuído se a chave 'content' não existir no escopo getHTTPRequestData()

            };

            local['s_return']={

                'status':200

            ,   'content':serializeJSON(data=local['s_content'])

            };

        }catch(any s_error){

            local['s_return']={

                'status':500

            ,   'content':serializeJSON(data={'error':duplicate(s_error)})

            };

        }

        restSetResponse(local['s_return']);

    }

 

    remote void function f_deleteVarious(required string v_url_param restArgName='urlparam' restArgSource='query', required string v_form_param restArgName='formparam' restArgSource='form') httpMethod='DELETE' restPath='/varios' consumes='application/json' produces='application/json' diaplayName='A descrição da sua função DELETE.' hint='Uma explicação detalhada sobre sua função DELETE com parâmetros do URL e do Form, pois você pode referenciar o(s) critério(s) de um ou mais registros a serem excluídos.'{

        try{

            local['s_return']={

                'status':200

            ,   'content':serializeJSON(data=arguments)

            };

        }catch(any s_error){

            local['s_return']={

                'status':500

            ,   'content':serializeJSON(data={'error':duplicate(s_error)})

            };

        }

        restSetResponse(local['s_return']);

    }

 

    remote void function f_deleteOne(required string v_path_param restArgName='pathparam' restArgSource='path') httpMethod='DELETE' restPath='/{pathparam}' consumes='application/json' produces='application/json' diaplayName='A descrição da sua função DELETE.' hint='Uma explicação detalhada sobre sua função DELETE que recebe parâmetro do path. Esse parâmetro do path é conhecido como identificador, pois é preciso referenciar qual registro será excluído.'{

        try{

            local['s_return']={

                'status':200

            ,   'content':serializeJSON(data=arguments)

            };

        }catch(any s_error){

            local['s_return']={

                'status':500

            ,   'content':serializeJSON(data={'error':duplicate(s_error)})

            };

        }

        restSetResponse(local['s_return']);

    }

}

 

Depois, registre seu Serviço REST usando essa função:

restInitApplication(expandPath('\rest\1'),'1',{

    'isDefault'=false // é possível definir uma aplicação padrão POR HOST ([subdominio.]dominio.com.br, por exemplo)... Ao definir como padrão, não é preciso incluir o nome do REST Service no URL de chamada

,   'skipCFCWithError'=false // seria bom colocar TRUE na produção para evitar a vergonha do status 500 (Internal Server Error) na chamada. No lugar, aparecerá o status 404 (File Not Found)

,   'autoRegister'=true // o servidor não guardará o código do CFC no cache. Significa que a função encapsulada será carregada toda vez que for invocada. Isso acarreta em perda de desempenho...

,   'useHost'=true // recomenda-se atribuir o Host para que nenhum outro site no mesmo servidor de Web esteja apto para receber a chamada

,   'host'='[seusubdominio.]sudominio.com.br' // se nao atribuir o Host, o próprio Servidor o adicionará no momento de criação da aplicação, pois usará o Host do link da página CFM chamada... Recomenda-se usar para definir qual SubDomínio usará e se obrigatoriamente usará o WWW também

});

 

Uma dica importante: você percebeu que, para cada parâmetro da função, foram colocadas as propriedades restArgName restArgSource? A propriedade restArgName é a referência ao parâmetro que será chamada no CFHTTPPARAM. Já o restArgSource é a forma que o parâmetro será enviado ao Serviço REST. No caso de URL, será incrementado ao endereço como uma query string. Um exemplo de query string é http://[seusubdominio.]seudominio.com.br/pagina.cfm?param1=valor1&param2=valor2&paramN=valorN. A parte sublinhada desse endereço é a query string. No caso de FormField, quando o envio é feito por HTTPS, os dados enviados acabam se tornando seguros por causa da criptografia gerada pelo certificado do site. Quando o envio é feito por HTTP, não há segurança. No caso da query string, os dados acabam sendo exlícitos em ambas as situações. Já no caso de Body, a informação é enviada pelo Filecontent. Quando o envio é feito por HTTPS, os dados acabam possuindo o mesmo nível de segurança do FormField devido à criptografia gerada pelo certificado do site. Outro detalhe importante é a referência do parâmetro no Path. Um parâmetro de Path significa que ele será incluído no meio ou no fim do endereço do Serviço REST, não levando em conta a query string. Recomenda-se que use um dado simples para os parâmetros de Path como o ID do ítem a ser consultado (método GET) atualizado (método PUT) ou removido (método DELETE). Esse ID pode ser o e-mail, o número do registro no Banco de Dados ou mesmo o código gerado pelo CreateUUID() na chamada POST feita anteriormente para criar esse registro. Por isso costumo chamar esse tipo de parâmetro de Identificador.

um exemplo de chamada com parâmetro de path está no método PUT: http://[seusubdominio.]seudominio.com.br/rest/1/first/[identificador]. Se esse identificador for referente ao usuário, pode ser o e-mail: http://[seusubdominio.]seudominio.com.br/rest/1/first/usu...@seudominio.com.br. É mais comum usar uma identificação numérica como o ítem 46283: http://[seusubdominio.]seudominio.com.br/rest/1/first/46283.

 

Por fim, você referencia o parâmetro de Path definido pelo restArgName na propriedade restPath da função, incluindo a barra (/) e a referência definida no restArgName do parâmetro que vai receber o valor no endereço da URL (nesse caso pathparam) entre as chaves { e }. Por exemplo: /{pathparam}. Em um restPath você pode colocar quantos parâmetros de Path você quiser, ídem para os níveis de roteamento, ou seja '/nivel1/nivel2/nivel3' e assim por diante. Da mesma forma, você pode incluir quantos níveis quiser para o restPath do componente. A única coisa que não tem como fazer é incluir um parâmetro de path no restPath do componente... Dãããã!!!

Outra dica importante: você não precisa colocar um parâmetro de Path sempre no fim! Você pode colocar entre 2 níveis de roteamento. Exemplo: '/nivel1/{pathparam}/nivel2'... Use a sua imaginação para definir as nomenclaturas! não tenha medo de errar!

Você deve estar se perguntando sobre a maneira do servidor ColdFusion identificar a função REST pelo endereço... Te respondo que não é apenas o endereço, mas a combinação do Método (GET|POST|PUT|DELETE) com o endereço.

Como você vai acessar seu primeiro Serviço REST? Criando uma página CFM contendo a tag CFHTTP:

cfhttp(method='GET|POST|PUT|DELETE' url='[seusubdominio.]seudominio.com.br/rest/[REST Service]/[restPath do CFC criado na primeira linha]/[funcao do CFC conforme o seu restPath][/[identificador, se necessário]]' result='variables.s_result'){

    cfhttpparam(type='URL|FormField|Body' name='[nome referenciado no restArgName]' value='[o valor que você quiser, respeitando o tipo de dado]');

}

writeDump(var=variables['s_result']); // o resultado desejado estará na chave Filecontent

 

Vamos tentar nesse caso com a função GET e a URL [seusubdominio.]seudominio.com.br/rest/1/first/ e com um parâmetro de URL e outro de Form? Depois com a URL [seusubdominio.]seudominio.com.br/rest/1/first/[identificador] onde o identificador é a sua idade? E o seu primeiro nome? E o seu endereço de E-Mail?

Vamos fazer um POST? Crie uma varável de estrutura qualquer em uma página CFM, depois use a função SerializeJSON para transformar essa estrutura em um texto JSON. Quanto mais complexa essa struct for, melhor! Que tal extrair uma query do Banco de Dados? Pegue o resultado dessa Query e a coloque no SerializeJSON, incluindo o parâmetro queryFormat='struct'...

writeOutput(string=serializeJSON(data=[sua_query],queryFormat='struct'));

 

Copie o texto JSON e coloque-o na sua chamada CFHTTP dessa forma:

cfhttp(method='POST|PUT|DELETE' url='[seusubdominio.]seudominio.com.br/rest/1/' result='variables.s_result'){

    cfhttpparam(type='Body' value='[o texto JSON extraído de sua struct ou query]');

}

writeDump(var=variables['s_result']); // o resultado desejado estará na chave Filecontent

 

Depois faça (praticamente) o mesmo com a função que exige parâmetros da URL e do Form, inclindo a palavra parametrizando no endereço do CFHTTP que é o restPath da função f_postWithParams, depois faça para ambas as funções de PUT e de DELETE. Se tiver alguma dúvida, responda essa mensagem que eu buscarei te explicar...

Se quiser testar o usuário e senha, adicione as propriedades username password no cabeçalho do CFHTTP com os valores que você quiser! Depois adicione a chave s_login na estrutura local['s_content'] da função f_postWithParams recebendo getHTTPRequestData()['headers']['Authorization']?:'' (com o Elvis operator ?:) e veja o resultado da chave Authorization na chamada. Por fim, siga as instruções da seção 2) para extrair o usuário e senha. Você vai se surpreender...

Basicamente é fazer local['s_content']['s_login']=ReplaceNoCase(local['s_content']['s_login'],'Basic ','') e depois local['s_content']['s_login']=ToString(ToBinary(local['s_content']['s_login']));

Lembre-se de colocar um IF antes para os casos que o local['s_content']['s_login'] esteja com a string vazia (''), ou seja. Uma chamada sem username, nem password no CFHTTP.

Depois me dica se você não se surpreendeu...


Dica importante: o JSON que você quer enviar no Body só serve para os métodos POSTPUT DELETE. Não adianta tentar enviar no GET que a chave content jamais aparecerá no GetHttpRequestData().

Observação: o Status HTTP para toda página ou componente que retorna o conteúdo com sucesso é o 200 (Ok). E o limite total de caracteres em uma URL é de 2048, incluindo todos parâmetros de query string. Caso a chamada extrapole esse limite, o servidor de web retornará o Status HTTP 414 (Request-URL Too Large). Caso você tente algum método diferente de GETPOSTPUT ou DELETE (ou mesmo HEAD ou OPTIONS, se você estiver usando o ColdFusion 2021), o servidor retornará o Status HTTP 405 (Method Not Allowed).

 

 

6) Como saber quais Serviços REST estão cadastrados no Servidor ColdFusion?

Se você está proibido de acessar o Administrator do ColdFusion? Pode buscar um arquivo WDDX que te informa a relação de Serviços REST existentes! É só executar esse código:

variables['a_activeRESTapps']=fileRead(file=server['coldfusion']['rootdir']&'\lib\neo-jaxrs.xml',charset='utf-8');

cfwddx(action='wddx2cfml',input=variables['a_activeRESTapps'],output='variables.a_activeRESTapps');

writeDump(var=variables['a_activeRESTapps']);


 

7) Como excluir um Serviço REST?

Basta executar esse código:

restDeleteApplication(path=expandPath('\rest\1'));

 

 

8) Afinal, por que as API's estão se tornando tão importantes para a construção de novas aplicações (Web e Mobile)?

Antes, era NOSSA responsabilidade retornar MAIS que uma resposta. NOSSA responsabilidade como Programador ColdFusion era processar e tratar a resposta, retornando um HTML com todos os conteúdos gerados nas disposições do HTML, ou seja nas DIVs e nas Células das Tabelas. Nós éramos Programadores FullStack. Hoje, as mecânicas mudaram com a criação da engine V8 do Chrome para preencher essas disposições. Os programadores Front End (antes conhecidos como Web Designers ou Criadores de Perfumaria) agora criam os templates HTML pré-prontos que chamam as APIs para preencherem essas disposições com conteúdos. Cada disposição chama uma API diferente! Quando essas disposições não são preenchidas, frases de alerta são colocadas no lugar do conteúdo esperado.

O conceito está evoluindo de tal forma que os Programadores Front End criaram mais uma mecânica com efeitos para dar o ar da graça aos usuários que não estão mais satisfeitos em apenas ler um conteúdo. A partir dessas mecânicas de carregamento com chamadas API, os Programadores Front End vão melhorando seus próprios conceitos, criando a PWA ou Progressive Web Applications. Se o carregamento não for satisfatório em cada disposição (DIV ou Célula da Tabela) uma mensagem de erro personalizada (e bem humorada, além de um efeito com JavaScript e CSS) aparece no lugar... E essa estética vai aprimorando mais conforme a criatividade e a necessidade de "humanizar" os websites.

 

 

Espero ter ajudado com essas dicas... Deus os abençoe!

--
--
Você recebeu este e-mail pois está cadastrado na lista ColdFusion Brasil
Para CANCELAR sua assinatura escreva para cfbrasil+u...@googlegroups.com
Para ASSINAR a lista escreva para cfbrasil+...@googlegroups.com
O endereço para ENVIO DE MENSAGENS da lista é cfbr...@googlegroups.com
REGRAS em http://groups.google.com/group/cfbrasil/web/regras-de-boa-convivncia-na-lista
Outras opções disponíveis em http://groups.google.com/group/cfbrasil

---
You received this message because you are subscribed to the Google Groups "ColdFusion Brasil" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cfbrasil+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/cfbrasil/42ca8bc0-0c49-46a7-b337-4e4e4af880b7n%40googlegroups.com.


--
Hermes Siqueira
Reply all
Reply to author
Forward
0 new messages