The Serverless LAMP stack part 3: Replacing the web server

In this post, you learn how to build serverless PHP applications without needing a web server.

Later in this post, Matthieu Napoli the creator of Bref and Serverless Visually Explained, tells how the implementation of FastCGI Process Manager inside of Lambda helps makes this possible. Bref is an open source runtime Lambda layer for PHP.

I show how to configure Amazon CloudFront to securely serve and cache static assets from a private Amazon S3 bucket. Dynamic requests are routed downstream to Amazon API Gateway and onto a single AWS Lambda function.

These services combine to replace the traditional web server for PHP applications.

Visit this GitHub repository for the sample code.

This serverless LAMP stack architecture is first discussed in this post. A web application is split in to two components (static assets and the backend application that generates dynamic content). The Lambda function contains the application’s business logic and interactions with the MySQL database. Each response is synchronously returned via API Gateway.

Routing with API Gateway

The serverless LAMP stack does not use an http server. Instead, API Gateway replaces the routing mechanism of Apache or NGINX. The AWS Serverless Application Model (AWS SAM) is used to configure API Gateway routing rules.

      Events:
        DynamicRequestsRoot:
          Type: HttpApi
          Properties:
            Path: /
            Method: ANY
        DynamicRequestsProxy:
          Type: HttpApi
          Properties:
            Path: /{proxy+}
            Method: ANY

AWS SAM template to route all inbound requests from HTTP API to a single Lambda function.

The preceding template creates an HTTP API with a “catch-all” rule for inbound requests. The request context is sent downstream to a single Lambda function. This is similar behavior to that of a PHP MVC framework that forwards requests to an index.php file. The following shows how this is achieved in a traditional LAMP stack, using a combination of web server and .htaccess configurations.

Alias /yourdir /var/www/html/yourdir/public/ 
<Directory “/var/www/html/yourdir/public”> 
AllowOverride All 
Order allow,deny 
Allow from all 
</Directory>

apache2.conf file configuration

<IfModule mod_rewrite.c> 
RewriteEngine On 
RewriteBase / 
RewriteRule ^index.php$ - [L] 
RewriteCond %{REQUEST_FILENAME} !-f 
RewriteCond %{REQUEST_FILENAME} !-d 
RewriteRule . /index.php [L] 
</IfModule>

Public/.htaccess configuration

Using Bref to host traditional PHP frameworks

Bref is an open source PHP runtime layer for Lambda. Using the bref-fpm layer, it’s possible to build applications with traditional PHP frameworks such as Symfony and Laravel. The framework sits within a single Lambda function and is invoked using the service architecture and routing rules illustrated previously. This is made possible due to Bref’s implementation of FastCGI Process Manager. Matthieu Napoli, creator of Bref, explains how.

Bref’s “FPM runtime” runs the php-fpm binary. PHP-FPM is a server implementing the FastCGI protocol, developed by the PHP core team. It is traditionally used with HTTP servers like Apache or NGINX.

Bref’s implementation of PHP-FPM allows PHP applications to run in a familiar environment by:

  • Running each HTTP request in a new process, which is the foundation of PHP’s “shared-nothing” execution model.
  • Populating the global variables ($_GET, $_POST…) used to access HTTP request data.
  • Providing a mechanism for PHP scripts to return HTTP responses (the header() function, stdout…).
  • Providing performance optimizations, such as OPcache (opcode cache), APCu (shared memory cache), or database persistent connections.

Most PHP frameworks are built around these PHP-FPM features, making this runtime an excellent transition from “server hosting” to serverless.

Here is an overview of how the runtime works:

Bref-fpm cycle

Startup

On initial invoke of a new Lambda environment, Bref’s bootstrap is executed and starts the php-fpm process in the background. This PHP-FPM server now waits for new connections on the FastCGI protocol.

The request/response cycle

Whenever a new HTTP request is sent to the application, the following happens:

  1. API Gateway receives the HTTP request and invokes AWS Lambda.
  2. The Lambda function environment executes the bootstrap for the Bref based runtime.
  3. Bref converts the HTTP request from the API Gateway format to the FastCGI format.
  4. Bref calls PHP-FPM through the FastCGI protocol.
  5. PHP-FPM runs the PHP handler and returns its response.
  6. Bref converts the FastCGI response to the API Gateway format.
  7. Bref returns the response to API Gateway, which returns the HTTP response to the client.

While there are multiple processes, this happens quickly.

AWS X-Ray trace view shows that the Lambda function finishes executing in 9 ms.

AWS X-Ray trace view shows that the Lambda function finishes executing in 9ms.

The Bref runtime performs a job similar to Apache or NGINX (forwarding an HTTP request through the FastCGI protocol), and PHP-FPM has been optimized for decades. Between requests, PHP-FPM does not kill and create new PHP processes. It keeps the same process and reset its memory (preserving in-memory caches like OPcache and APCu).

Configuring PHP for Lambda

