As I wrote, I'm using Amazon S3 to store files that are too expensive to keep on my web server, with the plan of having frequently-updated files on the server and relatively constant stuff on S3. The address for my S3 server is bililite.s3.amazonaws.com, which is stored in the global variable $_SERVER['CDN'].

So to include a file, I would do:

$filename = '/toinclude.php';
if (file_exists($_SERVER['DOCUMENT_ROOT'].$filename)){
  $filename = $_SERVER['DOCUMENT_ROOT'].$filename;
}else{
  $filename = $_SERVER['CDN'].$filename;
}
include ($filename);

Which I use often enough to want to generalize it into a class.

Continue reading ‘Using S3 files in PHP’ »

Since I started using nearlyfreespeech.net 3 months ago, I've been very pleased. Getting them to set up access with a private key was straightforward and the email support person was prompt, helpful and friendly. The only downsides are the safe mode restrictions, which I have been easily able to work around, and the expensive storage ($1/MB/month), which would add up quickly with all the icons and fonts I'm serving with the webservices.

So I put them onto Amazon's S3 at bililite.s3.amazonaws.com with the intent of using that like a Content Delivery Network (though it isn't really unless I pay for CloudFront as well)—static, large files should come tranparently from S3 while the dynamic site runs on NFS.net.

I do this with a bit of .htaccess hackery. It's harder to create or modify files on S3, so files that are in active developement are on the webserver. I want to serve those files if they exist. Only if the desired files do not exist do I want to get them from S3. Unfortunately, NFS.net does not support mod_proxy, so the redirecting is not transparent (and we can't do things that require same-origin security). But for images and the like, this works:

SetEnvIf Request_URI . CDN=http://bililite.s3.amazonaws.com
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} ^/(images|inc|fonts)/
RewriteRule . %{ENV:CDN}%{REQUEST_URI}

Line 1 creates a variable named CDN. The directive SetEnv would be more natural to use as SetEnv CDN http://bililite.s3.amazonaws.com, but URL rewriting is done before SetEnv runs. The newer SetEnvIf runs early enough for the variable to be used for rewriting, but it's conditional, so we use a dummy condition: REQUEST_URI ., which means "If the requested URI matches any character"

Line 4 tests whether the requested file exists on the server. Only if it does not exist (!-f) is the next line tested, which is whether the file is in any of the CDN directories.

If it is to be redirected, create the new URL by concatenating the CDN variable with the requested URI, which does not contain the protocol or hostname. Thus http://bililite.com/images/silk/add.png has a REQUEST_URI of /images/silk/add.png and the rewritten URL is http://bililite.s3.amazonaws.com/images/silk/add.png.

The advantage of setting a variable in the .htaccess (aside from having the "magic constant" at the top of the file") is that this is passed to the PHP code as . So the name of the S3 server is written in just one place, with no need to change multiple files if it changes.

Download the code.

Demo.

Download the WP Audio Player Standalone.

So the rabbi asked me to add the ability to play the audio on the taamim page (basically, a long list of short MP3's) directly on the page, rather than click the link to open a new page. No problem, right? We're living in an HTML5 world, so I should be able to do:

$('a[href$=mp3]').each(function(){
  $('<audio>').attr({src: this.href, controls: 'controls'}).insertBefore(this);
});

And everything ought to work: browsers that can't handle <audio> elements get nothing, modern browsers get a modern audio player. Nice progressive enhancement.

But of course, it's not that easy. Webkit (Chrome, Safari) supports MP3 playing, but Firefox does not (and won't), and Internet Explorer only does for IE9 and up, and I have to support Windows XP and IE8 (source; consistent with my experimentation). I don't like the <embed>ed players, so I'll go with Flash. I like the player that Language Log uses, and viewing the source tells me that's WPAudioplayer, which has a standalone version that requires just two files, the 11-kb swf file and a 12-kb javascript file.

To use it, include the javascript with a <script> element and initialize the player with AudioPlayer.setup('/path/to/player.swf', {width: 100}); where 100 is the desired width of the player in pixels (it's constant for every player on the page and it's a mandatory option). Then, each player is implemented by replacing an existing element, identified by id: AudioPlayer.embed(id, {soundFile: '/path/to/soundfile.mp3'});.

Of course, iOS won't run Flash, so I still need to use the <audio> element there. So I need to detect if the audio element works, and if not, insert the Flash substitute. Browsers that can't handle either get a blank spot.

Putting it together into a plugin:

(function($) {

var uid = 0;
var init = function (swf, width){
	AudioPlayer.setup(swf, {width: width});
	init = $.noop;
}
$.fn.inline_mp3 = function(swf){
  return this.each(function(){
		var id = 'audioplayer_'+(uid++);
		var player = $('<audio>').attr({
			src: this.href,
			controls: 'controls',
			id: id
		}).insertBefore(this);
		// audio.canPlayType test from http://diveintohtml5.com/everything.html#audio-mp3
		if (!(player[0].canPlayType && player[0].canPlayType('audio/mpeg;').replace(/no/, ''))){
			init (swf, player.width());
			AudioPlayer.embed(id, {soundFile: this.href});
		}
	});
};
})(jQuery);

It uses a unique number to assign an id to each element, and lazy-initializes the Flash player. The player should be styled with a given width (since IE8 doesn't have a default <audio> size):

audio {
	width: 80px; 
	display: inline-block;
}

And use it:

$('a[href$=mp3]').inline_mp3('/path/to/player.swf');

And there are lots of other packages of html5/Flash fallback audio players but this is small and easy enough for me to understand.

Brad noted an incredibly dumb bug; evidently I used a single = instead of a double == in an if condition, and never tested for the false case. It is now corrected (and named version 1.4).

I've used 1&1 since I started mucking about on the web; they had a cheap plan for $3/month with one domain name and a simple LAMP stack. But the price has been going up (now $5/month; still comparatively cheap!), and I'm starting to chafe at the limitations (no SSH shell access, proprietary 404 pages, no languages beside PHP) and I didn't want to shell out for my own virtual server (Rackspace goes as low as $11/month but I'm really cheap). So I was overjoyed when I found Nearly Free Speech.

Continue reading ‘Nearly Free Speech’ »

Just got an email from Amazon reminding me that "As part of our continued effort to ensure that the Product Advertising API is an efficient and effective advertising tool, we’ve identified opportunities to streamline the API", which is their way of saying that the API exists to make them money. Anything that doesn't serve that purpose is eliminated, however nice it might be for me. Their right, even if they're not right. I've already taken into account the major changes. The only thing is to change the API version to the most recent; in the aws_signed_request routine, change the $params["Version"] line to read $params["Version"] = "2011-08-01"; and that should be it.

nearlyfreespeech runs with safe mode and safe_mode_gid on, so you can't run shell scripts or other cool things. Usually that's not an issue, but if you do, they let you run CGI scripts, in any of a number of languages. You just have to treat it as a shell script, with a shebang notation of the proper language. There are a few caveats that took me a day to find out:

  1. The correct program is /usr/local/bin/php-cgi (or whereever your server puts PHP), not /usr/local/bin/php. That is the command-line interpreter, which does not output the headers. You can manually do echo "Content-type: text/html\n" etc., followed by a blank line, but who wants to try to debug that?
  2. The script has to be executable. You get a 500 Server Error otherwise. Do a chmod +x scriptname.cgi.
  3. Scripts need the appropriate file and directory permissions to write to the server. Changing the permissions of the server directory is probably too insecure (though for safe mode it's the only way); it's better to set-uid the script, but then keep it under wraps.

So a sample CGI script would be:

#!/usr/local/bin/php-cgi
<?php echo "Hello, world"; ?>

in an executable file with a .cgi extension.

Note that FTP and SFTP can't set the setuid bit, so that if you upload the file (or edit it with an FTP-aware editor like Notepad++), you need to chmod 477 scriptname.cgi again.

I've learned some things about .htaccess, but mostly I've learned that programming it is deep black magic. The most important thing is "Don't use mod-rewrite unless you absolutely need to". All the fancy stuff is better off done by the PHP (or whatever language you're using) code.

Don't do:

RewriteEngine On
RewriteBase /
RewriteRule ^([^/\.]+)/$ /index.php?main=$1&part=$1 [QSA,L]
RewriteRule ^([^/\.]+)/([^/\.]+)$ /index.php?main=$1&part=$2 [QSA,L]

to map your site to your index page; do:

FallbackResource /index.php

And in index.php parse the $_SERVER['REQUEST_URI'] to get your page/subpage arguments. Much easier to debug!

And to use FallbackResource you need to be running Apache 2.2, so get a good web host.

I'm working on moving bililite.com to nearlyfreespeech.net; it's cheaper, more reliable and more hacker-friendly than 1&1. I'll write a post with my experiences and reasons later. But for now just the blog has moved to bililite.nfshost.com/blog, with the plan to move the domain registration for bililite.com to nearlyfreespeech when everything is working and then return the blog to bililite.com/blog. Moving it was straightforward, as I've done it before, but there are some subtleties: nearlyfreespeech runs with safe mode and safe_mode_gid on, so you have to watch file and directory permissions; SaraPin explains exactly how. WordPress loads with the group me and nearlyfreespeech's webserver runs with the group web, so you need to SSH into the account and change the WordPress directory with chgrp -R * web. The file permisssions should be 664 (writeable to self and group, readable to all) and the directory permissions should be 775 (writeable and executable to self and group, executable to all; under UNIX, getting a file from a directory is "executing" it), but the default WordPress installation does that.

If you don't know what that means, you probably shouldn't be using a d.i.y. host like nearlyfreespeech.

And now it works!

Almost. SexyBookmarks's images aren't loading when I use the new code, so I went back to the old version. It slows the page load down anyway, so I may take it off entirely.

And the rest of the bililite site is still at the old host, and some of the sample code (in /blog/blogfiles) uses that, so for now I have a line in my .htaccess to refer it back: RedirectMatch /blog/blogfiles(.*) http://bililite.com/blog/blogfiles$1.

My office manager gave me this as a Christmas present. I have a great job.