Skip to content

Commit

Permalink
chore: use compression
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-aksamentov committed Aug 24, 2022
1 parent e12bbcb commit 1ac4763
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 3 deletions.
16 changes: 13 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ jobs:
with:
node-version: 10

- name: Install system dependencies
run: |
sudo apt-get update -qq --yes
sudo apt-get install pigz
- name: Install NPM dependencies
run: |
npm ci
Expand All @@ -70,7 +75,10 @@ jobs:
run: |
: ${DATA_ROOT_URL?"Environment variable is required"}
npm run build
ls -a1 public/*.html
- name: Compress
run: |
pigz -kfr public/ || true
- name: Check requirements
if: endsWith(github.ref, '/static-app') || endsWith(github.ref, '/release')
Expand All @@ -93,21 +101,23 @@ jobs:
aws s3 cp \
--recursive \
--cache-control "max-age=2592000, public" \
--metadata-directive REPLACE \
--exclude "*.html" \
--exclude "*.html.gz" \
"./" "s3://${AWS_S3_BUCKET}/"
- name: Deploy HTML files, stripping extension
if: endsWith(github.ref, '/static-app') || endsWith(github.ref, '/release')
run: |
cd public/
find * -type f -name "*.html" -print0 | xargs -0 -P4 -n1 -I '{}' -- bash -c '\
find * -type f \( -name "*.html" -o -iname "*.html.gz" \) -print0 | xargs -0 -P4 -n1 -I '{}' -- bash -c '\
file={}; \
aws s3 cp \
--content-type "text/html" \
--cache-control "no-cache" \
--metadata-directive REPLACE \
$file \
s3://${AWS_S3_BUCKET}/${file%.html}'
s3://${AWS_S3_BUCKET}/${file//.html/}'
- name: Invalidate AWS Cloudfront cache
if: endsWith(github.ref, '/static-app') || endsWith(github.ref, '/release')
Expand Down
28 changes: 28 additions & 0 deletions infra/data/lambda-at-edge/OriginRequest.lambda.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Implements rewrite of non-gz to gz URLs using AWS Lambda@Edge. This is
// useful if you have precompressed your files.
//
// Usage: Create an AWS Lambda function and attach it to "Origin Request" event
// of a Cloudfront distribution

ARCHIVE_EXTS = [
'.7z',
'.br',
'.bz2',
'.gz',
'.lzma',
'.xz',
'.zip',
'.zst',
]

exports.handler = (event, context, callback) => {
const request = event.Records[0].cf.request

// If not an archive file (which are not precompressed), rewrite the URL to
// get the corresponding .gz file
if(!ARCHIVE_EXTS.every(ext => request.uri.endsWith(ext))) {
request.uri += '.gz'
}

callback(null, request)
}
68 changes: 68 additions & 0 deletions infra/data/lambda-at-edge/ViewerResponse.lambda.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* eslint-disable prefer-destructuring */
// Adds additional headers to the response, including security headers and CORS.
// Suited for serving files and APIs.
//
// See also:
// - https://securityheaders.com/
//
// Usage: Create an AWS Lambda@Edge function and attach it to "Viewer Response"
// event of a Cloudfront distribution


const NEW_HEADERS = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Content-Security-Policy': `default-src 'none'; frame-ancestors 'none'`,
'Strict-Transport-Security': 'max-age=15768000; includeSubDomains; preload',
'X-Content-Type-Options': 'nosniff',
'X-DNS-Prefetch-Control': 'off',
'X-Download-Options': 'noopen',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
}

function addHeaders(headersObject) {
return Object.fromEntries(
Object.entries(headersObject).map(([header, value]) => [header.toLowerCase(), [{
key: header,
value
}]]),
)
}

const HEADERS_TO_REMOVE = new Set(['server', 'via'])

function filterHeaders(headers) {
return Object.entries(headers).reduce((result, [key, value]) => {
if(HEADERS_TO_REMOVE.has(key.toLowerCase())) {
return result
}

if(key.toLowerCase().includes('powered-by')) {
return result
}

return { ...result, [key.toLowerCase()]: value }
}, {})
}

function modifyHeaders({ request, response }) {
let newHeaders = addHeaders(NEW_HEADERS)

newHeaders = {
...response.headers,
...newHeaders,
}

newHeaders = filterHeaders(newHeaders)

return newHeaders
}

exports.handler = (event, context, callback) => {
const { request, response } = event.Records[0].cf
response.headers = modifyHeaders({ request, response })
callback(null, response)
}

exports.modifyHeaders = modifyHeaders
9 changes: 9 additions & 0 deletions infra/s3-cors-permissions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedOrigins": ["*"],
"ExposeHeaders": [],
"MaxAgeSeconds": 3000
}
]
28 changes: 28 additions & 0 deletions infra/web/lambda-at-edge/OriginRequest.lambda.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Implements rewrite of non-gz to gz URLs using AWS Lambda@Edge. This is
// useful if you have precompressed your files.
//
// Usage: Create an AWS Lambda function and attach it to "Origin Request" event
// of a Cloudfront distribution

