diff --git a/config.example.wikimedia.yaml b/config.example.wikimedia.yaml index b0a4dae3c..c63df098e 100644 --- a/config.example.wikimedia.yaml +++ b/config.example.wikimedia.yaml @@ -33,6 +33,11 @@ default_project: &default_project cache_control: s-maxage=86400, max-age=86400 # 10 days Varnish caching, one day client-side purged_cache_control: s-maxage=864000, max-age=86400 + pdf: + # Cache PDF for 5 minutes since it's not purged + cache_control: s-maxage=600, max-age=600 + uri: https://pdf-electron.wmflabs.org + secret: secret skip_updates: false # A different project template, sharing configuration options. diff --git a/config.test.yaml b/config.test.yaml index b82693fbf..6c5a00fbb 100644 --- a/config.test.yaml +++ b/config.test.yaml @@ -38,6 +38,11 @@ default_project: &default_project host: http://appservice.wmflabs.org events: {} purged_cache_control: test_purged_cache_control + pdf: + # Cache PDF for 5 minutes since it's not purged + cache_control: s-maxage=600, max-age=600 + uri: https://pdf-electron.wmflabs.org + secret: secret skip_updates: false labs_project: &labs_project diff --git a/projects/wmf_default.yaml b/projects/wmf_default.yaml index 06b07728e..b84a1f24e 100644 --- a/projects/wmf_default.yaml +++ b/projects/wmf_default.yaml @@ -54,6 +54,8 @@ paths: - path: v1/random.yaml options: '{{merge({"random_cache_control": "s-maxage=2, max-age=1"}, options.mobileapps)}}' + - path: v1/pdf.yaml + options: '{{options.pdf}}' /feed: x-modules: - path: v1/feed.js diff --git a/projects/wmf_wiktionary.yaml b/projects/wmf_wiktionary.yaml index 40734cb49..b350674bd 100644 --- a/projects/wmf_wiktionary.yaml +++ b/projects/wmf_wiktionary.yaml @@ -50,6 +50,8 @@ paths: options: response_cache_control: '{{options.purged_cache_control}}' host: '{{options.mobileapps.host}}' + - path: v1/pdf.yaml + options: '{{options.pdf}}' /transform: x-modules: - path: v1/transform.yaml diff --git a/test/features/feed.js b/test/features/feed.js index 9bac73b52..eca6a22f3 100644 --- a/test/features/feed.js +++ b/test/features/feed.js @@ -3,7 +3,6 @@ const assert = require('../utils/assert.js'); const server = require('../utils/server.js'); const preq = require('preq'); -const P = require('bluebird'); function assertStorageRequest(requests, method, bucket, expected) { const storageRequests = requests.filter((log) => diff --git a/test/features/pdf.js b/test/features/pdf.js new file mode 100644 index 000000000..348d2d309 --- /dev/null +++ b/test/features/pdf.js @@ -0,0 +1,25 @@ +'use strict'; + +const assert = require('../utils/assert.js'); +const server = require('../utils/server.js'); +const preq = require('preq'); + +describe('Feed', () => { + + before(() => server.start()); + + it('Should get PDF for a page', () => { + return preq.get({ + uri: `${server.config.hostPort}/ru.wikipedia.org/v1/page/pdf/%D0%94%D0%B0%D1%80%D1%82_%D0%92%D0%B5%D0%B9%D0%B4%D0%B5%D1%80` + }) + .then((res) => { + assert.deepEqual(res.status, 200); + assert.deepEqual(res.headers['content-disposition'], + 'attachment; filename=%D0%94%D0%B0%D1%80%D1%82_%D0%92%D0%B5%D0%B9%D0%B4%D0%B5%D1%80.pdf;' + + ' filename*=%D0%94%D0%B0%D1%80%D1%82_%D0%92%D0%B5%D0%B9%D0%B4%D0%B5%D1%80.pdf'); + assert.deepEqual(res.headers['content-type'], 'application/pdf'); + assert.ok(/"\d+\/[\d\w-]+"/.test(res.headers.etag)); + assert.ok(res.body.length !== 0); + }); + }); +}); diff --git a/v1/pdf.yaml b/v1/pdf.yaml new file mode 100644 index 000000000..73f5c398d --- /dev/null +++ b/v1/pdf.yaml @@ -0,0 +1,89 @@ +swagger: '2.0' +info: + version: '1.0.0-beta' + title: MediaWiki PDF API + description: Page PDF Render API + termsOfService: https://github.com/wikimedia/restbase#restbase + contact: + name: Services + email: services@lists.wikimedia.org + url: https://www.mediawiki.org/wiki/Services + license: + name: Apache licence, v2 + url: https://www.apache.org/licenses/LICENSE-2.0 +paths: + /pdf/{title}: + x-route-filters: + - path: ./lib/revision_table_access_check_filter.js + options: + redirect_cache_control: '{{options.cache_control}}' + get: + tags: + - Page content + summary: Get a page as PDF + description: | + Renders the page content as PDF. + + Stability: [experimental](https://www.mediawiki.org/wiki/API_versioning#Experimental) + produces: + - application/pdf + parameters: + - name: title + in: path + description: "Page title. Use underscores instead of spaces. Example: `Main_Page`." + type: string + required: true + responses: + '200': + description: The PDF render of an article + schema: + type: file + headers: + ETag: + description: > + Syntax: "{revision}/{tid}". + Example: "701384379/154d7bca-c264-11e5-8c2f-1b51b33b59fc" + '404': + description: Unknown page title + schema: + $ref: '#/definitions/problem' + default: + description: Error + schema: + $ref: '#/definitions/problem' + + x-request-handler: + - get_latest_revision: + request: + method: get + uri: /{domain}/sys/page_revisions/page/{title} + - get_pdf_from_backend: + request: + method: get + uri: '{{options.uri}}/pdf?accessKey={options.secret}&url=https://{{domain}}/wiki/{title}' + return: + status: 200 + headers: + # Firefox supports filename*= syntax while Chrome respect filename=. Safari is stupid and understands neither + content-disposition: 'attachment; filename={request.params.title}.pdf; filename*={request.params.title}.pdf' + content-type: '{{get_pdf_from_backend.headers.content-type}}' + content-length: '{{get_pdf_from_backend.headers.content-length}}' + cache-control: '{{default(options.cache_control, "s-maxage=600, max-age=600")}}' + etag: '{{get_latest_revision.headers.etag}}' + body: '{{get_pdf_from_backend.body}}' + x-monitor: false # PDF generation is expensive and it's not stored, so don't run checker script + +definitions: + # A https://tools.ietf.org/html/draft-nottingham-http-problem + problem: + required: + - type + properties: + type: + type: string + title: + type: string + detail: + type: string + instance: + type: string \ No newline at end of file