Atom API Update
by Joe Gregorio
February 03, 2004
Version 8 of the draft Atom API for weblog authoring has just been
released. The new version contains substantial changes from version
7, which was covered by Mark Pilgrim in a recent XML.com article "The Atom
API ". Despite the changes, the API hasn't left its guiding
principles behind:
- Well-defined data model -- with schemas and everything!
- Doc-literal style web services, not RPC
- Take full advantage of XML and namespaces
- Take full advantage of HTTP
- Secure; thus, no passwords in the clear
While version 8 is longer than version 7 the size of the API has
shrunk, with simplification happening on several fronts. Two of
the big changes have to do with API discovery. Version 7 contained
a file called the Introspection file. The Introspection file
listed all of the API endpoints supported by the server and the
URIs that handled them. Here is an example of a version 7
Introspection file:
<?xml version="1.0" encoding="utf-8"?>
<introspection xmlns="http://purl.org/atom/ns#" >
<search-entries>
http://example.com/myblog/atom.cgi/search
</search-entries>
<create-entry>
http://example.com/myblog/atom.cgi/edit
</create-entry>
<edit-template>
http://example.com/atom.cgi/templates
</edit-template>
<user-prefs>
http://example.com/myblog/atom.cgi/prefs
</user-prefs>
<categories>
http://example.com/atom.cgi/categories
</categories>
</introspection>
Each of the URIs listed in the Introspection file may have
different mime-types and allowed HTTP methods. For example, you
can only do a GET on the URI in search-entries, and
it returns the results in an XML format specific to search results
in the Atom API.
This has changed dramatically in revision 8: there are only 3 types
of URIs and they all work with the Atom format, either as a whole
or by using an entry fragment of the Atom feed
format.
| URI Type | Specified Methods |
PostURI POST
EditURI GET, PUT, DELETE
FeedURI GET
- PostURI
- Posting an Atom entry element to this URI
creates a new entry on the site. This has the same
functionality as the
create-entry facet in
revision 7. The behavior of the PostURI also covers creating
comments or trackbacks,
which was documented seperately under revision
7. - EditURI
- Editing the content of an Entry
is unchanged from revision 7 and won't be covered further in
this article. See
Mark
Pilgrim's article for
discussion.
- FeedURI
- Doing a GET on this URI
returns information formatted as an Atom Feed. More on this
later.
Another aspect of Atom is auto-discovery. One of the first uses of
auto-discovery was the HTML link tag to provide a machine readable
pointer to a site's feed. The Atom API also uses a link tag to
provide a method for automatically discovering the URI of the
Introspection file from a web page. Here is an example of such a
link tag, which points from a web page to the Introspection
file. (Note: this is in the obsolete revision 7 format.)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
lang="en" xml:lang="en">
<head>
<title>My Weblog</title>
<link rel="service.edit" type="application/atom+xml"
href="/myblog/atom.cgi/introspection" title="Atom API" />
</head>
<body>
...
</body>
</html>
There are several weaknesses with this approach. The first weakness
is that the Introspection file introduces yet another XML format
that needs to be parsed. The Atom API uses the Atom format and
also introduces different formats for Introspection, Preferences,
and Search results. Why are mulitple formats bad? The more
formats there are the more difficult it is to implement the
API. If the number of formats could be reduced, then the
implementation work could be simplified. Fewer file formats means
easier implementations.
The second drawback with this approach is that auto-discovery is
only available in the HTML file. Its helpful if you are viewing
the page in a browser or have the URI of the resource which
contains the link. But it leaves aggregators at a distinct
disadvantage, making it harder for them to take advantage of the
Atom API. That is a bit of a handicap given the number of
aggregators that already support weblog editing functions; they
would need to retrieve the HTML of the home page to discover the
URI of the Introspection file. It would be better if the
auto-discovery information could also exist in the Atom feed for a
site, a file which an aggregator is already going to have.
Version 8 of the draft RFC solves both of these problems by
changing the format of the Introspection file to be an Atom
Feed. It also uses a new link element that mirrors the one found
in HTML.
The new <link> tag
Let's review the format of the link tag in Atom 0.2, which revision
7 of the API was based on:
<link>http://www.example.com/</link>
Revision 8 of the API is based on the format in Atom 0.3; thus the
linke tag now looks like this:
<link rel="alternate" type="text/html"
href="http://www.example.com/"/>
If should look familiar, the style of the HTML link tag has been
borrowed for Atom. Note that each link now has a 'rel' attribute
which denotes its relation to the containing element. That is, if
the 'link' tag is in an entry, then the 'rel' attribute states the
relationship between the entry and the thing at the other end of
the link URI. Ditto for the feed: the 'rel' states the
relationship between the feed and the resource at the given URI.
The 'link' element also contains an 'href' attribute which
contains the URI, which used to be a value of the element in older
specs and not in an attribute. Lastly the 'type' attribute
indicates the mime-type of the resource found at the URI given in
the 'href' attribute. These three pieces of information combine to
uniquely determine a resource. For example, consider a feed
element containing a link of the form:
<link rel="service.post"
type="application/atom+xml"
href="http://blog.example.net" />
Now a 'rel' of 'service.post' means that the URI
given in the href attribute is a URI of type PostURI.
This implies that posting an Atom Entry fragment to
the URI http://blog.example.net
creates a new entry on the site.
Here's another example. A link tag can appear in one of two places
inside an Atom Feed, and it may have different meanings based on
which element it is contained in. The two places a link tag can
appear are as a child of the feed element or as the child of one
of the entry elements. Here is an example Atom feed with two link
elements, both with a 'rel' attribute set to 'alternate', that
appear in the feed and entry elements.
<?xml version="1.0" encoding="utf-8"?>
<feed version="0.3" xmlns="http://purl.org/atom/ns#">
<title>BitWorking</title>
<link
rel="alternate"
type="text/html"
href="http://blog.example.org/"/>
<modified>2003-12-13T18:30:02Z</modified>
<author>
<name>Joe Gregorio</name>
</author>
<entry>
<title>The Big Apple</title>
<link
rel="alternate"
type="text/html"
href="http://blog.example.org/2003/12/13/apple"/>
<id>tag:blog.example.org,2003:3.2397</id>
<issued>2003-12-13T08:29:29-04:00</issued>
<modified>2003-12-13T18:30:02Z</modified>
</entry>
</feed>
When found in a Feed, the 'href' attribute points to the home page
that this Atom feed represents. When found in an Entry, the
'href' attribute contains the URI of the page that this Entry is
about. Note that the meaning has changed based on context. Let's
add another set of link tags, this time with a 'rel' attribute
with a value of 'comments'.
<?xml version="1.0" encoding="utf-8"?>
<feed version="0.3" xmlns="http://purl.org/atom/ns#">
<title>BitWorking</title>
<link
rel="alternate"
type="text/html"
href="http://blog.example.org/"/>
<link
rel="comments"
type="text/html"
href="http://blog.example.org/comments.atom"/>
<modified>2003-12-13T18:30:02Z</modified>
<author>
<name>Joe Gregorio</name>
</author>
<entry>
<title>The Big Apple</title>
<link
rel="alternate"
type="text/html"
href="http://blog.example.org/2003/12/13/apple"/>
<link
rel="comments"
type="text/html"
href="http://blog.example.org/2003/12/13/atom03/comments.atom"/>
<id>tag:blog.example.org,2003:3.2397</id>
<issued>2003-12-13T08:29:29-04:00</issued>
<modified>2003-12-13T18:30:02Z</modified>
</entry>
</feed>
Note that these examples of using the link tag aren't normative and
that they are open to interpretation. The point is to demonstrate
some of the power that comes with the new link tag. The semantics
of the allowed values for the rel attribute are
currently under discussion.
The FeedURI
The new FeedURI takes the place of the search interface and also of
the Introspection file. It does that by leveraging the flexibility
of the new link tag format. Remember our three URI types? With a
link tag that has type="application/atom+xml", the following 'rel'
values map to our three types of URIs:
| URI Type | Rel |
PostURI service.post
EditURI service.edit
FeedURI service.feed
Below is an example Atom Feed found by dereferencing a
FeedURI. Note that it is a valid Atom Feed in it's own right, but
it contains more than the minimum required link tags. Those extra
link tags supply the Introspection and search functionality.
<?xml version="1.0" encoding="utf-8"?>
<feed version="0.3" xmlns="http://purl.org/atom/ns#">
<title>BitWorking</title>
<link
rel="service.post"
type="application/atom+xml"
href="http://blog.example.org/post.cgi"/>
<link
rel="next"
type="application/atom+xml"
href="http://blog.example.org/entries/2-20"/>
<link
rel="alternate"
type="text/html"
href="http://blog.example.org/"/>
<modified>2003-12-13T18:30:02Z</modified>
<author>
<name>Joe Gregorio</name>
</author>
<entry>
<title>The Big Apple</title>
<link
rel="service.edit"
type="application/atom+xml"
href="http://blog.example.org/edit.cgi/apple"/>
<link
rel="alternate"
type="text/html"
href="http://blog.example.org/2003/12/13/apple"/>
<id>tag:blog.example.org,2003:3.2397</id>
<issued>2003-12-13T08:29:29-04:00</issued>
<modified>2003-12-13T18:30:02Z</modified>
</entry>
</feed>
The first link tag in the file has rel="service.post". This means
that the URI http://blog.example.org/post.cgi is the
PostURI and that POSTing an Atom Entry to that URI will create a
new entry on this site.
The second link tag in the file has rel="next". This means that the
URI http://blog.example.org/entries/2-20 is another
Atom Feed, of type FeedURI, that contains the 'next' set of
entries. The word next refers to some ordering of the entries,
which will often mean reverse chronological order. This allows the
client to walk through all the entries on a site in a structured
manner. By having the client follow links with values of either
'prev' or 'next', the client can fetch sets of entries to be
presented to the user for editing.
Note that there is no restriction on the number of entries that can
be present in an Atom feed; a server may present FeedURIs as
entries grouped by day, week, or month.
The first link element in the 'entry' has rel="service.edit", which
means that the
URI http://blog.example.org/edit.cgi/atom03 is the
EditURI to be used to edit that entry.
Typical Editing Scenarios
So how would typical weblog usage scenarios play out?
Create a new Entry
What we need when we talk about publishing a new entry on a site is
the PostURI. The PostURI always appears in the FeedURI, so we'll
locate that first. Now the FeedURI could be entered directly into
the Atom editing client, or the client could support
auto-discovery of the FeedURI. Since the FeedURI is also useful
for searching for old entries to edit, and may contain other
useful link elements, once it is found, it should be persisted so
that the client doesn't have to go through the discovery phase
every time.
Let's walk through the auto-discovery process to see how it works.
Starting from either an Atom feed, or the main web page of a site,
we can load either of those representations and look at the
appropriate "service.feed" link element which points to the
FeedURI. For example,
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>My Weblog</title>
<link rel="service.feed" type="application/atom+xml"
href="http:/blog.example.org/service.atom"
title="Main Blog" />
</head>
<body>
...
</body>
</html>
The FeedURI in this case
is http:/blog.example.org/service.atom. We could also
have discovered the same FeedURI from the sites main syndication
feed. Now the client loads the Atom feed found at the FeedURI and
searches it for the PostURI. For example the FeedURI could
return:
<?xml version="1.0" encoding="utf-8"?>
<feed version="0.3" xmlns="http://purl.org/atom/ns#">
<title>BitWorking</title>
<link
rel="alternate"
type="text/html"
href="http://blog.example.org/"/>
<link
rel="service.post"
type="application/atom+xml"
href="http://blog.example.org/post.cgi"/>
<link
rel="next"
type="application/atom+xml"
href="http://blog.example.org/archives/2-20"/>
<modified>2003-12-13T18:30:02Z</modified>
<author>
<name>Joe Gregorio</name>
</author>
<entry>
<title>The Big Apple</title>
<link
rel="alternate"
type="text/html"
href="http://blog.example.org/2003/12/13/apple"/>
<link
rel="service.edit"
type="application/atom+xml"
href="http://blog.example.org/apple.atom"/>
<id>tag:blog.example.org,2003:3.2397</id>
<issued>2003-12-13T08:29:29-04:00</issued>
<modified>2003-12-13T18:30:02Z</modified>
</entry>
</feed>
The link tag with a rel="service.post" and
type="application/atom+xml" contains the PostURI. In this example
it is http://blog.example.org/post.cgi. Now that we
have the PostURI we can create a new Entry on the site by POST'ing
an Atom entry to that URI.
POST /post.cgi HTTP/1.1
Host: blog.example.org
Content-Type: application/atom+xml
<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://purl.org/atom/ns#">
<title>My Entry Title</title>
<created>2003-11-17T12:29:29Z</created>
<content type="application/xhtml+xml" xml:lang="en">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>Hello, <em>weblog</em> world!</p>
<p>This is my third post <strong>ever</strong>!</p>
</div>
</content>
</entry>
The server responds with an HTTP status code 201 "Created" and
gives the entry's EditURI in the HTTP Location:
header.
HTTP/1.1 201 Created
Location: http://blog.example.org/myblog/atom.cgi/edit/3
Editing an old Entry
To edit an existing entry, we need to find its EditURI. Once we
have that URI we can perform three operations on an entry. We can
do an HTTP GET on it to get the entry, PUT on it to update the
entry, and DELETE on it to delete the entry. Note that each and
every entry has its own URI.
So how do we find the EditURI? There are several options. Once we
created an entry, the server returns the EditURI in the Location:
header. The client software could persist that URI once it gets
that response.
Another way is to find the EditURI by browsing though the
FeedURI. Note in the Atom Feed returned from the FeedURI above
there is a link with rel="next" and a
type="application/atom+xml". This link points to the "next" Atom
Feed of entries on the site. Now I put next in quotes to emphasize
that it's a pretty vague concept. The idea of "next" assumes some
sort of linearity, some way of putting all the entries in
order. In the case of the rel="next", the ordering is defined by
the server and can really be anything, though the most likely
ordering will be reverse chronological, which is the most common
ordering for a weblog. But there is nothing to stop you from
ordering your posts in alphabetical order by title, for example..
To find an entry to edit, the client retrieves the Atom feed at the
FeedURI and presents the entries it contains to the user, along
with a way to navigate to the "next" and "prev" Feeds. In this way
the user can navigate through all entries till she finds the one
she wants to edit.
Once the user has picked an Entry to edit, the client locates the
EditURI in that entry and does a GET on that EditURI. The user can
then edit the entry, and when they are done the client PUTs the
updated entry back to the same EditURI. In the FeedURI above, the
first entry has an EditURI denoted as
<link
rel="service.edit"
type="application/atom+xml"
href="http://blog.example.org/apple.atom"/>
Doing a GET on the
URI http://blog.example.org/atom03.atom retrieves the
most current representation.
GET /apple.atom HTTP/1.1
Host: blog.example.org
Content-Type: application/atom+xml
The server responds with an HTTP status code 200 "Ok" and the
complete entry.
HTTP/1.1 200 Ok
Content-Type: application/atom+xml
<entry xmlns="http://purl.org/atom/ns#">
<title>The Big Apple</title>
<link
rel="alternate"
type="text/html"
href="http://blog.example.org/2003/12/13/apple"/>
<id>tag:blog.example.org,2003:3.2397</id>
<issued>2003-12-13T08:29:29-04:00</issued>
<modified>2003-12-13T18:30:02Z</modified>
<content mode="escaped" type="text/plain">
New York, New York what a ...
</content>
</entry>
The user edits the entry, the content for example,
then the modified entry is PUT back to the EditURI.
PUT /apple.atom HTTP/1.1
Host: blog.example.org
Content-Type: application/atom+xml
<entry xmlns="http://purl.org/atom/ns#">
<title>The Big Apple</title>
<link
rel="alternate"
type="text/html"
href="http://blog.example.org/2003/12/13/apple"/>
<id>tag:blog.example.org,2003:3.2397</id>
<issued>2003-12-13T08:29:29-04:00</issued>
<modified>2003-12-13T18:30:02Z</modified>
<content mode="escaped" type="text/plain">
I love New York.
</content>
</entry>
And the server responds with an HTTP status code of 201 if
everything went well.
HTTP/1.1 201 Created
Next time
In this article I showed how the Atom API can be used to edit the
content of a typical weblog. Atom isn't necessarily restricted to
just weblogs, and in a followup article I will look at other uses
of the Atom API.