Read HttpServletRequest payload [duplicate] - java

I would like to get the XML data from request and response and use it into Rest controller. I tried this:
#RestController()
public class HomeController {
#PostMapping(value = "/v1")
public Response handleMessage(#RequestBody Transaction transaction, HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest request, HttpServletResponse response
System.out.println("!!!!!!! InputStream");
System.out.println(request.getInputStream());
System.out.println(response.getOutputStream());
InputStream in = request.getInputStream();
String readLine;
BufferedReader br = new BufferedReader(new InputStreamReader(in));
while (((readLine = br.readLine()) != null)) {
System.out.println(readLine);
}
}
}
But I get java.io.IOException: UT010029: Stream is closed
What is the proper way to get the content into String variable?
EDIT: I also tried solution with Filter but I'm not aware how to use the request payload into rest controller:
Read request payload:
#Component
public class HttpLoggingFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(HttpLoggingFilter.class);
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
ResettableStreamHttpServletRequest wrappedRequest = new ResettableStreamHttpServletRequest((HttpServletRequest) request);
wrappedRequest.getInputStream().read();
String body = IOUtils.toString(wrappedRequest.getReader());
System.out.println("!!!!!!!!!!!!!!!!!! " + body);
wrappedRequest.resetInputStream();
chain.doFilter(request, response);
}
public class ResettableStreamHttpServletRequest extends HttpServletRequestWrapper {
private byte[] rawData;
private HttpServletRequest request;
private ResettableServletInputStream servletStream;
ResettableStreamHttpServletRequest(HttpServletRequest request) {
super(request);
this.request = request;
this.servletStream = new ResettableServletInputStream();
}
void resetInputStream() {
servletStream.stream = new ByteArrayInputStream(rawData);
}
#Override
public ServletInputStream getInputStream() throws IOException {
if (rawData == null) {
rawData = IOUtils.toByteArray(this.request.getInputStream());
servletStream.stream = new ByteArrayInputStream(rawData);
}
return servletStream;
}
#Override
public BufferedReader getReader() throws IOException {
if (rawData == null) {
rawData = IOUtils.toByteArray(this.request.getInputStream());
servletStream.stream = new ByteArrayInputStream(rawData);
}
String encoding = getCharacterEncoding();
if (encoding != null) {
return new BufferedReader(new InputStreamReader(servletStream, encoding));
} else {
return new BufferedReader(new InputStreamReader(servletStream));
}
}
private class ResettableServletInputStream extends ServletInputStream {
private InputStream stream;
#Override
public int read() throws IOException {
return stream.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 readListener) {
// TODO Auto-generated method stub
}
}
}
}
Rest endpoint:
#RestController()
public class HomeController {
#PostMapping(value = "/v1")
public Response handleMessage(#RequestBody Transaction transaction, HttpServletRequest request, org.zalando.logbook.HttpRequest requestv, HttpServletResponse response) throws Exception {
// Get here request and response and log it into DB
}
}
How I can call HttpLoggingFilter into the Java method handleMessage and get the request as body String? Probably I can make it service and Inject it? Can you give me some advice how I can assess the code?

