POST JSON in body request con Jersey

Ho un progetto web dinamico in Java (distribuito su un server di applicazioni locale Tomcat 7) che utilizza Jersey per creare API REST.

Non utilizzo alcuno strumento di automazione build (quindi le mie librerie vengono aggiunte al percorso di build e il servlet viene inserito nel file web.xml).

Le librerie che sto usando sono:

asm-3.1.jar gson-2.2.1.jar jersey-client-1.0.3.jar jersey-core-1.0.3.jar jersey-json-1.18.jar jersey-server-1.0.3.jar jettison-1.1.jar jsr311-api-1.0.jar mysql-connector-java-5.0.8-bin.jar 

Il mio web.xml è il seguente:

   UserAccount  ServletAdaptor com.sun.jersey.server.impl.container.servlet.ServletAdaptor  com.sun.jersey.api.json.POJOMappingFeature true  1   ServletAdaptor /rest/*   

L’applicazione sta interagendo con un database MySQL. Lo scenario è il seguente: Il database contiene una tabella degli account utente chiamata utenti. Le colonne sono id, nome, username e password.

Ho un metodo POST per verificare se un account (username + password) è valido. (nome utente e password passati come parametri di intestazione) http: // localhost: 8080 / UserAccount / rest / login / doLogin e le intestazioni: username: x password: 1234

 @POST @Path("/doLogin") @Produces(MediaType.APPLICATION_JSON) public String doLogin(@HeaderParam("username") String uname, @HeaderParam("password") String pwd){ String response = ""; if(checkCredentials(uname, pwd)){ response = Utility.constructJSON("login",true); }else{ response = Utility.constructJSON("login", false, "Incorrect Email or Password"); } return response; } 

Come puoi vedere, la risposta produrrà un JSON con vero o falso, basato se l’account utente è valido o meno. E fino ad ora funziona bene. Funziona bene anche facendo un GET e passando i parametri come parametri di query (ma che non è il punto).

Ora sto provando a fare un POST ma stavolta passando il nome utente e la password in un corpo JSON:

 { "username":"x", "password":"1234" } 

Per questo ho creato una class chiamata User.java:

 @XmlRootElement() @XmlAccessorType(XmlAccessType.FIELD) public class User implements Serializable { /** * */ private static final long serialVersionUID = 1L; @XmlElement(name="username") private String username; @XmlElement(name="password") private String password; public String getUsername() { return username; } public String getPassword() { return password; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } @Override public String toString() { // TODO Auto-generated method stub return "username: " + username; } } 

E ho creato un metodo:

  //pass the arguments as JSON body @POST // Path: http://localhost:8080/UserAccount/rest/login/asklogin @Path("/askLogin") // Produces JSON as response @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response askLogin(User user) { System.out.println("Inside POST askLogin"); if(checkCredentials(user.getUsername(), user.getPassword())) { return Response.ok().build(); } else { return Response.serverError().build(); } } 

Sto testando l’API usando Advanced Rest Client da Google Chrome e REST Easy da Firefox.

Ho anche creato una class che implementa MessageBodyReader come segue:

 @Provider public class UserBeanMessageBodyReader implements MessageBodyReader { //used for MessageBodyReader @Override public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return type == User.class; } @Override public User readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { try { JAXBContext jaxbContext = JAXBContext.newInstance(User.class); User user = (User) jaxbContext.createUnmarshaller().unmarshal(entityStream); return user; } catch (JAXBException jaxbException) { jaxbException.printStackTrace(); System.out.println("Error deserializing a User" + jaxbException); } return null; } } 

Dopo il debug, il problema sta arrivando qui:

 User user = (User) jaxbContext.createUnmarshaller().unmarshal(entityStream); 

e va sulla dichiarazione di cattura.

Lo stacktrace è il seguente:

  INFO: Server startup in 1239 ms javax.xml.bind.UnmarshalException - with linked exception: [org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.] at javax.xml.bind.helpers.AbstractUnmarshallerImpl.createUnmarshalException(AbstractUnmarshallerImpl.java:335) at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.createUnmarshalException(UnmarshallerImpl.java:563) at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:249) at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:214) at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:157) at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:204) at ro.useraccount.jersey.UserBeanMessageBodyReader.readFrom(UserBeanMessageBodyReader.java:36) at ro.useraccount.jersey.UserBeanMessageBodyReader.readFrom(UserBeanMessageBodyReader.java:1) at com.sun.jersey.spi.container.ContainerRequest.getEntity(ContainerRequest.java:393) at com.sun.jersey.server.impl.model.method.dispatch.EntityParamDispatchProvider$EntityInjectable.getValue(EntityParamDispatchProvider.java:139) at com.sun.jersey.server.impl.inject.InjectableValuesProvider.getInjectableValues(InjectableValuesProvider.java:43) at com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider$EntityParamInInvoker.getParams(AbstractResourceMethodDispatchProvider.java:126) at com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider$ResponseOutInvoker._dispatch(AbstractResourceMethodDispatchProvider.java:173) at com.sun.jersey.server.impl.model.method.dispatch.ResourceJavaMethodDispatcher.dispatch(ResourceJavaMethodDispatcher.java:67) at com.sun.jersey.server.impl.uri.rules.HttpMethodRule.accept(HttpMethodRule.java:163) at com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(RightHandPathRule.java:111) at com.sun.jersey.server.impl.uri.rules.ResourceClassRule.accept(ResourceClassRule.java:71) at com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(RightHandPathRule.java:111) at com.sun.jersey.server.impl.uri.rules.RootResourceClassesRule.accept(RootResourceClassesRule.java:63) at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:654) at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:612) at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:603) at com.sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.java:309) at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:425) at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:590) at javax.servlet.http.HttpServlet.service(HttpServlet.java:723) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293) at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:861) at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:612) at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:503) at java.lang.Thread.run(Thread.java:745) Caused by: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog. at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:203) at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.fatalError(ErrorHandlerWrapper.java:177) at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:400) at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:327) at com.sun.org.apache.xerces.internal.impl.XMLScanner.reportFatalError(XMLScanner.java:1437) at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:999) at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:606) at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:118) at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:510) at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:848) at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:777) at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141) at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1213) at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:643) at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:243) ... 35 more Error deserializing a Userjavax.xml.bind.UnmarshalException - with linked exception: [org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.] Inside POST askLogin Oct 06, 2016 8:57:27 AM org.apache.catalina.core.StandardWrapperValve invoke SEVERE: Servlet.service() for servlet ServletAdaptor threw exception java.lang.NullPointerException at ro.useraccount.jersey.Login.askLogin(Login.java:67) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider$ResponseOutInvoker._dispatch(AbstractResourceMethodDispatchProvider.java:175) at com.sun.jersey.server.impl.model.method.dispatch.ResourceJavaMethodDispatcher.dispatch(ResourceJavaMethodDispatcher.java:67) at com.sun.jersey.server.impl.uri.rules.HttpMethodRule.accept(HttpMethodRule.java:163) at com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(RightHandPathRule.java:111) at com.sun.jersey.server.impl.uri.rules.ResourceClassRule.accept(ResourceClassRule.java:71) at com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(RightHandPathRule.java:111) at com.sun.jersey.server.impl.uri.rules.RootResourceClassesRule.accept(RootResourceClassesRule.java:63) at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:654) at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:612) at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:603) at com.sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.java:309) at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:425) at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:590) at javax.servlet.http.HttpServlet.service(HttpServlet.java:723) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293) at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:861) at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:612) at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:503) at java.lang.Thread.run(Thread.java:745) 

Cosa ne pensi?

Dovresti dire a Jersey di usare Gson per la gestione di JSON. Puoi farlo implementando le interfacce MessageBodyReader e MessageBodyWriter fornite da Jersey. In questa domanda StackOverflow puoi trovare un esempio (vedi la risposta accettata). Se vuoi aggiungere un contesto ad esso, dai un’occhiata alla sezione JSON del manuale di Jersey 1.x.

EDIT: potrei non capire il tuo codice, ma dov’è la chiamata a Gson nel tuo UserBeanMessageBodyReader ? Prova a scovare il metodo reaedFrom con qualcosa sulla reaedFrom di

 String result = new BufferedReader(new InputStreamReader(entityStream)) .lines().collect(Collectors.joining("\n")); Gson gson = new GSon(); User myUser = gson.fromJson(result, User.class); 

invece di usare JAXBContext (che afaict comprende solo input XML).