This repository has been archived by the owner on Aug 18, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathwrapper.py
232 lines (198 loc) · 8.17 KB
/
wrapper.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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# neubot.mod_speedtest/wrapper.py
#
# Copyright (c) 2011 Simone Basso <bassosimone@gmail.com>,
# NEXA Center for Internet & Society at Politecnico di Torino
#
# This file is part of Neubot <http://www.neubot.org/>.
#
# Neubot is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Neubot is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Neubot. If not, see <http://www.gnu.org/licenses/>.
#
''' Wrapper for speedtest server. The new negotiator uses JSON
messages but old clients still use XML. Hence this layer
of code that maps between the old and the new semantic. '''
import StringIO
import json
from ..runtime.http_server import HttpServer
from ..runtime.http_server import HTTP_SERVER
from .server import SPEEDTEST_SERVER
from ..negotiate_server import NEGOTIATE_SERVER
from . import marshal
#
# Classes that represent the old XML messages used
# by speedtest. I need to disable pylint because
# it's not possible here to fix the names and/or to
# add methods or remove fields. Doing that will
# break the protocol.
#
class SpeedtestCollect(object):
''' Old XML collect request '''
# pylint: disable=R0902
# pylint: disable=R0903
def __init__(self):
# pylint: disable=C0103
self.client = ''
self.timestamp = 0
self.internalAddress = ''
self.realAddress = ''
self.remoteAddress = ''
self.connectTime = 0.0
self.latency = 0.0
self.downloadSpeed = 0.0
self.uploadSpeed = 0.0
self.privacy_informed = 0
self.privacy_can_collect = 0
self.privacy_can_share = 0
self.platform = ''
self.neubot_version = ''
# Test version (added Neubot 0.4.12)
self.testVersion = 1
class SpeedtestNegotiate_Response(object):
''' Old XML negotiate response '''
# pylint: disable=R0903
def __init__(self):
# pylint: disable=C0103
self.authorization = ''
self.publicAddress = ''
self.unchoked = 0
self.queuePos = 0
self.queueLen = 0
#
# This class is named wrapper because the negotiator uses
# JSON and clients use XML. And its task is to glue clients
# and server expectations, waiting for clients to switch to
# JSON.
#
class SpeedtestWrapper(HttpServer):
''' Speedtest server wrapper '''
# Adapted from neubot/negotiate/server.py
def got_request_headers(self, stream, request):
''' Decide whether we can accept this HTTP request '''
isgood = (request['transfer-encoding'] == '' and
# Fix for old clients
(request['content-length'] == '' or
request.content_length() <= 1048576) and
# XXX wrong because the scope of the check is too broad
request.uri.startswith('/speedtest/'))
return isgood
def process_request(self, stream, request):
''' Dispatch and process the incoming HTTP request '''
if request.uri == '/speedtest/negotiate':
self.do_negotiate(stream, request)
elif request.uri == '/speedtest/collect':
self.do_collect(stream, request)
else:
raise RuntimeError('Invalid URI')
@staticmethod
def _rewrite_response(request, response):
''' Rewrite response and translate JSON to XML '''
# Do not touch error responses
if response.code != '200':
return
# Convert JSON response to XML
elif request.uri == '/negotiate/speedtest':
response_body = json.loads(response.body)
xmlresp = SpeedtestNegotiate_Response()
xmlresp.authorization = response_body['authorization']
xmlresp.unchoked = response_body['unchoked']
xmlresp.queuePos = response_body['queue_pos']
xmlresp.publicAddress = response_body['real_address']
response.body = marshal.marshal_object(xmlresp, 'application/xml')
del response['content-type']
del response['content-length']
response['content-type'] = 'application/xml'
response['content-length'] = str(len(response.body))
# Suppress JSON response
elif request.uri == '/collect/speedtest':
del response['content-type']
del response['content-length']
response.body = ''
#
# We MUST NOT be too strict here because old clients
# use the same stream for both negotiation and testing
# and the stream already has the rewriter installed
# due to that.
# Probably we can remove the rewrite hook just after
# usage and be more strict here, but the current code
# seems to me more robust.
#
else:
pass
#
# Set the response rewriter so that we can spit out XML
# as expected by speedtest clients.
# We should rewrite the URI because the negotiate server
# does not like a URI starting with /speedtest.
#
def do_negotiate(self, stream, request):
''' Invoked on GET /speedtest/negotiate '''
stream.response_rewriter = self._rewrite_response
#
# Use POST because the semantic is that the body has no
# meaning, when the request method is GET.
#
request.method = 'POST'
request.uri = '/negotiate/speedtest'
request.body = StringIO.StringIO('{}')
NEGOTIATE_SERVER.process_request(stream, request)
#
# Set the response rewriter so that we can suppress the
# empty JSON returned by the negotiation server.
# We should rewrite the URI because the negotiate server
# does not like a URI starting with /speedtest.
# Map message fields from the unserialized XML object
# to the serialized JSON expected by the new speedtest
# negotiator code.
#
def do_collect(self, stream, request):
''' Invoked on GET /speedtest/collect '''
stream.response_rewriter = self._rewrite_response
request.uri = '/collect/speedtest'
xmlreq = marshal.unmarshal_object(request.body.read(),
'application/xml', SpeedtestCollect)
message = {
'uuid': xmlreq.client,
'timestamp': int(float(xmlreq.timestamp)), # old clients bug
'internal_address': xmlreq.internalAddress,
'real_address': xmlreq.realAddress,
'remote_address': xmlreq.remoteAddress,
'connect_time': xmlreq.connectTime,
'latency': xmlreq.latency,
'download_speed': xmlreq.downloadSpeed,
'upload_speed': xmlreq.uploadSpeed,
'privacy_informed': xmlreq.privacy_informed,
'privacy_can_collect': xmlreq.privacy_can_collect,
'privacy_can_share': xmlreq.privacy_can_share,
'platform': xmlreq.platform,
'neubot_version': xmlreq.neubot_version,
# Test version (added Neubot 0.4.12)
'test_version': xmlreq.testVersion,
}
# XXX Here we don't rewrite content-length which becomes bogus
request['content-type'] = 'application/json'
request.body = StringIO.StringIO(json.dumps(message))
NEGOTIATE_SERVER.process_request(stream, request)
# No poller, so it cannot be used directly
SPEEDTEST_WRAPPER = SpeedtestWrapper(None)
#
# TODO I've added here the run() function for convenience,
# but this should actually be moved in speedtest/__init__.py
# in the future.
#
def run(poller, conf):
''' Start the server-side of the speedtest module '''
HTTP_SERVER.register_child(SPEEDTEST_WRAPPER, '/speedtest/negotiate')
HTTP_SERVER.register_child(SPEEDTEST_WRAPPER, '/speedtest/collect')
HTTP_SERVER.register_child(SPEEDTEST_SERVER, '/speedtest/latency')
HTTP_SERVER.register_child(SPEEDTEST_SERVER, '/speedtest/download')
HTTP_SERVER.register_child(SPEEDTEST_SERVER, '/speedtest/upload')