Shared hosting for the AMF C extension

Posted on Thursday 8 February 2007

I just read this comment by Pete Mackie on Mike Potter’s blog concerning the C extension:

To add this code to Xnix platforms requires a recompile of the PHP scripting engine. I see this as a barrier to wide deployment of this capability.

Many PHP developers run their Web servers on the $5 to $20 per month virtual servers that a handful of national ISPs provide. You can observe this notion by the server platform deployment question on the WebORB for PHP forum. (A few ISPs are even stuck with only supporting PHP 4.x, heaven forbid.) These Web server platforms are Xnix in based, with the majority being Linux. These ISPs cannot / will not be bothered with a recompile of PHP to support the addition of a new and unproven C module. In a nutshell, even if this addition is really a killer improvement, widely available Web server platforms to support the enhancement may not be available for many Flex 2 / PHP developers.

I think it’s a valid point. My intent was originally to ask web hosts to install the extension, despite its “unproven” status, in exchange for the free publicity in the list of hosts who support the extension on the amfphp.org site. Perhaps, however, it would be better to offer shared hosting for the amf extension in a test environment, showing that indeed it does work and work out the kinks if there are any before asking hosts to support the extension.

So I was thinking, perhaps, of getting a virtual dedicated hosting account at Media Temple, place a few amfphp enthusiasts on it and have the cost split between them. I’d prefer not to have to charge any money for these early adopters, so if any of you run a dedicated web server and you want to give the amfphp a boost by lending a part of it for this live test, it’d be great. Send a comment if you’re interested either in testing it live (and what would seem a fair price to you with regards to features) or giving us some space on a dedicated server.

Filed under: News
Amfphp 1.9 beta 2 - ridiculously faster

Posted on Friday 26 January 2007

Amfphp 1.9 beta 2 is now available for download. This is a HUGE release, making amfphp the fastest Remoting implementation ever (I kid you not).

Remoting at the speed of C

As I've mentioned earlier, there are many potential bottlenecks in a Remoting implementation which can affect speed. AMF encoding and decoding at the PHP level is not optimal, since PHP is a dynamic language, and encoding and decoding things is a repetitive, CPU and memory intensive operation. PHP, at its heart, is a thin scripting language which is powered and extended by C.

Emanuele Ruffaldi, who had previously created a Remoting implementation for C++, created a C extension for PHP which does AMF encoding and decoding. We've tested it through and through, it is very stable, and let me tell you, it is ridiculously fast. On sample data varying in size and complexity, it's 50 to 200 times faster on pure encoding and decoding than the previous PHP solution. Of course, as I've explained earlier, encoding and decoding AMF is only one of the many things that amfphp does, and the bottleneck may or may not be there depending on the size of the data. This extension will really help when sending tons of complicated data over the wire, data like large recordsets and deeply nested object trees. As users create more and more complicated apps with Flex, this will ensure that amfphp won't fall behind, and that you can continue to concentrate on your UI instead of encoding/decoding matters.

I want to stress that you don't need the extension installed to run the new version of amfphp. The extension is an add-on to PHP that the new version of amfphp will detect, load and use if it finds it, and revert to the original PHP-based encoding/decoding if it doesn't.

Here are some real world, before and after tests of amfphp's speed with this new extension:

test Hello world Big RecordSet (4x1800) Huge Recordset (20x2500) Mike 100 Mike 1000 Mike 10000 Mike 100,000
total php 28 140 1378 45 188 2228 timeout
total native 21 55 165 20 26 99 988
diff (%) 25% 61% 88% 56% 86% 96%
encode php 1 89 1303 18 157 2164 timeout
encode native 0 13 102 0 3 35 558
diff (%) 100% 85% 92% 100% 98% 98%
size 482 bytes 78.4 KB 961.5 KB 7.2 KB 69.6 KB 720 KB 7.3 MBs

The tests are:

  • A simple Hello world test
  • Sending a recordset with all the cities in Quebec with their coordinates (1 integer column, 1 string column, 2 float columns)
  • Sending a recordset with 2500 user profiles in a dating site (10 string columns and 10 int columns)
  • Mike Potter's test, adjusted so it doesn't use AMF3 repeated strings, with 100, 1000, 10,000 and 100,000 elements.

The top half of the table shows the pingback time for every test, over localhost, with the top row showing the speed of amfphp without the extension and the bottom one with the extension. The bottom half of the table shows only the amount of time spent in the encoder.

At the extreme end of the table, you should notice that it takes amfphp with the C extension less than a second to send 7.3 MBs of data consisting of 100,000 array elements each containing an object with three fields. That is absolutely insanely fast, my friends. It's so fast in fact, that it takes Flash more time to decode the AMF data than it does for amfphp to create it, and it's such a ludicrously large amount of data that Flash 9 will time out when using ObjectUtil.toString on that dataset. Note also that the regular amfphp doesn't fare bad at all in these tests, as in the large recordset example.

