JAX-RS — Cara mengembalikan JSON dan kode status HTTP bersama-sama?

I'm menulis REST web app (NetBeans 6.9, JAX-RS, TopLink Essentials) dan mencoba untuk kembali JSON dan kode status HTTP. Saya memiliki kode siap dan bekerja yang mengembalikan JSON ketika HTTP MENDAPATKAN metode ini disebut dari klien. Pada dasarnya:

@Path("get/id")
@GET
@Produces("application/json")
public M_機械 getMachineToUpdate(@PathParam("id") String id) {

    // some code to return JSON ...

    return myJson;
}

Tapi aku juga ingin mengembalikan kode status HTTP (500, 200, 204, dll.) bersama dengan data JSON.

Saya mencoba untuk menggunakan HttpServletResponse:

response.sendError("error message", 500);

Tapi hal ini membuat browser pikir itu's "nyata" 500 sehingga output halaman web biasa HTTP error 500 halaman.

Saya ingin mengembalikan kode status HTTP sehingga saya client-side JavaScript dapat menangani beberapa logika yang tergantung di atasnya (misalnya untuk menampilkan kode kesalahan dan pesan pada halaman HTML). Ini mungkin atau harus kode status HTTP tidak dapat digunakan untuk hal seperti itu?

Mengomentari pertanyaan (3)
Larutan

Berikut ini's contoh:

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

Lihatlah Respon kelas.

Perhatikan bahwa anda harus selalu menentukan jenis konten, terutama jika anda melewati beberapa jenis konten, tetapi jika setiap pesan akan direpresentasikan sebagai JSON, anda hanya dapat membubuhi keterangan dengan metode @Menghasilkan("application/json")

Komentar (4)

Ada beberapa kasus penggunaan untuk pengaturan kode status HTTP di REST web service, dan setidaknya satu itu tidak cukup didokumentasikan dalam ada jawaban (yaitu ketika anda menggunakan auto-ajaib JSON/XML serialization menggunakan JAXB, dan anda ingin mengembalikan sebuah objek yang akan serial, tapi juga kode status yang berbeda dari default 200). Jadi biarkan saya mencoba dan menghitung kasus penggunaan yang berbeda dan solusi untuk masing-masing:

  1. Kode galat (500, 404,...)

    Penggunaan yang paling umum terjadi ketika anda ingin mengembalikan kode status yang berbeda dari 200 OK adalah ketika terjadi kesalahan. Misalnya:

  • entitas yang diminta tapi itu doesn't ada (404)
  • permintaan semantik yang salah (400)
  • pengguna tidak resmi (401)
  • ada masalah dengan koneksi database (500)
  • dll.. a) Melempar pengecualian

    Dalam hal ini, saya berpikir bahwa terbersih cara untuk menangani masalah ini adalah untuk melempar sebuah exception. Pengecualian ini akan ditangani oleh sebuah ExceptionMapper, yang akan menerjemahkan pengecualian menjadi respon sesuai dengan kode kesalahan. Anda dapat menggunakan default ExceptionMapper yang datang pra-dikonfigurasi dengan Jersey (dan saya kira itu's sama dengan implementasi lainnya) dan membuang apapun yang ada sub-kelas dari javax.ws.rs.WebApplicationException. Ini adalah pra-didefinisikan jenis pengecualian yang sudah dipetakan untuk kode kesalahan yang berbeda, misalnya:

  • BadRequestException (400)
  • InternalServerErrorException (500)
  • NotFoundException (404) Dll. Anda dapat menemukan daftar di sini: API Atau, anda dapat menentukan sendiri kustom pengecualian dan ExceptionMapper kelas, dan menambahkan ini pembuat peta untuk Jersey dengan maksud dari @Provider penjelasan (sumber dari contoh ini):
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);
    }
}

Penyedia :

    @Provider
    public class MyApplicationExceptionHandler implements ExceptionMapper 
    {
        @Override
        public Response toResponse(MyApplicationException exception) 
        {
            return Response.status(Status.BAD_REQUEST).entity(exception.getMessage()).build();  
        }
    }

Catatan: anda juga dapat menulis ExceptionMappers untuk ada pengecualian jenis yang anda gunakan. b) Menggunakan Respon builder

