JAX-RS / जर्सी त्रुटि हैंडलिंग को कैसे अनुकूलित करें?


216

मैं जर्सी का उपयोग कर JAX-RS (उर्फ, JSR-311) सीख रहा हूं। मैंने एक रूट रिसोर्स बनाया है और मैं मापदंडों के साथ खेल रहा हूं:

@Path("/hello")
public class HelloWorldResource {

    @GET
    @Produces("text/html")
    public String get(
        @QueryParam("name") String name,
        @QueryParam("birthDate") Date birthDate) {

         // Return a greeting with the name and age
    }
}

यह बहुत अच्छा काम करता है, और वर्तमान लोकेल में किसी भी प्रारूप को संभालता है जिसे दिनांक (स्ट्रिंग) निर्माता (जैसे YYYY / mm / dd और mm / dd / YYYY) द्वारा समझा जाता है। लेकिन अगर मैं एक मूल्य की आपूर्ति करता हूं जो अमान्य है या समझ में नहीं आता है, तो मुझे 404 प्रतिक्रिया मिलती है।

उदाहरण के लिए:

GET /hello?name=Mark&birthDate=X

404 Not Found

मैं इस व्यवहार को कैसे अनुकूलित कर सकता हूं? शायद एक अलग प्रतिक्रिया कोड (शायद "400 खराब अनुरोध")? त्रुटि लॉग करने के बारे में क्या? समस्या निवारण के लिए कस्टम हेडर में समस्या ("खराब तिथि प्रारूप") का वर्णन जोड़ सकते हैं? या 5xx स्टेटस कोड के साथ विवरण के साथ संपूर्ण त्रुटि प्रतिक्रिया लौटाएं?

जवाबों:


271

JAX-RS के साथ त्रुटि हैंडलिंग व्यवहार को अनुकूलित करने के लिए कई दृष्टिकोण हैं। यहां तीन आसान तरीके दिए गए हैं।

पहला दृष्टिकोण एक अपवाद वर्ग बनाना है जो WebApplicationException को विस्तारित करता है।

उदाहरण:

public class NotAuthorizedException extends WebApplicationException {
     public NotAuthorizedException(String message) {
         super(Response.status(Response.Status.UNAUTHORIZED)
             .entity(message).type(MediaType.TEXT_PLAIN).build());
     }
}

और इस नव निर्मित अपवाद को फेंकने के लिए आप बस:

@Path("accounts/{accountId}/")
    public Item getItem(@PathParam("accountId") String accountId) {
       // An unauthorized user tries to enter
       throw new NotAuthorizedException("You Don't Have Permission");
}

ध्यान दें, आपको थ्रोज़ क्लॉज़ में अपवाद घोषित करने की आवश्यकता नहीं है क्योंकि WebApplicationException एक रनटाइम अपवाद है। यह क्लाइंट को 401 प्रतिसाद लौटाएगा।

दूसरा और आसान तरीका यह है कि आप WebApplicationExceptionसीधे अपने कोड में एक इंस्टेंस का निर्माण करें। यह दृष्टिकोण तब तक काम करता है जब तक आपको अपने स्वयं के अनुप्रयोग अपवादों को लागू नहीं करना है।

उदाहरण:

@Path("accounts/{accountId}/")
public Item getItem(@PathParam("accountId") String accountId) {
   // An unauthorized user tries to enter
   throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}

यह कोड क्लाइंट को 401 भी लौटाता है।

बेशक, यह सिर्फ एक सरल उदाहरण है। यदि आवश्यक हो तो आप अपवाद को और अधिक जटिल बना सकते हैं, और आप कभी भी HTTP प्रतिक्रिया कोड को उत्पन्न कर सकते हैं जो आपको चाहिए।

