The core of Eventsites is a single-page Javascript application.
On visiting the site, a user receives a bare-bones XHTML page that binds all relevant Javascript and CSS files. After loading these files, the script is used to perform the following operations:
- Parse the event-ID from the URL, e.g. www.eventsites.co.uk/evdb/events/E0-001-000935917-5 equates to event ID E0-001-000935917-5. An invalid or non-event URL simply routes to the site homepage.
- Pull event data from EVDB using a REST request. The REST URL for the Carson Future of Web Apps would be: http://api.evdb.com/rest/events/get? &id=E0-001-000935917-5&app_key=mCzrkKSxK43TMhtN
- Parse data from the EVDB XML response
- Construct and despatch a request for the event Flickr RSS feed. In the Carson case, this would be: http://www.flickr.com/services/feeds /photos_public.gne?&format=rss_200 &tags=futureofwebapps
- Parse the Flickr feed to retrieve photo URL's
- Construct the application XHTML interface
- (on map select) create and initialise a Google Map with the event latitude / longitude
- (on photos select) create an XHTML photo gallery using the URLs specified in the Flickr feed
<body onload="new Application()" />
All the XHTML in Eventsites is built using Javascript DOM methods. I'm not a fan of using getElementById to attach methods to existing XHTML. I don't like the approach because it lacks any formal binding between the code and the structure - change an ID in the XHTML file and your code can't bind to the interface.
Building the XHTML in the code ensures that interface and its behaviours are bound together by definition.
Making XHR cross-domain
Eventsites uses the XMLHttpRequest object to pull in data from EVDB and Flickr. Unfortunately, since XHR is limited to same-domain calls, we need a server proxy to redirect these externally.
The proxy script that is used in Eventisites is shown below:
<?php
$remoteURL = $_GET['url'];
$pos_int = strpos($remoteURL,'http://');
if($pos_int===0){
$fp = fopen($remoteURL, 'r');
header('Content-type: text/xml');
if($fp){
fpassthru($fp);
}else{
echo
'<?xml version="1.0" encoding="UTF-8"?>
<root connection="false">
<eventsites_message>
Failed to connect
</eventsites_message>
</root>';
}
}
?>
This script simply sends a GET request to any address passed in the url argument and passes on the data that's returned.
You can see the script in action at: http://www.eventsites.co.uk/remote_proxy.php and its functionality can be seen by requesting the event's photo feed (URL encoded).
Handling URL's
As mentioned, Eventsites is a single-page application. This means that although the ID for an event is specified in its URL, e.g. www.eventsites.co.uk/evdb/ events/E0-001-000935917-5, all such URLs must still map to the same document on the server.
This could be done with mod_rewrite but since I haven't the faintest idea how mod_rewrite works, I worked the same magic using simple old .htaccess.
.htaccess allowed me to very simply specify the 404 error page as being the frontpage. All URL's that don't correspond to files present on the server just route back to root.
The root level .htaccess file for the domain is:
ErrorDocument 404 /
Once the frontpage has loaded, Javascript accesses the window.location object, and uses a regular expression to parse the event ID.
Next
Why XHR needs to become opt-in cross domain
Update:
As a number of people correctly pointed out, my server proxy originally gave full read access to the local file system. Although this was something I'd forbidden in earlier incarnations of the code, I filtered it out for the sake of making the essence of the code as understandable as possible. Not a smart thing to do though and all URL's are now filtered (as shown in the code) to make sure they're external.


7 comments:
I would have struggled getting mod_rewrite work with poor results...
Damn, I wish I was smart.
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^(.*) index.php
This just redirects all URLs to the index.php page as long as they don't reference files or directories that already exist. The URL in the address bar is not changed.
RewriteRule ^(.*) - [PT,L]
just before the last line to make it work properly. If you wanted to know what the requested URL was without using JavaScript to sniff the URL, then you could change the last line to:
RewriteRule ^(.*) index.php/$1
This would change a URL like domain.com/evdb to domain.com/index.php/evdb, without changing the URL in the address bar. You could then use PHP to get the path after the index.php...
You may be interested in an alternative way of making cross-domain requests:
http://blog.monstuff.com/archives/000280.html
It has some limitations (requires Flash 8, only GET/POST at this time, requires a proper cross-domain policy file in the target domains), but it avoids un-necessary server traffic...
I agree with you that a proper/controlled cross-domain XHR API would open many doors. The Flash-based approach is a workaround to experiment and write this kind of application before the browsers decide to offer an API like that....
I'd briefly seen that approach on Ajaxian and I think it's pretty much spot on. Having the server opt-in is the key.
Thanks!
I eagerly await your next post (which you hinted the title).
Just to let you know, I made some progress on cross-domain requests thru Flash, by making it work on Flash 6+ (instead of requiring Flash 8).
I still see a security problem with cross-domain XHR, even if it has a server-side policy file:
Let's say that you start having services with personal data (say: "my bookmarks").
As a web service, I don't want to dictate who should or shouldn't access the data. It should be up to the user. So the server-side policy file for most non-intranet web services is "allow all domains".
As a user, I may want some sites access and update that data, but I also want to prevent most sites from, say, deleting my bookmarks. How do you prevent a evil.com page from deleting my bookmarks when I open it and my browser has the right cookies to authenticate me with bookmarks.com?
It's a good question and is actually two separate questions
1. Should a browser be able to access data cross domain (IMO yes, as long as the server opts-in to cross domain requests)
2. What's the identity of the request originator and are they authorised to execute their requested operation?
The first problem is separate to the second. "Can I connect to a source" is a different problem to "Am I authorised to get data from a source".
I think that identity and security are going to be one of the biggest bugbears for compound browser applications and there are a lot of issues around them.
Exactly as you point out though, cookies probably aren't going to be the answer and cross domain requests should always be explicitly validated.