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