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