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