[java] JAX-RS — JSON과 HTTP 상태 코드를 함께 반환하는 방법?

REST 웹 응용 프로그램 (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 오류 페이지였습니다.

클라이언트 측 JavaScript가 이에 따라 일부 논리를 처리 할 수 ​​있도록 HTTP 상태 코드를 반환하려고합니다 (예 : 오류 코드 및 메시지를 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 웹 서비스에서 HTTP 상태 코드를 설정하는 몇 가지 유스 케이스가 있으며 기존 응답에 최소한 하나는 충분히 문서화되어 있지 않습니다 (예 : JAXB를 사용하여 자동 마법 JSON / XML 직렬화를 사용하는 경우) 직렬화 할 개체뿐만 아니라 기본 200과 다른 상태 코드).

서로 다른 사용 사례와 각 솔루션에 대한 솔루션을 시도하고 열거 해 보겠습니다.

1. 오류 코드 (500, 404, …)

200 OK오류가 발생한 경우 와 다른 상태 코드를 반환하려는 경우 가장 일반적인 사용 사례 입니다.

예를 들면 다음과 같습니다.

  • 엔티티가 요청되었지만 존재하지 않습니다 (404)
  • 요청이 의미 상 올바르지 않습니다 (400)
  • 사용자에게 권한이 부여되지 않음 (401)
  • 데이터베이스 연결에 문제가 있습니다 (500).
  • 기타..

a) 예외를 던져라

이 경우 문제를 처리하는 가장 깨끗한 방법은 예외를 throw하는 것입니다. 이 예외는에 의해 처리되어 ExceptionMapper예외를 적절한 오류 코드가 포함 된 응답으로 변환합니다.

ExceptionMapperJersey로 사전 구성된 기본값 을 사용하고 (다른 구현과 동일하다고 생각합니다)의 기존 하위 클래스를 던질 수 javax.ws.rs.WebApplicationException있습니다. 이들은 서로 다른 오류 코드로 사전 맵핑되는 사전 정의 된 예외 유형입니다. 예를 들면 다음과 같습니다.

  • BadRequestException (400)
  • InternalServerErrorException (500)
  • NotFoundException (404)

기타 목록은 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를 작성할 수도 있습니다.

b) 응답 빌더 사용

상태 코드를 설정하는 또 다른 방법은 Response빌더를 사용 하여 원하는 코드로 응답을 빌드하는 것입니다.

이 경우 메소드의 리턴 유형은이어야합니다 javax.ws.rs.core.Response. 이것은 hisdrewness의 승인 된 답변과 같은 다양한 다른 답변에 설명되어 있으며 다음과 같습니다.

@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();
    }
    ...
}

2. 성공, 그러나 200은 아님

반환 상태를 설정하려는 또 다른 경우는 작업이 성공했지만 본문에 반환하는 내용과 함께 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 표현이 반환되지만 반환 상태는 201이 아니라 200입니다.

이제 문제는 Response빌더를 사용하여 리턴 코드를 설정 하려는 경우 Response메소드에서 오브젝트 를 리턴해야한다는 것입니다. User직렬화 할 개체를 계속 반환하려면 어떻게합니까 ?

a) 서블릿 응답에 코드를 설정하십시오.

이 문제를 해결하는 한 가지 방법은 가렛 윌슨의 답변에서 볼 수 있듯이 서블릿 요청 객체를 얻고 직접 응답 코드를 직접 설정하는 것입니다.

@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 리소스에서 저수준 서블릿 API 코드의 불쾌한 재생이며, 훨씬 더 나쁜 것은 헤더로 이미 헤더로 전송 되었기 때문에 헤더를 수정할 수 없게 만듭니다.

b) 개체와 함께 응답 개체 사용

이 경우 가장 좋은 솔루션은 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();
}

이 경우 상태 코드를 201로 설정하기 위해 Response 빌더 클래스의 작성된 메소드를 사용합니다. entity () 메소드를 통해 엔티티 오브젝트 (사용자)를 응답에 전달합니다.

결과적으로 HTTP 코드는 원하는대로 401이며 응답 본문은 User 객체를 방금 반환했을 때와 똑같은 JSON입니다. 또한 위치 헤더를 추가합니다.

Response 클래스에는 다음과 같은 다양한 상태 (stati?)에 대한 여러 가지 빌더 메소드가 있습니다.

Response.accepted () Response.ok () Response.noContent () Response.notAcceptable ()

NB : hateoas 객체는 리소스 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;
}


답변

JAX-RS는 표준 / 사용자 정의 HTTP 코드를 지원합니다. 다음과 같은 ResponseBuilder 및 ResponseStatus를 참조하십시오.

http://jackson.codehaus.org/javadoc/jax-rs/1.0/javax/ws/rs/core/Response.ResponseBuilder.html#status%28javax.ws.rs.core.Response.Status%29

JSON 정보는 리소스 / 응용 프로그램과 관련된 데이터에 관한 것입니다. HTTP 코드는 요청중인 CRUD 작업의 상태에 대한 자세한 정보입니다. (적어도 그것이 RESTful 시스템에 있어야하는 방법입니다)