Connecting Directly to Web Services with Rhodes
===
Rhodes provides another utility for connecting to backend services besides the [`SyncEngine`](/rhodes/synchronization) called `AsyncHttp`. Your application can use the `AsyncHttp` library to interact with web services, pull remote images, etc.
## AsyncHttp API
Below is the list of available `AsyncHttp` methods you can use to asyncrhonously make calls to http(s) services:
### Common Parameters
* `:url` - URL of the request.
* `:headers` - Hash of headers to send with the request.
* `:callback` - Callback action to execute when the request is done.
* `:callback_param` - (optional) Parameters to send to the callback. Parameters values should be url encoded:
:callback_param => "action=text&title=#{Rho::RhoSupport.url_encode(@params['page_title'])}"
#values will be decoded automatically in callback
* `:authentication` - (optional) Send Basic Auth header with request. This takes the form:
:authentication => {
:type => :basic,
:username => "john",
:password => "secret"
}
* `:ssl_verify_peer` - (optional) Verify SSL certificates, `true` by default.
### `get(:url, :headers, :callback, :callback_param)`
Perform HTTP GET request to the specified :url
. You can also provide an optional hash of :headers
and :callback_param
.
:::ruby
Rho::AsyncHttp.get(
:url => "http://www.example.com",
:headers => {"Cookie" => cookie},
:callback => (url_for :action => :httpget_callback)
)
Example using Basic Auth:
:::ruby
Rho::AsyncHttp.get(
:url => "http://www.example.com",
:headers => {"Cookie" => cookie},
:callback => (url_for :action => :httpget_callback),
:authentication => {
:type => :basic,
:username => "john",
:password => "secret"
}
)
Example of synchronous call:
:::ruby
result = Rho::AsyncHttp.get(
:url => "http://www.apache.org/licenses/LICENSE-2.0"
)
@get_result = res["body"]
**NOTE: WARNING! Do NOT use synchronous calls unless you know what you are doing. This is a blocking call and will cause your UI to freeze.**
### `post(:url, :headers, :body, :callback, :callback_param)`
Perform HTTP POST request to the specified :url
. As with get, you can specify optional arguments.
:::ruby
# :post HTTP POST body to send with request.
# :http_command (optional) Use different HTTP method
# (i.e. "put").
Rho::AsyncHttp.post(
:url => "https://www.example.com",
:headers => {"Cookie" => cookie},
:body => "username=john&password=secret",
:callback => url_for(:action => :httppost_callback),
:callback_param => "post=complete"
)
### `download_file(:url, :headers, :filename, :callback, :callback_param)`
Download a file to the specified filename.
:::ruby
file_name = File.join(Rho::RhoApplication::get_base_app_path, "test.jpg")
# :filename Full path to download file target.
Rho::AsyncHttp.download_file(
:url => "http://www.google.com/images/logos/ps_logo2.png",
:filename => file_name,
:headers => {},
:callback => url_for(:action => :httpdownload_callback),
)
### `upload_file(:url, :headers, :filename, :body, :callback, :callback_param)`
Upload the specified file using HTTP POST:
:::ruby
file_name = File.join(Rho::RhoApplication::get_base_app_path, "myfile.txt")
# :filename Full path to download file target.
# :post HTTP POST body to send with request.
Rho::AsyncHttp.upload_file(
:url => "http://example.com/receive_file",
:filename => file_name,
:body => "" #=> leave blank, AsyncHttp will fill in multipart body
:headers => {"Content-Type"=>"text/plain"}, #=> used as body text content type
:callback => url_for(:action => :httpupload_callback),
:callback_param => "" )
You can also send multiple files in a single `upload_file` request:
:::ruby
# :multipart Array of hashes containing
# file information.
#
# :multipart[:filename] Name of file to be uploaded.
#
# :multipart[:filename_base] (optional) Base directory containing
# the :filename.
# :multipart[:name] (optional) File type, defaults
# to "blob".
#
# :multipart[:content_type] (optional) Content-Type header,
# defaults to "application/octet-stream".
Rho::AsyncHttp.upload_file(
:url => "some_url",
:multipart => [
{
:filename => file_name,
# if missed base name from file path used
:filename_base => "files_to_upload",
:name => "image",
:content_type => "application/octet-stream"
},
# You can specify file content inline.
{
:body => "upload test",
:name => "upload_body_test",
:content_type => "plain/text"
}
]
)
### `cancel(cancel_callback = "*")`
Cancel the current `AsyncHttp` call. Defaults to "*", which cancels all requests.
:::ruby
AsyncHttp.cancel
## AsyncHttp Callback
As you noticed with each of the code samples above, we specified a `:callback` action. This will execute with the `AsyncHttp` request is completed.
### Callback Parameters
The following parameters are available in an `AsyncHttp` callback:
* `@params["body"]` - The body of the HTTP response.
**NOTE: In the case of a JSON response (Content-Type="application/json"), the `@params["body"] will be parsed automatically and contain a ruby data structure. Otherwise, `@params["body"]` contains the raw response body.**
**NOTE: In the case of an XML response (Content-Type="application/xml"), Rhodes can automatically parse the `@params["body"]` as well if you enable the ["rexml extension"](/rhodes/extensions#rhodes-extensions) in your application.**
* `@params["headers"]` - A hash containing the response headers.
* `@params["cookies"]` - A the server cookies parsed and usable for subsequent requests.
* `@params["http_error"]` - HTTP error code if response code was not 200.
## AsyncHttp and Animated Transitions
Adding an animated transition to an `AsyncHttp` request requires some small setup and is useful for displaying a smoother user experience.
To enable an animated transition, the controller action must set a `"Wait-Page"` response header after making the `AsyncHttp` call. The response header tells the user interface that an `AsyncHttp` request has been spawned and that the rendered view should be treated as a transient page, it will not be added to the navigation history.
:::ruby
def async_show
Rho::AsyncHttp.get(
:url => "http://rhostore.heroku.com/products/#{@params["product_id"]}.json",
:callback => url_for(:action => :show_callback),
)
@response["headers"]["Wait-Page"] = "true"
render :action => :waiting
end
This example renders a waiting screen while awaiting a response from the `AsyncHttp` request. The `:waiting` page is transient and will not be added to the navigation history, which means clicking back won't open the page.
The `AsyncHttp` callback can render the response by calling `render_transition`. This function is defined in `ApplicationHelper` so make sure you `include` it in your controller. The `render_transition` function works much like `render` except that it will animate a transition from the previous page.
Below, a product model is created using the response from the web service and then calling `render_transition`, which leverages the show view template:
:::ruby
include ApplicationHelper
def show_callback
if @params["status"] == "ok"
@product = Product.new(@params["body"]["product"])
@product.object = @product.id
render_transition :action => :show
else
# In this example, an error just navigates back to the index w/o transition.
WebView.navigate url_for :action => :index
end
end
**NOTE: To disable jQuery Mobile page caching (by default jQuery Mobile cachepages in the DOM) globally, look for cache control options in jQuery Mobile documentation**
You can disable page caching globally by using jQuery Mobile initialization option in layout.erb file:
$.mobile.page.prototype.options.domCache = true;
Also, you can disable caching on exact page transition with `data-dom-cache` attribute, like that: *link text*
### Note About Animated Transitions
If you deploy to platforms that don"t handle animated transitions (like Windows Mobile and BlackBerry), the controller will need to handle both cases. In your `AsyncHttp` request, you"ll need to set the `callback_param` with the `@request` variable.
There's a helper function called `caller_request_hash_to_query` defined in `ApplicationHelper` that you can invoke. The returned value is a string that looks like "_request=", where `` is the URL-encoded JSON representation of the `@request` value. This parameter is used to give the callback function some context of whether the user interface made the request with or without transition enabled.
:::ruby
include ApplicationHelper
def async_show
Rho::AsyncHttp.get(
:url => "http://rhostore.heroku.com/products/#{@params["product_id"]}.json",
:callback => url_for(:action => :show_callback),
:callback_param => caller_request_hash_to_query
)
@response["headers"]["Wait-Page"] = "true"
render :action => :waiting
end
In your callback function, the first thing you need to do is invoke `caller_request_query_to_hash` (also defined in `ApplicationHelper`) that deserializes the `@request` query parameter value passed in via `callback_param` shown in the example above. The function sets a `@caller_request` in the current context. You can then use it to determine if the user interface had transition enabled by inspecting the `"Transition-Enabled"` request header. For transitions, call `render_transition`, otherwise call `WebView.navigate`.
:::ruby
def show_callback
caller_request_query_to_hash
if @params["status"] == "ok"
@product = Product.new(@params["body"]["product"])
@product.object = @product.id
if @caller_request["headers"]["Transition-Enabled"] == "true"
render_transition :action => :show
else
WebView.navigate(
url_for(:action => :show, :id => @product.object)
)
end
else
WebView.navigate( url_for(:action => :index) )
end
end
## AsyncHttp Example
Here is a controller in the [Rexml sample from the System API Samples](http://github.com/rhomobile/system-api-samples). It makes a `AsyncHttp.get` call to a test web service. Then it parses the web service response with rexml and displays the result.
:::ruby
def webservicetest
Rho::AsyncHttp.get(
:url => "http://rhostore.heroku.com/products.xml",
:callback => url_for(:action => :httpget_callback),
)
render :action => :wait
end
def get_res
@@get_result
end
def get_error
@@error_params
end
def httpget_callback
if @params["status"] != "ok"
@@error_params = @params
WebView.navigate( url_for(:action => :show_error) )
else
@@get_result = @params["body"]
begin
require "rexml/document"
doc = REXML::Document.new(@@get_result)
puts "doc : #{doc}"
rescue Exception => e
puts "Error: #{e}"
@@get_result = "Error: #{e}"
end
WebView.navigate( url_for(:action => :show_result) )
end
end
def show_result
render :action => :webservicetest, :back => "/app/RexmlTest"
end