Here are a bunch of classes to do it. This is a once a OncePerRequestFilter implementation, check here https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/OncePerRequestFilter.html. Basically the problem is that in the chain filter, the request stream and response stream can be read just once. So, need to wrap these 2 streams inside something that can be read more than once.
In the first 2 lines I wrapped request and response inside requestToUse and responseToUse. ResettableStreamHttpServletRequest and ResettableStreamHttpServletResponse are wrapper classes that keeps the original string body inside of them, and every time the stream is needed they return a new stream.Then from there, you forget about request and response and start using requestToUse and responseToUse.
I took this from an old project I did. Actually there are more clases, but I extracted the main parts for you. This may not compile right away. Give it a try and let me know and I will help you to make it work.
public class RequestResponseLoggingFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//here you wrap the request and response into some resetable istream class
HttpServletRequest requestToUse = new ResettableStreamHttpServletRequest(request);
HttpServletResponse responseToUse = new ResettableStreamHttpServletResponse(response);
//you read the request to log it
byte[] payload = IOUtils.toByteArray(requestToUse.getReader(), requestToUse.getCharacterEncoding());
String body = new String(payload, requestToUse.getCharacterEncoding());
//here you log the body request
log.(body);
//let the chain continue
filterChain.doFilter(requestToUse, responseToUse);
// Here we log the response
String response = new String(responseToUse.toString().getBytes(), responseToUse.getCharacterEncoding());
//since you can read the stream just once, you will need it again for chain to be able to continue, so you reset it
ResettableStreamHttpServletResponse responseWrapper = WebUtils.getNativeResponse(responseToUse, ResettableStreamHttpServletResponse.class);
if (responseWrapper != null) {
responseWrapper.copyBodyToResponse(true);
}
}
}
public class ResettableStreamHttpServletRequest extends HttpServletRequestWrapper {
private byte[] rawData;
private ResettableServletInputStream servletStream;
public ResettableStreamHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
rawData = IOUtils.toByteArray(request.getInputStream());
servletStream = new ResettableServletInputStream();
servletStream.setStream(new ByteArrayInputStream(rawData));
}
#Override
public ServletInputStream getInputStream() throws IOException {
servletStream.setStream(new ByteArrayInputStream(rawData));
return servletStream;
}
#Override
public BufferedReader getReader() throws IOException {
servletStream.setStream(new ByteArrayInputStream(rawData));
return new BufferedReader(new InputStreamReader(servletStream));
}
}
public class ResettableStreamHttpServletResponse extends HttpServletResponseWrapper {
private ByteArrayServletOutputStream byteArrayServletOutputStream = new ByteArrayServletOutputStream();
public ResettableStreamHttpServletResponse(HttpServletResponse response) throws IOException {
super(response);
}
/**
* Copy the cached body content to the response.
*
* #param complete whether to set a corresponding content length for the complete cached body content
* #since 4.2
*/
public void copyBodyToResponse(boolean complete) throws IOException {
byte[] array = byteArrayServletOutputStream.toByteArray();
if (array.length > 0) {
HttpServletResponse rawResponse = (HttpServletResponse) getResponse();
if (complete && !rawResponse.isCommitted()) {
rawResponse.setContentLength(array.length);
}
rawResponse.getOutputStream().write(byteArrayServletOutputStream.toByteArray());
if (complete) {
super.flushBuffer();
}
}
}
/**
* The default behavior of this method is to return getOutputStream() on the wrapped response object.
*/
#Override
public ServletOutputStream getOutputStream() throws IOException {
return byteArrayServletOutputStream;
}
/**
* The default behavior of this method is to return getOutputStream() on the wrapped response object.
*/
#Override
public String toString() {
String response = new String(byteArrayServletOutputStream.toByteArray());
return response;
}
}

You dont need to do anything special here, Spring framework will do it for you.
All you need is:
Create a Pojo or Bean which represents your XML data.
Add xml data format dependency to Gradle/Maven which will bind the request xml to your pojo.
compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '2.9.9'
Tell your request handler to accept XML like this:
#RequestMapping(value = "/xmlexample", method = RequestMethod.POST,consumes = "application/xml;charset=UTF-8")
public final boolean transactionHandler(#Valid #RequestBody Transaction transaction) {
log.debug("Received transaction request with data {}", transaction);
return true;
}
And voila, you will have your transaction bean populated with your XML data.

Related

Java Filter modified response is getting truncated

