aspose file tools*
The moose likes Web Services and the fly likes Intercepting XML using Servlet Filter Big Moose Saloon
  Search | Java FAQ | Recent Topics | Flagged Topics | Hot Topics | Zero Replies
Register / Login


Win a copy of Spring in Action this week in the Spring forum!
JavaRanch » Java Forums » Java » Web Services
Bookmark "Intercepting XML using Servlet Filter" Watch "Intercepting XML using Servlet Filter" New topic
Author

Intercepting XML using Servlet Filter

sridhar vu
Greenhorn

Joined: Feb 28, 2005
Posts: 15
Hello All,
I'm trying to intercept a SOAP request, modify the XML and pass it on to the AxisServlet. Gone through forums and got to a point where I could read the request content into a StringBuffer. Can someone please help me to set the read bytes back to the HttpServletRequest object.

// Servlet Filter

import java.io.*;
import java.util.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;


public final class SOAPServletFilter implements Filter {
private FilterConfig config = null;
PrintWriter out;
public void init(FilterConfig filterConfig) throws ServletException {
config = filterConfig;
}

public void destroy() {
config = null; // destroy method called
}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {

if (config == null) {
return;
}

CustomRequestWrapper cr = new
CustomRequestWrapper((HttpServletRequest)request);
chain.doFilter(cr, response);
}
}


// HttpServletRequestWrapper

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class CustomRequestWrapper extends HttpServletRequestWrapper{
private CustomServerInputStream in;

private HttpServletRequest request;
public CustomRequestWrapper(HttpServletRequest request) throws IOException{
super(request);
this.request = request;
in = new CustomServerInputStream(request.getInputStream());
}
public ServletInputStream getInputStream() {
return in;
}
public BufferedReader getReader() {
BufferedReader br = new BufferedReader(new InputStreamReader(in));
return br;
}
}

class CustomServerInputStream extends ServletInputStream {

final private ServletInputStream in;
private StringBuffer strbuf = new StringBuffer();

public CustomServerInputStream(ServletInputStream inputStream) {
super();
in = inputStream;
}

public int read(byte[] b, int off, int len) throws IOException{
final int chr = in.read(b,0,b.length);
System.out.println(" total : " + chr);
return chr;
}

public int read(byte[] b) throws IOException {
int chr = in.read(b);
strbuf.append(new String(b));
System.out.println(" content " + new String(b));
System.out.println(" noOfBytes " + chr);

return chr;
}

public int read() throws IOException {
final int chr = in.read();
System.out.println(" char " + chr);
return chr;
}

public StringBuffer getBody() {
return strbuf;
}
}


I have overrided the 3 read() methods. Not sure why the read(byte [] b) is only invoked. Also, it is invoked more than once depending on the no of bytes in the request object. The example I'm trying has 5278 bytes, it read the first 4000 bytes and rest in the 2nd invocation. I'm putting all the read bytes into a StringBuffer.

The problem is, after all the bytes are read, how to set it back to the Request object.

Any help in this regard is greatly appreciated.

Thanks in advance,
Sridhar.
Rahul Bhattacharjee
Ranch Hand

Joined: Nov 29, 2005
Posts: 2308
You can use a filer ,for changing the request before it reaches the main servlet.


Rahul Bhattacharjee
LinkedIn - Blog
William Brogden
Author and all-around good cowpoke
Rancher

Joined: Mar 22, 2000
Posts: 12806
    
    5
If I understand your question correctly, you don't need to "set it back to the request object", your CustomRequestWrapper class object is what gets passed to the servlet. Your wrapper needs to hold all the (modified) request bytes and provide a Reader or InputStream when the servlet requests them.

Bill
sridhar vu
Greenhorn

Joined: Feb 28, 2005
Posts: 15
Bill,
Yes..you're right. The "CustomRequestWrapper" object is what gets passed to the Actual Servlet. The manipulation of the bytes should be done in this object. Let me explain what I understood. The control flow goes like this from the doFilter() method.

1. Instantiates the CustomRequestWrapper Object

CustomRequestWrapper cr = new
CustomRequestWrapper((HttpServletRequest)request);

2. Invokes the overrided getInputStream of ServletInputStream.

public ServletInputStream getInputStream() {
return in;
}

3. Calls the read(byte[] b) of "CustomServerInputStream"

