From 6af348f1765a80a11a0aa2c156da47a9ef9d08de Mon Sep 17 00:00:00 2001 From: Mark Pilgrim Date: Wed, 10 Jun 2009 01:08:55 -0400 Subject: [PATCH] some progress on httplib2-beyond-get section --- dip3.css | 14 ++- http-web-services.html | 198 ++++++++++++++++++++++++++++---------- i/identica-deleted.png | Bin 0 -> 6145 bytes i/identica-screenshot.png | Bin 0 -> 14352 bytes publish | 7 +- 5 files changed, 164 insertions(+), 55 deletions(-) create mode 100644 i/identica-deleted.png create mode 100644 i/identica-screenshot.png diff --git a/dip3.css b/dip3.css index 411f050..b13d916 100644 --- a/dip3.css +++ b/dip3.css @@ -42,6 +42,8 @@ Classname Legend .nm = "no mobile" = hide this section on mobile devices .nd = "no decoration" = hide the widgets on this code block .pp = "pretty print" = apply syntax highlighting to this code block +.pf = "padded frame" = black border with internal padding +.fr = "framed" = black border, no padding .note = "note/caution/important" = indented block for tips/gotchas/language comparisons .baa = "best available ampersand" = wrapper block for ampersands @@ -142,6 +144,15 @@ form div, #level { .todo { color: #ddd; } +#level span { + color: #82b445; +} +.pf,.fr { + border: 1px solid; +} +.pf { + padding: 0 1.75em; +} /* links */ @@ -272,9 +283,6 @@ aside { -webkit-border-radius: 1em; border-radius: 1em; } -#level span { - color: #82b445; -} /* previous/next navigation links */ diff --git a/http-web-services.html b/http-web-services.html index 86bf445..669ae7b 100644 --- a/http-web-services.html +++ b/http-web-services.html @@ -636,61 +636,140 @@ user-agent: Python-httplib2/$Rev: 259 $'

Beyond HTTP GET

-

FIXME +

HTTP web services are not limited to GET requests. What if you want to create something new? Whenever you post a comment on a discussion forum, update your weblog, publish your status on a microblogging service like Twitter or Identi.ca, you’re probably already using HTTP POST. -

->>> import httplib2
+

Both Twitter and Identi.ca both offer a simple HTTP-based API for publishing and updating your status in 140 characters or less. Let’s look at Identi.ca’s API documentation for updating your status: + +

+

Identi.ca REST API Method: statuses/update
+Updates the authenticating user’s status. Requires the status parameter specified below. Request must be a POST. + +

+
URL +
https://identi.ca/api/statuses/update.format +
Formats +
xml, json, rss, atom +
HTTP Method(s) +
POST +
Requires Authentication +
true +
Parameters +
status. Required. The text of your status update. URL-encode as necessary. +
+
+ +

How does this work? To publish a new message on Identi.ca, you need to issue an HTTP POST request to http://identi.ca/api/statuses/update.format. (The format bit is not part of the URL; you replace it with the data format you want the server to return in response to your request. So if you want a response in XML, you would post the request to https://identi.ca/api/statuses/update.xml.) The request needs to include a parameter called status, which contains the text of your status update. And the request needs to be authenticated. + +

Authenticated? Sure. To update your status on Identi.ca, you need to prove who you are. Identi.ca is not a wiki; only you can update your own status. Identi.ca uses HTTP Basic Authentication (a.k.a. RFC 2617) over SSL to provide secure but easy-to-use authentication. httplib2 supports both SSL and HTTP Basic Authentication, so this part is easy. + +

A POST request is different from a GET request, because it includes a payload. The payload is the data you want to send to the server. The once piece of data that this API method requires is status, and it should be URL-encoded. This is a very simple serialization format that takes a set of key-value pairs (i.e. a dictionary) and transforms it into a string. + +

+>>> from urllib.parse import urlencode              
+>>> data = {'status': 'Test update from Python 3'}  
+>>> urlencode(data)                                 
+'status=test+update+from+python+3'
+
    +
  1. Python comes with a utility function to URL-encode a dictionary: urllib.parse.urlencode(). +
  2. This is the sort of dictionary that the Identi.ca API is looking for. It contains one key, status, whose value is the text of a single status update. +
  3. This is what the URL-encoded string looks like. This is the payload that will be sent “on the wire” to the Identi.ca API server in your HTTP POST request. +
+ +

+ +

 >>> from urllib.parse import urlencode
+>>> import httplib2
+>>> httplib2.debuglevel = 1
 >>> h = httplib2.Http('.cache')
 >>> data = {'status': 'Test update from Python 3'}
