Tuesday, March 3, 2009

Flex vs. Web Services (pt.2) SOAP faults

Today's fun with Flex came about because one of our developers was trying to figure out how to handle exceptions from a simple user authentication web service.

Basically, there's a few exceptions that can be thrown from the Java code (but this post applies to ANYONE using SOAP faults and flex). Those exceptions end up being wrapped in SOAP faults automatically (as defined in the WSDL).

The Flex Builder web service import wizard successfully builds the Actionscript classes and they seem fine.

Unfortunately, when you send a request that would result in an exception being thrown, you won't see your exception. Instead you'll be able to drill down in the Fault to the following somewhat cryptic message:

Error #2032: Stream Error.

What we discovered is that Flex can't see the body when ANY HTTP status code other than 200 is returned. (There's debate on that - see one of the Flex bugs) The "official" suggested workaround seems to be bastardize your web service so that it returns a 200 status code with a SOAP fault instead of the normal 500 status code. (This also implies you won't be able to use other published web services like Amazon's because they follow the standard with a status code of 500)

Anyhow, assuming you want to hack up your server running the web service and are using Java (we're using CXF, but this is applicable for a servlet container).

1) Create a class extending HttpServletResponseWrapper
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.apache.log4j.Logger;

public class FlexSoapFaultServletResponseWrapper extends HttpServletResponseWrapper {

static final Logger logger = Logger.getLogger(FlexSoapFaultServletResponseWrapper.class);

public FlexSoapFaultServletResponseWrapper(HttpServletResponse response) {
super(response);
}

@Override
public void setStatus(int statusCode) {
if (statusCode == HttpServletResponse.SC_INTERNAL_SERVER_ERROR) {
super.setStatus(HttpServletResponse.SC_OK);
logger.debug("Converted status code 500 -> 200");
}
}

}
2) Create a servlet filter using that wrapper
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;

public class FlexSoapFaultHandlingHackFilter implements Filter {

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
FlexSoapFaultServletResponseWrapper wrapper = new FlexSoapFaultServletResponseWrapper(httpServletResponse);
chain.doFilter(request, wrapper);
}

@Override
public void init(FilterConfig config) throws ServletException {
}

@Override
public void destroy() {
}
}
3) Define the filter in your WEB-INF/web.xml file and associate it with your CXFServlet
...
<filter>
<filter-name>FlexSoapFaultHandlingHackFilter</filter-name>
<filter-class>com.donbest.services.FlexSoapFaultHandlingHackFilter</filter-class>
</filter>
...
<filter-mapping>
<filter-name>FlexSoapFaultHandlingHackFilter</filter-name>
<servlet-name>CXFServlet</servlet-name>
</filter-mapping>
...
<servlet>
<servlet-name>CXFServlet</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
...

That's it, now any SOAP fault will be translated to a HTTP status 200 and Flex will be able to handle it. (oh, this might have the much unwanted side-effect of masking any TRUE 500 errors)

No comments: