| = AtomPub Guide | |
| Apache Roller Weblogger | |
| :toc: | |
| :sectnums: | |
| :imagesdir: ./images | |
| == Overview | |
| This is the reference for the Atom Publishing Protocol (AtomPub) interface of | |
| the Apache Roller Weblogger. With it, a program can create and edit weblog | |
| entries, upload media files, and manage a weblog's categories and templates | |
| over HTTP. Comments are moderated in Roller's web interface, not over AtomPub. | |
| Roller implements AtomPub as defined in | |
| https://www.rfc-editor.org/rfc/rfc5023[RFC 5023], using the Atom format of | |
| https://www.rfc-editor.org/rfc/rfc4287[RFC 4287]. Categories and templates | |
| are Roller extensions; they use ordinary Atom entries plus a few | |
| extension elements, so any AtomPub client library can read and write them. | |
| === Copyright and trademark information | |
| The contents of this document are subject to the terms of the Apache | |
| Software License. | |
| All trademarks within this document belong to legitimate owners. | |
| === Conventions | |
| * All URIs in this guide are relative to the AtomPub base URL, | |
| `https://<host>/<context>/roller-services/app`. In the examples the base URL is | |
| `https://example.com/roller/roller-services/app`. | |
| * `{h}` stands for a weblog handle, for example `myblog`. | |
| * Namespaces and the prefixes used in this guide: | |
| + | |
| [cols="1,3",options="header"] | |
| |=== | |
| |Prefix |Namespace | |
| |(default) |`http://www.w3.org/2005/Atom` (Atom, RFC 4287) | |
| |`app` |`http://www.w3.org/2007/app` (AtomPub, RFC 5023) | |
| |`roller` |`http://roller.apache.org/ns/atompub/1.0` (Roller extensions) | |
| |=== | |
| * Media types: | |
| + | |
| [cols="2,3",options="header"] | |
| |=== | |
| |Media type |Used for | |
| |`application/atomsvc+xml` |The service document | |
| |`application/atom+xml;type=feed` |Collections (feeds) | |
| |`application/atom+xml;type=entry` |Entries, in requests and responses | |
| |`application/atomcat+xml` |Category documents | |
| |=== | |
| The XML examples in this guide are generated from Roller's own code by its | |
| test suite, so they show exactly what the server writes. | |
| == Enabling AtomPub | |
| A Roller administrator controls AtomPub in *Server Administration > Configuration | |
| > Web Services Settings*: | |
| [cols="2,1,4",options="header"] | |
| |=== | |
| |Setting |Default |Meaning | |
| |`webservices.enableAtomPub` |`false` | |
| |Turns AtomPub on. While it is off, every AtomPub URL answers `404 Not Found`. | |
| |`webservices.atomPubAuth` |`basic` | |
| |Authentication method. Only `basic` is supported; any other value, including | |
| `oauth` or `wsse` left over from an older release, refuses all requests (see | |
| <<Authentication>>). | |
| |`webservices.atomPubMaxEntrySize` |`1048576` | |
| |Largest Atom entry body accepted, in bytes. A larger entry gets | |
| `413 Request Entity Too Large`. Media uploads are not affected; they use the | |
| file upload limits. | |
| |`themes.customtheme.allowed` |`false` | |
| |Must be on for the templates collection to exist. | |
| |=== | |
| Media uploads also need *File Upload Settings*: uploads enabled, the allowed | |
| file types, and the size and quota limits. | |
| == Authentication | |
| Every AtomPub request must be authenticated. Roller checks the credentials | |
| before it reads the request body. A request that fails authentication gets | |
| `401 Unauthorized`. | |
| Always use HTTPS: BASIC authentication sends the password with every request. | |
| === BASIC | |
| Roller supports only BASIC authentication for AtomPub. OAuth 1.0a was | |
| removed in Roller 7. With `webservices.atomPubAuth` set to `basic`, send the Roller user name and | |
| password in an HTTP `Authorization` header: | |
| ---- | |
| Authorization: Basic ZGF2ZTpzZWNyZXQ= | |
| ---- | |
| The user must be enabled. Without valid credentials Roller answers `401` with | |
| `WWW-Authenticate: Basic realm="Roller"`. | |
| == Discovery: the service document | |
| `GET /` returns the service document. It has one workspace for each weblog the | |
| user is a member of, and lists only the collections the user may use: | |
| [cols="2,3",options="header"] | |
| |=== | |
| |Collection |Listed when | |
| |Weblog Entries, Media Files (one per media directory) | |
| |The user is a member of the weblog | |
| |Categories | |
| |The user may post to the weblog (permission `POST` or higher) | |
| |Templates | |
| |The user is an administrator of the weblog (`ADMIN`), the weblog uses a custom | |
| theme, and the site allows custom themes | |
| |=== | |
| The entries collection describes the weblog's categories twice: inline, for | |
| older clients, and as a link to the weblog's category document. A third, | |
| empty `app:categories` element says that any other term is accepted as a tag. | |
| .Service document | |
| [source,xml] | |
| ---- | |
| include::atompub/examples/service.xml[] | |
| ---- | |
| == Requests and responses | |
| * Send entries with `Content-Type: application/atom+xml;type=entry`. | |
| * Roller refuses an entry that is not well-formed XML or that has a `DOCTYPE` | |
| (`400 Bad Request`). | |
| * A successful `POST` answers `201 Created` with the new member as the body and | |
| its edit URI in the `Location` and `Content-Location` headers. | |
| * A successful `PUT` or `DELETE` answers `200 OK` without a body. | |
| * The entries and media collections return at most 20 members. When | |
| there are more, the feed has `next` and `previous` links; follow them to page | |
| through. The categories and templates collections return all members. | |
| == Weblog entries | |
| [cols="2,1,4",options="header"] | |
| |=== | |
| |URI |Method |Action | |
| |`/{h}/entries`, `/{h}/entries/{offset}` |GET |List entries, newest first | |
| |`/{h}/entries` |POST |Create an entry | |
| |`/{h}/entry/{id}` |GET |Read an entry | |
| |`/{h}/entry/{id}` |PUT |Replace an entry | |
| |`/{h}/entry/{id}` |DELETE |Delete an entry | |
| |=== | |
| *Permission:* to list or create entries, `POST` on the weblog. To read, change | |
| or delete an entry, the user must be allowed to edit that entry. | |
| [cols="2,1,4",options="header"] | |
| |=== | |
| |Element |Access |Meaning | |
| |`atom:title` |read/write |Entry title | |
| |`atom:content` |read/write |Entry text, as HTML (`type="html"`) | |
| |`atom:summary` |read/write |Entry summary, as HTML | |
| |`atom:published` |read/write |Publication time; now when missing | |
| |`atom:updated` |read/write |Update time; now when missing | |
| |`atom:category` with the weblog's scheme |read/write | |
| |The weblog category. The scheme is the weblog's URL. Roller keeps the first | |
| one that names an existing category; otherwise it uses the weblog's default | |
| category. | |
| |`atom:category` without a scheme |read/write |A tag | |
| |`app:control/app:draft` |read/write |`yes` saves a draft; `no` publishes | |
| |`atom:author` |read |The user who created the entry; ignored on input | |
| |`atom:link rel="alternate"` |read |The entry's permalink | |
| |`app:edited` |read |Last change | |
| |=== | |
| .Creating an entry (request) | |
| [source,xml] | |
| ---- | |
| include::atompub/examples/entry-request.xml[] | |
| ---- | |
| .Created entry (response) | |
| [source,xml] | |
| ---- | |
| include::atompub/examples/entry-response.xml[] | |
| ---- | |
| .Entries collection | |
| [source,xml] | |
| ---- | |
| include::atompub/examples/entries-feed.xml[] | |
| ---- | |
| == Media files | |
| [cols="2,1,4",options="header"] | |
| |=== | |
| |URI |Method |Action | |
| |`/{h}/resources/{dir}`, `/{h}/resources/{dir}/{offset}` |GET | |
| |List the files in a media directory | |
| |`/{h}/resources`, `/{h}/resources/{dir}` |POST | |
| |Upload a file to the default or the named directory | |
| |`/{h}/resource/{dir}/{name}.media-link` |GET |Read the media link entry | |
| |`/{h}/resource/{dir}/{name}` |GET |Download the file | |
| |`/{h}/resource/{dir}/{name}` |PUT |Replace the file's contents | |
| |`/{h}/resource/{dir}/{name}` or `....media-link` |DELETE |Delete the file | |
| |=== | |
| *Permission:* `POST` on the weblog. | |
| To upload, `POST` the file's bytes with its media type in `Content-Type` and a | |
| file name in the `Slug` header: | |
| ---- | |
| POST /myblog/resources/default HTTP/1.1 | |
| Content-Type: image/jpeg | |
| Slug: sunset | |
| ---- | |
| * Without `Slug`, Roller names the file from the weblog handle and the date. | |
| The file name extension comes from the media type, without parameters or a | |
| `+xml`/`+json` suffix (`image/svg+xml` gives `.svg`, `image/jpeg` gives | |
| `.jpg`). A `Slug` that already ends in a matching extension keeps it. | |
| * The media type must be one the site allows; the service document lists them | |
| in each media collection's `app:accept`. Roller stores the type it derives | |
| from the file name and contents, not only the one the client sends. | |
| * A `POST` without `Content-Type` gets `415 Unsupported Media Type`. | |
| * A file that breaks the upload rules (type, size, quota) or that already | |
| exists is refused. | |
| Downloads of types that a browser could run, such as HTML, are served as | |
| attachments with `X-Content-Type-Options: nosniff`. | |
| .Media link entry | |
| [source,xml] | |
| ---- | |
| include::atompub/examples/media-link-entry.xml[] | |
| ---- | |
| == Categories | |
| === Category document | |
| `GET /{h}/categories.atomcat` returns the weblog's categories as an RFC 5023 | |
| category document. Clients use it to offer category choices for new entries. | |
| *Permission:* `POST` on the weblog. | |
| [source,xml] | |
| ---- | |
| include::atompub/examples/categories.xml[] | |
| ---- | |
| === Categories collection | |
| [cols="2,1,4",options="header"] | |
| |=== | |
| |URI |Method |Action | |
| |`/{h}/categories` |GET |List the weblog's categories | |
| |`/{h}/categories` |POST |Create a category | |
| |`/{h}/category/{id}` |GET |Read a category | |
| |`/{h}/category/{id}` |PUT |Change a category | |
| |`/{h}/category/{id}`, `/{h}/category/{id}?moveTo={otherId}` |DELETE | |
| |Delete a category | |
| |=== | |
| *Permission:* `POST` on the weblog. | |
| [cols="2,1,4",options="header"] | |
| |=== | |
| |Element |Access |Meaning | |
| |`atom:title` |read/write | |
| |Name; required on `POST`; at most 255 characters. It must be unique in the | |
| weblog and may not contain `<`, `>`, `&` or `"`. | |
| |`atom:summary` |read/write |Description, at most 255 characters | |
| |`roller:image` |read/write |Image URL (`http` or `https`); empty removes it | |
| |`roller:position` |read |Order in the weblog's category list | |
| |`roller:inUse` |read |`true` when entries use the category, or it is the | |
| weblog's default category | |
| |=== | |
| A duplicate name gets `409 Conflict`. Deleting a category is refused with | |
| `409 Conflict` when: | |
| * entries use it, or it is the weblog's default category, and the request has no | |
| `moveTo`. With `moveTo={otherId}`, Roller first moves the entries to that | |
| category, which also becomes the default if needed; | |
| * it is the weblog's only category. | |
| .Creating a category (request) | |
| [source,xml] | |
| ---- | |
| include::atompub/examples/category-request.xml[] | |
| ---- | |
| .Category entry | |
| [source,xml] | |
| ---- | |
| include::atompub/examples/category-entry.xml[] | |
| ---- | |
| == Templates | |
| The templates collection lets a client edit the templates of a weblog that uses | |
| a custom theme. Weblogs that use a shared theme have no templates collection: | |
| its URIs answer `404 Not Found`, even for administrators. | |
| [cols="2,1,4",options="header"] | |
| |=== | |
| |URI |Method |Action | |
| |`/{h}/templates` |GET |List the weblog's templates | |
| |`/{h}/templates` |POST |Create a template | |
| |`/{h}/template/{id}` |GET |Read a template | |
| |`/{h}/template/{id}` |PUT |Change a template | |
| |`/{h}/template/{id}` |DELETE |Delete a template | |
| |=== | |
| *Permission:* `ADMIN` on the weblog. The weblog must use a custom theme and | |
| `themes.customtheme.allowed` must be on. | |
| [cols="2,1,4",options="header"] | |
| |=== | |
| |Element |Access |Meaning | |
| |`atom:title` |read/write |Name; required on `POST`; unique in the weblog; at | |
| most 255 characters | |
| |`atom:summary` |read/write |Description, at most 255 characters | |
| |`atom:content type="text"` |read/write |The standard template (Velocity) | |
| |`roller:rendition type="mobile"` |read/write | |
| |The mobile template. Missing on `PUT`: unchanged. Empty: removed, so mobile | |
| visitors see the standard template. | |
| |`roller:action` |read/write | |
| |`weblog`, `permalink`, `search`, `tagsindex`, `stylesheet` or `custom`. | |
| Set on `POST` only; `custom` when missing. | |
| |`roller:link` |read/write |The template's URL path; unique in the weblog; at | |
| most 255 characters | |
| |`roller:navbar` |read/write |`true` to list the template in the navigation bar | |
| |`roller:hidden` |read/write |`true` to hide the template from visitors | |
| |`roller:contentType` |read/write |Output media type; empty for automatic | |
| |`roller:required` |read |`true` for templates the theme needs | |
| |=== | |
| Rules, the same as on the *Templates* pages: | |
| * Each action other than `custom` may have one template. Adding a second one, or | |
| a `stylesheet` template, gets `409 Conflict`. | |
| * A `weblog` template is always named `Weblog`. | |
| * The name, description, link, navigation bar and hidden settings change only | |
| for custom templates. For the others Roller changes only the template text and | |
| the content type. | |
| * Required templates (`Weblog` and `_day`) cannot be deleted (`409 Conflict`). | |
| Template text is Velocity markup. Send it as escaped text (`<div>`), not | |
| as XML elements, or use a CDATA section. | |
| .Creating a template (request) | |
| [source,xml] | |
| ---- | |
| include::atompub/examples/template-request.xml[] | |
| ---- | |
| .Template entry | |
| [source,xml] | |
| ---- | |
| include::atompub/examples/template-entry.xml[] | |
| ---- | |
| == Errors | |
| Errors carry a short message. Server errors (`5xx`) carry no detail; the cause | |
| is in Roller's log. | |
| [cols="1,4",options="header"] | |
| |=== | |
| |Status |Meaning | |
| |`400 Bad Request` |The entry is not well-formed, has a `DOCTYPE`, or has an | |
| invalid value (unknown action, invalid category name or image URL, | |
| a value longer than 255 characters, bad `moveTo`) | |
| |`401 Unauthorized` |Authentication failed, or the user lacks the permission the | |
| resource needs | |
| |`404 Not Found` |AtomPub is off, the resource does not exist or belongs to | |
| another weblog, or the collection is not available (for example templates of a | |
| shared theme) | |
| |`405 Method Not Allowed` |The method is not supported | |
| |`409 Conflict` |The change conflicts with the weblog: duplicate name or link, | |
| a template action that is taken, a required template, a category in use or the | |
| last category | |
| |`413 Request Entity Too Large` |The entry is larger than | |
| `webservices.atomPubMaxEntrySize` | |
| |`415 Unsupported Media Type` |A `POST` without `Content-Type`, or media sent to | |
| a collection that accepts only entries | |
| |`500 Internal Server Error` |An unexpected error | |
| |=== | |
| == Example session | |
| These `curl` commands use BASIC authentication. Replace the host, user and IDs | |
| with your own. | |
| [source,bash] | |
| ---- | |
| APP=https://example.com/roller/roller-services/app | |
| AUTH="dave:secret" | |
| # 1. Discover the weblogs and collections | |
| curl -u "$AUTH" "$APP/" | |
| # 2. Create an entry | |
| curl -u "$AUTH" -X POST "$APP/myblog/entries" \ | |
| -H "Content-Type: application/atom+xml;type=entry" \ | |
| --data-binary @entry-request.xml | |
| # 3. Upload an image | |
| curl -u "$AUTH" -X POST "$APP/myblog/resources/default" \ | |
| -H "Content-Type: image/jpeg" -H "Slug: sunset" \ | |
| --data-binary @sunset.jpg | |
| # 4. Create a category | |
| curl -u "$AUTH" -X POST "$APP/myblog/categories" \ | |
| -H "Content-Type: application/atom+xml;type=entry" \ | |
| --data-binary @category-request.xml | |
| # 5. Change a template's mobile version (edit URI from GET $APP/myblog/templates) | |
| curl -u "$AUTH" -X PUT "$APP/myblog/template/0e5d8b62" \ | |
| -H "Content-Type: application/atom+xml;type=entry" \ | |
| --data-binary @template-request.xml | |
| ---- | |
| == Schemas | |
| * Atom entries and feeds: the RELAX NG grammar in | |
| https://www.rfc-editor.org/rfc/rfc4287#appendix-B[RFC 4287, Appendix B]. | |
| * Service and category documents: the grammar in | |
| https://www.rfc-editor.org/rfc/rfc5023#appendix-B[RFC 5023, Appendix B]. | |
| * Roller's extension elements: `atompub/roller-atompub.rnc` | |
| in Roller's `docs` directory: | |
| + | |
| [source] | |
| ---- | |
| include::atompub/roller-atompub.rnc[lines=17..-1] | |
| ---- |