Consequências de não fazer uma API REST da maneira "certa"?

8

Farei essa pergunta dessa maneira - quais são as preocupações de engenharia de software por não implementar minha API REST da maneira "correta"?

O que você quer dizer com o caminho "certo"? Bem, permita-me explicar minha percepção do caminho certo, depois eu direi como estou fazendo isso (também, assuma que estou falando de uma API JSON REST).

O caminho certo

  1. Apatridia. Esta é a parte que recebo. O cliente mantém o estado sempre 100% do tempo para sempre. Não é o trabalho do servidor, é o cliente.

  2. As ações e respostas esperadas para cada verbo:

    • GET - Obtém um recurso especificado na íntegra, limitado apenas pela autorização na solicitação ou por um parâmetro de consulta. Isso garante nenhuma modificação de qualquer recurso no processo.
    • POST - Dada uma descrição completa do recurso (como um objeto JSON), cria um recurso e, em seguida, retorna esse recurso, com todas as propriedades do servidor também criadas, como datas ou IDs.
    • DELETE - Exclui um recurso especificado, fornecendo apenas 200 OK como resposta
    • PUT - Dada uma declaração de objeto inteira como entrada, atualiza o recurso em um local específico, atualizando todos os campos do recurso para cada um dos campos fornecidos na entrada. Para ficar claro, isso espera que todo o objeto seja passado como entrada. Todo o recurso atualizado é retornado, com todos os campos (de acordo com a autorização ou qualquer outro sinalizador de entrada).
    • PATCH - Dado apenas os campos que desejam ser modificados para um recurso, atualiza apenas os campos em um recurso especificado que são fornecidos como entrada. (Aqui é onde não estou claro): O recurso inteiro é retornado? (Ou são apenas os campos atualizados? Não sei. Não ligue.)
  3. Caminhos de recursos. Dado o relacionamento dos recursos entre si, um caminho de recurso pode se parecer com um dos seguintes:
    • / parentresource /: id
    • / parentresource /: id / childresource
    • / parentresource /: id / childresource /: childId
    • / parentresource /: id / childresource /: childId / subresource /: subresourceId (neste exemplo, um subresource pertence a um childresource, que pertence a um recurso pai).

Do jeito que eu quero fazer

A descrição acima é minha compreensão de como uma API REST deve funcionar. Agora, deixe-me listar algumas das minhas variações para o acima:

  1. PUT / PATCH - Qual o sentido de passar todo o recurso para modificação? Uso apenas PUTs para modificar recursos e passo apenas nos campos que quero atualizar. Como resultado, não preciso usar PATCH
  2. Caminhos de recursos - eu uso GUIDs no meu aplicativo. Como resultado, eles serão globalmente únicos. Por que preciso do caminho completo dos recursos, incluindo os recursos pai, se posso apenas me referir exclusivamente a um sub-recurso? Como:
     
    / subresource /: subresourceId
     
    Se eu fizesse isso da maneira "correta", tentar fazer referência ao subresource exigiria um caminho completo como:
     
    / parentresource /: id / childresource /: childId / subresource /: subresourceId
     
    É tudo o que é necessário ? Porque agora eu tenho que ter um tratamento de erro adicional se meu caminho contiver um: subresourceId que não seja realmente de propriedade de um determinado: childId e o mesmo para um: childId que não seja de propriedade de um pai: id. Meu servidor está cuidando da autorização de recursos. Não posso apenas referenciar o recurso em si, e não o caminho completo?

  3. A resposta de retorno. Digamos, por exemplo, que minha estrutura de dados é uma árvore hierárquica, sem limites práticos na profundidade da árvore. Os recursos estão em diferentes níveis abaixo da árvore, de maneira hierárquica.

    • O GET é óbvio. Se eu receber essa árvore inteira, espero que a árvore inteira seja uma resposta, com os recursos contidos nos recursos.
    • Se eu POST para criar um novo recurso, PUT para atualizar ou DELETE para remover, desejo ver os deltas na árvore, em vez de apenas ver o recurso que eu criei / atualizei / excluí. Não quero ter que chamar novamente o GET da árvore pai após cada POST, PUT ou DELETE, especialmente se houver pequenas alterações na árvore e eu apenas desejar ver os deltas.

Espero que minhas perguntas sejam claras.

