SlideShare a Scribd company logo
1 of 40
Download to read offline
MONTREAL 1/3 JULY 2011




Designing a good REST service
Pascal Robert
Conatus/MacTI
Using the correct HTTP verbs
          and codes
REST and HTTP


•   The principle of REST is to use all of the HTTP spec (verbs,
    HTTP codes, ...)

•   SOAP principle is to use HTTP only for transport
Good vs bad URLs

•   Bad URLs:

    •   POST http://api.twitter.com/version/statuses/update.format

    •   POST http://dev.twitter.com/doc/post/statuses/destroy/:id

•   Good URLs

    •   POST http://api.twitter.com/version/statuses.format

    •   DELETE http://dev.twitter.com/doc/post/statuses/:id
Use the correct HTTP codes
•   DON'T use codes in 2xx and 3xx for errors! Use the codes in
    the 4xx and 5xx range.

•   Error codes in the 4xx covers most of the error types, no need
    to create new codes.

•   The 5xx status code range is for server-side errors

•   Using the correct codes = automatic handling by browsers and
    libs!
400 (Bad Request)

•   If a required attribute is not part of a request's body content,
    return a 400 (Bad Request) HTTP code instead of 500

•   Since June 15, ERRest do it for you!

    •   Use ERXRest.strictMode=false if you want to use the old
        behavior (500)
201 Created

•   201 (Created) is the code to use when new resources are
    created.

•   Should have a Location header with a link to the new resource in
    the response.

    •   Why? Client can get a updated representation right away
201 Created
   public WOActionResults createAction() throws Throwable {
    ERXKeyFilter filter = ERXKeyFilter.filterWithAttributesAndToOneRelationships();
    filter.include(SomeEntity.DESTINATION_ENTITIES.dot(DestinationEntity.SOME_ATTRIBUTE));
    SomeEntity newEntity = create(filter);
    newEntity.editingContext().saveChanges();
    ERXRouteResults response = (ERXRouteResults)response(newEntity, filter);

    String host = this.request()._serverName();
    if (this.request().isSecure()) {
      host = "https://" + host;
    } else {
      host = "http://" + host;
    }
    if (this.request()._serverPort() != null) {
      host = host + ":" + this.request()._serverPort();
    }

    NSDictionary queryDict = new NSDictionary();
    response.setHeaderForKey(host + ERXRouteUrlUtils.actionUrlForRecord(_context, newEntity, "index", this.format
().name(), queryDict, this.request().isSecure(), this.request().isSessionIDInRequest()), "Location");
    return response;
  }
405 (Not Allowed)


Use this HTTP code when someone try to execute a method
(GET/POST/PUT/DELETE) that is not supported.
405 (Not Allowed)


If you are using ERXDefaultRouteController...

Instead of doing this:

  public WOActionResults destroyAction() throws Throwable {
    return null;
  }

You should do this:

  public WOActionResults destroyAction() throws Throwable {
    return errorResponse(ERXHttpStatusCodes.METHOD_NOT_ALLOWED);
  }
405 (Not Allowed)

If you are using ERXRouteController, it's done automatically!

... unless your ERRest framework is older than June 14
... or that ERXRest.isStrictMode = false
... so do that if that's the case:

    MyBaseController extends ERXRouteController

    protected WOActionResults performUnknownAction(String actionName) throws Exception {
      throw new ERXNotAllowedException(actionName);
    }

    MyBaseMissingController extends ERXMissingRouteController

    @Override
	   public WOActionResults missingAction() {
	      boolean isStrictMode = ERXProperties.booleanForKeyWithDefault("ERXRest.strictMode", true);
	       if (isStrictMode) {
	         return errorResponse(ERXHttpStatusCodes.METHOD_NOT_ALLOWED);
	       }
        super.missingAction();
	       }
	   }
Resources that went or moved
            away
•   If a resource was deleted or moved away, handle it with:

    •   It's gone for good and you have a trace it's gone? Use HTTP
        code 410 (Gone).

    •   It's gone but can't find out if it was there? Use HTTP code 404
        (ERRest do if for you).

    •   Resource was moved to a different URL? Use HTTP code 301
        and the Location header.
Long running jobs
•   Problem: client send a request, but the job on the server-side will
    take a long time to do it. You can't really use a
    WOLongResponsePage in those cases...

•   Solution: Use HTTP codes 200, 202 and 303 (See More).

    •   Client submit the job, answer with a 202 code.

    •   Client ask for status, return 200 while it's still running.

    •   Return a 303 code when it's completed.
Long running jobs
    --> POST /some/long/runningJob HTTP/1.1
--> ...Some data...
<-- HTTP/1.1 202 Accepted
<-- Content-Location: http://www.example.org/some/long/runningJob/<idOfJob>

--> GET /some/long/runningJob/<idOfJob> HTTP/1.1
<-- HTTP/1.1 200 Ok
<-- <status>Still running, sorry about that</status>

--> GET /some/long/runningJob/<idOfJob> HTTP/1.1
<-- HTTP/1.1 303 See Other
<-- Location: http://www.example.org/results/of/long/runningJob

--> GET http://www.example.org/results/of/long/runningJob HTTP/1.1
Query arguments

•   Use query arguments (?attr1=value&attr2=value) for non-
    resources data, or to filter results

•   Examples:

    •   GET /ra/users/login?username=user&password=btggdfgdf

    •   GET /ra/products/index?batchSize=10&sort=name

•   Use request().formValueForKey() to get the arguments
Batching/sorting
•   If you use ERXFetchSpecification...

    •   ?batchSize=25&batch=1

    •   ?sort=someAttr|asc,anotherAttr|desc

    •   ?qualifier=someAttr%3D'SomeValue'

•   Example: /cgi-bin/WebObjects/App.woa/ra/stuff.json?
    batchSize=5&sort=endDate|desc&qualifier=name%3D'General Labs'

•   If not using ERXFetchSpecification, use request().formValueForKey to get
    those parameters

•   Can also use the Range/Content-Range header for batching, but that's a
    violation of the HTTP spec
Caching
Why caching?


•   To reduce bandwith and processing time

•   Access to data when disconnected from the Net

•   More performance when people are behind proxies
When caching can't be done


•   POST/PUT/DELETE requests are not cached, only GET and
    HEAD can be cached

•   SSL responses are not cached in proxies
Caching options


•   Last-Modified/If-Modified-Since

•   Etag/If-None-Match

•   Cache-control/Expires
Last-Modified/If-Modified-Since
•   If-Modified-Since: Client check if the requested resource was
    updated after that value

