errest and dojo

Post on 14-May-2015

2.255 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

TRANSCRIPT

ERRest and DojoPascal Robert

• The basics

• Security

• Dojo and ERRest

• Using Dojo's DataGrid and JsonRestStore

• Client-side validation with JSON Schema

• Transactions

• Todo

Overview

The basics

The basics

• Think of REST as direct actions with specific action per HTTP methods (GET, POST, etc.).

• ERRest will create/modify EOs based on data sent by the client, not need to call request().formValueForKey().

• No envelope on the request, if meta-data is needed, it will use the HTTP headers or parameters in the URL.

HTTP methods and actions

HTTP method Action

GET Read (fetch)

POST Create

PUT Update

DELETE Delete

* This slide was taken from Mike's WOWODC 2009 session

One gotcha : you need to use the HTTP adaptor from Wonder or WO 5.4 for DELETE and PUT

support.

Basic architecture of ERRest

• ERRest uses the same architecture as the REST support in Ruby on Rails.

• You build controllers for each entity you want to offer as REST services.

• You create routes that you register in the REST request hander, and the routes are connected to controllers.

• Check Mike's session from WOWODC West 2009 for more details on the architecture.

Routes and actions

HTTP method URL Action

GET /movies fetch all movies

GET /movies/100 fetch movie with id 100

POST /movies create new movie object

PUT /movies/100 update movie id 100

DELETE /movies/100 delete movie id 100

* This slide was taken from Mike's WOWODC 2009 session

ERXDefaultRouteController

• Provides abstract methods for all REST actions.

• One line registration in the REST request handler

ERXRestFormat• Parse the request and write the response in different formats :

• JSON

• JS (same as JSON, but with a different MIME type)

• ASCII plist (iPhone/OS X apps!)

• XML

• SproutCore (JSON with specific properties)

• Rails (XML, but with a different format)

• Some chocolate and nuts framework that we can't talk about

• Default format is XML

Filtering

• You may not want to return all attributes and all relationships attributes in your response.

• Filtering works with ERXKeyFilter.

• You can have an different filter per route.

DEMO

Security

Security

• You don't want anyone to be able to delete any objects...

• The most popular way to do this is with "authkeys".

• Ask users to send the key in the URL...

• ... and use request().stringFormValueForKey to get it

• Use HTTPS when using auth keys!

Security

protected void checkAuthKey() throws SecurityException { String pw = context().request().stringFormValueForKey("pw"); if(!("mySecretKey".equals(pw))) { throw new SecurityException("Invalid authorization key"); } }

public WOActionResults indexAction() throws Throwable { checkAuthKey(); return response(editingContext(), MyEOClass.ENTITY_NAME, movies, showFilter()).generateResponse(); }

Same Origin Policy

• XMLHttpRequest won't load (but will fetch) data if the XMLHttpRequest call is not in the same domain as the REST service, it's the Same Origin Policy.

• 4 options to get over that policy : window.name, HTTP Access Control, proxing or JSONP.

Same Origin Policy

• HTTP Access Protocol sends a OPTIONS HTTP call to the service with specific headers.

• Access-Control-Request-Method

• Access-Control-Request-Headers

• Origin

• REST service must reply to the OPTIONS request with some options

• Supported by Safari 4+, Firefox 3.5+, IE 8 and Chrome 5

Same Origin Policy

To support HTTP Access Control, you have to add a route for the OPTIONS method:restReqHandler.insertRoute(new ERXRoute(Movie.ENTITY_NAME,"/movies", ERXRoute.Method.Options,MoviesController.class, "options"));

Add the optionsAction method in your controller:public WOActionResults optionsAction() throws Throwable { ERXResponse response = new ERXResponse(); response.setHeader("*", "Access-Control-Allow-Origin"); response.setHeaders(this.request().headersForKey("Access-Control-Request-Method"), "Access-Control-Allow-Methods"); response.setHeaders(this.request().headersForKey("Access-Control-Request-Headers"), "Access-Control-Allow-Headers"); response.setHeader("1728000", "Access-Control-Max-Age"); return response;}

Same Origin Policy

You also need to set the headers in the response of your other actions :public WOActionResults indexAction() throws Throwable {... WOResponse response = response(editingContext(), Movie.ENTITY_NAME, movies, showFilter()).generateResponse(); response.setHeader("*", "Access-Control-Allow-Origin"); return response;}

Same Origin Policy• window.name uses a iFrame to communicate with the REST

service, and the XMLHTTPRequest fetch the data locally from the iFrame window.name property.

• Adds "windowname=true" to the query

• Response must be text/html wrapped in script tag

• JSONP is the same concept, but use a <script> tag instead of an iFrame.

• Good alternative for older browsers that don't support HTTP Access Control.

Same Origin Policy

Adding window.name supportpublic WOActionResults indexAction() throws Throwable {... String windowNameArg = this.request().stringFormValueForKey("windowname"); if ((windowNameArg != null) && ("true".equals(windowNameArg))) { String content = response.contentString().replaceAll("\n", ""); response.setContent("<html><script type=\"text/javascript\">window.name='" + content + "';</script></html>"); response.setHeader("text/html", "Content-Type"); } return response;}

Same Origin Policy

• Last option : proxing on the client side, where the client XMLHTTPRequest makes its request to a local service (WO, JSP, PHP, etc.) and that service connects to your service.

DOJO

DOJO

• First release in March 2005

• Managed by the Dojo Foundation, backed by IBM, Sun and others

• Dual licensed : BSD and Academic Free

• Latest release is 1.5, on July 15th 2010

DOJO

• Lots of data widgets (Grid, Tree, etc.) and stores (JSON, Flickr, Picasa, etc.).

