HATEOAS: bezwzględne czy względne adresy URL?

84

Projektując usługę internetową zgodną z REST przy użyciu HATEOAS, jakie są zalety i wady wyświetlania linku jako pełnego adresu URL („ http: // serwer: port / aplikacja / klienci / 1234 ”), a nie tylko ścieżki („/ aplikacja / klientów / 1234 ")?

Mark Lutton
źródło

Odpowiedzi:

83

Kiedy ludzie mówią „względny URI”, występuje subtelna dwuznaczność pojęciowa.

Zgodnie z definicją RFC3986 , ogólny identyfikator URI zawiera:

  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

Problem polega na tym, że gdy pominie się schemat i autorytet, sama część „ścieżki” może być ścieżką bezwzględną (zaczyna się od /) lub ścieżką względną „bez korzeni”. Przykłady:

  1. Absolutnego URI lub pełny identyfikator URI:"http://example.com:8042/over/there?name=ferret"
  2. A to jest względne URI ze ścieżką bezwzględną :/over/there
  3. A to jest względny uri ze ścieżką względną : herelub ./herelub ../herelub itd.

Tak więc, jeśli pytanie brzmiało „czy serwer powinien generować ścieżkę względną w odpowiedzi spokojnej”, odpowiedź brzmi „Nie”, a szczegółowy powód jest dostępny tutaj . Myślę, że większość ludzi (łącznie ze mną) sprzeciwiających się „względnemu URI” jest w rzeczywistości przeciwna „względnej ścieżce”.

W praktyce większość frameworków MVC po stronie serwera może z łatwością generować względne URI ze ścieżką bezwzględną, taką jak /absolute/path/to/the/controller, i pojawia się pytanie „czy implementacja serwera powinna poprzedzać scheme://hostname:portścieżkę bezwzględną przed prefiksem a ”. Jak pytanie OP. Nie jestem pewien co do tego.

Z jednej strony nadal uważam, że serwer zwraca pełny identyfikator URI. Jednak serwer nigdy niehostname:port powinien zakodować rzeczy w kodzie źródłowym w ten sposób (w przeciwnym razie wolałbym raczej wrócić do względnego URI z bezwzględną ścieżką). Rozwiązanie polega na tym, że po stronie serwera zawsze uzyskuje się ten prefiks z nagłówka „Host” żądania HTTP. Nie jestem jednak pewien, czy to działa w każdej sytuacji.

Z drugiej strony łączenie http://example.com:8042ścieżki bezwzględnej i ścieżki absolutnej nie wydaje się kłopotliwe dla klienta . Przecież klient zna już ten schemat i nazwę domeny, kiedy wysyła żądanie do serwera, prawda?

Podsumowując, powiedziałbym, że zalecam użycie bezwzględnego URI, prawdopodobnie powrót do względnego URI ze ścieżką bezwzględną, nigdy nie używaj ścieżki względnej .

RayLuo
źródło
2
To dobra odpowiedź (+1), z którą zgadzam się poza końcowym wnioskiem. Jednak w mojej odpowiedzi twierdzę, że specyfikacja HTTP definiuje, na przykład , „bezwzględne” w odniesieniu do ścieżki bezwzględnej , a nie w pełni kwalifikowanego identyfikatora URI. Więc nie zgadzam się z twoim (2) - jest to absolutny URI, ale taki, dla którego klient musi wywnioskować protokół sieciowy i host, więc nie jest to w pełni kwalifikowany URI. Dlatego też nie zgadzam się z twoją definicją (1), która jest zarówno pełnym, jak i absolutnym URI.
Lawrence Dol
Dziękuję za komentarz. Po prostu pożyczam koncepcję ścieżki bezwzględnej i ścieżki względnej z systemu plików. Pomijając różne terminy, nie widzę znaczącej różnicy między twoją a moją opinią. Polecasz również formy 1 i 2, a ty przeciwko formie 3, prawda?
RayLuo
2
Praktycznie rzecz biorąc, jestem za (2); Myślę, że (1) wymaga od zaplecza zbyt dużej wiedzy na temat protokołu HTTP (co oznacza szczegóły konkretnego środowiska HTTP, a nie ogólnie protokołu HTTP), a (3) wydaje się wymagać zbyt dużej ilości klienta. Ale moje rozumowanie opierało się na pierwotnej wersji roboczej specyfikacji, a przykłady zostały zmienione w późniejszej wersji w sposób, który unieważnia moje rozumowanie.
Lawrence Dol
Osobiście nie jestem (jeszcze) w ogóle przekonany, że HATEOAS, a zatem żądanie zwrotu identyfikatorów URI ma sens dla API. Po prostu nie widzę, jak moje interfejsy API są uruchamiane na kliencie w sposób podobny do przeglądania witryny internetowej; przypadki użycia wydają się być w dużej mierze sterowane przez funkcję ad hoc.
Lawrence Dol
@LawrenceDol Na początku mam takie samo zamieszanie co do HATEOAS. Teraz uważam to za kwestię wyboru. Twoi klienci mogą na pewno korzystać z funkcji adhoc, aby konsumować Twój interfejs API, ale jeśli chcą, mogą nadal opracować wzorzec do naśladowania, aby klient nie musiał na stałe kodować każdego dokładnego adresu URL. To jest HATEOAS.
RayLuo
13

To zależy od tego, kto pisze kod klienta. Jeśli piszesz o kliencie i serwerze, nie ma to większego znaczenia. Będziesz cierpieć ból związany z budowaniem adresów URL na kliencie lub na serwerze.

Jeśli jednak budujesz serwer i oczekujesz, że inni ludzie będą pisać kod klienta, pokochają Cię znacznie bardziej, jeśli podasz pełne identyfikatory URI. Rozwiązywanie względnych identyfikatorów URI może być nieco trudne. Po pierwsze, sposób ich rozwiązania zależy od zwróconego typu nośnika. Html ma tag podstawowy, Xml może mieć tagi xml: base w każdym zagnieżdżonym elemencie, kanały Atom mogą mieć bazę w źródle i inną podstawę w treści. Jeśli nie podasz swojemu klientowi wyraźnych informacji o podstawowym URI, będzie on musiał uzyskać podstawowy URI z URI żądania lub może z nagłówka Content-Location! I uważaj na to końcowe ukośnik. Podstawowy identyfikator URI jest określany przez ignorowanie wszystkich znaków po prawej stronie ostatniego ukośnika. Oznacza to, że końcowy ukośnik jest teraz bardzo istotny podczas rozwiązywania względnych identyfikatorów URI.

Jedyną inną kwestią, która wymaga małej wzmianki, jest rozmiar dokumentu. Jeśli zwracasz dużą listę elementów, w których każdy element może mieć wiele linków, użycie bezwzględnych adresów URL może dodać znaczną ilość bajtów do jednostki, jeśli nie skompresujesz jednostki. Jest to problem z perfekcją i musisz zdecydować, czy jest istotny w każdym przypadku z osobna.

Darrel Miller
źródło
11

Jedyną prawdziwą różnicą wydaje się być to, że klientom łatwiej jest, jeśli używają bezwzględnych identyfikatorów URI, zamiast tworzyć je z wersji względnej. Oczywiście ta różnica wystarczyłaby, by skłonić mnie do zrobienia wersji absolutnej.

Hank Gay
źródło
7

W miarę skalowania aplikacji możesz chcieć wykonać równoważenie obciążenia, przełączanie awaryjne itp. Jeśli zwrócisz bezwzględne identyfikatory URI, aplikacje po stronie klienta będą podążać za zmieniającą się konfiguracją serwerów.

CyberFonic
źródło
Pod warunkiem, że zdefiniujesz „bezwzględną” jako ścieżkę bezwzględną (np. /xxx/yyy...), A nie jako w pełni kwalifikowany identyfikator URI (np http://api.example.com/xxx/yyy....).
Lawrence Dol
6

Korzystając z trychotomii RayLou, moja organizacja zdecydowała się na faworyzowanie (2). Głównym powodem jest unikanie ataków XSS (Cross-Site Scripting). Problem polega na tym, że jeśli atakujący może wstawić swój własny adres URL do odpowiedzi wracającej z serwera, wówczas kolejne żądania użytkowników (takie jak żądanie uwierzytelnienia z nazwą użytkownika i hasłem) mogą być przekazywane na własny serwer atakującego *.

Niektórzy poruszyli kwestię możliwości przekierowywania żądań do innych serwerów w celu równoważenia obciążenia, ale (chociaż nie jest to moja specjalizacja), założyłbym się, że istnieją lepsze sposoby włączenia równoważenia obciążenia bez konieczności jawnego przekierowywania klientów do innych zastępy niebieskie.

* proszę dać mi znać, jeśli są jakieś błędy w tej linii rozumowania. Celem oczywiście nie jest zapobieganie wszystkim atakom, ale przynajmniej jednej drodze ataku.

Rahs
źródło
Cieszę się, że moja poprzednia odpowiedź była pomocna dla Twojej organizacji. Tak, osobiście wolę też (2), czyli ścieżkę absolutną bez schematu. Jednak jestem ciekawy twojego rozumowania. Jak wymusiłeś na kliencie akceptowanie tylko adresu URL bez schematu? Ogólny klient, taki jak przeglądarka, w ogóle nie odrzuciłby adresu URL bez schematu. Zakładam więc, że musiałbyś napisać własny kod po stronie klienta, aby zweryfikować adresy URL, zanim faktycznie je zastosujesz? Chociaż jest to technicznie wykonalne (ale niekoniecznie przydatne), ten rodzaj walidacji po stronie klienta zwykle nie jest częścią dyskusji REST lub HATEOAS.
RayLuo
3
Wiem, że to stary post, ale chcę tylko zaznaczyć, że „jeśli osoba atakująca może wstawić własny katalog główny adresu URL w powracającej odpowiedzi” to bezsensowny powód. Jeśli potrafią „wstrzyknąć własny adres URL” we właściwe miejsca w odpowiedzi, założę się, że mogliby równie łatwo zastąpić twoją nazwę hosta własną. Dlatego z punktu widzenia bezpieczeństwa nie uważam tego za ważny argument.
Magnus Eriksson,
5

Należy zawsze używać pełnego adresu URL. Działa jako unikalny identyfikator zasobu, ponieważ wszystkie adresy URL muszą być unikalne.

Twierdziłbym również, że powinieneś być konsekwentny. Ponieważ nagłówek HTTP lokalizacji oczekuje pełnego adresu URL na podstawie specyfikacji HTTP, pełny adres URL jest przesyłany z powrotem w nagłówku lokalizacji do klienta, gdy tworzony jest nowy zasób. Byłoby dziwne podanie pełnego adresu URL w nagłówku lokalizacji, a następnie względnych identyfikatorów URI w linkach w treści odpowiedzi.

Mark Bober
źródło
1
Cóż, specyfikacja HTTP dla nagłówka Location mówi, że bezwzględny identyfikator URI. Bezwzględny URI musi zawierać schemat (np. Http).
Mark Bober
Ale nie chodzi o to, jak skonstruować nieprzezroczysty bezkontekstowy identyfikatory , ale o to, jak konstruować linki . Ten ostatni może słusznie wywnioskować „w tej samej lokalizacji sieciowej, co ten dokument” i dokładnie to Locationdaje przykład nagłówka specyfikacji - absolutny URI, który nie zawiera schematu URI ani lokalizacji sieciowej serwera. Chociaż linki i identyfikatory są często ze sobą powiązane, nie są tym samym - pierwszy ma kontekst, a drugi nie.
Lawrence Dol
Czy możesz wysłać link do części specyfikacji, o której mówisz?
Mark Bober
Bezwzględny identyfikator URI określa schemat; URI, który nie jest bezwzględny, jest określany jako względny. Identyfikatory URI są również klasyfikowane według tego, czy są nieprzejrzyste, czy hierarchiczne. Nieprzezroczysty identyfikator URI to bezwzględny identyfikator URI, którego część specyficzna dla schematu nie zaczyna się od ukośnika („/”). Nieprzezroczyste identyfikatory URI nie podlegają dalszej analizie. Oto kilka przykładów nieprzezroczystych identyfikatorów URI: mailto: [email protected] news: comp.lang.java urn: isbn: 096139210x
Mark Bober
1
Hej, nie martw się stary. Inną kwestią dotyczącą tych rzeczy jest to, że widziałem ludzi używających href jako identyfikatorów. Aby klient nie musiał rekonstruować adresu URL z jakiegoś pliku konfiguracyjnego i identyfikatora, po prostu zna adres URL i może na jego podstawie buforować.
Mark Bober
2

Ważną kwestią w przypadku dużych wyników API jest dodatkowe obciążenie sieci związane z wielokrotnym włączaniem pełnego identyfikatora URI. Wierz lub nie, ale gzip nie rozwiązuje całkowicie tego problemu (nie wiem dlaczego). Byliśmy zszokowani tym, ile miejsca zajmował pełny identyfikator URI, gdy wynik zawierał setki linków.

George Sibble
źródło
2

Jedną z wad używania bezwzględnych identyfikatorów URI jest to, że nie można proxy interfejsu API.

Cofnij to ... nieprawda. Powinieneś wybrać pełny adres URL, w tym domenę.

Jay Pete
źródło
3
Dlaczego bezwzględny identyfikator URI nie może używać nazwy hosta serwera proxy?
Ed Summers,
1
Pracuję teraz nad tym konkretnym problemem. Chcemy, aby wszystkie żądania najpierw przechodziły przez rodzaj warstwy „równoważenia obciążenia”. Bezwzględne identyfikatory URI wysyłane bezpośrednio do serwerów przełamią ten model.
mag382,
1
Używam Nginx do proxy witryny z bezwzględnymi adresami URL. Doskonale potrafi zastąpić adres URL zaplecza odpowiednikiem adresu URL serwera proxy. W szczególności jest to proxy windyroad.artifactoryonline.com (która ma w pełni kwalifikowane adresy URL i w pełni kwalifikowane przekierowania) do repo.windyroad.com.au
Tom Howard
2

Jeśli chodzi o zalety, widzę redukcję bajtów do przesłania kosztem dodatkowej obsługi wymaganej przez klienta dla ścieżki (bezwzględnej). Jeśli jesteś zdesperowany, aby zapisać każdy bajt, nawet po wypróbowaniu kodowania treści jako gzip, właściwego użycia nagłówków pamięci podręcznej, użycia etagów i warunkowych żądań na kliencie, może to być ostatecznie konieczne, ale spodziewam się znacznie wyższych zwrotów twoje wysiłki były gdzie indziej.

Jeśli chodzi o wady, widzę utratę kontroli nad sposobem kierowania przepływem klientów między zasobami w przyszłości (równoważenie obciążenia, testy A / B, ...) i uznałbym to za złą praktykę w zakresie zarządzania siecią API. Podany adres URL nie jest już w zasadzie nieprzejrzysty dla klienta (patrz Aksjomaty architektury sieciowej Tima Bernersa-Lee dotyczące nieprzezroczystości identyfikatora URI ). Ostatecznie stajesz się odpowiedzialny za zadowolenie klientów z kreatywnego wykorzystania interfejsu API, nawet jeśli dotyczy to tylko struktury przestrzeni adresów URL. Jeśli kiedykolwiek będziesz musiał zezwolić na jawnie zdefiniowaną modyfikację adresu URL, rozważ użycie szablonów URI, które są używane w języku aplikacji hipertekstu .

Michael Hartle
źródło