REST passion for 200
I have long wanted to write this article. I kept wondering which side to enter more correctly. But, suddenly, recently, a similar article appeared on Habré, which caused a storm in a glass. What surprised me most was the fact that the article began to be driven in minuses, although it did not even declare something, but rather raised the question of using web server response codes in REST. The debate flared up hot. And the apotheosis was that the article went into draft ... kilobytes of comments, opinions, etc. just disappeared. Many became karmo victims, consider, for nothing :)
In general, it was the fate of that article that prompted me to write this one. And I really hope that it will be useful and clarify a lot.
I warn you, everything written below is a real experience, not a cognitive balancing act. And so, they drove.
HTTP
The first thing to do is to very clearly separate the layers. The transport layer is http. Well, actually REST. This is a fundamentally important thing in accepting everything and “yourself” in it. Let's talk only about http first.
I used the term “transport layer”. And I did not make a reservation. The thing is that http itself implements the functions of transporting requests to the server and content to the client, regardless of tcp / ip. Yes, it is based on tcp / ip. And it seems, it is necessary to consider it as its transport. But no. And here is why - socket connections are not direct, i.e. this is not a client-server connection. Both the http request and the http response can go a long way through a ton of services. They can be aggregated or decomposed on the contrary. May be cached, may be modified.
Those. both the http request and the http response have their own route. And it does not depend on the end back, nor on the end front. I ask you to pay special attention to this.
The http routes are not static. They can be very complicated. For example, if a balancer is built into the infrastructure, it can send received requests to any of the back-nodes. At the same time, the backend itself can implement its own strategy for working with requests. Some of them will go to microservices directly, some will be processed by the web server itself, some will be supplemented and transferred to someone else, and some will be issued from the cache, etc. This is how the Internet works. Nothing new.
And here it is important to understand - why do we need response codes? The thing is that the whole model described above makes decisions based on them. Those. these are codes that allow you to make infrastructure and transport decisions during http routing.
For example, if the balancer encounters a response code from backing 503, when sending a request, he can take this as a basis to consider that the node is temporarily unavailable. I note that the response with code 503 provides a Retry-After header. Having received from the header the interval for repeated polling, the balancer will leave the node alone for the specified period and will work with the available ones. Moreover, such strategies are implemented “out of the box” by web servers.
A small offtopic for depth of understanding - what if the node responded 500? What should the balancer do? Switch to another? And many will answer - of course, all 5xx grounds for disabling a node. And they will be wrong. Code 500 is an unexpected error code. Those. one that may never happen again. And most importantly, switching to another node may not change anything. Those. we simply disable nodes without the slightest benefit.
In the case of 500, statistics come to our aid. The local WEB server of the node can translate the node itself into unavailable status with a large number of 500 answers. In this case, the balancer contacting this node will receive a 503 response and will not touch it. The result is the same, but now, this solution is meaningful and eliminates “false” responses.
But that is not all. In this situation, monitoring will allow admins to connect to the situation to maintain the node. Those. we get not just the implementation of a highly accessible service, with balancers, etc., but also an effective support process.
And all this allows you to make server response codes. Any WEB application architecture should begin with the design of the transport layer. I hope there is no doubt about that.
REST
I will ask a rhetorical question - what is it? And what did you answer to him? I will not give links to obvious proofs, but most likely it’s not quite what it is in fact :) This is just an ideology, style. Some considerations on the topic - how best to communicate with the back. And not just communicate, but communicate in the WEB infrastructure. Those. based on http. With all those “useful stuff” that I wrote about above. Ultimate decisions to implement your interface are always yours .
Have you ever wondered why a separate transport for REST was not invented? For example, for websocket it is. Yes, it also starts with http, but then, after the connection is established, this is generally a separate song. Why not do the same for REST?
The answer is simple - why? There is a beautiful, ready-made, verified protocol - http. It scales well. Allows you to implement complex, highly accessible services that can cope with a heavy load. All that is needed is to introduce some conceptual rules so that the developers understand each other.
From here follows a simple, obvious conclusion - everything that is inherent in http is inherent in REST. These are inseparable entities. There is no separate REST header, not even a hint that REST is REST. For any REST server, the request is exactly the same as any other. Those. REST is just what we have in mind.
REST http response codes
Let's talk about what code your server should respond to a REST request? Personally, it seems to me that from all the above, the answer is already obvious, because REST is no different from any other request, it must be subject to exactly the same rules. The response code is an integral part of REST and should be relevant to the essence of the response. Those. if the object was not found by request, it is 404, if the client made an incorrect request 400, etc. But, most often, the debate does not end there. Therefore, I will continue.
Is it possible to answer everything with code 200? And who will forbid you? Please ... code 200 is the same code as the others. True, the basis of this approach is a very simple thesis - my system is perfect, it has no errors. If you are a person who can create such systems - this can only be envied!
But most likely ... she's not perfect. And mistakes do happen. And it happens that they happen due to circumstances beyond our control. And here a typical solution is to create your own error coding system. This is bad? Yes this is bad. This is super bad. Let's figure out why.
And so, taking code 200 as the only true one, we take responsibility for developing the whole layer (critical layer) of the system - error handling. Those. the labor of many people to develop this layer is sent to scrap. And the construction of his “bicycle” begins. But this mega-building is doomed to failure.
Let's start with the code. If we are going to answer all 200, we ourselves will have to handle the errors. The classic method is try constructs. Each code segment we wrap with additional code. Handlers who do something useful. For example, they put something in the log. Something important. That will localize the error. And if the error arose not where it was expected? Or if an error occurred in the error handler? Those. this strategy at the code level is not working a priori. And in the end, the interpreter or platform will process your bugs. OS finally. The essence of the bug is that you are not waiting for it. You do not need to hide it, you need to find and fix it. Therefore, if REST responds to some requests with an error of 500, this is normal . And what's more, right .
Let's get back to the question - why is this right? Because:
- Code 500 is an infrastructure token based on which the node on which the problem occurs can be disabled;
- 5xx codes are what is being monitored and if such a code arises, any monitoring system will immediately notify you of this. And the support service in time will be able to connect to the solution of the problem;
- You do not write additional code. Do not waste precious time on this. Do not complicate the architecture. You do not deal with problems unusual for you - you write application code. What they want from you. What are they paying for?
- A trace that falls out by mistake 500 will be much more useful than your attempts to surpass it.
- If the REST request returns 500 code, the front already at the time of processing the response will know by what algorithm to process it. Moreover, the essence of the matter will not change in any way, you have not received anything sensible both from 200 and from 500. But from 500 you have received a profit - the realization that this is an UNEXPECTED error.
- Code 500 will come with a guarantee. No matter how bad or good you wrote your code. This is your fulcrum.
Separately, I will hammer a nail into the entire “body” of code 200:
7. Even if you try very hard to avoid other response codes from the server other than 200 to your requests, you cannot do this. Any intermediary server can respond to your request with absolutely any code. And you SHOULD process such an answer correctly.
Total, at the logical level, the struggle for code 200 is meaningless.
Now let's get back to the infrastructure level. Very often I hear the opinion - the 5xx code is not an application level, it can not be given backing. Ahem, well ... there is a contradiction in the statement itself. You can give. But this code is not an application level. That's more true. To understand this, I propose to consider the case:
You are implementing a gateway. You have several DCs, each with its own communication channel to a certain private service. Well, for example, to pay via VPN. And there is a channel of communication with the Internet. You receive a request for an operation with a gateway, but ... the service is unavailable.And so what should you answer? To whom? This is an infrastructure problem, and, specifically, the back-up ran into it. Of course, you need to boldly answer 503. These actions will lead to the fact that the node will be disabled by the balancer for some time. At the same time, the balancer, if configured correctly, without breaking the connection with the client, will send the request to another node. And ... the end customer, with a high degree of probability, received 200. And not a custom description of the error, which will not help him in any way.
Where and what code to use
The question is not simple. There is no definite answer to it. For each system, a transport layer is designed and the codes in it may be specific.
There are accepted standards. They can be easily found and, again, I will not give obvious proofs. But, let me give you an unobvious one - developer.mozilla.org/en/docs/Web/HTTP/Status
Why him? The thing is that code handlers can behave differently, depending on the implementation and context of “understanding the code." For example, browsers have a caching strategy based on response codes. And some services have their own, custom codes. For example, CloudFlare.
Those. making decisions about the use of codes, you need to base on all the elements included in the transport layer from your code on the back to the code on the client. This is the only way to find the right answers. I will not even try to give everyone a universal pill here.
Roots of evil
This is the third project that I come to suffers from code 200 in REST. It is suffering. There is no other word. If you carefully read everything up to the current moment, you already understand that as soon as the project begins to grow, it has a need for infrastructure development, for its sustainability. Code 200 kills all these attempts in the bud. And the first thing you have to do is break stereotypes.
The root of evil, it seems to me, lies in the fact that the code 500 is the first thing that a web developer encounters in his professional career. It can be said a childhood injury. And all his efforts at first boiled down to getting code 200.
By the way, for some reason, at the same stage, a strong opinion develops that only answers with code 200 can be supplied with a body. Of course, this is not so, and any answer may “come” with any code. Code is a code. The body is the body.
Further, with the development of the developer, he needs to manage the bugs of his own application. But ..., he does not know how to use logs. Cannot configure web server. He is studying. And those very “great ones” are born. Because they are available to him and he can quickly make them. Further, on this "great" he mounts new wheels, strengthens the frame, etc. And this great one becomes his companion for a sufficiently long period of time, until ... until he has really complex, multicomponent tasks. And here, as they say - the entrance to the supermarket with the "great" and roller skating is prohibited.
PS: The author of the mentioned article restored it from the drafts - habr.com/en/post/440382 , so you can familiarize yourself with it too.
PPS: I tried to state all the facets of the need to use relevant response codes in REST. I will not respond to comments, please understand me correctly. I will read them with great attention, but I have nothing to add. Thank you so much for being patient enough to read the article!