Monday, February 9, 2009

Flex: Proxied HTTPService and REST

My article Java EE and Flex: A compelling combination, Part 2 was published on JavaWorld last week. In that article, I briefly discussed some of the extra features that BlazeDS adds to HTTPService beyond what is support with Flex's HTTPService out of the box without BlazeDS.

I have previously blogged on some of these advantages of HTTPService with BlazeDS. In this blog posting, I plan to focus on something outside of the scope of both the article and previously blog posting: how BlazeDS allows the Flex HTTPService to be used with HTTP methods other than GET and POST.

In the JavaWorld article, I wrote the following: "Another advantage of using HTTPService in conjunction with BlazeDS is that you can use HTTP methods beyond GET and POST (the only two supported in HTTPService without the proxy service)." I will use the remainder of this blog posting to expand on this and illustrate this with examples.

To illustrate the differences between HTTP methods supported by non-proxied HTTPService (out-of-the-box without BlazeDS) and proxied HTTPService (with BlazeDS), I have put together a very simple Flex application that connects to a back-end HTTP-exposed server using both non-proxied and proxied HTTPService clients. The main MXML source code for this simple illustrative example is shown next.

FlexHttpClient.mxml


<?xml version="1.0" encoding="UTF-8" ?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:dustin="components.*"
width="800" height="500"
applicationComplete="invokeServer('GET');">

<mx:Script>
import mx.controls.Alert;
import mx.events.ItemClickEvent;
import flash.events.MouseEvent;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.utils.ObjectUtil;

include "includes/HttpMethodHandling.as";
include "includes/ResponseCodeHandling.as";
</mx:Script>

<mx:HTTPService id="httpServiceProxied"
useProxy="true"
resultFormat="object"
destination="DefaultHTTP"
fault="faultHandler(event);"
result="proxiedResultHandler(event);">
<mx:request>
<id>5</id>
</mx:request>
</mx:HTTPService>

<mx:HTTPService id="httpService"
useProxy="false"
resultFormat="object"
url="http://localhost:8080/RESTfulFlex/httpServer"
fault="faultHandler(event);"
result="nonProxiedResultHandler(event);">
<mx:request>
<id>5</id>
</mx:request>
</mx:HTTPService>

<mx:HTTPService id="httpServiceResponseCodes"
useProxy="false"
resultFormat="object"
method="GET"
url="http://localhost:8080/RESTfulFlex/responseCodes"
fault="responseCodesFaultHandler(event);"
result="resultCodeHandler(event);">
<mx:request>
<requestedCode>{desiredHttpCode.text}</requestedCode>
</mx:request>
</mx:HTTPService>

<mx:HTTPService id="httpServiceResponseCodesProxied"
useProxy="true"
resultFormat="object"
method="GET"
destination="DefaultHttpResponseCode"
fault="responseCodesFaultHandler(event);"
result="proxiedResultCodeHandler(event);">
<mx:request>
<requestedCode>{desiredHttpCode.text}</requestedCode>
</mx:request>
</mx:HTTPService>

<mx:TabNavigator id="tabs" width="750" height="450">
<mx:Panel id="mainPanel"
title="Flex HTTPService Demonstrated"
label="HTTPService Demonstrated">
<mx:Form>
<dustin:ProxySelectorFormItem label="To Proxy or Not To Proxy?"
selectorHandler="setProxyStatus" />
<mx:FormItem label="Select HTTP Method">
<mx:RadioButtonGroup id="method" itemClick="invokeHttpMethod(event);"/>
<mx:RadioButton groupName="method" id="get" label="GET"/>
<mx:RadioButton groupName="method" id="post" label="POST"/>
<mx:RadioButton groupName="method" id="put" label="PUT"/>
<mx:RadioButton groupName="method" id="remove" label="DELETE"/>
<mx:RadioButton groupName="method" id="options" label="OPTIONS"/>
<mx:RadioButton groupName="method" id="trace" label="TRACE"/>
</mx:FormItem>
<mx:FormItem label="HTTP Method Invoked">
<mx:Label id="serviceResults" />
</mx:FormItem>
</mx:Form>
</mx:Panel>
<mx:Panel id="responseCodePanel"
title="HTTP Response Codes"
label="HTTP Response Codes">
<mx:Form>
<dustin:ProxySelectorFormItem
label="Proxied?"
selectorHandler="setResponseCodesProxyStatus" />
<mx:FormItem label="Desired Return Code">
<mx:TextArea id="desiredHttpCode" />
</mx:FormItem>
<mx:FormItem>
<mx:Button label="Submit Desired Response Code"
click="invokeHttpResponseCodes(event);"/>
</mx:FormItem>
<mx:FormItem label="Returned Text">
<mx:Text id="returnedHttpResponse" />
</mx:FormItem>
</mx:Form>
</mx:Panel>
</mx:TabNavigator>

