Intercepting XML using Servlet Filter

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 {

// 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;

}

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.

The problem is, this method 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 the rest in the 2nd invocation. (If it was invoked just once and read the entire data in a single shot then I can modify the bytes in the method itself, as I mentioned in the method body.)

Is there a way to have control over all the bytes (The entire request body) at one place, modify it and set it.

Any help in this regard is greatly appreciated.

Thanks in advance,

Sridhar.

[3922 byte] By [Sridhar.Va] at [2007-10-3 11:22:13]
# 1

Axis has a mechanism using what they term as "handlers" to intercept web services calls and modify the XML.

Handler

A reusable class which is responsible for processing a MessageContext in some custom way. The Axis engine invokes a series of Handlers whenever a request comes in from a client or a transport listener.

http://www.javaworld.com/javaworld/jw-09-2003/jw-0912-fop_p.html

tolmanka at 2007-7-15 13:47:30 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 2

Thanks for the response. The problem I have is, the incoming SOAP request contains a DIME attachment and Axis2 is not supporting DIME content at this time. (Axis 1.x has the support though, we're not using it). So, at the time of contructing the SOAP envelope itself, Axis2 is blewing off. I mean, throwing a SAXParserException. That's the reason why I'm trying to filter out the DIME content using the Servlet Filter. Please let me know how to have control over the request body content inside the HttpServletRequestWrapper.

Thanks,

Sridhar.

Sridhar.Va at 2007-7-15 13:47:30 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 3

I am not sure you can do it the way the direction you are going. The problem is how do you identify the xml that you want to exclude? If it takes more then one character then you have the problem that the first read my only include the first part of the deliminating pattern. Maybe you could read the entire stream into a ByteArrayOutputStream parsing out the unwanted content and use the byte buffer in a ByteArrayInputStream as backing for your custom InputStream. For parsing if you had a SAX parser that can handle the unwanted content you could use it to parse out the unwanted content.

tolmanka at 2007-7-15 13:47:30 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 4

Hello,

I found the way to capture the bytes and modify it. Below is the code.

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

intchr = in.read(b);

String byteStr = new String(b);

String resultStr = manipulate(byteStr);

b = resultStr.getBytes("UTF-8");

chr = b.length;

return chr;

}

public String manipulate(String str) {

// Manipulation is done here...

return modifiedStr;

}

Not sure if this is right way, but it's working fine. I did some local testing and I was able to invoke the Web Service. I still have to test this code with the actual SOAP Request with DIME attachment.

I really want to know the solution you have in mind with the ByteArrayInputStream. Can you please send me the sample code to read the entire stream into a byte buffer and pass it on to the Actual Servlet.

If the above solution doesn't work, I would like to try yours.

Thanks for the help,

Sridhar.

Sridhar.Va at 2007-7-15 13:47:30 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 5
How do you identify the content taht you do not want to process?
tolmanka at 2007-7-15 13:47:30 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 6

The dime content is not in the SOAP Envelope. There's some header information before the begining of the xml.......and I think the actual attachment would be after the <</soap:Envelope>.

Ex:

huid:262c1ed1-4e68-4f32-8b03-6ef8e40e40

<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns...

></soap:Envelope> XXXXXXXXXxx attachment

I could n't see the content after the </soap:Envelope> becoz.....As the InputSream read the bytes, it's sending the bytes to the AxisServlet. After it sent the first chunk of bytes it blew. so I saw the junk data (the dime content) only before the start of <?xml...

Now what I'm doing is, the full method

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

int chr = 0;

try {

if (invocationCounter < 1) {

chr = in.read(b);

String initialByteStr = new String(b);

String resultStr = null;

if (initialByteStr.indexOf("<?xml") != -1) {

int startIndex = initialByteStr.indexOf("<?xml");

resultStr = initialByteStr.substring(startIndex);

System.out.println("************** No DIME String *********** ");

System.out.println(resultStr);

b = resultStr.getBytes("UTF-8");

chr = b.length;

}

} else {

if (!isEndOfEnvelopeReached) {

chr = in.read(b);

String byteStr = new String(b);

String resultStr = null;

if (byteStr.indexOf("</soap:Envelope>") != -1) {

int endIndex = byteStr.indexOf("</soap:Envelope>") + 16;

resultStr = byteStr.substring(0, endIndex);

System.out.println("Last Piece ********************** ");

System.out.println(resultStr);

b = resultStr.getBytes("UTF-8");

chr = b.length;

isEndOfEnvelopeReached = true;

}

}

else {

System.out.println(" Reading After the end of Envelope.... ");

chr = in.read(b);

b = new String(" ").getBytes();

chr = b.length;

}

}

} catch (Exception e) {

e.printStackTrace();

}

invocationCounter++;

return chr;

}

That's it. I hope this works.

I'll be thankful to you if you can send me a sample code to implement the same funtionality using ByteArrayInputStream, so, I could read the entire stream at one shot and pass it on to AxisServlet.

Thanks,

Sridhar.

Sridhar.Va at 2007-7-15 13:47:30 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 7

Hello tolmank,

That solution didn't work. I tried to implement your idea but not completely successful. I'm reading the entire body into a ByteArrayOutputStream. Whenever the servlet requests for the ServletInputStream, I should be creating a new one with the read bytes and return it. 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{

// Construct a new ServletInputStream with the byte array "data[]" 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.

Sridhar.Va at 2007-7-15 13:47:30 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 8
You can forget waht I said. At the time I wasn't sure what you were filtering out but looking at what you sent you don't need to store the message in a ByteArrayOutputStream.
tolmanka at 2007-7-15 13:47:30 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 9

Hello,

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.

I'm getting the following exception in the begining itself

org.apache.axis2.AxisFault: com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character '/' (code 47) in prolog; expected '<'

at [row,col {unknown-source}]: [1,1]; nested exception is:

org.apache.axiom.om.OMException: com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character '/' (code 47) in prolog; expected '<'

Please help me.

Thanks,

Sridhar.

Sridhar.Va at 2007-7-15 13:47:30 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...