एक अन्य दृष्टिकोण एक मौजूदा अपवाद को लपेटना है, शायद ObjectNotFoundExceptionएक छोटे आवरण वर्ग के साथ जो कि एनोटेशन के ExceptionMapperसाथ एनोटेट किए गए इंटरफ़ेस को लागू करता है @Provider। यह JAX-RS रनटाइम को बताता है, कि यदि लिपटे अपवाद को उठाया जाता है, तो में परिभाषित प्रतिक्रिया कोड वापस करें ExceptionMapper


3
आपके उदाहरण में, सुपर () को कॉल थोड़ा अलग होना चाहिए: सुपर (Response.status (Status.UNAUTHORIZED)। एंटिटी (संदेश) .type ("टेक्स्ट / प्लेन")। Build ()); हालांकि अंतर्दृष्टि के लिए धन्यवाद।
जॉन ओनस्टॉट

65
प्रश्न में उल्लिखित परिदृश्य में, आपको अपवाद फेंकने का मौका नहीं मिलेगा, क्योंकि जर्सी अपवाद को उठाएगा क्योंकि यह इनपुट मूल्य से दिनांक ऑब्जेक्ट का उदाहरण नहीं बना पाएगा। क्या जर्सी अपवाद को बाधित करने का एक तरीका है? एक एक्सेप्शनमैपर इंटरफ़ेस है, हालांकि यह भी विधि द्वारा फेंके गए अपवादों को स्वीकार करता है (इस मामले में प्राप्त करें)।
रीजीव दिवाकरन

7
यदि आप अपने सर्वर लॉग में दिखाई देने वाले अपवाद से बचते हैं, यदि 404 एक वैध मामला है और त्रुटि नहीं है (यानी हर बार जब आप किसी संसाधन के लिए क्वेरी करते हैं, तो यह देखने के लिए कि क्या यह पहले से मौजूद है, आपके दृष्टिकोण के साथ सर्वर में एक स्टैच्रेस दिखाई देता है। लॉग)।
गुइडो

3
यह उल्लेख करते हुए कि जर्सी 2.x कुछ सामान्य HTTP त्रुटि कोड के अपवादों को परिभाषित करता है। इसलिए WebApplication के अपने उपवर्गों को परिभाषित करने के बजाय, आप BadRequestException और NotAuthorizedException जैसे बिल्ट-इन का उपयोग कर सकते हैं। उदाहरण के लिए javax.ws.rs.ClientErrorException के उपवर्गों को देखें। यह भी ध्यान दें कि आप कंस्ट्रक्टरों को एक विवरण स्ट्रिंग प्रदान कर सकते हैं। उदाहरण के लिए: नई BadRequestException को फेंक दें ("प्रारंभ तिथि को अंतिम तिथि से पहले होना चाहिए");
बंफर

1
आप अभी तक एक और दृष्टिकोण का उल्लेख करना भूल गए: ExceptionMapperइंटरफ़ेस को लागू करना (जो कि एक बेहतर तरीका है फिर विस्तार करना)। यहाँ और देखें vvirlan.wordpress.com/2015/10/19/…
ACV

70
@Provider
public class BadURIExceptionMapper implements ExceptionMapper<NotFoundException> {

public Response toResponse(NotFoundException exception){

    return Response.status(Response.Status.NOT_FOUND).
    entity(new ErrorResponse(exception.getClass().toString(),
                exception.getMessage()) ).
    build();
}
}

कक्षा के ऊपर बनाएँ। यह 404 (NotFoundException) को हैंडल करेगा और यहाँ toResponse मेथड में आप अपनी कस्टम प्रतिक्रिया दे सकते हैं। इसी तरह से पैरामैक्सेप्शन आदि हैं जिन्हें आपको कस्टमाइज्ड रिस्पॉन्स देने के लिए मैप करना होगा।


आप ExceptionMapper <Exception> साथ ही जेनेरिक अपवादों के लिए उपयोग कर सकते हैं
सौरभ