Cara lain untuk menetapkan status kode ini untuk menggunakan Respon builder untuk membangun respon dengan tujuan kode. Dalam kasus tersebut, metode's return type harus javax.ws.rs.inti.Respon. Hal ini dijelaskan dalam berbagai tanggapan lain seperti hisdrewness' jawaban yang diterima dan terlihat seperti ini :

@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();
    }
    ...
}
  1. Sukses, tapi tidak 200

    Lain halnya bila anda ingin mengatur kembali status adalah ketika operasi itu sukses, tapi anda ingin kembali sukses kode yang berbeda dari 200, bersama dengan konten yang anda kembali dalam tubuh. Yang sering terjadi adalah ketika anda membuat entitas baru (POST permintaan) dan ingin kembali info tentang entitas baru ini atau mungkin entitas itu sendiri, bersama-sama dengan 201 Dibuat kode status. Salah satu pendekatan adalah dengan menggunakan respon objek seperti yang dijelaskan di atas dan mengatur tubuh dari permintaan sendiri. Namun, dengan melakukan ini, anda kehilangan kemampuan untuk menggunakan otomatis serialisasi XML atau JSON yang disediakan oleh JAXB. Ini adalah metode asli kembali suatu entitas objek yang akan bersambung ke JSON dengan JAXB:

@Path("/")
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public User addUser(User user){
    User newuser = ... do something like DB insert ...
    return newuser;
}

Ini akan mengembalikan JSON representasi dari pengguna yang baru dibuat, tapi kembali status akan menjadi 200, tidak 201. Sekarang masalahnya adalah jika saya ingin menggunakan Respon pembangun untuk mengatur kode kembali, aku harus kembali Respon objek dalam metode saya. Bagaimana saya masih kembali User objek yang akan serial? a) Mengatur kode pada servlet respon

Salah satu pendekatan untuk memecahkan masalah ini adalah untuk mendapatkan sebuah servlet permintaan objek dan mengatur respon kode secara manual diri kita sendiri, seperti yang ditunjukkan dalam Garett Wilson's jawaban :

@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;
}

Metode ini masih kembali suatu entitas objek dan kode status akan menjadi 201. Perhatikan bahwa untuk membuatnya bekerja, saya harus menyiram respon. Ini adalah aplikasi menyenangkan kebangkitan tingkat rendah Servlet API kode di kami bagus JAX_RS sumber daya, dan jauh lebih buruk, hal itu menyebabkan header untuk unmodifiable setelah ini karena mereka sudah dikirim pada kawat. b) Menggunakan respon objek dengan entitas

Solusi terbaik, dalam hal ini, adalah dengan menggunakan Respon objek dan mengatur entitas yang akan serial ini respon objek. Akan lebih baik untuk membuat Respon objek generik untuk menunjukkan jenis muatan entitas dalam kasus itu, tapi tidak saat ini kasus tersebut.

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

Dalam hal ini, kita gunakan dibuat metode Respon builder kelas dalam rangka untuk mengatur kode status ke 201. Kami melewati entitas objek (user) untuk respon melalui entitas() metode. Hasilnya adalah bahwa HTTP kode 401 seperti yang kita inginkan, dan tubuh respon yang sama persis JSON seperti sebelumnya ketika kami baru saja kembali objek Pengguna. Ia juga menambahkan lokasi header. Respon kelas memiliki sejumlah builder metode untuk status yang berbeda (amerika ?) seperti : Respon.diterima() Respon.ok() Respon.tidak adakonten() Respon.notAcceptable() NB: hateoas objek adalah sebuah kelas helper yang saya kembangkan untuk membantu menghasilkan sumber daya Uri. Anda akan perlu untuk datang dengan anda sendiri mekanisme di sini ;) Yang's tentang hal itu. Saya harap panjang ini membantu respon seseorang :)

Komentar (2)

Jawaban oleh hisdrewness akan bekerja, tetapi memodifikasi seluruh pendekatan untuk membiarkan penyedia seperti Jackson+JAXB secara otomatis mengkonversi objek kembali ke beberapa format output seperti JSON. Terinspirasi oleh Apache CXF pos (yang menggunakan CXF-kelas tertentu) I've ditemukan salah satu cara untuk mengatur respon kode yang harus bekerja di setiap JAX-RS implementasi: inject yang HttpServletResponse konteks dan secara manual mengatur kode respon. Sebagai contoh, berikut adalah cara untuk mengatur kode respon untuk MENCIPTAKAN saat yang tepat.

