How to rewrite POST request body on HttpServletRequest - java

I'm working on a Filter in which I have to get the request payload, decrypt it, check if it's a valid JSON and if it is go on with the chain and go to my service. The thing is that, so far I haven't been able to find a way to rewrite the body. Why I want to rewrite it? As the service expects a JSON and the request has an encrypted text in the body, once I decrypt it I want the body to be the decrypted JSON. Also, once I return from the service, I should rewrite the response to have the json encrypted. I've read a lot of forums and questions but couldn't get to a working solution.
Here's my code:
RequestLoginFilter.java
#WebFilter("/RequestLoginFilter")
public class RequestLoginFilter implements Filter{
protected final static Log logger = LogFactory.getLog(RequestLoginFilter.class);
private ServletContext context;
private CryptoUtil crypto;
public void init(FilterConfig fConfig) throws ServletException {
this.context = fConfig.getServletContext();
this.context.log("RequestLoggingFilter initialized");
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// use wrapper to read multiple times the content
AuthenticationRequestWrapper req = new AuthenticationRequestWrapper((HttpServletRequest) request);
HttpServletResponse resp = (HttpServletResponse) response;
String payload = req.getPayload();
try {
String decryptedPayload = crypto.decrypt(payload);
JSONUtils.convertJSONStringToObject(decryptedPayload, LoginTokenTO.class);
} catch (GeneralSecurityException e) {
logger.error("Error when trying to decrypt payload '"+payload+"'");
throw new ServletException("Error when trying to decrypt payload '"+payload+"'", e);
}
chain.doFilter(req, resp);
System.out.println("a ver");
}
#Override
public void destroy() {
// TODO Auto-generated method stub
}
}
And also the wrapper, just in case:
AuthenticationRequestWrapper.java
public class AuthenticationRequestWrapper extends HttpServletRequestWrapper {
protected final static Log logger = LogFactory.getLog(AuthenticationRequestWrapper.class);
private final String payload;
public AuthenticationRequestWrapper (HttpServletRequest request) throws AuthenticationException {
super(request);
// read the original payload into the payload variable
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
// read the payload into the StringBuilder
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 {
// make an empty string since there is no payload
stringBuilder.append("");
}
} catch (IOException ex) {
logger.error("Error reading the request payload", ex);
throw new AuthenticationException("Error reading the request payload", ex);
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException iox) {
// ignore
}
}
}
payload = stringBuilder.toString();
}
#Override
public ServletInputStream getInputStream () throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(payload.getBytes());
ServletInputStream inputStream = new ServletInputStream() {
public int read ()
throws IOException {
return byteArrayInputStream.read();
}
};
return inputStream;
}
public String getPayload() {
return payload;
}
}
Hopefully somebody here knows how I can get to get this working.
Thanks in advance guys.

