|
By Clay Loveless, Chief Architect, Mashery.
Introduction to Amazon Web Services
While Amazon Web Services (AWS) may seem like an unusual service for an online
retailer, AWS is actually a natural progression for a company as seasoned at providing
rock-solid internet applications as Amazon. Over the years, Amazon has cultivated a tremendous
amount of knowledge around what it takes to build and maintain a successful, highly scalable
web application. Fortunately for the rest of us, they've made this knowledge available to all
developers via AWS.
Since the first services were launched in 2004, AWS has expanded to offer most (if not all)
of the tools necessary for building "web-scale" applications -- applications uninhibited by
concerns over growth and demand. The core components for any highly scalable web offering are
here, including:
- Amazon Simple Storage Service (Amazon S3): Amazon S3 offers unlimited storage capacity for just
pennies per gigabyte per month. Files may be uploaded and downloaded directly by your
end-users, or you may access data stored on Amazon S3 via your application alone. Bandwidth rates
are inexpensive, so files with high volume access can be served from Amazon S3 without breaking the
bank.
- Amazon Elastic Compute Cloud (EC2): On-demand servers, available in three sizes, seven configurations, with the
operating system of your choice. Need an extra box to handle a load spike? New EC2 boxes
typically spin up within a few minutes, and you can bring up multiple servers in parallel.
If you need 20 new servers right now (really), EC2 is for you.
- Amazon Simple DB (SDB): Are
you sure you need all the bells and whistles a traditional relational database management
system (RDBMS) like MySQL? If you're building your application from scratch, simple,
highly-available key-value pair lookups may be all you need. Think outside the box and
consider SDB -- designed to withstand significant system failure without missing a beat. If
your data must be accessible even if the power is out in your data center, SDB was
built with you in mind.
- Amazon Simple Queue Service (SQS): A reliable message queue designed to act as the neural hub for a group of
servers cranking away on intensive tasks, or just as a central bucket for stuff that needs
to get done by someone, sometime soon.
And that's just the Infrastructure Services collection of AWS. The AWS offerings also cover
billing and payment APIs (Amazon Flexible
Payments Service), an on-demand workforce (Amazon Mechanical Turk), web search and information (Alexa), and even an ecommerce
fulfillment service (Amazon Fulfillment
Web Service). Entire commerce-based services can be built on top of Amazon's original
web service, Amazon Associates Web
Service (formerly known as Amazon E-Commerce Service, or ECS).
Clearly the AWS infrastructure is a robust solution to build a business on top of. Services
continue to roll out of AWS at a steady clip. Amazon isn't just dabbling in web services "on
the side" -- in fact, Amazon Web Services recently
surpassed the global Amazon.com retail operation in terms of bandwidth use. The root of
Amazon Web Services was actually based on the idea of internal development groups within
Amazon leveraging the capabilities of these services to be more efficient in continuing the
growth of the Amazon retail operation. As such, AWS is here to stay.
Undifferentiated Heavy Lifting
So Amazon will be in the web services business for the foreseeable future. Now that we've
established that learning more about AWS is not a risky investment of time, let's focus on
Amazon's Infrastructure Services -- EC2, Amazon S3, SQS, SDB and EBS -- that make up the core of what
some within AWS refer to as "undifferentiated heavy lifting."
All of these services are available via both REST
and SOAP interfaces. (We'll
touch on both in this article, but we'll concentrate on the REST and REST-like Query
approaches. ) A growing number of language specific toolkits are available for these services,
so you can feel comfortable basing an architecture on AWS given its language-agnostic
foundation.
The knowledge that you can ramp up 200 servers in five minutes if you need them may be a
liberating experience for you -- in the days before AWS, system architects had to worry about
handling rapid scale with "hot spares" and other expensive on-hand capacity that often sat
idle until the day of Big Traffic. With AWS at your back, the only requirement is to build
your system so that it can leverage the near-instant capacity that is at your disposal.
As we dive into some code, that's the big thing to keep in mind: Using Amazon Web Services
requires a slight but important change in perspective. Skeptics of cloud computing will tell
you "EC2 servers can fail on you at any time, and you'll lose all your data!" While it's true
that EC2 instances are virtual private servers that have some risk associated with them,
there's an inherent benefit to building an architecture on a transient system: it forces you
to think about flexibility, backups, and adaptability early on in the development cycle. If
you wait until you must grow to think about how you're going to grow, or migrate your
system from one machine to another, you haven't done yourself any favors. Building on AWS puts
building for resilience in the face of system failure at the top of your priority list, where
it should be anyway.
Check Your System
The examples in this article and the libraries they use require PHP 5.1.2+. To check your
system for all the requirements assumed by the examples, please run this script available here: http://s3.killersoft.com/AWSforPHP/compatibility.php. You can run it
without setting it up locally, simply by doing this from your terminal:
curl --silent http://s3.killersoft.com/AWSforPHP/compatibility.php | php
If you see "OK! This system is compatible with the examples.", you're good to go. If you
don't see that message, keep reading, as we'll step briefly through setting up an EC2 instance
that can run these examples as well.
Getting Started
Before we go any further, start by registering for an AWS account. All of the services we'll
be discussing require an Access Key ID and a Secret Access Key from AWS. If you don't have
these keys, you can get them quickly by visiting aws.amazon.com and following the quick and easy sign-up process. Once you get your AWS credentials, put them in a safe place. Where that will ultimately be
depends on your security preferences, but I recommend a centrally-located file that is
accessible only by web applications and privileged users.
Download the files covered in this article here: http://s3.killersoft.com/AWSforPHP/awsfiles.zip.
For our examples, we'll assume that you've placed your credentials in /etc/aws.conf, in standard INI file format. For example:
; AWS credentials
; Account ID
account_id = "1234567890"
; Access Key Id
access_key = "06224BHAZ75910F2"
; Secret Access Key
secret_key = "aIfbA2568+12TEqLDYpiqOyRULvi9"
Please take care to set appropriate permissions on this file to limit access to it. Keep these credentials
safe.
Making the Connection
There are several libraries available for AWS, but if you're at all like me, you want to know
more about what's going on under the hood. When beginning work with any new web services API,
my first concern is always "how do I connect?" The answer is different depending on the
library you're using, so let's look at a bare-bones connection to the EC2 API, using nothing
more than a stock PHP 5 installation.
What you need to know about connecting to AWS APIs using REST or REST-like methods:
- Every request must be authenticated.
- Authentication is via RFC 2104-compliant HMAC-SHA1 or HMAC-SHA256 signature. (Signature Version 2)
- Signature is generated using a request-specific string and your AWS Secret Access Key.
With these truths in mind, here's a simple request to the EC2 Query API to check on the
status of three EC2 Availability Zones. EC2 Availability Zones offer the ability for EC2
customers to place their EC2 instances in distinct fault-tolerance zones. While the zones have
the same name for all customers, their actual distriubution is determined per-customer so that
no one zone becomes over-used relative to the others.
File: avail-zones.php
<?php
// load in credentials
$creds = parse_ini_file('/etc/aws.conf');
// Define query string keys/values
$params = array(
'Action' => 'DescribeAvailabilityZones',
'AWSAccessKeyId' => $creds['access_key'],
'Timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
'Version' => '2008-05-05',
'ZoneName.0' => 'us-east-1a',
'ZoneName.1' => 'us-east-1b',
'ZoneName.2' => 'us-east-1c',
'SignatureVersion' => 2,
'SignatureMethod' => 'HmacSHA256'
);
// See docs
// http://tr.im/jbjd
uksort($params, 'strnatcmp');
$qstr = '';
foreach ($params as $key => $val) {
$qstr .= "&{$key}=".rawurlencode($val);
}
$qstr = substr($qstr, 1);
// Signature Version 2
$str = "GET\n"
. "ec2.amazonaws.com\n"
. "/\n"
. $qstr;
// Generate base64-encoded RFC 2104-compliant HMAC-SHA256
// signature with Secret Key using PHP 5's native
// hash_hmac function.
$params['Signature'] = base64_encode(
hash_hmac('sha256', $str, $creds['secret_key'], true)
);
// simple GET request to EC2 Query API with regular URL
// encoded query string
$req = 'https://ec2.amazonaws.com/?' . http_build_query(
$params
);
$result = file_get_contents($req);
// do something with the XML response
echo $result;
From your workstation:
PROMPT> cd ~/awsfiles
php avail-zones.php
That's it! Armed with that approach and details of the specific parameters needed by each AWS
API, you're now able to roll your own ad-hoc AWS script to use the REST or REST-like Query
APIs.
Just to reiterate: Once you have generated the string to feed the signature along with your
private key, the following code is all you need with PHP 5.1.2+ to generate an actual SHA1
signature.
<?php
// fast, native code for signatures in PHP 5.1.2+
$the_signature = base64_encode(
hash_hmac('sha1', $the_string, $your_secret_key, true)
);
?>
There's an awful lot of code floating around in various libraries and examples that is much more verbose. If you're using PHP 5.1.2 or later, the hash_hmac() function is enabled by default, and the fourth
parameter returns the binary output that AWS wants in base64 encoded form. If you're not using
PHP 5.1.2, please consider upgrading or just moving onto an EC2 instance where you can control
your runtime environment.
Making the Connection with SOAP
Is SOAP more your style? Since PHP 5 was released in 2004, a native SOAP extension has been bundled. However,
the SOAP extension is not enabled by default. Fortunately, many PHP packagers enable it, or
provide a separate package via yum, apt-get and the like to install it. If you're building PHP
from source, just make sure to include --enable-soap in your configure statement. A useful PHP
SOAP tutorial is available at the Zend Developer Zone.
AWS SOAP requests follow current WS-Security standards, which require that each SOAP request
message be hashed and signed for integrity. All Amazon Web Services secure SOAP messages using
the WS-Security BinarySecurityToken profile, which consists of an X.509 certificate and an RSA
private key. You may download both of those from the AWS Access Identifiers section of your
AWS account. I recommend that once you download those files, put them in a safe place and add
the paths to your /etc/aws.conf file, like so:
; X.509 Certificate Path
cert_file = "/path/to/my/cert-....pem"
; RSA private key
private_key_file = "/path/to/my/pk-....pem"
In order to make a compliant SOAP request, we need a WS-Security library in addition to the
SOAP extension. I've bundled an excellent open source library called xmlseclibs by
Rob Richards
along with an extension to PHP's SOAP client in a file that provides AWSSoapClient. See avail-zones-soap.php for a SOAP example of the DescribeAvailabilityZones request we made above.
Note: The AWSSoapClient includes xmlseclibs, but xmlseclibs relies on
the mcrypt PHP extension. As with the
SOAP extension, the mcrypt extension is widely available and easy to install.
Why even bother with SOAP? As you make your way through the AWS documentation, you may find
that certain features are initially only available via the SOAP API. For example, the ec2-describe-instances command-line tool does not provide
detailed information on why an instance may have terminated immediately after launch. However,
the Technical FAQ in the documentation states that detailed information may be obtained from the SOAP response.
So, sometimes it helps to be able to access AWS via SOAP from PHP. Now you can!
However, since there is some additional overhead in using SOAP (messages on the wire are
larger, additional extensions must be loaded into PHP, etc), let's look at what we can do with
the wrappers around the REST and Query APIs that AWS offers.
Beyond the Connection: Libraries Galore
We PHP developers are not well known for being quick to standardize on one particular library
to get a particular job done. We tend to tinker, and re-chisel the wheel a few times before
settling on dominant library for a particular task.
That means a few things related to AWS:
- there are several well-done PHP libraries for AWS to choose from.
- there is no comprehensive library for AWS Infrastructure Services in PHP like there is in
Ruby and Python.
So you have choices, and you have opportunity (you could write the dominant library,
as the field is fairly wide open). Some of the choices you may wish to consider are:
You should spend some time exploring these libraries to determine the one that's right for you and your needs.
For the remainder of this article, however, we'll concentrate on the Amazon
libraries for EC2, SDB and SQS, and where Amazon S3 is needed, on the Killersoft
S3 stream wrapper.
A Basic Example
To illustrate the use of AWS services, we'll build a simple video database with the following
characteristics:
- Hosted on EC2
- Uses Amazon S3 as the primary data store.
- Uses SQS and a separate EC2 server to manage batch encoding of video for Flash display.
- Uses SimpleDB to store metadata about the videos.
While there are a number of ways to get all these services running, including very convenient
management interfaces in Firefox (such as Elasticfox),
plus web-based management tools from RightScale and Scalr, we'll do it all with PHP and the
API, since that's what this article is about.
To keep the examples as focused as possible, from here on they will make use of an inclusion called example_setup.php,
which includes and parses your aws.conf file, configures PHP 5's autoload functionality, and a few other things to keep example code
concise and accurate. The example_setup.php file looks like this:
File: example_setup.php
<?php
// keep ourselves honest
error_reporting(E_ALL);
ini_set('display_errors', 'on');
ini_set('log_errors', 'on');
ini_set('error_log', '/tmp/php.log');
// use the config that we set up previously
$creds = parse_ini_file('/etc/aws.conf');
if (! isset($creds['tutorial_file_path'])) {
$creds['tutorial_file_path'] = sys_get_temp_dir();
}
// set include_path for article's bundled AWS libraries
ini_set('include_path', dirname(__FILE__)
. DIRECTORY_SEPARATOR
. 'AWSforPHP'
. PATH_SEPARATOR
. ini_get('include_path'));
// make loading simple
function __autoload($class_name)
{
if (class_exists($class_name, false) ||
interface_exists($class_name, false)) {
return;
}
// AWS and Killersoft classes follow PEAR
// file naming conventions
$file = str_replace('_',
DIRECTORY_SEPARATOR,
$class_name
) . '.php';
@include_once $file;
}
Finally, we'll be generating an SSH keypair specific to this tutorial, so please add a path to your
config file that specifies where those files should be stored once generated. Like so:
; Path to writable directory where we can save files
tutorial_file_path = "/tmp"
Start Your Server
We need a place to host the scripts associated with this video library, so let's start by
getting a server running with the latest Amazon-provided Fedora Core Linux image.
At the time of this writing, that's Fedora Core 8 and has an AMI ID of ami-2b5fba42.
Before launching an instance, let's briefly explore how to set up EC2 Security Groups and
generate EC2 KeyPairs. In a nutshell, EC2 Security Groups are firewall rules that control
what ports and serivces may be accessed on any server running under the specified group.
The full details are outside the scope of this article, but there is an excellent
exploration of difference Security Group scenarios and use cases in the
EC2 Developer Guide.
We'll also set up an EC2 KeyPair, which is simply an SSH keypair that you'll need in order to
access the EC2 server once the instance is launched. The public key will be stored on the
instance when you launch it, and you will then be able to login via SSH using the
private key.
The following block of code will:
- Make sure that the 'awstutorial' KeyPair exists, and will create
it if it does not exist. The private key will be stored as
'id_rsa-awstutorial' in the 'tutorial_file_path' directory you
specified in your aws.conf file above.
- Make sure that the 'awstutorial' Security Group exists, and will
create it if it does not exist.
- Make sure that ports 80 and 22 are open in the 'awstutorial'
Security Group.
Note: Careful tuning of EC2 Security Group settings is beyond the
scope of this article. Please read the documentation on that subject carefully.
File: ec2-prelaunch.php
<?php
/**
* Script to check port 80 on the 'awstutorial' security group,
* and open it if it is not open. This will use the 'awstutorial'
* keypair as well, creating it if it does not exist.
*/
require_once 'example_setup.php';
// load the service
$ec2 = new Amazon_EC2_Client(
$creds['access_key'],
$creds['secret_key']
);
/**
*
* Use 'awstutorial' keypair
*
*/
$req = new Amazon_EC2_Model_DescribeKeyPairsRequest();
$kpmatch = false;
// run the request
$resp = $ec2->describeKeyPairs($req);
$pairs = $resp->getDescribeKeyPairsResult()->getKeyPair();
foreach ($pairs as $pair) {
if ($pair->getKeyName() == 'awstutorial') {
echo "KeyPair awstutorial exists!\n";
$kpmatch = true;
break;
}
}
// create the keypair if we need to
$keypath = $creds['tutorial_file_path']
. DIRECTORY_SEPARATOR
. 'id_rsa-awstutorial';
if (! $kpmatch) {
echo "Creating awstutorial keypair...\n";
$req = new Amazon_EC2_Model_CreateKeyPairRequest();
$req->withKeyName('awstutorial');
$resp = $ec2->createKeyPair($req);
if ($resp->isSetCreateKeyPairResult()) {
$res = $resp->getCreateKeyPairResult();
$kp = $res->getKeyPair();
echo "generated fingerprint\n";
echo $kp->getKeyFingerprint() . "\n";
echo "generated material\n";
echo $kp->getKeyMaterial() . "\n";
// save private key for future use
@file_put_contents(
$keypath,
$kp->getKeyMaterial()
);
// set permissions appropriately for key
chmod($keypath, 0600);
}
} else {
// emit a notice if key file isn't where we expect it
if (! is_readable($keypath)) {
echo "!! Couldn't read id_rsa-awstutorial file "
. "at $keypath\n";
}
}
/**
*
* Use 'awstutorial' Security Group. Create it if
* necessary.
*
*/
$req = new Amazon_EC2_Model_DescribeSecurityGroupsRequest();
$req->withGroupName('awstutorial');
// run the request
try {
$resp = $ec2->describeSecurityGroups($req);
echo "Security Group awstutorial exists!\n";
} catch (Amazon_EC2_Exception $e) {
// doesn't exist, create it
echo "Creating awstutorial security group ...";
$new = new Amazon_EC2_Model_CreateSecurityGroupRequest();
$new->withGroupName('awstutorial')
->withGroupDescription(
'AWS for PHP Developers Tutorial Group'
);
$ec2->createSecurityGroup($new);
echo "done.\n";
// now fe-fetch response
$resp = $ec2->describeSecurityGroups($req);
}
/**
*
* Make sure 'awstutorial' group has ports 80 and 22 open.
*
*/
if ($resp->isSetDescribeSecurityGroupsResult()) {
$list = $resp->getDescribeSecurityGroupsResult()
->getSecurityGroup();
// look for wide-open port 80
$port80open = false;
$port22open = false;
foreach ($list as $securityGroup) {
if (! $securityGroup->isSetIpPermission()) {
continue;
}
$perms = $securityGroup->getIpPermission();
foreach ($perms as $perm) {
if ($perm->isSetIpProtocol()
&& $perm->getIpProtocol() == 'tcp'
&& $perm->isSetFromPort()
&& $perm->getFromPort() == '80'
&& $perm->isSetToPort()
&& $perm->getToPort() == '80'
) {
$port80open = true;
continue;
}
if ($perm->isSetIpProtocol()
&& $perm->getIpProtocol() == 'tcp'
&& $perm->isSetFromPort()
&& $perm->getFromPort() == '22'
&& $perm->isSetToPort()
&& $perm->getToPort() == '22'
) {
$port22open = true;
continue;
}
}
}
}
if ($port80open) {
echo "Security Group awstutorial port 80 is open!\n";
} else {
// open port 80
echo "Opening awstutorial port 80 ...";
$req = new Amazon_EC2_Model_AuthorizeSecurityGroupIngressRequest();
$req->withGroupName('awstutorial')
->withIpProtocol('tcp')
->withFromPort(80)
->withToPort(80)
->withCidrIp('0.0.0.0/0');
$ec2->authorizeSecurityGroupIngress($req);
echo "done.\n";
}
if ($port22open) {
echo "Security Group awstutorial port 22 is open!\n";
} else {
// open port 22
echo "Opening awstutorial port 22 ...";
$req = new Amazon_EC2_Model_AuthorizeSecurityGroupIngressRequest();
$req->withGroupName('awstutorial')
->withIpProtocol('tcp')
->withFromPort(22)
->withToPort(22)
->withCidrIp('0.0.0.0/0');
$ec2->authorizeSecurityGroupIngress($req);
echo "done.\n";
}
From your workstation:
PROMPT> cd ~/awsfiles
php ec2-prelaunch.php
Now that credentials are created and a known firewall setting is in place,
it's time to launch the server. In the next block of code, we set up the
Amazon_EC2_Client object provided by the Amazon EC2 PHP library, launch a small
Fedora Core instance, and wait for it to complete the initial boot process so
that we can retrieve its public hostname.
File: ec2-launch.php
<?php
/**
* Script to launch a small EC2 instance with Amazon's
* Fedora Core 8 AMI.
*
* The script will loop during the launch, waiting
* for the instance to obtain a public DNS name.
*/
require_once 'example_setup.php';
// load the service
$ec2 = new Amazon_EC2_Client(
$creds['access_key'],
$creds['secret_key']
);
// set up the request with nice fluent interface
$req = new Amazon_EC2_Model_RunInstancesRequest();
// ami-2b5fba42 is Amazon's Fedora Core 8 image
$req->setImageId('ami-2b5fba42')
->withMinCount(1)
->withMaxCount(1)
->withKeyName('awstutorial')
->withSecurityGroup('awstutorial');
// run the request
echo "Launching awstutorial instance.\n";
$response = $ec2->runInstances($req);
// this can take a few minutes
$start = microtime(true);
set_time_limit(0);
// get the pending instance id
$reservation = $response->getRunInstancesResult()
->getReservation();
$instances = $reservation->getRunningInstance();
$runningInstance = $instances[0];
// output instance id and state
$id = $runningInstance->getInstanceId();
echo $id
. ' is '
. $runningInstance->getInstanceState()
->getName()."\n";
// save example instance id
@file_put_contents(
$creds['tutorial_file_path']
. DIRECTORY_SEPARATOR
. 'awstutorial-instance.txt',
$id
);
// set up DescribeInstances request to fetch status on new
// instance in a loop.
$desc = new Amazon_EC2_Model_DescribeInstancesRequest();
$desc->setInstanceId($id);
echo 'Waiting for public hostname';
while (1) {
// output progress
echo '.';
$response = $ec2->describeInstances($desc);
$res = $response->getDescribeInstancesResult()
->getReservation();
$ri = $res[0]->getRunningInstance();
$runningInstance = $ri[0];
if ($runningInstance->isSetPublicDnsName()) {
$end = microtime(true);
break;
}
sleep(2);
}
echo "\n";
// now we're ready to set up our server
echo $id
. ' is '
. $runningInstance->getInstanceState()->getName()
. ' at '
. $runningInstance->getPublicDnsName()
. "\n";
// save hostname for later
@file_put_contents(
$creds['tutorial_file_path']
. DIRECTORY_SEPARATOR
. 'awstutorial-hostname.txt',
$runningInstance->getPublicDnsName()
);
$span = $end - $start;
echo "Instance booted in {$span} seconds.\n";
exit;
From your workstation:
PROMPT> cd ~/awsfiles
php ec2-launch.php
Note: Establishing a public DNS name does not necessarily mean that the server is
ready to accept SSH logins. It may take a minute or two after running the launch example
before you can log in to your instance. Do not panic, this is normal.
As you can see from these examples, the Amazon_EC2_Model class and its
descendants fully encapsulate all the details of making the requests and
parsing the responses from the API.
This concludes the majority of this article's use of the EC2 API. The
Amazon-provided EC2 library is robust and well-documented. Be sure
to check out its
reference documentation.
Setting Up the Server
Now that we have an EC2 instance available, it's time to set up PHP and Apache so we can serve the video library interface.
Start by logging in to the instance, using SSH key, instance id and hostname that we acquired in the previous section of the tutorial.
If you need introductory assistance on how to log in to an instance using SSH, please refer to the
EC2 Getting Started Guide.
If you want to use the data we saved in the previous section to login, set these shell variables (using the appropriate path, of course).
From your workstation:
# assumes current working directory - change path as needed
PROMPT> export AWSTUTORIAL_HOST=`cat \`pwd\`/awstutorial-hostname.txt`
PROMPT> export AWSTUTORIAL_KEY=`pwd`/id_rsa-awstutorial
Now, make sure you'll be able to run tutorial examples from the server by copying the
aws.conf file we've been working with to the new instance.
From your workstation:
PROMPT> scp -i $AWSTUTORIAL_KEY /etc/aws.conf root@$AWSTUTORIAL_HOST:/etc/aws.conf
PROMPT> ssh -i $AWSTUTORIAL_KEY root@$AWSTUTORIAL_HOST \
'curl -O http://s3.killersoft.com/AWSforPHP/awsfiles.zip; \
unzip awsfiles.zip -d /var/www/'
PROMPT> scp -i $AWSTUTORIAL_KEY awstutorial-*.txt root@$AWSTUTORIAL_HOST:/tmp/
With that, you're ready to login:
From your workstation:
PROMPT> ssh -2 -i $AWSTUTORIAL_KEY root@$AWSTUTORIAL_HOST
Once you're in, we need to set up PHP and Apache. In case you're inclined to try out
the SOAP examples mentioned above, we'll install the packages necessary for those as
well as the basic PHP 5 and Apache requirements.
From your instance:
PROMPT> yum -y install php php-mcrypt.i386 php-soap.i386 \
php-xml.i386 php-cli.i386 httpd.i386
The server should be ready to go. To confirm that PHP is properly configured, and Apache is
running, do the following:
From your instance:
PROMPT> echo "<?php phpinfo(); ?>" > /var/www/html/index.php
PROMPT> service httpd start
Now load the hostname of your instance in your browser. You should see the standard phpinfo() output page. If you don't,
please review the steps above again -- it's possible that you missed a step, or that something's gone wrong. However,
if all's right with the world, you'll see phpinfo() output and be ready to move on to the next step.
Building a Video Sharing Site, the AWS Way
There's a blog post out there that gives a brief rundown on building a video sharing site.
The post gives a quick overview of what's necessary, and even provides sample code for some of the steps.
Obviously you can build something simple for your home movies, or something as complex as YouTube.
Yes, you really could build something like the next YouTube on top of Amazon Web Services. All you'd need to do is:
- Automate the conversion of uploaded videos to a web-friendly format.
- Coordinate a heavy workload of automated conversions.
- Create a directory of videos hosted on the site.
- Serve the video files on demand.
Use Amazon Web Services as a basis for this, and you're all set. Briefly, here's how we'll do it:
- Accept video file uploads
- Create a job in Amazon Simple Queue Service for conversion of the video
- Pick the job out of SQS and convert to a web-friendly format
- Create a record in a video database on Amazon SimpleDB
- Serve the video from a robust location: Amazon S3
Ready?
Step 1: Accept Video File Upload
There are a lot of fancy ways to upload files these days, such
as SWFUpload.
We won't be using techniques like that in this article. Instead, we'll
use the super-simple index.php file the awsfiles.zip mentioned previously.
On your EC2 instance, grab the tutorial files ZIP and unzip it in your home directory.
From your instance:
PROMPT> cd /var/www
PROMPT> cp -f awsfiles/index.php html/index.php
PROMPT> mkdir /var/www/html/uploads
PROMPT> chown apache:apache /var/www/html/uploads
Without digging into all the details, the commands above will create an uploads
directory, place a really simple file upload form on your instance homepage,
and put files uploaded through it in the uploads folder.
To test the upload, use this sample QuickTime file, if
you don't have one of your own lying around. This sample is from Apple's XCode QuickTime examples.
Step 2: Queue Conversion Job
In order to use the Amazon SQS service, first make sure you've signed up for it with your AWS account. Then,
use the following code sample to create an 'awstutorial' queue for your video site's conversion jobs.
File: sqs-makequeue.php
<?php
/**
* Script to check for an 'awstutorial' queue, and create
* one if necessary.
*/
require_once 'example_setup.php';
// load the service
$sqs = new Amazon_SQS_Client(
$creds['access_key'],
$creds['secret_key']
);
try {
// get the list of queues
$newqueue = 'awstutorial';
$req = new Amazon_SQS_Model_ListQueuesRequest();
$req->withQueueNamePrefix($newqueue);
$resp = $sqs->listQueues($req);
$list = $resp->getListQueuesResult()
->getQueueUrl();
$exists = false;
foreach ($list as $url) {
if (strpos($url, $newqueue) !== false) {
$exists = true;
break;
}
}
if ($exists) {
echo "Queue $newqueue exists!\n";
} else {
// create it
echo "Creating $newqueue...\n";
$req = new Amazon_SQS_Model_CreateQueueRequest();
$req->withQueueName($newqueue)
->withDefaultVisibilityTimeout(30);
$resp = $sqs->createQueue($req);
if ($resp->isSetCreateQueueResult()) {
$result = $resp->getCreateQueueResult();
if ($result->isSetQueueUrl()) {
echo "Queue {$newqueue} created "
. "at "
. $result->getQueueUrl()
. "\n";
}
} else {
// cope with non-exceptional abject failure
}
}
} catch (Amazon_SQS_Exception $e) {
echo $e->getErrorMessage() . "\n";
echo $e->getXML() . "\n";
exit;
}
As you can see, use of the SQS PHP library is very similar to use of the EC2 library.
From your instance:
PROMPT> cd /var/www/awsfiles
PROMPT> php sqs-makequeue.php
Now that your queue is
set up, we can submit the conversion job related to the upload of the sample QuickTime movie.
File: sqs-queue-conversion.php
<?php
/**
* Script to shuttle an uploaded movie to the 'awstutorial'
* queue so that another process can pick it up and convert
* it.
*/
require_once 'example_setup.php';
// load the service
$sqs = new Amazon_SQS_Client(
$creds['access_key'],
$creds['secret_key']
);
// Put the Uploaded File on S3
Killersoft_Wrapper_S3::selfRegister();
// establish a unique bucket name, based on
// hash of access_key, secret_key, and public
// instance hostname.
$hostfile = $creds['tutorial_file_path']
. DIRECTORY_SEPARATOR
. 'awstutorial-hostname.txt';
if (file_exists($hostfile)) {
$host = file_get_contents($hostfile);
} else {
$host = file_get_contents(
'http://169.254.169.254/latest/meta-data/public-hostname'
);
}
$bucket = 's3://awstutorial-'
. md5(
$creds['access_key'] .
$creds['secret_key'] .
$host
);
if (! is_dir($bucket)) {
mkdir($bucket);
}
// put the uploaded file on S3.
// For your YouTube-killer, you'll want to make sure this
// filename doesn't conflict with other files.
// NOTE: This won't work until it's injected into the
// upload script from Step 1.
file_put_contents(
"{$bucket}/{$upload_name}",
file_get_contents($upload_file)
);
// now that the file to be converted is in place, put
// a message in the queue for another server
// to pick it up
// In a production script, you'd skip this step
// and store the URL to your queue in a config file
$req = new Amazon_SQS_Model_ListQueuesRequest();
$req->withQueueNamePrefix('awstutorial');
$resp = $sqs->listQueues($req);
$list = $resp->getListQueuesResult()
->getQueueUrl();
$queue_url = array_shift($list);
// Send the actual message
$msg = new Amazon_SQS_Model_SendMessageRequest();
$msg->withMessageBody("{$bucket}/{$upload_name}")
->withQueueUrl($queue_url);
$resp = $sqs->sendMessage($msg);
Easy enough; now let's inject this work into the flow of our upload
script from Step 1. That's already been done in the "index2.php" file
of the awsfiles.zip.
From your instance:
PROMPT> cd /var/www
PROMPT> cp -f awsfiles/index2.php htdocs/index.php
Now a successfully uploaded file will be sent off to S3 and queued into
SQS for processing.
Try it out, and then let's peek in S3 and SQS to see if everything happened as it should have. Note: SQS used to have a method
that was actually called "PeekMessage", but that functionality was removed in the 2008-01-01 update. We can still take a peek, though
simply by receiving messages in the 'awstutorial' queue without removing them. Since the Simple Queue Service is a "deliver at least once" system,
the message will stay in the queue until it is deleted, or until is greater than four days old.
File: job-check.php
<?php
/**
* Check to see if sqs-queue-conversion.php tasks
* actually put a file on S3 and a message in SQS.
*/
require_once 'example_setup.php';
// Register S3 wrapper
Killersoft_Wrapper_S3::selfRegister();
// set our bucket name
$hostfile = $creds['tutorial_file_path']
. DIRECTORY_SEPARATOR
. 'awstutorial-hostname.txt';
if (file_exists($hostfile)) {
$host = file_get_contents($hostfile);
} else {
$host = file_get_contents(
'http://169.254.169.254/latest/meta-data/public-hostname'
);
}
$bucket = 's3://awstutorial-'
. md5(
$creds['access_key'] .
$creds['secret_key'] .
$host
);
// change this if you want to check a different file
if (file_exists("{$bucket}/sample.mov")) {
echo "{$bucket}/sample.mov is on S3!\n";
}
// now peek in the queue
$sqs = new Amazon_SQS_Client(
$creds['access_key'],
$creds['secret_key']
);
// In a production script, you'd skip this step
// and store the URL to your queue in a config file
$req = new Amazon_SQS_Model_ListQueuesRequest();
$req->withQueueNamePrefix('awstutorial');
$resp = $sqs->listQueues($req);
$list = $resp->getListQueuesResult()
->getQueueUrl();
$queue_url = array_shift($list);
// UNCOMMENT IF YOU WANT TO MANUALLY INJECT A MESSAGE
// $msg = new Amazon_SQS_Model_SendMessage();
// $msg->withMessageBody("{$bucket}/sample.mov")
// ->withQueueName('awstutorial');
// $resp = $sqs->sendMessage($msg);
// sleep(5);
// first, get the approximate number of messages
// in the queue. Why approximate? See the SQS
// architecture documentation.
$q = new Amazon_SQS_Model_GetQueueAttributesRequest();
$q->withQueueUrl($queue_url)
->withAttributeName('ApproximateNumberOfMessages');
$resp = $sqs->getQueueAttributes($q);
$num = $resp->getGetQueueAttributesResult()
->getAttribute('ApproximateNumberOfMessages');
echo "Queue awstutorial has ~{$num[0]->getValue()} messages.\n";
// now take a look with a minimal VisibilityTimeout,
// so as to not interfere with other processes
// which may access the queue.
$msg = new Amazon_SQS_Model_ReceiveMessageRequest();
$msg->withQueueUrl($queue_url)
->withMaxNumberOfMessages(1)
->withVisibilityTimeout(1);
$resp = $sqs->receiveMessage($msg);
if ($resp->isSetReceiveMessageResult()) {
$msg = $resp->getReceiveMessageResult()
->getMessage();
if (isset($msg[0])) {
echo "Found a message with a body of:\n";
echo $msg[0]->getBody() . "\n";
} else {
echo "Message not fully retrieved:\n";
var_dump($msg);
}
} else {
echo "Couldn't retrieve a message.\n";
}
To check on jobs in your queue, just run the script.
From your instance:
PROMPT> cd /var/www/awsfiles
PROMPT> php job-check.php
Step 3: Perform Conversion Job
Flash-encoded video (FLV) has emerged as the de-facto standard for embedded online video. So, we want to convert our video from
QuickTime (.mov) to Flash (.flv) so that we can embed it in a Flash-based video player.
For our sample video, this isn't a big task. But if you start working through conversions of longer, multi-megabyte videos,
you'll definitely want to divide that job up over multiple machines and automate it.
The details of the conversion process itself are outside the scope of this
article -- but we'll convert a video nonetheless. Just do a web search for the
tools you see used in the conversion process if you want to learn more about them.
They're popular tools that have been written about a great deal.
First: we need a tool called ffmpeg, which will do the conversion. For that,
we'll set up an additional yum repository to pull the necessary packages from.
From your instance:
PROMPT> curl -L -O http://dag.wieers.com/rpm/packages/rpmforge-release/rpmforge-release-0.3.6-1.el5.rf.i386.rpm
PROMPT> rpm -ivh rpmforge-release-0.3.6-1.el5.rf.i386.rpm
PROMPT> yum -y install ffmpeg
You'll see a lot of output during the course of installing ffmpeg and its dependencies. When installation is complete,
you should be able to test a conversion manually. The awsfiles.zip bundle contains a sample.mov to test manual
conversion.
From your instance:
PROMPT> cd /var/www/awsfiles
PROMPT> ffmpeg -i sample.mov -ar 22050 \
-acodec mp3 -ab 32k -r 25 -s 320x240 -vcodec flv \
-qscale 9.5 output.flv
If it works, you're almost in business! (You'll know it worked if there's a file called output.flv in the /var/www/awsfiles directory.)
Now we just need to write a script to fetch the name of the file to convert from SQS, retrieve the file
from S3, and run that conversion on it automatically. If the command did not work for you and you'd like to run this tutorial through every step,
you may need to hit some support forums for ffmpeg usage -- the author of this article is no expert on that topic.
In a high-volume video sharing site, you'd want to run something like the
following on multiple EC2 instances on a cronjob, quite possibly
working on more than one job at a time. You'd also want to tune the
Visibility Timeout settings to be closer to what you actually need to
do an average conversion. The following script is just to illustrate
the main segments of the workflow with SQS and S3.
File: job-fetch.php
<?php
/**
* Check to see if sqs-queue-conversion.php tasks
* actually put a file on S3 and a message in SQS.
*/
require_once 'example_setup.php';
// Register S3 wrapper
Killersoft_Wrapper_S3::selfRegister();
// Fetch a job from SQS
$sqs = new Amazon_SQS_Client(
$creds['access_key'],
$creds['secret_key']
);
// In a production script, you'd skip this step
// and store the URL to your queue in a config file
$req = new Amazon_SQS_Model_ListQueuesRequest();
$req->withQueueNamePrefix('awstutorial');
$resp = $sqs->listQueues($req);
$list = $resp->getListQueuesResult()
->getQueueUrl();
$queue_url = array_shift($list);
// - we know all 'awstutorial' jobs are videos to convert.
// - hide job from others while we're working on it.
$msg = new Amazon_SQS_Model_ReceiveMessageRequest();
$msg->withQueueUrl($queue_url)
->withMaxNumberOfMessages(1)
->withVisibilityTimeout(20);
$resp = $sqs->receiveMessage($msg);
if (!$resp->isSetReceiveMessageResult()) {
echo "Couldn't retrieve a message.\n";
exit;
}
$msg = $resp->getReceiveMessageResult()
->getMessage();
if (!isset($msg[0])) {
echo "Message not fully retrieved:\n";
var_dump($msg);
exit;
}
// Now we've got a job. Let's check to see if the
// file to work on is still present.
$source = $msg[0]->getBody();
if (! file_exists($source)) {
// source is gone, so kill the job and exit
echo "Deleting orphaned job from queue.\n";
$del = new Amazon_SQS_Model_DeleteMessageRequest();
$del->withReceiptHandle(
$msg[0]->getReceiptHandle()
)
->withQueueUrl($queue_url);
$sqs->deleteMessage($del);
exit;
}
// file is present, let's convert it!
$localin = '/tmp/'.basename($source);
file_put_contents(
$localin,
file_get_contents($source)
);
$localout = substr($localin, 0, -3) . 'flv';
// ffmpeg command for transformation
$cmd = '/usr/bin/ffmpeg -i '
. escapeshellcmd($localin)
. ' -ar 22050 '
. '-acodec mp3 -ab 32k -r 25 -s 320x240 '
. '-vcodec flv -qscale 9.5 '
. escapeshellcmd($localout);
// run the conversion
// you'd probably want exec() or proc_open()
// in production, but we use passthru()
// for this tutorial so you can watch!
passthru($cmd);
// check for expected output
if (!file_exists($localout)) {
die("Conversion failed, exiting.\n");
}
echo "Conversion complete!\n"
. "Deleting completed job from queue.\n";
$del = new Amazon_SQS_Model_DeleteMessageRequest();
$del->withReceiptHandle(
$msg[0]->getReceiptHandle()
)
->withQueueUrl($queue_url);
$sqs->deleteMessage($del);
// Clean up
$bucket = parse_url($source, PHP_URL_HOST);
$cbucket = 's3://' . $bucket . '/converted';
if (! is_dir($cbucket)) {
mkdir($cbucket);
}
echo "Uploading converted file back to S3\n";
file_put_contents(
$cbucket . '/' . basename($localout),
file_get_contents($localout)
);
echo "Deleting original since we no longer need it\n";
unlink($source);
From your instance:
PROMPT> cd /var/www/awsfiles
PROMPT> php job-fetch.php
This command determines the URL of your 'awstutorial' queue and attempts to receive a message
from that queue. When successful, the file referenced in the message is
downloaded and processed with ffmpeg. If the conversion fails, the message
is left in the queue for a future attempt. If conversion succeeds, the job
is deleted from the queue and the resulting file is uploaded back to S3. The original
file is also deleted from S3, since this site is only concerned with hosting
the converted files.
Step 4: Create a Record in the Video Database
With a converted video, now you're ready to put that into a database somewhere
so that you can dynamically update the content on your video sharing site.
Rather than building a MySQL database, managing a MySQL server and dealing with
the assorted issues that pop up with scaling any RDBMS, we'll leverage
Amazon's SimpleDB and leave the scalability, redundancy and performance
concerns in their hands.
For a deep
dive into the use Amazon's SimpleDB for PHP library, be sure to go
through the excellent SimpleDB Getting Started Guide.
If you narrow the scope of the examples in the guide down to PHP, you'll see that the
Getting Started Guide uses the same library and similar structure
to this article.
We'll create a very simple data structure for this example, which looks like this:
Before getting into the steps for setting up the data store, it's important
to note: It really is this easy.
Some developers have shown a resistance to even experimenting with SimpleDB,
because of a disbelief over how few steps are required to get up and
running with it relative to MySQL or other RDBMS. Don't worry! SimpleDB
has "Simple" in the name for a reason.
To begin with, we'll create a Domain. A SimpleDB Domain is roughly the
equivalent of a table in a traditional database system, in that they
are intended to house similar data in each item. Keep in mind that
part of "Simple" is that you cannot perform joins between Domains.
File: sdb-create-domain.php
<?php
/**
* Script to create the data domain for this tutorial
*/
require_once 'example_setup.php';
// load the service
$sdb = new Amazon_SimpleDB_Client(
$creds['access_key'],
$creds['secret_key']
);
// create the domain
$req = new Amazon_SimpleDB_Model_CreateDomainRequest();
$req->setDomainName('awstutorial');
$result = $sdb->createDomain($req);
if ($result->isSetResponseMetadata()) {
$meta = $result->getResponseMetadata();
if ($meta->isSetRequestId()) {
echo 'RequestId: ' . $meta->getRequestId() . "\n";
}
if ($meta->isSetBoxUsage()) {
echo 'BoxUsage: ' . $meta->getBoxUsage() . "\n";
}
}
From your instance:
PROMPT> cd /var/www/awsfiles
PROMPT> php sdb-create-domain.php
As you can see, each operation on the SimpleDB service has BoxUsage value
available in the response metadata. Since billing is done in large part
based on BoxUsage, keep an eye on the BoxUsage values while building
your SimpleDB applications.
Now that you've got a Domain created, let's make sure that it's really
there. To do that, issue a List Domains request.
File: sdb-list-domains.php
<?php
/**
* Script to check that awstutorial exists
*/
require_once 'example_setup.php';
// load the service
$sdb = new Amazon_SimpleDB_Client(
$creds['access_key'],
$creds['secret_key']
);
// make sure the domain is active
$req = new Amazon_SimpleDB_Model_ListDomainsRequest();
$result = $sdb->listDomains($req);
if ($result->isSetListDomainsResult()) {
$list = $result->getListDomainsResult()
->getDomainName();
foreach ($list as $domain) {
echo "DomainName {$domain}\n";
}
}
From your instance:
PROMPT> cd /var/www/awsfiles
PROMPT> php sdb-list-domains.php
With a Domain created, we can immediately begin inserting data. That's
right, no need to define a schema -- just start inserting items with
the attributes that are relevant to them. Attributes are a lot like
columns in a spreadsheet.
File: sdb-create-records.php
<?php
/**
* Insert a test record into the awstutorial domain.
*/
require_once 'example_setup.php';
// load the service
$sdb = new Amazon_SimpleDB_Client(
$creds['access_key'],
$creds['secret_key']
);
// Let's call this item1 -- acts as the "id" column
// in our table graphic.
$req = new Amazon_SimpleDB_Model_PutAttributesRequest();
$req->withDomainName('awstutorial')
->withItemName('item1');
// create a sample record for the table structure we're
// working with
$att = new Amazon_SimpleDB_Model_ReplaceableAttribute();
$att->withName('userid')
->withValue('clay');
$req->withAttribute($att);
$att = new Amazon_SimpleDB_Model_ReplaceableAttribute();
$att->withName('category')
->withValue('tutorials');
$req->withAttribute($att);
$att = new Amazon_SimpleDB_Model_ReplaceableAttribute();
$att->withName('file')
->withValue('sample.mov');
$req->withAttribute($att);
$att = new Amazon_SimpleDB_Model_ReplaceableAttribute();
$att->withName('description')
->withValue('Just a sample video. Watch!');
$req->withAttribute($att);
$att = new Amazon_SimpleDB_Model_ReplaceableAttribute();
$att->withName('size')
->withValue('71k');
$req->withAttribute($att);
$att = new Amazon_SimpleDB_Model_ReplaceableAttribute();
$att->withName('tags')
->withValue('apple');
$req->withAttribute($att);
$result = $sdb->putAttributes($req);
if ($result->isSetResponseMetadata()) {
$meta = $result->getResponseMetadata();
if ($meta->isSetRequestId()) {
echo 'RequestId: ' . $meta->getRequestId() . "\n";
}
if ($meta->isSetBoxUsage()) {
echo 'BoxUsage: ' . $meta->getBoxUsage() . "\n";
}
}
From your instance:
PROMPT> cd /var/www/awsfiles
PROMPT> php sdb-create-records.php
Just like that, you've got a resilient database containing one record. To see it,
you may use the following script:
File: sdb-list-items.php
<?php
/**
* List test records in the awstutorial domain.
*/
require_once 'example_setup.php';
// load the service
$sdb = new Amazon_SimpleDB_Client(
$creds['access_key'],
$creds['secret_key']
);
/**
*
* A query just returns the name of the items in a domain,
* which means you need to send another query for
* attributes relating to each item.
*
*/
$req = new Amazon_SimpleDB_Model_QueryRequest();
$req->setDomainName('awstutorial');
$response = $sdb->query($req);
if ($response->isSetQueryResult()) {
echo "Amazon_SimpleDB_Model_QueryRequest result\n";
echo "-----------------------------------------\n";
$result = $response->getQueryResult();
$list = $result->getItemName();
foreach ($list as $item_name) {
echo "ItemName: {$item_name}\n\n";
}
}
/**
*
* Query with Attributes returns item names and their attributes
* in a single query.
*
*/
$req = new Amazon_SimpleDB_Model_QueryWithAttributesRequest();
$req->setDomainName('awstutorial');
$response = $sdb->queryWithAttributes($req);
if ($response->isSetQueryWithAttributesResult()) {
echo "Amazon_SimpleDB_Model_QueryWithAttributesRequest result\n";
echo "-------------------------------------------------------\n";
$result = $response->getQueryWithAttributesResult();
$list = $result->getItem();
foreach ($list as $item) {
echo "Item Name: " . $item->getName() . "\n";
$attlist = $item->getAttribute();
foreach ($attlist as $attribute) {
echo $attribute->getName() . ": ";
echo $attribute->getValue() . "\n";
}
}
}
From your instance:
PROMPT> cd /var/www/awsfiles
PROMPT> php sdb-list-items.php
At the time of this writing, SimpleDB is in beta, which means there are
a few limitations. You may create up to 100 Domains, and each Domain
is limited to 10GB. Additional limitations are applied to each call
to the SimpleDB service, but they are permissive enough to allow a very
wide variety of applications to be built on top of SimpleDB.
And, since December 1, 2008, a free tier has been added to the SimpleDB
service that allows light-to-moderate usage of SimpleDB at no charge. Please
refer to the
SimpleDB Developer Guide
for details on per-call limitations.
Step 5: Put It All Together
If you've made it this far in this article, congratulations! We've covered how to
take an uploaded file, load it to S3, queue up a conversion job with SQS and EC2
and run that conversion. Tie that together with creating a SimpleDB database of
files, and all you need now is selecting data from SimpleDB to display on your
website.
SimpleDB makes that easy with its Select method. For example, here's
how to pull up all the records with the tag "apple" from the Domain we created
in the previous section.
File: sdb-sqlselect.php
<?php
/**
* Select specific records in the awstutorial domain using
* the SQL SELECT style interface.
*/
require_once 'example_setup.php';
// load the service
$sdb = new Amazon_SimpleDB_Client(
$creds['access_key'],
$creds['secret_key']
);
// Craft a SQL-like SELECT expression
$q = "SELECT * FROM awstutorial WHERE tags LIKE 'app%'";
$req = new Amazon_SimpleDB_Model_SelectRequest();
$req->setSelectExpression($q);
$response = $sdb->select($req);
if ($response->isSetSelectResult()) {
$result = $response->getSelectResult();
$list = $result->getItem();
foreach ($list as $item) {
echo "Item Name: " . $item->getName() . "\n";
$attlist = $item->getAttribute();
foreach ($attlist as $attribute) {
echo $attribute->getName() . ": ";
echo $attribute->getValue() . "\n";
}
}
}
From your instance:
PROMPT> cd /var/www/awsfiles
PROMPT> php sdb-sqlselect.php
Be sure to dig into the details of the SimpleDB query syntax in the
SimpleDB Developer Guide.
You'll find that it's a bit different than standard SQL, but it's expressive enough that you can
easily assemble a community-driven video sharing site, that allows for selecting
videos by tags, users, file types, etc.
Extra Credit: Tie Step 3 and Step 4 Together
By now, you've got visions of video-sharing domination dancing through your head
(as well you should!). To make this all smooth, go back to Step 3 and
alter the 'job-fetch.php' script. Combine it with Step 4's 'sdb-create-records.php'
script to create a record for the converted movie, with the 'file' value
of the location of the converted .flv that's uploaded to S3.
With that, the SELECT query above in Step 5, plus a handy .flv player like
the JW FLV Media Player,
and you're almost ready to launch!
Don't Forget to Cache!
One thing to keep in mind: when dealing with database driven websites,
even SimpleDB driven websites, it's always a good idea to make good
use of caching. SimpleDB is fast, but it is still a web services call.
You'll get the best performance utilizing a local cache on your webserver
to minimize the number of times you need to go back to the database
to fetch data.
A well thought-out caching plan is essential for getting the best performance
out of a dynamic website. Plus, with SimpleDB, a carefully considered
approach to caching will also save you money!
The details of how to cache, when to cache, and what tools to use for caching
are outside the scope of this article. Be sure to check your favorite
framework for caching classes, as well as PEAR's Cache_Lite
and the PHP Memcache extension.
Conclusion
It's difficult to imagine building web applications "the old way" once you've gotten the hang of the
various Amazon Web Services offerings. New and exciting services are continually added to the AWS arsenal,
so be sure to get up to speed on those discussed here so that you can be ready to integrate the next
set of tools from the Amazon labs.
The next generation of YouTube-like services will most likely be built on the basic services outlined here. Master them, and you'll be
ready for whatever heavy lifting you find you need.
|