I have a spring java application that has a EncryptDecryptFilter.java which is a "OncePerRequestFilter" within which I am trying to modify the response payload based on certain conditions. All is going well for happy path.
During the un-happy path, when my application code in the RestController throws an exception, that exception is captured by a #ControllerAdvice class and the controller advice class returns a ResponseEntity. Let's say this response is "string1" with a length 105 characters. After this, the call is intercepted by the "OncePerRequestFilter" and when I attempt to modify the response in this filter with a new response, lets say the modified response is "string2" with a length 200 characters, the modified response string2 is truncated to 105 characters and delievered to the client. So, while the response it self is being modified, the setContentLength() on the response has no effect.
Any suggestions on how to fix this?
See response.setContentLength(encryptedResponse.length()); in the below filter
#Component
public class EncryptDecryptFilter extends OncePerRequestFilter {
public static final Gson gson = new GsonBuilder()
.addSerializationExclusionStrategy(new GsonExclusionStrategy())
.setPrettyPrinting()
.create();
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String requestURI = request.getRequestURI();
String requestMethod = request.getMethod();
HttpServletRequest requestToUse = new ResettableStreamHttpServletRequest(request);
BufferResponseWrapper responseToUse = new BufferResponseWrapper(response); // declared as a member of this filter class below
interceptRequest(requestToUse, request);
filterChain.doFilter(requestToUse, responseToUse);
interceptResponse(response, responseToUse);
}
private void interceptRequest(HttpServletRequest requestToUse, HttpServletRequest request) throws IOException {
byte[] payload = IOUtils.toByteArray(requestToUse.getReader(), requestToUse.getCharacterEncoding());
String urlEncodedEncryptedBody = new String(payload, requestToUse.getCharacterEncoding());
System.out.println(urlEncodedEncryptedBody);
AesDecryptionRequest decryptionRequest = gson.fromJson(urlEncodedEncryptedBody, AesDecryptionRequest.class);
decryptionRequest.initialize();
String plainTextBody = AesUtil.decrypt(decryptionRequest);
requestToUse.setAttribute("requestBody", plainTextBody);
}
private void interceptResponse(HttpServletResponse response, BufferResponseWrapper responseToUse) throws IOException {
String responseStr = new String(responseToUse.getWrapperBytes());
System.out.println(responseStr); // full response string with length 105 from #ControllerAdvice
AesEncryptionResponse encryptionResponse = AesUtil.encrypt(responseStr);
String encryptedResponse = gson.toJson(encryptionResponse); // encrypted form of #ControllerAdvice response length 200n chars
response.setContentLength(encryptedResponse.length()); // this does not seem to have any effect, the response is still truncated to 105 chars
response.getOutputStream().write(encryptedResponse.getBytes(StandardCharsets.UTF_8));
}
private final class BufferResponseWrapper extends HttpServletResponseWrapper
{
MyServletOutputStream stream = new MyServletOutputStream();
public BufferResponseWrapper(HttpServletResponse httpServletResponse)
{
super(httpServletResponse);
}
public ServletOutputStream getOutputStream() throws IOException
{
return stream;
}
public PrintWriter getWriter() throws IOException
{
return new PrintWriter(stream);
}
public byte[] getWrapperBytes()
{
return stream.getBytes();
}
}
private final class MyServletOutputStream extends ServletOutputStream
{
private ByteArrayOutputStream out = new ByteArrayOutputStream();
public void write(int b) throws IOException
{
out.write(b);
}
public byte[] getBytes()
{
return out.toByteArray();
}
#Override
public boolean isReady() {
return false;
}
#Override
public void setWriteListener(WriteListener writeListener) {
}
}
}
public class ResettableStreamHttpServletRequest extends HttpServletRequestWrapper {
// this class allows the input stream to be read more than once
private static final String UTF_8 = "UTF-8";
private byte[] rawData;
private HttpServletRequest request;
private ResettableServletInputStream servletStream;
public ResettableStreamHttpServletRequest(HttpServletRequest request) {
super(request);
this.request = request;
this.servletStream = new ResettableServletInputStream();
}
public void resetInputStream() {
servletStream.stream = new ByteArrayInputStream(rawData);
}
public void resetInputStream(byte[] newRawData) {
servletStream.stream = new ByteArrayInputStream(newRawData);
}
#Override
public ServletInputStream getInputStream() throws IOException {
if (rawData == null) {
rawData = IOUtils.toByteArray(this.request.getReader(), UTF_8);
servletStream.stream = new ByteArrayInputStream(rawData);
}
return servletStream;
}
#Override
public BufferedReader getReader() throws IOException {
if (rawData == null) {
rawData = IOUtils.toByteArray(this.request.getReader(), UTF_8);
servletStream.stream = new ByteArrayInputStream(rawData);
}
return new BufferedReader(new InputStreamReader(servletStream, UTF_8));
}
private class ResettableServletInputStream extends ServletInputStream {
private ByteArrayInputStream stream;
#Override
public int read() throws IOException {
return stream.read();
}
#Override
// Returns true when all the data from the stream has been read else it returns false.
public boolean isFinished() {
return stream.available() == 0;
}
#Override
// Returns true if data can be read without blocking else returns false.
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener listener) {
throw new RuntimeException("Not implemented");
}
}
}
Controller Advice class
#ControllerAdvice
public class ExceptionAdvice extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {Exception.class})
protected ResponseEntity<Object> handleException(Exception exception, WebRequest request) {
return handleExceptionInternal(exception, exception.getMessage(), getStandardHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
}
}
Try using .reset() method on response object before you rewrite it.
private void interceptResponse(HttpServletResponse response, BufferResponseWrapper responseToUse) throws IOException {
String responseStr = new String(responseToUse.getWrapperBytes());
System.out.println(responseStr); // full response string with length 105 from #ControllerAdvice
AesEncryptionResponse encryptionResponse = AesUtil.encrypt(responseStr);
String encryptedResponse = gson.toJson(encryptionResponse); // encrypted form of #ControllerAdvice response length 200n chars
response.reset(); <-------------
response.setContentLength(encryptedResponse.length()); // this does not seem to have any effect, the response is still truncated to 105 chars
response.getOutputStream().write(encryptedResponse.getBytes(StandardCharsets.UTF_8));
}
According to the doc
void reset() Clears any data that exists in the buffer as well as the
status code and headers. If the response has been committed, this
method throws an IllegalStateException.
Throws: IllegalStateException
if the response has already been committed

