• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Jeanne Boyarsky
  • Ron McLeod
  • Paul Clapham
  • Liutauras Vilda
Sheriffs:
  • paul wheaton
  • Rob Spoor
  • Devaka Cooray
Saloon Keepers:
  • Stephan van Hulst
  • Tim Holloway
  • Carey Brown
  • Frits Walraven
  • Tim Moores
Bartenders:
  • Mikalai Zaikin

Intercepting XML using Servlet Filter

 
Greenhorn
Posts: 15
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
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.
 
Ranch Hand
Posts: 2308
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
You can use a filer ,for changing the request before it reaches the main servlet.
 
Author and all-around good cowpoke
Posts: 13078
6
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
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
Posts: 15
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
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
Posts: 13078
6
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

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
Posts: 15
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
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
Posts: 15
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
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
Posts: 13078
6
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

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
Posts: 15
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
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
Posts: 13078
6
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
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
Posts: 15
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
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.
reply
    Bookmark Topic Watch Topic
  • New Topic