</mx:Application>


The above MXML file contains most of the layout for the simple application and includes the configuration of the proxied and non-proxied instances of HTTPService. You may have noticed that there are actually four instances of HTTPService. Two instances are the proxied and non-proxied instances for testing the different HTTP methods while the other two instances are the proxied and non-proxied instances used to test handling of HTTP response codes. I will not cover the latter two in this post, but plan to cover response code handling in a future post.

I intentionally placed the static layout MXML code in the file above. I factored the event handling code, written in ActionScript, into two separate files that are included by the MXML code above. These two ActionScript files are shown next.

HttpMethodHandling.as

/** true if proxied service is to be used; false otherwise. */
[Bindable] private var proxiedService:Boolean = false;

/**
* Invoke HTTPservice with HTTP method specified by parameter.
*
* @param httpMethod HTTP method to be used.
*/
private function invokeServer(httpMethod:String):void
{
if (proxiedService)
{
httpServiceProxied.method = httpMethod;
httpServiceProxied.send();
}
else
{
httpService.method = httpMethod;
if (httpMethod == "POST")
{
const dummyObject:Object = { "key" : "value" };
httpService.send(dummyObject);
}
else
{
httpService.send();
}
}
}

/**
* Handler for HTTP method selected from radio button group
*
* @param event Event associated with clicking of radio button to select HTTP
* method.
*/
private function invokeHttpMethod(event:ItemClickEvent):void
{
serviceResults.text = "Loading ...";
const selectedHttpMethod:String = event.currentTarget.selectedValue;
invokeServer(selectedHttpMethod);
}

/**
* Handler for radio button group used to determine if proxied or non-proxied
* HTTPService is to be used.
*/
private function setProxyStatus(event:ItemClickEvent):void
{
const proxyStatus:String = event.currentTarget.selectedValue;
proxiedService = (proxyStatus == "BlazeDS Proxied");
}

/**
* Fault handler for faults encountered during a service call.
*
* @param event Fault Event needing to be handled.
*/
private function faultHandler(event:FaultEvent):void
{
serviceResults.text = "Failure trying to access service.\n"
+ event.fault.faultString + "\n" + event.fault.faultDetail;
}

/**
* Results handler for result of proxied HTTPService invocation.
*
* @param event Result Handler for proxied HTTPService call.
*/
private function proxiedResultHandler(event:ResultEvent):void
{
serviceResults.text = ObjectUtil.toString(httpServiceProxied.lastResult);
}

/**
* Results handler for result of non-proxied HTTPService invocation.
*
* @param event Result Handler for non-proxied HTTPService call.
*/
private function nonProxiedResultHandler(event:ResultEvent):void
{
Alert.show("Non-Proxied Result Handler Accessed!");
serviceResults.text = ObjectUtil.toString(httpService.lastResult);
}



ResponseCodeHandling.as


/** true if proxied service is to be used; false otherwise. */
[Bindable] private var responseCodesProxiedService:Boolean = false;

/**
* Handler for invoking of response codes service.
*
* @param event Event associated with clicking of radio button to submit
* response code.
*/
private function invokeHttpResponseCodes(event:MouseEvent):void
{
returnedHttpResponse.text = "Loading ...";
if (responseCodesProxiedService)
{
httpServiceResponseCodesProxied.send();
}
else
{
httpServiceResponseCodes.send();
}
}

/**
* Handler for radio button group used to determine if proxied or non-proxied
* HTTPService is to be used for response code service.
*/
private function setResponseCodesProxyStatus(event:ItemClickEvent):void
{
const proxyStatus:String = event.currentTarget.selectedValue;
responseCodesProxiedService = (proxyStatus == "BlazeDS Proxied");
}

/**
* Results handler for result of service call for HTTP response code.
*
* @param event Result Handler for non-proxied response code reply.
*/
private function resultCodeHandler(event:ResultEvent):void
{
returnedHttpResponse.text =
"SUCCESS: " + ObjectUtil.toString(httpServiceResponseCodes.lastResult);
}