->>> h.add_credentials('diveintomark', 'MY_SECRET_PASSWORD')
->>> resp, content = h.request('http://twitter.com/statuses/update.xml', 'POST', urlencode(data))
->>> resp.status
-200
->>> from xml.etree import ElementTree as etree
->>> tree = etree.fromstring(content)
->>> print(etree.tostring(tree))
-<status>
-  <created_at>Sat May 30 19:11:38 +0000 2009</created_at>
-  <id>1973974228</id>
-  <text>Test update from Python 3</text>
-  <source>web</source>
-  <truncated>false</truncated>
-  <in_reply_to_status_id />
-  <in_reply_to_user_id />
-  <favorited>false</favorited>
-  <in_reply_to_screen_name />
-  <user>
-    <id>8294212</id>
-    <name>Mark Pilgrim</name>
-    <screen_name>diveintomark</screen_name>
-    <location>Apex, NC</location>
-    <description>Like a fine spice</description>
-    <profile_image_url>http://s3.amazonaws.com/twitter_production/profile_images/72859681/beau_normal.jpg</profile_image_url>
+>>> h.add_credentials('diveintomark', 'MY_SECRET_PASSWORD')                 
+>>> 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. +
- <url>http://diveintomark.org/</url> - <protected>false</protected> - <followers_count>2565</followers_count> - <profile_background_color>FFFFFF</profile_background_color> - <profile_text_color>333333</profile_text_color> - <profile_link_color>333333</profile_link_color> - <profile_sidebar_fill_color>ffffff</profile_sidebar_fill_color> - <profile_sidebar_border_color>333333</profile_sidebar_border_color> - <friends_count>44</friends_count> - <created_at>Sun Aug 19 23:58:36 +0000 2007</created_at> - <favourites_count>71</favourites_count> - <utc_offset>-18000</utc_offset> - <time_zone>Eastern Time (US & Canada)</time_zone> - <profile_background_image_url>http://static.twitter.com/images/themes/theme1/bg.gif</profile_background_image_url> - <profile_background_tile>false</profile_background_tile> - <statuses_count>527</statuses_count> - <notifications>false</notifications> - <following>false</following> - </user> +
+# continued from the previous example
+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
+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
+Host: identi.ca
+Accept-Encoding: identity
+Content-Length: 32
+content-type: application/x-www-form-urlencoded
+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'
+
    +
  1. FIXME +
+ +
+# continued from the previous example
+>>> print(content.decode('utf-8'))
+<?xml version="1.0" encoding="UTF-8"?>
+<status>
+ <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>
+ <in_reply_to_user_id></in_reply_to_user_id>
+ <in_reply_to_screen_name></in_reply_to_screen_name>
+ <favorited>false</favorited>
+ <user>
+  <id>3212</id>
+  <name>Mark Pilgrim</name>
+  <screen_name>diveintomark</screen_name>
+  <location>27502, US</location>
+  <description>tech writer, husband, father</description>
+  <profile_image_url>http://avatar.identi.ca/3212-48-20081216000626.png</profile_image_url>
+  <url>http://diveintomark.org/</url>
+  <protected>false</protected>
+  <followers_count>329</followers_count>
+  <profile_background_color></profile_background_color>
+  <profile_text_color></profile_text_color>
+  <profile_link_color></profile_link_color>
+  <profile_sidebar_fill_color></profile_sidebar_fill_color>
+  <profile_sidebar_border_color></profile_sidebar_border_color>
+  <friends_count>2</friends_count>
+  <created_at>Wed Jul 02 22:03:58 +0000 2008</created_at>
+  <favourites_count>30768</favourites_count>
+  <utc_offset>0</utc_offset>
+  <time_zone>UTC</time_zone>
+  <profile_background_image_url></profile_background_image_url>
+  <profile_background_tile>false</profile_background_tile>
+  <statuses_count>122</statuses_count>
+  <following>false</following>
+  <notifications>false</notifications>
+</user>
 </status>
+
    +
  1. FIXME +

FIXME +

screenshot of identi.ca/notice/5131472 showing status just published +

Beyond HTTP POST

@@ -699,11 +778,32 @@ user-agent: Python-httplib2/$Rev: 259 $'
 # continued from the previous example
->>> tree.findtext('id')
-'1973974228'
->>> resp, delete_content = h.request('http://twitter.com/statuses/destroy/{0}.xml'.format(tree.findtext('id')), 'DELETE')
+>>> from xml.etree import ElementTree as etree
+>>> 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
+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
+Host: identi.ca
+Accept-Encoding: identity
+authorization: Basic SECRET_HASH_CONSTRUCTED_BY_HTTPLIB2
+user-agent: Python-httplib2/$Rev: 259 $
+
+'
+reply: 'HTTP/1.1 200 OK'
 >>> resp.status
 200
+
    +
  1. FIXME +

⁂ diff --git a/i/identica-deleted.png b/i/identica-deleted.png new file mode 100644 index 0000000000000000000000000000000000000000..2e913aa04d351f791eb80c09eb99bfe3c2139b56 GIT binary patch literal 6145 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 I_vWMj0?v9MxBvhE literal 0 HcmV?d00001 diff --git a/i/identica-screenshot.png b/i/identica-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..a124a1190e70a86f7816169ae9d977b48c576794 GIT binary patch literal 14352 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