[英]JAX-RS — How to return JSON and HTTP status code together?
我正在編寫 REST Web 應用程序(NetBeans 6.9、JAX-RS、TopLink Essentials)並嘗試返回 JSON和HTTP 狀態代碼。 我已准備好代碼並在從客戶端調用 HTTP GET 方法時返回 JSON。 本質上:
@Path("get/id")
@GET
@Produces("application/json")
public M_機械 getMachineToUpdate(@PathParam("id") String id) {
// some code to return JSON ...
return myJson;
}
但我也想用JSON數據一起返回的HTTP狀態代碼(500,200,204,等)。
我嘗試使用HttpServletResponse
:
response.sendError("error message", 500);
但這讓瀏覽器認為這是一個“真實”的 500,因此輸出網頁是一個常規的 HTTP 500 錯誤頁面。
我想返回一個 HTTP 狀態代碼,以便我的客戶端 JavaScript 可以根據它處理一些邏輯(例如,在 HTML 頁面上顯示錯誤代碼和消息)。 這是可能的還是不應該將 HTTP 狀態代碼用於此類事情?
下面是一個例子:
@GET
@Path("retrieve/{uuid}")
public Response retrieveSomething(@PathParam("uuid") String uuid) {
if(uuid == null || uuid.trim().length() == 0) {
return Response.serverError().entity("UUID cannot be blank").build();
}
Entity entity = service.getById(uuid);
if(entity == null) {
return Response.status(Response.Status.NOT_FOUND).entity("Entity not found for UUID: " + uuid).build();
}
String json = //convert entity to json
return Response.ok(json, MediaType.APPLICATION_JSON).build();
}
看看Response類。
請注意,您應該始終指定一個內容類型,尤其是當您傳遞多個內容類型時,但如果每條消息都將表示為 JSON,您只需使用@Produces("application/json")
注釋該方法
在 REST Web 服務中設置 HTTP 狀態代碼有多種用例,至少有一個沒有在現有答案中充分記錄(即,當您使用 JAXB 使用自動神奇的 JSON/XML 序列化,並且您想返回一個要序列化的對象,但也是一個不同於默認 200 的狀態代碼)。
因此,讓我嘗試列舉不同的用例和每個用例的解決方案:
當您想要返回不同於200 OK
的狀態代碼時,最常見的用例是發生錯誤時。
例如:
在那種情況下,我認為處理問題的最干凈的方法是拋出異常。 此異常將由ExceptionMapper
處理,它將異常轉換為具有適當錯誤代碼的響應。
您可以使用 Jersey 預先配置的默認ExceptionMapper
(我猜它與其他實現相同)並拋出javax.ws.rs.WebApplicationException
任何現有子類。 這些是預定義的異常類型,它們預先映射到不同的錯誤代碼,例如:
等等。你可以在這里找到列表: API
或者,您可以定義自己的自定義異常和ExceptionMapper
類,並通過@Provider
注釋( 本示例的來源)將這些映射器添加到 Jersey:
public class MyApplicationException extends Exception implements Serializable
{
private static final long serialVersionUID = 1L;
public MyApplicationException() {
super();
}
public MyApplicationException(String msg) {
super(msg);
}
public MyApplicationException(String msg, Exception e) {
super(msg, e);
}
}
提供者:
@Provider
public class MyApplicationExceptionHandler implements ExceptionMapper<MyApplicationException>
{
@Override
public Response toResponse(MyApplicationException exception)
{
return Response.status(Status.BAD_REQUEST).entity(exception.getMessage()).build();
}
}
注意:您還可以為您使用的現有異常類型編寫 ExceptionMappers。
設置狀態代碼的另一種方法是使用Response
構建器來構建具有預期代碼的響應。
在這種情況下,您的方法的返回類型必須是javax.ws.rs.core.Response
。 這在其他各種回應中有所描述,例如他的魯尼接受的答案,看起來像這樣:
@GET
@Path("myresource({id}")
public Response retrieveSomething(@PathParam("id") String id) {
...
Entity entity = service.getById(uuid);
if(entity == null) {
return Response.status(Response.Status.NOT_FOUND).entity("Resource not found for ID: " + uuid).build();
}
...
}
您想要設置返回狀態的另一種情況是操作成功時,但您想要返回一個不同於 200 的成功代碼,以及您在正文中返回的內容。
一個常見的用例是當您創建一個新實體( POST
請求)並希望返回有關此新實體或實體本身的信息以及201 Created
狀態代碼時。
一種方法是使用如上所述的響應對象並自己設置請求的主體。 但是,通過這樣做,您將失去使用 JAXB 提供的 XML 或 JSON 自動序列化的能力。
這是返回將被 JAXB 序列化為 JSON 的實體對象的原始方法:
@Path("/")
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public User addUser(User user){
User newuser = ... do something like DB insert ...
return newuser;
}
這將返回新創建用戶的 JSON 表示,但返回狀態將是 200,而不是 201。
現在的問題是,如果我想使用Response
構建器來設置返回碼,我必須在我的方法中返回一個Response
對象。 我如何仍然返回要序列化的User
對象?
解決此問題的一種方法是獲取 servlet 請求對象並自己手動設置響應代碼,如 Garett Wilson 的回答中所示:
@Path("/")
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public User addUser(User user, @Context final HttpServletResponse response){
User newUser = ...
//set HTTP code to "201 Created"
response.setStatus(HttpServletResponse.SC_CREATED);
try {
response.flushBuffer();
}catch(Exception e){}
return newUser;
}
該方法仍然返回一個實體對象,狀態碼將為 201。
請注意,為了使其工作,我必須刷新響應。 這是我們漂亮的 JAX_RS 資源中低級 Servlet API 代碼的令人不快的復蘇,更糟糕的是,它導致標頭在此之后無法修改,因為它們已經在線路上發送了。
在這種情況下,最好的解決方案是使用 Response 對象並將實體設置為在此響應對象上進行序列化。 在這種情況下,讓 Response 對象通用以指示有效負載實體的類型會很好,但目前情況並非如此。
@Path("/")
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public Response addUser(User user){
User newUser = ...
return Response.created(hateoas.buildLinkUri(newUser, "entity")).entity(restResponse).build();
}
在這種情況下,我們使用 Response 構建器類的 created 方法將狀態代碼設置為 201。我們通過 entity() 方法將實體對象(用戶)傳遞給響應。
結果是我們想要的 HTTP 代碼是 401,並且響應的主體與我們剛剛返回 User 對象時的 JSON 完全相同。 它還添加了一個位置標題。
Response 類有許多用於不同狀態(stati ?)的構建器方法,例如:
Response.accepted() Response.ok() Response.noContent() Response.notAcceptable()
注意: hatoas 對象是我開發的一個幫助類,用於幫助生成資源 URI。 您需要在這里提出自己的機制;)
就是這樣。
我希望這個冗長的回復對某人有所幫助:)
hisdrewness 的答案會起作用,但它修改了整個方法,讓提供程序(例如 Jackson+JAXB)自動將返回的對象轉換為某種輸出格式,例如 JSON。 受 Apache CXF 帖子(使用特定於 CXF 的類)的啟發,我找到了一種方法來設置應該在任何 JAX-RS 實現中工作的響應代碼:注入 HttpServletResponse 上下文並手動設置響應代碼。 例如,這里是如何在適當的時候將響應代碼設置為CREATED
。
@Path("/foos/{fooId}")
@PUT
@Consumes("application/json")
@Produces("application/json")
public Foo setFoo(@PathParam("fooID") final String fooID, final Foo foo, @Context final HttpServletResponse response)
{
//TODO store foo in persistent storage
if(itemDidNotExistBefore) //return 201 only if new object; TODO app-specific logic
{
response.setStatus(Response.Status.CREATED.getStatusCode());
}
return foo; //TODO get latest foo from storage if needed
}
改進:在找到另一個相關答案后,我了解到可以將HttpServletResponse
作為成員變量注入,即使對於單例服務類(至少在 RESTEasy 中)! 這是比用實現細節污染 API 更好的方法。 它看起來像這樣:
@Context //injected response proxy supporting multiple threads
private HttpServletResponse response;
@Path("/foos/{fooId}")
@PUT
@Consumes("application/json")
@Produces("application/json")
public Foo setFoo(@PathParam("fooID") final String fooID, final Foo foo)
{
//TODO store foo in persistent storage
if(itemDidNotExistBefore) //return 201 only if new object; TODO app-specific logic
{
response.setStatus(Response.Status.CREATED.getStatusCode());
}
return foo; //TODO get latest foo from storage if needed
}
如果你想讓你的資源層沒有Response
對象,那么我建議你使用@NameBinding
並綁定到ContainerResponseFilter
實現。
這是注釋的主要內容:
package my.webservice.annotations.status;
import javax.ws.rs.NameBinding;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Status {
int CREATED = 201;
int value();
}
這是過濾器的主要內容:
package my.webservice.interceptors.status;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
@Provider
public class StatusFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException {
if (containerResponseContext.getStatus() == 200) {
for (Annotation annotation : containerResponseContext.getEntityAnnotations()) {
if(annotation instanceof Status){
containerResponseContext.setStatus(((Status) annotation).value());
break;
}
}
}
}
}
然后你的資源上的實現就變成了:
package my.webservice.resources;
import my.webservice.annotations.status.StatusCreated;
import javax.ws.rs.*;
@Path("/my-resource-path")
public class MyResource{
@POST
@Status(Status.CREATED)
public boolean create(){
return true;
}
}
如果您因為異常而想要更改狀態代碼,使用 JAX-RS 2.0,您可以像這樣實現一個 ExceptionMapper。 這為整個應用程序處理這種異常。
@Provider
public class UnauthorizedExceptionMapper implements ExceptionMapper<EJBAccessException> {
@Override
public Response toResponse(EJBAccessException exception) {
return Response.status(Response.Status.UNAUTHORIZED.getStatusCode()).build();
}
}
如果您的 WS-RS 需要引發錯誤,為什么不直接使用 WebApplicationException?
@GET
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Path("{id}")
public MyEntity getFoo(@PathParam("id") long id, @QueryParam("lang")long idLanguage) {
if (idLanguage== 0){
// No URL parameter idLanguage was sent
ResponseBuilder builder = Response.status(Response.Status.BAD_REQUEST);
builder.entity("Missing idLanguage parameter on request");
Response response = builder.build();
throw new WebApplicationException(response);
}
... //other stuff to return my entity
return myEntity;
}
我發現用重復的代碼構建一個 json 消息非常有用,如下所示:
@POST
@Consumes("application/json")
@Produces("application/json")
public Response authUser(JsonObject authData) {
String email = authData.getString("email");
String password = authData.getString("password");
JSONObject json = new JSONObject();
if (email.equalsIgnoreCase(user.getEmail()) && password.equalsIgnoreCase(user.getPassword())) {
json.put("status", "success");
json.put("code", Response.Status.OK.getStatusCode());
json.put("message", "User " + authData.getString("email") + " authenticated.");
return Response.ok(json.toString()).build();
} else {
json.put("status", "error");
json.put("code", Response.Status.NOT_FOUND.getStatusCode());
json.put("message", "User " + authData.getString("email") + " not found.");
return Response.status(Response.Status.NOT_FOUND).entity(json.toString()).build();
}
}
JAX-RS 支持標准/自定義 HTTP 代碼。 參見 ResponseBuilder 和 ResponseStatus,例如:
請記住,JSON 信息更多地是關於與資源/應用程序關聯的數據。 HTTP 代碼更多地與所請求的 CRUD 操作的狀態有關。 (至少在 REST-ful 系統中應該是這樣)
請看這里的例子,它最好地說明了這個問題以及它在最新的(2.3.1)版本的 Jersey 中是如何解決的。
https://jersey.java.net/documentation/latest/representations.html#d0e3586
它基本上涉及定義自定義異常並將返回類型保留為實體。 有錯誤時拋出異常,否則返回POJO。
我沒有使用 JAX-RS,但我有一個類似的場景,我使用:
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
另外,請注意,默認情況下,如果 http 代碼為 400 或更多,Jersey 將覆蓋響應正文。
為了讓您指定的實體作為響應正文,請嘗試將以下 init-param 添加到您的 web.xml 配置文件中的 Jersey 中:
<init-param>
<!-- used to overwrite default 4xx state pages -->
<param-name>jersey.config.server.response.setStatusOverSendError</param-name>
<param-value>true</param-value>
</init-param>
以下代碼對我有用。 通過帶注釋的 setter 注入 messageContext 並在我的“add”方法中設置狀態代碼。
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import org.apache.cxf.jaxrs.ext.MessageContext;
public class FlightReservationService {
MessageContext messageContext;
private final Map<Long, FlightReservation> flightReservations = new HashMap<>();
@Context
public void setMessageContext(MessageContext messageContext) {
this.messageContext = messageContext;
}
@Override
public Collection<FlightReservation> list() {
return flightReservations.values();
}
@Path("/{id}")
@Produces("application/json")
@GET
public FlightReservation get(Long id) {
return flightReservations.get(id);
}
@Path("/")
@Consumes("application/json")
@Produces("application/json")
@POST
public void add(FlightReservation booking) {
messageContext.getHttpServletResponse().setStatus(Response.Status.CREATED.getStatusCode());
flightReservations.put(booking.getId(), booking);
}
@Path("/")
@Consumes("application/json")
@PUT
public void update(FlightReservation booking) {
flightReservations.remove(booking.getId());
flightReservations.put(booking.getId(), booking);
}
@Path("/{id}")
@DELETE
public void remove(Long id) {
flightReservations.remove(id);
}
}
使用Microprofile OpenAPI擴展Nthalk的答案,您可以使用@APIResponse注釋將返回代碼與您的文檔對齊。
這允許標記 JAX-RS 方法,如
@GET
@APIResponse(responseCode = "204")
public Resource getResource(ResourceRequest request)
您可以使用ContainerResponseFilter解析此標准化注釋
@Provider
public class StatusFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
if (responseContext.getStatus() == 200) {
for (final var annotation : responseContext.getEntityAnnotations()) {
if (annotation instanceof APIResponse response) {
final var rawCode = response.responseCode();
final var statusCode = Integer.parseInt(rawCode);
responseContext.setStatus(statusCode);
}
}
}
}
}
當您在方法上放置多個注釋時會出現一個警告,例如
@APIResponse(responseCode = "201", description = "first use case")
@APIResponse(responseCode = "204", description = "because you can")
public Resource getResource(ResourceRequest request)
我正在將 jersey 2.0 與消息正文讀取器和寫入器一起使用。 我將我的方法返回類型作為一個特定實體,它也用於消息正文編寫器的實現,並且我返回了相同的 pojo,一個 SkuListDTO。 @GET @Consumes({"application/xml", "application/json"}) @Produces({"application/xml", "application/json"}) @Path("/skuResync")
public SkuResultListDTO getSkuData()
....
return SkuResultListDTO;
我所做的只是改變了這一點,我將編寫器的實現放在一邊,但它仍然有效。
public Response getSkuData()
...
return Response.status(Response.Status.FORBIDDEN).entity(dfCoreResultListDTO).build();
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.