Praktik Terbaik untuk Penanganan Error REST API

1. Perkenalan

REST adalah arsitektur stateless di mana klien dapat mengakses dan memanipulasi sumber daya di server. Secara umum, layanan REST menggunakan HTTP untuk mengiklankan sekumpulan sumber daya yang mereka kelola dan menyediakan API yang memungkinkan klien untuk mendapatkan atau mengubah status sumber daya ini.

Dalam tutorial ini, kita akan belajar tentang beberapa praktik terbaik untuk menangani error REST API, termasuk pendekatan yang berguna untuk memberikan informasi yang relevan kepada pengguna, contoh dari situs web skala besar, dan implementasi konkret menggunakan contoh aplikasi Spring REST.

2. Kode Status HTTP

Ketika klien membuat permintaan ke server HTTP - dan server berhasil menerima permintaan tersebut - server harus memberi tahu klien jika permintaan berhasil ditangani atau tidak . HTTP menyelesaikan ini dengan lima kategori kode status:

  • 100 tingkat (Informasi) - Server menerima permintaan
  • 200 level (Berhasil) - Server menyelesaikan permintaan seperti yang diharapkan
  • 300-level (Redirection) - Klien perlu melakukan tindakan lebih lanjut untuk menyelesaikan permintaan
  • 400-level (kesalahan Klien) - Klien mengirim permintaan yang tidak valid
  • 500-level (Server error) - Server gagal memenuhi permintaan yang valid karena ada kesalahan dengan server

Berdasarkan kode respons, klien dapat menduga hasil dari permintaan tertentu.

3. Penanganan Kesalahan

Langkah pertama dalam menangani kesalahan adalah memberikan kode status yang tepat kepada klien. Selain itu, kami mungkin perlu memberikan lebih banyak informasi di badan tanggapan.

3.1. Tanggapan Dasar

Cara termudah kami menangani kesalahan adalah menanggapi dengan kode status yang sesuai .

Beberapa kode respons umum meliputi:

  • 400 Permintaan Buruk - Klien mengirim permintaan yang tidak valid - seperti tidak ada badan atau parameter permintaan yang diperlukan
  • 401 Unauthorized - Klien gagal melakukan otentikasi dengan server
  • 403 Forbidden - Klien diotentikasi tetapi tidak memiliki izin untuk mengakses sumber yang diminta
  • 404 Not Found - Sumber yang diminta tidak ada
  • 412 Prekondisi Gagal - Satu atau lebih kondisi di bidang tajuk permintaan dievaluasi ke salah
  • 500 Internal Server Error - Kesalahan umum terjadi di server
  • 503 Layanan Tidak Tersedia - Layanan yang diminta tidak tersedia

Meskipun dasar, kode-kode ini memungkinkan klien untuk memahami sifat umum kesalahan yang terjadi. Misalnya, kami tahu jika kami menerima kesalahan 403 bahwa kami tidak memiliki izin untuk mengakses sumber daya yang kami minta.

Namun, dalam banyak kasus, kami perlu memberikan detail tambahan dalam tanggapan kami.

500 kesalahan menandakan bahwa beberapa masalah atau pengecualian terjadi di server saat menangani permintaan. Umumnya, kesalahan internal ini bukan urusan klien kami.

Oleh karena itu, untuk meminimalkan respons semacam itu kepada klien, kita harus dengan tekun berusaha menangani atau menangkap kesalahan internal dan menanggapi dengan kode status lain yang sesuai jika memungkinkan . Misalnya, jika pengecualian terjadi karena sumber daya yang diminta tidak ada, kita harus mengeksposnya sebagai kesalahan 404, bukan 500.

Ini bukan untuk mengatakan bahwa 500 tidak boleh dikembalikan, hanya itu harus digunakan untuk kondisi yang tidak terduga - seperti pemadaman layanan - yang mencegah server menjalankan permintaan.

3.2. Respons Kesalahan Musim Semi Default

Prinsip-prinsip ini ada di mana-mana sehingga Spring telah mengkodifikasi mereka dalam mekanisme penanganan kesalahan defaultnya.

Untuk mendemonstrasikan, misalkan kita memiliki aplikasi Spring REST sederhana yang mengelola buku, dengan titik akhir untuk mengambil buku dengan ID-nya:

curl -X GET -H "Accept: application/json" //localhost:8082/spring-rest/api/book/1

Jika tidak ada buku dengan ID 1, kami berharap pengontrol kami akan menampilkan BookNotFoundException . Melakukan GET pada titik akhir ini, kita melihat bahwa pengecualian ini dilemparkan dan isi tanggapannya adalah:

{ "timestamp":"2019-09-16T22:14:45.624+0000", "status":500, "error":"Internal Server Error", "message":"No message available", "path":"/api/book/1" }

Perhatikan bahwa penangan kesalahan default ini menyertakan stempel waktu kapan kesalahan terjadi, kode status HTTP, judul ( bidang kesalahan ), pesan (yang kosong secara default), dan jalur URL tempat kesalahan terjadi.

Bidang ini memberikan informasi kepada klien atau pengembang untuk membantu memecahkan masalah dan juga merupakan beberapa bidang yang membentuk mekanisme penanganan kesalahan standar.

Selain itu, perhatikan bahwa Spring secara otomatis mengembalikan kode status HTTP 500 saat BookNotFoundException kami ditampilkan . Meskipun beberapa API akan mengembalikan kode status 500 atau yang umum lainnya, seperti yang akan kita lihat pada API Facebook dan Twitter - untuk semua kesalahan demi kesederhanaan, yang terbaik adalah menggunakan kode kesalahan yang paling spesifik jika memungkinkan .