•   Last-Modified: Server return the date of when the resource was
    last updated

•   HTTP code 304 (Not Modified): return this if the document was
    not updated after the value of If-Modified-Since

•   Will reduce bandwith and processing usage

•   ...You do need something on your EO that can generate a "last
    updated" timestamp to set Last-Modified
Last-Modified/If-Modified-Since
First request:

-->   GET /some/attribute.json HTTP/1.1
<--   HTTP/1.1 200 Ok
<--   Last-Modified: 25 May 2011 13:02:30 GMT
<--   { someData }

Second request:

--> GET /some/attribute.json HTTP/1.1
--> If-Modified-Since: 25 May 2011 13:02:30 GMT
 if last modified for representation == 25 May 2011 13:02:30 GMT
<-- HTTP/1.1 304 Not Modified
  else
<-- HTTP/1.1 200 OK
<-- { someData }
Last-Modified/If-Modified-Since
    public WOActionResults showAction() throws Throwable {
    SomeEntity someEntity = routeObjectForKey("someEntity");

    String ifModifiedSinceHeader = request().headerForKey("If-Modified-Since");
    String strLastModifiedDate = formatter.format(someEntity.lastModified());
    java.util.Calendar later = GregorianCalendar.getInstance();
    later.add(Calendar.HOUR, 1);

    if (ifModifiedSinceHeader != null) {
      NSTimestamp ifModifiedSince = new NSTimestamp(formatter.parse(ifModifiedSinceHeader));
      if ((ifModifiedSince.after(someEntity.lastModified())) || (ifModifiedSince.equals(someEntity.lastModified()))) {
        WOResponse response = new WOResponse();
        response.setStatus(304);
        response.appendHeader(strLastModifiedDate, "Last-Modified");
        response.appendHeader(formatter.format(later), "Expires");
        return response;
      }
    }

    ERXRouteResults results = (ERXRouteResults)response(someEntity, ERXKeyFilter.filterWithAttributesAndToOneRelationships());
    results.setHeaderForKey(strLastModifiedDate, "Last-Modified");
    results.setHeaderForKey(strLastModifiedDate, "Expires");
    results.setHeaderForKey("max-age=600,public", "Cache-Control");
    return results;
}
ETag/If-None-Match
•   ETag is like a hash code of the data representation

•   Better than Last-Modified, no need for a stored timestamp

    •   ... but you do need to generate a hash code that will stay the
        same if the EO didn't change

    •   ... and that's not easy to do with EOs

    •   Best option is to create a MD5 digest with some values
        coming from attributes
ETag/If-None-Match
First request:

-->   GET /some/attribute.json HTTP/1.1
<--   HTTP/1.1 200 Ok
<--   ETag: a135790
<--   { someData }

Second request:

--> GET /some/attribute.json HTTP/1.1
--> If-None-Match: a135790
 if generated ETag for representation == a135790
<-- HTTP/1.1 304 Not Modified
  else
<-- HTTP/1.1 200 OK
<-- { someData }
ETag/If-None-Match
      public WOActionResults showAction() throws Throwable {
    SomeEntity someEntity = routeObjectForKey("someEntity");

    String ifNoneMatchHeader = request().headerForKey("If-None-Match");

    ERXRouteResults results = (ERXRouteResults)response(someEntity, ERXKeyFilter.filterWithAttributesAndToOneRelationships());
    String etagHash = ERXCrypto.sha256Encode(results.responseNode().toString(results.format(), this.restContext()));

    if (ifNoneMatchHeader != null) {
      if (ifNoneMatchHeader.equals(etagHash)) {
        WOResponse response = new WOResponse();
        response.setStatus(ERXHttpStatusCodes.NOT_MODIFIED);
        response.appendHeader(etagHash, "ETag");
        return response;
      }
    }

    results.setHeaderForKey(etagHash, "ETag");
    return results;
}
Cache-control/Expires

•   Both let you add a "age" on the request

•   Cache-control is for HTTP 1.1 clients

•   Expires is for HTTP 1.0 clients

    •   It's just a date value
Cache-control options
•   Visibility:

    •   public: default, both private and shared (eg, proxies) will cache it

    •   private: client will cache it, but shared caches (eg, proxies) will not cache it

    •   no-cache and no-store: don't store a cache

•   Age:

    •   max-age: freshness lifetime in seconds

    •   s-maxage: same as max-age, but for shared caches

•   Revalidation:

    •   must-revalidate: cache(s) have to check the origin server before serving a cached
        presentation

    •   proxy-revalidate: same as must-revalidate, but for shared caches only
Using Cache-control
First request:

-->   GET /some/attribute.json HTTP/1.1
<--   Date: 25 May 2011 14:00:00 GMT
<--   HTTP/1.1 200 Ok
<--   Last-Modified: 25 May 2011 13:02:30 GMT
<--   Cache-Control: max-age=3600,must-revalidate
<--   Expires: 25 May 2011 15:00:00 GMT
<--   { someData }

Second request made 10 minutes later, response will come from the cache:

-->   GET /some/attribute.json HTTP/1.1
<--   HTTP/1.1 200 OK
<--   Cache-Control: max-age=3600,must-revalidate
<--   Expires: 25 May 2011 15:00:00 GMT
<--   Age: 600
Our friend : optimistic locking
Optimistic locking


•   If you have two REST clients doing a PUT on the same resource,
    client B will override the changes made by client A... No
    optimistic locking for you!

•   ... Unless you use the ETag or Last-Modified headers
Optimistic concurrency control


•   Last-Modified/If-Unmodified-Since

•   ETag/If-Match

•   HTTP code 412 (Precondition failed)
Optimistic concurrency control (Last-Modified method)
Both Client A and Client B ask for the same data:

-->   GET /some/data.json HTTP/1.1
<--   Date: 25 May 2011 14:00:00 GMT
<--   HTTP/1.1 200 Ok
<--   Last-Modified: 25 May 2011 13:02:30 GMT
<--   Expires: 25 May 2011 15:00:00 GMT
<--   { someData }

Client A send an update:

--> PUT /some/data.json HTTP/1.1
--> If-Unmodified-Since: 25 May 2011 13:02:30 GMT
{ someAttribute: 'blabla from client A', id: 1, type: 'SomeEntity' }
-----> Last-Modified on the representation before the update is 25 May 2011 13:02:30 GMT, so we are good to go <-----
<-- HTTP/1.1 200 OK
<-- Last-Modified: 25 May 2011 14:05:30 GMT
<-- { someData }

