Spring REST service: retrieving JSON from Request

2019-01-06 13:42发布

I am building a REST service on Spring 3.1. I am using @EnableWebMVC annotation for that. Since my service will only be accepting JSON requests, I would also like to dump the incoming request into a MongoDB collection for logging (and, later, for data transformation). I would like to access the raw JSON Request (which I could do on a non-spring implementation using "@Content HttpServletRequest request" as a method parameter).

I am a Spring newbie. So, kindly help me with directions to achieve this. Thanks!

UPDATE: The issue is not completely resolved. Only my tests with GET worked. It fails with POST. Therefore unchecked the accepted answer

The issue is, even if I create a HttpServletRequestWrapper, I cannot forward the request after I process and wrap the request. Here is what happens:

Interceptor:

public class DBLogInterceptor extends HandlerInterceptorAdapter {
    MyRequestWrapper requestWrapper;

    private final static Logger logger = Logger.getLogger(DBLogInterceptor.class);

    @Override
    public boolean preHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler) throws Exception 
    {
        requestWrapper = new MyRequestWrapper(request);
        // Code removed, but it just dumps requestWrapper.getBody() into DB
        return super.preHandle(requestWrapper, response, handler);
    }
}

HTTP POST Servicing method

@RequestMapping(method = RequestMethod.POST, consumes="application/json", produces="application/json", value = "employee")
@ResponseBody
public String updateEntity(@RequestBody Employee emp) {
    // Do some DB Stuff. Anyway, the control flow does not reach this place.
    return "Employee " + emp.getName() + " updated successfully!";
}

Now I get an exception whenever I send a POST:

12:04:53,821 DEBUG DBLogInterceptor:22 - {"name":"Van Damme","dept":"Applied Martial Arts"}
12:04:53,843 DEBUG RequestResponseBodyMethodProcessor:117 - Reading [com.test.webapp.login.domain.Employee] as "application/json" using [org.springframework.http.converter.json.MappingJacksonHttpMessageConverter@154174f9]
12:04:53,850 DEBUG ExceptionHandlerExceptionResolver:132 - Resolving exception from handler [public java.lang.String com.test.webapp.controller.EmployeeService.updateEntity(com.test.webapp.login.domain.Employee)]: java.io.IOException: Stream closed
12:04:53,854 DEBUG ResponseStatusExceptionResolver:132 - Resolving exception from handler [public java.lang.String com.test.webapp.controller.EmployeeService.updateEntity(com.test.webapp.login.domain.Employee)]: java.io.IOException: Streamclosed
12:04:53,854 DEBUG DefaultHandlerExceptionResolver:132 - Resolving exception from handler [public java.lang.String com.test.webapp.controller.EmployeeService.updateEntity(com.test.webapp.login.domain.Employee)]: java.io.IOException: Streamclosed
12:04:53,859 DEBUG DispatcherServlet:910 - Could not complete request
java.io.IOException: Stream closed
        at org.apache.catalina.connector.InputBuffer.read(InputBuffer.java:312)
        at org.apache.catalina.connector.CoyoteInputStream.read(CoyoteInputStream.java:200)
        at org.codehaus.jackson.impl.ByteSourceBootstrapper.ensureLoaded(ByteSourceBootstrapper.java:507)
        at org.codehaus.jackson.impl.ByteSourceBootstrapper.detectEncoding(ByteSourceBootstrapper.java:129)
        at org.codehaus.jackson.impl.ByteSourceBootstrapper.constructParser(ByteSourceBootstrapper.java:224)
        at org.codehaus.jackson.JsonFactory._createJsonParser(JsonFactory.java:785)
        at org.codehaus.jackson.JsonFactory.createJsonParser(JsonFactory.java:561)
        at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1914)
        at org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.readInternal(MappingJacksonHttpMessageConverter.java:124)
        at org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.java:153)
        at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:120)
        at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:91)
        at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:71)
        at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:75)
        at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:156)
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:117)
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:96)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:617)
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:578)
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:923)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:852)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:789)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:225)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:169)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
        at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:927)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:999)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:565)
        at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:307)
        at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.lang.Thread.run(Unknown Source)

I expected the HttpServletRequestWrapper to be taking care of caching the request. But it doesn't happen somehow.

标签: json spring rest
10条回答
2楼-- · 2019-01-06 14:12

You can simply use :

import org.apache.commons.io.IOUtils;
import java.nio.charset.Charset;

