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