Client B send an update:

--> PUT /some/data.json HTTP/1.1
--> If-Unmodified-Since: 25 May 2011 13:02:30 GMT
{ someAttribute: 'blabla from client B', id: 1, type: 'SomeEntity' }
-----> BUSTED! Last-Modified on the representation is now 25 May 2011 14:05:30 because of Client A update! <-----
<-- HTTP/1.1 412 Precondition Failed
<-- { error: 'Ya man, someone before you updated the same object' }
How-to with Last-Modified
      public WOActionResults updateAction() throws Throwable {
       SomeEntity someEntity = routeObjectForKey("someEntity");

      String ifUnmodifiedSinceHeader = request().headerForKey("If-Unmodified-Since");

    if (ifUnmodifiedSinceHeader != null) {
      NSTimestamp ifUnmodifiedSince = new NSTimestamp(formatter.parse(ifUnmodifiedSinceHeader));
      if (!(ifUnmodifiedSince.equals(someEntity.lastModified()))) {
        return errorResponse("Ya man, someone before you updated the same object",
ERXHttpStatusCodes.PRECONDITION_FAILED);
      }
    }

      update(someEntity, ERXKeyFilter.filterWithAll());
      someEntity.setLastModified(new NSTimestamp());
      someEntity.editingContext().saveChanges();

      String strLastModifiedDate = formatter.format(new java.util.Date(someEntity.lastModified().getTime()));
      ERXRouteResults response = (ERXRouteResults)response(someEntity, ERXKeyFilter.filterWithAll());
      response.setHeaderForKey(strLastModifiedDate, "Last-Modified");
      response.setHeaderForKey(strLastModifiedDate, "Expires");
      return response;
  }
Optimistic concurrency control


The "last modified" date should be a value that clients can't update
Optimistic concurrency control (ETag method)
Both Client A and Client B ask for the same data:

-->   GET /some/data.json HTTP/1.1
<--   Date: 25 May 2011 14:00:00 GMT
<--   HTTP/1.1 200 Ok
<--   ETag: a2468013579
<--   Expires: 25 May 2011 15:00:00 GMT
<--   { someData }

Client A send an update:

--> PUT /some/data.json HTTP/1.1
--> If-Match: a2468013579
{ someAttribute: 'blabla from client A', id: 1, type: 'SomeEntity' }
-----> ETag on the representation before the update was a2468013579, so we are good to go <-----
<-- HTTP/1.1 200 OK
<-- ETag: b3579024680
<-- { someData }

Client B send an update:

--> PUT /some/data.json HTTP/1.1
--> If-Match: a2468013579
{ someAttribute: 'blabla from client B', id: 1, type: 'SomeEntity' }
-----> BUSTED! ETag on the representation is now b3579024680 because of Client A update! <-----
<-- HTTP/1.1 412 Precondition Failed
<-- { error: 'Ya man, someone before you updated the same object!' }
How-to with ETag
    public WOActionResults updateAction() throws Throwable {
    SomeEntity someEntity = routeObjectForKey("someEntity");

    ERXRouteResults results = (ERXRouteResults)response(someEntity, ERXKeyFilter.filterWithAttributesAndToOneRelationships());
    String etagHash = ERXCrypto.sha256Encode(results.responseNode().toString(results.format(), this.restContext()));

    String ifMatchHeader = request().headerForKey("If-Match");

    if (ifMatchHeader != null) {
      if (!(ifMatchHeader.equals(etagHash))) {
        return errorResponse("Ya man, someone before you updated the same object", ERXHttpStatusCode.PRECONDITION_FAILED);
      }
    }

    update(someEntity, ERXKeyFilter.filterWithAll());
    someEntity.editingContext().saveChanges();

    results.setHeaderForKey(etagHash, "ETag");

    return results;
}
Future topics (WebEx)
•   Localization

•   ERRest and Dojo

•   ERRest and SproutCore

•   HTML5 Storage

•   File uploads/downloads

•   Consuming REST services

•   ERRest and Titanium

•   HATEOAS/Atom
Resources