Se você visse uma implementação REST como a descrevi, você a observaria e me contaria suas preocupações de engenharia de software? Se sim, quais seriam?

Michael Plautz
fonte
4
Curiosamente, nenhuma de suas preocupações envolve as restrições arquiteturais do REST. Suas perguntas estão estritamente vinculadas à semântica WEB e HTTP. Parece que você não está confortável com estes últimos, o que me faz pensar: por que fazer uma interface da web? Por outro lado, não existe algo como "caminho certo" ou "caminho errado". Apenas implementações que melhor atendam às suas necessidades. O que você chama de "certo" é apenas mais "amigável à web" e "amigável ao desenvolvedor". Quanto mais "amigável à web", maior a vantagem da arquitetura da WWW. Esse seria o objetivo de fazer "coisas" na web.
LAIV
O risco de se desviar do REST é que alguém que espera uma interface RESTful fique confuso com o comportamento do seu aplicativo da web. No entanto, o que você sugere nem parece desviar tanto para causar confusão total, então eu argumentaria que o que você está sugerindo está perfeitamente correto. Basta adicionar documentação!
Neil
@ Laiv, você faz uma boa pergunta - por que fazer uma interface web? Como não estou familiarizado com nenhuma outra arquitetura que ofereça suporte a uma estrutura de renderização do lado do cliente, como React ou Vue, do que ter uma interface da web que envie os dados necessários. Para ser claro, não quero a renderização no servidor, por motivos de flexibilidade e desempenho. Sei também que o GraphQL é uma opção, mas não estou familiarizado com isso.
Michael Plautz
O GraphQL não é um estilo arquitetural, é apenas mais uma linguagem de consulta com muito código de caldeira sob o capô. você pode tirar proveito da WWW sem precisar descansar. Ainda há WebSockets ou ProtocolBuffers. se o seu aplicativo não for uma transferência puramente representativa de estatísticas, o REST não lhe servirá.
LAIV

Respostas:

3

A resposta geral subjacente aqui é que suas idéias podem funcionar em um nível técnico, mas isso não significa que estejam em conformidade com as convenções padronizadas do REST.


  1. PUT / PATCH - Qual o sentido de passar todo o recurso para modificação? Uso apenas PUTs para modificar recursos e passo apenas nos campos que quero atualizar. Como resultado, não preciso usar PATCH

Sua ideia funciona em nível técnico, mas simplesmente não é como o REST foi descrito. Lembre-se de que qualquer discussão sobre código de trabalho (ou seja, nenhum erro de compilação ou tempo de execução) sempre será uma questão de convenção , não necessariamente de clara superioridade técnica.


  1. Caminhos de recursos - eu uso GUIDs no meu aplicativo. Como resultado, eles serão globalmente únicos. Por que preciso do caminho completo dos recursos, incluindo os recursos pai, se posso apenas me referir exclusivamente a um sub-recurso?

Existem muitas nuances sobre como definimos entidades "filho / pai". Mais comumente, refere-se a um relacionamento um para muitos (pai para filhos).

No entanto, suspeito que, para o REST, parte do que faz de uma criança uma criança é a expectativa de que ela só possa acessá-la através dos pais, de que eles não possuam seu próprio identificador globalmente exclusivo (e conhecido externamente).
Suspeito que isso esteja seguindo a mesma filosofia (mas não necessariamente pelo mesmo motivo) que a dos agregados (e suas raízes) no Desenvolvimento Orientado a Domínios .

Um agregado DDD é um cluster de objetos de domínio que podem ser tratados como uma única unidade. Um agregado terá um de seus objetos componentes como a raiz agregada. Quaisquer referências externas ao agregado devem apenas ir para a raiz agregada .

No seu caso, o que você chama de "pai" funciona como a raiz agregada. O ponto de contato único (se você desejar) para chamadas externas.

Você pode concluir que seu filho é realmente um agregado diferente. Pode ser esse o caso, mas quero emitir um aviso com essa decisão. Você não deve basear sua arquitetura no tipo específico de um campo. Você não tem como saber se sempre usará IDs exclusivos globalmente para todas as suas entidades. Se isso mudar, por qualquer motivo, você comprometerá a viabilidade de sua arquitetura REST; pois você pode acabar em uma situação em que a criança não é mais identificável exclusivamente e, portanto, precisa ser referenciada por seus pais.


  1. Se eu POST para criar um novo recurso, PUT para atualizar ou DELETE para remover, desejo ver os deltas na árvore, em vez de apenas ver o recurso que eu criei / atualizei / excluí.

