[Authored by Ian]
I’ve been doing work on a couple projects integrating Rails with FileMaker Pro (FMP) over HTTP. There are a few different FMP features and plugins the FMP developers use to connect to Rails, none of which I truly grok. Notwithstanding my lack of FMP knowledge, I wanted to write-up what I expect from FMP as an HTTP client, and what I think would be neat to see.
A Good Foundation
When I’m integrating with another system over HTTP, I usually provide usage examples for my Rails-based service using the lingua franca of *nix system tools like Bash, Curl, WGet, LWP, and Telnet. I expect an integrating client like FMP to be able to perform the same basic functions those commandline tools perform.
For example, if I’ve written a RESTful Rails service that allows secure management of widgets, I might send the FMP client developer an example like this (using LWP):
# a list of widgets you have access to GET -H "Accept: application/xml" https://user:email@example.com/widgets # create a new widget cat widget.xml | POST -H "Accept: application/xml" -c "application/xml" https://user:firstname.lastname@example.org/widgets
While the client developer may not understand every bit of the example, I think it’s still relatively self-explanatory and has the added bonus of being a reference implementation.
My example, and the Rails convention, requires the client to have basic HTTP functionality like
- performing the four CRUD HTTP methods: POST, GET, PUT, and DELETE
- specifying arbitrary HTTP headers, especially Accept and Content-type
- furnishing HTTP basic authn credentials
- talking HTTPS
FMP, however, doesn’t include a HTTP client that fulfills these requirements.
On the other hand, FMP does include an embeddable web browser. It’s a nice way to slap an already-made web interface into FMP. My biggest gripe about this feature is that its user-agent string is not a reliable identifier; making it impossible to customize server behavior for FMP without some hacks. It has many other spots for possible improvement (which I’ll get to shortly), but if it just had a reliable user-agent string we could avoid a lot of kludgey hacks.
As far as the basic HTTP client wishlist goes, there isn’t much to say. It should be a full implementation of RFC 2616. It should have a usable API. ‘Nuff said.
The goal with all of this is to make FMP responsible for its own concerns. If Rails publishes a working HTTP interface, FMP should be able to use it without modification on the server-side. Right now, FMP requires server-side hacks that violate separation of concerns and tie Rails to FMP in a brittle manner.
That all being said, one can still hack around FMP’s shortcomings in Rails and get some acceptable results. Here’s a few case-studies of said hacks with explanations of how my suggestions would render them unnecessary.
FMP needs to access the Rails site with the embedded browser and to run non-GUI scripts against it. The Rails site has optional authn; that is, if the user doesn’t login they’ll see public content, and once they login they’ll see private content at the same URL.
Seems simple enough: enable HTTP basic authn in Rails, have FMP ask for the creds, cache them, and supply them to the script and the browser. In Rails, this would look something like
# app/controllers/application.rb def current_user user_from_session or user_from_http_basic end def user_from_http_basic authenticate_with_http_basic do |login, password| User.authenticate login, password end end
The problem is that the FMP browser doesn’t preemptively provide the HTTP credentials, even if you use URL credentials like
http://user:email@example.com. Since authn is optional, Rails doesn’t respond with a 401, FMP doesn’t see a need to login, and the credentials are discarded. In all fairness, FMP gets this behavior from the underlying browser (IE or Safari), and Firefox does the same thing. To get it to work, you can allow browsers to request strict HTTP authn; ie. the server should respond 401 if they don’t provide credentials, instead of rendering the public content.
# app/controllers/application.rb before_filter :http_basic_strict_authentication def http_basic_strict_authentication return true unless params[:authn] == 'strict' authenticate_or_request_with_http_basic do |login, password| self.current_user = User.authenticate login, password end end
Thus, any URL you request with “?authn=strict” will respond with a 401 unless you submit valid credentials. FMP responds to the challenge by doing just that, and the user is logged in.
Like I said, this is a problem with many web browsers, not just FMP. Nonetheless, FMP could fix it by allowing the developer to specify the HTTP Authorization header for every request. Perhaps preferably, if FMP allowed the developer to hijack the browser’s HTTP client and session (eg. with an XMLHTTPRequest), the user could login using the HTML form, which would set the session cookie, which could then be used by the script.
Since FMP is cramming the embedded browser into an unusual GUI, it needs a few small customizations to the Rails site’s UI.
You can’t specify or customize the user agent stylesheet in FMP, so that’s right out. FMP’s user-agent header isn’t a reliable identifier; it varies between OSs and versions, so Rails has no good way to tell when the user is coming through FMP. Furthermore, since FMP can’t specify headers, there’s no way to create custom HTTP headers or to modify the user-agent header to something useful.
So, instead, you can have FMP explicitly state that it is FMP with a GET parameter, and store that statement as a user preference.
# app/controllers/application.rb def current_user= user ... session[:preferences] = params[:preferences] ... end # app/views/layouts/application.html.erb ... <%= stylesheet_link_tag "fmp" if session[:preferences][:fmp] %> ...
So, then, FMP can point its users at a URL like
https://example.com/login?preferences[fmp]=1. Anything put into the preferences parameter will be maintained in the session.
If you’re using form-based login, you’ll need to persist the FMP-supplied preferences through the login process by adding a hidden field to your login form.
# app/views/sessions/new.html.erb ... <%= hidden_field_tag :preferences, Marshal.dump(params[:preferences]) %> ...
… and you’ll need to modify #current_user to unmarshal the preferences if Rails hasn’t already.
# app/controllers/application.rb def current_user= user ... session[:preferences] = case e = params[:preferences] when String then Marshal.load e when Hash then e end ... end
Allowing a user to insert arbitrary data into their session during login is probably a Bad Idea. Having to include explicit checks for FMP and host FMP-specific CSS is also Bad. All this could be avoided if FMP allowed customization of user agent stylesheets. The session preferences could be avoided if FMP provided a usable user agent header.
Let’s face it: FMP is not a web app. It was first released (as Nutshell) a full decade before Tim Berners-Lee released the World Wide Web. Its proprietary licensing creates legal and cultural difficulties in leveraging F/OSS libraries that could provide the needed functionality at little to no cost. Nevertheless, it’s still a viable platform for integration with web services, and with a little duct tape and ingenuity can be a highly competitive solution. Third-party products like FM::Nexus’s Web Services and Troi’s URL plugin, while not free like either freedom or beer, are still making FMP more capable and easy to work with.