Using a cxf interceptor to handle gzip compression in a ws:consumer
A recent client engagement set me the challenge of handling compressed SOAP requests, after some investigation on a lead suggested by one of our architects I successfully managed to build out a cxf configuration and custom java classes that are able to compress/decompress a request/response.
Your cxf need be no more complex than this to invoke the 2 java classes we touch on in the next step.
Steps
Step one:- the cxf configuration
My journey started with this support article and frankly it's quite light on details, but if you want the TL;DR version you need to place a valid cxf.xml under src/main/resources.Your cxf need be no more complex than this to invoke the 2 java classes we touch on in the next step.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:cxf="http://cxf.apache.org/core"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean class="com.blogger.jackandmare.PossiblyCompressedInboundInterceptor" id="in"/>
<bean class="com.blogger.jackandmare.CompressingOutboundInterceptor" id="out"/>
<cxf:bus>
<cxf:inInterceptors>
<ref bean="in"/>
</cxf:inInterceptors>
<cxf:inFaultInterceptors>
<ref bean="in"/>
</cxf:inFaultInterceptors>
<cxf:outInterceptors>
<ref bean="out"/>
</cxf:outInterceptors>
<cxf:outFaultInterceptors>
<ref bean="out"/>
</cxf:outFaultInterceptors>
</cxf:bus>
</beans>
Step two:- custom java classes
The above configuration relies on 2 java classes, one for the inbound (so in this case the response from our SOAP endpoint) and the other for outbound processing (in this case the request we send to the SOAP endpoint).
import java.util.*;
import java.util.zip.*;
import org.apache.cxf.interceptor.*;
import org.apache.cxf.message.*;
import org.apache.cxf.phase.*;
public class CompressingOutboundInterceptor extends AbstractPhaseInterceptor<Message> {
public CompressingOutboundInterceptor() {
super(Phase.PRE_STREAM);
}
public void handleMessage(Message msg) throws Fault {
OutputStream os = msg.getContent(OutputStream.class);
try {
GZIPOutputStream gos = new GZIPOutputStream( os );
msg.setContent(OutputStream.class, gos);
} catch (IOException e) {
// TODO: you should handle this exception
}
}
}
Outbound Java
import java.io.*;import java.util.*;
import java.util.zip.*;
import org.apache.cxf.interceptor.*;
import org.apache.cxf.message.*;
import org.apache.cxf.phase.*;
public class CompressingOutboundInterceptor extends AbstractPhaseInterceptor<Message> {
public CompressingOutboundInterceptor() {
super(Phase.PRE_STREAM);
}
public void handleMessage(Message msg) throws Fault {
OutputStream os = msg.getContent(OutputStream.class);
try {
GZIPOutputStream gos = new GZIPOutputStream( os );
msg.setContent(OutputStream.class, gos);
} catch (IOException e) {
// TODO: you should handle this exception
}
}
}
Inbound Java
import java.io.*;
import java.util.*;
import java.util.zip.*;
import org.apache.cxf.interceptor.*;
import org.apache.cxf.message.*;
import org.apache.cxf.phase.*;
/**
* Custom PhaseInterceptor that can be associated with a cxf configuration to
* handle an incoming message that may or may-not be compressed using gzip.
*
* If it starts with the GZIP member header bytes (@see http://www.gzip.org/zlib/rfc-gzip.html#file-format)
* then this will wrap the message with a GZIPInputStream (and reset),
* otherwise the message is reset and being wrapped in a PushbackInputStream.
*/
public class PossiblyCompressedInboundInterceptor extends AbstractPhaseInterceptor<Message> {
private static final byte[] GZIP_SIGNATURE = new byte[] {(byte) 0x1f,(byte) 0x8b}; // GZip compressed files/streams will start with these two bytes (and importantly any regular XML payload will not)
public PossiblyCompressedInboundInterceptor() {
super(Phase.PRE_STREAM);
}
public void handleMessage(Message msg) throws Fault {
// In order to handle a response (incoming) message that may or may not have been compressed using gzip we need to read the first 2 characters of the stream - see https://stackoverflow.com/questions/4818468/how-to-check-if-inputstream-is-gzipped
PushbackInputStream pb = new PushbackInputStream( msg.getContent(InputStream.class), 2);
byte [] signature = new byte[2];
try {
int len = pb.read( signature ); //read the signature
pb.unread( signature, 0, len ); //push back the signature to the stream
if( Arrays.equals(signature,GZIP_SIGNATURE) ) { //check if matches standard gzip magic number
GZIPInputStream gis = new GZIPInputStream( pb );
msg.setContent(InputStream.class, gis);
} else {
msg.setContent(InputStream.class, pb);
}
} catch (IOException e) {
// TODO: you should handle this exception too
}
}
}
Some light reading if you want to know more.
I freely confess i got the above working in no small part through trial and error so my understanding of how CXF really works is quite limited.
This stack overflow response was in fairness super-helpful, and really helped me make basic progress
I did find the RedHat manuals sort-of helpful, especially these two tables
* Inbound message phasesThis stack overflow response was in fairness super-helpful, and really helped me make basic progress
I did find the RedHat manuals sort-of helpful, especially these two tables
* Outbound message phases
This is fairly trivial, but important (and allows me to show the use of a more compact way todo this than a chain of property elements). Add the "Message Properties" element prior to the Web Service Consumer (by all means give it a descriptive name) and configure the HTTP headers "Accept-Charset" and "Content-Encoding" as illustrated.
Or if you prefer to work in xml the following code snippet will help you.
As a note, if anyone has a good public SOAP service that accepts requests & supplies responses respecting the headers above, i will happily set up a full example based on this.
Step three:- set the headers correctly (using a message property transformer).
This is fairly trivial, but important (and allows me to show the use of a more compact way todo this than a chain of property elements). Add the "Message Properties" element prior to the Web Service Consumer (by all means give it a descriptive name) and configure the HTTP headers "Accept-Charset" and "Content-Encoding" as illustrated.
Or if you prefer to work in xml the following code snippet will help you.
<message-properties-transformer doc:name="assert gzip request and request UTF-8 + gzip response">
<add-message-property key="Accept-Charset" value="UTF8"/>
<add-message-property key="Accept-Encoding" value="gzip"/>
<add-message-property key="Content-Encoding" value="gzip"/>
</message-properties-transformer>
Comments
Post a Comment