1
यह JAX-RS क्लाइंट द्वारा फेंके गए WebApplicationException को भी हैंडल करेगा, जो एरर ऑरिजिन को छिपाता है। बेहतर एक कस्टम अपवाद है (WebApplicationException से व्युत्पन्न नहीं) या पूर्ण प्रतिक्रिया के साथ WebApplications फेंकें। JAX-RS क्लाइंट द्वारा फेंके गए WebApplicationException को सीधे कॉल पर नियंत्रित किया जाना चाहिए, अन्यथा किसी अन्य सेवा की प्रतिक्रिया आपकी सेवा की प्रतिक्रिया के माध्यम से पारित की जाती है, हालांकि यह एक अखंडित आंतरिक सर्वर त्रुटि है।
मार्कस कल

38

जर्सी एक com.sun.jersey.api.ParamException को फेंकता है जब यह मापदंडों को अनमर्ष करने में विफल रहता है तो एक समाधान एक अपवाद बनाने के लिए है जो अपवाद के इन प्रकारों को संभालता है:

@Provider
public class ParamExceptionMapper implements ExceptionMapper<ParamException> {
    @Override
    public Response toResponse(ParamException exception) {
        return Response.status(Status.BAD_REQUEST).entity(exception.getParameterName() + " incorrect type").build();
    }
}

जर्सी के लिए विशेष रूप से पंजीकृत करने के लिए मुझे यह मैपर कहां से बनाना चाहिए?
पेट्रीकियो

1
आपको बस इतना करना है कि @Provider एनोटेशन को जोड़ना है, अधिक जानकारी के लिए यहां देखें: stackoverflow.com/questions/15185299/…
Jan Kronquist

27

आप QueryParam-annotated चर के लिए एक पुन: प्रयोज्य वर्ग भी लिख सकते हैं

public class DateParam {
  private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

  private Calendar date;

  public DateParam(String in) throws WebApplicationException {
    try {
      date = Calendar.getInstance();
      date.setTime(format.parse(in));
    }
    catch (ParseException exception) {
      throw new WebApplicationException(400);
    }
  }
  public Calendar getDate() {
    return date;
  }
  public String format() {
    return format.format(value.getTime());
  }
}

फिर इसे इस तरह उपयोग करें:

private @QueryParam("from") DateParam startDateParam;
private @QueryParam("to") DateParam endDateParam;
// ...
startDateParam.getDate();

यद्यपि इस मामले में त्रुटि हैंडलिंग तुच्छ है (400 प्रतिक्रिया फेंकना), इस वर्ग का उपयोग करने से आप सामान्य रूप से कारक-आउट पैरामीटर को संभाल सकते हैं जिसमें लॉगिंग आदि शामिल हो सकते हैं।


मैं जर्सी में एक कस्टम क्वेरी परम हैंडलर जोड़ने की कोशिश कर रहा हूं (सीएक्सएफ से पलायन) यह उल्लेखनीय रूप से उसी तरह दिखता है जो मैं कर रहा हूं, लेकिन मुझे पता नहीं है कि एक नया प्रदाता कैसे स्थापित करें / कैसे बनाएं। आपकी उपरोक्त कक्षा मुझे यह नहीं दिखाती है। मैं QueryParam के लिए JodaTime DateTime वस्तुओं का उपयोग कर रहा हूं और उन्हें डिकोड करने के लिए एक प्रदाता नहीं है। क्या यह उतना ही आसान है जितना कि इसे उप-वर्ग बनाना, इसे एक स्ट्रिंग निर्माणकर्ता देना और इसे संभालना?
क्रिश्चियन बोंजियोनो

1
बस DateParamऊपर की तरह एक वर्ग बनाएं जो इसके org.joda.time.DateTimeबजाय लपेटता है java.util.Calendar। आप इसका उपयोग स्वयं के @QueryParamबजाय करते DateTimeहैं।
चार्ली ब्रुकिंग