•   RESTful Web Services Cookbook (O'Reilly)

•   REST in Practice (O'Reilly)
MONTREAL 1/3 JULY 2011




Q&A
Pascal Robert

More Related Content

What's hot

Introduction to Solr
Introduction to SolrIntroduction to Solr
Introduction to SolrJayesh Bhoyar
 
Class-based views with Django
Class-based views with DjangoClass-based views with Django
Class-based views with DjangoSimon Willison
 
How to write easy-to-test JavaScript
How to write easy-to-test JavaScriptHow to write easy-to-test JavaScript
How to write easy-to-test JavaScriptYnon Perek
 
Mastering solr
Mastering solrMastering solr
Mastering solrjurcello
 
Http4s, Doobie and Circe: The Functional Web Stack
Http4s, Doobie and Circe: The Functional Web StackHttp4s, Doobie and Circe: The Functional Web Stack
Http4s, Doobie and Circe: The Functional Web StackGaryCoady
 
Object Oriented Exploitation: New techniques in Windows mitigation bypass
Object Oriented Exploitation: New techniques in Windows mitigation bypassObject Oriented Exploitation: New techniques in Windows mitigation bypass
Object Oriented Exploitation: New techniques in Windows mitigation bypassSam Thomas
 
JSP Standart Tag Lİbrary - JSTL
JSP Standart Tag Lİbrary - JSTLJSP Standart Tag Lİbrary - JSTL
JSP Standart Tag Lİbrary - JSTLseleciii44
 
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...GeeksLab Odessa
 
Web весна 2013 лекция 6
Web весна 2013 лекция 6Web весна 2013 лекция 6
Web весна 2013 лекция 6Technopark
 
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"GeeksLab Odessa
 
Web осень 2012 лекция 6
Web осень 2012 лекция 6Web осень 2012 лекция 6
Web осень 2012 лекция 6Technopark
 
Codegeneration With Xtend
Codegeneration With XtendCodegeneration With Xtend
Codegeneration With XtendSven Efftinge
 
Building High Performance and Reliable Windows Phone 8 Apps
Building High Performance and Reliable Windows Phone 8 AppsBuilding High Performance and Reliable Windows Phone 8 Apps
Building High Performance and Reliable Windows Phone 8 AppsMichele Capra
 
Developing Useful APIs
Developing Useful APIsDeveloping Useful APIs
Developing Useful APIsDmitry Buzdin
 
Rebuilding Solr 6 examples - layer by layer (LuceneSolrRevolution 2016)
Rebuilding Solr 6 examples - layer by layer (LuceneSolrRevolution 2016)Rebuilding Solr 6 examples - layer by layer (LuceneSolrRevolution 2016)
Rebuilding Solr 6 examples - layer by layer (LuceneSolrRevolution 2016)Alexandre Rafalovitch
 

What's hot (20)

Introduction to Solr
Introduction to SolrIntroduction to Solr
Introduction to Solr
 
Class-based views with Django
Class-based views with DjangoClass-based views with Django
Class-based views with Django
 
Solr workshop
Solr workshopSolr workshop
Solr workshop
 
Jstl Guide
Jstl GuideJstl Guide
Jstl Guide
 
How to write easy-to-test JavaScript
How to write easy-to-test JavaScriptHow to write easy-to-test JavaScript
How to write easy-to-test JavaScript
 
Mastering solr
Mastering solrMastering solr
Mastering solr
 
Http4s, Doobie and Circe: The Functional Web Stack
Http4s, Doobie and Circe: The Functional Web StackHttp4s, Doobie and Circe: The Functional Web Stack
Http4s, Doobie and Circe: The Functional Web Stack
 
Object Oriented Exploitation: New techniques in Windows mitigation bypass
Object Oriented Exploitation: New techniques in Windows mitigation bypassObject Oriented Exploitation: New techniques in Windows mitigation bypass
Object Oriented Exploitation: New techniques in Windows mitigation bypass
 
55 New Features in Java 7
55 New Features in Java 755 New Features in Java 7
55 New Features in Java 7
 
JSP Standart Tag Lİbrary - JSTL
JSP Standart Tag Lİbrary - JSTLJSP Standart Tag Lİbrary - JSTL
JSP Standart Tag Lİbrary - JSTL
 
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
 
Web весна 2013 лекция 6
Web весна 2013 лекция 6Web весна 2013 лекция 6
Web весна 2013 лекция 6
 
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
 
Web осень 2012 лекция 6
Web осень 2012 лекция 6Web осень 2012 лекция 6
Web осень 2012 лекция 6
 
Codegeneration With Xtend
Codegeneration With XtendCodegeneration With Xtend
Codegeneration With Xtend
 
Building High Performance and Reliable Windows Phone 8 Apps
Building High Performance and Reliable Windows Phone 8 AppsBuilding High Performance and Reliable Windows Phone 8 Apps
Building High Performance and Reliable Windows Phone 8 Apps
 
Ajax
AjaxAjax
Ajax
 
Developing Useful APIs
Developing Useful APIsDeveloping Useful APIs
Developing Useful APIs
 
Django Pro ORM
Django Pro ORMDjango Pro ORM
Django Pro ORM
 
Rebuilding Solr 6 examples - layer by layer (LuceneSolrRevolution 2016)
Rebuilding Solr 6 examples - layer by layer (LuceneSolrRevolution 2016)Rebuilding Solr 6 examples - layer by layer (LuceneSolrRevolution 2016)
Rebuilding Solr 6 examples - layer by layer (LuceneSolrRevolution 2016)
 

Similar to ERRest - Designing a good REST service

Data Access Options in SharePoint 2010
Data Access Options in SharePoint 2010Data Access Options in SharePoint 2010
Data Access Options in SharePoint 2010Rob Windsor
 
05 status-codes
05 status-codes05 status-codes
05 status-codessnopteck
 
Karate for Complex Web-Service API Testing by Peter Thomas
Karate for Complex Web-Service API Testing by Peter ThomasKarate for Complex Web-Service API Testing by Peter Thomas
Karate for Complex Web-Service API Testing by Peter Thomasintuit_india
 
jQuery : Talk to server with Ajax
jQuery : Talk to server with AjaxjQuery : Talk to server with Ajax
jQuery : Talk to server with AjaxWildan Maulana
 
Tutorial, Part 3: SharePoint 101: Jump-Starting the Developer by Rob Windsor ...
Tutorial, Part 3: SharePoint 101: Jump-Starting the Developer by Rob Windsor ...Tutorial, Part 3: SharePoint 101: Jump-Starting the Developer by Rob Windsor ...
Tutorial, Part 3: SharePoint 101: Jump-Starting the Developer by Rob Windsor ...SPTechCon
 
Building High Performance Web Applications and Sites
Building High Performance Web Applications and SitesBuilding High Performance Web Applications and Sites
Building High Performance Web Applications and Sitesgoodfriday
 
IT talk SPb "Full text search for lazy guys"
IT talk SPb "Full text search for lazy guys" IT talk SPb "Full text search for lazy guys"
IT talk SPb "Full text search for lazy guys" DataArt
 
More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)
More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)
More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)Jen Wong
 
Lucene for Solr Developers
Lucene for Solr DevelopersLucene for Solr Developers
Lucene for Solr DevelopersErik Hatcher
 
What's new in jQuery 1.5
What's new in jQuery 1.5What's new in jQuery 1.5
What's new in jQuery 1.5Martin Kleppe
 
03 form-data
03 form-data03 form-data
03 form-datasnopteck
 
Implementing Ajax In ColdFusion 7
Implementing Ajax In ColdFusion 7Implementing Ajax In ColdFusion 7
Implementing Ajax In ColdFusion 7Pranav Prakash
 
Velocity EU 2014 — Offline-first web apps
Velocity EU 2014 — Offline-first web appsVelocity EU 2014 — Offline-first web apps
Velocity EU 2014 — Offline-first web appsandrewsmatt
 
Rethinking Syncing at AltConf 2019
Rethinking Syncing at AltConf 2019Rethinking Syncing at AltConf 2019
Rethinking Syncing at AltConf 2019Joe Keeley
 
Ajax tutorial by bally chohan
Ajax tutorial by bally chohanAjax tutorial by bally chohan
Ajax tutorial by bally chohanWebVineet
 
Introducing Struts 2
Introducing Struts 2Introducing Struts 2
Introducing Struts 2wiradikusuma
 

Similar to ERRest - Designing a good REST service (20)

Data Access Options in SharePoint 2010
Data Access Options in SharePoint 2010Data Access Options in SharePoint 2010
Data Access Options in SharePoint 2010
 
05 status-codes
05 status-codes05 status-codes
05 status-codes
 
RIA and Ajax
RIA and AjaxRIA and Ajax
RIA and Ajax
 
AJAX.ppt
AJAX.pptAJAX.ppt
AJAX.ppt
 
AJAX Transport Layer
AJAX Transport LayerAJAX Transport Layer
AJAX Transport Layer
 