Spring - Change request Content-Type: text/plain to Content-Type: multipart/form-data in a Filter

I’m using custom encryption to communicate front-end and back-end.
I’m trying to change the request Content-Type in a filter (Spring GenericFilterBean) before it reaches the controller.
To be more specific I want to change the request Content-Type: text/plain sent by front-end to Content-Type: multipart/form-data.
I made an infographic:
CustomFilter.java
public class CustomFilter extends GenericFilterBean {
...
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// ORIGINALS
HttpServletRequest originalRequest = (HttpServletRequest) request;
HttpServletResponse originalResponse = (HttpServletResponse) response;
// DECRYPT REQUEST
String bodyDecrypted = bodyDecrypted = decryptRequest(originalRequest);
CryptRequestWrapper wrappedRequest = new CryptRequestWrapper(originalRequest, bodyDecrypted.getBytes());
CryptResponseWrapper wrappedResponse = new CryptResponseWrapper(originalResponse);
// ---------------------------------------------//
// CHANGE CONTENT TYPE HERE? //
// ---------------------------------------------//
// CHAIN
chain.doFilter(wrappedRequest, wrappedResponse);
// ENCRYPT RESPONSE
String responseEncrypted = encryptResponse(vec, wrappedResponse);
response.getOutputStream().write(responseEncrypted.getBytes());
}
private String decryptRequest(HttpServletRequest originalRequest){
// STUFF
}
private String encryptResponse(ModifyResponseBodyWrapper wrappedResponse){
// STUFF
}
}
CryptRequestWrapper.java
public class CryptRequestWrapper extends HttpServletRequestWrapper {
private final Logger log = LoggerFactory.getLogger(CryptRequestWrapper.class);
private final ByteArrayInputStream decryptedDataBAIS;
CryptRequestWrapper(HttpServletRequest request, byte[] decryptedData) {
super(request);
decryptedDataBAIS = new ByteArrayInputStream(decryptedData);
}
#Override
public BufferedReader getReader() throws UnsupportedEncodingException {
return new BufferedReader(new InputStreamReader(decryptedDataBAIS, StandardCharsets.UTF_8.name()));
}
#Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStream() {
#Override
public int read() {
return decryptedDataBAIS.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 readListener) {
// TODO Auto-generated method stub
}
};
}
}
Question
Its possible to do this? What I'm missing?
On the front-end side I tried to send a multipart request with body encrypted, but it's not working because the multipart request seems to need a "key/value" body. This is why i'm sending a text/plain.

