Skip to content

Commit bbb4682

Browse files
authored
Merge pull request #183 from IIIF/add-accept-header
Add accept header
2 parents b602feb + 95ca6fe commit bbb4682

File tree

5 files changed

+77
-26
lines changed

5 files changed

+77
-26
lines changed

.github/workflows/deploy.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
runs-on: ubuntu-latest
1212

1313
steps:
14-
- uses: actions/checkout@v2
14+
- uses: actions/checkout@v4
1515
- name: docker login
1616
env:
1717
DOCKER_USER: ${{secrets.DOCKER_USER}}
@@ -33,15 +33,15 @@ jobs:
3333
run: docker logout
3434

3535
- name: Configure AWS credentials
36-
uses: aws-actions/configure-aws-credentials@v1
36+
uses: aws-actions/configure-aws-credentials@v4
3737
with:
3838
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
3939
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
4040
aws-region: us-east-1
4141

4242
- name: Login to Amazon ECR
4343
id: login-ecr
44-
uses: aws-actions/amazon-ecr-login@v1
44+
uses: aws-actions/amazon-ecr-login@v2
4545

4646
- name: Fill in the new image ID in the Amazon ECS task definition
4747
id: task-def
@@ -52,7 +52,7 @@ jobs:
5252
image: docker.io/glenrobson/iiif-presentation-validator:${{ github.sha }}
5353

5454
- name: Deploy Amazon ECS task definition
55-
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
55+
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
5656
with:
5757
task-definition: ${{ steps.task-def.outputs.task-definition }}
5858
service: PreziValidatorService

.github/workflows/test.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,21 @@ jobs:
1414
python-version: [ '3.9', '3.10', '3.11']
1515
name: Python ${{ matrix.python-version }} sample
1616
steps:
17-
- uses: actions/checkout@v2
17+
- uses: actions/checkout@v4
1818
- name: Setup python
19-
uses: actions/setup-python@v2
19+
uses: actions/setup-python@v5
2020
with:
2121
python-version: ${{ matrix.python-version }}
2222
architecture: x64
2323

24-
- uses: actions/cache@v2
24+
- uses: actions/cache@v4
2525
with:
2626
path: ${{ env.pythonLocation }}
2727
key: ${{ env.pythonLocation }}-${{ hashFiles('setup.py') }}-${{ hashFiles('dev-requirements.txt') }}
2828

29+
- name: Install setup tools
30+
run: pip install setuptools
31+
2932
- name: Install
3033
run: python setup.py install
3134

iiif-presentation-validator.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,24 @@
3030
from pyld import jsonld
3131
jsonld.set_document_loader(jsonld.requests_document_loader(timeout=60))
3232

33+
IIIF_HEADER = "application/ld+json;profile=http://iiif.io/api/presentation/{iiif_version}/context.json"
34+
3335
class Validator(object):
3436
"""Validator class that runs with Bottle."""
3537

3638
def __init__(self):
3739
"""Initialize Validator with default_version."""
3840
self.default_version = "2.1"
3941

40-
def fetch(self, url):
42+
def fetch(self, url, accept_header):
4143
"""Fetch manifest from url."""
4244
req = Request(url)
4345
req.add_header('User-Agent', 'IIIF Validation Service')
4446
req.add_header('Accept-Encoding', 'gzip')
4547

48+
if accept_header:
49+
req.add_header('Accept', accept_header)
50+
4651
try:
4752
wh = urlopen(req)
4853
except HTTPError as wh:
@@ -150,13 +155,24 @@ def do_GET_test(self):
150155
"""Implement GET request to test url at version."""
151156
url = request.query.get('url', '')
152157
version = request.query.get('version', self.default_version)
158+
accept = request.query.get('accept')
159+
accept_header = None
153160
url = url.strip()
154161
parsed_url = urlparse(url)
162+
163+
if accept and accept == 'true':
164+
if version in ("2.0", "2.1"):
165+
accept_header = IIIF_HEADER.format(iiif_version=2)
166+
elif version in ("3.0",):
167+
accept_header = IIIF_HEADER.format(iiif_version=3)
168+
else:
169+
accept_header = "application/json"
170+
155171
if (parsed_url.scheme != 'http' and parsed_url.scheme != 'https'):
156172
return self.return_json({'okay': 0, 'error': 'URLs must use HTTP or HTTPS', 'url': url})
157173

