/ nginx

nginx 'intercept errors' and 'recursive error pages'

Previous we serverd images from a staic server, the url like: http://static.mycompany.com/resources/r/?d=20181029&i=1317166669&w=550 , which is apache httpd on aws EC2 box and resize images with php.
d: image date
i: image id
w: image width
The apache find the original image from aws EFS (mount to EC2 box) and rsized it with specified width save to EC2 local and return to client.

We plan save images to s3 instead of EFS for saving cost. And removed apache EC2 box, resized image in lambda.
The system diagram like below:

aws resize image

The images work flow:

  1. user browser access aws cloudfront get images use original url (like: http://static.mycompany.com/resources/r/?d=20181029&i=1317166669&w=550) . If hit from cloudfront cache reutn it. Or couldfront send the request url to nginx (in our aws vpc).
  2. nginx convert the url to thumb s3 bucket url (thumb bucket store resized images), url like: https://s3.amazonaws.com/bucketname/2018/10/29/i13171.jpg. Use proxy pass accss thumb s3 url.
  3. When get image from thumb s3 bucket, return it to cloudfront. When image not find, thumb s3 bucket trigger a 302 redirect to an api gateway url. Nginx deal with the 302 response, proxy pass access api gateway.
  4. api gateway backend is an image resize lambda, resize lambda get original image from origin images s3 bucket, then resize image and save it to thumb s3 bucket. After resize it trigger an 301 redirect to thumb s3 url, nginx deal with 301 thumb s3 url, proxy pass access thumb s3 url.

The nginx proxy pass config like below:

server {
  listen  80;
  server_name static.mycompany.com;
  # deal with https proxy pass
  proxy_ssl_server_name on;
  # deal with 301, 302, 404 reponse in nginx
  proxy_intercept_errors on;
  # deal with recursive error pages
  recursive_error_pages on;
  
  location /resources/r/ {
    error_page 404 = @handle_404;
    set $image_d '$arg_d';
    if ($image_d ~ "(\d{4})(\d{2})(\d{2})") {
      set $image_year $1;
      set $image_month $2;
      set $image_day $3;
    }
    if ($image_day = "") {
      return 404;
    }
    set $file_name 'img';
    if ($arg_r != "") {
      set $file_name '$file_name-r$arg_r';
    }
    ...
    et $s3_base 'http://bucketname.s3.amazonaws.com/';
    set $s3_url '$s3_base/$image_year/$image_month/$image_day/$file_name.JPG';

    proxy_pass $s3_url;
    error_page 301 302 307 = @handle_apigateway_redirects;
  }
  
  location @handle_apigateway_redirects {
    set $saved_redirect_location '$upstream_http_location';
    proxy_pass $saved_redirect_location;
    error_page 301 302 307 = @handle_s3_redirects;
    error_page 404 = @handle_404;
  }
  
  location @handle_s3_redirects {
    set $saved_redirect_location $upstream_http_location;
    proxy_pass $saved_redirect_location;
  }
  
  location @handle_404 {
    set $default_image 'http://bucketname.s3.amazonaws.com/pixel.png';
    proxy_pass $default_image;
  }

The good point of this implementation:

  1. In normal case, most images are dealed by cloudfront.
  2. The reszied image generated on the fly. Only needed images invoke resized lambda. Resize image lambda and image bucket in same aws region.

Other ref docs about AWS resized images:
https://aws.amazon.com/blogs/compute/resize-images-on-the-fly-with-amazon-s3-aws-lambda-and-amazon-api-gateway/

https://aws.amazon.com/blogs/networking-and-content-delivery/resizing-images-with-amazon-cloudfront-lambdaedge-aws-cdn-blog/