I am trying to log all the outgoing Http requests in my spring based web application. Is there is interceptor for this purpose? I want to log all outgoing the contents and headers before it leaves the application. I am using spring-ws to send SOAP requests. So basically, I want to log not only the SOAP request xml (as mentioned here How can I make Spring WebServices log all SOAP requests?) but the http request as a whole.
Intercept the request/response using a ClientInterceptor on the WebServiceGatewaySupport:
// soapClient extends WebServiceGatewaySupport
soapClient.setInterceptors(new ClientInterceptor[]{new ClientInterceptor() {
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
messageContext.getRequest().writeTo(os);
} catch (IOException e) {
throw new WebServiceIOException(e.getMessage(), e);
}
String request = new String(os.toByteArray());
logger.trace("Request Envelope: " + request);
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
messageContext.getResponse().writeTo(os);
} catch (IOException e) {
throw new WebServiceIOException(e.getMessage(), e);
}
String response = new String(os.toByteArray());
logger.trace("Response Envelope: " + response);
return true;
}
...
To get the headers as well you need an instance of TransportOutputStream.
Unfortunately the class is abstract, so you need to subclass is. Here's how it might look:
class ByteArrayTransportOutputStream extends TransportOutputStream {
private ByteArrayOutputStream outputStream;
#Override
public void addHeader(String name, String value) throws IOException {
createOutputStream();
String header = name + ": " + value + "\n";
outputStream.write(header.getBytes());
}
public byte[] toByteArray() {
return outputStream.toByteArray();
}
#Override
protected OutputStream createOutputStream() throws IOException {
if (outputStream == null) {
outputStream = new ByteArrayOutputStream();
}
return outputStream;
}
}
Related
I'm adding message level encryption (MLE) to an existing code base for outgoing requests. To do this, I simply wrote an interceptor that will catch outgoing requests, encrypt their bodies, and then send the request out. The response we get is also encrypted, and must be decrypted. This all is working fine for me. The only problem I'm having is that I must replace the ClientHttpResponse encrypted body with the now decrypted JSON. How can I do this? I don't see any methods that will let me alter the response body. Thanks in advance.
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
ClientHttpResponse response;
String bodyStr = new String(body);
// Encrypt the body and send
bodyStr = encrypt(bodyStr);
try {
response = execution.execute(request, bodyStr.getBytes());
} catch (Exception e) {
throw e;
}
// Decrypt the response body
String decryptedResponseBody = decrypt(response.getBody());
// Set the response body to the decrypted data (JSON)
// response.setBody(decryptedResponseBody)?????????
return response;
}
You will need to create an implementation of ClientHttpResponse which is not too hard since there are only a few methods to override, I added an example of how you would fix this. I hope this helps. I would suggest adding a named bean for this type of request, you don't want to have all your resttemplates being encrypted/decrypted.
restTemplate.getInterceptors().add( (ClientHttpRequestInterceptor)
(request, body, execution) -> {
ClientHttpResponse response;
String bodyStr = new String(body);
// Encrypt the body and send
bodyStr = encrypt(bodyStr);
try {
response = execution.execute(request, bodyStr.getBytes());
String text = IOUtils.toString(response.getBody(), StandardCharsets.UTF_8.name());
// Decrypt the response body
String decryptedResponseBody = decrypt(text);
} catch (Exception e) {
throw e;
}
InputStream inputStream = inputStream = new ByteArrayInputStream(decryptedResponseBody.getBytes());
return new ClientHttpResponse() {
#Override
public HttpHeaders getHeaders() {
return response.getHeaders();
}
#Override
public InputStream getBody() throws IOException {
return inputStream;
}
#Override
public HttpStatus getStatusCode() throws IOException {
return response.getStatusCode();
}
#Override
public int getRawStatusCode() throws IOException {
return response.getRawStatusCode();
}
#Override
public String getStatusText() throws IOException {
return response.getStatusText();
}
#Override
public void close() {
response.close();
}
};
}))
I've written a basic REST Server using Jersey2 on top of Jetty, to test out HTTP Chunked Transfer-Encoding, and gzip Content-Encoding. However I've found that the recommended method of implementing a WriterInceptor to apply a GZIPOutputStream for gzip encoding results in the server blocking instead of sending through a gzip'd chunk.
I believe it is the GZIPOutputStream waiting for it's own buffer to fill up, so I tried overriding write() method in the WriterInterceptor to force a flush() after every write (as my server always writes one chunk at a time) but that made no difference. Is there a way of forcing the flush to occur whenever a write occurs?
App.java
public class App
{
public static int lineCount=0;
public static void main( String[] args ) {
System.out.println( "Hello World!" );
ResourceConfig config = new ResourceConfig();
config.packages("com.example.mockAPIjava");
ServletHolder servlet = new ServletHolder(new ServletContainer(config));
EncodingFilter.enableFor(config, GZipEncoder.class);
Server server = new Server(2222);
ServletContextHandler context = new ServletContextHandler(server, "/*");
context.addServlet(servlet, "/*");
try {
server.start();
server.join();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
server.destroy();
}
}
}
GZIPWriterInterceptor.java
#Provider
#Compress
public class GZIPWriterInterceptor implements WriterInterceptor {
#Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
MultivaluedMap<String,Object> headers = context.getHeaders();
headers.add("Content-Encoding", "gzip");
final OutputStream outputStream = context.getOutputStream();
context.setOutputStream(new GZIPOutputStream(outputStream) {
#Override
public void write(final int b) throws IOException {
out.write(b);
out.flush();
}
#Override
public void write(final byte[] b) throws IOException {
out.write(b);
out.flush();
}
#Override
public void write(final byte[] b, final int off, final int len) throws IOException {
out.write(b, off, len);
out.flush();
}
});
context.proceed();
}
}
Resource.java
#Path("stream")
public class Resource {
#GET
#Path("test")
#Compress
#Produces(MediaType.APPLICATION_JSON)
public ChunkedOutput<String> helloWorld(#Context HttpHeaders header, #Context HttpServletResponse response) {
final ChunkedOutput<String> output = new ChunkedOutput<String>(String.class, "\r\n");
new Thread() {
public void run() {
BufferedReader br = null;
try {
String chunk;
// open file for reading
File file = new File("/tmp/stream.txt");
FileReader fr = new FileReader(file);
br = new BufferedReader(fr);
while ((chunk = getNextString(br)) != null) {
// write a chunk every second
output.write(chunk);
try {
Thread.sleep(1 * 1000);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
} catch (Exception e) {
// IOException thrown when writing the
// chunks of response: should be handled
e.printStackTrace();
} finally {
try {
output.close();
// simplified: IOException thrown from
// this close() should be handled here...
if (br!=null) { br.close(); }
} catch (IOException e1){
e1.printStackTrace();
}
}
}
}.start();
// the output will be probably returned even before
// a first chunk is written by the new thread
return output;
}
private String getNextString(BufferedReader br) throws IOException, ParseException {
App.lineCount++;
return br.readLine();;
}
}
Compress.java
//#Compress annotation is the name binding annotation for the GZIPWriterInterceptor
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
public #interface Compress {}
By overriding the write methods of GZIPOutputStream, you have just stopped it from gzipping!
public void write(final int b) throws IOException {
out.write(b);
out.flush();
}
Because you've overridden it to not invoke super.write (which you should have done), but rather out.write, you're sending directly to the context OutputStream, uncompressed.
Presumably, the receiving side is expecting gzip data and not receiving it, which may lead to all kinds of wrong behaviour.
Change the code to invoke super.write and flush:
public void write(final int b) throws IOException {
super.write(b);
flush();
}
etc.
I know a similar problem has been posted several times on StackOverflow, still all the answers did not help in my case.
Target: I need to log the SOAP-Request XML into a database before processing it further.
For this discussion, I'm happy if I can get it as a String and log it to console.
Problem: Request XML is always empty.
Environment: IBM WebSphere Application Server v8.5, EJB 3.1 web service (session bean)
I actually produced a working solution using javax.xml.soap.SOAPBody and SOAPMessage, but on production there seems to be another component which causes the following JAX-WS conflict:
ERROR org.apache.axis2.engine.AxisEngine receive - com.sun.xml.messaging.saaj.soap.ver1_1.Body1_1Impl incompatible with com.ibm.ws.webservices.engine.xmlsoap.SOAPBody
Yes, there are multiple workarounds on StackOverflow and IBM about changing the ClassLoader policy, like "local class loader first (parent last)", but we currently can't go this way.
So, my current approach (which was working on a different servlet) is to get the HTTPServletRequest, get it's InputStream and convert it to a String using IOUtils.toString().
I am aware that a request can only be consumed once and I found several approaches to avoid it (like using a HTTPServletRequestWrapper) but even using these workarounds the request XML is always empty.
Eventually I want to do the logging in an adapter, but for testing reasons I also put my attempts into the service itself (it did not have any effect).
The strange thing is: I can read all Headers and Attributes from the request (using request.getHeader() and request.getAttribute()! Only the body itself is blank!
I'm testing the application using SoapUI.
Request-XML:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ping="http://services.axonactive.com/wsdl/cet/pingservice-v1.0">
<soapenv:Header/>
<soapenv:Body>
<ping:PingPingIn/>
</soapenv:Body>
</soapenv:Envelope>
Response is actually irrelevant, but correctly working.
Console output:
[ch.zek.ecls.EclsPingServiceImpl]: Initialization successful.
EclsPingServiceImpl ping - start ping()...
EclsPingServiceImpl ping - Body:
EclsPingServiceImpl ping - body2:
EclsUtil printRequestData - [H] Accept-Encoding: gzip,deflate
EclsUtil printRequestData - [H] Content-Type: text/xml;charset=UTF-8
EclsUtil printRequestData - [H] SOAPAction: ""
EclsUtil printRequestData - [H] Content-Length: 254
EclsUtil printRequestData - [H] Host: localhost:9443
EclsUtil printRequestData - [H] Connection: Keep-Alive
EclsUtil printRequestData - [H] User-Agent: Apache-HttpClient/4.1.1 (java 1.5)
EclsUtil printRequestData - [A] javax.servlet.request.key_size: 128
EclsUtil printRequestData - [A] javax.servlet.request.cipher_suite: SSL_RSA_WITH_AES_128_CBC_SHA
EclsUtil printRequestData - [A] com.ibm.websphere.servlet.uri_non_decoded: /NewZekEclsHTTPRouter/PingService
EclsPingServiceImpl ping - end ping()
Edit: Note the headers [H] for Content-Type and Content-Length:
They "know" the content - if I put some more characters into the request XML then Content-Length is updated accordingly.
So I conclude that the content is not lost, but somehow not accessible... .
Webservice:
public class EclsPingServiceImpl{
#javax.annotation.Resource
WebServiceContext wsContext;
#javax.annotation.Resource
SessionContext sessionContext;
private Log log = LogFactory.getLog(EclsPingServiceImpl.class);
public PingOut ping(PingIn parameters) throws PingEntityNotFoundException, PingPermissionException, PingSystemException {
MessageContext messageContext = wsContext.getMessageContext();
HttpServletRequest request = (HttpServletRequest) messageContext.get(MessageContext.SERVLET_REQUEST);
// start Try 1
MultiReadHttpServletRequest multiReadHttpServletRequest = new MultiReadHttpServletRequest(request);
try {
InputStream bodyInputStream = multiReadHttpServletRequest.getInputStream();
String body = IOUtils.toString(bodyInputStream);
if (log.isDebugEnabled()) {
log.debug("Body: " + body);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// end Try 1
// start Try 2
try {
InputStream body2 = request.getInputStream();
String xml = IOUtils.toString(body2, "UTF-8");
if (log.isDebugEnabled()) {
log.debug("body2: " + xml);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// end Try 2
// Get Header data:
Enumeration<String> headerNames = request.getHeaderNames();
while(headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
if (log.isDebugEnabled()) {
log.debug("[H] " + headerName + ": " + request.getHeader(headerName));
}
}
// Get Attribute data:
Enumeration<String> attributeNames = request.getAttributeNames();
while(attributeNames.hasMoreElements()) {
String attributeName = attributeNames.nextElement();
if (log.isDebugEnabled()) {
log.debug("[A] " + attributeName + ": " + request.getAttribute(attributeName));
}
}
PingOut pingOut = new PingOut();
// some irrelevant stuff...
return pingOut;
}
}
MultiReadHttpServletRequestWrapper:
Copied from StackOverflow: Http Servlet request lose params from POST body after read it once
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedBytes;
public MultiReadHttpServletRequest(HttpServletRequest request) {
super(request);
}
#Override
public ServletInputStream getInputStream() throws IOException {
if (cachedBytes == null)
cacheInputStream();
return new CachedServletInputStream();
}
#Override
public BufferedReader getReader() throws IOException{
return new BufferedReader(new InputStreamReader(getInputStream()));
}
private void cacheInputStream() throws IOException {
/* Cache the inputstream in order to read it multiple times. For
* convenience, I use apache.commons IOUtils
*/
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}
/* An inputstream which reads the cached request body */
public class CachedServletInputStream extends ServletInputStream {
private ByteArrayInputStream input;
public CachedServletInputStream() {
/* create a new input stream from the cached request body */
input = new ByteArrayInputStream(cachedBytes.toByteArray());
}
#Override
public int read() throws IOException {
return input.read();
}
}
}
LogHandler - just to show what I tried, too...:
public class EclsSimpleLogHandler implements javax.xml.ws.handler.soap.SOAPHandler
{
private Log log = LogFactory.getLog(EclsSimpleLogHandler.class);
#Override
public boolean handleMessage(MessageContext context) {
boolean success = false;
if (log.isDebugEnabled()) {
log.debug("handleMessage()...");
}
Boolean outboundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
// check if handler is called for inbound (request) or outbound (response) message
if (outboundProperty.booleanValue()) {
success = handleResponse(context);
} else {
//success = handleRequest(context);
success = true;
}
return success;
}
private boolean handleRequest(MessageContext messageContext) {
if(log.isDebugEnabled()) {
log.debug("handling request (inbound)");
}
// Initially logging planned here, moved out for testing reasons
boolean success = false;
return success;
}
private boolean handleResponse(MessageContext messageContext) {
if(log.isDebugEnabled()) {
log.debug("handling response (outbound)");
}
boolean success = false;
ByteArrayOutputStream outputStream = null;
SOAPMessageContext context = (SOAPMessageContext) messageContext;
SOAPMessage soapMessage = (SOAPMessage) context.getMessage();
try {
/*
Initial solution, but sometimes causing:
ERROR org.apache.axis2.engine.AxisEngine receive - com.sun.xml.messaging.saaj.soap.ver1_1.Body1_1Impl
incompatible with com.ibm.ws.webservices.engine.xmlsoap.SOAPBody
// SOAPBody soapBody = soapMessage.getSOAPBody();
// String soapBodyXml = soapBody.toString();
*/
// This is working - but I want to avoid using SOAPMessage:
outputStream = new ByteArrayOutputStream();
soapMessage.writeTo(outputStream);
String soapBodyXml = new String(outputStream.toByteArray(), "UTF-8");
if (log.isDebugEnabled()) {
log.debug("responseXml:\n" + soapBodyXml);
}
success = true;
} catch (SOAPException e) {
if (log.isErrorEnabled()) {
log.error("Error while accessing SOAPMessage: " + e.getMessage());
}
} catch (IOException e) {
if (log.isErrorEnabled()) {
log.error("IOException for soapMessage.writeTo(): " + e.getMessage());
}
e.printStackTrace();
}
return success;
}
// Another method, but also resulting in an empty body:
static String extractPostRequestBody(HttpServletRequest request) {
if ("POST".equalsIgnoreCase(request.getMethod())) {
Scanner s = null;
try {
s = new Scanner(request.getInputStream(), "UTF-8").useDelimiter("\\A");
} catch (IOException e) {
e.printStackTrace();
}
return s.hasNext() ? s.next() : "";
}
return "";
}
// Another method, but also resulting in an empty body:
private String getRequestBody(HttpServletRequest request) {
HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(request);
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = requestWrapper.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) != -1) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
}
} catch (IOException ex) {
log.error("Error reading the request payload", ex);
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException iox) {
// ignore
}
}
}
return stringBuilder.toString();
}
}
I'm curious if there is an annotation/filter/interceptor capability in JAX-RS to detect if PUT or POST method contains an empty body.
Currently I have method that, if request has empty body, possibly throws NPE.
#PUT
#Produces("application/json")
#Consumes("application/json")
#Path("/update/{id}")
public Response updateCustomer(#PathParam("id") final String customerIdStr, final CustomerJson customer) {
// if request body is empty -> customer == null
return Response.ok().build();
}
I can check customer for null . But since I have plenty of such methods, it's better to have filter to do such validation.
Please!
Did you try to use Bean Validation, using an #NotNull annotation on your CustomerJson method parameter ?
Interceptors read the HTTP body and I dont find a way to send the body for further processing. But you can do this by Servlet Filter and HTTP servlet request wrapper,
public class EmptyCheckFilter implements javax.servlet.Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
if (req.getMethod().equals("POST") || req.getMethod().equals("PUT")) {
boolean dirty = false;
HttpRequestWrapper wrapper = new MyHTTPRequestWrapper(req);
try {
// check body is empty by wrapper.getBody() and set dirty = true;
} catch (Exception e) {
}
if (dirty) {
res.sendError(400, "Invalid input");
} else
chain.doFilter(wrapper, response);
} else
chain.doFilter(request, response);
}
#Override
public void destroy() {
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
}
public class MyHTTPRequestWrapper extends HttpServletRequestWrapper {
private final String body;
public MyHTTPRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
if (request.getCharacterEncoding() != null)
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, request.getCharacterEncoding()));
else
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 {
stringBuilder.append("");
}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
body = stringBuilder.toString();
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(
body.getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
return servletInputStream;
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
public String getBody() {
return this.body;
}
}
I m working with httpCore in order to create my own basic web server. Upon receiving a certain request from the user a file transfer operation has to be initiated. I m handling the request with HttpRequestHandler. My code looks like this
private HttpRequestHandler mRequestHandler = new HttpRequestHandler()
{
#Override
public void handle(HttpRequest request, HttpResponse response, HttpContext httpContext) throws HttpException, IOException
{
try{
HttpEntity entity=null;
String contentType="text/html";
entity = new EntityTemplate(new ContentProducer()
{
public void writeTo(final OutputStream outstream)throws IOException
{
OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8");
String resp = "Server is up and running";
writer.write(resp);
writer.flush();
}
});
((EntityTemplate)entity).setContentType(contentType);
response.setEntity(entity);
}catch(Exception e)
{
e.printStackTrace();
}
}
};
This is a very basic response , however What I m looking to transfer a file from the server to the client machine. How can I send a file in response ? Thanks
I figured it out myself. If someone stumbles across the same problem, here is how I got it working.
private HttpRequestHandler mRequestHandler = new HttpRequestHandler()
{
#Override
public void handle(HttpRequest request, HttpResponse response, HttpContext context)
throws HttpException, IOException
{
try{
File file = new File("/mnt/sdcard/Music/song.mp3");
FileEntity body = new FileEntity(file, "audio/mpeg");
response.setHeader("Content-Type", "application/force-download");
response.setHeader("Content-Disposition","attachment; filename=song.mp3");
response.setEntity(body);
}catch(Exception e)
{
e.printStackTrace();
}
}
};