158174
try:
159-
(data, webhandle) = self.fetch(url)
175+
(data, webhandle) = self.fetch(url, accept_header)
160176
except Exception as error:
161177
return self.return_json({'okay': 0, 'error': 'Cannot fetch url. Got "{}"'.format(error), 'url': url})
162178

@@ -236,6 +252,7 @@ def main():
236252
args = parser.parse_args()
237253

238254
v = Validator()
255+
239256
run(host=args.hostname, port=args.port, app=v.get_bottle_app())
240257

241258

index.html

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@
5353

5454
<section class="wrapper">
5555
<div>
56-
This service will validate a IIIF Presentation API resource against the specification. Fill in the URL of your manifest, and it will try to parse it and issue errors for failed requirements, and warnings for recommendations that haven't been followed.
56+
This service will validate a IIIF Presentation API resource against the specification. Fill in the URL
57+
of your manifest, and it will try to parse it and issue errors for failed requirements, and warnings for
58+
recommendations that haven't been followed.
5759
</div>
5860

5961
<div style="border: 1px solid black;margin-left: 10px; margin-top: 20px; padding: 10px">
@@ -70,6 +72,8 @@
7072
<option value="1.0">1.0</option>
7173
</select>
7274
<br/>
75+
<input type="checkbox" id="accept" name="accept">
76+
<label for="accept">Include <code>Accept</code> header in request for specified version</label><br />
7377

7478
<input type="submit" value="Go!" id="submit-url">
7579
</form>
@@ -85,13 +89,20 @@ <h3>Validation Results:</h3>
8589
<hr/>
8690

8791
<div style="margin-top:20px">
88-
<b>Technical Note</b>
92+
<b>Technical Notes</b>
93+
<p>
94+
The <code>Accept</code> header option tells the validator to use <a href="https://tools.ietf.org/html/rfc7231#section-5.3.2">content negotiation</a>
95+
to retrieve a manifest at a given URL. This may be used to retrieve manifests from service
96+
providers that support content negotiation for switching between IIIF versions.
97+
</p>
8998
<p>
9099
If you would like to use the validator programatically, there are two options:
91100
</p>
92101
<ul>
93102
<li><a href="https://github.com/IIIF/presentation-validator">Download</a> the code from github and run it locally.</li>
94-
<li>Use it online with JSON based output, by an HTTP GET to this endpoint:<br/>https://presentation-validator.iiif.io/validate?version=2.1&amp;url=<i>manifest-url-here</i></li>
103+
<li>Use it online with JSON based output, by an HTTP GET to this endpoint: <br/>
104+
http://iiif.io/api/presentation/validator/service/validate?version=2.1&amp;url=<i>manifest-url-here</i>&accept=<i>true|false</i>
105+
</li>
95106
</ul>
96107
</div>
97108
</section>
@@ -115,25 +126,27 @@ <h3>Validation Results:</h3>
115126
// Call out to the validation service and get a result
116127
function handleSubmission(e) {
117128
e.preventDefault();
129+
118130
var data = {
119131
url: $("input[name='url']").val(),
120-
version: $("select[name='version']").val()
121-
}
132+
version: $("select[name='version']").val(),
133+
accept: $("#accept").is(":checked"),
134+
};
122135
$('#results-content').html('Processing ' + data.version + " validation...");
123136
$('#results').show();
124137
var url = $('#manifest-validation-form').attr("action");
125-
$.getJSON(url,data,handleValidationResponse);
138+
$.getJSON(url, data, handleValidationResponse);
126139
}
127140

