Monthly Archives: March 2015

Direct File Uploads for PHP 5.5

One of the new features we announced in the 1.9.18 App Engine release is the ability to upload files directly to you application, without the need to upload the files to Google Cloud Storage first.

Direct uploads leverages the same in memory virtual filesystem that is used to provide temporary filesystem support. Direct uploads are only available with the PHP 5.5 runtime and are also limited to a maximum combined size of 32MB, which is the incoming request size limit.

Direct Upload Example

Following is a small sample application that demonstrates direct file uploads. This sample application:

  • Uses HTML5 multiple file upload support to upload image files.
  • Saves the original uploaded file into Google Cloud Storage.
  • Creates a greyscale version of the uploaded image and writes that to Google Cloud Storage.
  • Uses the image serving API to create links to serve the stored images at original size.
  • Also uses the image serving API to create thumbnail links of the images, without the application having to create the thumbnails.
  • Finally, displays a simple page with the thunbnailed links to the saved files.

For this application we’ll create 3 source files.

app.yaml

The app.yaml file has the following contents. Note that the runtime is php55.

application: php-uploads
version: 1
runtime: php55
api_version: 1
threadsafe: true

handlers:
- url: /handle_upload
  script: handle_upload.php

- url: .*
  script: direct_upload.php

direct_upload.php

This file presents a form for the user to upload multiple files to your application. As direct uploads are only in PHP 55 it checks that as well.

<?php
// Direct uploads requires PHP 5.5 on App Engine.
if (strncmp("5.5", phpversion(), strlen("5.5")) != 0) {
  die("Direct uploads require the PHP 5.5 runtime. Your runtime: " . phpversion());
}
?>
<html>
<body>
<form action="handle_upload" method="post" enctype="multipart/form-data">
  Send these files:<p/>
  <input name="userfile[]" type="file" multiple="multiple"/><p/>
  <input type="submit" value="Send files" />
</form>
</body>
</html>

handle_upload.php

The handle_upload script does the heavy lifting of creating the greyscale images, saving them in Cloud Storage and displaying the results. If you were doing this in a production app you’d need a lot more error handling, but I’ve left it out for brevity.

<?php

use google\appengine\api\cloud_storage\CloudStorageTools;

$bucket = CloudStorageTools::getDefaultGoogleStorageBucketName();
$root_path = 'gs://' . $bucket . '/' . $_SERVER["REQUEST_ID_HASH"] . '/';

$public_urls = [];
foreach($_FILES['userfile']['name'] as $idx => $name) {
  if ($_FILES['userfile']['type'][$idx] === 'image/jpeg') {
    $im = imagecreatefromjpeg($_FILES['userfile']['tmp_name'][$idx]);
    imagefilter($im, IMG_FILTER_GRAYSCALE);
    $grayscale = $root_path .  'gray/' . $name;
    imagejpeg($im, $grayscale);

    $original = $root_path . 'original/' . $name;
    move_uploaded_file($_FILES['userfile']['tmp_name'][$idx], $original);

    $public_urls[] = [
        'name' => $name,
        'original' => CloudStorageTools::getImageServingUrl($original),
        'original_thumb' => CloudStorageTools::getImageServingUrl($original, ['size' => 75]),
        'grayscale' => CloudStorageTools::getImageServingUrl($grayscale),
        'grayscale_thumb' => CloudStorageTools::getImageServingUrl($grayscale, ['size' => 75]),
    ];
  } 
}

?>
<html>
<body>
<?php
foreach($public_urls as $urls) {
  echo '<a href="' . $urls['original'] .'"><IMG src="' . $urls['original_thumb'] .'"></a> ';
  echo '<a href="' . $urls['grayscale'] .'"><IMG src="' . $urls['grayscale_thumb'] .'"></a>';
  echo '<p>';
}
?>
<p>
<a href="/">Upload More</a>
</body>
</html>

If you stumble across any problems, please let us know on either stack overflow or by creating an issue in our tracker.

File system changes in App Engine 1.9.18

In the 1.9.18 App Engine release we added a two new filesystem features to help application developers. The first is a change to the local filesystem in the development server that makes it appear to be readonly, to match how the application works in production. The second is we’ve added an in memory virtual filesystem that makes it possible to create temporary files using sys_get_temp_dir and associated functions.

Development Server Read-Only File System

We notices that a lot of new developers were caught out by the fact that the local filesystem in production is read-only, so we added a feature to mimic this behavior in the development environment. Now, by default, if your application tries to write to the local filesystem you will see an error message similar to what is shown below.

>>> file_put_contents('foo.txt', 'ttt');
file_put_contents(foo.txt): failed to open stream: Read-only file system

We know there are scenarios where you want to be able to write to the local filesystem, for example if pre-caching data before you deploy the application to production. To make this possible, we’ve added a flag that you can set in your applications php.ini file to make the filesystem read/write. Note this flag only works in the development server, the local filesystem will always be readonly in production.

To make the filesystem read/write, add the following to your php.ini file.

google_app_engine.disable_readonly_filesystem = 1

Then you’ll be able to write to the local filesystem without restriction – just remember that the production filesystem is always read-only.

Memory backed virtual filesystem

In the new PHP 5.5 runtime You can now use sys_get_temp_dir(), tmpfile() and tempnam() functions to create temporary files.

The temporary files generated are stored in an in memory virtual filesystem. The paths will start with vfs:// and look something like

>>> echo tempnam('foo', 'bar');
vfs://root/temp/foo/bar54f5751c88c520.85116141
>>> echo sys_get_temp_dir();
vfs://root/temp

The in memory filesystem will be flushed at the end of the request, and is not shared in between requests to the same instance. We’ve made it possible to call rename() with a cloud storage path as the destination so that you can write out the temporary file for permanent storage.

$tmp_name = tempnam('foo', 'bar');
$gcs_file = 'gs://my_bucket/foo/bar.txt';

file_put_contents($tmp_name, "Hello World");
rename($tmp_name, $gcs_file);
echo file_get_contents($gcs_file);

If you run into any problems with either of these new features, please let us know by filing an issue.