ARCHIVE_EXTS = [
'.7z',
'.br',
'.bz2',
'.gz',
'.lzma',
'.xz',
'.zip',
'.zst',
]

exports.handler = (event, context, callback) => {
const request = event.Records[0].cf.request

// If not an archive file (which are not precompressed), rewrite the URL to
// get the corresponding .gz file
if(!ARCHIVE_EXTS.every(ext => request.uri.endsWith(ext))) {
request.uri += '.gz'
}

callback(null, request)
}
144 changes: 144 additions & 0 deletions infra/web/lambda-at-edge/ViewerResponse.lambda.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/* eslint-disable prefer-destructuring */
// Adds additional headers to the response, including security headers.
// Suited for websites.
//
// See also:
// - https://securityheaders.com/
//
// Usage: Create an AWS Lambda@Edge function and attach it to "Viewer Response"
// event of a Cloudfront distribution

const FEATURE_POLICY = {
'accelerometer': `'none'`,
'autoplay': `'none'`,
'camera': `'none'`,
'document-domain': `'none'`,
'encrypted-media': `'none'`,
'fullscreen': `'none'`,
'geolocation': `'none'`,
'gyroscope': `'none'`,
'magnetometer': `'none'`,
'microphone': `'none'`,
'midi': `'none'`,
'payment': `'none'`,
'picture-in-picture': `'none'`,
'sync-xhr': `'none'`,
'usb': `'none'`,
'xr-spatial-tracking': `'none'`,
}

function generateFeaturePolicyHeader(featurePolicyObject) {
return Object.entries(featurePolicyObject)
.map(([policy, value]) => `${policy} ${value}`)
.join('; ')
}

const PERMISSIONS_POLICY = {
'accelerometer': '()',
'ambient-light-sensor': '()',
'autoplay': '()',
'battery': '()',
'camera': '()',
'clipboard-read': '()',
'clipboard-write': '()',
'conversion-measurement': '()',
'cross-origin-isolated': '()',
'display-capture': '()',
'document-domain': '()',
'encrypted-media': '()',
'execution-while-not-rendered': '()',
'execution-while-out-of-viewport': '()',
'focus-without-user-activation': '()',
'fullscreen': '()',
'gamepad': '()',
'geolocation': '()',
'gyroscope': '()',
'hid': '()',
'idle-detection': '()',
'interest-cohort': '()',
'keyboard-map': '()',
'magnetometer': '()',
'microphone': '()',
'midi': '()',
'navigation-override': '()',
'payment': '()',
'picture-in-picture': '()',
'publickey-credentials-get': '()',
'screen-wake-lock': '()',
'serial': '()',
'speaker-selection': '()',
'sync-script': '()',
'sync-xhr': '()',
'trust-token-redemption': '()',
'usb': '()',
'vertical-scroll': '()',
'web-share': '()',
'window-placement': '()',
'xr-spatial-tracking': '()',
}

function generatePermissionsPolicyHeader(permissionsPolicyObject) {
return Object.entries(permissionsPolicyObject)
.map(([policy, value]) => `${policy}=${value}`)
.join(', ')
}

const NEW_HEADERS = {
'Content-Security-Policy':
`default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: ; connect-src *`,
'Referrer-Policy': 'no-referrer',
'Strict-Transport-Security': 'max-age=15768000; includeSubDomains; preload',
'X-Content-Type-Options': 'nosniff',
'X-DNS-Prefetch-Control': 'off',
'X-Download-Options': 'noopen',
'X-Frame-Options': 'SAMEORIGIN',
'X-XSS-Protection': '1; mode=block',
'Feature-Policy': generateFeaturePolicyHeader(FEATURE_POLICY),
'Permissions-Policy': generatePermissionsPolicyHeader(PERMISSIONS_POLICY),
}

function addHeaders(headersObject) {
return Object.fromEntries(
Object.entries(headersObject).map(([header, value]) => [header.toLowerCase(), [{
key: header,
value
}]]),
)
}

const HEADERS_TO_REMOVE = new Set(['server', 'via'])

function filterHeaders(headers) {
return Object.entries(headers).reduce((result, [key, value]) => {
if(HEADERS_TO_REMOVE.has(key.toLowerCase())) {
return result
}

if(key.toLowerCase().includes('powered-by')) {
return result
}

return { ...result, [key.toLowerCase()]: value }
}, {})
}

function modifyHeaders({ request, response }) {
let newHeaders = addHeaders(NEW_HEADERS)

newHeaders = {
...response.headers,
...newHeaders,
}

newHeaders = filterHeaders(newHeaders)

return newHeaders
}

exports.handler = (event, context, callback) => {
const { request, response } = event.Records[0].cf
response.headers = modifyHeaders({ request, response })
callback(null, response)
}

exports.modifyHeaders = modifyHeaders

0 comments on commit 1ac4763

Please sign in to comment.