blob: 5acc7c2a1ec11be536232bbbb2f5b0d5e87c9d1e [file]
= 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 (`&lt;div&gt;`), 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]
----