I should note that the serializer times shown above include all the time spent in the serializer, not just the time calling amf_encode or writeData. Therefore in the case of the large recordset, the speed improvement is not as dramatic as in the other cases because most of the time is actually being spent in the mysql recordset adapter when using the C extension, which obscures the real speed improvement. Furthermore, these tests don't use deeply nested data, but with the limited testing I've done, I can assert that the speed improvements are even better when serializing large trees.

So here's how to enable this extension: download it from Emanuele's site. On Windows, place the dll in php's ext directory and add a php_extension=php_amf.dll line in php.ini; restart Apache. On *NIX, compile php --with-amf from the source. Once that's done, load gateway.php in a browser, and you should see "AMF C Extension is loaded and enabled" on the second line. The new extension should also appear in phpinfo(). You don't have to change a setting in amfphp to enable it, if it sees it, it'll use it, if it doesn't, it'll revert to the default PHP encoding/decoding.

Also note that on Emanuele's site there is documentation on the callbacks that the extension defines, and that Evert Pot is interested in adding support for it in SabreAMF. I've also notified Mark Piller at MidnightCoders, so they may support the extension in WebORB for php. Once the extension reaches 1.0, I would like to have a list of shared hosting services that support it on the amfphp site; if you're interested in supporting it give me a buzz.

Remoting at the speed of gzip

The second beta comes with another nice speed boost in the form of decreased transfer times over the wire, thanks to gzip encoding. Basically, if a browser sends an Accept:gzip header to the server (ie, all modern browsers since IE5), amfphp will detect it and send the content gzipped. For large amounts of data, this can result in much decreased network traffic; typical compression ratios can vary from 2:1 to 10:1, depending on data set.

The nice part is that, once again, you don't need to configure anything to get this to work; if amfphp finds the gzip extension on the server, and the browser accepts gzip encoding, it'll do it, if not, it'll revert to the default behavior. By default, only results which are more than 25KBs in length are gzipped, as below that I don't think it's worth it to compress the data; you can change this setting in gateway.php though.

Better recordset support

I've added better recordset support to amfphp, which now includes:

  • MySQL
  • SQLite
  • PDO
  • Zend::DB, including ZendDbTable_Rowset
  • PHP Doctrine
  • Postgres (untested)
  • PEAR::DB (untested)
  • ODBC (untested)
  • MySQLi (untested)
  • MS-SQL (untested)
  • Informix (untested)
  • Frontbase (untested)
  • ADODB (untested)
  • Oracle - oci8 (untested)

The untested adapters should work, since they used to in amfphp 1.2, but I can't install every database known to man so if you get errors, load up Charles or ServiceCapture, see the error that's being sent, and send me a buzz.

Also noteworthy is a new plain RecordSet class. Basically, sometimes you want to loop through your dataset before sending it to Flash; in this case you can use the RecordSet wrapper. For example:


for(var i = 0; i < 100; i++)
{
    $rows[] = array("val" => i, "cube" => i*i*i);
}
return new RecordSet($rows);
 

You will receive this as a RecordSet in AMF0 and as an ArrayCollection in AMF3.

Enhanced service browser

I've tightened up the service browser interface. Most noteworthy are the enhanced info tab, which shows profiling information for every call, and the tree view tab, which allows you to browse returned object in a tree control, à la ServiceCapture and Charles.

This service browser will serve as the basis for the new Universal Remoting service browser, which is an initiative by myself, Evert Pot (SabreAMF), Aaron Smith (AMFRUBY) and Zoltan Csibi (Fluorine) to create a service browser that will work across all these Remoting implementations. That's right, all the warm fuzzy feeling of amfphp's service browser will be available in [your favorite Remoting implementation]. Fuggawesome.

ByteArray support

ByteArray support was in the last version, but I just didn't document it. Let me do that right now:

If you send a ByteArray instance from Flex to amfphp, you will receive an instance of ByteArray (a custom PHP class) in your method. The data is in $byteArray->data. To send back a ByteArray, just return an instance of ByteArray. For example, to save a bitmap through amfphp, in ActionScript:


var ba:ByteArray = bmp.getBytes();
ba.compress();
service.saveBitmap(ba);
 

In PHP:


function saveBitmap($ba)
{
    $data = $ba->data;
    $data = gzuncompress($data);
    file_put_contents("rawdata.rgba", $data);
    return true;
}
 

And to send a ByteArray to Flash:


function getSwf()
{
    return new ByteArray(file_get_contents("my.swf"));
}
 

Removed things

