##// END OF EJS Templates
tests: change how sockets are closed...
Gregory Szorc -
r41605:d343d9ac default
parent child Browse files
Show More
@@ -1,301 +1,318 b''
1 1 # badserverext.py - Extension making servers behave badly
2 2 #
3 3 # Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 # no-check-code
9 9
10 10 """Extension to make servers behave badly.
11 11
12 12 This extension is useful for testing Mercurial behavior when various network
13 13 events occur.
14 14
15 15 Various config options in the [badserver] section influence behavior:
16 16
17 17 closebeforeaccept
18 18 If true, close() the server socket when a new connection arrives before
19 19 accept() is called. The server will then exit.
20 20
21 21 closeafteraccept
22 22 If true, the server will close() the client socket immediately after
23 23 accept().
24 24
25 25 closeafterrecvbytes
26 26 If defined, close the client socket after receiving this many bytes.
27 27
28 28 closeaftersendbytes
29 29 If defined, close the client socket after sending this many bytes.
30 30 """
31 31
32 32 from __future__ import absolute_import
33 33
34 34 import socket
35 35
36 36 from mercurial import(
37 pycompat,
37 38 registrar,
38 39 )
39 40
40 41 from mercurial.hgweb import (
41 42 server,
42 43 )
43 44
44 45 configtable = {}
45 46 configitem = registrar.configitem(configtable)
46 47
47 48 configitem(b'badserver', b'closeafteraccept',
48 49 default=False,
49 50 )
50 51 configitem(b'badserver', b'closeafterrecvbytes',
51 52 default=b'0',
52 53 )
53 54 configitem(b'badserver', b'closeaftersendbytes',
54 55 default=b'0',
55 56 )
56 57 configitem(b'badserver', b'closebeforeaccept',
57 58 default=False,
58 59 )
59 60
60 61 # We can't adjust __class__ on a socket instance. So we define a proxy type.
61 62 class socketproxy(object):
62 63 __slots__ = (
63 64 '_orig',
64 65 '_logfp',
65 66 '_closeafterrecvbytes',
66 67 '_closeaftersendbytes',
67 68 )
68 69
69 70 def __init__(self, obj, logfp, closeafterrecvbytes=0,
70 71 closeaftersendbytes=0):
71 72 object.__setattr__(self, '_orig', obj)
72 73 object.__setattr__(self, '_logfp', logfp)
73 74 object.__setattr__(self, '_closeafterrecvbytes', closeafterrecvbytes)
74 75 object.__setattr__(self, '_closeaftersendbytes', closeaftersendbytes)
75 76
76 77 def __getattribute__(self, name):
77 78 if name in ('makefile',):
78 79 return object.__getattribute__(self, name)
79 80
80 81 return getattr(object.__getattribute__(self, '_orig'), name)
81 82
82 83 def __delattr__(self, name):
83 84 delattr(object.__getattribute__(self, '_orig'), name)
84 85
85 86 def __setattr__(self, name, value):
86 87 setattr(object.__getattribute__(self, '_orig'), name, value)
87 88
88 89 def makefile(self, mode, bufsize):
89 90 f = object.__getattribute__(self, '_orig').makefile(mode, bufsize)
90 91
91 92 logfp = object.__getattribute__(self, '_logfp')
92 93 closeafterrecvbytes = object.__getattribute__(self,
93 94 '_closeafterrecvbytes')
94 95 closeaftersendbytes = object.__getattribute__(self,
95 96 '_closeaftersendbytes')
96 97
97 98 return fileobjectproxy(f, logfp,
98 99 closeafterrecvbytes=closeafterrecvbytes,
99 100 closeaftersendbytes=closeaftersendbytes)
100 101
101 102 # We can't adjust __class__ on socket._fileobject, so define a proxy.
102 103 class fileobjectproxy(object):
103 104 __slots__ = (
104 105 '_orig',
105 106 '_logfp',
106 107 '_closeafterrecvbytes',
107 108 '_closeaftersendbytes',
108 109 )
109 110
110 111 def __init__(self, obj, logfp, closeafterrecvbytes=0,
111 112 closeaftersendbytes=0):
112 113 object.__setattr__(self, '_orig', obj)
113 114 object.__setattr__(self, '_logfp', logfp)
114 115 object.__setattr__(self, '_closeafterrecvbytes', closeafterrecvbytes)
115 116 object.__setattr__(self, '_closeaftersendbytes', closeaftersendbytes)
116 117
117 118 def __getattribute__(self, name):
118 if name in ('read', 'readline', 'write', '_writelog'):
119 if name in ('_close', 'read', 'readline', 'write', '_writelog'):
119 120 return object.__getattribute__(self, name)
120 121
121 122 return getattr(object.__getattribute__(self, '_orig'), name)
122 123
123 124 def __delattr__(self, name):
124 125 delattr(object.__getattribute__(self, '_orig'), name)
125 126
126 127 def __setattr__(self, name, value):
127 128 setattr(object.__getattribute__(self, '_orig'), name, value)
128 129
129 130 def _writelog(self, msg):
130 131 msg = msg.replace(b'\r', b'\\r').replace(b'\n', b'\\n')
131 132
132 133 object.__getattribute__(self, '_logfp').write(msg)
133 134 object.__getattribute__(self, '_logfp').write(b'\n')
134 135 object.__getattribute__(self, '_logfp').flush()
135 136
137 def _close(self):
138 # Python 3 uses an io.BufferedIO instance. Python 2 uses some file
139 # object wrapper.
140 if pycompat.ispy3:
141 orig = object.__getattribute__(self, '_orig')
142
143 if hasattr(orig, 'raw'):
144 orig.raw._sock.shutdown(socket.SHUT_RDWR)
145 else:
146 self.close()
147 else:
148 self._sock.shutdown(socket.SHUT_RDWR)
149
136 150 def read(self, size=-1):
137 151 remaining = object.__getattribute__(self, '_closeafterrecvbytes')
138 152
139 153 # No read limit. Call original function.
140 154 if not remaining:
141 155 result = object.__getattribute__(self, '_orig').read(size)
142 156 self._writelog(b'read(%d) -> (%d) (%s) %s' % (size,
143 157 len(result),
144 158 result))
145 159 return result
146 160
147 161 origsize = size
148 162
149 163 if size < 0:
150 164 size = remaining
151 165 else:
152 166 size = min(remaining, size)
153 167
154 168 result = object.__getattribute__(self, '_orig').read(size)
155 169 remaining -= len(result)
156 170
157 171 self._writelog(b'read(%d from %d) -> (%d) %s' % (
158 172 size, origsize, len(result), result))
159 173
160 174 object.__setattr__(self, '_closeafterrecvbytes', remaining)
161 175
162 176 if remaining <= 0:
163 177 self._writelog(b'read limit reached, closing socket')
164 self._sock.close()
178 self._close()
179
165 180 # This is the easiest way to abort the current request.
166 181 raise Exception('connection closed after receiving N bytes')
167 182
168 183 return result
169 184
170 185 def readline(self, size=-1):
171 186 remaining = object.__getattribute__(self, '_closeafterrecvbytes')
172 187
173 188 # No read limit. Call original function.
174 189 if not remaining:
175 190 result = object.__getattribute__(self, '_orig').readline(size)
176 191 self._writelog(b'readline(%d) -> (%d) %s' % (
177 192 size, len(result), result))
178 193 return result
179 194
180 195 origsize = size
181 196
182 197 if size < 0:
183 198 size = remaining
184 199 else:
185 200 size = min(remaining, size)
186 201
187 202 result = object.__getattribute__(self, '_orig').readline(size)
188 203 remaining -= len(result)
189 204
190 205 self._writelog(b'readline(%d from %d) -> (%d) %s' % (
191 206 size, origsize, len(result), result))
192 207
193 208 object.__setattr__(self, '_closeafterrecvbytes', remaining)
194 209
195 210 if remaining <= 0:
196 211 self._writelog(b'read limit reached; closing socket')
197 self._sock.close()
212 self._close()
213
198 214 # This is the easiest way to abort the current request.
199 215 raise Exception('connection closed after receiving N bytes')
200 216
201 217 return result
202 218
203 219 def write(self, data):
204 220 remaining = object.__getattribute__(self, '_closeaftersendbytes')
205 221
206 222 # No byte limit on this operation. Call original function.
207 223 if not remaining:
208 224 self._writelog(b'write(%d) -> %s' % (len(data), data))
209 225 result = object.__getattribute__(self, '_orig').write(data)
210 226 return result
211 227
212 228 if len(data) > remaining:
213 229 newdata = data[0:remaining]
214 230 else:
215 231 newdata = data
216 232
217 233 remaining -= len(newdata)
218 234
219 235 self._writelog(b'write(%d from %d) -> (%d) %s' % (
220 236 len(newdata), len(data), remaining, newdata))
221 237
222 238 result = object.__getattribute__(self, '_orig').write(newdata)
223 239
224 240 object.__setattr__(self, '_closeaftersendbytes', remaining)
225 241
226 242 if remaining <= 0:
227 243 self._writelog(b'write limit reached; closing socket')
228 self._sock.close()
244 self._close()
245
229 246 raise Exception('connection closed after sending N bytes')
230 247
231 248 return result
232 249
233 250 def extsetup(ui):
234 251 # Change the base HTTP server class so various events can be performed.
235 252 # See SocketServer.BaseServer for how the specially named methods work.
236 253 class badserver(server.MercurialHTTPServer):
237 254 def __init__(self, ui, *args, **kwargs):
238 255 self._ui = ui
239 256 super(badserver, self).__init__(ui, *args, **kwargs)
240 257
241 258 recvbytes = self._ui.config(b'badserver', b'closeafterrecvbytes')
242 259 recvbytes = recvbytes.split(b',')
243 260 self.closeafterrecvbytes = [int(v) for v in recvbytes if v]
244 261 sendbytes = self._ui.config(b'badserver', b'closeaftersendbytes')
245 262 sendbytes = sendbytes.split(b',')
246 263 self.closeaftersendbytes = [int(v) for v in sendbytes if v]
247 264
248 265 # Need to inherit object so super() works.
249 266 class badrequesthandler(self.RequestHandlerClass, object):
250 267 def send_header(self, name, value):
251 268 # Make headers deterministic to facilitate testing.
252 269 if name.lower() == 'date':
253 270 value = 'Fri, 14 Apr 2017 00:00:00 GMT'
254 271 elif name.lower() == 'server':
255 272 value = 'badhttpserver'
256 273
257 274 return super(badrequesthandler, self).send_header(name,
258 275 value)
259 276
260 277 self.RequestHandlerClass = badrequesthandler
261 278
262 279 # Called to accept() a pending socket.
263 280 def get_request(self):
264 281 if self._ui.configbool(b'badserver', b'closebeforeaccept'):
265 282 self.socket.close()
266 283
267 284 # Tells the server to stop processing more requests.
268 285 self.__shutdown_request = True
269 286
270 287 # Simulate failure to stop processing this request.
271 288 raise socket.error('close before accept')
272 289
273 290 if self._ui.configbool(b'badserver', b'closeafteraccept'):
274 291 request, client_address = super(badserver, self).get_request()
275 292 request.close()
276 293 raise socket.error('close after accept')
277 294
278 295 return super(badserver, self).get_request()
279 296
280 297 # Does heavy lifting of processing a request. Invokes
281 298 # self.finish_request() which calls self.RequestHandlerClass() which
282 299 # is a hgweb.server._httprequesthandler.
283 300 def process_request(self, socket, address):
284 301 # Wrap socket in a proxy if we need to count bytes.
285 302 if self.closeafterrecvbytes:
286 303 closeafterrecvbytes = self.closeafterrecvbytes.pop(0)
287 304 else:
288 305 closeafterrecvbytes = 0
289 306 if self.closeaftersendbytes:
290 307 closeaftersendbytes = self.closeaftersendbytes.pop(0)
291 308 else:
292 309 closeaftersendbytes = 0
293 310
294 311 if closeafterrecvbytes or closeaftersendbytes:
295 312 socket = socketproxy(socket, self.errorlog,
296 313 closeafterrecvbytes=closeafterrecvbytes,
297 314 closeaftersendbytes=closeaftersendbytes)
298 315
299 316 return super(badserver, self).process_request(socket, address)
300 317
301 318 server.MercurialHTTPServer = badserver
General Comments 0
You need to be logged in to leave comments. Login now