-
Notifications
You must be signed in to change notification settings - Fork 256
/
rpc.py
202 lines (165 loc) · 4.83 KB
/
rpc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# -*- coding: utf-8 -*-
import httplib_chunk as httplib
import json
import cStringIO
import conf
class Client(object):
_conn = None
_header = None
def __init__(self, host):
self._conn = httplib.HTTPConnection(host)
self._header = {}
def round_tripper(self, method, path, body):
self._conn.request(method, path, body, self._header)
resp = self._conn.getresponse()
return resp
def call(self, path):
return self.call_with(path, None)
def call_with(self, path, body, content_type=None, content_length=None):
ret = None
self.set_header("User-Agent", conf.USER_AGENT)
if content_type is not None:
self.set_header("Content-Type", content_type)
if content_length is not None:
self.set_header("Content-Length", content_length)
resp = self.round_tripper("POST", path, body)
try:
ret = resp.read()
ret = json.loads(ret)
except IOError, e:
return None, e
except ValueError:
pass
if resp.status / 100 != 2:
err_msg = ret if "error" not in ret else ret["error"]
detail = resp.getheader("x-log", None)
if detail is not None:
err_msg += ", detail:%s" % detail
return None, err_msg
return ret, None
def call_with_multipart(self, path, fields=None, files=None):
"""
* fields => {key}
* files => [{filename, data, content_type}]
"""
content_type, mr = self.encode_multipart_formdata(fields, files)
return self.call_with(path, mr, content_type, mr.length())
def call_with_form(self, path, ops):
"""
* ops => {"key": value/list()}
"""
body = []
for i in ops:
if isinstance(ops[i], (list, tuple)):
data = ('&%s=' % i).join(ops[i])
else:
data = ops[i]
body.append('%s=%s' % (i, data))
body = '&'.join(body)
content_type = "application/x-www-form-urlencoded"
return self.call_with(path, body, content_type, len(body))
def set_header(self, field, value):
self._header[field] = value
def set_headers(self, headers):
self._header.update(headers)
def encode_multipart_formdata(self, fields, files):
"""
* fields => {key}
* files => [{filename, data, content_type}]
* return content_type, content_length, body
"""
if files is None:
files = []
if fields is None:
fields = {}
readers = []
BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
CRLF = '\r\n'
L1 = []
for key in fields:
L1.append('--' + BOUNDARY)
L1.append('Content-Disposition: form-data; name="%s"' % key)
L1.append('')
L1.append(fields[key])
b1 = CRLF.join(L1)
readers.append(b1)
for file_info in files:
L = []
L.append('')
L.append('--' + BOUNDARY)
disposition = "Content-Disposition: form-data;"
filename = _qiniu_escape(file_info.get('filename'))
L.append('%s name="file"; filename="%s"' % (disposition, filename))
L.append('Content-Type: %s' % file_info.get('content_type', 'application/octet-stream'))
L.append('')
L.append('')
b2 = CRLF.join(L)
readers.append(b2)
data = file_info.get('data')
readers.append(data)
L3 = ['', '--' + BOUNDARY + '--', '']
b3 = CRLF.join(L3)
readers.append(b3)
content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
return content_type, MultiReader(readers)
def _qiniu_escape(s):
edits = [('\\', '\\\\'), ('\"', '\\\"')]
for (search, replace) in edits:
s = s.replace(search, replace)
return s
class MultiReader(object):
""" class MultiReader([readers...])
MultiReader returns a read()able object that's the logical concatenation of
the provided input readers. They're read sequentially.
"""
def __init__(self, readers):
self.readers = []
self.content_length = 0
self.valid_content_length = True
for r in readers:
if hasattr(r, 'read'):
if self.valid_content_length:
length = self._get_content_length(r)
if length is not None:
self.content_length += length
else:
self.valid_content_length = False
else:
buf = r
if not isinstance(buf, basestring):
buf = str(buf)
buf = _u2s(buf)
r = cStringIO.StringIO(buf)
self.content_length += len(buf)
self.readers.append(r)
# don't name it __len__, because the length of MultiReader is not alway valid.
def length(self):
return self.content_length if self.valid_content_length else None
def _get_content_length(self, reader):
data_len = None
if hasattr(reader, 'seek') and hasattr(reader, 'tell'):
try:
reader.seek(0, 2)
data_len= reader.tell()
reader.seek(0, 0)
except OSError:
# Don't send a length if this failed
data_len = None
return data_len
def read(self, n=-1):
if n is None or n == -1:
return ''.join([_u2s(r.read()) for r in self.readers])
else:
L = []
while len(self.readers) > 0 and n > 0:
b = self.readers[0].read(n)
if len(b) == 0:
self.readers = self.readers[1:]
else:
L.append(_u2s(b))
n -= len(b)
return ''.join(L)
def _u2s(u):
if isinstance(u, unicode):
u = u.encode('utf8')
return u