128141
// Handle validation service response, render response block
129142
function handleValidationResponse(data) {
130-
str = '<div style="margin-left: 20px">'
131-
str += '<div>URL Tested: '+ data.url + '</div><br/>'
143+
var str = '<div style="margin-left: 20px">';
144+
str += '<div>URL Tested: '+ data.url + '</div><br/>';
132145
if (data.okay) {
133-
str += '<div><h2 style="color:green">Validated successfully</h2></div>'
146+
str += '<div><h2 style="color:green">Validated successfully</h2></div>';
134147
} else {
135148
if (data.errorList) {
136-
for (var i = 0; i < data.errorList.length; i++) {
149+
for (var i = 0, len = data.errorList.length; i < len; i++) {
137150
var error = data.errorList[i];
138151
str+='<div>';
139152
str+='<h2 style="color:red">' + error.title + '</h2>';
@@ -148,12 +161,12 @@ <h3>Validation Results:</h3>
148161
str+='</div>';
149162
}
150163
} else {
151-
str += '<div><h2 style="color:red">Validation Error: '+data.error+'</h2></div>'
164+
str += '<div><h2 style="color:red">Validation Error: '+data.error+'</h2></div>';
152165
}
153166
}
154167
if (data.warnings && data.warnings.length){
155168
str += '<div style="margin-top: 20px">Warnings:<ul>';
156-
for(var i =0; i < data.warnings.length; i++) {
169+
for(var i =0, len = data.warnings.length; i < len; i++) {
157170
str+= '<li>'+data.warnings[i]+'</li>';
158171
}
159172
str += '</ul></div>';

tests/test_validator.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ def test01_get_bottle_app(self):
6767

6868
def test02_fetch(self):
6969
v = val_mod.Validator()
70-
(data, wh) = v.fetch('file:fixtures/1/manifest.json')
70+
(data, wh) = v.fetch('file:fixtures/1/manifest.json', 'false')
7171
self.assertTrue(data.startswith('{'))
72-
self.assertRaises(URLError, v.fetch, 'file:DOES_NOT_EXIST')
72+
self.assertRaises(URLError, v.fetch, 'file:DOES_NOT_EXIST', 'false')
7373

7474
def test03_check_manifest(self):
7575
v = val_mod.Validator()
@@ -126,6 +126,21 @@ def test05_do_GET_test(self):
126126
v.fetch = Mock(return_value=(read_fixture('fixtures/1/manifest.json'), MockWebHandle()))
127127
j = json.loads(v.do_GET_test())
128128
self.assertEqual(j['okay'], 0)
129+
# Check v3 requests pass
130+
request = LocalRequest({'QUERY_STRING': 'version=3.0&url=https://a.b/&accept=true'})
131+
v.fetch = Mock(return_value=(read_fixture('fixtures/3/full_example.json'), MockWebHandle()))
132+
j = json.loads(v.do_GET_test())
133+
self.assertEqual(j['okay'], 1)
134+
# Check v3 requests allow accept = false
135+
request = LocalRequest({'QUERY_STRING': 'version=3.0&url=https://a.b/&accept=false'})
136+
v.fetch = Mock(return_value=(read_fixture('fixtures/3/full_example.json'), MockWebHandle()))
137+
j = json.loads(v.do_GET_test())
138+
self.assertEqual(j['okay'], 1)
139+
# Check v2 requests do not validate v3 manifests
140+
request = LocalRequest({'QUERY_STRING': 'version=2.1&url=https://a.b/&accept=false'})
141+
v.fetch = Mock(return_value=(read_fixture('fixtures/3/full_example.json'), MockWebHandle()))
142+
j = json.loads(v.do_GET_test())
143+
self.assertEqual(j['okay'], 0)
129144

130145
def test06_index_route(self):
131146
"""Test index page."""
@@ -181,10 +196,13 @@ def test07_check_manifest3(self):
181196
j = json.loads(v.check_manifest(data, '3.0'))
182197

183198
if j['okay'] == 1:
184-
print ("Expected {} to fail validation but it passed....".format(bad_data))
199+
print("Expected {} to fail validation but it passed....".format(bad_data))
185200

186201
self.assertEqual(j['okay'], 0)
187202

203+
def printValidationerror(self, filename, errors):
204+
print('Failed to validate: {}'.format(filename))
205+
188206
def test08_errortrees(self):
189207
with open('fixtures/3/broken_service.json') as json_file:
190208
iiif_json = json.load(json_file)
@@ -338,15 +356,15 @@ def helperRunValidation(self, validator, iiifFile, version="3.0"):
338356
data = fh.read()
339357
return json.loads(validator.check_manifest(data, '3.0'))
340358

341-
def printValidationerror(self, filename, errors):
342-
print ('Failed to validate: {}'.format(filename))
343359
errorCount = 1
360+
344361
for err in errors:
345362
print(err['title'])
346363
print(err['detail'])
347364
print('\n Path for error: {}'.format(err['path']))
348365
print('\n Context: {}'.format(err['context']))
349366
errorCount += 1
350367

368+
351369
if __name__ == '__main__':
352370
unittest.main()

0 commit comments

Comments
 (0)