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