MONTREAL 1/3 JULY 2011ERRestPascal RobertConatus/MacTI
The Menu•   Whats new in ERRest        •   Debugging•   Security                    •   Caching (Sunday!)•   Versioning   ...
Whats New in ERRest
Anymous updates•   No need to send the ids of nested objects anymore•   Call ERXKeyFilter.setAnonymousUpdateEnabled(true)•...
Anonymous update      protected ERXKeyFilter filter() {       ERXKeyFilter filter = ERXKeyFilter.filterWithAttributes();  ...
Sort ordering on 1:N•   You can now sort a 1:N relationship•   Call ERXKeyFilter.setSortOrderings()
Sort orderingERXKeyFilter filter = ERXKeyFilter.filterWithAttributes();ERXKeyFilter categoryFilter = ERXKeyFilter.filterWi...
Ignoring unknow keys•   By default, returns status 500 if unknow attribute is found in    request•   To ignore those error...
ERXRouteController.performActi         onName That method have been split in 5 methods to make it easier to override on th...
ERXRestContext•   Hold a userInfo dict + the editing context•   Can pass a different date format per controller•   Overrid...
ERXRestContext   public class BlogEntryController extends BaseRestController {...  @Override  protected ERXRestContext cre...
Other new stuff•   More strict HTTP status code in responses•   Support for @QueryParam, @CookieParam and    @HeaderParam ...
Security
What other REST services uses?•   Twitter and Google: OAuth•   Amazon S3: signature•   Campaign Monitor: Basic Authenticat...
Security•   Basic Authentification•   Sessions•   Tokens
USE SSL!
Basic Auth
Basic Auth•   Pros:    •   99.9% of HTTP clients can work with it    •   Easy to implement•   Cons:    •   Its just a Base...
Implementing Basic Auth    protected void initAuthentication() throws MemberException, NotAuthorizedException {    String ...
Implementing Basic Auth    @Overridepublic WOActionResults performActionNamed(String actionName, boolean throwExceptions) ...
Sessions•   Pros:    •   Can store other data on the server-side (but REST is suppose to be        stateless)    •   Easy ...
Login with a session     curl -X GET http://127.0.0.1/cgi-bin/WebObjects/App.woa/ra/users/login.json?username=auser&passwo...
Login with a sessionprotected void initAuthentication() throws MemberException, NotAuthorizedException {  if (context().ha...
Tokens•   Pros:    •   No timeout based on inactivity (unless you want to)•   Cons:    •   More work involved    •   Clien...
Login with a token curl -X GET http://127.0.0.1/cgi-bin/WebObjects/App.woa/ra/members/login.json?username=auser&password=m...
Login with a token   public static final ERXBlowfishCrypter crypter = new ERXBlowfishCrypter();protected void initTokenAut...
Browser vs System-to-System  It near impossible to have a REST backend with security that  works well with both browsers-b...
Handling HTML and routes auth    @Overrideprotected WOActionResults performHtmlActionNamed(String actionName) throws Excep...
Other options•   OAuth•   Custom HTTP Authentication scheme•   Digest Authentification•   OpenID•   API Key (similar to tok...
Versioning
Versioning•   Try hard to not having to version your REST services...•   ... but life is never as planified•   Use mod_rewr...
VersioningIn Apache config:  RewriteEngine On  RewriteRule ^/your-service/v1/(.*)$ /cgi-bin/WebObjects/YourApp-v1.woa/ra$1...
Versioning: the gotchaWatch out for schema changes or other changes that can breakold versions if all versions use the sam...
HTML routing
HTML routing?•   Power of ERRest + WO/EOF + clean URLs!•   Like DirectActions, but with a lot of work done for you•   Usef...
Automatic routing: damn easy•   Create a REST controller for your entity and set    isAutomaticHtmlRoutingEnabled() to tru...
Passing data to the componentUse the ERXRouteParameter annotation to tag methods toreceive data:@ERXRouteParameter public ...
Automatic HTML routingIf the <EntityName><Action>Page component is not found, it will defaultback to the controller and tr...
HTML routing gotchas•   When submitting forms, youre back to the stateful request    handler•   ERXRouteUrlUtils doesnt cr...
Manual HTML routingThats easy: same as a DirectAction: public WOActionResults indexAction() throws Throwable {     return ...
100% RESTpublic Application() {    ERXRouteRequestHandler restRequestHandler = new ERXRouteRequestHandler();    requestHan...
HTML routing: demo
Cool trick: Application Cache              Manifest•   Let you specify that some URLs of your app can be available    offli...
Cache Manifest   In your DirectAction class:  public WOActionResults manifestAction() {    EOEditingContext ec = ERXEC.new...
Debugging
Debugging REST problems•   curl -v : will display all headers and content, for both request and    response•   Firebug and...
Debugging Demo
MONTREAL 1/3 JULY 2011Q&A
Upcoming SlideShare
Loading in …5
×

ERRest

5,620 views

Published on

Learn what's new in Project Wonder's ERRest framework. Also, see some tips about security and versioning for your REST services, and learn how you can use HTML routing to build Web apps with ERRest.

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
5,620
On SlideShare
0
From Embeds
0
Number of Embeds
3
Actions
Shares
0
Downloads
27
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

ERRest

  1. 1. MONTREAL 1/3 JULY 2011ERRestPascal RobertConatus/MacTI
  2. 2. The Menu• Whats new in ERRest • Debugging• Security • Caching (Sunday!)• Versioning • Optimistic locking (Sunday!)• HTML routing • Using the correct HTTP verbs and codes (Sunday!)
  3. 3. Whats New in ERRest
  4. 4. Anymous updates• No need to send the ids of nested objects anymore• Call ERXKeyFilter.setAnonymousUpdateEnabled(true)• If 1:N relationship, will replace existing values for all nested objects
  5. 5. Anonymous update protected ERXKeyFilter filter() { ERXKeyFilter filter = ERXKeyFilter.filterWithAttributes(); ERXKeyFilter personFilter = ERXKeyFilter.filterWithAttributes(); personFilter.include(Person.FIRST_NAME); personFilter.setAnonymousUpdateEnabled(true); filter.include(BlogEntry.PERSON, personFilter); return filter; }curl -X PUT -d "{ title: New Post, person: {firstName: Test} }" http://127.0.0.1/cgi-bin/WebObjects/SimpleBlog.woa/ra/posts/23.json
  6. 6. Sort ordering on 1:N• You can now sort a 1:N relationship• Call ERXKeyFilter.setSortOrderings()
  7. 7. Sort orderingERXKeyFilter filter = ERXKeyFilter.filterWithAttributes();ERXKeyFilter categoryFilter = ERXKeyFilter.filterWithAttributes();categoryFilter.setSortOrderings(BlogCategory.SHORT_NAME.ascs());filter.include(BlogEntry.CATEGORIES, categoryFilter);
  8. 8. Ignoring unknow keys• By default, returns status 500 if unknow attribute is found in request• To ignore those errors, call: yourErxKeyFilter.setUnknownKeyIgnored(true)
  9. 9. ERXRouteController.performActi onName That method have been split in 5 methods to make it easier to override on the method.
  10. 10. ERXRestContext• Hold a userInfo dict + the editing context• Can pass a different date format per controller• Override createRestContext to do that
  11. 11. ERXRestContext public class BlogEntryController extends BaseRestController {... @Override protected ERXRestContext createRestContext() { ERXRestContext restContext = new ERXRestContext(editingContext()); restContext.setUserInfoForKey("yyyy-MM-dd", "er.rest.dateFormat"); restContext.setUserInfoForKey("yyyy-MM-dd", "er.rest.timestampFormat"); return restContext; }}
  12. 12. Other new stuff• More strict HTTP status code in responses• Support for @QueryParam, @CookieParam and @HeaderParam for JSR-311 annotations• Indexed bean properties are supported in bean class descriptions• updateObjectWithFilter will update subobjects
  13. 13. Security
  14. 14. What other REST services uses?• Twitter and Google: OAuth• Amazon S3: signature• Campaign Monitor: Basic Authentication• MailChimp: API Key
  15. 15. Security• Basic Authentification• Sessions• Tokens
  16. 16. USE SSL!
  17. 17. Basic Auth
  18. 18. Basic Auth• Pros: • 99.9% of HTTP clients can work with it • Easy to implement• Cons: • Its just a Base64 representation of your credentials! • No logout option (must close the browser) • No styling of the user/pass box
  19. 19. Implementing Basic Auth protected void initAuthentication() throws MemberException, NotAuthorizedException { String authValue = request().headerForKey( "authorization" ); if( authValue != null ) { try { byte[] authBytes = new BASE64Decoder().decodeBuffer( authValue.replace( "Basic ", "" ) ); String[] parts = new String( authBytes ).split( ":", 2 ); String username = parts[0]; String password = parts[1]; setAuthenticatedUser(Member.validateLogin(editingContext(), username, password)); } catch ( IOException e ) { log.error( "Could not decode basic auth data: " + e.getMessage() ); e.printStackTrace(); } } else { throw new NotAuthorizedException(); }}public class NotAuthorizedException extends Exception { public NotAuthorizedException() { super(); }}
  20. 20. Implementing Basic Auth @Overridepublic WOActionResults performActionNamed(String actionName, boolean throwExceptions) { // This is if you dont want to use Basic Auth for HTML apps if (!(ERXRestFormat.html().name().equals(this.format().name()))) { try { initAuthentication(); } catch (UserLoginException ex) { WOResponse response = (WOResponse)errorResponse(401); response.setHeader("Basic realm="ERBlog"", "WWW-Authenticate"); return response; } catch (NotAuthorizedException ex) { WOResponse response = (WOResponse)errorResponse(401); response.setHeader("Basic realm="ERBlog"", "WWW-Authenticate"); return response; } } return super.performActionNamed(actionName, throwExceptions);}
  21. 21. Sessions• Pros: • Can store other data on the server-side (but REST is suppose to be stateless) • Easy to implement• Cons: • Timeouts... • Sessions are bind to a specific instance of the app • State on the server • Non-browser clients have to store the session ID
  22. 22. Login with a session curl -X GET http://127.0.0.1/cgi-bin/WebObjects/App.woa/ra/users/login.json?username=auser&password=md5pass public Session() { setStoresIDsInCookies(true); } public WOActionResults loginAction() throws Throwable { try { String username = request().stringFormValueForKey("username"); String password = request().stringFormValueForKey("password"); Member member = Member.validateLogin(session().defaultEditingContext(), username, password); return response(member, ERXKeyFilter.filterWithNone()); } catch (MemberException ex) { return errorResponse(401); } }(This only works on a version of ERRest after June 9 2011)
  23. 23. Login with a sessionprotected void initAuthentication() throws MemberException, NotAuthorizedException { if (context().hasSession()) { Session session = (Session)context()._session(); if (session.member() == null) { throw new NotAuthorizedException(); } } else { throw new NotAuthorizedException(); }}@Overridepublic WOActionResults performActionNamed(String actionName, boolean throwExceptions) { try { initAuthentication(); } catch (MemberException ex) { return pageWithName(Login.class); } catch (NotAuthorizedException ex) { return pageWithName(Login.class); } return super.performActionNamed(actionName, throwExceptions);}
  24. 24. Tokens• Pros: • No timeout based on inactivity (unless you want to)• Cons: • More work involved • Client must request a token• Can store the token in a cookie, Authorization header or as a query argument
  25. 25. Login with a token curl -X GET http://127.0.0.1/cgi-bin/WebObjects/App.woa/ra/members/login.json?username=auser&password=md5passpublic static final ERXBlowfishCrypter crypter = new ERXBlowfishCrypter();public WOActionResults loginAction() throws Throwable { try { String username = request().stringFormValueForKey("username"); String password = request().stringFormValueForKey("password"); Member member = Member.validateLogin(editingContext(), username, password); String hash = crypter.encrypt(member.username()); if (hash != null) { return response(hash, ERXKeyFilter.filterWithAll()); } } catch (MemberException ex) { return errorResponse(401); }}
  26. 26. Login with a token public static final ERXBlowfishCrypter crypter = new ERXBlowfishCrypter();protected void initTokenAuthentication() throws MemberException, NotAuthorizedException { String tokenValue = this.request().cookieValueForKey("someCookieKeyForToken"); if (tokenValue != null) { String username = crypter.decrypt(tokenValue); Member member = Member.fetchMember(editingContext(), Member.USERNAME.eq(username)); if (member == null) { throw new NotAuthorizedException(); } } else { throw new NotAuthorizedException(); }}@Overridepublic WOActionResults performActionNamed(String actionName, boolean throwExceptions) { try { initTokenAuthentication(); } catch (MemberException ex) { return pageWithName(Login.class); } catch (NotAuthorizedException ex) { return pageWithName(Login.class); } return super.performActionNamed(actionName, throwExceptions);}
  27. 27. Browser vs System-to-System It near impossible to have a REST backend with security that works well with both browsers-based and "system-to-system" applications.• For browser apps: use cookies• For system-to-system: use the Authorization header
  28. 28. Handling HTML and routes auth @Overrideprotected WOActionResults performHtmlActionNamed(String actionName) throws Exception { try { initCookieAuthentication(); } catch (MemberException ex) { return pageWithName(LoginPage.class); } catch (NotAuthorizedException ex) { return pageWithName(LoginPage.class); } return super.performHtmlActionNamed(actionName);}@Overrideprotected WOActionResults performRouteActionNamed(String actionName) throws Exception { try { initTokenAuthentication(); } catch (MemberException ex) { return errorResponse(401); } catch (NotAuthorizedException ex) { return errorResponse(401); } return super.performRouteActionNamed(actionName);}
  29. 29. Other options• OAuth• Custom HTTP Authentication scheme• Digest Authentification• OpenID• API Key (similar to token)
  30. 30. Versioning
  31. 31. Versioning• Try hard to not having to version your REST services...• ... but life is never as planified• Use mod_rewrite and ERXApplication._rewriteURL to make it easier • Use mod_rewrite even if you are not versionning! It makes shorter and nicer URLs
  32. 32. VersioningIn Apache config: RewriteEngine On RewriteRule ^/your-service/v1/(.*)$ /cgi-bin/WebObjects/YourApp-v1.woa/ra$1 [PT,L] RewriteRule ^/your-service/v2/(.*)$ /cgi-bin/WebObjects/YourApp-v2.woa/ra$1 [PT,L]In Application.java: public String _rewriteURL(String url) { String processedURL = url; if (url != null && _replaceApplicationPathPattern != null && _replaceApplicationPathReplace != null) { processedURL = processedURL.replaceFirst(_replaceApplicationPathPattern, _replaceApplicationPathReplace); } return processedURL; }In the Properties of YourApp-v1.woa: er.extensions.ERXApplication.replaceApplicationPath.pattern=/cgi-bin/WebObjects/YourApp-v1.woa/ra er.extensions.ERXApplication.replaceApplicationPath.replace=/your-service/v1/In the Properties of YourApp-v2.woa: er.extensions.ERXApplication.replaceApplicationPath.pattern=/cgi-bin/WebObjects/YourApp-v2.woa/ra er.extensions.ERXApplication.replaceApplicationPath.replace=/your-service/v2/
  33. 33. Versioning: the gotchaWatch out for schema changes or other changes that can breakold versions if all versions use the same database schema!
  34. 34. HTML routing
  35. 35. HTML routing?• Power of ERRest + WO/EOF + clean URLs!• Like DirectActions, but with a lot of work done for you• Useful for small public apps that can be cacheable (or accessible offline)
  36. 36. Automatic routing: damn easy• Create a REST controller for your entity and set isAutomaticHtmlRoutingEnabled() to true• Create a <EntityName><Action>Page (eg, MemberIndexPage.wo) component• Register your controller• Your component must implements IERXRouteComponent• Run your app• Profits!
  37. 37. Passing data to the componentUse the ERXRouteParameter annotation to tag methods toreceive data:@ERXRouteParameter public void setMember(Member member) { this.member = member; }
  38. 38. Automatic HTML routingIf the <EntityName><Action>Page component is not found, it will defaultback to the controller and try to execute the requested method.
  39. 39. HTML routing gotchas• When submitting forms, youre back to the stateful request handler• ERXRouteUrlUtils doesnt create rewritten URLs
  40. 40. Manual HTML routingThats easy: same as a DirectAction: public WOActionResults indexAction() throws Throwable { return pageWithName(Main.class); }
  41. 41. 100% RESTpublic Application() { ERXRouteRequestHandler restRequestHandler = new ERXRouteRequestHandler(); requestHandler.insertRoute(new ERXRoute("Main","", MainController.class, "index"));... setDefaultRequestHandler(requestHandler);}public class MainController extends BaseController { public MainController(WORequest request) { super(request); } @Override protected boolean isAutomaticHtmlRoutingEnabled() { return true; } @Override public WOActionResults indexAction() throws Throwable { return pageWithName(Main.class); } @Override protected ERXRestFormat defaultFormat() { return ERXRestFormat.html(); }...
  42. 42. HTML routing: demo
  43. 43. Cool trick: Application Cache Manifest• Let you specify that some URLs of your app can be available offline• URLs in the CACHE section will be available offline until you change the manifest and remove the URLs from the CACHE section• Use a DirectAction or a static file to create the manifest• One cool reason to use the HTML routing stuff
  44. 44. Cache Manifest In your DirectAction class: public WOActionResults manifestAction() { EOEditingContext ec = ERXEC.newEditingContext(); WOResponse response = new WOResponse(); response.appendContentString("CACHE MANIFESTn"); response.appendContentString("CACHE:n"); response.appendContentString(ERXRouteUrlUtils.actionUrlForEntityType(this.context(), Entity.ENTITY_NAME, "index",ERXRestFormat.HTML_KEY, null, false, false) + "n"); response.appendContentString("NETWORK:n"); response.setHeader("text/cache-manifest", "Content-Type"); return response; }In your component: <wo:WOGenericContainer elementName="html" manifest=$urlToManifest" lang="en" xmlns="http://www.w3.org/1999/xhtml"> public String urlForManifest() { return this.context().directActionURLForActionNamed("manifest", null); }
  45. 45. Debugging
  46. 46. Debugging REST problems• curl -v : will display all headers and content, for both request and response• Firebug and WebKit Inspector : useful to see the XMLHttpRequest calls• tcpflow : see all trafic on a network interface, can do filters• Apache DUMPIO (mod_dumpio) : dump ALL requests and responses data to Apaches error log
  47. 47. Debugging Demo
  48. 48. MONTREAL 1/3 JULY 2011Q&A

×