Post request data lost after using custom filter and custom requestWrapper in Spring boot

I am writing a Spring boot filter to verify request data before it hits the rest controller. To avoid HttpServletRequest read ServletInputStream twice, I writed a CustomRequestWrapper to wrap it. But When I send a post request(conetentType = "multipart/form-data") by postman , I get a null in rest controller.
Here is my filter:
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
CustomReqeustWrapper requestWrapper = new CustomReqeustWrapper(req);
CustomResponseWrapper responseWrapper = new CustomResponseWrapper(resp);
// get requestBody from requestWrapper and verify.
readContent(requestWrapper);
chain.doFilter(requestWrapper, responseWrapper);
writeContent(response);
}
Here is my RequestWrapper:
public class CustomRequestWrapper extends HttpServletRequestWrapper {
private byte[] requestBody = new byte[0];
public CustomRequestWrapper (HttpServletRequest request) {
super(request);
try {
requestBody = StreamUtils.copyToByteArray(request.getInputStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
#Override
public int read() throws IOException {
return bais.read();
}
#Override
public boolean isFinished() {
return false;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener listener) {
}
};
}
public byte[] getRequestData() {
return requestBody;
}
public void setRequestData(byte[] requestData) {
this.requestBody = requestData;
}
}
Here is my controller:
#PostMapping("/exchange")
public ResponseEntity<Void> keyExchange(HttpServletRequest request, String Key) throws Exception {
// get the "key" is null.
}
If the type of parameter 'key' is String,maybe you should't use 'multipart' data type.
For all I know,I had met this problem once.The wrapper is useless for file parameter.
If you have to use 'multipart' type,perhaps you should use other manner to figure out it.

How to serialize Java object into JSON and return it in servlet filter?

I have this javax.servlet.Filter to check whether client is allowed to access API REST resource.
#Component
public class AuthorizationRequestFilter implements Filter {
public static final String AUTHORIZATION_TOKEN = "X-Access-Token";
#Autowired
#Qualifier("loginService")
private ILoginService loginService;
private void throwUnauthorized(ServletResponse res) throws IOException {
HttpServletResponse response = (HttpServletResponse) res;
response.reset();
response.setHeader("Content-Type", "application/json;charset=UTF-8");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
private void throwForbidden(ServletResponse res) throws IOException {
HttpServletResponse response = (HttpServletResponse) res;
response.reset();
response.setHeader("Content-Type", "application/json;charset=UTF-8");
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
String accessToken = request.getHeader(AUTHORIZATION_TOKEN);
if (StringUtils.isEmpty(accessToken)) {
throwUnauthorized(res);
} else {
AccountLoginData account = loginService.find(accessToken);
if (account == null) {
throwForbidden(res);
}
}
chain.doFilter(req, res);
}
#Override
public void destroy() {
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
}
it works but I would like to in these two throw*() methods write to the client JSON with appropriate information. In another part of this application I use these response message objects to inform client what happened.
For example, when record has not been found:
public class NotFoundResponseMessage extends ResponseMessage {
public NotFoundResponseMessage(String message) {
super(HttpStatus.NOT_FOUND, 1, message);
}
}
and
public class ResponseMessage {
private int status;
private int code;
private String message;
private String reason;
public ResponseMessage(int status, int code, String message, String reason) {
Assert.notNull(reason, "Reason must not be null.");
Assert.isTrue(status > 0, "Status must not be empty.");
this.status = status;
this.code = code;
this.message = message;
this.reason = reason;
}
}
My Question
I would like to return JSON with serialized objects (UnauthorizedResponseMessage and ForbiddenResponseMessage) in my javax.servlet.Filter authorization / authentication filter. I use Spring Boot and Jackson library.
How can I manually serialize ResponseMessage into its JSON representation?
How can I write out this JSON back to the client in my filter class?
Edit 1:
private void throwUnauthorized(ServletResponse res) throws IOException {
HttpServletResponse response = (HttpServletResponse) res;
response.reset();
response.setHeader("Content-Type", "application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"foo\":\"boo\"}");
}
Now I can write out JSON but HTTP 500 is returned, because:
java.lang.IllegalStateException: getWriter() has already been called for this response
at org.apache.catalina.connector.Response.getOutputStream(Response.java:544)
Using Jackson convert Object to JSON, the following is an example
ObjectMapper mapper = new ObjectMapper();
String Json = mapper.writeValueAsString(object);
I had the same problem, the complete solution is the following:
try {
restResponse = service.validate(httpReq);
} catch (ForbiddenException e) {
ObjectMapper mapper = new ObjectMapper();
ResponseObject object = new ResponseObject();
object.setStatus(HttpServletResponse.SC_FORBIDDEN);
object.setMessage(e.getMessage());
object.setError("Forbidden");
object.setTimestamp(String.valueOf(new Date().getTime()));
HttpServletResponse httpResp = (HttpServletResponse) response;
httpResp.reset();
httpResp.setHeader("Content-Type","application/json;charset=UTF-8");
httpResp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
String json = mapper.writeValueAsString(object);
response.getWriter().write(json);
return;
}
and the result is:
Just throw your exceptions from the filter and annotate the thrown exception with #ResponseStatus. This way it automatically gets translated to the given http error code. (you can also define the error message)
Code example:
#ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Error while trying to add the feed.")
public class AddFeedException extends Exception {
private static final long serialVersionUID = 290724913968202592L;
public AddFeedException(Throwable throwable) {
super(throwable);
}
}

Reading httprequest content from spring exception handler

I Am using Spring's #ExceptionHandler annotation to catch exceptions in my controllers.
Some requests hold POST data as plain XML string written to the request body, I want to read that data in order to log the exception.
The problem is that when i request the inputstream in the exception handler and try to read from it the stream returns -1 (empty).
The exception handler signature is:
#ExceptionHandler(Throwable.class)
public ModelAndView exception(HttpServletRequest request, HttpServletResponse response, HttpSession session, Throwable arff)
Any thoughts? Is there a way to access the request body?
My controller:
#Controller
#RequestMapping("/user/**")
public class UserController {
static final Logger LOG = LoggerFactory.getLogger(UserController.class);
#Autowired
IUserService userService;
#RequestMapping("/user")
public ModelAndView getCurrent() {
return new ModelAndView("user","response", userService.getCurrent());
}
#RequestMapping("/user/firstLogin")
public ModelAndView firstLogin(HttpSession session) {
userService.logUser(session.getId());
userService.setOriginalAuthority();
return new ModelAndView("user","response", userService.getCurrent());
}
#RequestMapping("/user/login/failure")
public ModelAndView loginFailed() {
LOG.debug("loginFailed()");
Status status = new Status(-1,"Bad login");
return new ModelAndView("/user/login/failure", "response",status);
}
#RequestMapping("/user/login/unauthorized")
public ModelAndView unauthorized() {
LOG.debug("unauthorized()");
Status status = new Status(-1,"Unauthorized.Please login first.");
return new ModelAndView("/user/login/unauthorized","response",status);
}
#RequestMapping("/user/logout/success")
public ModelAndView logoutSuccess() {
LOG.debug("logout()");
Status status = new Status(0,"Successful logout");
return new ModelAndView("/user/logout/success", "response",status);
}
#RequestMapping(value = "/user/{id}", method = RequestMethod.POST)
public ModelAndView create(#RequestBody UserDTO userDTO, #PathVariable("id") Long id) {
return new ModelAndView("user", "response", userService.create(userDTO, id));
}
#RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
public ModelAndView getUserById(#PathVariable("id") Long id) {
return new ModelAndView("user", "response", userService.getUserById(id));
}
#RequestMapping(value = "/user/update/{id}", method = RequestMethod.POST)
public ModelAndView update(#RequestBody UserDTO userDTO, #PathVariable("id") Long id) {
return new ModelAndView("user", "response", userService.update(userDTO, id));
}
#RequestMapping(value = "/user/all", method = RequestMethod.GET)
public ModelAndView list() {
return new ModelAndView("user", "response", userService.list());
}
#RequestMapping(value = "/user/allowedAccounts", method = RequestMethod.GET)
public ModelAndView getAllowedAccounts() {
return new ModelAndView("user", "response", userService.getAllowedAccounts());
}
#RequestMapping(value = "/user/changeAccount/{accountId}", method = RequestMethod.GET)
public ModelAndView changeAccount(#PathVariable("accountId") Long accountId) {
Status st = userService.changeAccount(accountId);
if (st.code != -1) {
return getCurrent();
}
else {
return new ModelAndView("user", "response", st);
}
}
/*
#RequestMapping(value = "/user/logout", method = RequestMethod.GET)
public void perLogout(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
userService.setOriginalAuthority();
response.sendRedirect("/marketplace/user/logout/spring");
}
*/
#ExceptionHandler(Throwable.class)
public ModelAndView exception(HttpServletRequest request, HttpServletResponse response, HttpSession session, Throwable arff) {
Status st = new Status();
try {
Writer writer = new StringWriter();
byte[] buffer = new byte[1024];
//Reader reader2 = new BufferedReader(new InputStreamReader(request.getInputStream()));
InputStream reader = request.getInputStream();
int n;
while ((n = reader.read(buffer)) != -1) {
writer.toString();
}
String retval = writer.toString();
retval = "";
} catch (IOException e) {
e.printStackTrace();
}
return new ModelAndView("profile", "response", st);
}
}
Thank you
I've tried your code and I've found some mistakes in the exception handler, when you read from the InputStream:
Writer writer = new StringWriter();
byte[] buffer = new byte[1024];
//Reader reader2 = new BufferedReader(new InputStreamReader(request.getInputStream()));
InputStream reader = request.getInputStream();
int n;
while ((n = reader.read(buffer)) != -1) {
writer.toString();
}
String retval = writer.toString();
retval = "";
I've replaced your code with this one:
BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
String line = "";
StringBuilder stringBuilder = new StringBuilder();
while ( (line=reader.readLine()) != null ) {
stringBuilder.append(line).append("\n");
}
String retval = stringBuilder.toString();
Then I'm able to read from InputStream in the exception handler, it works!
If you can't still read from InputStream, I suggest you to check how you POST xml data to the request body.
You should consider that you can consume the Inputstream only one time per request, so I suggest you to check that there isn't any other call to getInputStream(). If you have to call it two or more times you should write a custom HttpServletRequestWrapper like this to make a copy of the request body, so you can read it more times.
UPDATE
Your comments has helped me to reproduce the issue. You use the annotation #RequestBody, so it's true that you don't call getInputStream(), but Spring invokes it to retrieve the request's body. Have a look at the class org.springframework.web.bind.annotation.support.HandlerMethodInvoker: if you use #RequestBody this class invokes resolveRequestBody method, and so on... finally you can't read anymore the InputStream from your ServletRequest. If you still want to use both #RequestBody and getInputStream() in your own method, you have to wrap the request to a custom HttpServletRequestWrapper to make a copy of the request body, so you can manually read it more times.
This is my wrapper:
public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {
private static final Logger logger = Logger.getLogger(CustomHttpServletRequestWrapper.class);
private final String body;
public CustomHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String line = "";
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line).append("\n");
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
logger.error("Error reading the request body...");
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
logger.error("Error closing bufferedReader...");
}
}
}
body = stringBuilder.toString();
}
#Override
public ServletInputStream getInputStream() throws IOException {
final StringReader reader = new StringReader(body);
ServletInputStream inputStream = new ServletInputStream() {
public int read() throws IOException {
return reader.read();
}
};
return inputStream;
}
}
Then you should write a simple Filter to wrap the request:
public class MyFilter implements Filter {
public void init(FilterConfig fc) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(new CustomHttpServletRequestWrapper((HttpServletRequest)request), response);
}
public void destroy() {
}
}
Finally, you have to configure your filter in your web.xml:
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>test.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
You can fire your filter only for controllers that really needs it, so you should change the url-pattern according to your needs.
If you need this feature in only one controller, you can also make a copy of the request body in that controller when you receive it through the #RequestBody annotation.
Recently I faced this issue and solved it slightly differently. With spring boot 1.3.5.RELEASE
The filter was implemented using the Spring class ContentCachingRequestWrapper. This wrapper has a method getContentAsByteArray() which can be invoked multiple times.
import org.springframework.web.util.ContentCachingRequestWrapper;
public class RequestBodyCachingFilter implements Filter {
public void init(FilterConfig fc) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(new ContentCachingRequestWrapper((HttpServletRequest)request), response);
}
public void destroy() {
}
}
Added the filter to the chain
#Bean
public RequestBodyCachingFilter requestBodyCachingFilter() {
log.debug("Registering Request Body Caching filter");
return new RequestBodyCachingFilter();
}
In the Exception Handler.
#ControllerAdvice(annotations = RestController.class)
public class GlobalExceptionHandlingControllerAdvice {
private ContentCachingRequestWrapper getUnderlyingCachingRequest(ServletRequest request) {
if (ContentCachingRequestWrapper.class.isAssignableFrom(request.getClass())) {
return (ContentCachingRequestWrapper) request;
}
if (request instanceof ServletRequestWrapper) {
return getUnderlyingCachingRequest(((ServletRequestWrapper)request).getRequest());
}
return null;
}
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(Throwable.class)
public #ResponseBody Map<String, String> conflict(Throwable exception, HttpServletRequest request) {
ContentCachingRequestWrapper underlyingCachingRequest = getUnderlyingCachingRequest(request);
String body = new String(underlyingCachingRequest.getContentAsByteArray(),Charsets.UTF_8);
....
}
}
I had the same problem and solved it with HttpServletRequestWrapper as described above and it worked great. But then, I found another solution with extending HttpMessageConverter, in my case that was MappingJackson2HttpMessageConverter.
public class CustomJsonHttpMessageConverter extends MappingJackson2HttpMessageConverter{
public static final String REQUEST_BODY_ATTRIBUTE_NAME = "key.to.requestBody";
#Override
public Object read(Type type, Class<?> contextClass, final HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
final ByteArrayOutputStream writerStream = new ByteArrayOutputStream();
HttpInputMessage message = new HttpInputMessage() {
#Override
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
#Override
public InputStream getBody() throws IOException {
return new TeeInputStream(inputMessage.getBody(), writerStream);
}
};
RequestContextHolder.getRequestAttributes().setAttribute(REQUEST_BODY_ATTRIBUTE_NAME, writerStream, RequestAttributes.SCOPE_REQUEST);
return super.read(type, contextClass, message);
}
}
com.sun.xml.internal.messaging.saaj.util.TeeInputStream is used.
In spring mvc config
<mvc:annotation-driven >
<mvc:message-converters>
<bean class="com.company.remote.rest.util.CustomJsonHttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
In #ExceptionHandler method
#ExceptionHandler(Exception.class)
public ResponseEntity<RestError> handleException(Exception e, HttpServletRequest httpRequest) {
RestError error = new RestError();
error.setErrorCode(ErrorCodes.UNKNOWN_ERROR.getErrorCode());
error.setDescription(ErrorCodes.UNKNOWN_ERROR.getDescription());
error.setDescription(e.getMessage());
logRestException(httpRequest, e);
ResponseEntity<RestError> responseEntity = new ResponseEntity<RestError>(error,HttpStatus.INTERNAL_SERVER_ERROR);
return responseEntity;
}
private void logRestException(HttpServletRequest request, Exception ex) {
StringWriter sb = new StringWriter();
sb.append("Rest Error \n");
sb.append("\nRequest Path");
sb.append("\n----------------------------------------------------------------\n");
sb.append(request.getRequestURL());
sb.append("\n----------------------------------------------------------------\n");
Object requestBody = request.getAttribute(CustomJsonHttpMessageConverter.REQUEST_BODY_ATTRIBUTE_NAME);
if(requestBody != null) {
sb.append("\nRequest Body\n");
sb.append("----------------------------------------------------------------\n");
sb.append(requestBody.toString());
sb.append("\n----------------------------------------------------------------\n");
}
LOG.error(sb.toString());
}
I hope it helps :)

Categories

Resources