Karate for Complex Web-Service API Testing by Peter Thomas
Karate for Complex Web-Service API Testing by Peter ThomasKarate for Complex Web-Service API Testing by Peter Thomas
Karate for Complex Web-Service API Testing by Peter Thomas
 
jQuery : Talk to server with Ajax
jQuery : Talk to server with AjaxjQuery : Talk to server with Ajax
jQuery : Talk to server with Ajax
 
Tutorial, Part 3: SharePoint 101: Jump-Starting the Developer by Rob Windsor ...
Tutorial, Part 3: SharePoint 101: Jump-Starting the Developer by Rob Windsor ...Tutorial, Part 3: SharePoint 101: Jump-Starting the Developer by Rob Windsor ...
Tutorial, Part 3: SharePoint 101: Jump-Starting the Developer by Rob Windsor ...
 
Building High Performance Web Applications and Sites
Building High Performance Web Applications and SitesBuilding High Performance Web Applications and Sites
Building High Performance Web Applications and Sites
 
IT talk SPb "Full text search for lazy guys"
IT talk SPb "Full text search for lazy guys" IT talk SPb "Full text search for lazy guys"
IT talk SPb "Full text search for lazy guys"
 
More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)
More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)
More on Fitnesse and Continuous Integration (Silicon Valley code camp 2012)
 
Lucene for Solr Developers
Lucene for Solr DevelopersLucene for Solr Developers
Lucene for Solr Developers
 
What's new in jQuery 1.5
What's new in jQuery 1.5What's new in jQuery 1.5
What's new in jQuery 1.5
 
Intro to Sail.js
Intro to Sail.jsIntro to Sail.js
Intro to Sail.js
 
03 form-data
03 form-data03 form-data
03 form-data
 
Implementing Ajax In ColdFusion 7
Implementing Ajax In ColdFusion 7Implementing Ajax In ColdFusion 7
Implementing Ajax In ColdFusion 7
 
Velocity EU 2014 — Offline-first web apps
Velocity EU 2014 — Offline-first web appsVelocity EU 2014 — Offline-first web apps
Velocity EU 2014 — Offline-first web apps
 
Rethinking Syncing at AltConf 2019
Rethinking Syncing at AltConf 2019Rethinking Syncing at AltConf 2019
Rethinking Syncing at AltConf 2019
 
Ajax tutorial by bally chohan
Ajax tutorial by bally chohanAjax tutorial by bally chohan
Ajax tutorial by bally chohan
 
Introducing Struts 2
Introducing Struts 2Introducing Struts 2
Introducing Struts 2
 

More from WO Community

In memory OLAP engine
In memory OLAP engineIn memory OLAP engine
In memory OLAP engineWO Community
 
Using Nagios to monitor your WO systems
Using Nagios to monitor your WO systemsUsing Nagios to monitor your WO systems
Using Nagios to monitor your WO systemsWO Community
 
Build and deployment
Build and deploymentBuild and deployment
Build and deploymentWO Community
 
Reenabling SOAP using ERJaxWS
Reenabling SOAP using ERJaxWSReenabling SOAP using ERJaxWS
Reenabling SOAP using ERJaxWSWO Community
 
Chaining the Beast - Testing Wonder Applications in the Real World
Chaining the Beast - Testing Wonder Applications in the Real WorldChaining the Beast - Testing Wonder Applications in the Real World
Chaining the Beast - Testing Wonder Applications in the Real WorldWO Community
 
D2W Stateful Controllers
D2W Stateful ControllersD2W Stateful Controllers
D2W Stateful ControllersWO Community
 
Deploying WO on Windows
Deploying WO on WindowsDeploying WO on Windows
Deploying WO on WindowsWO Community
 
Unit Testing with WOUnit
Unit Testing with WOUnitUnit Testing with WOUnit
Unit Testing with WOUnitWO Community
 
Apache Cayenne for WO Devs
Apache Cayenne for WO DevsApache Cayenne for WO Devs
Apache Cayenne for WO DevsWO Community
 
Advanced Apache Cayenne
Advanced Apache CayenneAdvanced Apache Cayenne
Advanced Apache CayenneWO Community
 
Migrating existing Projects to Wonder
Migrating existing Projects to WonderMigrating existing Projects to Wonder
Migrating existing Projects to WonderWO Community
 
iOS for ERREST - alternative version
iOS for ERREST - alternative versioniOS for ERREST - alternative version
iOS for ERREST - alternative versionWO Community
 
"Framework Principal" pattern
"Framework Principal" pattern"Framework Principal" pattern
"Framework Principal" patternWO Community
 
Localizing your apps for multibyte languages
Localizing your apps for multibyte languagesLocalizing your apps for multibyte languages
Localizing your apps for multibyte languagesWO Community
 

More from WO Community (20)

In memory OLAP engine
In memory OLAP engineIn memory OLAP engine
In memory OLAP engine
 
Using Nagios to monitor your WO systems
Using Nagios to monitor your WO systemsUsing Nagios to monitor your WO systems
Using Nagios to monitor your WO systems
 
Build and deployment
Build and deploymentBuild and deployment
Build and deployment
 
High availability
High availabilityHigh availability
High availability
 
Reenabling SOAP using ERJaxWS
Reenabling SOAP using ERJaxWSReenabling SOAP using ERJaxWS
Reenabling SOAP using ERJaxWS
 
Chaining the Beast - Testing Wonder Applications in the Real World
Chaining the Beast - Testing Wonder Applications in the Real WorldChaining the Beast - Testing Wonder Applications in the Real World
Chaining the Beast - Testing Wonder Applications in the Real World
 
D2W Stateful Controllers
D2W Stateful ControllersD2W Stateful Controllers
D2W Stateful Controllers
 
Deploying WO on Windows
Deploying WO on WindowsDeploying WO on Windows
Deploying WO on Windows
 
Unit Testing with WOUnit
Unit Testing with WOUnitUnit Testing with WOUnit
Unit Testing with WOUnit
 
Life outside WO
Life outside WOLife outside WO
Life outside WO
 
Apache Cayenne for WO Devs
Apache Cayenne for WO DevsApache Cayenne for WO Devs
Apache Cayenne for WO Devs
 
Advanced Apache Cayenne
Advanced Apache CayenneAdvanced Apache Cayenne
Advanced Apache Cayenne
 
Migrating existing Projects to Wonder
Migrating existing Projects to WonderMigrating existing Projects to Wonder
Migrating existing Projects to Wonder
 
