Skip to content

Commit

Permalink
Merge pull request #118 from samvera/nextra-docs
Browse files Browse the repository at this point in the history
Update documentation and create static site
  • Loading branch information
mbklein committed Aug 1, 2023
2 parents 68c1528 + f3f0e87 commit a0db911
Show file tree
Hide file tree
Showing 54 changed files with 8,762 additions and 212 deletions.
66 changes: 39 additions & 27 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,35 +1,47 @@
{
"extends" : [
"standard"
],
"env" : {
"browser" : false
"extends": ["standard"],
"env": {
"browser": false
},
"globals" : {
"__DEV__" : false,
"__TEST__" : false,
"__PROD__" : false,
"__COVERAGE__" : false
"globals": {
"__DEV__": false,
"__TEST__": false,
"__PROD__": false,
"__COVERAGE__": false
},
"ignorePatterns": "tests/*",
"rules" : {
"brace-style" : [2, "1tbs"],
"comma-dangle" : [2, "never"],
"indent" : ["error", 2, {"SwitchCase" : 1}],
"key-spacing" : 0,
"max-len" : [0, 120, 2],
"max-lines-per-function": ["warn", {"max": 30, "skipBlankLines": true, "skipComments": true}],
"no-unused-vars" : [1, { "vars": "all", "args": "after-used", "ignoreRestSiblings": false, "argsIgnorePattern": "^_" }],
"no-var" : 1,
"object-curly-spacing" : [2, "always"],
"prefer-const" : [1, {
"destructuring" : "any",
"ignoreReadBeforeAssign" : true
}],
"semi" : [2, "always"],
"space-in-parens" : ["error", "never"],
"rules": {
"brace-style": [2, "1tbs"],
"comma-dangle": [2, "never"],
"indent": ["error", 2, { "SwitchCase": 1 }],
"key-spacing": 0,
"max-len": [0, 120, 2],
"max-lines-per-function": [
"warn",
{ "max": 30, "skipBlankLines": true, "skipComments": true }
],
"no-unused-vars": [
1,
{
"vars": "all",
"args": "after-used",
"ignoreRestSiblings": false,
"argsIgnorePattern": "^_"
}
],
"no-var": 1,
"object-curly-spacing": [2, "always"],
"prefer-const": [
1,
{
"destructuring": "any",
"ignoreReadBeforeAssign": true
}
],
"semi": [2, "always"],
"space-in-parens": ["error", "never"],

// This should replicate Code Climate's computational complexity code smells warning. It is actually more strict.
"complexity" : ["warn", 5]
"complexity": ["warn", 5]
}
}
60 changes: 60 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Deploy docs to GitHub Pages
on:
push:
branches: ["main"]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./docs
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16
cache: npm
- name: Setup Pages
uses: actions/configure-pages@v3
with:
static_site_generator: next
- name: Restore cache
uses: actions/cache@v3
with:
path: |
docs/.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('docs/**/package-lock.json') }}-${{ hashFiles('docs/**.[jt]s', 'docs/**.[jt]sx') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('docs/**/package-lock.json') }}-
- name: Install dependencies
run: npm install
- name: Build with Next.js
run: npx --no-install next build
env:
NEXTJS_BASE_PATH: /serverless-iiif
- name: Upload artifact
uses: actions/upload-pages-artifact@v2
with:
path: ./docs/out

# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.sortJSON": false
}
}
180 changes: 3 additions & 177 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,183 +8,9 @@