Você está violando a ordem das operações do design. Uma API REST destina-se especificamente a ser independente do consumidor. A API não deve ser construída de acordo com as especificações de um de seus consumidores.

Quando você diz "Quero ver os deltas na árvore", o que você realmente está dizendo é "o aplicativo consumidor apenas precisa ver os deltas na árvore". Mas isso não importa muito para a API REST. Apenas fornece uma abordagem padronizada.

É da natureza das abordagens padronizadas muitas vezes carecer de ferramentas altamente personalizáveis e, em vez disso, favorecer as ferramentas mais usadas .


Você pode se desviar do caminho? Bem, funcionará em um nível técnico. Mas não será mais puro descanso. Isso é algo altamente contextual e você precisa avaliar as opções.

  • Se você está criando uma API que deve atender a muitos consumidores variados, sugiro que adira ao REST da melhor maneira possível.
  • Se você estiver criando uma API que terá apenas um consumidor que também é desenvolvido por você; então não há necessidade real de se ater ao REST puro.
  • Desviar-se do caminho significa que você precisará documentar como se desviou para que outros desenvolvedores ainda possam entender o sentido. Se você seguir o REST puro, não precisará escrever a documentação e os outros desenvolvedores não precisarão gastar tempo e esforço para descobrir sua abordagem personalizada.
Flater
fonte
9

quais são as preocupações de engenharia de software por não implementar minha API REST da maneira "certa"?

A principal, na minha opinião, é a capacidade de delegar trabalho a componentes genéricos que conhecem apenas os padrões, não o seu caso de negócios específico.

Se você está aderindo à interface uniforme, é mais fácil para outras partes criar componentes que se integram perfeitamente aos seus.

Aqui está Fielding escrevendo em 2008

O REST é destinado a aplicativos baseados em rede de longa duração que abrangem várias organizações.

Uma das maneiras pelas quais gerenciamos a "vida longa" é ter um padrão claro que descreva a semântica das mensagens que estamos passando. Se todos concordarem com o que PUTsignifica, os consumidores e produtores dessas solicitações poderão ser desenvolvidos de forma independente, e os componentes intermediários entre as duas poderão executar ações sensatas sem precisar conhecer os detalhes da mensagem em seu contexto específico.

PUT / PATCH - Qual o sentido de passar todo o recurso para modificação? Uso apenas PUTs para modificar recursos e passo apenas nos campos que quero atualizar. Como resultado, não preciso usar PATCH

Qual é o sentido de usar PUTentão?

PURPLE /014d8c83-604d-4cf0-a6ba-e1f7ef8c4898 HTTP/1.1
...

Essa é uma linha de solicitação perfeitamente válida para uma mensagem HTTP, e a mudança na semântica não vai confundir ninguém.

Equivalentemente

POST /014d8c83-604d-4cf0-a6ba-e1f7ef8c4898 HTTP/1.1
...

Que praticamente tem semântica sem restrições; o servidor pode implementar o tratamento dessa solicitação da maneira que desejar.

suponha que eu esteja falando de uma API JSON REST

Sua relutância em usar o PATCH é especialmente estranha nesse caso, pois já existem padrões propostos para o JSON Patch e o JSON Merge Patch - o trabalho de padronizar um formato de documento de patch já pode ser feito para você.

Outra alternativa válida seria tratar o documento de correção como um recurso separado. Semanticamente, você pode imaginar algo como

PUT /014d8c83-604d-4cf0-a6ba-e1f7ef8c4898/patches/5c42c414-03c0-4ac5-af14-2b1165ac98b3 HTTP/1.1

Isso faz com que você seja honesto, uniforme, semântica de mensagens, sacrificando a invalidação de cache padronizada .

Em uma configuração de revisão de código, rejeitaria uma alteração proposta que tentasse redefinir a semântica de PUT.

O HTTP não tenta exigir que os resultados de um GET sejam seguros. O que faz é exigir que a semântica da operação seja segura e, portanto, é uma falha da implementação, não da interface ou do usuário dessa interface, se algo acontecer como resultado que cause perda de propriedade (dinheiro, BTW, é considerado propriedade por causa desta definição). - Fielding, 2002 .

