title | image |
---|---|
Pages as webservices |
../images/email.jpg |
Now that our project is nearing release, it's time to go back and fill in some missing pieces. Typically, sites will send a confirmation email after purchase. There are many template engines available, but it would be nice to use Tapestry tml files to create the email, allowing reuse of the UI elements, generating links etc. While it's possible to use Tapestry templates without an http request, that just goes against the grain.
Since the idea is to develop an HTML email, it would be nice just to develop that in a browser using the normal Tapestry tools. Taking a hint from the library test cases -- just create a page and prototype up the email.
public class BookingEmail {
@PageActivationContext
Booking booking;
}
<!DOCTYPE html>
<html lang="en"
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
xmlns:p="tapestry:parameter">
<body>
<h2>Booking Confirmation</h2>
<t:beandisplay object="booking"/>
</body>
</html>
The next step is to render the HTML so it can be sent via email. A great thing about Tapestry IOC is that it's flexible enough to do this. It's possible to create services that look like an HTTP request and plug them in. That's a lot of work and just not the right direction to head. Again taking a hint from testing, the best way to get Tapestry to render a page is going to the URL. This means the email page is now a simple REST service.
The simplest way to do that is creating a service that takes a page, calls it and returns the body. The email page will need activation parameters. Ideally, there would just be some service to inject and call the email method. Interestingly enough, the email page can already do most of that. By adding a sendEmail method, it can do it all. The method takes the parameters required for page activation, plugs them in then calls the sendEmail service with this.
The only issue here is that services use @Inject, and pages need @InjectPage. What's the difference and can it be fixed?
The inject worker uses the InjectProvider service to find the right object and inject it. The InjectPage worker uses ComponentResources to find the page class and inject that. It appears the InjectProvider interface could do that work if a PageInjectionProvider was added to the configuration. To find out, create a PageInjectionProvider and copy/paste the code from the PageInjectionWorker. Injection provider is a chain, so contribute to the chain and return true to indicate the method handled the request. Now @Inject can handle Page injection. The InjectPage annotation is still needed because it can take a page name as an argument.
Unfortunately the internet has its dark side, so some kind of authentication is needed for the email page to ensure it's only called by the application. One way is with the Tapestry @WhitelistAccessOnly annotation. This allows localhost only access to pages and is mostly used for development, but might be OK in this case also. The page could also be protected by Shiro, but the standard form login would not be useful. What's needed instead is authentication via the Authorization header. To make this work, a filter is needed to get the Authorization header to login the user and call the Shiro login process.
To make Shiro work with an Authorization header, a filter is needed. First the header will contain PASSWD user="" password="". If the request contains this header, the filter will use the info to login via Shiro. Since many types of authorization are possible, a chain of command will be used so different ones can be plugged in.
This method works OK as long as the request is https and the user/password is encrypted. For non-ssl use, it's better to use some kind of one-time token. This can be done with a shared secret and something like sha256. The shared secret can just be the hashed password and the header will now be SHA256 user="" hash="". The hash is calculated by using sha256 on the url, secret and the time. This means the hash can only be used with the url over a short period of time and the password is not passed over the wire.
The usual caution here: both authentication methods are for demonstration purposes only. Using them may result in your system being compromised. Always use https when communicating over the internet. If your system is compromised, take immediate action!