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