Also the entire problem of the OP would go away if they just implemented opentelemetry tracing to their logs.
Also the entire problem of the OP would go away if they just implemented opentelemetry tracing to their logs.
HTTP code: 200 ok
Body: {"error":"internal server error"}
In some mental models, though, it did make sense. Particularly the one that went, "Well, we never would have errored, if you never called us!"
It also simplifies your business logic in that all the possible user defined idiocies are caught before your business logic actually processes the request.
Some frameworks do this better than others. And rather than documentation, I tend to prefer comprehensive error messages.
That is what they are suggesting. You check the request and return 400 if it’s bad.
A 500 error should be your problem with a stack trace in the log. A 400 error should provide enough description to tell the user it's theirs and how to fix it.
Just marking recoding a 500 to a 400 because of a null pointer error would get noticed on a code review and marked up on a code review.
500 - we fucked up
json-rpc is probably what your describing over http, maybe if you squint enough graphql too
It's is certainly not comprehensive, but it's right there and it works.
Moving to your own solution means that you have to reimplement all of this in every client.
Please elaborate. In my experience, most of HTTP client libraries do not automatically retry any requests, and thank goodness for that since they don't, and can't, know whether such retries are safe or even needed.
> redirection
An example of service where, at the higher business logic level, it makes sense to force the underlying HTTP transport level to emit a 301/302 response, would be appreciated. In my experience, this stuff is usually handled in the load-balancing proxy before the actual service, so it's similar to QoS and network management stuff: the application does not care about it, it just uses TCP.
The same holds true if you are using any programming library: there is a plethora of handlers for HTTP errors.
As for redirection, a common example is offering downloads through S3 using pre-signed URLs (you share a URL with your own domain, but after auth redirect to a pre-signed S3 URL for direct download or upload).
You don't need to reinvent HTTP semantics when HTTP is already there, standard, doing the right thing, compatible with millions of programs all across the stack, out of the box.
HTTP is so well designed it almost makes me angry when people try to sidestep it and inevitably end up causing pain in the future due to some subtle semantic detail that HTTP does right and they didn't even think to reimplement.
And the only solution to such issues (as they arise, and they will) is to slowly reimplement HTTP across the whole stack: oh, you need to monitor your internal server errors? Now you have to configure your monitoring tool (or create your own) to inspect all your response bodies (no matter how huge) and parse their JSON (no matter how irrelevant) instead of just monitoring the status code in the response header and easily ignore the expensive body parsing.
Even worse when people go all the way. If we don't need status codes, why do we need URLs at all? Just POST everything to /api/rpc with an `operation` payload. Congrats, none of your monitoring tools can easily calculate request rates by operation without some application-specific configuration (I wish this was a made up scenario).
Just use HTTP ffs. You'd need a very good reason not to use it.
Shoehorning http protocols error codes as application error codes, drinking the cool aid and calling it best practice is beyond bizzare.
500 is "failed to do anything at all"
Also that is not a controversial take. It is at best a naive or inexperienced take.
We, very roughly, do it like this:
- 200: all good
- 401: we don't know who you are
- 403: you're not allowed to do that
- 400: something's wrong and you can fix it
- 500: something's wrong and you can't fix it
Each response (other than 401) includes a json blob with details that our UI can do something with, but any other consumer of the API or HTTP traffic still knows roughly what's going on.
I've worked in places where we really sweated on getting the perfect HTTP status codes, and I'm not sure it added much benefit.
On POST - I find myself doing logical GETs with POST a lot, because the endpoint requires more information than can be conveyed in URL params. It makes me feel dirty, and it's obviously not RESTful but you know - sometimes you just have to get things done.
The fact that existing tooling relies on some of these conventions is probably a good enough reason to do things this way, but it's not obvious to me that this is because it's actually better rather than inertia. Conventions could be developed around the body of requests as well, and at least to me, it doesn't seem obvious that the amount of information conveyed at the HTTP method/response status layer was necessary to try to separate from the semantics of the request and response bodies. I'm sure that a part of that was due to HTTP supporting different content types for payloads, but nowadays it seems like quite a lot of the common alternatives to JSON APIs were designed not to even use HTTP (GraphQL, gRPC, etc.), which I'd argue is evidence that HTTP isn't necessarily being used as well for APIs as some people would like.
To make something explicit that I've been alluding to, everything I've said is about using APIs in HTTP, not HTTP in the context of viewing webpages in a browser. It really seems like a lot of the complications in HTTP are due to it trying to be sufficient for both browsers and APIs, and in my opinion this comes mostly at the expense of the latter.
It seems you have some alternative in mind but it wasn't presented.
...until you discover an intermediate later strips the body of certain error codes. Like apache, which IIRC includes all 5xx and most 4xx.
4xx or 5xx gets you the default HTTP handling for that kind of error. Occasionally - especially in small examples - that default handling does what you want and saves you duplicating a lot of work. More often it gets in your way.
I'd compare it to browser default styling - in small examples it sounds useful, but in a decent-sized site you just end up having to do a "CSS reset" to get it out of the way before you do your styling.
We've been doing that for 20 years with json-rpc 1.0
--> { "method": "echo", "params": ["Hello JSON-RPC"], "id":1}
<-- { "result": "Hello JSON-RPC", "error": null, "id": 1}
In this context, HTTP is just the transport and HTTP errors are only transport errors.Yes, you throw away lots of HTTP goodies with that, but there are many situations where it makes more sense than some half-assed ReSTish API. YMMV.
I use http status codes to encode how the _request_ was handled, not necessarily the data within the request.
A 400 if you send mangled JSON, but a 200 if the request was valid but does not pass business validation rules.
Inside the 200 response is structured JSON that also has a status that is relevant at the application level.
Otherwise how can for example you tell if a 404 response is because the endpoint doesn't exist, or because the item requested at the endpoint doesn't exist?
I believe it's important to have a separation between what is happening at the API level vs Application, and this approach caters for both.
What about empty required field in JSON? Is it still mangled or it's already BL?
200 OK {status: "failed", errors: ["field X is required"]}
How you deal with this on the application side, what JSON statuses you have etc is up to you.
For example, on a server I'm working on there are helper functions that generate different types of responses. Responding in certain ways will produce a 200, but will also log a warning or error.
On the client side, you can create request helpers that all requests go through and that can resolve requests appropriately, rendering error messages to the user etc.
The main thing is to have a well defined, consistent approach.
Either<Error, Foo>
then, in the HTTP layer, your endpoint code just looks like return fooService
.getFoo(
fooId
) //Either<Error, Foo>
.fold({ //left (error) case
when (it) {
is GetFooErrors.NotAuthenticated -> { Response.status(401).build() }
is GetFooErrors.InalidFooId -> { Response.status(400).build() }
is GetFooErrors.Other -> { Response.status(500).build() }
}
}, { //right case
Response.ok(it)
})
the benefits of this are hard to overstate.* Errors are clearly enumerated in a single place. * Errors are clearly separated from but connected to HTTP, in the appropriate layer. (=the HTTP layer) Developers can tell from a glance at the resource method what the endpoint will return for each outcome, in context. * Errors are guaranteed to be exhaustively mapped because Kotlin enforces that sealed classes are exhaustively mapped at compile time. So a 500 resulting from forgetting to catch a ReusedPasswordException is impossible, and if new errors are added without being mapped to HTTP responses, the compiler will let us know.
It beats exceptions, it beats Go's shitty error handling.
Deciding on end user error handling in a low level is making assumptions that cannot be known at the low level. The caller decides how something is going to be handled and presented, not the callee, or you inevitably miss them in important places and silently miscategorize stuff. Far better to have that scenario lead to a 500 (unmapped error, unknown problem) so it can be fixed.