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 0000000..2e913aa Binary files /dev/null and b/i/identica-deleted.png differ diff --git a/i/identica-screenshot.png b/i/identica-screenshot.png new file mode 100644 index 0000000..a124a11 Binary files /dev/null and b/i/identica-screenshot.png differ diff --git a/publish b/publish index cd9593c..e9c2a44 100755 --- a/publish +++ b/publish @@ -7,6 +7,7 @@ rm -rf build mkdir build cp robots.txt *.css build/ cp -R j build/ +cp -R i build/ rm -f examples/*.pyc cp -R examples build/ @@ -68,10 +69,10 @@ sed -i -e "s|=http:|=|g" build/*.html sed -i -e "s|href=index.html|href=/|g" build/*.html # set file permissions (hg resets these, don't know why) -chmod 755 build/j -chmod 644 build/*.html build/*.css build/examples/*.py build/examples/*.txt build/*.txt build/j/*.js build/j/.htaccess +chmod 755 build/j build/i +chmod 644 build/*.html build/*.css build/examples/*.py build/examples/*.txt build/*.txt build/j/*.js build/j/.htaccess build/i/* # ship it! echo "publishing" rsync -essh -avzP build/j/$revision.js build/j/html5.js build/j/.htaccess diveintomark.org:~/web/diveintopython3.org/j/ -rsync -essh -avzP build/*.html build/examples build/*.txt diveintomark.org:~/web/diveintopython3.org/ +rsync -essh -avzP build/*.html build/examples build/*.txt build/i diveintomark.org:~/web/diveintopython3.org/