Archive for the ‘Work’ category

Cross-Origin Resource Sharing Demo

September 11th, 2009

The Problem

Many times I’ve been frustrated with the same-origin policy for AJAX requests. I have legitimate content on another domain that I would like to retrieve and display on another page using AJAX.

As an example, department.unl.edu would like to display a list of upcoming events from events.unl.edu/department/.

Historically you would have to serve the content out as JSON, or use a proxy to convert the content to json, and reference the resource using a script tag which gets around the same-origin policy – and for years, this has worked well.

Unfortunately this requires you to either send content out in multiple formats (html + json for these requests), or use html and send all requests for cross-site resources through a proxy script, which adds a translation delay.

Serving the content out in two formats requires more work to maintain, and if the content output is cached, requires more resources to generate. If you use a proxy to translate the content, you have an open proxy unless you lock down what resources the proxy script can retrieve and translate.

CORS is the Answer

The answer is in the Cross-Origin Resource Sharing specification, which is supported in Firefox 3.5+, Safari 4+ and albeit using agent specific methods, supported in Internet Explorer 8+.

The specification allows resources on other domains to be retrieved through AJAX methods, if the resource announces support as a Cross-Origin Resource. This is done through special headers which identify to the user-agent which alternate origin domains can request the resource and what methods are supported.

Demo

For the demo, the html and js is served off of this domain, and the CORS resource is located on ucommbieber.unl.edu.

Click here to go to the demo.

This page demonstrates both GET and POST methods, with custom callbacks, using an api just like the jQuery get and post methods.

The HTML:

<html>
<script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></script>

<script type="text/javascript" src="cors.js"></script>
<script type="text/javascript">

function testGet()
{
    getCORS('http://ucommbieber.unl.edu/CORS/cors.php', null, function(data){alert(data);});
}
function testPost(form)
{
    postCORS('http://ucommbieber.unl.edu/CORS/cors.php', {name: form.name.value}, function(data){alert(data);});
}
</script>
<body>
<h1>CORS Examples</h1>

<p>Test GET
    This page retrieves content from another server, using CORS<br />
    <a href="#" onclick="testGet(); return false;">Get content from another server</a>
</p>

<p>Test POST:
    <form action="#" onsubmit="testPost(this); return false">

        Name: <input type="text" name="name" />
        <input type="submit" />
    </form>

</p>
<a href="http://saltybeagle.com/2009/09/cross-origin-resource-sharing-demo/">More info</a>
</body>
</html>

The javascript:

/**
 * This is for Cross-site Origin Resource Sharing (CORS) requests.
 *
 * Additionally the script will fail-over to a proxy if you have one set up.
 *
 * @param string   url      the url to retrieve
 * @param mixed    data     data to send along with the get request [optional]
 * @param function callback function to call on successful result [optional]
 * @param string   type     the type of data to be returned [optional]
 */
function getCORS(url, data, callback, type) {
    try {
        // Try using jQuery to get data
        jQuery.get(url, data, callback, type);
    } catch(e) {
        // jQuery get() failed, try IE8 CORS, or use the proxy
        if (jQuery.browser.msie && window.XDomainRequest) {
            // Use Microsoft XDR
            var xdr = new XDomainRequest();
            xdr.open("get", url);
            xdr.onload = function() {
                callback(this.responseText, 'success');
            };
            xdr.send();
        } else {
            try {
                // Ancient browser, use our proxy
                var mycallback = function() {
                    var textstatus = 'error';
                    var data = 'error';
                    if ((this.readyState == 4)
                        && (this.status == '200')) {
                        textstatus = 'success';
                        data = this.responseText;
                    }
                    callback(data, textstatus);
                };
                // proxy_xmlhttp is a separate script you'll have to set up
                request = new proxy_xmlhttp();
                request.open('GET', url, true);
                request.onreadystatechange = mycallback;
                request.send();
            } catch(e) {
                // Could not fetch using the proxy
            }
        }
    }
}

/**
 * This method is for Cross-site Origin Resource Sharing (CORS) POSTs
 *
 * @param string   url      the url to post to
 * @param mixed    data     additional data to send [optional]
 * @param function callback a function to call on success [optional]
 * @param string   type     the type of data to be returned [optional]
 */
function postCORS(url, data, callback, type)
{
    try {
        // Try using jQuery to POST
        jQuery.post(url, data, callback, type);
    } catch(e) {
        // jQuery POST failed
        var params = '';
        for (key in data) {
            params = params+'&'+key+'='+data[key];
        }
        // Try XDR, or use the proxy
        if (jQuery.browser.msie && window.XDomainRequest) {
            // Use XDR
            var xdr = new XDomainRequest();
            xdr.open("post", url);
            xdr.send(params);
            xdr.onload = function() {
                callback(xdr.responseText, 'success');
            };
        } else {
            try {
                // Use the proxy to post the data.
                request = new proxy_xmlhttp();
                request.open('POST', url, true);
                request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
                request.send(params);
            } catch(e) {
                // could not post using the proxy
            }
        }
    }
}

The PHP code responding from the server looks like this:

<?php

// Specify domains from which requests are allowed
header('Access-Control-Allow-Origin: *');

// Specify which request methods are allowed
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');

// Additional headers which may be sent along with the CORS request
// The X-Requested-With header allows jQuery requests to go through
header('Access-Control-Allow-Headers: X-Requested-With');

// Set the age to 1 day to improve speed/caching.
header('Access-Control-Max-Age: 86400');

// Exit early so the page isn't fully loaded for options requests
if (strtolower($_SERVER['REQUEST_METHOD']) == 'options') {
    exit();
}

