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