HATEOAS: URLs absolutos ou relativos?

Respostas:

83

Há uma sutil ambigüidade conceitual quando as pessoas dizem "URI relativo".

Pela definição do RFC3986 , um URI genérico contém:

  URI         = scheme ":" hier-part [ "?" query ] [ "#" fragment ]

  hier-part   = "//" authority path-abempty
              / path-absolute
              / path-rootless
              / path-empty

     foo://example.com:8042/over/there?name=ferret#nose
     \_/   \______________/\_________/ \_________/ \__/
      |           |            |            |        |
   scheme     authority       path        query   fragment

O complicado é que, quando o esquema e a autoridade são omitidos, a parte do "caminho" em si pode ser um caminho absoluto (começa com /) ou um caminho relativo "sem raiz". Exemplos:

  1. Um URI absoluto ou um URI completo:"http://example.com:8042/over/there?name=ferret"
  2. E este é um uri relativo, com caminho absoluto :/over/there
  3. E este é um uri relativo, com caminho relativo : hereou ./hereou ../hereou etc.

Portanto, se a pergunta for "se um servidor deve produzir um caminho relativo em uma resposta repousante", a resposta é "Não" e o motivo detalhado está disponível aqui . Acho que a maioria das pessoas (inclusive eu) contra "URI relativo" é, na verdade, contra "caminho relativo".

E, na prática, a maioria dos frameworks MVC do lado do servidor pode facilmente gerar URI relativo com caminho absoluto , como /absolute/path/to/the/controller, e a questão se torna "se a implementação do servidor deve prefixar a scheme://hostname:portna frente do caminho absoluto". Como a pergunta do OP. Eu não tenho certeza sobre este.

Por um lado, ainda acho que o servidor que retorna um uri completo é recomendado. No entanto, o servidor nuncahostname:port deve codificar a coisa dentro do código-fonte como este (caso contrário, prefiro recorrer ao uri relativo com caminho absoluto). A solução é o lado do servidor sempre obter esse prefixo do cabeçalho "Host" da solicitação HTTP. Não tenho certeza se isso funciona para todas as situações.

Por outro lado, não parece muito problemático para o cliente concatenar o http://example.com:8042caminho e o caminho absoluto. Afinal, o cliente já conhece aquele esquema e nome de domínio quando envia a requisição para o servidor certo?

Resumindo, eu diria que recomendo usar URI absoluto, possivelmente fallback para URI relativo com caminho absoluto, nunca use caminho relativo .

RayLuo
fonte
2
Esta é uma boa resposta (+1) com a qual concordo, exceto a conclusão final. No entanto, em minha resposta, argumento que a especificação HTTP define, por exemplo , "absoluto" para se referir a um caminho absoluto , não a um URI totalmente qualificado. Portanto, discordo do seu (2) - é um URI absoluto, mas para o qual o cliente deve inferir o protocolo de rede e o host, portanto, não é um URI totalmente qualificado. E, portanto, eu também não concordar com a sua definição de (1), que é tanto um URI completo e e URI absoluto.
Lawrence Dol
Obrigado pelo comentário. Acabei de pegar emprestado o caminho absoluto e o conceito de caminho relativo do sistema de arquivos. Diferentes termos à parte, não vejo diferença substancial entre a sua opinião e a minha. Você também recomenda os formulários 1 e 2 e contra os 3, não é?
RayLuo
2
Em termos práticos, eu sou a favor de (2); Acho que (1) exige que o back-end tenha muito conhecimento específico de HTTP (ou seja, sobre os detalhes do ambiente HTTP específico, não HTTP em geral) e (3) parece exigir muito do cliente. Porém , meu raciocínio foi baseado na especificação do rascunho original e os exemplos foram alterados em uma versão posterior de uma forma que invalida meu raciocínio.
Lawrence Dol
Pessoalmente, (ainda) não estou convencido de que o HATEOAS e, portanto, a demanda de retorno de URIs faz muito sentido para uma API. Simplesmente não estou vendo minhas APIs sendo conduzidas no cliente de uma maneira semelhante à navegação em um site; os casos de uso parecem muito orientados por função ad hoc.
Lawrence Dol
@LawrenceDol Eu tenho a mesma confusão sobre o HATEOAS no início. Agora considero isso uma questão de escolha. Seus clientes podem usar a função adhoc para consumir sua api com certeza, mas se eles / você quiserem, eles / você ainda pode desenvolver um padrão para eles seguirem, de modo que o cliente não precise codificar cada url exato. Isso é HATEOAS.
RayLuo
13