public int read(byte[] b) throws IOException {

// Any manipulation should be done here with the read bytes (byte array b)
// set the modified bytes in the below read method.

int chr = in.read(b);
strbuf.append(new String(b));
System.out.println(" content " + new String(b));
System.out.println(" noOfBytes " + chr);
return chr;
}

so, If the above read method is called only once I can modify the bytes and pass it to the "in" (ServletInputStream) object's read method (in.read(b)). The problem is, this method is invoked in increments (I mean, reads chunks of data). Unless I have all the data in hand I cannot do any modification. Is there way to read the whole data in a single shot?



Rahul,
Never heard of a filer. Can you please send me some sample code to modify the read bytes?


Thanks,
Sridhar.
William Brogden
Author and all-around good cowpoke
Rancher

Joined: Mar 22, 2000
Posts: 12806
    
    5
The problem is, this method is invoked in increments (I mean, reads chunks of data).


You are still missing the point - you do NOT pass the ServletInputStream from the wrapped request, you:
1. Read the entire ServletInputStream from the wrapped request, writing the bytes as received into a local ByteArrayOutputStream.
2. Get the resulting byte[] (or maybe as a String) which contains the entire request body
3. Modify as desired
4. When the actual servlet requests the InputStream or the Reader from your custom wrapper, create a new stream or reader from the modified byte[] or String and retun it.
5. The servlet will now read your modified request body.
6. Pass requests for headers to the original request (or maybe add your own if you like)
Bill
sridhar vu
Greenhorn

Joined: Feb 28, 2005
Posts: 15
Bill,
Thanks for the detailed steps. I'm still not able to resolve step#4. I read all the bytes into a byte array. Now how to construct a new ServletInputStream when the actual servlet requests it?

Below is the modified code. Can you please fix what I'm missing here.



public class CustomRequestWrapper extends HttpServletRequestWrapper {
private byte data[] = null;
private CustomServerInputStream in;

public CustomRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
InputStream is = request.getInputStream();

int ch;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((ch = is.read()) != -1)
{
baos.write((byte)ch);
}
data = baos.toByteArray();
}

public ServletInputStream getInputStream() throws IOException{

// I should create a new ServletInputStream object here and return it ??

CustomServerInputStream csis = new CustomServerInputStream(data);
return csis;
}

public BufferedReader getReader() {
BufferedReader br = new BufferedReader(new InputStreamReader(in));
return br;
}
}

class CustomServerInputStream extends ServletInputStream {

//final private ServletInputStream in;
byte data[];
int invocationCounter = 0;
boolean isEndOfEnvelopeReached = false;

public CustomServerInputStream(byte[] b) {
super();
data = b;
}


public int read(byte[] b) throws IOException {
return b.length;
}

public int read() throws IOException {
return 1;
}

}
sridhar vu
Greenhorn

Joined: Feb 28, 2005
Posts: 15
Bill,
Thanks for the detailed steps. I'm still not able to resolve step#4. I read all the bytes into a byte array. Now how to construct a new ServletInputStream when the actual servlet requests it?

Below is the modified code. Can you please fix what I'm missing here.



public class CustomRequestWrapper extends HttpServletRequestWrapper {
private byte data[] = null;
private CustomServerInputStream in;

public CustomRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
InputStream is = request.getInputStream();

int ch;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((ch = is.read()) != -1)
{
baos.write((byte)ch);
}
data = baos.toByteArray();
}

public ServletInputStream getInputStream() throws IOException{

// I should create a new ServletInputStream object here and return it ??

CustomServerInputStream csis = new CustomServerInputStream(data);
return csis;
}

public BufferedReader getReader() {
BufferedReader br = new BufferedReader(new InputStreamReader(in));
return br;
}
}

class CustomServerInputStream extends ServletInputStream {

//final private ServletInputStream in;
byte data[];
int invocationCounter = 0;
boolean isEndOfEnvelopeReached = false;

public CustomServerInputStream(byte[] b) {
super();
data = b;
}


public int read(byte[] b) throws IOException {
return b.length;
}

public int read() throws IOException {
return 1;
}

}

Thanks,
Sridhar.
William Brogden
Author and all-around good cowpoke
Rancher

Joined: Mar 22, 2000
Posts: 12806
    
    5
Now how to construct a new ServletInputStream when the actual servlet requests it?