A mesma consideração vale PUTtambém; se sua implementação de PUT se desviar da semântica padronizada, sua implementação será responsável pelo dano resultante.

Caminhos de recursos - eu uso GUIDs no meu aplicativo. Como resultado, eles serão globalmente únicos. Por que preciso do caminho completo dos recursos, incluindo os recursos pai, se posso apenas me referir exclusivamente a um sub-recurso?

Está perfeitamente bem. O REST não se importa com as grafias usadas para os identificadores de recursos.

Considere a página de destino do Google. Você precisa prestar atenção à ortografia do URI para o doodle de hoje? ou para onde o formulário de pesquisa está sendo enviado? Não, claro que não. A carga útil HTML inclui URI, e os clientes apenas usam os identificadores fornecidos, de maneira padrão, sem a necessidade de analisar esses identificadores.

As informações codificadas em um URI ficam a critério do servidor de origem, para seus próprios fins.

Gostaria de desencorajar o uso de um URI como o ponto de entrada da sua API. https://www.example.org/df8f5f87-15ff-4212-8fb8-4fbca2c7efcfé um pouco estranho para o consumo humano. Um URI legível por humanos que redireciona para o recurso UUID seria bom, um URI legível por humanos que retorna o conteúdo do recurso UUID seria melhor.

Se eu POST para criar um novo recurso, PUT para atualizar ou DELETE para remover, quero ver os deltas na árvore, em vez de apenas ver o recurso que eu criei / atualizei / excluí

Tudo bem - novamente, olhe para o padrão .

A carga útil enviada em uma resposta 200 depende do método de solicitação. Para os métodos definidos por esta especificação, o significado pretendido da carga útil pode ser resumido como

POST uma representação do status ou resultados obtidos da ação

PUT, DELETE uma representação do status da ação

Em alguns casos, faz sentido enviar a nova representação do recurso como parte da resposta (para poupar o cliente da latência de uma solicitação / resposta GET).

VoiceOfUnreason
fonte
2
What's the point in using PUT then?-- De fato. Um POST funcionaria perfeitamente bem.
Robert Harvey
@RobertHarvey Ou por que não usar PATCH? A definição se encaixa melhor.
Solomon Ucko
@SolomonUcko: Tornar tudo um POST tem a virtude da simplicidade.
Robert Harvey
3

REST é um estilo de arquitetura para manipular recursos de maneira sem estado (onde sem estado significa que cada manipulação se sustenta por si mesma e não depende de outras manipulações que possam ter ocorrido).

O uso dos verbos PUT / PATCH / POST / GET / DELETE vem do uso comum do protocolo HTTP usado para transferir e manipular os recursos. O significado desses verbos é definido por um padrão da Internet ( RFC7231 ).

Dado esse histórico, seu uso do PUT não é padrão e pode confundir outros desenvolvedores que desejam usar sua API.

Com relação aos caminhos dos recursos, quase ninguém se importa com a ortografia exata (incluindo se um recurso filho está listado como filho). O que as pessoas se preocupam é que cada recurso seja identificado exclusivamente. O sistema /parent/:pid/child/:cidé frequentemente usado quando os IDs filhos são únicos dentro de um pai para ainda ter um caminho globalmente exclusivo para o recurso.

Bart van Ingen Schenau
fonte
2
Além da resposta de Bart. Lembre-se de que o único que deve ler e entender o URI é o Cliente HTTP, e ele não se importa com a hierarquia dos recursos. Então, se o URI expressar hierarquia, relacionamento ou qualquer outro tipo de relacionamento só é significativa para a vida pobre ser quem precisa lê-lo
LAIV
1
  1. PUT / PATCH - Qual o sentido de passar todo o recurso para modificação? Uso apenas PUTs para modificar recursos e passo apenas nos campos que quero atualizar. Como resultado, não preciso usar PATCH

Parece-me que você realmente deveria estar usando PATCH como semântica aqui.

  • PUT explica o estado exato desejado. Isso é útil quando um recurso pode estar mudando com freqüência e a alteração desejada precisa ocorrer dentro de um contexto específico.

  • PATCH explica o delta desejado para o que estiver lá. Isso é útil quando a mudança não se importa com o contexto ou o contexto relevante é muito menor que o recurso inteiro.

Uma imagem é um bom exemplo onde faria sentido fazer o upload da coisa toda. É importante que todo o recurso seja comunicado para garantir um contexto relevante.