• Create a store that use the dojo.data system and it will be available for all data widgets like the DataGrid.

• Switching to a different store might require only one line of change.

JsonRestStore

• Easy to use data store.

dojo.require("dojox.data.JsonRestStore");

var jsonStore = new dojox.data.JsonRestStore({ target: "/ra/movies" });

• store.save() method will contact your REST service to save changes.

• Must change the default format in your REST controller to JSON.

protected ERXRestFormat defaultFormat() { return ERXRestFormat.JSON; }

DataGrid

• DataGrid displays data from a data store in a table.

• Columns are resizable and sortable.

• Paging is supported.

• Rows are editable.

DataGrid paging

• In the data grid properties, add :

rowsPerPage="a int value"

• Dojo will send an extra header when paging is required :

Range: 0-19

• The REST service must reply with :

Content-Range: 0-19/99

Adding new objects

• We can add new objects to the store, they will be created server-side when the save() method is called on the store.

• The new object have to be added to the store with the newItem() method.

JSON Schema

• Define the structure of JSON data.

• List of properties for the JSON object. Type can be string, number, integer, object, array, enum, etc.

• You can reference other schemas, including schemas specified at full URLs.

• Dojo can use it to perform client side validation.

• We can call it WSDL for JSON, with less chat.

JSON Schema example

{ "name":"Movie", "properties":{ "title":{ "maxLength":255, "minLength":1, "type":"string" }, "dateReleased":{ "optional":true, "format":"date-time", "type":"string" }, "category":{ "maxLength":20, "optional":true, "type":"string" } }}

JSON Schema

• Wait... We already have the list of properties in the model...

• ... So we should generate the schema from the controller...

Schema generation

public WOActionResults indexAction() throws Throwable {... String schemaArg = this.request().stringFormValueForKey("schema"); if (schemaArg != null) { return schemaAction(showFilter()); } }

public WOActionResults schemaAction(ERXKeyFilter keyFilter) { HashMap rootObject = new HashMap(); rootObject.put("name", Movie.ENTITY_NAME); HashMap properties = new HashMap(); addSchemaProperties(properties, Movie.ENTITY_NAME, keyFilter); rootObject.put("properties", properties); WOResponse response = new WOResponse(); response.setHeader("application/json", "Content-Type"); response.setContent(JSONSerializer.toJSON(rootObject, ERXJSONRestWriter._config).toString()); return response; }

Schema generation

protected void addSchemaProperties(HashMap properties, String entityName, ERXKeyFilter keyFilter) { EOEntity entity = EOModelGroup.globalModelGroup().entityNamed(entityName); EOClassDescription classDescription = ERXRestClassDescriptionFactory.classDescriptionForEntityName(entityName);

for (String attributeName : (NSArray<String>) classDescription.attributeKeys()) { ERXKey<Object> key = new ERXKey<Object>(attributeName); if (keyFilter.matches(key, ERXKey.Type.Attribute)) { EOAttribute attribute = entity.attributeNamed(key.key()); if (attribute != null) { HashMap property = new HashMap(); if (attribute.allowsNull()) { property.put("optional", true); } if (attribute.className().equals("java.lang.String")) { property.put("type", "string"); if (attribute.width() > 0) { property.put("maxLength",attribute.width()); if ((property.get("optional") == null) || (!(Boolean)property.get("optional"))) { property.put("minLength",1); } } }... properties.put(attribute.name(), property); } } } }

Schema generation

<script type="text/javascript"> var theSchema;

function fetchSchema() { var xhrArgs = { url: "/cgi-bin/WebObjects/Movies.woa/-6100/ra/movies?schema=true", handleAs: "json", sync: true, load: function(data){ theSchema = data; }, error: function(error){ console.log = "An unexpected error occurred: " + error; } } dojo.xhrGet(xhrArgs); } fetchSchema();

var jsonStore = new dojox.data.JsonRestStore({ target: "/cgi-bin/WebObjects/Movies.woa/-6100/ra/movies", schema: theSchema });</script>

JSON Schema

• With JSON schema, when adding new items to the store, validation will be done client-side, no need to contact the server for basic validation.

Transactions

• When store.save() is called, if multiple objects have to be saved, Dojo will add a Transaction, a Seq-Id and a Client-Seq-Id headers.

• The value of the header is either Open or Commit.

• Seq-Id is a integer with the transaction number.

• Client-Seq-Id is a unique id per browser page.

• Only one problem : transactions are not sent in order!

• Let see this in action!

DEMO

Todo

• JSON Referencing

• HTML5 Storage

• Service Mapping Description (SMD)

JSON Referencing• Instead of putting the content of related objects in the root

object, you put references to those objects.

• Dojo will do lazy-loading of the related objects, eg it will fetch them when needed.

• Data will look like this :{ "title": "title", "studio": {"$ref": "/studio/1" }}

• http://www.sitepen.com/blog/2008/11/21/effective-use-of-jsonreststore-referencing-lazy-loading-and-more/

HTML5 Storage

• Dojo 1.5 have support for HTML5 local storage, but I didn't find the documention for it.

• Previous Dojo versions have OfflineRest, but it uses Flash (doh!) as the data storage engine for WebKit browsers, and Google Gears on Firefox.

Service Mapping Description• SMD describes available services, just like WSDL for SOAP.

• JSON Schema is part of SMD.

• Look like this:{ target:"/jsonrpc", transport:"POST", envelope:"JSON-RPC-1.2", SMDVersion:"2.0", services: { add : { // define a service to add two numbers parameters: [ {name:"a",type:"number"}, // define the two parameters {name:"b",type:"number"} ], returns:{"type":"number"} }}

• http://www.sitepen.com/blog/2008/03/19/pluggable-web-services-with-smd/

Q&A

top related