1
यदि आप Joda DateTime का उपयोग कर रहे हैं, तो जर्सी आपके लिए सीधे DateTimeParam के साथ आता है। खुद लिखने की जरूरत नहीं। देखें github.com/dropwizard/dropwizard/blob/master/dropwizard-jersey/...
श्रीकांत

मैं इसे जोड़ने जा रहा हूं क्योंकि इसका सुपर उपयोगी है, लेकिन केवल अगर आप जैक्सन को जर्सी के साथ उपयोग कर रहे हैं। जैक्सन 2.x में एक विधि के JodaModuleसाथ पंजीकृत किया जा सकता है ObjectMapper registerModules। यह सभी प्रकार के जोडा रूपांतरणों को संभाल सकता है। com.fasterxml.jackson.datatype.joda.JodaModule
j_walker_dev

11

एक स्पष्ट समाधान: एक स्ट्रिंग में ले लो, अपने आप को तिथि में परिवर्तित करें। इस तरह आप अपने इच्छित प्रारूप को परिभाषित कर सकते हैं, अपवादों को पकड़ सकते हैं और या तो फिर से फेंक सकते हैं या भेजी जा सकने वाली त्रुटि को अनुकूलित कर सकते हैं। पार्सिंग के लिए, SimpleDateFormat को ठीक काम करना चाहिए।

मुझे यकीन है कि डेटा प्रकारों के लिए भी हैंडर को हुक करने के तरीके हैं, लेकिन शायद थोड़ा सा सरल कोड आपको इस मामले में चाहिए।


7

मुझे भी StaxMan पसंद है कि शायद एक स्ट्रिंग के रूप में उस QueryParam को लागू करेगा, फिर रूपांतरण को संभालना, आवश्यक के रूप में पुनर्विचार करना।

यदि स्थानीय विशिष्ट व्यवहार वांछित और अपेक्षित व्यवहार है, तो आप 400 BAD अनुरोध त्रुटि को वापस करने के लिए उपयोग करेंगे:

throw new WebApplicationException(Response.Status.BAD_REQUEST);

अधिक विकल्पों के लिए javax.ws.rs.core.Response.Status के लिए JavaDoc देखें ।


4

@QueryParam प्रलेखन कहता है

"एनोटेट पैरामीटर, क्षेत्र या संपत्ति का प्रकार टी या तो होना चाहिए:

1) एक आदिम प्रकार
2 हो) एक कंस्ट्रक्टर है जो एक एकल स्ट्रिंग तर्क को स्वीकार करता है
3) एक स्थिर विधि है जिसका नाम valueOf है या एक स्ट्रिंग है जो एकल स्ट्रिंग तर्क स्वीकार करता है (देखें, उदाहरण के लिए, Integer.valueOf (स्ट्रिंग)
4) एक है javax.ws.rs.ext.ext.ParamConverterProvider JAX-RS एक्सटेंशन SPI का पंजीकृत कार्यान्वयन जो एक javax.ws.rs.ext.ParamConverter उदाहरण देता है जो "स्ट्रिंग से" प्रकार के लिए रूपांतरण करने में सक्षम है।
5) सूची, सेट या सॉर्टसेटसेट, जहां टी 2 से ऊपर, 3 या 4 को संतुष्ट करता है। परिणामी संग्रह केवल पढ़ने के लिए है। "

यदि आप नियंत्रित करना चाहते हैं कि स्ट्रिंग के रूप में क्वेरी पैरामीटर उपयोगकर्ता के लिए क्या प्रतिक्रिया देता है तो उसे आपके टाइप T में परिवर्तित नहीं किया जा सकता है, तो आप WebApplicationException को फेंक सकते हैं। Dropwizard आपके पास अपनी आवश्यकताओं के लिए उपयोग कर सकते हैं * परम कक्षाओं के बाद आता है।

