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