It just has to be an InputStream - thats the interface that any application will typically use. Or - since ServletInputStream is abstract, you could have a custom class extending it. Note you should also be prepared to return a Reader if the servlet calls getReader().

Browse through the JavaDocs for the java.io package, you will find lots of ways to make an InputStream or Reader from a String or byte[].

If working with a String, there is a StringReader class for example.

Bill
sridhar vu
Greenhorn

Joined: Feb 28, 2005
Posts: 15
Bill,
Thanks for the info. Finally I'm able to modify the SOAP request and send the modified bytes to the AxisServlet. I'm still having problems in the real time application. Please check the comments in the below code.

public class CustomRequestWrapper extends HttpServletRequestWrapper {
private static final Logger logger = Logger.getLogger(CustomRequestWrapper.class);
private CustomServerInputStream in;

public CustomRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
InputStream is = request.getInputStream();
int ch;
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
while ((ch = is.read()) != -1)
{
buffer.write((byte)ch);
}
ByteArrayOutputStream baos = ripDimeContent(buffer);
in = new CustomServerInputStream(baos);
}

/*
The SOAP request I'm getting contains a dime attachment which I have to ignore. the below method is used for that, it just takes off the unnecessary content around the <?xml... ><soap:Envelope> ..........</soap:Envelope> tags and returns just the SOAP Envelope.

*/
private ByteArrayOutputStream ripDimeContent(ByteArrayOutputStream buffer) {
byte result[];
String resultStr;
ByteArrayOutputStream baos = null;
try {
String byteStr = new String(buffer.toByteArray());
logger.info("**************** Actual SOAP Request ***********************************************************************");
logger.info(byteStr);
int startIndex = byteStr.indexOf("<?xml");
int endIndex = byteStr.indexOf("</soap:Envelope>") + 16;
resultStr = byteStr.substring(startIndex,endIndex);
result = resultStr.getBytes("UTF-8");
baos = new ByteArrayOutputStream();
baos.write(result,0,result.length);
}catch(Exception e) {
e.printStackTrace();
}
return baos;
}

public ServletInputStream getInputStream() throws IOException{
return in;
}

public BufferedReader getReader() throws IOException {
final String enc = getCharacterEncoding();
final InputStream istream = getInputStream();
final Reader r = new InputStreamReader(istream, enc);

return new BufferedReader(r);
}
}

class CustomServerInputStream extends ServletInputStream {
private static final Logger logger = Logger.getLogger(CustomServerInputStream.class);
private InputStream in;

public CustomServerInputStream(ByteArrayOutputStream baos) throws IOException{
super();
logger.info("**************** Modified SOAP Request ***********************************************************************");

/*
I'm printing the modified SOAP string. When I copy this string and send it using TCPMON tool, the webservice works fine.

*/
logger.info(new String(baos.toByteArray()));

in = new ByteArrayInputStream(baos.toByteArray());
}

public int read() throws IOException {
return in.read();
}

public void close() throws IOException {
in.close();
}
}


When I test the service with my local client (with no dime attachment) , it works fine. The actual client who's hitting my web service is a .NET client. When I check the log file and see the Actual and Modified SOAP request, the modified one doesn't contain anything around the SOAP Envelope tags. Not able to figure out why it's failing in the real time scenario. Am I missing anything while creating the ServletInputStream object ?? Is it not sending the modified SOAP string.

Please help me.

Thanks,
Sridhar.
William Brogden
Author and all-around good cowpoke
Rancher

Joined: Mar 22, 2000
Posts: 12806
    
    5
It sounds like you have made a lot of progress.
Am I missing anything while creating the ServletInputStream object ?? Is it not sending the modified SOAP string.

Seems to me that if it works for your local test it should work for the live example.

How exactly does it fail with the .NET client?

Perhaps you should provide a modified "Content-Length" header - the original one will be wrong after your filtering. That would involve a custom getHeader() method that passes all but "Content-Length" requests to the original request but knows to return a String with the new content length value.

Bill
sridhar vu
Greenhorn

Joined: Feb 28, 2005
Posts: 15
Hey Bill,
Thanks for all the help. It's finally working in the real time scenario with the .NET client. I wasn't overriding the original content type "application/dime". I just added one more method "getContentType()" and returned "text/xml". This solved the problem.

Thanks again,
Sridhar.
 
I agree. Here's the link: http://aspose.com/file-tools
 
subject: Intercepting XML using Servlet Filter