बूलियनपरम, डेटाइमपराम, इंटपराम, लोंगपमारम, लोकलडाइटपराम, नॉनमिप्टीस्ट्रिंगपराम, यूआईआईडीपीरम। Https://github.com/dropwizard/dropwizard/tree/masterwdrop-jersey/src/main/java/io/dropwizard/jersey/params देखें

यदि आपको Joda DateTime की आवश्यकता है, तो Dropwizard DateTimeParam का उपयोग करें ।

यदि उपरोक्त सूची आपकी आवश्यकताओं के अनुरूप नहीं है, तो AbstractParam का विस्तार करके अपने को परिभाषित करें। पार्स विधि को ओवरराइड करें। यदि आपको त्रुटि प्रतिक्रिया निकाय पर नियंत्रण की आवश्यकता है, तो त्रुटि विधि को ओवरराइड करें।

इस पर कोडा हेल का अच्छा लेख http://codahale.com/what-makes-jersey-interesting-parameter-classes/ पर है

import io.dropwizard.jersey.params.AbstractParam;

import java.util.Date;

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

public class DateParam extends AbstractParam<Date> {

    public DateParam(String input) {
        super(input);
    }

    @Override
    protected Date parse(String input) throws Exception {
        return new Date(input);
    }

    @Override
    protected Response error(String input, Exception e) {
        // customize response body if you like here by specifying entity
        return Response.status(Status.BAD_REQUEST).build();
    }
}

दिनांक (स्ट्रिंग arg) कंस्ट्रक्टर को पदावनत किया जाता है। यदि आप जावा 8 पर हैं, तो मैं जावा 8 तिथि वर्गों का उपयोग करूंगा। अन्यथा जोडा तिथि समय की सिफारिश की जाती है।


1

यह वास्तव में सही व्यवहार है। जर्सी आपके इनपुट के लिए हैंडलर खोजने की कोशिश करेगी और दिए गए इनपुट से एक ऑब्जेक्ट बनाने की कोशिश करेगी। इस मामले में यह कंस्ट्रक्टर को दिए गए मूल्य X के साथ एक नई दिनांक ऑब्जेक्ट बनाने का प्रयास करेगा। चूंकि यह एक अमान्य तारीख है, कन्वेंशन द्वारा जर्सी 404 लौटाएगा।

आप जो कर सकते हैं वह फिर से लिखना और जन्म तिथि को एक स्ट्रिंग के रूप में रखना है, फिर पार्स करने का प्रयास करें और यदि आपको वह नहीं मिलता है जो आप चाहते हैं, तो आप किसी भी अपवाद मानचित्रण तंत्र द्वारा किसी भी अपवाद को फेंकने के लिए स्वतंत्र हैं (कई हैं) )।


1

मैं उसी मुद्दे का सामना कर रहा था।

मैं एक केंद्रीय स्थान पर सभी त्रुटियों को पकड़ना और उन्हें बदलना चाहता था।

निम्नलिखित कोड है कि मैंने इसे कैसे संभाला है।

निम्नलिखित वर्ग बनाएं जो इस वर्ग को लागू करता है ExceptionMapperऔर @Providerएनोटेशन जोड़ता है । यह सभी अपवादों को संभाल लेगा।

ओवरराइड toResponseविधि और अनुकूलित डेटा के साथ आबादी वाले रिस्पांस ऑब्जेक्ट को वापस करें।

//ExceptionMapperProvider.java
/**
 * exception thrown by restful endpoints will be caught and transformed here
 * so that client gets a proper error message
 */
@Provider
public class ExceptionMapperProvider implements ExceptionMapper<Throwable> {
    private final ErrorTransformer errorTransformer = new ErrorTransformer();

    public ExceptionMapperProvider() {

    }

