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 $'
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 thestatusparameter specified below. Request must be aPOST. + ++
+- 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
POSTrequest tohttp://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 tohttps://identi.ca/api/statuses/update.xml.) The request needs to include a parameter calledstatus, 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.
httplib2supports both SSL and HTTP Basic Authentication, so this part is easy. + +A
POSTrequest is different from aGETrequest, 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 isstatus, 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'++
+ +- Python comes with a utility function to URL-encode a dictionary:
urllib.parse.urlencode(). +- 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. +- 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
POSTrequest. ++ +
>>> 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'}) ⑤++
- <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> +- This is how
httplib2handles authentication. Store your username and password with theadd_credentials()method. Whenhttplib2tries to issue the request, the server will respond with a401 Authentication Required, and it will list which authentication methods it supports.httplib2will automatically format the appropriate FIXME +- +
- +
- +
- +
+# 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'++
+ +- 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>++
- FIXME +
FIXME +
+
⁂
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++
- 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/