Skip to content

Latest commit

 

History

History
500 lines (399 loc) · 22.3 KB

http-headers.adoc

File metadata and controls

500 lines (399 loc) · 22.3 KB

REST Basics - HTTP headers

We describe a handful of standard HTTP headers, which we found raising the most questions in our daily usage, or which are useful in particular circumstances but not widely known.

Though we generally discourage usage of proprietary headers, they are useful to pass generic, service independent, overarching information relevant for our specific application architecture. We consistently define these proprietary headers in this section below. Whether services support these concerns or not is optional. Therefore, the OpenAPI API specification is the right place to make this explicitly visible — use the parameter definitions of the resource HTTP methods.

{MAY} use standard headers

Use this list and explicitly mention its support in your OpenAPI definition.

{SHOULD} use kebab-case with uppercase separate words for HTTP headers

This convention is followed by (most of) the standard headers e.g. as defined in {RFC-2616}[RFC 2616] and {RFC-4229}[RFC 4229]. Examples:

If-Modified-Since
Accept-Encoding
Content-ID
Language

Note, HTTP standard defines headers as case-insensitive ({RFC-7230}#page-22[RFC 7230, p.22]). However, for sake of readability and consistency you should follow the convention when using standard or proprietary headers. Exceptions are common abbreviations like ID.

{MUST} use Content-* headers correctly

Content or entity headers are headers with a Content- prefix. They describe the content of the body of the message and they can be used in both, HTTP requests and responses. Commonly used content headers include but are not limited to:

  • {Content-Disposition} can indicate that the representation is supposed to be saved as a file, and the proposed file name.

  • {Content-Encoding} indicates compression or encryption algorithms applied to the content.

  • {Content-Length} indicates the length of the content (in bytes).

  • {Content-Language} indicates that the body is meant for people literate in some human language(s).

  • {Content-Location} indicates where the body can be found otherwise ({MAY} use Content-Location header for more details]).

  • {Content-Range} is used in responses to range requests to indicate which part of the requested resource representation is delivered with the body.

  • {Content-Type} indicates the media type of the body content.

{SHOULD} use Location header instead of Content-Location header

As the correct usage of {Content-Location} response header (see below) with respect to caching and its method specific semantics is difficult, we discourage the use of {Content-Location}. In most cases it is sufficient to inform clients about the resource location in create or re-direct responses by using the {Location} header while avoiding the {Content-Location} specific ambiguities and complexities.

More details in RFC 7231 {RFC-7231}#section-7.1.2[7.1.2 Location], {RFC-7231}#section-3.1.4.2[3.1.4.2 Content-Location]

{MAY} use Content-Location header

{Content-Location} is an optional response header that can be used in successful write operations ({PUT}, {POST}, or {PATCH}) or read operations ({GET}, {HEAD}) to guide caching and signal a receiver the actual location of the resource transmitted in the response body. This allows clients to identify the resource and to update their local copy when receiving a response with this header.

The Content-Location header can be used to support the following use cases:

  • For reading operations {GET} and {HEAD}, a different location than the requested URL can be used to indicate that the returned resource is subject to content negotiations, and that the value provides a more specific identifier of the resource.

  • For writing operations {PUT} and {PATCH}, an identical location to the requested URL can be used to explicitly indicate that the returned resource is the current representation of the newly created or updated resource.

  • For writing operations {POST} and {DELETE}, a content location can be used to indicate that the body contains a status report resource in response to the requested action, which is available at provided location.

Note: When using the {Content-Location} header, the {Content-Type} header has to be set as well. For example:

GET /products/123/images HTTP/1.1

HTTP/1.1 200 OK
Content-Type: image/png
Content-Location: /products/123/images?format=raw

{MAY} consider to support Prefer header to handle processing preferences

The {Prefer} header defined in {RFC-7240}[RFC 7240] allows clients to request processing behaviors from servers. It pre-defines a number of preferences and is extensible, to allow others to be defined. Support for the {Prefer} header is entirely optional and at the discretion of API designers, but as an existing Internet Standard, is recommended over defining proprietary "X-" headers for processing directives.

The {Prefer} header can defined like this in an API definition:

