• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

winazurestorageのフォーク


Commit MetaInfo

Révisiondaaf7ce6bbab25bf8718aee876ab288f4db0f549 (tree)
l'heure2008-11-22 14:07:50
AuteurSteve Marx <Steve.Marx@micr...>
CommiterSteve Marx

Message de Log

Add early table support, support for non-path-style-URIs, and move to urllib2 for proxy support.

Change Summary

Modification

--- a/winazurestorage.py
+++ b/winazurestorage.py
@@ -7,22 +7,24 @@ Sriram Krishnan <sriramk@microsoft.com>
77
88 import base64
99 import hmac
10-import httplib
1110 import hashlib
1211 import time
13-import urllib
14-import urlparse
1512 import sys
1613 import os
1714 from xml.dom import minidom #TODO: Use a faster way of processing XML
15+import re
16+from urllib2 import Request, urlopen
17+from urlparse import urlsplit
18+from datetime import datetime, timedelta
1819
1920 DEVSTORE_ACCOUNT = "devstoreaccount1"
2021 DEVSTORE_SECRET_KEY = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
2122
23+DEVSTORE_BLOB_HOST = "127.0.0.1:10000"
24+DEVSTORE_TABLE_HOST = "127.0.0.1:10002"
2225
23-DEVSTORE_HOST="127.0.0.1:10000"
24-CLOUD_HOST = "blob.core.windows.net"
25-
26+CLOUD_BLOB_HOST = "blob.core.windows.net"
27+CLOUD_TABLE_HOST = "table.core.windows.net"
2628
2729 PREFIX_PROPERTIES = "x-ms-prop-"
2830 PREFIX_METADATA = "x-ms-meta-"
@@ -34,36 +36,157 @@ DEBUG = False
3436
3537 TIME_FORMAT ="%a, %d %b %Y %H:%M:%S %Z"
3638
39+def parse_edm_datetime(input):
40+ d = datetime.strptime(input[:input.find('.')], "%Y-%m-%dT%H:%M:%S")
41+ if input[:input.find('.')] != -1:
42+ d += timedelta(0, 0, int(round(float(input[input.index('.'):-1])*1000000)))
43+ return d
3744
38-class WAStorageConnection:
39- def __init__(self, server_url = DEVSTORE_HOST, account_name=DEVSTORE_ACCOUNT,secret_key = DEVSTORE_SECRET_KEY):
40- self.server_url = server_url
41- self.account_name = account_name
42- self.secret_key = base64.decodestring(secret_key)
43-
44-
45- def create_container(self,container_name, is_public):
46- headers ={}
47- if is_public:
48- headers[PREFIX_PROPERTIES+ "publicaccess"] = "true"
45+def parse_edm_int32(input):
46+ return int(input)
47+
48+class SharedKeyCredentials(object):
49+ def __init__(self, account_name, account_key, use_path_style_uris = None):
50+ self._account = account_name
51+ self._key = base64.decodestring(account_key)
52+
53+ def _sign_request_impl(self, request, for_tables = False, use_path_style_uris = None):
54+ (scheme, host, path, query, fragment) = urlsplit(request.get_full_url())
55+ if use_path_style_uris:
56+ path = path[path.index('/'):]
57+
58+ canonicalized_resource = "/" + self._account + path
59+ match = re.search(r'comp=[^&]*', query)
60+ if match is not None:
61+ canonicalized_resource += "?" + match.group(0)
4962
50- return self._do_store_request(container_name, None, 'PUT', headers)
51-
52- def put_blob(self, container_name, key, data, content_type=None):
53- return self._do_store_request(container_name, key, 'PUT', {}, data, content_type )
54-
55- def get_blob(self, container_name, key):
56- response = self._do_store_request(container_name, key, 'GET' )
57- return response.read()
58-
59- def list_containers(self):
60- #TODO: Deal with nextmarker for large requests (only 5K containers returned by default)
61- #TODO: Use a different XML parsing scheme
62-
63- response = self._do_store_request(query_string = "?comp=list")
64-
63+ if use_path_style_uris is None:
64+ use_path_style_uris = re.match('^[\d.:]+$', host) is not None
65+
66+ request.add_header(PREFIX_STORAGE_HEADER + 'date', time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())) #RFC 1123
67+ canonicalized_headers = NEW_LINE.join(('%s:%s' % (k.lower(), request.get_header(k).strip()) for k in sorted(request.headers.keys(), lambda x,y: cmp(x.lower(), y.lower())) if k.lower().startswith(PREFIX_STORAGE_HEADER)))
68+
69+ string_to_sign = request.get_method().upper() + NEW_LINE # verb
70+ string_to_sign += NEW_LINE # MD5 not required
71+ if request.get_header('Content-type') is not None: # Content-Type
72+ string_to_sign += request.get_header('Content-type')
73+ string_to_sign += NEW_LINE
74+ if for_tables: string_to_sign += request.get_header(PREFIX_STORAGE_HEADER.capitalize() + 'date') + NEW_LINE
75+ else: string_to_sign += NEW_LINE # Date
76+ if not for_tables:
77+ string_to_sign += canonicalized_headers + NEW_LINE # Canonicalized headers
78+ string_to_sign += canonicalized_resource # Canonicalized resource
79+
80+ request.add_header('Authorization', 'SharedKey ' + self._account + ':' + base64.encodestring(hmac.new(self._key, unicode(string_to_sign).encode("utf-8"), hashlib.sha256).digest()).strip())
81+ return request
82+
83+ def sign_request(self, request, use_path_style_uris = None):
84+ return self._sign_request_impl(request, use_path_style_uris)
85+
86+ def sign_table_request(self, request, use_path_style_uris = None):
87+ return self._sign_request_impl(request, for_tables = True, use_path_style_uris = use_path_style_uris)
88+
89+class RequestWithMethod(Request):
90+ '''Subclass urllib2.Request to add the capability of using methods other than GET and POST.
91+ Thanks to http://benjamin.smedbergs.us/blog/2008-10-21/putting-and-deleteing-in-python-urllib2/'''
92+ def __init__(self, method, *args, **kwargs):
93+ self._method = method
94+ Request.__init__(self, *args, **kwargs)
95+
96+ def get_method(self):
97+ return self._method
98+
99+class Table(object):
100+ def __init__(self, url, name):
101+ self.url = url
102+ self.name = name
103+
104+class Storage(object):
105+ def __init__(self, host, account_name, secret_key, use_path_style_uris):
106+ self._host = host
107+ self._account = account_name
108+ self._key = secret_key
109+ if use_path_style_uris is None:
110+ use_path_style_uris = re.match(r'^[^:]*[\d:]+$', self._host)
111+ self._use_path_style_uris = use_path_style_uris
112+ self._credentials = SharedKeyCredentials(self._account, self._key)
113+
114+ def get_base_url(self):
115+ if self._use_path_style_uris:
116+ return "http://%s/%s" % (self._host, self._account)
117+ else:
118+ return "http://%s.%s" % (self._account, self._host)
119+
120+class TableEntity(object): pass
121+
122+class TableStorage(Storage):
123+ '''Due to local development storage not supporting SharedKeyLite authentication, this class
124+ will only work against cloud storage.'''
125+ def __init__(self, host, account_name, secret_key, use_path_style_uris = None):
126+ super(TableStorage, self).__init__(host, account_name, secret_key, use_path_style_uris)
127+
128+ def list_tables(self):
129+ req = Request("%s/Tables" % self.get_base_url())
130+ self._credentials.sign_table_request(req)
131+ response = urlopen(req)
132+
65133 dom = minidom.parseString(response.read())
66134
135+ entries = dom.getElementsByTagName("entry")
136+ for entry in entries:
137+ table_url = entry.getElementsByTagName("id")[0].firstChild.data
138+ table_name = entry.getElementsByTagName("content")[0].getElementsByTagName("m:properties")[0].getElementsByTagName("d:TableName")[0].firstChild.data
139+ yield Table(table_url, table_name)
140+ dom.unlink()
141+
142+ def get_entity(self, table_name, partition_key, row_key):
143+ dom = minidom.parseString(urlopen(self._credentials.sign_table_request(Request("%s/%s(PartitionKey='%s',RowKey='%s')" % (self.get_base_url(), table_name, partition_key, row_key)))).read())
144+ entity = self._parse_entity(dom.getElementsByTagName("entry")[0])
145+ dom.unlink()
146+ return entity
147+
148+ def _parse_entity(self, entry):
149+ entity = TableEntity()
150+ for property in (p for p in entry.getElementsByTagName("m:properties")[0].childNodes if p.nodeType == minidom.Node.ELEMENT_NODE):
151+ key = property.tagName[2:]
152+ if property.hasAttribute('m:type'):
153+ t = property.getAttribute('m:type')
154+ if t.lower() == 'edm.datetime': value = parse_edm_datetime(property.firstChild.data)
155+ elif t.lower() == 'edm.int32': value = parse_edm_int32(property.firstChild.data)
156+ else: raise Exception(t.lower())
157+ else: value = property.firstChild.data
158+ setattr(entity, key, value)
159+ return entity
160+
161+ def get_all(self, table_name):
162+ dom = minidom.parseString(urlopen(self._credentials.sign_table_request(Request("%s/%s" % (self.get_base_url(), table_name)))).read())
163+ entries = dom.getElementsByTagName("entry")
164+ entities = []
165+ for entry in entries:
166+ entities.append(self._parse_entity(entry))
167+ dom.unlink()
168+ return entities
169+
170+class BlobStorage(Storage):
171+ def __init__(self, host = DEVSTORE_BLOB_HOST, account_name = DEVSTORE_ACCOUNT, secret_key = DEVSTORE_SECRET_KEY, use_path_style_uris = None):
172+ super(BlobStorage, self).__init__(host, account_name, secret_key, use_path_style_uris)
173+
174+ def create_container(self, container_name, is_public = False):
175+ req = RequestWithMethod("PUT", "%s/%s" % (self.get_base_url(), container_name))
176+ req.add_header("Content-Length", "0")
177+ self._credentials.sign_request(req)
178+ if is_public: req.add_header(PREFIX_PROPERTIES + "publicaccess", "true")
179+ return urlopen(req)
180+
181+ def delete_container(self, container_name):
182+ req = RequestWithMethod("DELETE", "%s/%s" % (self.get_base_url(), container_name))
183+ self._credentials.sign_request(req)
184+ return urlopen(req)
185+
186+ def list_containers(self):
187+ req = Request("%s/?comp=list" % self.get_base_url())
188+ self._credentials.sign_request(req)
189+ dom = minidom.parseString(urlopen(req).read())
67190 containers = dom.getElementsByTagName("Container")
68191 for container in containers:
69192 container_name = container.getElementsByTagName("Name")[0].firstChild.data
@@ -72,85 +195,21 @@ class WAStorageConnection:
72195 yield (container_name, etag, last_modified)
73196
74197 dom.unlink() #Docs say to do this to force GC. Ugh.
75-
76- def _get_auth_header(self, http_method, path, data, headers):
77- string_to_sign =""
78-
79- #First element is the method
80- string_to_sign += http_method + NEW_LINE
81-
82- #Second is the optional content MD5
83- string_to_sign += NEW_LINE
84-
85- #content type - this should have been initialized atleast to a blank value
86- if headers.has_key("content-type"):
87- string_to_sign += headers["content-type"]
88- string_to_sign += NEW_LINE
89-
90- # date - we don't need to add header here since the special date storage header
91- # always exists in our implementation
92- string_to_sign += NEW_LINE
93-
94- # Construct canonicalized headers.
95- # TODO: Note that this doesn't implement parts of the spec - combining header fields with same name,
96- # unfolding long lines and trimming white spaces around the colon
97-
98- ms_headers =[header_key for header_key in headers.keys() if header_key.startswith(PREFIX_STORAGE_HEADER)]
99- ms_headers.sort()
100- for header_key in ms_headers:
101- string_to_sign += "%s:%s%s" % (header_key, headers[header_key], NEW_LINE)
102-
103- # Add canonicalized resource
104- string_to_sign += "/" + self.account_name + path
105- utf8_string_to_sign = unicode(string_to_sign).encode("utf-8")
106- hmac_digest = hmac.new(self.secret_key, utf8_string_to_sign, hashlib.sha256).digest()
107- return base64.encodestring(hmac_digest).strip()
108-
109-
110-
111- def _do_store_request(self, container=None, blob_name=None, http_method="GET", headers = {},data = "",
112- content_type=None, query_string = None, signed=True):
113- connection = httplib.HTTPConnection(self.server_url)
114-
115- # Construct right path based on account name , container name and blob name if any
116- path = "/" + self.account_name + "/"
117- if container!= None:
118- path = path + container +"/"
119- if blob_name != None:
120- path = path + blob_name
121-
122- if query_string != None:
123- path = path + query_string
124-
125-
126- headers[PREFIX_STORAGE_HEADER + "date"] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) #RFC 1123
127- if content_type != None:
128- headers["content-type"] = content_type
129-
130- if signed:
131- auth_header = self._get_auth_header(http_method,path, data, headers)
132- headers["Authorization"] = "SharedKey " + self.account_name + ":" + auth_header
133- headers["content-length"] = len(data)
134-
135- connection.request(http_method, path, data, headers)
136- response = connection.getresponse()
137- if DEBUG:
138- print response.status
139- return response
140-
141-
142-def main():
143- conn = WAStorageConnection()
144- for (container_name,etag, last_modified ) in conn.list_containers():
145- print container_name
146- print etag
147- print last_modified
148-
149- conn.create_container("testcontainer", False)
150- conn.put_blob("testcontainer","test","Hello World!" )
151- print conn.get_blob("testcontainer", "test")
152198
199+ def put_blob(self, container_name, blob_name, data, content_type = None):
200+ req = RequestWithMethod("PUT", "%s/%s/%s" % (self.get_base_url(), container_name, blob_name), data=data)
201+ req.add_header("Content-Length", "%d" % len(data))
202+ if content_type is not None: req.add_header("Content-Type", content_type)
203+ self._credentials.sign_request(req)
204+ return urlopen(req)
205+
206+ def get_blob(self, container_name, blob_name):
207+ req = Request("%s/%s/%s" % (self.get_base_url(), container_name, blob_name))
208+ self._credentials.sign_request(req)
209+ return urlopen(req).read()
210+
211+def main():
212+ pass
153213
154214 if __name__ == '__main__':
155- main()
156-
215+ main()
\ No newline at end of file