Dataprovider
From OpenLaszlo
Contents |
Introduction
A dataprovider (DataProvider) specifies the transport mechanism and policies for communicating data requests from the client to the server. For example, dataproviders may batch requests, whitelist/blacklist URIs.
Datasets make data requests through dataproviders. The transport mechanism is abstracted away from the dataset. That is, a dataset could use a dataprovider that uses HTTP in one instance, and then swap out to another dataprovider that uses XMPP as its transport as long as the dataprovider can support the dataset's request.
Requirements
- The design MUST provide a dataprovider interface that developers may implement.
- The design MUST provide a mechanism to set a different default dataprovider.
- The design MUST provide a mechanism for datasets to set a different dataprovider.
- The platform SHOULD provide a default dataprovider implementation
- The implementation MUST allow different instances of dataproviders to co-exist in a Laszlo application.
Rationale
To provide a way for datasets to use data protocols other than HTTP. Enhancements provided by the dataprovider specification will allow clients to set differing data request policies--currently only supported through a server proxy like LPS--like security (e.g., blacklisting and whitelisting of URIs) and transport mechanims (e.g., batching of HTTP requests)
API Spec
DataProvider
A dataprovider must implement this interface:
interface DataProvider {
function doRequest( dataRequest : DataRequest );
}
Callers of the dataprovider invoke doRequest() with a DataRequest object. A datarequest instance implements a DataRequest interface with which a dataprovider can make a data request on behalf of the caller.
DataRequest
The interface for DataRequest looks like:
interface DataRequest {
var requestor : Object;
var src : String;
var timeout : Number;
var status : String;
var onstatus : Event;
var error : String;
var rawdata : String;
}
- Datarequest Properties
- requestor An optional property that's the object using the DataRequest to pass into the dataprovider's doRequest method.
- src A URI, like "http://host.com:80/path?query=value" or "ftp://". A dataprovider may support only a certain set of protocols.
- timeout The length of time that the request should be made before the request should be aborted.
- status A read-only attribute which can be one of "ready" (default), "success", "error", "timeout".
- onstatus The event sent whenever the status changes. The event SHOULD be sent with the DataRequest instance.
- error Error messages from the dataprovider are stored here
- rawdata The raw data received from a server.
Dataset
In addition to the current dataset API:
class Dataset {
var dataprovider : DataProvider;
var multirequest : Boolean;
var datarequest : DataRequest;
var datarequestclass : String;
function doRequest( dataRequest : DataRequest );
function handleResponse( dataRequest: DataRequest );
}
- New dataset properties:
- dataprovider The dataprovider which will handle the dataset's request.
- multirequest True if multiple sequential requests can be made without override previous requests. Default is false for backward compatibility.
- datarequest The current datarequest instance to be used by the dataset to call the dataprovider with. Other methods like setQueryParam() and setSrc() set properties of dataRequest.
- datarequestclass The default datarequest class to be used by the dataset.
- New dataset methods:
- doRequest( DataRequest ) behaves the same way as the previous doRequest except a DataRequest instance may be passed in. If passed in, the dataRequest param is used to call into the dataprovider, otherwise the dataset's dataRequest instance is used.
- handleResponse( DataRequest ) the callback handler for doRequest().
Data Request Sequence
The request life cycle begins with the dataset.doRequest() method. In doRequest(), a DataRequest instance is generated to call into the dataprovider with. Before the dataprovider is invoked, the DataRequest is filled in with enough data for the dataprovider to handle the request. In turn, the dataprovider sets a data callback on the DataRequest instance and then, using request information provided by the DataRequest, makes a server data request. When the server responds, the callback handler of the DataRequest instance is invoked, which then calls the calling dataset's handleResponse method.
Implementation
This section provides a possible implementation, in a mix of LZX and pseudo-code, of the default HTTP DataProvider and HTTP DataRequest. The HTTP DataProvider only accepts HTTP DataRequests. Different DataProvider implementations may accept multiple DataRequests and handle multiple protocols.
Code for the dataset is shown here to demonstrate how it would make a request and handle the data response through the DataProvider API. The dataset here allows method to be declared inline. In this example, the instance of the HTTP DataProvider is assumed to be named LzHttpDataProvider.
<dataset src="http://myhost/mypath?query=value" method="POST">
<attribute name="dataprovider" value="LzHttpDataProvider" when="once"/>
<attribute name="_dataRequest" value="null" />
<attribute name="queryParams" value="new Object()" when="once"/>
<handler name="onconstruct">
this.dataRequest = new HTTPDataRequest();
</handler>
<method name="doRequest" args="dReq">
if ( ! dReq ) dReq = this.dataRequest;
dReq.setAttribute("method", this.method);
dReq.setAttribute("src", this.src);
dReq.setAttribute("queryParams", this.queryParams);
dReq.setAttribute("timeout", this.timeout);
dReq.setAttribute("requestor", this);
/*
Set other attributes here.
*/
this.dataprovider.doRequest( dReq );
this.dataRequest = new dataRequestClass();
return dReq;
</method>
// @param String key: query param key
// @param String|Array value: string for a single valued param, array for a
// multi-value param
<method name="setQueryParam" args="key,val">
this.queryParams[ key ] = val;
</method>
</dataset>
class HttpDataRequest implements DataRequest {
var requestor : Object; // inherited from DataRequest
var src : String; // URI, inherited from DataRequest
var method : String; // GET, POST, PUT, DELETE
var postBody : String;
var proxied : Boolean; // is this request to be proxied?
// String value for single valued param.
// queryParams["aKey"] = "oneValue";
// Array value for a multi-value param
// queryParams["aKey"] = [ "value1", "value2", "value3" ]
var queryParams : Object = {};
// Similar to queryParams but for HTTP headers.
var headers : Object = {};
var timeout : Number; // inherited from DataRequest
var status : String; // response attribute inherited from DataRequest
var rawdata : String; // response attribute inherited from DataRequest
var xmldata : LzDataElement; // XML data associated with rawdata,
// if rawdata is an XML String
var getresponseheaders : Boolean // ask proxy server to forward backend HTTP response headers
var responseheaders : : Hash table // response headers proxied from server
}
class HttpDataProvider implements DataProvider {
function doRequest( dataRequest : HttpDataRequest ) {
var loader = new LzHTTPLoader(...);
loader.dataRequest = dataRequest;
loader.loadSuccess = this.loadSuccess;
loader.loadError = this.loadError;
loader.loadTimeout = this.loadTimeout;
var uri = this.buildURI( dataRequest );
var method = dataRequest.method;
var postBody = dataRequest.postBody;
dataRequest.setAttribute( "status", "awaiting" );
loader.open( method, uri );
loader.send( postBody );
}
function buildURI( dataRequest: DataRequest ) {
var uri = /* create URI using dataRequest */
return uri;
}
function loadSuccess ( loader : LzHTTPLoader, data : String ) {
loader.dataRequest.status = "success";
loadResponse( loader.dataRequest, data );
}
function loadError( loader : LzHTTPLoader, data : String) {
loader.dataRequest.status = "error";
loadResponse( loader.dataRequest, data );
}
function loadTimeout( loader : LzHTTPLoader, data : String ) {
loader.dataRequest.status = "timeout";
loadResponse( loader.dataRequest, data );
}
function loadResponse( dataRequest : DataRequest, data : String ) {
var dReq = loader.dataRequest;
dReq.setAttribute( "rawdata", data );
// assume there's a method that can parse the XML data
dReq.setAttribute( "xmldata", parseXMLData( data ) );
if (dReq.requestor) dReq.requestor.handleResponse( dReq );
}
}
Notes
- [pkang 8/1/07] Maybe better to pass in an optional second "requestor" parameter like DataProvider.doRequest(dataRequest : DataRequest, requestor : Object) instead of passing it in DataRequest.
- [pkang 8/1/07] The handleResponse callback seems unnecessary. The dataset could listen for DataRequest's onstatus event.
- [pkang 8/1/07] LzLoader should send raw text response and let datasets or dataproviders interpret the data. Maybe send the XML (if data is XML) and raw data. Better yet, callbacks return the loader with data and rawdata already set. This way we don't have to call back with the loader and data.


