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