I've removed webservice support. If you didn't know, basically the original Remoting spec allowed you to call a SOAP-based webservice through a Remoting gateway. This was thought up in the days of Flash 6, when there wasn't an easy way to consume SOAP web services in Flash. Since Flash 7, there is a component that can connect to SOAP web services, and of course in Flex 2, it's as simple as using mx:WebService. Thus the only advantage of consuming a SOAP service through amfphp is the "free proxy", which allows you to bypass crossdomain.xml. However, it added bloat to amfphp, it was buggy, and SOAP support in PHP is pretty sucky anyway, which meant you were better off calling the webservice directly. Thus is was removed.

I've also removed a lot of settings in gateway.php which were seldom used and confused users. globals.php now contains global settings such as the service path, and this is the recommended place to add things like libraries or global includes (ie, for frameworks that expect to be included as globals, like WordPress, TextPattern, etc.).

Bug fixes

A few bugs with regards to encoding/decoding were fixed (as noticed by comparing with the C extension). A few random compatibility problems with low permissions on shared hosting were fixed. Dead code was removed. json.php can now accept an ? instead of / as the first character after it for incompatible web servers. Lots of other little things I didn't write down and can't remember right now were patched.

What's next

JSON-RPC, as opposed to straight JSON, enhanced service browser will be in the 2.0 release. I will try to find a more comprehensive solution to authentication, but I'm starting to run out of ideas. Pageable recordsets will definitely not make it back into 2.0. Zoltan figured out how to get FDS functionality working in Fluorine and we may implement a subset of that, but not until after 2.0 (and with that pageable recordsets will be back, but also lots of other nice things).

Filed under: Remoting and Actionscript and PHP
Why you shouldn’t use class mapping and VOs in amfphp

Posted on Thursday 18 January 2007

I am not one to start a fight over the use of this or that pattern, but since Flex 2 has arrived, a lot of people have started using VOs, TOs and DTOs (which are more or less the same, as I understand it) in amfphp, a trend I have some issues with.

What other people have to say about VOs

The motivation behind TransferObjects, according to the Sun design pattern site, is:

Some entities contain a group of attributes that are always accessed together. Accessing these attributes in a fine-grained manner through a remote interface causes network traffic and high latency, and consumes server resources unnecessarily.

A transfer object is a serializable class that groups related attributes, forming a composite value. This class is used as the return type of a remote business method. Clients receive instances of this class by calling coarse-grained business methods, and then locally access the fine-grained values within the transfer object. Fetching multiple values in one server roundtrip decreases network traffic and minimizes latency and server resource usage.

According to the ARP manual:

We use Value Objects [...] to model problem domain objects and structure the exchange of data among the various tiers.

According to the c2 wiki:

The reason you want to use an actual type [for a DTO] rather than a Hashtable is that the lookup of instance variables is orders of magnitude faster than looking something up in a hashtable. It also has nice features from a type safety perspective.

And again:

The DTO is typically implemented as a class with a bunch of fields, and getters and setters for each field.

Since every DTO class is essentially the same, except the field names and data types, why have I never seen this refactored? Isn't a generic DTO a simpler solution? If the data constraints were defined in a generic way then a simple user interface could be built automatically: create, search, modify, delete.

With the answer:

Yup, that's a recordset, and it kills the advantage of static typing and you end up always trying to figure out what's in it. Better to use real objects with real properties. aPerson.Name is better than aPerson["Name"], because it's easier to maintain in the face of change, the compiler will help you.

And a counter answer:

I'm not advocating throwing out typing altogether. I've known people who have been on projects where the DTO was a HashMap. They said it was far more hassle than it was worth precisely because of the lack of type checking. I would like to use a data transfer object that was constrained to a particular schema, like a class definition, but checked at runtime.

How this relates to amfphp

So basically a VO or DTO is a "throwaway" object which is to used for the transfer of data across a wire. The main advantages are: they define a serializable interface, and they are type-checked. Now the first argument doesn't make any sense in the context of Remoting, where everything has a serializable interface (of sorts). The second argument has a lot more weight, that is, type checking.

Let me state the obvious here: PHP is a dynamic language. That means that if you send a VO from Flex to amfphp, you will have to check yourself whether you are receiving what you are expecting. Consider the following in Java:


void addUser(UserVO userVO)
 

and in PHP:


function addUser($userVO)
 

You can immediately see that while in Java you can rest assured that the first argument you receive is a UserVO, that is not the case in PHP. There is nothing that tells you that instead of getting a UserVO you get an AdminVO, a stdClass object, or even a boolean. So you're going to have to add code to check whether you're indeed receiving what you're expecting. Also, if what you want is your objects to be sealed, so that other values can't be added to the VO, again, that won't work, because PHP objects are dynamic. You could use E_STRICT mode, but amfphp won't run in E_STRICT mode, as it supports PHP4 (SabreAMF does support E_STRICT mode).

