From ec0b90cd846483aa2be8451bbd64317adf39f821 Mon Sep 17 00:00:00 2001 From: Mark Pilgrim Date: Wed, 10 Jun 2009 11:44:01 -0400 Subject: [PATCH] finished HTTP Web Services chapter --- http-web-services.html | 81 ++++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 27 deletions(-) diff --git a/http-web-services.html b/http-web-services.html index 62e3167..68a15aa 100644 --- a/http-web-services.html +++ b/http-web-services.html @@ -684,18 +684,19 @@ Updates the authenticating user’s status. Requires the status>>> h = httplib2.Http('.cache') >>> data = {'status': 'Test update from Python 3'} >>> h.add_credentials('diveintomark', 'MY_SECRET_PASSWORD') ->>> resp, content = h.request('https://identi.ca/api/statuses/update.xml', +>>> resp, content = h.request('https://identi.ca/api/statuses/update.xml', ... 'POST', ... urlencode(data), ... headers={'Content-Type': 'application/x-www-form-urlencoded'})
    -
  1. This is how httplib2 handles authentication. Store your username and password with the add_credentials() method. When httplib2 tries to issue the request, the server will respond with a 401 Authentication Required, and it will list which authentication methods it supports. httplib2 will automatically format the appropriate FIXME -
  2. -
  3. -
  4. -
  5. +
  6. This is how httplib2 handles authentication. Store your username and password with the add_credentials() method. When httplib2 tries to issue the request, the server will respond with a 401 Unauthorized status code, and it will list which authentication methods it supports (in the WWW-Authenticate header). httplib2 will automatically construct an Authorization header and re-request the URL. +
  7. The second parameter is the type of HTTP request, in this case POST. +
  8. The third parameter is the payload to send to the server. We’re sending the URL-encoded dictionary with a status message. +
  9. Finally, we need to tell the server that the payload is URL-encoded data.
+

This is what goes over the wire: +

 # continued from the previous example
 send: b'POST /api/statuses/update.xml HTTP/1.1
@@ -706,32 +707,37 @@ content-type: application/x-www-form-urlencoded
 user-agent: Python-httplib2/$Rev: 259 $
 
 status=Test+update+from+Python+3'
-reply: 'HTTP/1.1 401 Unauthorized'
-send: b'POST /api/statuses/update.xml HTTP/1.1
+reply: 'HTTP/1.1 401 Unauthorized'                        
+send: b'POST /api/statuses/update.xml HTTP/1.1            
 Host: identi.ca
 Accept-Encoding: identity
 Content-Length: 32
 content-type: application/x-www-form-urlencoded
-authorization: Basic SECRET_HASH_CONSTRUCTED_BY_HTTPLIB2
+authorization: Basic SECRET_HASH_CONSTRUCTED_BY_HTTPLIB2  
 user-agent: Python-httplib2/$Rev: 259 $
 
 status=Test+update+from+Python+3'
-reply: 'HTTP/1.1 200 OK'
+reply: 'HTTP/1.1 200 OK'
    -
  1. FIXME +
  2. After the first request, the server responds with a 401 Unauthorized status code. httplib2 will never send authentication headers unless the server explicitly asks for them. This is how the server asks for them. +
  3. httplib2 immediately turns around and requests the same URL a second time. +
  4. This time, it includes the username and password that you added with the add_credentials() method. +
  5. It worked!
+

What does the server send back after a successful request? That depends entirely on the web service API. In some protocols (like the Atom Publishing Protocol), the server sends back a 201 Created status code and the location of the newly created resource in the Location header. Identi.ca sends back a 200 OK and an XML document containing information about the newly created resource. +

 # continued from the previous example
->>> print(content.decode('utf-8'))
+>>> print(content.decode('utf-8'))                             
 <?xml version="1.0" encoding="UTF-8"?>
 <status>
- <text>Test update from Python 3</text>
+ <text>Test update from Python 3</text>                        
  <truncated>false</truncated>
  <created_at>Wed Jun 10 03:53:46 +0000 2009</created_at>
  <in_reply_to_status_id></in_reply_to_status_id>
  <source>api</source>
- <id>5131472</id>
+ <id>5131472</id>                                              
  <in_reply_to_user_id></in_reply_to_user_id>
  <in_reply_to_screen_name></in_reply_to_screen_name>
  <favorited>false</favorited>
@@ -763,48 +769,69 @@ reply: 'HTTP/1.1 200 OK'
</user> </status>
    -
  1. FIXME +
  2. Remember, the data returned by httplib2 is always bytes, not a string. To convert it to a string, you need to decode it using the proper character encoding. Identi.ca’s API always returns results in UTF-8, so that part is easy. +
  3. There’s the text of the status message we just published. +
  4. There’s the unique identifier for the new status message. Identi.ca uses this to construct a URL for viewing the message on the web.
-

FIXME +

And here it is: -

screenshot showing published status on identi.ca +

screenshot showing published status message on Identi.ca

Beyond HTTP POST

-

FIXME +

HTTP isn’t limited to GET and POST. Those are certainly the most common types of requests, especially in web browsers. But web service APIs can go beyond GET and POST, and httplib2 is ready.

 # continued from the previous example
 >>> from xml.etree import ElementTree as etree
->>> tree = etree.fromstring(content)
->>> status_id = tree.findtext('id')
+>>> tree = etree.fromstring(content)                                          
+>>> status_id = tree.findtext('id')                                           
 >>> status_id
 '5131472'
->>> resp, deleted_content = h.request('https://identi.ca/api/statuses/destroy/{0}.xml'.format(status_id), 'DELETE')
-send: b'DELETE /api/statuses/destroy/5131472.xml HTTP/1.1
+>>> url = 'https://identi.ca/api/statuses/destroy/{0}.xml'.format(status_id)  
+>>> resp, deleted_content = h.request(url, 'DELETE')                          
+
    +
  1. The server returned XML, right? You know how to parse XML. +
  2. The findtext() method finds the first instance of the given expression and extracts its text content. In this case, we’re just looking for an <id> element. +
  3. Based on the text content of the <id> element, we can construct a URL to delete the status message we just published. +
  4. To delete a message, you simply issue an HTTP DELETE request to that URL. +
+ +

This is what goes over the wire: + +

+send: b'DELETE /api/statuses/destroy/5131472.xml HTTP/1.1      
 Host: identi.ca
 Accept-Encoding: identity
 user-agent: Python-httplib2/$Rev: 259 $
 
 '
-reply: 'HTTP/1.1 401 Unauthorized'
-send: b'DELETE /api/statuses/destroy/5131472.xml HTTP/1.1
+reply: 'HTTP/1.1 401 Unauthorized'                             
+send: b'DELETE /api/statuses/destroy/5131472.xml HTTP/1.1      
 Host: identi.ca
 Accept-Encoding: identity
-authorization: Basic SECRET_HASH_CONSTRUCTED_BY_HTTPLIB2
+authorization: Basic SECRET_HASH_CONSTRUCTED_BY_HTTPLIB2       
 user-agent: Python-httplib2/$Rev: 259 $
 
 '
-reply: 'HTTP/1.1 200 OK'
+reply: 'HTTP/1.1 200 OK'                                       
 >>> resp.status
 200
    -
  1. FIXME +
  2. “Delete this status message, please.” +
  3. “I’m sorry, Dave, I’m afraid I can’t do that.” +
  4. “Delete this status message, please… +
  5. …here’s my username and password.” +
  6. “Consider it done!”
+

And just like that, poof, it’s gone. + +

screenshot showing deleted message on Identi.ca +

Further Reading