    @Override
    public Response toResponse(Throwable throwable) {
        //transforming the error using the custom logic of ErrorTransformer 
        final ServiceError errorResponse = errorTransformer.getErrorResponse(throwable);
        final ResponseBuilder responseBuilder = Response.status(errorResponse.getStatus());

        if (errorResponse.getBody().isPresent()) {
            responseBuilder.type(MediaType.APPLICATION_JSON_TYPE);
            responseBuilder.entity(errorResponse.getBody().get());
        }

        for (Map.Entry<String, String> header : errorResponse.getHeaders().entrySet()) {
            responseBuilder.header(header.getKey(), header.getValue());
        }

        return responseBuilder.build();
    }
}

// ErrorTransformer.java
/**
 * Error transformation logic
 */
public class ErrorTransformer {
    public ServiceError getErrorResponse(Throwable throwable) {
        ServiceError serviceError = new ServiceError();
        //add you logic here
        serviceError.setStatus(getStatus(throwable));
        serviceError.setBody(getBody(throwable));
        serviceError.setHeaders(getHeaders(throwable));

    }
    private String getStatus(Throwable throwable) {
        //your logic
    }
    private Optional<String> getBody(Throwable throwable) {
        //your logic
    }
    private Map<String, String> getHeaders(Throwable throwable) {
        //your logic
    }
}

//ServiceError.java
/**
 * error data holder
 */
public class ServiceError {
    private int status;
    private Map<String, String> headers;
    private Optional<String> body;
    //setters and getters
}

1

दृष्टिकोण 1: WebApplicationException वर्ग का विस्तार करके

WebApplicationException बढ़ाकर नया अपवाद बनाएं

public class RestException extends WebApplicationException {

         private static final long serialVersionUID = 1L;

         public RestException(String message, Status status) {
         super(Response.status(status).entity(message).type(MediaType.TEXT_PLAIN).build());
         }
}

अब जब भी आवश्यकता हो, 'RestException' को फेंकें।

public static Employee getEmployee(int id) {

         Employee emp = employees.get(id);

         if (emp == null) {
                 throw new RestException("Employee with id " + id + " not exist", Status.NOT_FOUND);
         }
         return emp;
}

आप इस लिंक पर पूरा आवेदन देख सकते हैं ।

दृष्टिकोण 2: अपवाद अपवाद लागू करें

निम्नलिखित मैपर 'DataNotFoundException' प्रकार के अपवाद को संभालता है

@Provider
public class DataNotFoundExceptionMapper implements
        ExceptionMapper<DataNotFoundException> {

    @Override
    public Response toResponse(DataNotFoundException ex) {
        ErrorMessage model = new ErrorMessage(ex.getErrorCode(),
                ex.getMessage());
        return Response.status(Status.NOT_FOUND).entity(model).build();
    }

}

आप इस लिंक पर पूरा आवेदन देख सकते हैं ।


0

यदि आप ब्राउज़र लॉगिन विंडो खोलना चाहते हैं तो @Steven Lavine उत्तर के विस्तार के रूप में। उपयोगकर्ता द्वारा अभी तक प्रमाणित नहीं किए जाने पर मुझे फ़िल्टर से रिस्पांस ( MDN HTTP प्रमाणीकरण ) को ठीक से वापस करने के लिए मुश्किल था

इसने मुझे ब्राउज़र लॉगिन को मजबूर करने के लिए रिस्पांस बनाने में मदद की, हेडर के अतिरिक्त संशोधन पर ध्यान दिया। यह स्थिति कोड को 401 पर सेट करेगा और उस शीर्ष लेख को सेट करेगा जो ब्राउज़र को उपयोगकर्ता नाम / पासवर्ड संवाद खोलने के लिए प्रेरित करता है।

// The extended Exception class
public class NotLoggedInException extends WebApplicationException {
  public NotLoggedInException(String message) {
    super(Response.status(Response.Status.UNAUTHORIZED)
      .entity(message)
      .type(MediaType.TEXT_PLAIN)
      .header("WWW-Authenticate", "Basic realm=SecuredApp").build()); 
  }
}

// Usage in the Filter
if(headers.get("Authorization") == null) { throw new NotLoggedInException("Not logged in"); }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.