@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
}

Improvement: Setelah menemukan lain yang terkait jawaban, saya belajar bahwa seseorang dapat menyuntikkan HttpServletResponse sebagai variabel anggota, bahkan untuk singleton layanan kelas (setidaknya di RESTEasy)!! Ini adalah banyak pendekatan yang lebih baik dari polusi API dengan rincian pelaksanaan. Itu akan terlihat seperti ini:

@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
}
Komentar (5)

Jika anda ingin menyimpan sumber daya lapisan bersih dari Respon benda-benda, maka saya sarankan anda menggunakan @NameBinding dan mengikat untuk implementasi ContainerResponseFilter.

Berikut ini's daging anotasi:

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

Berikut ini's daging filter:

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;
        }
      }
    }
  }
}

Dan kemudian pelaksanaan pada sumber daya anda hanya menjadi:

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;
  }
}
Komentar (4)

Dalam kasus anda ingin mengubah kode status karena pengecualian, dengan JAX-RS 2.0 anda dapat menerapkan ExceptionMapper seperti ini. Ini menangani semacam ini pengecualian untuk seluruh aplikasi.

@Provider
public class UnauthorizedExceptionMapper implements ExceptionMapper {

    @Override
    public Response toResponse(EJBAccessException exception) {
        return Response.status(Response.Status.UNAUTHORIZED.getStatusCode()).build();
    }

}
Komentar (0)

Jika anda WS-RS perlu meningkatkan kesalahan mengapa tidak hanya menggunakan 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;
}
Komentar (1)

JAX-RS memiliki dukungan untuk standar/custom HTTP kode. Lihat ResponseBuilder dan ResponseStatus, misalnya:

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

Perlu diingat bahwa JSON informasi lebih lanjut tentang data-data yang berhubungan dengan sumber daya/aplikasi. HTTP kode lebih lanjut tentang status operasi CRUD yang diminta. (setidaknya itu adalah bagaimana hal itu's seharusnya di SISA-ful sistem)

Komentar (1)

Silahkan lihat contoh di sini, itu yang terbaik menggambarkan masalah dan bagaimana hal itu diselesaikan dalam terbaru (2.3.1) versi Jersey.

https://jersey.java.net/documentation/latest/representations.html#d0e3586

Hal ini pada dasarnya melibatkan mendefinisikan sebuah Pengecualian kustom dan menjaga jenis kembali sebagai entitas. Ketika ada kesalahan, pengecualian dilemparkan, jika tidak, anda kembali POJO.

Komentar (2)

Saya menemukan itu sangat berguna untuk membangun juga json pesan dengan berulang-ulang kode, seperti ini:

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

I'm tidak menggunakan JAX-RS, tapi saya've punya skenario yang sama di mana saya gunakan:

response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
Komentar (2)

Juga, perhatikan bahwa secara default Jersey akan menimpa respon tubuh dalam kasus http kode 400 atau lebih.

Dalam rangka untuk mendapatkan yang ditetapkan entitas sebagai respon tubuh, cobalah untuk menambahkan berikut init-param untuk anda Jersey di web.xml file konfigurasi :



        <param-namejersey.config.server.response.setStatusOverSendError
        <param-value>true
Komentar (0)

I'm menggunakan jersey 2.0 dengan badan pesan pembaca dan penulis. Saya punya metode jenis kembali sebagai entitas tertentu yang juga digunakan dalam pelaksanaan badan pesan penulis dan saya kembali sama pojo, yang SkuListDTO. @DAPATKAN @Mengkonsumsi({"application/xml", "application/json"}) @Menghasilkan({"application/xml", "application/json"}) @Path("/skuResync")

public SkuResultListDTO getSkuData()
    ....
return SkuResultListDTO;

semua aku berubah ini, aku meninggalkan penulis pelaksanaannya saja dan itu masih bekerja.

public Response getSkuData()
...
return Response.status(Response.Status.FORBIDDEN).entity(dfCoreResultListDTO).build();
Komentar (0)

Berikut kode yang bekerja untuk saya. Menyuntikkan messageContext melalui dijelaskan setter dan pengaturan kode status saya "tambahkan" metode.

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 flightReservations = new HashMap();

    @Context
    public void setMessageContext(MessageContext messageContext) {
        this.messageContext = messageContext;
    }

    @Override
    public Collection 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);
    }
}
Komentar (0)