/**
* Results handler for result of service call for HTTP response code that is
* based on a proxied call.
*
* @param event Result Handler for proxied response code reply.
*/
private function proxiedResultCodeHandler(event:ResultEvent):void
{
returnedHttpResponse.text =
"SUCCESS: " + ObjectUtil.toString(httpServiceResponseCodesProxied.lastResult);
}

/**
* Fault handler for faults encountered during a service call on the
* response codes service.
*
* @param event Fault Event that needs to be handled.
*/
private function responseCodesFaultHandler(event:FaultEvent):void
{
returnedHttpResponse.text = "ERROR: Failure trying to access service.\n"
+ event.fault.faultString + "\n" + event.fault.faultDetail;
}



The MXML file and two ActionScript files shown above constitute the client side of this example. Now, a HTTP-exposed server side is required to complete the example. This is implemented using a Java servlet. The servlet, which passes back a simple String indicating that a particular HTTP method has been called, is shown next.

SimpleHttpServer.java


package dustin.flex.rest;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.logging.Logger;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


/**
* Simplistic Java EE server-side application intended for demonstration of
* Flex HTTPService capabilities with and without BlazeDS.
*
* @author Dustin
*/
public class SimpleHttpServer extends HttpServlet
{
/** Set up java.util.Logger. */
private static Logger LOGGER =
Logger.getLogger("dustin.flex.rest.SimpleHttpServer");

/**
* Servlet method responding to HTTP GET methods calls.
*
* @param request HTTP request.
* @param response HTTP response.
*/
@Override
public void doGet( HttpServletRequest request,
HttpServletResponse response ) throws IOException
{
LOGGER.warning("[DUSTIN] doGet() Accessed!");
final PrintWriter out = response.getWriter();
final String requestId = request.getParameter("id");
out.write("GET method (retrieving data) was invoked with ID " + requestId + "!");
}

/**
* Servlet method responding to HTTP POST methods calls.
*
* @param request HTTP request.
* @param response HTTP response.
*/
@Override
public void doPost( HttpServletRequest request,
HttpServletResponse response ) throws IOException
{
LOGGER.warning("[DUSTIN] doPost() Accessed!");
final PrintWriter out = response.getWriter();
out.write("POST method (changing data) was invoked!");
}

/**
* Servlet method responding to HTTP PUT methods calls.
*
* @param request HTTP request.
* @param response HTTP response.
*/
@Override
public void doPut( HttpServletRequest request,
HttpServletResponse response ) throws IOException
{
LOGGER.warning("[DUSTIN] doPut() Accessed!");
final PrintWriter out = response.getWriter();
final String requestId = request.getParameter("id");
out.write("PUT method (inserting data) was invoked with ID " + requestId + "!");
response.setStatus(HttpServletResponse.SC_CREATED);
}

/**
* Servlet method responding to HTTP DELETE methods calls.
*
* @param request HTTP request.
* @param response HTTP response.
*/
@Override
public void doDelete( HttpServletRequest request,
HttpServletResponse response ) throws IOException
{
LOGGER.warning("[DUSTIN] doDelete() Accessed!");
response.setStatus(HttpServletResponse.SC_OK);
final PrintWriter out = response.getWriter();
final String requestId = request.getParameter("id");
out.write("DELETE method (removing data) was invoked with ID " + requestId + "!");
}

@Override
public String getServletInfo()
{
return "Provide examples of HTTP methods invoked by clients.";
}
}



With this servlet in place, we can now look at the differences between HTTP method handling for HTTPService with and without BlazeDS. I will be using GlassFish to host all server-side code shown in this blog posting.

Without BlazeDS (or similar proxy server support), the Flex HTTPService only supports the GET and POST methods. I previously blogged about even the POST in a non-proxied HTTPService being treated like a GET in some cases. The example code above does use a body in the POST request to ensure that it is treated like a POST. As the following screen snapshots indicate, all of the other major HTTP methods (PUT, DELETE, OPTIONS, TRACE) are treated as GET methods. POST is treated like a POST.

Non-Proxied HTTPService GET



Non-Proxied HTTPService POST



Non-Proxied HTTPService PUT



Non-Proxied HTTPService DELETE



Non-Proxied HTTPService OPTIONS



Non-Proxied HTTPService TRACE





In the non-proxied HTTPService examples illustrated above, all of the HTTP methods were treated as GET except for the POST method. As stated earlier, even that can be treated as a GET if no body is included in the request.

The next series of screen snapshots show the same HTTPService calls as before, but with a BlazeDS proxy used now.

