Skip to content

Instantly share code, notes, and snippets.

@borisguery
Last active February 20, 2018 10:28
Show Gist options
  • Save borisguery/7acd746c8b37837977e7d601101db53c to your computer and use it in GitHub Desktop.
Save borisguery/7acd746c8b37837977e7d601101db53c to your computer and use it in GitHub Desktop.
HTTP Cache API Doc

Errors

There are currently 3 types of errors:

  • Server errors
  • Client errors
  • Forms validations errors

The Error object

{
    "error_code": 4009, 
    "type": "client_error",
    "http_status_code": 400, 
    "message": "Invalid parameter provided for {since} parameter, should be a valid and parsable date",
    "extended_message": "Check strtotime() PHP function for valid format",
    "info_url": "https://redmine/projects/project/wiki/Errors#4009"
}

Error types

  • client_error

Wrong/Invalid parameter.

  • authorization_error

Authorization error along with a 401 or a 403.

  • form_validation_error

Form validation error, invalid fields detected.

  • unknown_error

Unknown error/Server error, the server made a boo-boo. Most of the time it is a temporary error.

Server errors

Server errors always returns an HTTP status code >= 500. It is likely unrecoverable and should be temporary. If the problem persist please contact us describing precisely what happened.

If a X-debug-token is present in the headers, please join it to your issue.

Client errors

Client errors are the most current errors, they are mostly addressed to third-party developers to provide helpful informations when a request failed.

