There has been a brewing discussion on the wiki on how the AtomAPI needs to be structured. While the discussion is long and some of the issues murky, the question is quite simple and is boiled down into one poll.
The poll, which was on RestEchoApiDiscuss, but through the wonder of wiki has now migrated to CarrotVsOrangeDiscuss. The poll is restricted to just looking at the simple case of editing an Atom Entry, and the question is "What should the relationship between URIs and HTTP verbs be?"
- One URI for create, edit and delete
- Each Entry has it's own URI that accepts GET, PUT, and DELETE
One of the misconceptions hinted at in the discussion on the wiki, but never flatly stated is that if you choose #2 you can't run everything through a single CGI script, which will make the system difficult to implement or hard to maintain. This isn't true and I have implemented both interfaces in Python in a side-by-side comparison to demonstrate that they can both be done in a single CGI.
The first script implements option #1:
#!/usr/bin/python
import cgitb; cgitb.enable()
import os, cgi, urlparse, sys
import myLowLevelBloggingAPI
import xmltramp
method = os.environ['REQUEST_METHOD']
id = None
if method == 'POST':
content = sys.stdin.read()
d = xmltramp.parse(content)
if d._name == 'delete':
myLowLevelBloggingAPI.delete(d('id'))
try:
id = d.entry.id
except:
pass
if id == None:
id = myLowLevelBloggingAPI.create(sys.stdin.read())
print "Status: 201 Created"
print "Location: " + urlparse.urlunparse\
(('http', os.environ['SERVER_NAME'],\
os.environ['REQUEST_URI'] + '/%d', '', '', ''))\
+ "\n" % (id, )
else:
myLowLevelBloggingAPI.update(id, content)
print "Status: 205 Reset Content\n"
elif method == 'GET':
id = os.environ.get('PATH_INFO', '')
print "Status: 200 Ok\n"
print myLowLevelBloggingAPI.get(id)
The second script implements option #2:
#!/usr/bin/python
import cgitb; cgitb.enable()
import os, urlparse, sys
import myLowLevelBloggingAPI
method = os.environ['REQUEST_METHOD']
id = os.environ.get('PATH_INFO', '')
if method == 'POST':
id = myLowLevelBloggingAPI.create(sys.stdin.read())
print "Status: 201 Created"
print "Location: " + urlparse.urlunparse\
(('http', os.environ['SERVER_NAME'],\
os.environ['REQUEST_URI'] + '/%d', '', '', ''))\
+ "\n" % (id, )
elif method == 'GET':
print "Status: 200 Ok\n"
print myLowLevelBloggingAPI.get(id)
elif method == 'PUT':
myLowLevelBloggingAPI.update(id, sys.stdin.read())
print "Status: 205 Reset Content\n"
elif method == 'DELETE':
myLowLevelBloggingAPI.delete(id)
print "Status: 200 Ok\n"
There are several additional things to note from these example implementations:
- If either of these cgi scripts were installed as atom.cgi on example.org then doing a GET on http://example.org/atom.cgi/1 would get the Entry with an 'id' of 1.
- Note that in the second example all the dispatching is done via the HTTP verbs, no parsing of the content is necessary.
- For both examples there is a single CGI script that processes the requests.
- Option #2 is a far simpler script that option #1.
Posted by bryan on 2003-07-25
I'm not arguing that there will never be another layer of dispatch above or below POST. The example here is to demonstrate that such dispatching need not require parsing the contents of the request. I also don't think well ever need to dispatch based on the content, as long as we use all Six Places to store information.
Posted by Joe on 2003-07-25
Posted by Sam Ruby on 2003-07-25
Posted by anonymous on 2003-08-05
FWIW, I don't believe that you will ever get away from a second level dispatch on POST. Updating an entry and adding a comment are two distinct operations, both of which map to POST. There will be others.
I don't object to separating out PUT and DELETE, but I don't believe that separating out these two, relatively infrequently used, cases solves the root problem.
Things never turn out to be as orthogonal as people would like.
Posted by Sam Ruby on 2003-07-25