A [IIIF 2.1 Image API](https://iiif.io/api/image/2.1/) compliant server written as an [AWS Serverless Application](https://aws.amazon.com/serverless/sam/).

## Components
## Documentation

* A simple [Lambda Function](https://aws.amazon.com/lambda/) wrapper for the [iiif-processor](https://www.npmjs.com/package/iiif-processor) module.
* A [Lambda Function URL](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html) that is used to invoke the IIIF API via HTTPS.
* A [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html) containing all the dependencies for the Lambda Function.
* An optional [CloudFormation](https://aws.amazon.com/cloudformation/) template describing the resources needed to deploy the application.

## Prerequisites

* Some basic knowledge of AWS.
* An Amazon Web Services account with permissions to create resources via the console and/or command line.
* An [Amazon S3](https://aws.amazon.com/s3/) bucket to hold the source images to be served via IIIF.
**Note: The Lambda Function will be granted read access to this bucket.**

## Quick Start

`serverless-iiif` comes in two flavors: *Standalone (Lambda-only)* and *Caching (CloudFront-enabled)*. The Standalone version is much simpler, but lacks the following features:

- Custom Domain Name
- Standalone URLs are in the `lambda-url.AWS_REGION.on.aws` domain (e.g., `https://fu90293j0pj902j902c32j902.lambda-url.us-east-1.on.aws/iiif/2/`)
- Caching URLs *without* Custom Domains are in the `cloudfront.net` domain (e.g., `https://d3kmjdzzy1l5t3.cloudfront.net/iiif/2/`)
- Responses larger than ~6MB
- CloudFront function support (for pre/post-processing requests and responses)

### Deploying via the AWS Serverless Application Repository

`serverless-iiif` is distributed and deployed via the [AWS Serverless Application Repository](https://aws.amazon.com/serverless/serverlessrepo/). To deploy it using the AWS Console:

1. Click one of the following links to deploy the desired application from the AWS Console:
- [Standalone (Lambda-Only) Version](https://console.aws.amazon.com/lambda/home#/create/app?applicationId=arn:aws:serverlessrepo:us-east-1:625046682746:applications/serverless-iiif-standalone)
- [Caching (CloudFront-Enabled) Version](https://console.aws.amazon.com/lambda/home#/create/app?applicationId=arn:aws:serverlessrepo:us-east-1:625046682746:applications/serverless-iiif-cloudfront)
2. Make sure your currently selected region (in the console's top navigation bar) is the one you want to deploy to.
3. Scroll down to the **Application settings** section.
4. Configure the deploy template:
- Give your stack a unique **Application name**
- Enter the name of the **SourceBucket** the service will serve images from
- Check the box acknowledging that the app will create a custom IAM roles and resource policies (and if deploying the Caching version, that it will also deploy a nested application)
- *Optional*: Enter or change any other parameters that apply to your desired configuration.
5. Click **Deploy**.
6. When all the resources are properly created and configured, the new stack should be in the **CREATE_COMPLETE** stage. If there's an error, it will delete all the resources it created, roll back any changes it made, and eventually reach the **ROLLBACK_COMPLETE** stage.
7. Click the **CloudFormation stack** link.
8. Click the **Outputs** tab to see (and copy) the IIIF Endpoint URL.

### Deploying via the Command Line

1. Make sure you have the [SAM CLI](https://aws.amazon.com/serverless/sam/) and [AWS CLI](https://aws.amazon.com/cli/) installed.
2. Make sure the AWS CLI is [properly configured](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) with credentials that have sufficient access to manage IAM, S3, Lambda, and (optionally) CloudFront resources.
3. Clone this repository.
4. Copy .env.example to .env. Update the various values within.
5. Build the application:
```shell
$ npm run build
```
6. Deploy the application:
```shell
$ npm run deploy
```

You'll be prompted for various configuration parameters, confirmations, and acknowledgments of specific issues (particularly the creation of IAM resources and the deployment of an open/unauthenticated Lambda Function URL).
7. Follow the prompts to complete the deployment process and get the resulting endpoint.
### Deleting the application
The easiest way to delete the application is either from the [Lambda Applications Console](https://console.aws.amazon.com/lambda/home#/applications) or by deleting its [CloudFormation Stack](https://console.aws.amazon.com/cloudformation/home#/stacks?filteringStatus=active&filteringText=&viewNested=true&hideStacks=false). If you deployed from the command line, you can also use the `npm run delete` command.
## Source Images
The S3 key of any given file, minus the extension, is its IIIF ID. For example, if you want to access the image manifest for the file at `abcdef.tif`, you would get `https://.../iiif/2/abcdef/info.json`. If your key contains slashes, they must be URL-encoded: e.g., `ab/cd/ef/gh.tif` would be at `https://.../iiif/2/ab%2Fcd%2Fef%2Fgh/info.json`. (This limitation could easily be fixed by encoding only the necessary slashes in the incoming URL before handing it off to the IIIF processor, but that's beyond the scope of the demo.)

`iiif-processor` can use any image format _natively_ supported by [libvips](https://libvips.github.io/libvips/), including JPEG 2000 (`.jp2`), but best results will come from using tiled, multi-resolution TIFFs. The Lambda Function wrapper included in this application assumes a `.tif` extension unless you set ResolverTemplate in your .env file.

### Creating tiled TIFFs

#### Using VIPS

vips tiffsave source_image.tif output_image.tif --tile --pyramid --compression jpeg --tile-width 256 --tile-height 256

#### Using ImageMagick

convert source_image.tif -define tiff:tile-geometry=256x256 -compress jpeg 'ptif:output_image.tif'

## Testing

If tests are run locally they will start in "watch" mode. If a CI environment is detected they will only run once. From the project root run:

```
npm test
```
To generate a code coverage report run:
```
npm test --coverage
```
## Custom Sharp Layer
This lambda uses the Sharp layer from https://github.com/samvera/lambda-layer-sharp-jp2/releases in order to get a version of Sharp with jp2 support. You can build your own local version using that code and then copy the file to serverless-iiif/sharp-lambda-layer.x86_64.zip. Then set LOCAL_SHARP=true and build/deploy to use your own version.
## Advanced Usage
### Cross-Origin Request Sharing (CORS)
For security reasons, web browsers have built in limits on what sort of requests can be made to a given domain from a page hosted under a different domain. Since this is a common use case for IIIF (resources embedded in pages whose domains differ from that of the server), IIIF interactions are particularly susceptible to these limits. The mechanism for determining which of these requests should be allowed or blocked is known as Cross-Origin Resource Sharing, or [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). A full explanation of CORS is beyond the scope of this project, but the SAM deploy template contains five parameters relating to how the IIIF server handles CORS:
* `CorsAllowCredentials` contains the value that will be returned in the `Access-Control-Allow-Credentials` response header.
* `CorsAllowHeaders` contains the value that will be returned in the `Access-Control-Allow-Headers` response header.
* `CorsAllowOrigin` contains the value that will be returned in the `Access-Control-Allow-Origin` response header. In addition, a special value, `REFLECT_ORIGIN`, instructs the IIIF server to copy the value of the incoming request's `Origin` header into the `Access-Control-Allow-Origin` response header.
* `CorsExposeHeaders` contains the value that will be returned in the `Access-Control-Expose-Headers` response header.
* `CorsMaxAge` contains the value that will be returned in the `Access-Control-Max-Age` response header.
The default values will work in most circumstances, but if you need the IIIF server to accept requests that include credentials or other potentially sensitive information (e.g., `Authorization` and/or `Cookie` headers), you'll need to set `CorsAllowOrigin` to `REFLECT_ORIGIN` and `CorsAllowCredentials` to `true`. Other settings allow further customization.
### Request/Response Functions
The SAM deploy template takes several optional parameters to enable the association of [CloudFront Functions](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html) or [Lambda@Edge Functions](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-at-the-edge.html) with the CloudFront distribution. These functions can perform authentication and authorization functions, change how the S3 file and/or image dimensions are resolved, or alter the response from the lambda or cache. These parameters are:
* `OriginRequestARN`: ARN of the Lambda@Edge Function to use at the origin-request stage
* `OriginResponseARN`: ARN of the Lambda@Edge Function to use at the origin-response stage
* `ViewerRequestARN`: ARN of the CloudFront or Lambda@Edge Function to use at the viewer-request stage
* `ViewerRequestType`: Type of viewer-request Function to use (`CloudWatch Function` or `Lambda@Edge`)
* `ViewerResponseARN`: ARN of the CloudFront or Lambda@Edge Function to use at the viewer-response stage
* `ViewerResponseType`: Type of viewer-response Function to use (`CloudWatch Function` or `Lambda@Edge`)
These functions, if used, must be created, configured, and published before the serverless application is deployed.
#### Examples
These examples use CloudFront Functions. Lambda@Edge functions are slightly more complicated in terms of the event structure but the basic idea is the same.
##### Simple Authorization
```JavaScript
function handler(event) {
if (notAuthorized) { // based on something in the event.request
return {
statusCode: 403,
statusDescription: 'Unauthorized'
};
};
return event.request;
}
```

##### Custom File Location / Image Dimensions

```JavaScript
function handler(event) {
var request = event.request;
request.headers['x-preflight-location'] = {value: 's3://image-bucket/path/to/correct/image.tif'};
request.headers['x-preflight-dimensions'] = {value: JSON.stringify({ width: 640, height: 480 })};
return request;
}
```

The `x-preflight-dimensions` header can take several shapes:

* `{ width, height }` (or `[{ width, height }]`) - a straightforward, single-resolution image
* `[{ width, height }, { width, height }, ...]` - a multi-resolution image with pages of the specified sizes
* `{ width, height, pages }` - a multi-resolution image with the specified number of `pages`, each half the size of the one before
* `{ width, height, limit }` - a multi-resolution image in which the smallest width and height are both less than the specified `limit`

For example, the following dimension values would all describe the same pyramidal image:

* `[{ width: 2048, height: 1536 }, { width: 1024, height: 768 }, { width: 512, height: 384 }]`
* `{ width: 2048, height: 1536, pages: 3 }`
* `{ width: 2048, height: 1536, limit: 480 }`

The `limit` calculator will keep going until both dimensions are _less than_ the limit, not _less than or equal to_. So a `limit: 512` on the third example above would generate a fourth page at `{ width: 256, height: 192 }`.

*Note:* The SAM deploy template adds a `preflight=true` environment variable to the main IIIF Lambda if a preflight function is provided. The function will _only_ look for the preflight headers if this environment variable is `true`. This prevents requests from including those headers directly if no preflight function is present. If you do use a preflight function, make sure it strips out any `x-preflight-location` and `x-preflight-dimensions` headers that it doesn't set itself.

## Notes

Lambda Function URLs have a payload (request/response body) size limit of approximately 6MB in both directions. To overcome this limitation, the Lambda URL is configured behind an AWS CloudFront distribution with two origins - the API and a cache bucket. Responses larger than 6MB are saved to the cache bucket at the same relative path as the request, and the Lambda returns a `404 Not Found` response to CloudFront. CloudFront then fails over to the second origin (the cache bucket), where it finds the actual response and returns it.

The cache bucket uses an S3 lifecycle rule to expire cached responses in 1 day.
Please see the [full documentation](https://samvera.github.io/serverless-iiif/docs) for information about deployment, customization, image creation, and more.

## License

Expand All @@ -197,7 +23,7 @@ The cache bucket uses an S3 lifecycle rule to expire cached responses in 1 day.
* [Rob Kaufman](https://github.com/orangewolf)
* [Edward Silverton](https://github.com/edsilv)
* [Trey Pendragon](https://github.com/tpendragon)
* [Dan Wolfe](https://github.com/danthewolfe)
* [Theia Wolfe](https://github.com/theiawolfe)

## Contributing

Expand Down
7 changes: 7 additions & 0 deletions docs/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{

"rules": {
"space-before-function-paren": 0,
"react/prop-types": 0
}
}
48 changes: 48 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel

# typescript
*.tsbuildinfo

# terraform
*.tfvars
/terraform/.terraform
/terraform/*.plan

# ephemeral build artifacts
/lib/honeybadger/config.vars.js

# vscode
/.vscode/*
Loading

0 comments on commit a0db911

Please sign in to comment.