##// END OF EJS Templates
configitems: register the 'badserver.closeaftersendbytes' config
marmoute -
r33190:cbb50fd8 default
parent child Browse files
Show More
@@ -1,284 +1,287 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 37 registrar,
38 38 )
39 39
40 40 from mercurial.hgweb import (
41 41 server,
42 42 )
43 43
44 44 configtable = {}
45 45 configitem = registrar.configitem(configtable)
46 46
47 47 configitem('badserver', 'closeafteraccept',
48 48 default=False,
49 49 )
50 50 configitem('badserver', 'closeafterrecvbytes',
51 51 default=0,
52 52 )
53 configitem('badserver', 'closeaftersendbytes',
54 default=0,
55 )
53 56
54 57 # We can't adjust __class__ on a socket instance. So we define a proxy type.
55 58 class socketproxy(object):
56 59 __slots__ = (
57 60 '_orig',
58 61 '_logfp',
59 62 '_closeafterrecvbytes',
60 63 '_closeaftersendbytes',
61 64 )
62 65
63 66 def __init__(self, obj, logfp, closeafterrecvbytes=0,
64 67 closeaftersendbytes=0):
65 68 object.__setattr__(self, '_orig', obj)
66 69 object.__setattr__(self, '_logfp', logfp)
67 70 object.__setattr__(self, '_closeafterrecvbytes', closeafterrecvbytes)
68 71 object.__setattr__(self, '_closeaftersendbytes', closeaftersendbytes)
69 72
70 73 def __getattribute__(self, name):
71 74 if name in ('makefile',):
72 75 return object.__getattribute__(self, name)
73 76
74 77 return getattr(object.__getattribute__(self, '_orig'), name)
75 78
76 79 def __delattr__(self, name):
77 80 delattr(object.__getattribute__(self, '_orig'), name)
78 81
79 82 def __setattr__(self, name, value):
80 83 setattr(object.__getattribute__(self, '_orig'), name, value)
81 84
82 85 def makefile(self, mode, bufsize):
83 86 f = object.__getattribute__(self, '_orig').makefile(mode, bufsize)
84 87
85 88 logfp = object.__getattribute__(self, '_logfp')
86 89 closeafterrecvbytes = object.__getattribute__(self,
87 90 '_closeafterrecvbytes')
88 91 closeaftersendbytes = object.__getattribute__(self,
89 92 '_closeaftersendbytes')
90 93
91 94 return fileobjectproxy(f, logfp,
92 95 closeafterrecvbytes=closeafterrecvbytes,
93 96 closeaftersendbytes=closeaftersendbytes)
94 97
95 98 # We can't adjust __class__ on socket._fileobject, so define a proxy.
96 99 class fileobjectproxy(object):
97 100 __slots__ = (
98 101 '_orig',
99 102 '_logfp',
100 103 '_closeafterrecvbytes',
101 104 '_closeaftersendbytes',
102 105 )
103 106
104 107 def __init__(self, obj, logfp, closeafterrecvbytes=0,
105 108 closeaftersendbytes=0):
106 109 object.__setattr__(self, '_orig', obj)
107 110 object.__setattr__(self, '_logfp', logfp)
108 111 object.__setattr__(self, '_closeafterrecvbytes', closeafterrecvbytes)
109 112 object.__setattr__(self, '_closeaftersendbytes', closeaftersendbytes)
110 113
111 114 def __getattribute__(self, name):
112 115 if name in ('read', 'readline', 'write', '_writelog'):
113 116 return object.__getattribute__(self, name)
114 117
115 118 return getattr(object.__getattribute__(self, '_orig'), name)
116 119
117 120 def __delattr__(self, name):
118 121 delattr(object.__getattribute__(self, '_orig'), name)
119 122
120 123 def __setattr__(self, name, value):
121 124 setattr(object.__getattribute__(self, '_orig'), name, value)
122 125
123 126 def _writelog(self, msg):
124 127 msg = msg.replace('\r', '\\r').replace('\n', '\\n')
125 128
126 129 object.__getattribute__(self, '_logfp').write(msg)
127 130 object.__getattribute__(self, '_logfp').write('\n')
128 131 object.__getattribute__(self, '_logfp').flush()
129 132
130 133 def read(self, size=-1):
131 134 remaining = object.__getattribute__(self, '_closeafterrecvbytes')
132 135
133 136 # No read limit. Call original function.
134 137 if not remaining:
135 138 result = object.__getattribute__(self, '_orig').read(size)
136 139 self._writelog('read(%d) -> (%d) (%s) %s' % (size,
137 140 len(result),
138 141 result))
139 142 return result
140 143
141 144 origsize = size
142 145
143 146 if size < 0:
144 147 size = remaining
145 148 else:
146 149 size = min(remaining, size)
147 150
148 151 result = object.__getattribute__(self, '_orig').read(size)
149 152 remaining -= len(result)
150 153
151 154 self._writelog('read(%d from %d) -> (%d) %s' % (
152 155 size, origsize, len(result), result))
153 156
154 157 object.__setattr__(self, '_closeafterrecvbytes', remaining)
155 158
156 159 if remaining <= 0:
157 160 self._writelog('read limit reached, closing socket')
158 161 self._sock.close()
159 162 # This is the easiest way to abort the current request.
160 163 raise Exception('connection closed after receiving N bytes')
161 164
162 165 return result
163 166
164 167 def readline(self, size=-1):
165 168 remaining = object.__getattribute__(self, '_closeafterrecvbytes')
166 169
167 170 # No read limit. Call original function.
168 171 if not remaining:
169 172 result = object.__getattribute__(self, '_orig').readline(size)
170 173 self._writelog('readline(%d) -> (%d) %s' % (
171 174 size, len(result), result))
172 175 return result
173 176
174 177 origsize = size
175 178
176 179 if size < 0:
177 180 size = remaining
178 181 else:
179 182 size = min(remaining, size)
180 183
181 184 result = object.__getattribute__(self, '_orig').readline(size)
182 185 remaining -= len(result)
183 186
184 187 self._writelog('readline(%d from %d) -> (%d) %s' % (
185 188 size, origsize, len(result), result))
186 189
187 190 object.__setattr__(self, '_closeafterrecvbytes', remaining)
188 191
189 192 if remaining <= 0:
190 193 self._writelog('read limit reached; closing socket')
191 194 self._sock.close()
192 195 # This is the easiest way to abort the current request.
193 196 raise Exception('connection closed after receiving N bytes')
194 197
195 198 return result
196 199
197 200 def write(self, data):
198 201 remaining = object.__getattribute__(self, '_closeaftersendbytes')
199 202
200 203 # No byte limit on this operation. Call original function.
201 204 if not remaining:
202 205 self._writelog('write(%d) -> %s' % (len(data), data))
203 206 result = object.__getattribute__(self, '_orig').write(data)
204 207 return result
205 208
206 209 if len(data) > remaining:
207 210 newdata = data[0:remaining]
208 211 else:
209 212 newdata = data
210 213
211 214 remaining -= len(newdata)
212 215
213 216 self._writelog('write(%d from %d) -> (%d) %s' % (
214 217 len(newdata), len(data), remaining, newdata))
215 218
216 219 result = object.__getattribute__(self, '_orig').write(newdata)
217 220
218 221 object.__setattr__(self, '_closeaftersendbytes', remaining)
219 222
220 223 if remaining <= 0:
221 224 self._writelog('write limit reached; closing socket')
222 225 self._sock.close()
223 226 raise Exception('connection closed after sending N bytes')
224 227
225 228 return result
226 229
227 230 def extsetup(ui):
228 231 # Change the base HTTP server class so various events can be performed.
229 232 # See SocketServer.BaseServer for how the specially named methods work.
230 233 class badserver(server.MercurialHTTPServer):
231 234 def __init__(self, ui, *args, **kwargs):
232 235 self._ui = ui
233 236 super(badserver, self).__init__(ui, *args, **kwargs)
234 237
235 238 # Need to inherit object so super() works.
236 239 class badrequesthandler(self.RequestHandlerClass, object):
237 240 def send_header(self, name, value):
238 241 # Make headers deterministic to facilitate testing.
239 242 if name.lower() == 'date':
240 243 value = 'Fri, 14 Apr 2017 00:00:00 GMT'
241 244 elif name.lower() == 'server':
242 245 value = 'badhttpserver'
243 246
244 247 return super(badrequesthandler, self).send_header(name,
245 248 value)
246 249
247 250 self.RequestHandlerClass = badrequesthandler
248 251
249 252 # Called to accept() a pending socket.
250 253 def get_request(self):
251 254 if self._ui.configbool('badserver', 'closebeforeaccept'):
252 255 self.socket.close()
253 256
254 257 # Tells the server to stop processing more requests.
255 258 self.__shutdown_request = True
256 259
257 260 # Simulate failure to stop processing this request.
258 261 raise socket.error('close before accept')
259 262
260 263 if self._ui.configbool('badserver', 'closeafteraccept'):
261 264 request, client_address = super(badserver, self).get_request()
262 265 request.close()
263 266 raise socket.error('close after accept')
264 267
265 268 return super(badserver, self).get_request()
266 269
267 270 # Does heavy lifting of processing a request. Invokes
268 271 # self.finish_request() which calls self.RequestHandlerClass() which
269 272 # is a hgweb.server._httprequesthandler.
270 273 def process_request(self, socket, address):
271 274 # Wrap socket in a proxy if we need to count bytes.
272 275 closeafterrecvbytes = self._ui.configint('badserver',
273 276 'closeafterrecvbytes')
274 277 closeaftersendbytes = self._ui.configint('badserver',
275 'closeaftersendbytes', 0)
278 'closeaftersendbytes')
276 279
277 280 if closeafterrecvbytes or closeaftersendbytes:
278 281 socket = socketproxy(socket, self.errorlog,
279 282 closeafterrecvbytes=closeafterrecvbytes,
280 283 closeaftersendbytes=closeaftersendbytes)
281 284
282 285 return super(badserver, self).process_request(socket, address)
283 286
284 287 server.MercurialHTTPServer = badserver
General Comments 0
You need to be logged in to leave comments. Login now