// If raw post data, this could be from IE8 XDomainRequest
// Only use this if you want to populate $_POST in all instances
if (isset($HTTP_RAW_POST_DATA)) {
    
$data explode('&'$HTTP_RAW_POST_DATA);
    foreach (
$data as $val) {
        if (!empty(
$val)) {
            list(
$key$value) = explode('='$val);   
            
$_POST[$key] = urldecode($value);
        }
    }
}

echo 'Hello CORS, this is '
     
$_SERVER['SERVER_NAME'] . PHP_EOL
     
.'You sent a '.$_SERVER['REQUEST_METHOD'] . ' request.' PHP_EOL;

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    echo 
'Your name is ' htmlentities($_POST['name']);
}


Note the special headers that have to be returned to allow the request to complete:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: X-Requested-With

The X-Requested-With allows us to use jQuery’s internal GET method which adds in the additional header.

View all the code on GitHub.

Support for proxy fallback for older browsers is left up to you to code. The proxy requires you to set up a script which translates a page into JSON and handles the response. There are many examples of how to do this.

Catalog of Courses/Graduate Bulletin

April 17th, 2008

Course catalogs are a beast of a publication. The documents are traditionally printed on regular intervals and offer a complete listing of requirements and courses of study for an academic institution. For some students, these books become the bible by which their lives revolve around for 4+ years of professional education. They become a carefully studied map guiding them through the courses required to earn a very expensive piece of paper.

I followed my bulletin closely and kept a copy of it in a secure location. I would refer to it after every semester to make sure I was still on track. Sure there were minute changes, but this was a mammoth printed catalog… and acted as a binding contract between the school and the student.

As the electronic era has progressed, we’re offered new opportunities for publishing these large documents, be it PDF, HTML or even print on demand. At some point I’m sure typesetters were working on this document and it was rarely updated. At some point it was transferred to an electronic format, but still printed to paper. But recently I had the opportunity to transfer one of these large publications into an electronic format, that will hopefully become a long lasting appointment.

I’m referring to, an online course catalog or bulletin of courses. In this instance, the University of Nebraska-Lincoln’s graduate bulletin. Not to a PDF, but to a solely electronic based medium that will act as the permanent location of the publication.

The old file was a meticulously updated FrameMaker document, which offered some flexibility in output, but nothing to the extent required. The process took probably 3 months to complete, and went online last November to some quiet fanfare. But today, I felt one of the most gratifying moments any web developer could ask for… an end user expressed an unprovoked appreciation and testimonial of the website.

There may never come a day when someone acknowledges the revisioning features, the XCRI XML output format, the simple URL structure or the ease of editing – but after today, it was all worth it.

With that, here’s the link – I commend everyone else that has transferred one of these large publications into an online format, it is no easy task.

The University of Nebraska-Lincoln 2007-2008 Graduate Studies Bulletin

Converting a PNG to GIF with Transparency Matte

December 4th, 2006

A couple months ago a coworker and I were trying to figure out how to use ImageMagick to convert a bunch of .png images to .gif. Using mogrify, the process is fairly straightforward, but preserving the transparency by matting alpha pixels against a white background was pretty tricky.

Here’s the issue:

Original png zip icon
Bad gif zip icon
Good gif zip icon

The epic search turned up the following command:

mogrify -format gif -bordercolor white -border 0x0  application-zip.png.png

Which gives you the good gif above.

Use Dreamweaver Templates as PHP Objects

January 27th, 2006

Yesterday I finished up the first release of a PEAR package for using Macromedia Dreamweaver templates as PHP objects.

The package is named UNL_DWT (eventually may be renamed HTML_Template_DWT), and is now available on the UNL PEAR Channel server.

This package builds PHP classes for your .dwt files with member variables for editable regions.

This is a simple abstraction of page presentation and programmable content, but is useful in large organizations that use Dreamweaver templates for their page designs.

[Some of you may be asking, why another damn templating engine?]

The problem with other template engines is that they use unique syntax that can’t be sent to a browser without processing, and (without 3rd party plugins) aren’t integrated into major HTML Editing Applications.

Dreamweaver uses HTML comments to define editable content regions which ensures pages can be sent to a browser without any processing, while still allowing the templates to be used in other editors.

Designers can build a site design within Dreamweaver and use that design in static HTML files, pages editable with Macromedia Contribute, and now — programmer friendly PHP objects.

One set of templates can be used by users who understand any web publishing application, used as static (x)html pages, ASP pages, PHP pages, as well as PHP programmers who prefer to use the templates as objects.


[Where is this going?]

This package was a necessary foundation for creating a UNL PEAR channel package for the UNL Templates that can be easily adopted across all of the PHP servers at our University.

We currently distribute a zip file with the Dreamweaver templates to over 120 registered web developers on campus, who use the templates in every application imaginable. Dreamweaver templates suit our needs to use one design across such a wide variety of applications, but for those familiar with PHP it would be easier to distribute the UNL templates as a PEAR package and be able to use them as objects.

- That’s where this is going.

Get the package at http://pear.unl.edu/

Learn more about how the University of Nebraska-Lincoln (UNL) is organizing it’s web developers to create a consistent web design at the university.
http://www.unl.edu/webdevnet/

getFTPDir, PHP Class that downloads a ftp directory

December 13th, 2004

This little php class connects to a ftp server and downloads an entire directory, including subdirectories.
Useful for syncronizing files between servers, using ftp.

Documentation here.

Download here.

Post suggestions for improvements.