Whilst what you are asking is probably technically possible, it doesn't sound like the right approach to me.
What you need is a security layer that sits between the incoming request (endpoint) and your service. Re-writing the body of the request is a strange thing to be doing (which probably explains why you're having issues). Is there a reason you want this to be done in a Filter? After all, filters are designed to filter requests, not rewrite them ;)
A more logical/transparent solution would be to have your endpoint accept all incoming requests, decrypt and validate them before passing the request onto your service tier. Something like this:
public void handleRequest(Request request) {
try {
IncomingRequest x = securityManager.decrypt(request);
Response r = myService.handleRequest(x);
handleResponse(securityManager.encrypt(r));
}catch(InvlidateMessage x) {
handleInvalidMessage...
}catch(BusinessException x) {
handleBusinessException...
}
}

Related

How do I get the response payload of a REST service from the Message of an outgoing Interceptor?

I have written a interceptor in the outgoing chain in the SEND phase and I want to get the payload of the response of my REST service into a String variable. How can I achieve this?
Here is my interceptor
public class MyLoginOutInterceptor extends LoggingOutInterceptor {
public MyLoginOutInterceptor() {
super(Phase.SEND);
}
#Override
public void handleMessage(Message message) throws Fault {
OutputStream os = message.getContent(OutputStream.class);
}
}
When I put a breakpoint to the OutputStream os = message.getContent(OutputStream.class); I see that the payload is in the os but I don't know how to get it into a String.
Any ideas?
In the responsePayload you should have what you want.
public void handleMessage(Message message) throws Fault {
OutputStream os = message.getContent(OutputStream.class);
StringBuilder responsePayload = new StringBuilder();
CachedOutputStream cos = (CachedOutputStream) os;
try {
cos.writeCacheTo(responsePayload);
} catch (IOException e) {
e.printStackTrace();
}
}

HttpServletRequestWrapper not working as expected

I need to log request body content. So I used filter and HttpServletRequestWrapper as below for this purpose. But when I invoke request.getParameter from my servlet I'm not getting anything. Appreciate any help
RequestWrapper Code
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
private static final Log log = LogFactory.getLog(MultiReadHttpServletRequest.class);
private ByteArrayOutputStream cachedBytes;
public MultiReadHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
cachedBytes = new ByteArrayOutputStream();
byte[] buffer = new byte[1024 * 4];
int n;
while (-1 != (n = request.getInputStream().read(buffer))) {
cachedBytes.write(buffer, 0, n);
}
}
#Override
public ServletInputStream getInputStream() throws IOException {
return new CachedServletInputStream();
}
private class CachedServletInputStream extends ServletInputStream {
private ByteArrayInputStream input;
public CachedServletInputStream() {
input = new ByteArrayInputStream(cachedBytes.toByteArray());
}
#Override
public int read() throws IOException {
return input.read();
}
}
String getRequestBody() throws IOException {
StringBuilder inputBuffer = new StringBuilder();
String line;
BufferedReader reader = new BufferedReader(new InputStreamReader(getInputStream()));
try {
do {
line = reader.readLine();
if (null != line) {
inputBuffer.append(line.trim());
}
} while (line != null);
} catch (IOException ex) {
log.error("Unable to get request body from request: " + ex.getMessage(), ex);
} finally {
try {
reader.close();
} catch (IOException e) {
// Just log error
log.warn("Unable to close BufferReader: " + e.getMessage(), e);
}
}
return inputBuffer.toString().trim();
}
My filter code
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
if ((servletRequest instanceof HttpServletRequest) && (messageTracerApiClient != null)) {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
MultiReadHttpServletRequest bufferedRequest = new MultiReadHttpServletRequest(httpServletRequest);
Message message = new Message();
message.setHost(bufferedRequest.getLocalAddr());
message.setPayload(bufferedRequest.getRequestBody());
messageTracerApiClient.publishMessage(message);
System.out.println("bufferedRequest param= " + bufferedRequest.getParameterMap().size());
filterChain.doFilter(bufferedRequest, servletResponse);
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
Please note bufferedRequest.getParameterMap().size() also print 0 even there are parameter.

How to stream an endless InputStream with JAX-RS

I have an endless InputStream with some data, which I want to return in response to a GET HTTP request. I want my web/API client to read from it endlessly. How can I do it with JAX-RS? I'm trying this:
#GET
#Path("/stream")
#Produces(MediaType.TEXT_PLAIN)
public StreamingOutput stream() {
final InputStream input = // get it
return new StreamingOutput() {
#Override
public void write(OutputStream out) throws IOException {
while (true) {
out.write(input.read());
out.flush();
}
}
};
}
But content doesn't appear for the client. However, if I add OutputStream#close(), the server delivers the content at that very moment. How can I make it truly streamable?
Simply use the StreamingOutput of JAX-RS
#Path("/numbers")
public class NumbersResource {
#GET
public Response streamExample(){
StreamingOutput stream = new StreamingOutput() {
#Override
public void write(OutputStream out) throws IOException, WebApplicationException {
Writer writer = new BufferedWriter(new OutputStreamWriter(out));
for (int i = 0; i < 10000000 ; i++){
writer.write(i + " ");
}
writer.flush();
}
};
return Response.ok(stream).build();
}
}
So, you have flush issues, you could try to get the ServletResponse as the spec says:
The #Context annotation can be used to indicate a dependency on a
Servlet-defined resource. A Servlet- based implementation MUST support
injection of the following Servlet-defined types: ServletConfig,
ServletContext, HttpServletRequest and HttpServletResponse.
An injected HttpServletResponse allows a resource method to commit the
HTTP response prior to returning. An implementation MUST check the
committed status and only process the return value if the response is
not yet committed.
Then flushing everything you can, like this:
#Context
private HttpServletResponse context;
#GET
#Path("/stream")
#Produces(MediaType.TEXT_PLAIN)
public String stream() {
final InputStream input = // get it
ServletOutputStream out = context.getOutputStream();
while (true) {
out.write(input.read());
out.flush();
context.flushBuffer();
}
return "";
}
Just a wild guess:
#GET
#Path("/stream")
#Produces(MediaType.TEXT_PLAIN)
public Response stream() {
final InputStream input = getit();
return Response.ok(input, MediaType.TEXT_PLAIN_TYPE).build();
}
Folks should be using Java 9 or later can use transferTo to copy the input stream to the output stream so do this:
#GET
#Path("/stream")
#Produces(MediaType.TEXT_PLAIN)
public StreamingOutput stream() {
final InputStream input = // get it
StreamingOutput stream = output -> {
try {
is.transferTo(output);
}
catch (Exception e) {
throw new WebApplicationException(e);
} finally {
is.close();
}
};
return Response.ok(stream, MediaType.APPLICATION_OCTET_STREAM).build();
}

file downloading in restful web services

My requirement is, I should send a 10MB zip file to the client with a restful service. I found the code in forums that sending a StreamingOutput object is the better way, but how can I create a StreamingOutput object in the following code:
#Path("PDF-file.pdf/")
#GET
#Produces({"application/pdf"})
public StreamingOutput getPDF() throws Exception {
return new StreamingOutput() {
public void write(OutputStream output) throws IOException, WebApplicationException
{
try {
//------
} catch (Exception e) {
throw new WebApplicationException(e);
}
}
};
}
Its the better way and easy way for file dowload.
private static final String FILE_PATH = "d:\\Test2.zip";
#GET
#Path("/get")
#Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response getFile() {
File file = new File(FILE_PATH);
ResponseBuilder response = Response.ok((Object) file);
response.header("Content-Disposition", "attachment; filename=newfile.zip");
return response.build();
}
For your code as you asked:
#GET
#Path("/helloWorldZip")
#Produces(MediaType.APPLICATION_OCTET_STREAM)
public StreamingOutput helloWorldZip() throws Exception {
return new StreamingOutput(){
#Override
public void write(OutputStream arg0) throws IOException, WebApplicationException {
// TODO Auto-generated method stub
BufferedOutputStream bus = new BufferedOutputStream(arg0);
try {
//ByteArrayInputStream reader = (ByteArrayInputStream) Thread.currentThread().getContextClassLoader().getResourceAsStream();
//byte[] input = new byte[2048];
java.net.URL uri = Thread.currentThread().getContextClassLoader().getResource("");
File file = new File("D:\\Test1.zip");
FileInputStream fizip = new FileInputStream(file);
byte[] buffer2 = IOUtils.toByteArray(fizip);
bus.write(buffer2);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}

IllegalStateException on redirect

I have a PhaseListener which listens on phaseId RENDER_RESPONSE. This faceListener calls this method:
public void doLogin(ServletRequest request) throws IOException {
FacesContext fc = FacesContext.getCurrentInstance();
HttpServletRequest req = (HttpServletRequest) request;
String code = req.getParameter("code");
if (StringUtil.isNotBlankString(code)) {
String authURL = Facebook.getAuthURL(code);
URL url = new URL(authURL);
try {
....
if (accessToken != null && expires != null) {
boolean isLoginOk = service.authFacebookLogin(accessToken);
if (isLoginOk) {
fc.getApplication().getNavigationHandler().handleNavigation(fc, "/welcome.xhtml", "logged-in");
}
} else {
throw new RuntimeException("Access token and expires not found");
}
} catch (IOException e) {
throw new RuntimeException(e);
} catch (FacebookException e) {
Logger.getLogger(FBOauth.class.getName()).log(Level.SEVERE, "Facebook error", e);
}
}
}
private String readURL(URL url) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream is = url.openStream();
int r;
while ((r = is.read()) != -1) {
baos.write(r);
}
return new String(baos.toByteArray());
}
When it redirects I get the following exception which I cant really find any solution to. From what I understand it is thrown because response is already comitted but why is it already comitted?
java.lang.IllegalStateException at
org.apache.catalina.connector.ResponseFacade.sendRedirect(ResponseFacade.java:522)
at com.sun.faces.context.ExternalContextImpl.redirect(ExternalContextImpl.java:572)
at com.sun.faces.application.NavigationHandlerImpl.handleNavigation(NavigationHandlerImpl.java:182)
at wmc.web.facebook.FBOauth.doLogin(FBOauth.java:57)
at wmc.web.listeners.FacebookSignInListener.afterPhase(FacebookSignInListener.java:56)
at com.sun.faces.lifecycle.Phase.handleAfterPhase(Phase.java:189)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:107)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:139)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:313)
By the way I really appreciate all the help I get in here :)
Your phaselistener is apparently hooking on afterPhase of RENDER_RESPONSE. It's too late to change the response then. The response is already been sent to the client. Rather hook on beforePhase() of the `RENDER_RESPONSE.
My guess would be that before the sendRedirect() is executed some part of your code has already streamed text to the servlet repsonse, maybe some generic information that is send to all responses?

Categories

Resources