Proxied HTTPService GET



Proxied HTTPService POST



Proxied HTTPService PUT



Proxied HTTPService DELETE



Proxied HTTPService OPTIONS



Proxied HTTPService TRACE




The results shown in the sample Flex client for BlazeDS-proxied HTTPService instances are more interesting than the non-proxied counterparts. The GET and POSt work the same for both proxied and non-proxied, but the other HTTP methods have different results in the proxied versions than in the non-proxied versions. In the screen snapshots of the results of the TRACE and OPTIONS calls, I took advantage of the Flex Label feature that displays an automatic truncation tip when the contents of the Label are larger than the label and no tooltip is provided for the Label. We can see that when BlazeDS is used, HTTPService can "see" the options returned by the server and can see the traced route.

The results for PUT and DELETE are not quite as encouraging. While we see "null" in the text field, all is not lost. The result handler is called when the HTTP PUT or DELETE invocation returns, but the text does not get populated. Also, we can see that the PUT and DELETE are being called when we look at the GlassFish log as shown in the next screen snapshot.



In the GlassFish log record shown immediately above, log record number 300 represents the GET initially performed when the Flex application's applicationComplete event is triggered. Log record 301 is the explicit non-proxied GET and log record 302 is the explicit non-proxied POST. Log records 303 through 306 are the PUT, DELETE, OPTIONS, and TRACE non-proxied calls treated like GETs. Log record 307 is the BlazeDS-proxied GET and log record 308 is the BlazeDS-proxied POST. Log record 309 and log record 310 are clearly the proxied PUT and DELETE calls as evidenced by the WARNING text. This also proves that the server-side doPut() and doDelete() methods were in fact invoked.

It is nice to be able to use Flex's HTTPService with BlazeDS to use a more complete range of the standard HTTP methods. I would like to find out if there is a way to return a non-null body from PUT and DELETE calls so that the REST-inspired concept of Hypermedia as the Engine of Application State (HATEOAS) might be more fully realized for those two HTTP methods.

As mentioned earlier, BlazeDS also makes HTTPService more useful by improving its handling of HTTP response codes similarly to how it improves HTTPService's handling of HTTP method differentiation. I hope to cover this BlazeDS-improved handling of HTTP response codes in a future blog post.


Addition (March 2010)

Christian Junk pointed out that I had not included the piece of MXML code defining ProxySelectorFormItem. That code is shown next and this sample comes from a file called ProxySelectorFormItem.mxml.


<?xml version="1.0" encoding="utf-8"?>
<mx:FormItem xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:dustin="components.*">
<mx:Script>
<![CDATA[
[Bindable] public var selectorHandler:Function;
[Bindable] public var proxiedLabel:String = "BlazeDS Proxied";
[Bindable] public var nonProxiedLabel:String = "Not Proxied";
]]>
</mx:Script>

<mx:RadioButtonGroup id="proxyStatus" itemClick="{selectorHandler(event)}"/>
<mx:RadioButton groupName="proxyStatus" id="proxy" label="{proxiedLabel}" />
<mx:RadioButton groupName="proxyStatus" id="nonproxy" label="{nonProxiedLabel}" />

</mx:FormItem>



Conclusion

BlazeDS allows Flex developers to use a wider range of HTTP methods. This is one of the many advantages that BlazeDS offers for proxied HTTPService when compared to non-proxied HTTPService.


Additional Resources

There are several good blog postings on using Flex with HTTP and REST. A particularly insightful resource is the StackOverflow thread Is It Feasible to Create a REST Client with Flex?.

5 comments:

Dean said...

Hi Dustin,

Create set of examples. I have an issue using HTTPService in Flex3. IE works find on the POST when calling a PHP script to write to a MYSQL. Other Browers (Firefox, Safari etc) do nothing and show no FaultEvent.

Any suggestions please as this is driving me crazy.

L. Kirven said...

Hi Dustin,

Did you ever try non-proxied with Flex 4? Or did you get the same results?

@DustinMarx said...

Lazar,

I have not yet tried it with Flex 4. My guess is that proxies are still required in the same situations as in Flex 3, but it would be worth a try.

Dustin

Anonymous said...

Hi Dustin,

could you please send me the whole source code of this article. I'm missing "ProxySelectorFormItem" ...

Much thanks in advance!

Regards,
Christian

@DustinMarx said...

Christian,

I have added the code snippet for the file called ProxySelectorFormItem.mxml into the main blog post above. Thanks for pointing out the omission.