在所有 JSON 生成端点上使用 @Produces("application/json") 是一种好习惯吗?是一种、习惯、Produces、JSON

2023-09-06 11:07:00 作者:幸福↘正在加载中…

我们开始将 Jersey/JAX-RS 用于我们的前端代码使用的内部 REST 端点.必须返回结果的端点总是发送 JSON 对象.

We started using Jersey/JAX-RS for internal REST endpoints that get used by our front-end code. Endpoints that have to return a result, always send JSON objects.

出于调试目的,我们使用了 firefox restclient 扩展.直到最近,我只是输入 URL 并点击发送,然后返回显示为 JSON 的内容.

For debugging purposes, we are using the firefox restclient extension. Until recently, I would just enter the URL and hit send, and would get back content displayed as JSON.

但是当我今天早上这样做时,FF 扩展又回来了,告诉我必须将响应类型更改为二进制 (BLOB).这样做会导致显示编码字符串而不是 JSON.

But when I did that this morning, the FF extension comes back and tells me that I have to change the response type to binary (BLOB). Doing so results in displaying an encoded string instead of JSON.

我可以通过设置请求标头(Accept:application/json)来解决这个问题.

I could resolve that by setting a request header (Accept: to be application/json).

做了更多研究,我遇到了这个问题.我的结论是:可能我们应该将 @Produces("application/json") 添加到所有这些端点.

Doing some more research, I came across this question. My conclusion is: probably we should add @Produces("application/json") to all these endpoints.

问题:真的那么简单,还是有充分的技术理由不这样做?

Question: is it really that simple, or are there good technical reasons to not do that?

推荐答案

你应该总是声明 @Produces@Consumes 注释(出于内容协商和HTTP协议正确性的目的,在类级别或方法级别.如果没有这些注释,结果将取决于客户端请求和服务器的默认行为(在不同的实现中可能会有所不同),这会导致无法预测和模棱两可的结果.

You should always declare the @Produces and @Consumes annotations (either at the class level or method level) for the purpose of Content Negotiation and HTTP protocol correctness. Without these annotations, the result will be dependent on the client request and the default behavior of the server (which may be different across implementations), which leads to unpredictable and ambiguous results.

通过这些注释,我们宣传我们可以生产和消费哪些媒体类型.在检索 (GET) 请求中,客户端应发送一个 Accept 标头,其中包含他们期望返回的资源的媒体类型.在创建请求(PUT、POST)时,客户端应该发送一个 Content-Type 标头,告诉服务器他们正在发送的数据是什么媒体类型.如果这些标头与服务器宣传要处理的标头不匹配,那么客户端将收到错误响应,告诉他们问题所在;带有 Retrieve 请求和不匹配的 Accept 标头,响应将是 406 不可接受.对于创建请求和不匹配的 Content-Type 标头,响应将是 415 不支持的媒体类型.

With these annotations, we advertise what media types we can produce and consume. On Retrieve (GET) requests, the client should send an Accept header with the media type of the resource they expect back. And on Create requests (PUT, POST), the client should send a Content-Type header telling the server what media type the data is that they are sending. If these headers don't match what the server is advertised to handle, then the client will get error responses back telling them what the problem is; with a Retrieve request and a non-matching Accept header, the response will be a 406 Not Acceptable. With a Create request and a non-matching Content-Type header, the response will be a 415 Unsupported Media Type.

这就是内容协商的工作原理.并且为了确保我们的服务器按照客户期望的方式运行,我们应该声明我们可以在服务器上处理什么.注释就是这样做的.

This is how content negotiation works. And to make sure our server behaves as the clients expect, we should declare what we can handle on the server. The annotations do just this.

正如您所提到的,当您离开 @Produces 时,客户端告诉您需要更改响应类型.这是因为结果是 Content-Type 响应标头被设置为 application/octet-stream,这就是 这里的答案得出结论.客户端使用 Content-Type 标头来确定如何处理响应.

As you mentioned, when you left off the @Produces, the client told you you needed to change the response type. This is because the result was that the Content-Type response header was set to application/octet-stream, which is what the answers here conclude. Clients use the Content-Type header to determine how to handle the response.

最后一个例子是一个检索请求.如果我们在 Create 端点上省略了 @Consumes,很多不同的事情都会出错.举个例子,我们有一个我们想要接受 JSON 的端点,所以我们创建一个 POJO 来将 JSON 映射到.

That last example was for a Retrieve request. If we left off the @Consumes on a Create endpoint, a lot of different things can go wrong. Take for example we have an endpoint that we want to accept JSON, so we create a POJO to map the JSON to.

@POST
public Response create(Customer customer) {}

为此,它依赖于客户端将请求中的 Content-Type 标头设置为 application/json.但是如果没有 @Consumes 注释,我们基本上是在宣传这个端点能够接受 any 媒体类型,这太荒谬了.@Consumes 注释就像一个守卫说如果你不发送正确类型的数据,你就不能通过".但是由于我们没有守卫,所以所有数据都被允许通过,并且结果是不可预测的,因为根据客户端将 Content-Type 设置为什么,我们不知道 MessageBodyReader1 将处理从实体主体到Customer 的转换.如果没有选择正确的 MessageBodyReader(将 JSON 转换为 POPJO 的那个),那么很可能会导致异常,并且客户端将返回 500 Internal Server Error,这不是具体如获取 415 Unsupported Media Type.

For this to work, it is dependent on the client setting the Content-Type header on the request to application/json. But without the @Consumes annotation, we are basically advertising this endpoint to be able to accept any media type, which is just ridiculous. The @Consumes annotation acts like a guard saying "If you don't send the right type of data, you cannot pass". But since we don't have the guard, all data is allowed through, and the result is unpredictable, because depending on what the client sets the Content-Type to, we don't know what MessageBodyReader1 will handle the conversion from the entity body to Customer. If the correct MessageBodyReader is not chosen (the one that converts JSON to POPJOs), then most likely it will lead to an exception, and the client will get back a 500 Internal Server Error, which is not as specific as getting a 415 Unsupported Media Type.

1.参见 Jersey 文档的第 8 章和第 9 章.它将分别解释如何使用 MessageBodyReaderMessageBodyWriter 将实体主体转换为 Java 对象(反之亦然).

1. See chapter 8 and 9 of the Jersey docs. It will explain how entity bodies are converted to Java objects (and vice versa) using MessageBodyReader and MessageBodyWriter, respectively.