Native Client

URL Loading

Introduction

This chapter describes how to use the URLLoader API to load resources such as images and sound files from a server into your application.

The example discussed in this chapter is included in the SDK in the directory examples/geturl.

Reference information

For reference information related to loading data from URLs, see the following documentation:

Background

When a user launches your Native Client web application, Chrome downloads and caches your application's HTML file, manifest file (.nmf), and Native Client module (.nexe). If your application needs additional assets, such as images and sound files, it must explicitly load those assets. You can use the Pepper APIs described in this chapter to load assets from a URL into your application.

After you've loaded assets into your application, Chrome will cache those assets. To avoid being at the whim of the Chrome cache, however, you may want to use the Pepper FileIO API to write those assets to a persistent, sandboxed location on the user's file system.

The geturl example

The SDK includes an example called geturl demonstrating downloading files from a server. This example has five primary files:

  • geturl.html - The HTML code that launches the Native Client module and sends a PostMessage request to the module with the URL of the file to retrieved.
  • geturl_success.html - An HTML file on the server whose contents are being retrieved using the URLLoader API.
  • geturl.cc - The code that sets up and provides and entry point into the Native client module.
  • geturl_handler.cc - The code that retrieves the contents of the geturl_success.html file and returns the results (this is where the bulk of the work is done).
  • geturl.nmf - The Native Client manifest file.
The remainder of this document covers the code in the geturl.cc and geturl_handler.cc files.

URL loading overview

Like many Pepper APIs, the URLLoader API includes a set of methods that execute asynchronously and that invoke callback functions in your Native Client module. The high-level flow for the geturl example is described below. Note that methods in the namespace pp::URLLoader are part of the Pepper URLLoader API, while functions in the namespace GetURLHandler are part of the code in the Native Client module (specifically in the file examples/geturl/geturl_handler.cc). The following image shows the flow of the geturl_handler code:

url loader

Following are the high-level steps involved in URL loading.

  1. The Native Client module calls pp::URLLoader::Open() to begin opening the URL.
  2. When Open() completes, it invokes a callback function in the Native Client module (in this case, OnOpen()).
  3. The Native Client module calls the Pepper function URLLoader::ReadResponseBody() to begin reading the response body with the data. ReadResponseBody() is passed an optional callback function in the Native Client module (in this case, On Read()). The callback function is an optional callback because ReadResponseBody() may read data and return synchronously if data is available (this improves performance for large files and fast connections).

The remainder of this document demonstrates how the previous steps are implemented in the geturl example.

geturl deep dive

Setting up the request

HandleMessage() in geturl.cc creates a GetURLHandler instance and passes it the URL of the asset to be retrieved. Then HandleMessage() calls Start() to start retrieving the asset from the server:


void GetURLInstance::HandleMessage(const pp::Var& var_message) {
  if (!var_message.is_string()) {
    return;
  }
  std::string message = var_message.AsString();
  if (message.find(kLoadUrlMethodId) == 0) {
  	// The argument to getUrl is everything after the first ':'.
  	size_t sep_pos = message.find_first_of(kMessageArgumentSeparator);
  	if (sep_pos != std::string::npos) {
  		std::string url = message.substr(sep_pos + 1);
  		printf("GetURLInstance::HandleMessage('%s', '%s')\n", message.c_str(), url.c_str());
		fflush(stdout);
      	GetURLHandler* handler = GetURLHandler::Create(this, url);
      	if (handler != NULL) {
        	// Starts asynchronous download. When download is finished or when an
        	// error occurs, |handler| posts the results back to the browser
        	// vis PostMessage and self-destroys.
        	handler->Start();	
      	}
    }
  }
}	

Notice that the constructor for GetUrlHandler in geturl_handler.cc sets up the parameters of the URL request (using SetURL(), SetMethod(), and SetRecordDownloadProgress()):


GetURLHandler::GetURLHandler(pp::Instance* instance,
                             const std::string& url)
	 : instance_(instance),
	 url_(url),
	 url_request_(instance),
	 url_loader_(instance),
	 buffer_(new char[READ_BUFFER_SIZE]),
	 cc_factory_(this) {
	 url_request_.SetURL(url);
	 url_request_.SetMethod("GET");
	 url_request_.SetRecordDownloadProgress(true);
}

Downloading the data

Start() in geturl_handler.cc creates a callback (cc) using a CompletionCallbackFactory. The callback is passed to Open() to be called upon its completion. Open() begins loading the URLRequestInfo.

	
void GetURLHandler::Start() {
  pp::CompletionCallback cc =
      cc_factory_.NewCallback(&GetURLHandler::OnOpen);
  url_loader_.Open(url_request_, cc);
}

OnOpen() ensures that the Open call was successful and, if so, calls GetDownloadProgress() to determine the amount of data to be downloaded so it can allocate memory for the response body.

Note that the amount of data to be downloaded may be unknown, in which case GetDownloadProgress() sets total_bytes_to_be_received to -1. It is not a problem if total_bytes_to_be_received is set to -1 or if GetDownloadProgress() fails; in these scenarios memory for the read buffer can't be allocated in advance and must be allocated as data is received.

Finally, OnOpen() calls ReadBody().

	
void GetURLHandler::OnOpen(int32_t result) {
  if (result != PP_OK) {
    ReportResultAndDie(url_, "pp::URLLoader::Open() failed", false);
    return;
  }
  int64_t bytes_received = 0;
  int64_t total_bytes_to_be_received = 0;
  if (url_loader_.GetDownloadProgress(&bytes_received, &total_bytes_to_be_received)) {
    if (total_bytes_to_be_received > 0) {
      url_response_body_.reserve(total_bytes_to_be_received);
    }
  }
  url_request_.SetRecordDownloadProgress(false);

  ReadBody();
}

ReadBody() creates another CompletionCallback (a NewOptionalCallback) and passes it to ReadResponseBody(), which reads the response body, and AppendDataBytes(), which appends the resulting data to the previously read data.


void GetURLHandler::ReadBody() {
  pp::CompletionCallback cc =
      cc_factory_.NewOptionalCallback(&GetURLHandler::OnRead);
  int32_t result = PP_OK;
  do {
    result = url_loader_.ReadResponseBody(buffer_, READ_BUFFER_SIZE, cc);
    if (result > 0) {
      AppendDataBytes(buffer_, result);
    }
  } while (result > 0);

  if (result != PP_OK_COMPLETIONPENDING) {
    cc.Run(result);
  }
}	
	
void GetURLHandler::AppendDataBytes(const char* buffer, int32_t num_bytes) {
  if (num_bytes <= 0)
    return;
  num_bytes = std::min(READ_BUFFER_SIZE, num_bytes);
  url_response_body_.insert(url_response_body_.end(), buffer, buffer + num_bytes);
}

Eventually either all the bytes have been read for the entire file (resulting in PP_OK or 0), all the bytes have been read for what has been downloaded, but more is to be downloaded (PP_OK_COMPLETIONPENDING or -1), or there is an error (less than -1). OnRead() is called in the event of an error or PP_OK.

Displaying a result

OnRead calls ReportResultAndDie() when either an error or PP_OK is returned to indicate streaming of file is complete. ReportResultAndDie() then calls ReportResult(), which calls PostMessage() to send the result back to the HTML page.

URLs

You must currently distribute Native Client applications as either hosted applications or packaged applications in the Chrome Web Store (see Distributing Your Application for a description of these distribution options). If your application is a hosted application, you can use the Pepper APIs to load an asset from your server by passing a fully-qualified URL for the asset to the Pepper APIs. If you application is a packaged application, you can still use the Pepper APIs, but you must pass a relative path to your asset.

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.