Dalam contoh kami, kami dapat menambahkan @ControllerAdvice sehingga ketika BookNotFoundException dilemparkan, API kami mengembalikan status 404 untuk menunjukkan Tidak Ditemukan, bukan 500 Kesalahan Server Internal .

3.3. Tanggapan Lebih Detail

Seperti yang terlihat pada contoh Musim Semi di atas, terkadang kode status tidak cukup untuk menunjukkan secara spesifik kesalahan. Jika diperlukan, kami dapat menggunakan isi respons untuk memberikan informasi tambahan kepada klien. Saat memberikan tanggapan rinci, kami harus menyertakan:

  • Error - Pengenal unik untuk error tersebut
  • Pesan - Pesan singkat yang bisa dibaca manusia
  • Detail - Penjelasan kesalahan yang lebih panjang

Misalnya, jika klien mengirim permintaan dengan kredensial yang salah, kami dapat mengirim respons 401 dengan isi:

{ "error": "auth-0001", "message": "Incorrect username and password", "detail": "Ensure that the username and password included in the request are correct" }

Bidang kesalahan tidak boleh cocok dengan kode tanggapan . Sebaliknya, itu harus menjadi kode kesalahan unik untuk aplikasi kita. Umumnya, tidak ada konvensi untuk bidang kesalahan , harap itu unik.

Usually, this field contains only alphanumerics and connecting characters, such as dashes or underscores. For example, 0001, auth-0001, and incorrect-user-pass are canonical examples of error codes.

The message portion of the body is usually considered presentable on user interfaces. Therefore we should translate this title if we support internationalization. So, if a client sends a request with an Accept-Language header corresponding to French, the title value should be translated to French.

The detail portion is intended for use by developers of clients and not the end-user, so the translation is not necessary.

Additionally, we could also provide a URL — such as the help field — that clients can follow to discover more information:

{ "error": "auth-0001", "message": "Incorrect username and password", "detail": "Ensure that the username and password included in the request are correct", "help": "//example.com/help/error/auth-0001" }

Sometimes, we may want to report more than one error for a request. In this case, we should return the errors in a list:

{ "errors": [ { "error": "auth-0001", "message": "Incorrect username and password", "detail": "Ensure that the username and password included in the request are correct", "help": "//example.com/help/error/auth-0001" }, ... ] }

And when a single error occurs, we respond with a list containing one element. Note that responding with multiple errors may be too complicated for simple applications. In many cases, responding with the first or most significant error is sufficient.

3.4. Standardized Response Bodies

While most REST APIs follow similar conventions, specifics usually vary, including the names of fields and the information included in the response body. These differences make it difficult for libraries and frameworks to handle errors uniformly.

In an effort to standardize REST API error handling, the IETF devised RFC 7807, which creates a generalized error-handling schema.

This schema is composed of five parts:

  1. type — A URI identifier that categorizes the error
  2. title — A brief, human-readable message about the error
  3. status — The HTTP response code (optional)
  4. detail — A human-readable explanation of the error
  5. instance — A URI that identifies the specific occurrence of the error

Instead of using our custom error response body, we can convert our body to:

{ "type": "/errors/incorrect-user-pass", "title": "Incorrect username or password.", "status": 401, "detail": "Authentication failed due to incorrect username or password.", "instance": "/login/log/abc123" }

Note that the type field categorizes the type of error, while instance identifies a specific occurrence of the error in a similar fashion to classes and objects, respectively.

By using URIs, clients can follow these paths to find more information about the error in the same way that HATEOAS links can be used to navigate a REST API.

Adhering to RFC 7807 is optional, but it is advantageous if uniformity is desired.

4. Examples

The above practices are common throughout some of the most popular REST APIs. While the specific names of fields or formats may vary between sites, the general patterns are nearly universal.

4.1. Twitter

For example, let's send a GET request without supplying the required authentication data:

curl -X GET //api.twitter.com/1.1/statuses/update.json?include_entities=true

The Twitter API responds with an error with the following body:

{ "errors": [ { "code":215, "message":"Bad Authentication data." } ] }

This response includes a list containing a single error, with its error code and message. In Twitter's case, no detailed message is present and a general error — rather than a more specific 401 error — is used to denote that authentication failed.

Sometimes a more general status code is easier to implement, as we'll see in our Spring example below. It allows developers to catch groups of exceptions and not differentiate the status code that should be returned. When possible, though, the most specific status code should be used.

4.2. Facebook

Similar to Twitter, Facebook's Graph REST API also includes detailed information in its responses.

For example, let's perform a POST request to authenticate with the Facebook Graph API:

curl -X GET //graph.facebook.com/oauth/access_token?client_id=foo&client_secret=bar&grant_type=baz

We receive the following error:

{ "error": { "message": "Missing redirect_uri parameter.", "type": "OAuthException", "code": 191, "fbtrace_id": "AWswcVwbcqfgrSgjG80MtqJ" } }

Like Twitter, Facebook also uses a generic error — rather than a more specific 400-level error — to denote a failure. In addition to a message and numeric code, Facebook also includes a type field that categorizes the error and a trace ID (fbtrace_id) that acts as an internal support identifier.

5. Conclusion

In this article, we examined some of the best practices of REST API error handling, including:

  • Providing specific status codes
  • Including additional information in response bodies
  • Handling exceptions in a uniform manner

While the details of error handling will vary by application, these general principles apply to nearly all REST APIs and should be adhered to when possible.

Hal ini tidak hanya memungkinkan klien menangani kesalahan secara konsisten, tetapi juga menyederhanakan kode yang kita buat saat mengimplementasikan REST API.

Kode yang direferensikan dalam artikel ini tersedia di GitHub.