A atualização inversa da contagem de reprodução em uma lista de reprodução de música pode fazer mais sentido ser um delta. Não porque é uma pequena alteração, mas porque reenviar a lista inteira pode facilmente desfazer alterações no conteúdo da lista.

  1. Caminhos de recursos - eu uso GUIDs no meu aplicativo. Como resultado, eles serão globalmente únicos. Por que preciso do caminho completo dos recursos, incluindo os recursos pai, se posso apenas me referir exclusivamente a um sub-recurso?

...recorte...

Tudo isso é necessário? Porque agora eu tenho que ter um tratamento de erro adicional se meu caminho contiver um: subresourceId que não seja realmente de propriedade de um determinado: childId e o mesmo para um: childId que não seja de propriedade de um pai: id. Meu servidor está cuidando da autorização de recursos. Não posso apenas referenciar o recurso em si, e não o caminho completo?

Não, você realmente não precisa usar caminhos - nunca. Digamos que você mantenha todos os seus arquivos na área de trabalho? Não? Por que não?

Provavelmente algo a ver com tornar mais fácil ver o mesmo problema aqui. Um GUID não informa o que você está manipulando enquanto está configurando, depurando ou executando.

Então, como é apoiar esse sistema? ou para interagir com ele? Se você ainda não pensou nisso, leve algum tempo e considere o trabalho que está empurrando na linha.

Ter informações explícitas de caminhos serve para ajudar a validar a solicitação. Também ajuda a diferenciar informações o suficiente para que os desenvolvedores de sistemas de suporte e downstream possam abordar esses URLs e usá-los.

  1. A resposta de retorno. Digamos, por exemplo, que minha estrutura de dados é uma árvore hierárquica, sem limites práticos na profundidade da árvore. Os recursos estão em diferentes níveis abaixo da árvore, de maneira hierárquica.

    uma. O GET é óbvio. Se eu receber essa árvore inteira, espero que a árvore inteira seja uma resposta, com os recursos contidos nos recursos.

Você pode impor um limite de profundidade, simplesmente para que um garoto esperto não obtenha a raiz do seu site para todas as operações que eles possam imaginar.

b. Se eu POST para criar um novo recurso, PUT para atualizar ou DELETE para remover, desejo ver os deltas na árvore, em vez de apenas ver o recurso que eu criei / atualizei / excluí. Não quero ter que chamar novamente o GET da árvore pai após cada POST, PUT ou DELETE, especialmente se houver pequenas alterações na árvore e eu apenas desejar ver os deltas.

Se a atualização de um recurso está afetando seu pai de uma maneira não trivial e previsível, você tem outros problemas ... Você realmente precisa examinar o modelo de estado e descobrir por que as informações estão pulando.

Se você deseja retornar apenas uma lista detalhada de deltas, por que não? Por que não suportar várias visualizações de saída alternadas por vários parâmetros? Jenkins retorna suas respostas de API em uma opção de xml ou json e permite especificar vários filtros para extrair a subárvore desejada.

Use você mesmo

Francamente, afaste-se do que você está criando e tente apoiá-lo ou faça outro aplicativo para usá-lo (não um dos aplicativos pré-existentes). Faça algo semelhante para APIs de terceiros para ter um pouco de contexto em segundo plano.

Sempre que você tiver que fazer algo que não está resolvendo diretamente a solicitação de suporte ou diretamente necessário para o aplicativo cliente, a API é menos que o ideal e você sabe por que é menos que o ideal, o que é ainda melhor, porque você pode conserte ou não cometa o mesmo erro.

Por exemplo, se as solicitações para um determinado URL falham constantemente, quanto esforço você tem para investir para determinar o que está falhando e por quê? Que medidas você tomou, poderia ter evitado uma dessas etapas por ter um URI melhor, ou melhor registro, ou melhor monitoramento, ou etc ...?

Da mesma forma, se você estiver escrevendo um novo cliente, quantas vezes você precisa consultar a documentação ou o código-fonte da API? O que você poderia fazer para reduzir essa necessidade? O que você poderia fazer para parar de violar suas próprias expectativas? O que você poderia fazer para simplificar o problema dos aplicativos clientes sem tornar o servidor um pesadelo para manter?

Caminho certo