iOS for ERREST - alternative version
iOS for ERREST - alternative versioniOS for ERREST - alternative version
iOS for ERREST - alternative version
 
iOS for ERREST
iOS for ERRESTiOS for ERREST
iOS for ERREST
 
"Framework Principal" pattern
"Framework Principal" pattern"Framework Principal" pattern
"Framework Principal" pattern
 
WOver
WOverWOver
WOver
 
Localizing your apps for multibyte languages
Localizing your apps for multibyte languagesLocalizing your apps for multibyte languages
Localizing your apps for multibyte languages
 
WOdka
WOdkaWOdka
WOdka
 
ERGroupware
ERGroupwareERGroupware
ERGroupware
 

Recently uploaded

Unlocking the Potential of the Cloud for IBM Power Systems
Unlocking the Potential of the Cloud for IBM Power SystemsUnlocking the Potential of the Cloud for IBM Power Systems
Unlocking the Potential of the Cloud for IBM Power SystemsPrecisely
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationRidwan Fadjar
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupFlorian Wilhelm
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...shyamraj55
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Allon Mureinik
 
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr LapshynFwdays
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitecturePixlogix Infotech
 
APIForce Zurich 5 April Automation LPDG
APIForce Zurich 5 April  Automation LPDGAPIForce Zurich 5 April  Automation LPDG
APIForce Zurich 5 April Automation LPDGMarianaLemus7
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationSlibray Presentation
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreternaman860154
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphNeo4j
 
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Alan Dix
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024Scott Keck-Warren
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Scott Keck-Warren
 
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptxMaking_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptxnull - The Open Security Community
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersThousandEyes
 
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | DelhiFULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhisoniya singh
 

Recently uploaded (20)

Unlocking the Potential of the Cloud for IBM Power Systems
Unlocking the Potential of the Cloud for IBM Power SystemsUnlocking the Potential of the Cloud for IBM Power Systems
Unlocking the Potential of the Cloud for IBM Power Systems
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 Presentation
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project Setup
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
 
Vulnerability_Management_GRC_by Sohang Sengupta.pptx
Vulnerability_Management_GRC_by Sohang Sengupta.pptxVulnerability_Management_GRC_by Sohang Sengupta.pptx
Vulnerability_Management_GRC_by Sohang Sengupta.pptx
 
Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)Injustice - Developers Among Us (SciFiDevCon 2024)
Injustice - Developers Among Us (SciFiDevCon 2024)
 
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
"Federated learning: out of reach no matter how close",Oleksandr Lapshyn
 
Understanding the Laravel MVC Architecture
Understanding the Laravel MVC ArchitectureUnderstanding the Laravel MVC Architecture
Understanding the Laravel MVC Architecture
 
APIForce Zurich 5 April Automation LPDG
APIForce Zurich 5 April  Automation LPDGAPIForce Zurich 5 April  Automation LPDG
APIForce Zurich 5 April Automation LPDG
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck Presentation
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
 
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...Swan(sea) Song – personal research during my six years at Swansea ... and bey...
Swan(sea) Song – personal research during my six years at Swansea ... and bey...
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024
 
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptxMaking_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
 
DMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special EditionDMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special Edition
 
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | DelhiFULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
 

