There is a blog post and comment thread on Ben Nadel's site about File Downloads Without Using CFContent that Sami Hoda alerted me to. Specifically, he pointed to comments about mod_xsendfile, an Apache module that serves files by scanning the output for a special HTTP header. This is really awesome in a CFML/JEE environment because the application server is freed up from waiting for a file to finish transfering.
I did some experimentation with mod_xsendfile v0.11 on Windows XP (yes, you read that correctly, on Windows) using Apache 2.2. It works beautifully. Here's an example of the web server configuration:
LoadModule xsendfile_module modules/mod_xsendfile-0.11.so
<VirtualHost *:80>
ServerName downloads
DocumentRoot "C:/workspace/Download"
XSendFile on
XSendFileIgnoreEtag on
XSendFileIgnoreLastModified on
XSendFilePath "C:/Documents and Settings/jlamoree/My Documents/Downloads"
<Directory "C:/workspace/Download">
AllowOverride all
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
The experimental CFML reads a directory of files and displays a list of links, one for a download using mod_xsendfile, and another to download a file using cfcontent. The meat of the code is pasted below, but you can download the entire experiment as mod_xsendfile-experiment.zip.
<cfheader name="Content-Disposition" value="attachment; filename=""#url.filename#""" />
<cfif url.method eq "mod_xsendfile">
<cfheader name="Content-Type" value="application/octet-stream" />
<cfheader name="X-Sendfile" value="#local.filename#" />
<cfelseif url.method eq "cfcontent">
<cfcontent file="#local.filename#" reset="yes" deletefile="no" type="application/octet-stream" />
</cfif>
<cfabort />
I created a quick and dirty JMeter test to compare both methods of sending a 8 Mb file. The first request using mod_xsendfile took 327 ms. The second request, only 99 ms. Using cfcontent the request times were 205 ms and 183 ms. So, take that with a grain of salt. In fact, use a whole salt shaker.
Apache,
CFML
|
Posted
9/30/09
@ 7:17 PM
by Joseph Lamoree
While working on a Customer and Address object relationship that is many-to-many, I wanted a way to send a single bean to the form that included all the fields of the customer, shipping address, and billing address. I modified BeanUtils to support merging two beans, while adding a prefix to property names. The reverse operation, pulling properties with a specified prefix out of a single bean, I've called an extraction.
Why go to all this trouble? Certainly this solves a very specific problem, however it works very well to mask the underlying aggregation or composition. Since the client will return all the properties in a single batch, it makes sense. Obviously, it's possible to split the client-server interaction up to separate the fields. Consider a blog form that allows dynamic creation and association with categories or tags using background AJAX.
At any rate, here's an example of preparing a bean to hand to the view layer that populates an HTML form:
var bu = getBeanUtils();
var customer = getCustomerService().getCustomer(customerId);
var shippingAddressBean = bu.create("name,streetAddress,city,region,postalCode,country");
var billingAddressBean = bu.create("name,streetAddress,city,region,postalCode,country");
bu.transfer(customer, customerBean);
if (customer.hasShippingAddress()) {
shippingAddress = customer.getShippingAddress();
bu.transfer(shippingAddress, shippingAddressBean);
bu.merge(customerBean, shippingAddressBean, "shippingAddress");
}
if (customer.hasBillingAddress()) {
billingAddress = customer.getBillingAddress();
bu.transfer(billingAddress, billingAddressBean);
bu.merge(customerBean, billingAddressBean, "billingAddress");
}
return customerBean;
When processing the user input, BeanUtils pulls the addresses out:
var bu = getBeanUtils();
var customerBean = _event.getArg("formBean");
var shippingAddressBean = bu.create("name,streetAddress,city,region,postalCode,country");
var billingAddressBean = bu.create("name,streetAddress,city,region,postalCode,country");
bu.extract(customerBean, shippingAddressBean, "shippingAddress");
bu.extract(customerBean, billingAddressBean, "billingAddress");
Following the property extraction, the beans can all be passed to the appropriate validator. Assuming all is well, the properties can be pushed back to the object instances that are persisted:
var bu = getBeanUtils();
var cs = getCustomerService();
var customer = cs.getCustomer(customerId);
var shippingAddress = "null";
var billingAddress = "null";
bu.compose(customer, customerBean, "name,email,phone");
shippingAddress = customer.getShippingAddress();
bu.compose(shippingAddress, shippingAddressBean);
shippingAddress.save();
billingAddress = customer.getBillingAddress();
bu.compose(billingAddress, billingAddressBean);
billingAddress.save();
customer.save();
Hopefully that makes sense. I pulled out a lot of superfluous code, and it's not clear where each chunk is executing. My objective was to show how the bean utility assists in the data round trip. The current version of the bean utility is posted so you can see the implementation of the transfer, compose, merge, and extract methods: BeanUtils.cfc
CFML
|
Posted
9/1/09
@ 5:18 AM
by Joseph Lamoree
The way that myOpenID does user feedback on pages with forms is understated and functional. In their HTML source, they give this content the “alert box” class, and have a link to dismiss the message at the upper right. This seems very Facebook-esque, which leads me to believe it's a widely seen/used/understood user interface element. Not too long ago, I coded a site that had UI messages very similar to this, with a jQuery timer that hid the message after a few seconds. I'd bet that no matter how many seconds it waited to remove the message, it was wrong. It also slid content up the page without a user input — another no-no.
I should note that the green trim around the main content area is clipped weirdly because of my screenshot dimensions, not due to their design. It looks much better with the rest of the site chrome.