components:
  headers:
  - Prefer:
      description: >
        The RFC7240 Prefer header indicates that a particular server behavior
        is preferred by the client but is not required for successful completion
        of the request (see [RFC 7240](https://tools.ietf.org/html/rfc7240).
        The following behaviors are supported by this API:

        # (indicate the preferences supported by the API or API endpoint)
        * **respond-async** is used to suggest the server to respond as fast as
          possible asynchronously using 202 - accepted - instead of waiting for
          the result.
        * **return=<minimal|representation>** is used to suggest the server to
          return using 204 without resource (minimal) or using 200 or 201 with
          resource (representation) in the response body on success.
        * **wait=<delta-seconds>** is used to suggest a maximum time the server
          has time to process the request synchronously.
        * **handling=<strict|lenient>** is used to suggest the server to be
          strict and report error conditions or lenient, i.e. robust and try to
          continue, if possible.

      type: array
      items:
        type: string
      required: false

Note: Please copy only the behaviors into your {Prefer} header specification that are supported by your API endpoint. If necessary, specify different {Prefer} headers for each supported use case.

Supporting APIs may return the {Preference-Applied} header also defined in {RFC-7240}[RFC 7240] to indicate whether a preference has been applied.

{MAY} consider to support ETag together with If-Match/If-None-Match header

When creating or updating resources it may be necessary to expose conflicts and to prevent the 'lost update' or 'initially created' problem. Following {RFC-7232}[RFC 7232 "HTTP: Conditional Requests"] this can be best accomplished by supporting the {ETag} header together with the {If-Match} or {If-None-Match} conditional header. The contents of an ETag: <entity-tag> header is either (a) a hash of the response body, (b) a hash of the last modified field of the entity, or (c) a version number or identifier of the entity version.

To expose conflicts between concurrent update operations via {PUT}, {POST}, or {PATCH}, the If-Match: <entity-tag> header can be used to force the server to check whether the version of the updated entity is conforming to the requested {entity-tag}. If no matching entity is found, the operation is supposed a to respond with status code {412} - precondition failed.

Beside other use cases, If-None-Match: * can be used in a similar way to expose conflicts in resource creation. If any matching entity is found, the operation is supposed a to respond with status code {412} - precondition failed.

The {ETag}, {If-Match}, and {If-None-Match} headers can be defined as follows in the API definition:

components:
  headers:
  - ETag:
      description: |
        The RFC 7232 ETag header field in a response provides the entity-tag of
        a selected resource. The entity-tag is an opaque identifier for versions
        and representations of the same resource over time, regardless whether
        multiple versions are valid at the same time. An entity-tag consists of
        an opaque quoted string, possibly prefixed by a weakness indicator (see
        [RFC 7232 Section 2.3](https://tools.ietf.org/html/rfc7232#section-2.3).

      type: string
      required: false
      example: W/"xy", "5", "5db68c06-1a68-11e9-8341-68f728c1ba70"

  - If-Match:
      description: |
        The RFC7232 If-Match header field in a request requires the server to
        only operate on the resource that matches at least one of the provided
        entity-tags. This allows clients express a precondition that prevent
        the method from being applied if there have been any changes to the
        resource (see [RFC 7232 Section
        3.1](https://tools.ietf.org/html/rfc7232#section-3.1).

      type: string
      required: false
      example: "5", "7da7a728-f910-11e6-942a-68f728c1ba70"

  - If-None-Match:
      description: |
        The RFC7232 If-None-Match header field in a request requires the server
        to only operate on the resource if it does not match any of the provided
        entity-tags. If the provided entity-tag is `*`, it is required that the
        resource does not exist at all (see [RFC 7232 Section
        3.2](https://tools.ietf.org/html/rfc7232#section-3.2).

      type: string
      required: false
      example: "7da7a728-f910-11e6-942a-68f728c1ba70", *

Please see [optimistic-locking] for a detailed discussion and options.

{MAY} consider to support Idempotency-Key header

When creating or updating resources it can be helpful or necessary to ensure a strong [idempotent] behavior comprising same responses, to prevent duplicate execution in case of retries after timeout and network outages. Generally, this can be achieved by sending a client specific unique request key – that is not part of the resource – via {Idempotency-Key} header.

The unique request key is stored temporarily, e.g. for 24 hours, together with the response and the request hash (optionally) of the first request in a key cache, regardless of whether it succeeded or failed. The service can now look up the unique request key in the key cache and serve the response from the key cache, instead of re-executing the request, to ensure [idempotent] behavior. Optionally, it can check the request hash for consistency before serving the response. If the key is not in the key store, the request is executed as usual and the response is stored in the key cache.

This allows clients to safely retry requests after timeouts, network outages, etc. while receive the same response multiple times. Note: The request retry in this context requires to send the exact same request, i.e. updates of the request that would change the result are off-limits. The request hash in the key cache can protection against this misbehavior. The service is recommended to reject such a request using status code {400}.

Important: To grant a reliable [idempotent] execution semantic, the resource and the key cache have to be updated with hard transaction semantics – considering all potential pitfalls of failures, timeouts, and concurrent requests in a distributed systems. This makes a correct implementation exceeding the local context very hard.

The {Idempotency-Key} header must be defined as follows, but you are free to choose your expiration time:

components:
  headers:
  - Idempotency-Key:
      description: |
        The idempotency key is a free identifier created by the client to
        identify a request. It is used by the service to identify subsequent
        retries of the same request and ensure idempotent behavior by sending
        the same response without executing the request a second time.

        Clients should be careful as any subsequent requests with the same key
        may return the same response without further check. Therefore, it is
        recommended to use an UUID version 4 (random) or any other random
        string with enough entropy to avoid collisions.

        Idempotency keys expire after 24 hours. Clients are responsible to stay
        within this limit, if they require idempotent behavior.

      type: string
      format: uuid
      required: false
      example: "7da7a728-f910-11e6-942a-68f728c1ba70"

Hint: The key cache is not intended as request log, and therefore should have a limited lifetime, else it could easily exceed the data resource in size.

Note: The {Idempotency-Key} header unlike other headers in this section is not standardized in an RFC. Our only reference are the usage in the Stripe API. However, we do not want to change the header name and semantic, and do not name it like the proprietry headers below. The header addresses a generic REST concern and is different from the Zalando landscape specific proprietary headers.

{SHOULD} use only the specified proprietary Zalando headers

As a general rule, proprietary HTTP headers should be avoided. From a conceptual point of view, the business semantics and intent of an operation should always be expressed via the URLs path and query parameters, the method, and the content, but not via proprietary headers. Headers are typically used to implement protocol processing aspects, such as flow control, content negotiation, and authentication, and represent business agnostic request modifiers that provide generic context information ({RFC-7231}#section-5[RFC 7231]).

However, the exceptional usage of proprietary headers is still helpful when domain-specific generic context information…​

  1. needs to be passed end to end along the service call chain (even if not all called services use it as input for steering service behavior e.g. {X-Sales-Channel} header) and/or…​

  2. is provided by specific gateway components, for instance, our Fashion Shop API or Merchant API gateway.

Below, we explicitly define the list of proprietary header exceptions usable for all services for passing through generic context information of our fashion domain (use case 1).

Per convention, non standardized, proprietary header names are prefixed with X-. (Due to backward compatibility, we do not follow the Internet Engineering Task Force’s recommendation in {RFC-6648}[RFC 6648] to deprecate usage of X- headers.) Remember that HTTP header field names are not case-sensitive:

Header field name Type Description Header field value example

{X-Flow-ID}

String

For more information see {MUST} support {X-Flow-ID}.

GKY7oDhpSiKY_gAAAABZ_A

{X-Tenant-ID}

String

Identifies the tenant initiated the request to the multi tenant Zalando Platform. The {X-Tenant-ID} must be set according to the Business Partner ID extracted from the OAuth token when a request from a Business Partner hits the Zalando Platform.

9f8b3ca3-4be5-436c-a847-9cd55460c495

{X-Sales-Channel}

String

Sales channels are owned by retailers and represent a specific consumer segment being addressed with a specific product assortment that is offered via CFA retailer catalogs to consumers (see fashion platform glossary (internal link)).

52b96501-0f8d-43e7-82aa-8a96fab134d7

{X-Frontend-Type}

String

Consumer facing applications (CFAs) provide business experience to their customers via different frontend application types, for instance, mobile app or browser. Info should be passed-through as generic aspect — there are diverse concerns, e.g. pushing mobiles with specific coupons, that make use of it. Current range is mobile-app, browser, facebook-app, chat-app, email.

mobile-app

{X-Device-Type}

String

There are also use cases for steering customer experience (incl. features and content) depending on device type. Via this header info should be passed-through as generic aspect. Current range is smartphone, tablet, desktop, other.

tablet

{X-Device-OS}

String

On top of device type above, we even want to differ between device platform, e.g. smartphone Android vs. iOS. Via this header info should be passed-through as generic aspect. Current range is iOS, Android, Windows, Linux, MacOS.

Android

{X-Mobile-Advertising-Id}

String

It is either the IDFA (Apple Identifier for mobile Advertising) for iOS, or the GAID (Google mobile Advertising Identifier) for Android. It is a unique, customer-resettable identifier provided by mobile device’s operating system to facilitate personalized advertising, and usually passed by mobile apps via http header when calling backend services. Called services should be ready to pass this parameter through when calling other services. It is not sent if the customer disables it in the settings for respective mobile platform.

b89fadce-1f42-46aa-9c83-b7bc49e76e1f

Exception: The only exception to this guideline are the conventional hop-by-hop X-RateLimit- headers which can be used as defined in [153].

As part of the guidelines we sourced the OpenAPI definition of all proprietary headers; you can simply reference it when defining the API endpoint requests e.g.

parameters:
- $ref: "https://opensource.zalando.com/restful-api-guidelines/models/request-headers-1.0.0.yaml#/X-Flow-ID"
- $ref: "https://opensource.zalando.com/restful-api-guidelines/models/request-headers-1.0.0.yaml#/X-Tenant-ID"

Response headers can be referenced in the API endpoint e.g.

parameters:
- $ref: "https://opensource.zalando.com/restful-api-guidelines/models/response-headers-1.0.0.yaml#/ETag"
- $ref: "https://opensource.zalando.com/restful-api-guidelines/models/response-headers-1.0.0.yaml#/Cache-Control"

Hint: This guideline does not standardize proprietary headers for our specific gateway components (2. use case above). This include, for instance, non pass-through headers X-Zalando-Client-ID, X-Zalando-Request-Host, X-Zalando-Request-URI defined by Fashion Shop API (RKeep), or X-Consumer, X-Consumer-Signature, X-Consumer-Key-ID defined by Merchant API gateway. All these proprietary headers are whitelisted in the API Linter (Zally) checking this rule.

{MUST} propagate proprietary headers

All Zalando’s proprietary headers listed above are end-to-end headers [1]) defines two types of headers: end-to-end and hop-by-hop headers. End-to-end headers must be transmitted to the ultimate recipient of a request or response. Hop-by-hop headers, on the contrary, are meaningful for a single connection only.] and must be propagated to the services down the call chain. The header names and values must remain unchanged.

For example, the values of the custom headers like {X-Device-Type} can affect the results of queries by using device type information to influence recommendation results. Besides, the values of the custom headers can influence the results of the queries (e.g. the device type information influences the recommendation results).

Sometimes the value of a proprietary header will be used as part of the entity in a subsequent request. In such cases, the proprietary headers must still be propagated as headers with the subsequent request, despite the duplication of information.

{MUST} support {X-Flow-ID}

The {Flow-ID} is a generic parameter to be passed through service APIs and events and written into log files and traces. A consequent usage of the {Flow-ID} facilitates the tracking of call flows through our system and allows the correlation of service activities initiated by a specific call. This is extremely helpful for operational troubleshooting and log analysis. Main use case of {Flow-ID} is to track service calls of our SaaS fashion commerce platform and initiated internal processing flows (executed synchronously via APIs or asynchronously via published events).

Data Definition

The {Flow-ID} must be passed through:

The following formats are allowed:

  • UUID ({RFC-4122}[RFC-4122])

  • base64 ({RFC-4648}[RFC-4648])

  • base64url ({RFC-4648}#section-5[RFC-4648 Section 5])

  • Random unique string restricted to the character set [a-zA-Z0-9/+_-=] maximal of 128 characters.

Note: If a legacy subsystem can only process Flow-IDs with a specific format or length, it must define this restriction in its API specification, and be generous and remove invalid characters or cut the length to the supported limit.

Hint: In case distributed tracing is supported by {SRE-Tracing}[OpenTracing (internal link)] you should ensure that created spans are tagged using flow_id — see {SRE-Tracing}/blob/master/wg-semantic-conventions/best-practices/flowid.md[How to Connect Log Output with OpenTracing Using Flow-IDs (internal link)] or {SRE-Tracing}/blob/master/wg-semantic-conventions/best-practices.md[Best practises (internal link)].

Service Guidance

  • Services must support {Flow-ID} as generic input, i.e.

    • RESTful API endpoints must support {X-Flow-ID} header in requests

    • Event listeners must support the metadata flow-id from events.

    Note: API-Clients must provide {Flow-ID} when calling a service or producing events. If no {Flow-ID} is provided in a request or event, the service must create a new {Flow-ID}.

  • Services must propagate {Flow-ID}, i.e. use {Flow-ID} received with API calls or consumed events as…​

    • input for all API called and events published during processing

    • data field written for logging and tracing

Hint: This rule also applies to application internal interfaces and events not published via Nakadi (but e.g. via AWS SQS, Kinesis or service specific DB solutions).


1. HTTP/1.1 standard ({RFC-7230}#section-6.1[RFC 7230