Bref optimizes the configuration of PHP-FPM for AWS Lambda:

  • PHP-FPM runs a single “worker” because a Lambda instance handles one HTTP request at a time.
  • The standard error output of PHP-FPM is forwarded to CloudWatch. This makes logging from PHP as easy as writing to “stderr”.
  • All PHP errors, warnings, and notices are logged outside of the HTTP response and forwarded to CloudWatch by default.
  • PHP’s OPcache is optimized to avoid reading from disk because the PHP code base is mounted as read-only in Lambda.

Additionally, Bref adds behaviors that provide an easy migration from Apache/NGINX to API Gateway and Lambda:

  • Uploaded files are bridged with PHP-FPM’s uploaded file mechanism.
  • HTTP requests with binary content are automatically decoded from API Gateway’s base64 format.
  • Binary HTTP responses can also be automatically encoded to base64 by Bref.
  • Cookies are adapted to work with PHP-FPM’s mechanisms.

Bref also supports both v1 and v2 payload formats from API Gateway requests.

Static content routing and caching with Amazon CloudFront

The Lambda pricing model charges per request and per duration at GB of RAM allocated. This makes it ideal for handling requests for dynamic compute.

Amazon CloudFront handles requests for static content more efficiently than a server. This is a large scale, global, content delivery network (CDN) that provides secure, scalable delivery of content. It does this by caching data across a points of presence distributed all over the globe. This reduces the load on an application origin and improves the experience of the requestor by delivering a local copy of the content.

A CloudFront web distribution can serve different types of data from multiple origins. This template configures CloudFront to route requests for static assets directly to an S3 bucket. It routes all other requests directly to API Gateway.

Origins:
   -   Id: Website  
   DomainName: !Join ['.', [!Ref ServerlessHttpApi, 'execute-api', !Ref AWS::Region, 'amazonaws.com']]
   # This is the stage
   OriginPath: "/dev"
   CustomOriginConfig:
   	   OriginProtocolPolicy: 'https-only' # API Gateway only supports HTTPS
   # The assets (S3)
   -   Id: Assets
   DomainName: !GetAtt Assets.RegionalDomainName
   S3OriginConfig: {}

API Gateway routing is configured with HTTP APIs to route all inbound requests downstream to a single Lambda function, as previously shown.

Restricting access to Amazon S3 assets by using an origin access identity (OAI)

It is best practice to implement least privilege access permissions for each resource. This reduces security risk and the impact that could result from errors or malicious intent. Following this best practice, a security restriction is applied to the S3 bucket. The bucket is made private, with the objects inside only made available via the CloudFront distribution.

This is achieved by using an origin access identity (OAI). The OAI is defined within the CloudFormation template:

  S3OriginIdentity:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: Cloudfront AOI

It is then set as a principal within the S3 bucket’s policy.

AssetsBucketPolicy: 
    Type: AWS::S3::BucketPolicy
    Properties: 
      Bucket:
        Ref: Assets # References the bucket we defined above
      PolicyDocument: 
        Statement:
          Effect: Allow  
          Action: s3:GetObject # to read
          Principal: 
            CanonicalUser: 
              Fn::GetAtt: S3OriginIdentity.S3CanonicalUserId
          Resource: # things in the bucket 'arn:aws:s3:::<bucket-name>/*'
            Fn::Join: 
                - ""
                - 
                  - "arn:aws:s3:::"
                  - 
                    Ref: Assets
                  - "/*"

Deploying the infrastructure

This GitHub repository contains an AWS SAM template with instructions to deploy this infrastructure. It has a single Lambda function (index.php) which uses Bref’s php-73-fpm:25 runtime layer:

Layers:
        - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73-fpm:25'

A /vendors directory, holding the Bref runtime dependencies is also included. The handler inside Index.php returns HTML content to API Gateway’s requests. Within the Lambda function handler, there is a reference to a static image and static css file:

<link href="/assets/style.css" rel="stylesheet">
…
<img src="/assets/serverless-lamp-stack.png">

These files are referenced relatively (and not absolutely), because they are served under the same CloudFront domain as the dynamic portion of the website. Navigating to the generated CloudFront domain shows the dynamic webpage, along with the referenced static image. The Lambda function uses the global $_GET variable, made available to it by FastCGI process manager.

Servelress PHP website exampleBy building or replacing the index.php with your own framework, it’s possible to deploy feature-rich serverless web applications with PHP. Refer to Bref’s documentation for more information on building with popular PHP frameworks using the bref-fpm custom runtime.

Conclusion

This post explains how to build PHP applications with Lambda and API Gateway in place of an HTTP server like Apache or NGINX. It describes how to separate your application into static and dynamic requests. All dynamic HTTP requests are routed to a single Lambda function using Bref’s FPM custom runtime layer. The custom runtime’s implementation of FastCGI Process Manager makes it possible to build PHP applications with traditional frameworks.

Replacing the HTTP server frees developers from the responsibilities of web server maintenance, configuration, synchronization and scaling. PHP development teams can focus on shipping code without changing the way they build.

Start building serverless applications with PHP.