String requestBody = IOUtils.toString(request.getInputStream(), Charset.forName("UTF-8").toString());
查看更多
Summer. ? 凉城
3楼-- · 2019-01-06 14:15

In my experiences,just develop as follows: Using the filter in order to wrapper ServletRequest,then you can repeatly use getting request input stream.

查看更多
我命由我不由天
4楼-- · 2019-01-06 14:19

One simple way to do this would be to get the request body as String and then parse as a Java object. You can use this String then as you want.

So in your example:

@RequestMapping(method = RequestMethod.POST, consumes="application/json", produces="application/json", value = "employee")
@ResponseBody
public String updateEntity(@RequestBody String empAsString) {

    // Do whatever with the json as String 
    System.out.println(empAsString);

    // Transform it into the Java Object you want
    ObjectMapper mapper = new ObjectMapper();
    Employee emp = mapper.readValue(empAsString, Employee.class);

    // Do some DB Stuff. Anyway, the control flow does not reach this place.
    return "Employee " + emp.getName() + " updated successfully!";
}

As a note, if you need it as a list you can use:

List<Employee> eventsList =
                mapper.readValue(jsonInString, mapper.getTypeFactory().constructCollectionType(List.class, Employee.class));
查看更多
Root(大扎)
5楼-- · 2019-01-06 14:20

You need to implement the requestWrapper as follows:

public class DocVerificationRequestWrapper extends HttpServletRequestWrapper {
 private final String body;
 public DocVerificationRequestWrapper(HttpServletRequest request) throws IOException {
   super(request);
   StringBuilder stringBuilder = new StringBuilder();
   BufferedReader bufferedReader = null;
   try {
     InputStream inputStream = request.getInputStream();
     if (inputStream != null) {
       bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
       char[] charBuffer = new char[128];
       int bytesRead = -1;
       while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
         stringBuilder.append(charBuffer, 0, bytesRead);
       }
     } else {
       stringBuilder.append("");
     }
   } catch (IOException ex) {
       throw ex;
   } finally {
     if (bufferedReader != null) {
       try {
         bufferedReader.close();
       } catch (IOException ex) {
         throw ex;
       }
     }
   }
   body = stringBuilder.toString();
 }

 @Override
 public ServletInputStream getInputStream() throws IOException {
   final ByteArrayInputStream byteArrayInputStream = new     ByteArrayInputStream(body.getBytes());
   ServletInputStream servletInputStream = new ServletInputStream() {
     public int read() throws IOException {
       return byteArrayInputStream.read();
     }

    @Override
    public boolean isFinished() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean isReady() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void setReadListener(ReadListener listener) {
        // TODO Auto-generated method stub

    }
   };
   return servletInputStream;
 }

 @Override
 public BufferedReader getReader() throws IOException {
   return new BufferedReader(new InputStreamReader(this.getInputStream()));
 }

 public String getBody() {
   return this.body;
 }
}

and then inside the chain.doFilter method of filter class pass the requestWrapper object instead of the request object as follows:

@Override
public void doFilter(ServletRequest arg0, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {

    logger.info("checking token in filter");
    HttpServletRequest request = (HttpServletRequest) arg0;

    DocVerificationRequestWrapper myRequestWrapper = new DocVerificationRequestWrapper((HttpServletRequest) request);

    String body = myRequestWrapper.getBody();
    logger.info("body = "+body);
    Token token = null;
    try {
        JSONObject jsonObj = new JSONObject(body);
        JSONObject tokenObj = (JSONObject) jsonObj.get("token");
        Gson gson = new Gson();
        token = gson.fromJson(tokenObj.toString(), Token.class);

        if(null != token) {
                if(userVerificationService==null){
                ServletContext servletContext = request.getServletContext();
                WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
                userVerificationService = webApplicationContext.getBean(UserVerificationService.class);
            }
                String verStatus = userVerificationService.verifyUser(token);
                logger.info("verStatus = "+verStatus);
                if(verStatus != null && verStatus.equalsIgnoreCase("success")) {
                    chain.doFilter(myRequestWrapper, response); //here replacing request with requestWrapper 
                }else
                    logger.error("Invalid token");
        }else {
                logger.error("token missing.");
        }
    } catch (JSONException e) {
            logger.error("exception in authetication filter " + e);
    }
}

Thus solving the IOStream closed exception.

查看更多
登录 后发表回答