Francamente, o caminho certo é circunstancial. O REST é um conjunto de práticas demonstradas para funcionar em várias circunstâncias para vários problemas. Se o seu problema não se encaixar, não o faça, mas também não pretenda usar essas práticas.

Kain0_0
fonte
-1

A maioria dos recursos das APIs REST existe por um motivo, mas pode não ser relevante para você. Fornecer todo o recurso, como em PUT, por exemplo, é relevante se você precisar de idempotência, caso contrário, não é. (Embora eu ache que seria melhor para os usuários / colegas / o que quer que seja anunciar o fato de que seu terminal não é idempotente usando POST ou PATCH.)

Para a coisa do caminho, nunca ouvi dizer que estava relacionado ao descanso. /root/345dd4dc-e175-455f-b545-85b1b1ce3e82faz parte de uma árvore tanto quanto /foo/bar/baz. Talvez um pouco menos amigável, mas não menos tranquilo, tanto quanto eu posso ver.

Se você quiser um raciocínio mais detalhado sobre o motivo pelo qual o REST foi projetado, acho que deveria ler a dissertação original: https://www.ics.uci.edu/~fielding/pubs/dissertation/fielding_dissertation.pdf

Lendo que você pode descobrir que é bem diferente de como o REST é representado em conversas ou usado nas APIs hoje. Claramente, muitas outras pessoas encontraram uma razão, boa ou má, para se afastar dela.

Eu particularmente gosto desta citação que você pode achar relevante:

O HTTP não foi projetado para ser um protocolo de transporte. É um protocolo de transferência no qual as mensagens refletem a semântica da arquitetura da Web, executando ações nos recursos por meio da transferência e manipulação de representações desses recursos. É possível obter uma ampla gama de funcionalidades usando essa interface muito simples, mas é necessário seguir a interface para que a semântica HTTP permaneça visível para os intermediários. É por isso que o HTTP passa por firewalls. A maioria das extensões propostas recentemente ao HTTP, além do WebDAV [60], apenas usou o HTTP como uma maneira de mover outros protocolos de aplicativos através de um firewall, o que é uma idéia fundamentalmente equivocada. Não só derrota o propósito de ter um firewall, mas não funcionará a longo prazo, porque os fornecedores de firewall simplesmente precisam executar uma filtragem de protocolo adicional. Portanto, não faz sentido fazer essas extensões sobre o HTTP, pois a única coisa que o HTTP realiza nessa situação é adicionar sobrecarga a partir de uma sintaxe herdada. Uma verdadeira aplicação de HTTP mapeia as ações do usuário do protocolo para algo que pode ser expresso usando a semântica HTTP, criando assim uma API baseada em rede para serviços que podem ser entendidos por agentes e intermediários sem nenhum conhecimento do aplicativo.

monocelular
fonte
2
"Embora eu ache que seria melhor para os usuários / colegas / o que quer que seja anunciar o fato de que seu endpoint não é idempotente usando POST ou PATCH." - Isso não é uma questão de gentileza. Os métodos HTTP definiram semântica com precisão, e toda a infraestrutura da Web em todo o mundo depende dessa semântica. Por exemplo, os proxies podem tentar novamente as PUTsolicitações várias vezes sem que você saiba, e eles podem fazer isso porque a especificação HTTP diz que PUTé idempotente . Se seus PUTs não são idempotentes, isso interromperá seu serviço .
Jörg W Mittag
2
Por um tempo, os "aceleradores da web" estavam na moda e, de fato, estão voltando aos dispositivos móveis. Esses aceleradores fizeram a pré-busca e o cache de recursos, e eles foram autorizados a fazer isso, porque a especificação HTTP garante isso GETe HEADé pura e sem efeitos colaterais. Algumas pessoas perderam dados, porque estavam usando aplicativos da Web mal projetados, onde a exclusão do conteúdo era feita com uma GETsolicitação e o acelerador da Web enviava GETsolicitações para todos os URIs que conseguisse encontrar.
Jörg W Mittag
Você está certo, é claro. Eu simplesmente não consigo mais entusiasmar uma web que funcione corretamente. Parece uma batalha perdida para mim, mas talvez você esteja em um ambiente melhor, onde coisas como essas realmente funcionam. Onde estou http é apenas um transporte com a adição de que é depurável a partir de um navegador em vez de netcat, e isso é tudo.
monocell 8/03/19