There are two levels (actually three) of informations along with the errors:

  • The HTTP status code from 400 to 499 (both inclusive), see List of HTTP status codes for more information about the meaning of http status code.
  • Along with the HTTP status code, the response may include an Error object. The errors are always returned as a collection, therefore, multiple errors may be returned.
  • The Error object contains at least 3 properties: error_code, message, type (see [[#The-Error-object|The Error Object]] below)

Form errors

Form errors are returned when a form validation fails. This may occurs every time you POST, PUT, PATCH or DELETE a resource or a collection. It likely means that one the property of the submitted objects does not pass the validation process. (ie: Min/max length, invalid email adress, etc.)

Form validation errors embed fields errors and looks like:

{
   "message":"An error has occurred while processing your request, make sure your data are valid",
   "error_code":0,
   "type":"form_validation_error",
   "http_status_code":400,
   "error_code_name":"Acme.CoreBundle.Exception.FormErrorException",
   "form_errors":{
      "form_errors":[

      ],
      "field_errors":{
         "username":[
            "user.form.username.already_used"
         ]
      }
   }
}

The form_errors contains global erros which applies to the whole form, for example a bad CRSF token, or empty form.

Each field_errors maps to a form field and returns an array of error messages.

You don't need to localize fields error messages. The localisation is done on server-side.

Debugging APIEdit “I got an error” or “there is something wrong” Whenever you attempt a request to the server and you get an unexpected result, the server returns a special header which is called X-Debug-Token this header contains a unique identifier of the request which helps us to replay what you did and reproduce your use case. An issue submitted without this token is likely to be rejected on an arbitrary basis. Make sure to include it.

You may also provide the full response body including the returned stack trace.

Testing tools

httpie is a nice curl wrapper it eases the use of curl by providing a shorter syntax, colorize responses, and much more awesome stuffs.

  • Requesting an endpoint using an authenticated token in the Accept-Currency header:
$ http GET http://api.example.com/api/users Accept-Currency:"EUR"

Errors

All errors should be assigned a numeric code, in most case, a given range is assigned to an endpoint or a bunch of related resources.

For example, the Auth* endpoint has been assigned the range 4100 to 4200.

4000 range

4001

4100 range

4101

Account suspended. Your account may have been banned or locked by an administrator.

This probably means that a pre-authorization check has failed, most of the time, it means you’ve been banned.

4200 range

4201

4300 range

4301

4400 range

4401

Unknown client detected

It usually means that you're trying to make a request on a device associated resource but the current request can't identify the current device. Make sure you are using a valid User-agent and add a Device-Identifier

4402

Unknown subscription topic

It is likely you are trying to perform an action on a un-existing topic. The topic is valid but it shouldn't exist or has already be deleted.

4500 range

4501

Unknown device detected. Update forbidden.

We were unable to detect your device, it probably means the User-Agent is incorrect or the Device-Identifier is missing.

4502

Invalid registration token

You have probably submitted an empty token or the request format is incorrect.

HTTP Cache

Introduction

HTTP Cache is about using the HTTP/1.0 and HTTP/1.1 specification to implement a strategy of caching through the HTTP layer. There are several caching strategies which may involve different component and actors amongst other.

The HTTP Cache specification is based on two concepts: Invalidation & Expiration. Both allow really efficient caching strategies to be implemented and solve almost all the problematic of working with cache.

Invalidation

Invalidation is the purpose of working with the state of a data, it allows to easily determine if a data is fresh or slate against a given algorithm. For example, a revision number may be used to determine if a data is up-to-date. In some case a serialized data + hash function can do the trick. The algorithm used to generate an unique identifier representing the state of the resource is free to implement on the server side.

Both HTTP/1.0 & HTTP/1.1 provides methods to send and receive the freshness status of the resource, but for the sake of simplicity, we will only talk about the HTTP/1.1 specification.

ETag and If-None-Match

The ETag, Entity Tag, is an unique identifier associated with an URI which is a sort of serialized version of an entity at a given time.

Example:

> GET /users/1
< 200 OK
< Etag: version-2
{"user": {"updated_on": "2012-10-01"}}

So the following user has been updated on 2012-10-01 and is at its version 2. As soon as the entity will change, the version number will be updated resulting in: Etag: version-3.

Now, suppose you need to request the user's profile server time in a short amount of time, you'll unnecessarily download again and again the same json object which is useless, slow and pricy.

The Etag may be used to generate conditional response based on the status of the entity.

Example:

> GET /users/1
< 200 OK
< Etag: version-2
{"user": {"updated_on": "2012-10-01"}}

5 mins later...

> GET /users/1
> If-None-Match: version-2
< 304 Not Modified

In this case, no body has been returned, only the HTTP status code indicating that the result has not changed since the previous request was done. The freshness of the data has been "validated" on the server side based on the entity tag.

Now, let's say the user with id # 1 updates its profile. And you want to request it again.

> GET /users/1
> If-None-Match: version-2
< 200 OK
< Etag: version-3
{"user": {"updated_on": "2012-10-02"}}

The server "invalidates" the previous identifier by returning a new one according the update of the user profile. It obviously appends the up-to-date entity in the response body.

This strategy is particularly useful for randomly updated entities which may or may not change often. It doesn't save an HTTP request (yet, we will see how later), but it saves a lot of bytes on the network which reduces latency and the fees of internet provider.

Expiration

Expiration is a way to determine how long a data should be considered fresh. You define an absolute or relative time in the future to consider the data fresh.

Example:

> GET /users/1
< 200 OK
< Cache-Control: public, max-age=60
{"user": {"updated_on": "2012-10-02"}}

The Cache-Control directive contains a max-age parameter, it specifies how long the data should be considered fresh. It is specified in seconds, so here we consider it fresh for 1 minute.

The expiration strategy may apply to both http client and/or proxy. The proxy part being a server-side issue we will omit it.

Using the max-age parameter you may decide for how long no requests will be done on the server. It means, you need to implement a local cache storage.

In the previous example, the max-age were set for 1 minute, during 1 minute, the client is allowed to use its local cache to fetch the response instead of asking the server.

It is a huge improvement over the Invalidation strategy because it doesn't even need to use the network. It may also be used to navigate offline.

Mixing expiration and invalidation

The invalidation strategy is great to check for change on the server side but require the network to be active, while the expiration allows to completely bypass the server-side.

But some case an entity may not change a for long time.

Example:

> GET /users/1
< 200 OK
< Etag: version-2
< Cache-Control: max-age: 60
{"user": {"updated_on": "2012-10-01"}}

30 secs later... an user wants to request the same URI, but because the Cache-Control directive, the client knows it may use local cache, it won't perform over the network and use a local representation of the entity.

90 secs later... an user requests the same URI, but the expiration is done, though there is still the Etag directive which is used to validate the freshness of the entity.

> GET /users/1
> If-None-Match: version-2
< 200 OK
< Cache-Control: max-age=60
{"user": {"updated_on": "2012-10-01"}}

The result returns the whole entity with the same ETag and an updated expiration. There is subtile difference though, invisible for the end-user, the request has not even reached the backend but the proxy behind it. It means that the response is cached version of the entity previously provided by the backend.

In the deep abyss of the server side here is what happens:

  • The client requests the proxy and ask for validation
  • The proxy requests the backend
  • The backend validates or invalidates the Etag and returns accordingly a 304 or a 200 depending on the validity of the ETag
  • If a 304 Not modified is returned by the backend, the reverse proxy uses its local cache to construct the response for the clients.

You must be concerned by this implementation because it interacts on the server-side performance. Putting validation and expiration together allow to almost never request the backend.

How do I implement this on my client?

Most HTTP Client library already implements such behaviors, make sure it is before to choose one or you may have to implement it yourself.

However, you may have to implement the local storage cache strategy yourself.

Further reading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment