<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Hacking at 0300 &#187; Web Design</title>
	<atom:link href="http://bililite.nfshost.com/blog/category/web-design/feed/" rel="self" type="application/rss+xml" />
	<link>http://bililite.nfshost.com/blog</link>
	<description>Thoughts on web design and programming from a very occasional volunteer webmaster</description>
	<lastBuildDate>Mon, 07 May 2012 14:38:35 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
		<item>
		<title>Using S3 files in PHP</title>
		<link>http://bililite.nfshost.com/blog/2012/04/03/using-s3-files-in-php/</link>
		<comments>http://bililite.nfshost.com/blog/2012/04/03/using-s3-files-in-php/#comments</comments>
		<pubDate>Tue, 03 Apr 2012 19:37:49 +0000</pubDate>
		<dc:creator>Danny</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Web Design]]></category>

		<guid isPermaLink="false">http://bililite.nfshost.com/blog/?p=2278</guid>
		<description><![CDATA[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 [...]]]></description>
			<content:encoded><![CDATA[<p><a href="/blog/2012/04/02/using-nfs-net-with-amazon-s3/">As I wrote</a>, I'm using <a href="http://aws.amazon.com/s3/">Amazon S3</a> 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 <code class="language-php">$_SERVER['CDN']</code>.</p>
<p>So to <a href="http://php.net/include">include</a> a file, I would do:</p>
<pre><code class="language-php">$filename = '/toinclude.php';
if (file_exists($_SERVER['DOCUMENT_ROOT'].$filename)){
  $filename = $_SERVER['DOCUMENT_ROOT'].$filename;
}else{
  $filename = $_SERVER['CDN'].$filename;
}
include ($filename);</code></pre>
<p>Which I use often enough to want to generalize it into a class.</p>
<span id="more-2278"></span>
<p>The other thing that would be useful is a directory listing, which is harder than it sounds for S3 since it has no directory structure; it's just a flat database of keys (the equivalent of filenames) and values (the files themselves). Thus <code>bililite.s3.amazonaws.com/images/silk/add.png</code> has S3 return the file labelled <code>/images/silk/add.png</code>; it has no inherent relationship to <code>/images/silk/delete.png</code> or <code>/images/silk/</code>.</p>
<p>The key is that just retrieving the server URL returns an XML listing of all the files, and there is an API to limit the files returned. <a href="http://bililite.s3.amazonaws.com">bililite.s3.amazonaws.com</a> returns all the files (up to a numerical limit; see below). <a href="http://bililite.s3.amazonaws.com?prefix=images/silk/">bililite.s3.amazonaws.com?prefix=images/silk/</a> returns all the filenames that start with <code>images/silk/</code> (note no leading slash). That's not quite enough, since it gives us sub-folders as well, but the <code>delimiter</code> parameter tells S3 to group all files that contain the delimiter after the prefix into one entry in the XML list. That's the equivalent of a subfolder. So <a href="http://bililite.s3.amazonaws.com?prefix=images/silk/&#038;delimiter=/">bililite.s3.amazonaws.com?prefix=images/silk/&amp;delimiter=/</a> gives us the list we want.</p>
<p>One more subtlety: S3 returns a maximum of 1000 names, then sets a flag in the XML to say the list was truncated. You can then ask for the next 1000 by naming the last returned file.</p>
<p>The <a href="http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGET.html">documentation</a> is pretty opaque, but it's all in there.</p>
<p><a href="/blog/blogfiles/highlight.php?source=s3.class.php">I put it all together</a> into an abstract class that just handles the file part (assuming that any CDN would work the same way, just appending the host name to the file name) and a concrete class that handles the S3-specific directory-simulating parts. <a href="/blog/blogfiles/highlight.php?source=s3.class.php">See the source code</a>. The method names are meant to parallel the built-in PHP functions.</p>
<div class="prelike"><dl>
<dt>constructor</dt>
<dd><code class="language-php">$s3 = new S3('http://bililite.s3.amazonaws.com');</code></dd>
<dt>realpath</dt>
<dd><pre><code class="language-php">$path = $s3->realpath('/toinclude.php');
include($path);
// or
$content = file_get_contents($path);</code></pre> returns the real path for the file, either from <code class="language-php">$_SERVER['DIRECTORY_ROOT']</code> or the S3 root passed in with the constructor. In other words, if the file exists on the web server, <code>realpath</code> returns something like '/public/www/toinclude.php' and if it does note, returns something like 'http://bililite.s3.amazonaws.com/public/www/toinclude.php'. Note that if the file does not exist on the web server, this will return the path on the S3 root without checking if the file actually exists; use <code>file_exists</code> for that.</dd>
<dt>isCDN</dt>
<dd><code class="language-php">$flag = $s3->isCDN($s3->realpath('/toinclude.php'));</code> returns <code class="language-php">FALSE</code> if the path represents a file on the web server (i.e. from <code class="language-php">$_SERVER['DIRECTORY_ROOT']</code>), <code class="language-php">TRUE</code> otherwise (note that it does not check if the file actually exists on the S3 server). Note also that this requires the path returned by <code>realpath</code>.</dd>
<dt>file_exists</dt>
<dd><code class="language-php">$flag= $s3->file_exists('/toinclude.php');</code> returns <code class="language-php">TRUE</code> if the file exists on the web server or the S3 server (this does check the S3 server).</dd>
<dt>filemtime</dt>
<dd><code class="language-php">$timestamp = $s3->filemtime('/toinclude.php');</code> returns the time the file was last modified.</dd>
<dt>scandir</dt>
<dd><code class="language-php">$files = $s3->scandir('/images/');</code> returns an array of names of files that exist <em>either</em> on the web server or the S3 server (it's the union of the directory contents).</dd>
</dl></div>
<p>This assumes that the ACL (access control list) for the files is set to allow anonymous reading; if not, use Donovan Schönknecht's excellent <a href="http://undesigned.org.za/2007/10/22/amazon-s3-php-class">S3 class</a>. Of course, you'd have to rename one of these classes to avoid the conflict (or use <a href="http://php.net/namespace">namespaces</a>).</p>
<p>Hope this helps someone.</p>]]></content:encoded>
			<wfw:commentRss>http://bililite.nfshost.com/blog/2012/04/03/using-s3-files-in-php/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Using NFS.net with Amazon S3</title>
		<link>http://bililite.nfshost.com/blog/2012/04/02/using-nfs-net-with-amazon-s3/</link>
		<comments>http://bililite.nfshost.com/blog/2012/04/02/using-nfs-net-with-amazon-s3/#comments</comments>
		<pubDate>Mon, 02 Apr 2012 21:28:22 +0000</pubDate>
		<dc:creator>Danny</dc:creator>
				<category><![CDATA[Web Design]]></category>

		<guid isPermaLink="false">http://bililite.nfshost.com/blog/?p=2265</guid>
		<description><![CDATA[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 [...]]]></description>
			<content:encoded><![CDATA[<p>Since I started using <a href="http://nearlyfreespeech.net">nearlyfreespeech.net</a> 3 months ago, I've been very pleased. Getting them to set up <a href="https://members.nearlyfreespeech.net/dwachss/support/faq?q=SSHKeys&#038;keywords=ssh&#038;form=1#SSHKeys">access with a private key</a> was straightforward and the email support person was prompt, helpful and friendly. The only <a href="http://bililite.nfshost.com/blog/2012/02/03/nearly-free-speech/" title="Nearly Free Speech">downsides</a> 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 <a href="/webservices/?page=icons">icons</a> and <a href="/webservices/?page=fonts">fonts</a> I'm serving with the <a href="/webservices/">webservices</a>.</p>
<p>So I put them onto <a href="http://aws.amazon.com/s3/">Amazon's S3</a> at <a href="http://bililite.s3.amazonaws.com">bililite.s3.amazonaws.com</a> with the intent of using that like a <a href="http://en.wikipedia.org/wiki/Content_delivery_network">Content Delivery Network</a> (though it isn't really unless I pay for <a href="http://aws.amazon.com/cloudfront/">CloudFront</a> as well)&mdash;static, large files should come tranparently from S3 while the dynamic site runs on NFS.net.</p>
<p>I do this with a bit of <code>.htaccess</code> 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 <em>if they exist</em>. Only if the desired files do not exist do I want to get them from S3. Unfortunately, NFS.net does not support <a href="http://httpd.apache.org/docs/2.0/mod/mod_proxy.html"><code>mod_proxy</code></a>, so the redirecting is not transparent (and we can't do things that require <a href="https://developer.mozilla.org/en/Same_origin_policy_for_JavaScript">same-origin security</a>). But for images and the like, this works:</p>
<pre></code>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}</code></pre>
<p>Line 1 creates a variable named <code>CDN</code>. The directive <a href="http://httpd.apache.org/docs/2.3/mod/mod_env.html#setenv"><code>SetEnv</code></a> would be more natural to use as <code>SetEnv CDN http://bililite.s3.amazonaws.com</code>, but URL rewriting is done <em>before</em> <code>SetEnv</code> runs. The newer <code>SetEnvIf</code> runs early enough for the variable to be used for rewriting, but it's conditional, so we use a dummy condition: <code>REQUEST_URI .</code>, which means "If the requested URI matches any character"</p>
<p>Line 4 tests whether the requested file exists on the server. Only if it does not exist (<code>!-f</code>) is the next line tested, which is whether the file is in any of the CDN directories.</p> 
<p>If it is to be redirected, create the new URL by concatenating the <code>CDN</code> variable with the requested URI, which does not contain the protocol or hostname. Thus <code><a href="http://bililite.com/images/silk/add.png">http://bililite.com/images/silk/add.png</a></code> has a <code>REQUEST_URI</code> of <code>/images/silk/add.png</code> and the rewritten URL is <code>http://bililite.s3.amazonaws.com/images/silk/add.png</code>.</p>
<p>The advantage of setting a variable in the <code>.htaccess</code> (aside from having the "magic constant" at the top of the file") is that this is passed to the PHP code as <code class="language-php>$_SERVER['CDN']</code> and can be used like <code class="language-php>imagecreatefrompng($_SERVER['CDN'].'/images/silk/add.png')</code>. So the name of the S3 server is written in just one place, with no need to change multiple files if it changes.</p>]]></content:encoded>
			<wfw:commentRss>http://bililite.nfshost.com/blog/2012/04/02/using-nfs-net-with-amazon-s3/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Inline Audio Player</title>
		<link>http://bililite.nfshost.com/blog/2012/04/02/inline-audio-player/</link>
		<comments>http://bililite.nfshost.com/blog/2012/04/02/inline-audio-player/#comments</comments>
		<pubDate>Mon, 02 Apr 2012 16:08:52 +0000</pubDate>
		<dc:creator>Danny</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[Web Design]]></category>

		<guid isPermaLink="false">http://bililite.nfshost.com/blog/?p=2250</guid>
		<description><![CDATA[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 [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://youngisrael-stl.org/inc/inline_mp3.js">Download the code</a>.</p>
<p><a href="http://youngisrael-stl.org/articlereader.php?author=shulman&#038;archive=yes&#038;tags[]=trop&#038;tags[]=Audio">Demo</a>.</p>
<p><a href="http://wpaudioplayer.com/wp-content/downloads/audio-player-standalone.zip">Download the WP Audio Player Standalone</a>.</p>
<p>So the rabbi asked me to add the ability to play the audio on the <a href="http://youngisrael-stl.org/articlereader.php?author=shulman&#038;archive=yes&#038;tags[]=trop&#038;tags[]=Audio">taamim page</a> (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:</p>
<pre><code class="language-javascript">$('a[href$=mp3]').each(function(){
  $('&lt;audio&gt;').attr({src: this.href, controls: 'controls'}).insertBefore(this);
});</code></pre>
<p>And everything ought to work: browsers that can't handle <code><a href="http://www.w3schools.com/html5/tag_audio.asp">&lt;audio&gt;</a></code> elements get nothing, modern browsers get a modern audio player. Nice progressive enhancement.</p>
<p>But of course, it's not that easy. Webkit (Chrome, Safari) supports MP3 playing, but Firefox does not (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=562730">and won't</a>), and Internet Explorer only does for IE9 and up, and I have to support Windows XP and IE8 (<a href="http://en.wikipedia.org/wiki/Html5_audio">source</a>; consistent with my experimentation). I don't like the <code>&lt;embed&gt;</code>ed players, so I'll go with Flash. I like the player that <a href="http://languagelog.ldc.upenn.edu/nll/">Language Log</a> uses, and viewing the source tells me that's <a href="http://wpaudioplayer.com/">WPAudioplayer</a>, which has a standalone version that requires just two files, the 11-kb <code>swf</code> file and a 12-kb javascript file.</p>
<p>To use it, include the javascript with a &lt;script&gt; element and initialize the player with <code class="language-javascript">AudioPlayer.setup('/path/to/player.swf', {width: 100});</code> where <code class="language-javascript">100</code> 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 <em>replacing</em> an existing element, identified by <code>id</code>: <code class="language-javascript">AudioPlayer.embed(id, {soundFile: '/path/to/soundfile.mp3'});</code>.</p>
<p>Of course, iOS won't run Flash, so I still need to use the <code><a href="http://www.w3schools.com/html5/tag_audio.asp">&lt;audio&gt;</a></code> element there. So I need to detect if the <code>audio</code> element works, and if not, insert the Flash substitute. Browsers that can't handle either get a blank spot.</p>
<p>Putting it together into a plugin:</p>
<pre><code class="language-javascript">(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 = $('&lt;audio&gt;').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 &#038;& player[0].canPlayType('audio/mpeg;').replace(/no/, ''))){
			init (swf, player.width());
			AudioPlayer.embed(id, {soundFile: this.href});
		}
	});
};
})(jQuery);</code></pre>
<p>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 <code>&lt;audio&gt;</code> size):</>
<pre><code class="language-css">audio {
	width: 80px; 
	display: inline-block;
}</code></pre>
<p>And use it:</p>
<pre><code class="language-javascript">$('a[href$=mp3]').inline_mp3('/path/to/player.swf');</code></pre>
<p>And there are <a href="http://praegnanz.de/html5video/">lots</a> <a href="http://www.schillmania.com/projects/soundmanager2/">of other packages of html5/Flash fallback audio players</a> but this is small and easy enough for me to understand.]]></content:encoded>
			<wfw:commentRss>http://bililite.nfshost.com/blog/2012/04/02/inline-audio-player/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Nearly Free Speech</title>
		<link>http://bililite.nfshost.com/blog/2012/02/03/nearly-free-speech/</link>
		<comments>http://bililite.nfshost.com/blog/2012/02/03/nearly-free-speech/#comments</comments>
		<pubDate>Fri, 03 Feb 2012 10:05:17 +0000</pubDate>
		<dc:creator>Danny</dc:creator>
				<category><![CDATA[Web Design]]></category>

		<guid isPermaLink="false">http://bililite.nfshost.com/blog/?p=2228</guid>
		<description><![CDATA[I've used 1&#038;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 [...]]]></description>
			<content:encoded><![CDATA[<p>I've used <a href="http://1and1.com">1&#038;1</a> since I started mucking about on the web; they had a cheap plan for $3/month with one domain name and a simple <a href="http://en.wikipedia.org/wiki/LAMP_(software_bundle)">LAMP stack</a>. 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 (<a href="http://www.rackspace.com/cloud/cloud_hosting_products/servers/pricing/">Rackspace</a> goes as low as $11/month but I'm really cheap). So I was overjoyed when I found <a href="http://nearlyfreespeech.net">Nearly Free Speech</a>.<p>
<span id="more-2228"></span>
<p>It has several advantages:</p>
<ol>
<li>SSH into a real Linux shell. It's been a while since I used Unix, but the old habits come back fast and I like having all those tools available.</li>
<li><a href="http://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol">SFTP</a> support. More secure and faster (at least it seems that way) than FTP, especially with <a href="http://notepad-plus-plus.org/">Notepad++</a>.</li>
<li>It sure seems cheap (only $0.01/day for the site, $0.02/day for as many mySQL databases as you want).</li>
<li><a href="http://example72.nfshost.com/versions.php">Lots of languages</a> besides PHP. Maybe I will learn <a href="http://xkcd.com/353/">Python</a> someday.</li>
<li>Up-to-date versions of the tools, like PHP 5.3 (<a href="http://us.php.net/manual/en/functions.anonymous.php">anonymous functions</a>!) and Apache 2.2 (<a href="http://httpd.apache.org/docs/2.3/mod/mod_dir.html#fallbackresource">FallbackResource</a>!). 1and1 was still stuck on the old versions.</li>
<li>They enable <a href="http://us.php.net/manual/en/features.remote-files.php">file URL wrappers</a>, so I can use get_file_contents('http://example.com') and the like, rather than cURL.</li>
<li>A general feel that these guys are <a href="http://faq.nearlyfreespeech.net/full/easy#easy">more hacker-oriented rather than simple, one-click-install-package oriented</a>.</li>
</ol>
<p>But there are disadvantages as well.</p>
<ol>
<li>It's not nearly as cheap as it seems. A simple site as above is $0.03/day = $10.95/year, but it's all pay for what you use. They charge for server usage based on storage, $0.01 per megabyte-month, so my 100 megabytes of fonts and images is another $12/year. Domain name registration is $9.49/year, plus $0.41/year for DNS. Bandwidth is $0.20 to $1.00/gigabyte, depending on usage. For my relatively popular blog (about 300-600 visits/day) I'm sending about 100 MB/day or $0.07/day or $25.55/year. That adds up to $58.40/year, basically the same price as 1and1. And I feel like I have to watch every byte stored or transferred. I know it's not really much money, but it's an avocation rather than a vocation so I need to justify the cost to myself. The bandwidth I'm going to have to explore to find out what's going on and possibly stop hotlinking and the like. The storage costs I can hack around by putting the big stuff on <a href="http://aws.amazon.com/s3/pricing/">Amazon S3</a> at only $0.14/gigabyte.</li>
<li>They don't automatically take the money from a credit card. You have to deposit funds in your account, and if it runs dry, the site goes down. They'll send out low-balance warnings but I'd like an automated method. Alos, every payment comes with a $1.00 deposit fee, so making monthly payments is another $12/year.</li>
<li>PHP runs in <a href="http://us.php.net/manual/en/features.safe-mode.php">safe mode</a>, so a lot of file and system functions are disabled. This is deprecated in the latest PHP, so I don't know what NearlyFreeSpeech will do, but right now it makes life much more difficult. There are workarounds, notably using <a href="/blog/2012/01/12/writing-cgi-scripts/" title="Writing CGI scripts">CGI scripts</a> rather than straight PHP, but this is the biggest downside.</li>
<li><a href="https://www.nearlyfreespeech.net/about/faq?&#038;keywords=cron&#038;form=1#WontGo">No cron jobs</a>. You can't run any processes on their servers besides the webserver, mySQL and whatever you're doing when SSH'ing in. <a href="http://faq.nearlyfreespeech.net/section/programming/cron#cron">They promise they're working on a solution</a>, but no update since 2008. You can use hacks like <a href="http://codex.wordpress.org/Category:WP-Cron_Functions">WP-cron</a> but that is exactly that, a hack.</li>
</ol>
<p>But overall, it looks like a good thing and I'm looking forward to (slowly, as I have the time) moving all of bililite.com over to the new host (then transferring the domain when I'm sure it works).</p>]]></content:encoded>
			<wfw:commentRss>http://bililite.nfshost.com/blog/2012/02/03/nearly-free-speech/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>New Amazon Advertising API</title>
		<link>http://bililite.nfshost.com/blog/2012/02/01/newamzonapi/</link>
		<comments>http://bililite.nfshost.com/blog/2012/02/01/newamzonapi/#comments</comments>
		<pubDate>Wed, 01 Feb 2012 14:48:06 +0000</pubDate>
		<dc:creator>Danny</dc:creator>
				<category><![CDATA[Web Design]]></category>

		<guid isPermaLink="false">http://bililite.nfshost.com/blog/?p=2225</guid>
		<description><![CDATA[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 [...]]]></description>
			<content:encoded><![CDATA[<p>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 <em>them</em> money. Anything that doesn't serve that purpose is eliminated, however nice it might be for me. Their right, even if they're <em>not</em> right. I've already taken into account <a href="/blog/2011/11/01/the-new-amazon-advertising-api/" title="The New Amazon Advertising API">the major changes</a>. The only thing is to change the API version to the most recent; in the <a href="http://mierendo.com/software/aws_signed_query/">aws_signed_request</a> routine, change the <code class="language-html">$params["Version"]</code> line to read <code class="language-html">$params["Version"] = "2011-08-01";</code> and that should be it.</p> ]]></content:encoded>
			<wfw:commentRss>http://bililite.nfshost.com/blog/2012/02/01/newamzonapi/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Writing CGI scripts</title>
		<link>http://bililite.nfshost.com/blog/2012/01/12/writing-cgi-scripts/</link>
		<comments>http://bililite.nfshost.com/blog/2012/01/12/writing-cgi-scripts/#comments</comments>
		<pubDate>Thu, 12 Jan 2012 18:28:41 +0000</pubDate>
		<dc:creator>Danny</dc:creator>
				<category><![CDATA[Web Design]]></category>

		<guid isPermaLink="false">http://bililite.nfshost.com/blog/?p=2206</guid>
		<description><![CDATA[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 [...]]]></description>
			<content:encoded><![CDATA[<p>nearlyfreespeech runs with <a href="http://www.php.net/manual/en/features.safe-mode.php">safe mode</a> and <a href="http://www.php.net/manual/en/ini.sect.safe-mode.php#ini.safe-mode-gid">safe_mode_gid</a> 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 <a href="http://en.wikipedia.org/wiki/Common_Gateway_Interface">CGI</a> scripts, in any of a <a href="http://example72.nfshost.com/versions.php">number of languages</a>. You just have to treat it as a shell script, with a <a href="http://en.wikipedia.org/wiki/Shebang_(Unix)">shebang</a> notation of the proper language. There are a few caveats that took me a day to find out:</p>
<ol><li>The correct program is <code>/usr/local/bin/php-cgi</code> (or whereever your server puts PHP), not <code>/usr/local/bin/php</code>. That is the command-line interpreter, which does not output the headers. You can manually do <code class="language-php">echo "Content-type: text/html\n"</code> etc., followed by a blank line, but who wants to try to debug that?</li>
<li>The script has to be executable. You get a 500 Server Error otherwise. Do a <code>chmod +x scriptname.cgi</code>.</li>
<li>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. </li>
</ol>
<p>So a sample CGI script would be:</p>
<pre><code class="language-html">#!/usr/local/bin/php-cgi
&lt;?php echo "Hello, world"; ?&gt;</code></pre>
<p>in an executable file with a <code>.cgi</code> extension.</p>
<p>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 <a href="http://notepad-plus-plus.org/">Notepad++</a>), you need to <code>chmod 477 scriptname.cgi</code> again.]]></content:encoded>
			<wfw:commentRss>http://bililite.nfshost.com/blog/2012/01/12/writing-cgi-scripts/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Things I Learned About .htaccess, part 2</title>
		<link>http://bililite.nfshost.com/blog/2012/01/12/things-i-learned-about-htaccess-part-2/</link>
		<comments>http://bililite.nfshost.com/blog/2012/01/12/things-i-learned-about-htaccess-part-2/#comments</comments>
		<pubDate>Thu, 12 Jan 2012 16:29:48 +0000</pubDate>
		<dc:creator>Danny</dc:creator>
				<category><![CDATA[Web Design]]></category>

		<guid isPermaLink="false">http://bililite.nfshost.com/blog/?p=2200</guid>
		<description><![CDATA[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 ^([^/\.]+)/$ [...]]]></description>
			<content:encoded><![CDATA[<p>I've <a href="/blog/2009/07/27/things-i-learned-about-htaccess/">learned some things about .htaccess</a>, 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.</p>
<p>Don't do:</p>
<pre><code>RewriteEngine On
RewriteBase /
RewriteRule ^([^/\.]+)/$ /index.php?main=$1&#038;part=$1 [QSA,L]
RewriteRule ^([^/\.]+)/([^/\.]+)$ /index.php?main=$1&#038;part=$2 [QSA,L]</code></pre>
<p>to map your site to your index page; do:</p>
<pre><code><a href="https://httpd.apache.org/docs/2.3/mod/mod_dir.html#fallbackresource">FallbackResource</a> /index.php</code></pre>
<p>And in <code>index.php</code> parse the <code class="language-php">$_SERVER['REQUEST_URI']</code> to get your page/subpage arguments. Much easier to debug!</p>
<p>And to use <code>FallbackResource</code> you need to be running Apache 2.2, so get a good web host.</p>]]></content:encoded>
			<wfw:commentRss>http://bililite.nfshost.com/blog/2012/01/12/things-i-learned-about-htaccess-part-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>A New Twist in Comment Spam</title>
		<link>http://bililite.nfshost.com/blog/2011/07/08/a-new-twist-in-comment-spam/</link>
		<comments>http://bililite.nfshost.com/blog/2011/07/08/a-new-twist-in-comment-spam/#comments</comments>
		<pubDate>Fri, 08 Jul 2011 19:46:25 +0000</pubDate>
		<dc:creator>Danny</dc:creator>
				<category><![CDATA[Web Design]]></category>

		<guid isPermaLink="false">http://bililite.nfshost.com/blog/?p=1829</guid>
		<description><![CDATA[I get lots of comment spam, either obvious ads for dubious products or boilerplate praise in almost understandable Engfish ("This is the most coherent soliloquy on this germane topic in the recent memory") with links to the ads. But I'd never seen comment spam that went out of its way to insult me: The subsequent [...]]]></description>
			<content:encoded><![CDATA[<p>I get lots of comment spam, either obvious ads for dubious products or boilerplate praise in almost understandable <a href="http://www.kristisiegel.com/CXC/engfish2.htm">Engfish</a> ("This is the most coherent soliloquy on this germane topic in the recent memory") with links to the ads. But I'd never seen comment spam that went out of its way to insult me:</p>
<blockquote>The subsequent time I learn a blog, I hope that it doesnt disappoint me as much as this one. I imply, I know it was my choice to read, however I truly thought youd have one thing attention-grabbing to say. All I hear is a bunch of whining about one thing that you would repair in the event you werent too busy searching for attention.<em>[links to some construction contractor removed]</em></blockquote>
<p>I guess they know how humble I am and that I'd take criticism more seriously than praise.</p>
<p>But it's still painful to read something negative and have to take the time to parse it and realize it doesn't say anything at all, so I hope it doesn't become a trend. Thank goodness for Akismet!</p>
]]></content:encoded>
			<wfw:commentRss>http://bililite.nfshost.com/blog/2011/07/08/a-new-twist-in-comment-spam/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Trying to Search with Bing and Failing</title>
		<link>http://bililite.nfshost.com/blog/2011/02/24/trying-to-search-with-bing-and-failing/</link>
		<comments>http://bililite.nfshost.com/blog/2011/02/24/trying-to-search-with-bing-and-failing/#comments</comments>
		<pubDate>Thu, 24 Feb 2011 18:21:04 +0000</pubDate>
		<dc:creator>Danny</dc:creator>
				<category><![CDATA[Web Design]]></category>

		<guid isPermaLink="false">http://bililite.nfshost.com/blog/?p=1614</guid>
		<description><![CDATA[I'm not a fan of having to create a Google Custom Search Engine to limit searches to one site with an HTML form (which seems to be necessary for mobile sites), so let's play with Bing: &#60;form method="get" action="http://www.bing.com/search" &#62; &#60;input name="q" type="text"/&#62; &#60;input type="submit" value="Search with Bing"/&#62; &#60;input name="q1" value="site:http://bililite.nfshost.com/blog" type="hidden"/&#62; &#60;/form&#62; And it [...]]]></description>
			<content:encoded><![CDATA[<p>I'm not a fan of having to create a Google Custom Search Engine to limit searches to one site with an HTML form (which seems to <a href="/blog/2011/02/23/back-to-a-simple-google-search-form/">be necessary for mobile sites</a>), so let's play with Bing:</p>
<pre><code class="language-html demo">
&lt;form method="get" action="http://www.bing.com/search" &gt;
	&lt;input name="q" type="text"/&gt;
	&lt;input type="submit" value="Search with Bing"/&gt;
	&lt;input name="q1" value="site:http://bililite.nfshost.com/blog" type="hidden"/&gt;
&lt;/form&gt;</code></pre>
<p>And it works, but fails again on the mobile site, just like Google. This may get better as the search engines figure it out, but I'm going to have to write my own search engine portal. What a pain.</p>
<p>Actually, not such a pain. Something like:</p>
<pre><code class="language-php">header('Location: http://www.bing.com/search?q='.urlencode($_GET['q']).'+'.urlencode($_GET['q1']))</code></pre>
<p>for Bing and</p>
<pre><code class="language-php">header('Location: http://www.google.com/search?q='.urlencode($_GET['q']).'+site:'.urlencode($_GET['sitesearch']))</code></pre>
<p>for Google. And that's what I did for the <a href="http://youngisrael-stl.org">Young Israel site</a></p>


]]></content:encoded>
			<wfw:commentRss>http://bililite.nfshost.com/blog/2011/02/24/trying-to-search-with-bing-and-failing/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Back to a Simple Google Search Form</title>
		<link>http://bililite.nfshost.com/blog/2011/02/23/back-to-a-simple-google-search-form/</link>
		<comments>http://bililite.nfshost.com/blog/2011/02/23/back-to-a-simple-google-search-form/#comments</comments>
		<pubDate>Wed, 23 Feb 2011 23:20:41 +0000</pubDate>
		<dc:creator>Danny</dc:creator>
				<category><![CDATA[Web Design]]></category>

		<guid isPermaLink="false">http://bililite.nfshost.com/blog/?p=1607</guid>
		<description><![CDATA[Earlier, I noted that the old, simple Google search: &#60;form method="get" action="http://www.google.com/search" &#62; &#60;input name="q" type="text"/&#62; &#60;input type="submit" value="Search with Google"/&#62; &#60;input name="sitesearch" value="http://bililite.nfshost.com/blog" type="hidden"/&#62; &#60;/form&#62; is deprecated and doesn't work from Google's mobile site. Google does have an API for custom searches that has all sorts of fancy parameters to manipulate, but it requires [...]]]></description>
			<content:encoded><![CDATA[<p><a href="/blog/2011/02/23/a-google-bug/">Earlier</a>, I noted that the old, simple Google search:</p>
<pre><code class="language-html demo">
&lt;form method="get" action="http://www.google.com/search" &gt;
	&lt;input name="q" type="text"/&gt;
	&lt;input type="submit" value="Search with Google"/&gt;
	&lt;input name="sitesearch" value="http://bililite.nfshost.com/blog" type="hidden"/&gt;
&lt;/form&gt;</code></pre>
<p>is deprecated and doesn't work from Google's mobile site. Google does have an API for custom searches that has all sorts of fancy parameters to manipulate, but it requires signing up for a key and constructing the search ahead of time. However, some experimentation shows that <em>not</em> including a key brings back the old, simple search query (with a new URL and <a href="http://www.google.com/cse/docs/resultsxml.html">some new parameters</a>), so we're back in business:</p>
<pre><code class="language-html demo">
&lt;form method="get" action="http://www.google.com/cse" &gt;
	&lt;input name="q" type="text"/&gt;
	&lt;input type="submit" value="Search with Google"/&gt;
	&lt;input name="as_sitesearch" value="http://bililite.nfshost.com/blog" type="hidden"/&gt;
&lt;/form&gt;</code></pre>
<p>And there's all sorts of interesting things hidden in there, like sorting by date (Google tries to <a href="https://code.google.com/apis/customsearch/docs/structured_data.html#page_dates">guess that, but doesn't do so well</a>):</p>
<pre><code class="language-html demo">
&lt;form method="get" action="http://www.google.com/cse" &gt;
	&lt;input name="q" type="text"/&gt;
	&lt;input type="submit" value="Search with Google"/&gt;
	&lt;input name="as_sitesearch" value="http://bililite.nfshost.com/blog" type="hidden"/&gt;
	&lt;input name="sort" value="date:d" type="hidden"/&gt;
&lt;/form&gt;</code></pre>
<p>And the entire Web Search API is <a href="http://code.google.com/apis/websearch/docs/">deprecated</a>, so I'll have to change the googleSearch widget to use the <a href="https://code.google.com/apis/customsearch/v1/overview.html">JSON Custom Search API</a> eventually.</p>
<p>Overall, sweet. At least until Google changes things again.</p>
<p>Addendum: it looks as though the mobile site <em>does</em> require the <code>cx</code> parameter to limit the search to a specific site. Oh, well. It's not that hard to create a custom search.</p>]]></content:encoded>
			<wfw:commentRss>http://bililite.nfshost.com/blog/2011/02/23/back-to-a-simple-google-search-form/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

