Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo

1

MONTREAL 1/3 JULY 2011




ERRest
Pascal Robert
Conatus/MacTI

2

The Menu
•   What's new in ERRest        •   Debugging

•   Security                    •   Caching (Sunday!)

•   Versioning                  •   Optimistic locking (Sunday!)

•   HTML routing                •   Using the correct HTTP verbs
                                    and codes (Sunday!)

3

What's New in ERRest

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

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

Sort ordering on 1:N


•   You can now sort a 1:N relationship

•   Call ERXKeyFilter.setSortOrderings()

7

Sort ordering
ERXKeyFilter filter = ERXKeyFilter.filterWithAttributes();

ERXKeyFilter categoryFilter = ERXKeyFilter.filterWithAttributes();
categoryFilter.setSortOrderings(BlogCategory.SHORT_NAME.ascs());

filter.include(BlogEntry.CATEGORIES, categoryFilter);

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

ERXRouteController.performActi
         onName


 That method have been split in 5 methods to make it easier to
 override on the method.

10

ERXRestContext


•   Hold a userInfo dict + the editing context

•   Can pass a different date format per controller

•   Override createRestContext to do that

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

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

Security

14

What other REST services uses?

•   Twitter and Google: OAuth

•   Amazon S3: signature

•   Campaign Monitor: Basic Authentication

•   MailChimp: API Key

15

Security


•   Basic Authentification

•   Sessions

•   Tokens

16

USE SSL!

17

Basic Auth

18

Basic Auth
•   Pros:

    •   99.9% of HTTP clients can work with it

    •   Easy to implement

•   Cons:

    •   It's just a Base64 representation of your credentials!

    •   No logout option (must close the browser)

    •   No styling of the user/pass box

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

Implementing Basic Auth
    @Override
public WOActionResults performActionNamed(String actionName, boolean throwExceptions)   {
  // This is if you don't 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

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

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

Login with a session
protected void initAuthentication() throws MemberException, NotAuthorizedException {
  if (context().hasSession()) {
    Session session = (Session)context()._session();
    if (session.member() == null) {
      throw new NotAuthorizedException();
    }
  } else {
    throw new NotAuthorizedException();
  }
}

@Override
public 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

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

Login with a token
 curl -X GET http://127.0.0.1/cgi-bin/WebObjects/App.woa/ra/members/login.json?username=auser&password=md5pass

public 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

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();
  }
}

@Override
public 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

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

Handling HTML and routes auth
    @Override
protected 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);
}

@Override
protected 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

Other options

•   OAuth

•   Custom HTTP Authentication scheme

•   Digest Authentification

•   OpenID

•   API Key (similar to token)

30

Versioning

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

Versioning
In 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

Versioning: the gotcha


Watch out for schema changes or other changes that can break
old versions if all versions use the same database schema!

34

HTML routing

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

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

Passing data to the component

Use the ERXRouteParameter annotation to tag methods to
receive data:
@ERXRouteParameter

 public void setMember(Member member) {
   this.member = member;
 }

38

Automatic HTML routing


If the <EntityName><Action>Page component is not found, it will default
back to the controller and try to execute the requested method.

39

HTML routing gotchas


•   When submitting forms, you're back to the stateful request
    handler

•   ERXRouteUrlUtils doesn't create rewritten URLs

40

Manual HTML routing


That's easy: same as a DirectAction:
 public WOActionResults indexAction() throws Throwable {

     return pageWithName(Main.class);
 }

41

100% REST
public 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

HTML routing: demo

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

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

Debugging

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 Apache's error log

47

Debugging Demo

48

MONTREAL 1/3 JULY 2011




Q&A

More Related Content

ERRest

  • 1. MONTREAL 1/3 JULY 2011 ERRest Pascal Robert Conatus/MacTI
  • 2. The Menu • What's new in ERRest • Debugging • Security • Caching (Sunday!) • Versioning • Optimistic locking (Sunday!) • HTML routing • Using the correct HTTP verbs and codes (Sunday!)
  • 3. What's New in ERRest
  • 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. 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. Sort ordering on 1:N • You can now sort a 1:N relationship • Call ERXKeyFilter.setSortOrderings()
  • 7. Sort ordering ERXKeyFilter filter = ERXKeyFilter.filterWithAttributes(); ERXKeyFilter categoryFilter = ERXKeyFilter.filterWithAttributes(); categoryFilter.setSortOrderings(BlogCategory.SHORT_NAME.ascs()); filter.include(BlogEntry.CATEGORIES, categoryFilter);
  • 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. ERXRouteController.performActi onName That method have been split in 5 methods to make it easier to override on the method.
  • 10. ERXRestContext • Hold a userInfo dict + the editing context • Can pass a different date format per controller • Override createRestContext to do that
  • 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. 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
  • 14. What other REST services uses? • Twitter and Google: OAuth • Amazon S3: signature • Campaign Monitor: Basic Authentication • MailChimp: API Key
  • 15. Security • Basic Authentification • Sessions • Tokens
  • 18. Basic Auth • Pros: • 99.9% of HTTP clients can work with it • Easy to implement • Cons: • It's just a Base64 representation of your credentials! • No logout option (must close the browser) • No styling of the user/pass box
  • 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. Implementing Basic Auth @Override public WOActionResults performActionNamed(String actionName, boolean throwExceptions) { // This is if you don't 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. 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. 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. Login with a session protected void initAuthentication() throws MemberException, NotAuthorizedException { if (context().hasSession()) { Session session = (Session)context()._session(); if (session.member() == null) { throw new NotAuthorizedException(); } } else { throw new NotAuthorizedException(); } } @Override public 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. 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. Login with a token curl -X GET http://127.0.0.1/cgi-bin/WebObjects/App.woa/ra/members/login.json?username=auser&password=md5pass public 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. 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(); } } @Override public 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. 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. Handling HTML and routes auth @Override protected 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); } @Override protected 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. Other options • OAuth • Custom HTTP Authentication scheme • Digest Authentification • OpenID • API Key (similar to token)
  • 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. Versioning In 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. Versioning: the gotcha Watch out for schema changes or other changes that can break old versions if all versions use the same database schema!
  • 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. 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. Passing data to the component Use the ERXRouteParameter annotation to tag methods to receive data: @ERXRouteParameter public void setMember(Member member) { this.member = member; }
  • 38. Automatic HTML routing If the <EntityName><Action>Page component is not found, it will default back to the controller and try to execute the requested method.
  • 39. HTML routing gotchas • When submitting forms, you're back to the stateful request handler • ERXRouteUrlUtils doesn't create rewritten URLs
  • 40. Manual HTML routing That's easy: same as a DirectAction: public WOActionResults indexAction() throws Throwable { return pageWithName(Main.class); }
  • 41. 100% REST public 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(); } ...
  • 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. 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); }
  • 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 Apache's error log
  • 48. MONTREAL 1/3 JULY 2011 Q&A