ERRest - Designing a good REST service

  • 1. MONTREAL 1/3 JULY 2011 Designing a good REST service Pascal Robert Conatus/MacTI
  • 2. Using the correct HTTP verbs and codes
  • 3. REST and HTTP • The principle of REST is to use all of the HTTP spec (verbs, HTTP codes, ...) • SOAP principle is to use HTTP only for transport
  • 4. Good vs bad URLs • Bad URLs: • POST http://api.twitter.com/version/statuses/update.format • POST http://dev.twitter.com/doc/post/statuses/destroy/:id • Good URLs • POST http://api.twitter.com/version/statuses.format • DELETE http://dev.twitter.com/doc/post/statuses/:id
  • 5. Use the correct HTTP codes • DON'T use codes in 2xx and 3xx for errors! Use the codes in the 4xx and 5xx range. • Error codes in the 4xx covers most of the error types, no need to create new codes. • The 5xx status code range is for server-side errors • Using the correct codes = automatic handling by browsers and libs!
  • 6. 400 (Bad Request) • If a required attribute is not part of a request's body content, return a 400 (Bad Request) HTTP code instead of 500 • Since June 15, ERRest do it for you! • Use ERXRest.strictMode=false if you want to use the old behavior (500)
  • 7. 201 Created • 201 (Created) is the code to use when new resources are created. • Should have a Location header with a link to the new resource in the response. • Why? Client can get a updated representation right away
  • 8. 201 Created public WOActionResults createAction() throws Throwable { ERXKeyFilter filter = ERXKeyFilter.filterWithAttributesAndToOneRelationships(); filter.include(SomeEntity.DESTINATION_ENTITIES.dot(DestinationEntity.SOME_ATTRIBUTE)); SomeEntity newEntity = create(filter); newEntity.editingContext().saveChanges(); ERXRouteResults response = (ERXRouteResults)response(newEntity, filter); String host = this.request()._serverName(); if (this.request().isSecure()) { host = "https://" + host; } else { host = "http://" + host; } if (this.request()._serverPort() != null) { host = host + ":" + this.request()._serverPort(); } NSDictionary queryDict = new NSDictionary(); response.setHeaderForKey(host + ERXRouteUrlUtils.actionUrlForRecord(_context, newEntity, "index", this.format ().name(), queryDict, this.request().isSecure(), this.request().isSessionIDInRequest()), "Location"); return response; }
  • 9. 405 (Not Allowed) Use this HTTP code when someone try to execute a method (GET/POST/PUT/DELETE) that is not supported.
  • 10. 405 (Not Allowed) If you are using ERXDefaultRouteController... Instead of doing this: public WOActionResults destroyAction() throws Throwable { return null; } You should do this: public WOActionResults destroyAction() throws Throwable { return errorResponse(ERXHttpStatusCodes.METHOD_NOT_ALLOWED); }
  • 11. 405 (Not Allowed) If you are using ERXRouteController, it's done automatically! ... unless your ERRest framework is older than June 14 ... or that ERXRest.isStrictMode = false ... so do that if that's the case: MyBaseController extends ERXRouteController protected WOActionResults performUnknownAction(String actionName) throws Exception { throw new ERXNotAllowedException(actionName); } MyBaseMissingController extends ERXMissingRouteController @Override public WOActionResults missingAction() { boolean isStrictMode = ERXProperties.booleanForKeyWithDefault("ERXRest.strictMode", true); if (isStrictMode) { return errorResponse(ERXHttpStatusCodes.METHOD_NOT_ALLOWED); } super.missingAction(); } }
  • 12. Resources that went or moved away • If a resource was deleted or moved away, handle it with: • It's gone for good and you have a trace it's gone? Use HTTP code 410 (Gone). • It's gone but can't find out if it was there? Use HTTP code 404 (ERRest do if for you). • Resource was moved to a different URL? Use HTTP code 301 and the Location header.
  • 13. Long running jobs • Problem: client send a request, but the job on the server-side will take a long time to do it. You can't really use a WOLongResponsePage in those cases... • Solution: Use HTTP codes 200, 202 and 303 (See More). • Client submit the job, answer with a 202 code. • Client ask for status, return 200 while it's still running. • Return a 303 code when it's completed.
  • 14. Long running jobs --> POST /some/long/runningJob HTTP/1.1 --> ...Some data... <-- HTTP/1.1 202 Accepted <-- Content-Location: http://www.example.org/some/long/runningJob/<idOfJob> --> GET /some/long/runningJob/<idOfJob> HTTP/1.1 <-- HTTP/1.1 200 Ok <-- <status>Still running, sorry about that</status> --> GET /some/long/runningJob/<idOfJob> HTTP/1.1 <-- HTTP/1.1 303 See Other <-- Location: http://www.example.org/results/of/long/runningJob --> GET http://www.example.org/results/of/long/runningJob HTTP/1.1
  • 15. Query arguments • Use query arguments (?attr1=value&attr2=value) for non- resources data, or to filter results • Examples: • GET /ra/users/login?username=user&password=btggdfgdf • GET /ra/products/index?batchSize=10&sort=name • Use request().formValueForKey() to get the arguments
  • 16. Batching/sorting • If you use ERXFetchSpecification... • ?batchSize=25&batch=1 • ?sort=someAttr|asc,anotherAttr|desc • ?qualifier=someAttr%3D'SomeValue' • Example: /cgi-bin/WebObjects/App.woa/ra/stuff.json? batchSize=5&sort=endDate|desc&qualifier=name%3D'General Labs' • If not using ERXFetchSpecification, use request().formValueForKey to get those parameters • Can also use the Range/Content-Range header for batching, but that's a violation of the HTTP spec
  • 18. Why caching? • To reduce bandwith and processing time • Access to data when disconnected from the Net • More performance when people are behind proxies
  • 19. When caching can't be done • POST/PUT/DELETE requests are not cached, only GET and HEAD can be cached • SSL responses are not cached in proxies
  • 20. Caching options • Last-Modified/If-Modified-Since • Etag/If-None-Match • Cache-control/Expires
  • 21. Last-Modified/If-Modified-Since • If-Modified-Since: Client check if the requested resource was updated after that value • Last-Modified: Server return the date of when the resource was last updated • HTTP code 304 (Not Modified): return this if the document was not updated after the value of If-Modified-Since • Will reduce bandwith and processing usage • ...You do need something on your EO that can generate a "last updated" timestamp to set Last-Modified
  • 22. Last-Modified/If-Modified-Since First request: --> GET /some/attribute.json HTTP/1.1 <-- HTTP/1.1 200 Ok <-- Last-Modified: 25 May 2011 13:02:30 GMT <-- { someData } Second request: --> GET /some/attribute.json HTTP/1.1 --> If-Modified-Since: 25 May 2011 13:02:30 GMT if last modified for representation == 25 May 2011 13:02:30 GMT <-- HTTP/1.1 304 Not Modified else <-- HTTP/1.1 200 OK <-- { someData }
  • 23. Last-Modified/If-Modified-Since public WOActionResults showAction() throws Throwable { SomeEntity someEntity = routeObjectForKey("someEntity"); String ifModifiedSinceHeader = request().headerForKey("If-Modified-Since"); String strLastModifiedDate = formatter.format(someEntity.lastModified()); java.util.Calendar later = GregorianCalendar.getInstance(); later.add(Calendar.HOUR, 1); if (ifModifiedSinceHeader != null) { NSTimestamp ifModifiedSince = new NSTimestamp(formatter.parse(ifModifiedSinceHeader)); if ((ifModifiedSince.after(someEntity.lastModified())) || (ifModifiedSince.equals(someEntity.lastModified()))) { WOResponse response = new WOResponse(); response.setStatus(304); response.appendHeader(strLastModifiedDate, "Last-Modified"); response.appendHeader(formatter.format(later), "Expires"); return response; } } ERXRouteResults results = (ERXRouteResults)response(someEntity, ERXKeyFilter.filterWithAttributesAndToOneRelationships()); results.setHeaderForKey(strLastModifiedDate, "Last-Modified"); results.setHeaderForKey(strLastModifiedDate, "Expires"); results.setHeaderForKey("max-age=600,public", "Cache-Control"); return results; }
  • 24. ETag/If-None-Match • ETag is like a hash code of the data representation • Better than Last-Modified, no need for a stored timestamp • ... but you do need to generate a hash code that will stay the same if the EO didn't change • ... and that's not easy to do with EOs • Best option is to create a MD5 digest with some values coming from attributes
  • 25. ETag/If-None-Match First request: --> GET /some/attribute.json HTTP/1.1 <-- HTTP/1.1 200 Ok <-- ETag: a135790 <-- { someData } Second request: --> GET /some/attribute.json HTTP/1.1 --> If-None-Match: a135790 if generated ETag for representation == a135790 <-- HTTP/1.1 304 Not Modified else <-- HTTP/1.1 200 OK <-- { someData }
  • 26. ETag/If-None-Match public WOActionResults showAction() throws Throwable { SomeEntity someEntity = routeObjectForKey("someEntity"); String ifNoneMatchHeader = request().headerForKey("If-None-Match"); ERXRouteResults results = (ERXRouteResults)response(someEntity, ERXKeyFilter.filterWithAttributesAndToOneRelationships()); String etagHash = ERXCrypto.sha256Encode(results.responseNode().toString(results.format(), this.restContext())); if (ifNoneMatchHeader != null) { if (ifNoneMatchHeader.equals(etagHash)) { WOResponse response = new WOResponse(); response.setStatus(ERXHttpStatusCodes.NOT_MODIFIED); response.appendHeader(etagHash, "ETag"); return response; } } results.setHeaderForKey(etagHash, "ETag"); return results; }
  • 27. Cache-control/Expires • Both let you add a "age" on the request • Cache-control is for HTTP 1.1 clients • Expires is for HTTP 1.0 clients • It's just a date value
  • 28. Cache-control options • Visibility: • public: default, both private and shared (eg, proxies) will cache it • private: client will cache it, but shared caches (eg, proxies) will not cache it • no-cache and no-store: don't store a cache • Age: • max-age: freshness lifetime in seconds • s-maxage: same as max-age, but for shared caches • Revalidation: • must-revalidate: cache(s) have to check the origin server before serving a cached presentation • proxy-revalidate: same as must-revalidate, but for shared caches only
  • 29. Using Cache-control First request: --> GET /some/attribute.json HTTP/1.1 <-- Date: 25 May 2011 14:00:00 GMT <-- HTTP/1.1 200 Ok <-- Last-Modified: 25 May 2011 13:02:30 GMT <-- Cache-Control: max-age=3600,must-revalidate <-- Expires: 25 May 2011 15:00:00 GMT <-- { someData } Second request made 10 minutes later, response will come from the cache: --> GET /some/attribute.json HTTP/1.1 <-- HTTP/1.1 200 OK <-- Cache-Control: max-age=3600,must-revalidate <-- Expires: 25 May 2011 15:00:00 GMT <-- Age: 600
  • 30. Our friend : optimistic locking
  • 31. Optimistic locking • If you have two REST clients doing a PUT on the same resource, client B will override the changes made by client A... No optimistic locking for you! • ... Unless you use the ETag or Last-Modified headers
  • 32. Optimistic concurrency control • Last-Modified/If-Unmodified-Since • ETag/If-Match • HTTP code 412 (Precondition failed)
  • 33. Optimistic concurrency control (Last-Modified method) Both Client A and Client B ask for the same data: --> GET /some/data.json HTTP/1.1 <-- Date: 25 May 2011 14:00:00 GMT <-- HTTP/1.1 200 Ok <-- Last-Modified: 25 May 2011 13:02:30 GMT <-- Expires: 25 May 2011 15:00:00 GMT <-- { someData } Client A send an update: --> PUT /some/data.json HTTP/1.1 --> If-Unmodified-Since: 25 May 2011 13:02:30 GMT { someAttribute: 'blabla from client A', id: 1, type: 'SomeEntity' } -----> Last-Modified on the representation before the update is 25 May 2011 13:02:30 GMT, so we are good to go <----- <-- HTTP/1.1 200 OK <-- Last-Modified: 25 May 2011 14:05:30 GMT <-- { someData } Client B send an update: --> PUT /some/data.json HTTP/1.1 --> If-Unmodified-Since: 25 May 2011 13:02:30 GMT { someAttribute: 'blabla from client B', id: 1, type: 'SomeEntity' } -----> BUSTED! Last-Modified on the representation is now 25 May 2011 14:05:30 because of Client A update! <----- <-- HTTP/1.1 412 Precondition Failed <-- { error: 'Ya man, someone before you updated the same object' }
  • 34. How-to with Last-Modified public WOActionResults updateAction() throws Throwable { SomeEntity someEntity = routeObjectForKey("someEntity"); String ifUnmodifiedSinceHeader = request().headerForKey("If-Unmodified-Since"); if (ifUnmodifiedSinceHeader != null) { NSTimestamp ifUnmodifiedSince = new NSTimestamp(formatter.parse(ifUnmodifiedSinceHeader)); if (!(ifUnmodifiedSince.equals(someEntity.lastModified()))) { return errorResponse("Ya man, someone before you updated the same object", ERXHttpStatusCodes.PRECONDITION_FAILED); } } update(someEntity, ERXKeyFilter.filterWithAll()); someEntity.setLastModified(new NSTimestamp()); someEntity.editingContext().saveChanges(); String strLastModifiedDate = formatter.format(new java.util.Date(someEntity.lastModified().getTime())); ERXRouteResults response = (ERXRouteResults)response(someEntity, ERXKeyFilter.filterWithAll()); response.setHeaderForKey(strLastModifiedDate, "Last-Modified"); response.setHeaderForKey(strLastModifiedDate, "Expires"); return response; }
  • 35. Optimistic concurrency control The "last modified" date should be a value that clients can't update
  • 36. Optimistic concurrency control (ETag method) Both Client A and Client B ask for the same data: --> GET /some/data.json HTTP/1.1 <-- Date: 25 May 2011 14:00:00 GMT <-- HTTP/1.1 200 Ok <-- ETag: a2468013579 <-- Expires: 25 May 2011 15:00:00 GMT <-- { someData } Client A send an update: --> PUT /some/data.json HTTP/1.1 --> If-Match: a2468013579 { someAttribute: 'blabla from client A', id: 1, type: 'SomeEntity' } -----> ETag on the representation before the update was a2468013579, so we are good to go <----- <-- HTTP/1.1 200 OK <-- ETag: b3579024680 <-- { someData } Client B send an update: --> PUT /some/data.json HTTP/1.1 --> If-Match: a2468013579 { someAttribute: 'blabla from client B', id: 1, type: 'SomeEntity' } -----> BUSTED! ETag on the representation is now b3579024680 because of Client A update! <----- <-- HTTP/1.1 412 Precondition Failed <-- { error: 'Ya man, someone before you updated the same object!' }
  • 37. How-to with ETag public WOActionResults updateAction() throws Throwable { SomeEntity someEntity = routeObjectForKey("someEntity"); ERXRouteResults results = (ERXRouteResults)response(someEntity, ERXKeyFilter.filterWithAttributesAndToOneRelationships()); String etagHash = ERXCrypto.sha256Encode(results.responseNode().toString(results.format(), this.restContext())); String ifMatchHeader = request().headerForKey("If-Match"); if (ifMatchHeader != null) { if (!(ifMatchHeader.equals(etagHash))) { return errorResponse("Ya man, someone before you updated the same object", ERXHttpStatusCode.PRECONDITION_FAILED); } } update(someEntity, ERXKeyFilter.filterWithAll()); someEntity.editingContext().saveChanges(); results.setHeaderForKey(etagHash, "ETag"); return results; }
  • 38. Future topics (WebEx) • Localization • ERRest and Dojo • ERRest and SproutCore • HTML5 Storage • File uploads/downloads • Consuming REST services • ERRest and Titanium • HATEOAS/Atom
  • 39. Resources • RESTful Web Services Cookbook (O'Reilly) • REST in Practice (O'Reilly)
  • 40. MONTREAL 1/3 JULY 2011 Q&A Pascal Robert