Depende de quem está escrevendo o código do cliente. Se você está escrevendo o cliente e o servidor, isso não faz muita diferença. Você sofrerá a dor de construir as URLs no cliente ou no servidor.

No entanto, se você estiver construindo o servidor e espera que outras pessoas escrevam o código do cliente, elas o amarão muito mais se você fornecer URIs completos. Resolver URIs relativos pode ser um pouco complicado. Primeiro, como você os resolve depende do tipo de mídia retornado. Html tem a tag base, o Xml pode ter xml: tags base em cada elemento aninhado, os feeds Atom podem ter uma base no feed e uma base diferente no conteúdo. Se você não fornecer ao seu cliente informações explícitas sobre o URI de base, ele terá que obter o URI de base do URI de solicitação ou talvez do cabeçalho Content-Location! E tome cuidado com essa barra à direita. O URI básico é determinado ignorando todos os caracteres à direita da última barra. Isso significa que a barra final agora é muito significativa ao resolver URIs relativos.

O único outro problema que requer uma pequena menção é o tamanho do documento. Se você estiver retornando uma grande lista de itens em que cada item pode ter vários links, o uso de URLs absolutos pode adicionar uma quantidade significativa de bytes à sua entidade se você não compactar a entidade. Este é um problema de desempenho e você precisa decidir se é significativo caso a caso.

Darrel Miller
fonte
11

A única diferença real parece ser que é mais fácil para os clientes se eles consumirem URIs absolutos, em vez de ter que construí-los a partir da versão relativa. Claro, essa diferença seria o suficiente para me convencer a fazer a versão absoluta.

Hank Gay
fonte
7

Conforme o seu aplicativo é dimensionado, você pode desejar fazer balanceamento de carga, failover, etc. Se você retornar URIs absolutos, seus aplicativos do lado do cliente seguirão a configuração em evolução dos servidores.