Why sending non-dumb objects is also a bad idea

So you can see that from the preceding discussion that you don't get any clear advantage from sending a VO to amfphp as opposed to a plain object (associative array), given that the object is "dumb". What if the object was not dumb (ie, it has methods defined on it)? Then you could do the following:


function addUser($userVO) {
    return $userVO->save();
}
 

Admit that the save function saves the VO to a table in a database. You say, how convenient. However, the issue here is again that you have no idea whether you are receiving what you are expecting. So if instead a user would send an AdminVO to the user function, he could add an admin to the database if the object defined the same save function. Bad. So you have to check the type before saving. What if there are side-effects in the constructor? That means that the code in the constructor of AdminVO will be executed, even before you check that you are expecting a UserVO. If the AdminVO connected to a database with higher rights in the constructor, for example, well, you see where this is going.

Basically using class mapping can make you open to using remote code execution. Now the whole point of amfphp is remote code execution, but the issue here is that the kind of side-effects and issues with it can be very subtle, so it's much easier to miss. That is why I've purposefully hidden the class mapping features so that only an experienced developer who would be aware of these issues would bother to look into them.

Other reasons why you're better off sending a plain object

If you use (incoming) class mapping, your code will break if you attempt to use other RPC protocols which don't support that feature (XML-RPC, JSON). In addition, associative arrays have more methods available to them which can effectively lower the amount of code that you write. For example, I saw the following code recently:


function Register ($artistVO) {
$username = mysql_real_escape_string($artistVO->username);
$firstname = mysql_real_escape_string($artistVO->firstname);
$lastname = mysql_real_escape_string($artistVO->lastname);
$password = mysql_real_escape_string($artistVO->password);
$email = mysql_real_escape_string($artistVO->email);
$city = mysql_real_escape_string($artistVO->city);
$provstate = mysql_real_escape_string($artistVO->provstate);
$postzip = mysql_real_escape_string($artistVO->postzip);
$country = mysql_real_escape_string($artistVO->country);
 

If $artistVO was an associative array, then:


function Register ($artistVO) {
$escapedVO = array_map('mysql_real_escape_string', $artistVO);
 

Much cleaner.

Bottom line: don't use incoming class mapping, use outgoing class mapping

Now the typing arguments and the remote code execution issues become invalid when we consider the inverse process, sending class instances from amfphp to Flash/Flex, because Flash/Flex runs in a sandbox and AS2 is compile-time type-checked/AS3 is runtime type-checked. So you can certainly use $_explicitType to send back a typed object to Flash if you so wish. If your data looks like a RecordSet, however, don't bother looping through it to transform it to an array of VOs. Waste of time, especially since in AS2 RecordSet is much more powerful than Array.

As for incoming class mapping, remember that if amfphp doesn't find the class to be mapped in the services/vo folder, it will simply deserialize the object as though it were untyped, that is, as an associative array. So you don't have to change your UI code or anything, you can send typed objects to amfphp if you wish, you will receive it as an associative array fine unless you specifically put a class to the vo folder to enable class mapping.

Also, I don't want to start a storm or anything about best-practices and patterns. The VO pattern is fine, it just makes a lot more sense in Java than it does in PHP. There is no use in trying to make PHP code look like Java; Java already looks like Java, and if you like that look, then please, by all means, use OpenAMF or FDS.

Filed under: Remoting and Actionscript and PHP
Amfphp-frameworks integration - make yourself known

Posted on Tuesday 16 January 2007

I've added a page on amfphp.org that shows the current amfphp-frameworks integration efforts. We currently have working/in development integrations for CakePHP, Drupal, Seagull and PostNuke. Frameworks integration is top-priority in amfphp 2.0 (along with amf3, json and xml-support, service browser enhancements, and a super secret feature that will rock your world that will be revealed later this week). So if you have worked on amfphp-something integration, let me know, post a comment on the blog, and I will certainly help (try to) you out. Wanted are:

  • WordPress (top priority)
  • Code Ignitor (top priority)
  • Textpattern
  • Symfony

WordPress is a very tightly coupled, and perhaps TextPattern would be easier to support; it would allow people who want to use amfphp as a backend for a Flash/Flex CMS/blog to do so without having to roll their own CMS, which IMHO is a waste of time (why DIY when you can plug-and-play).

Before you ask, Zend DB support is coming, and PDO support is in. Let me know.

Filed under: Remoting and PHP
Tutorial: Flex 2 and amfphp 1.9

Posted on Monday 15 January 2007

Quite a few of you requested a tutorial on getting amfphp 1.9 running with Flex 2 and RemoteObject. Alessandro Crugnola, aka sephiroth, has posted a complete tutorial on the subject. Thanks Alessandro!

View it here.

Filed under: News