Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recipe #0057: v2 and v3 manifests at the same URL #181

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 78 additions & 18 deletions recipe/0057-publishing-v2-and-v3/index.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,103 @@
---
title: Publishing v2 and v3 versions
title: Making IIIF Presentation API v2 and v3 manifests available at the same URL
id: 57
layout: recipe
tags: [tbc]
tags: [implementation_note]
summary: "tbc"
---


## Use Case

Why is this pattern is important?
This recipe describes publishing IIIF v2 and v3 resources (both Presentation and Image API) at the same URL by using
Content Negotiation. It is presented as an alternative approach to publishing version-specific URLs for retrieving
these resources.

Using Content Negotiation is useful in cases where changing the location of these resources would cause annotations
targeting those resources, particularly those created and stored by third-party users, to no longer work. For example,
an annotation targeting a region of a canvas that relies on resolving that canvas and any media (image, video, or audio)
to show to end users. Using multiple URLs risks losing any work your users have done to annotate these canvases. Content
negotiation also provides a stable URL to reference a IIIF Manifest that will work, even as providers transition through
supported IIIF versions.

## Implementation notes

How does one implement the pattern?
[Content Negotiation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation) is an established method of requesting varying responses from a server. In this case, the server will
be asked to vary its response for either a version 2 or version 3 formatted JSON-LD. This can be accomplished by using
the `Accept` HTTP header. The value of this header contains a `profile` section that varies according to the IIIF
version that is desired. Servers that implement this method should also provide a default response
if the request does not contain these values. The examples below illustrate this process.

The IIIF API specifications give the values for the header. It follows a straightforward format:

`application/ld+json;profile="http://iiif.io/api/presentation/{VERSION}/context.json"`

If successful, the server should respond with a value in the `Content-Type` header that mirrors
the requested value. If the server is unable to respond as requested--for example, it does not support v3
manifests--then it may either choose to return its default response (a v2 manifest), or it may choose to return a
`406 Not Acceptable` status code.

## Restrictions

When is this pattern is usable / not usable? Is it deprecated? If it uses multiple specifications, which versions are needed, etc.? (Not present if not needed.)
There are several restrictions to using this pattern. Perhaps the most notable is that web browsers do
not allow clients to vary their `Accept` headers, so viewing IIIF resources in a browser is restricted to viewing
the server's default response. To view varying responses, the user must have the ability to control the HTTP request
headers. This is available in tools such as [Postman](https://www.postman.com/).

This is an active area of work in web specifications, and may change as methodologies develop. The W3C has a current
working group looking at [Content Negotiation by Profile](https://www.w3.org/TR/2019/WD-dx-prof-conneg-20191126/).
This specification offers dedicated `Accept-Profile` and `Content-Profile` headers, however these recommendations are
still in draft form.

## Example

Describe in prose and provide examples, e.g.:
These examples will use the `cURL` command-line HTTP client to control the request and view the response from
the server. The `-H` flag controls the value of the request header. The `-v` flag makes the process "verbose" so
that the response headers can be inspected. The leading `$` is used to illustrate a prompt and is not part of the command.

The first example shows a basic request to an HTTP service, but with an explicitly-set `Accept` header:

$ curl -v -H "Accept: application/ld+json" "https://example.iiif.io/0057-publishing-v2-and-v3/manifest.json"

This provides a default response of a IIIF v2 manifest. Looking at the request (`>`) and response (`<`) (truncated for
clarity):

> GET /0057-publishing-v2-and-v3/manifest.json HTTP/2
> Host: example.iiif.io
> User-Agent: curl/7.64.1
> Accept: application/ld+json
>

< HTTP/2 200
< server: nginx/1.18.0
< date: Wed, 24 Jun 2020 06:21:42 GMT
< content-type: application/json; charset=utf-8

The response content should be a v2 manifest:

{% include jsonviewer.html src="manifest-v2.json" %}

To request a IIIF v3 manifest at the same URL the `Accept` header value can be adjusted:

$ curl -v -H "Accept: application/ld+json;profile="http://iiif.io/api/presentation/3/context.json"" "https://example.iiif.io/0057-publishing-v2-and-v3/manifest.json"

The response has varied accordingly:

``` json-doc
{
"@context": [
"http://www.w3.org/ns/anno.jsonld",
"http://iiif.io/api/presentation/{{ page.major }}/context.json"
],
"id": "https://example.org/iiif/book1/manifest",
"type": "Manifest"
}
```
> GET /0057-publishing-v2-and-v3/manifest.json HTTP/2
> Host: example.iiif.io
> User-Agent: curl/7.64.1
> Accept: application/ld+json;profile=http://iiif.io/api/presentation/3/context.json
>

# Related recipes
< HTTP/2 200
< server: nginx/1.18.0
< date: Wed, 24 Jun 2020 06:29:39 GMT
< content-type: application/ld+json;profile="http://iiif.io/api/presentation/3/context.json"

Provide a bulleted list of related recipes and why they are relevant.
The response should be a v3 manifest. Note that the value of the `id` field remains the same as the v2
response:

{% include jsonviewer.html src="manifest-v3.json" %}

{% include acronyms.md %}
{% include links.md %}
Expand Down
36 changes: 36 additions & 0 deletions recipe/0057-publishing-v2-and-v3/manifest-v2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"@context": "http://iiif.io/api/presentation/2/context.json",
"@id": "{{ id.path }}/manifest.json",
"@type": "sc:Manifest",
"label": "Image 1",
"sequences": [
{
"@id": "{{ id.path }}/sequences/s1",
"@type": "sc:Sequence",
"label": "Default",
"canvases": [
{
"@id": "{{ id.path }}/canvas/p1",
"@type": "sc:Canvas",
"height": 1800,
"width": 1200,
"images": [
{
"@id": "{{ id.path }}/annotation/p0001-image",
"@type": "oa:Annotation",
"motivation": "sc:painting",
"resource": {
"@id": "http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png",
"@type": "dctypes:Image",
"format": "image/png",
"height": 1800,
"width": 1200
},
"on": "{{ id.path }}/canvas/p1"
}
]
}
]
}
]
}
35 changes: 35 additions & 0 deletions recipe/0057-publishing-v2-and-v3/manifest-v3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"@context": "http://iiif.io/api/presentation/3/context.json",
"id": "{{ id.path }}/manifest.json",
"type": "Manifest",
"label": { "en": [ "Image 1" ] },
"items": [
{
"id": "{{ id.path }}/canvas/p1",
"type": "Canvas",
"height": 1800,
"width": 1200,
"items": [
{
"id": "{{ id.path }}/page/p1/1",
"type": "AnnotationPage",
"items": [
{
"id": "{{ id.path }}/annotation/p0001-image",
"type": "Annotation",
"motivation": "painting",
"body": {
"id": "http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png",
"type": "Image",
"format": "image/png",
"height": 1800,
"width": 1200
},
"target": "{{ id.path }}/canvas/p1"
}
]
}
]
}
]
}