CyberFonic
fonte
Desde que você defina "absoluto" como caminho absoluto (por exemplo /xxx/yyy...) e não como significando um URI totalmente qualificado (por exemplo http://api.example.com/xxx/yyy...).
Lawrence Dol
6

Usando a tricotomia de RayLou, minha organização optou por favorecer (2). O principal motivo é evitar ataques XSS (Cross-Site Scripting). O problema é que, se um invasor pode injetar sua própria raiz de URL na resposta que vem do servidor, as solicitações subsequentes do usuário (como uma solicitação de autenticação com nome de usuário e senha) podem ser encaminhadas para o próprio servidor do invasor *.

Alguns levantaram a questão de ser capaz de redirecionar solicitações para outros servidores para balanceamento de carga, mas (embora essa não seja minha área de especialização) eu apostaria que há melhores maneiras de habilitar o balanceamento de carga sem ter que redirecionar explicitamente clientes para diferentes hospedeiros.

* por favor, deixe-me saber se há alguma falha nesta linha de raciocínio. O objetivo, claro, não é prevenir todos os ataques, mas pelo menos uma via de ataque.

Rahs
fonte
Ainda bem que minha resposta anterior foi útil para sua organização. Sim, eu pessoalmente também prefiro (2), também conhecido como caminho absoluto sem esquema. No entanto, estou curioso sobre o seu raciocínio. Como você fez com que seu cliente aceitasse apenas o url sem esquema? Um cliente genérico, como um navegador, não rejeitaria um url sem esquema de forma alguma. Portanto, suponho que você teria que escrever seu próprio código do lado do cliente para validar os urls antes de realmente segui-los? Embora seja tecnicamente factível (mas não necessariamente útil), esse tipo de validação do lado do cliente normalmente não faz parte da discussão do REST ou do HATEOAS.
RayLuo 01 de
3
Sei que esta é uma postagem antiga, mas só quero salientar que "se um invasor pode injetar sua própria raiz de URL na resposta que está voltando" é um motivo meio sem sentido. Se eles puderem "injetar seu próprio URL" nos locais corretos da resposta, aposto que sim, simplesmente substitua seu nome de host pelo seu próprio. Portanto, do ponto de vista da segurança, não vejo isso como um argumento válido.
Magnus Eriksson
5

Você deve sempre usar o URL completo. Ele atua como o identificador exclusivo para o recurso, pois todos os URLs devem ser exclusivos.

Eu também diria que você deve ser consistente. Como o cabeçalho Location HTTP espera um URL completo com base na especificação HTTP, o URL completo é enviado de volta no cabeçalho Location para o cliente quando um novo recurso é criado. Seria estranho para você fornecer um URL completo no cabeçalho Location e, em seguida, URIs relativos nos links dentro do corpo de sua resposta.

Mark Bober
fonte
1
Bem, a especificação HTTP para o cabeçalho Location diz URI absoluto. Um URI absoluto deve conter um esquema (por exemplo, http).
Mark Bober
Mas a questão não é como construir identificadores opacos sem contexto , mas como construir links . O último pode inferir corretamente "no mesmo local de rede que este documento", e isso é exatamente o que o exemplo de um Locationcabeçalho da especificação fornece - um URI absoluto que não contém o esquema de URI ou o local de rede do servidor. Embora links e IDs sejam frequentemente confundidos, eles não são a mesma coisa - o primeiro tem contexto, o último não.
Lawrence Dol
Você pode enviar um link para a parte da especificação de que está falando?
Mark Bober
Um URI absoluto especifica um esquema; um URI que não é absoluto é considerado relativo. URIs também são classificados de acordo com se são opacos ou hierárquicos. Um URI opaco é um URI absoluto cuja parte específica do esquema não começa com um caractere de barra ('/'). URIs opacos não estão sujeitos a análises adicionais. Alguns exemplos de URIs opacos são: mailto: [email protected] news: comp.lang.java urn: isbn: 096139210x
Mark Bober
1
Ei, não se preocupe, cara. Um outro ponto sobre essas coisas é que vi pessoas usando hrefs como IDs. Para que o cliente não precise reconstruir a URL de algum arquivo de configuração e um id, ele apenas conhece a URL e pode armazenar em cache com base nela.
Mark Bober
2

Uma consideração importante em grandes resultados de API é a sobrecarga de rede extra de incluir o URI completo repetidamente. Acredite ou não, o gzip não resolve totalmente esse problema (não sei por quê). Ficamos chocados com a quantidade de espaço que o URI completo ocupou quando havia centenas de links incluídos em um resultado.

George Sibble
fonte
2

Uma desvantagem de usar URIs absolutos é que a API não pode ser proxy.

Retire ... não é verdade. Você deve ir para um URL completo, incluindo o domínio.

Jay Pete
fonte
3
Por que o URI absoluto não pode usar o nome do host do proxy?
Ed Summers em
1
Trabalhando exatamente com esse problema no momento. Queremos que todas as solicitações passem por uma espécie de camada de "balanceamento de carga" primeiro. URIs absolutos para os servidores diretamente quebrarão esse modelo.
mag382
1
Estou usando o Nginx para fazer proxy de um site com URLs absolutos. É perfeitamente capaz de substituir o URL de back-end pelo URL de proxy equivalente. Especificamente, é proxy windyroad.artifactoryonline.com (que possui URLs totalmente qualificados e redirecionamentos totalmente qualificados) para repo.windyroad.com.au
Tom Howard
2

Em relação aos profissionais, vejo a redução de bytes a serem transmitidos em detrimento do manuseio extra exigido por um cliente para o caminho (absoluto). Se você está desesperado para salvar cada byte, mesmo depois de tentar a codificação de conteúdo como gzip, uso adequado de cabeçalhos de cache, uso de etags e solicitações condicionais no cliente, então isso pode ser necessário no final, mas espero retornos muito maiores em seus esforços em outro lugar.

Em relação aos contras, vejo uma perda de controle sobre como você pode direcionar o fluxo de clientes entre recursos no futuro (balanceamento de carga, teste A / B, ...) e consideraria uma prática ruim em relação ao gerenciamento de uma web API. A URL que você fornece não é mais basicamente opaca para o cliente (consulte Tim Berners-Lee Axiomas de Arquitetura da Web sobre opacidade de URI ). No final, você se torna responsável por manter os clientes satisfeitos em relação ao uso criativo de sua API, mesmo que seja apenas em relação à estrutura de seu espaço de URL. Se você precisar permitir uma modificação de URL explicitamente definida, considere o uso de modelos de URI conforme usados ​​na linguagem de aplicativo de hipertexto .

Michael Hartle
fonte