badserverext.py
376 lines
| 12.2 KiB
| text/x-python
|
PythonLexer
/ tests / badserverext.py
Gregory Szorc
|
r32001 | # badserverext.py - Extension making servers behave badly | ||
# | ||||
# Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com> | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
# no-check-code | ||||
"""Extension to make servers behave badly. | ||||
This extension is useful for testing Mercurial behavior when various network | ||||
events occur. | ||||
Various config options in the [badserver] section influence behavior: | ||||
closebeforeaccept | ||||
If true, close() the server socket when a new connection arrives before | ||||
accept() is called. The server will then exit. | ||||
closeafteraccept | ||||
If true, the server will close() the client socket immediately after | ||||
accept(). | ||||
closeafterrecvbytes | ||||
If defined, close the client socket after receiving this many bytes. | ||||
closeaftersendbytes | ||||
If defined, close the client socket after sending this many bytes. | ||||
""" | ||||
from __future__ import absolute_import | ||||
import socket | ||||
Augie Fackler
|
r43346 | from mercurial import ( | ||
Gregory Szorc
|
r41605 | pycompat, | ||
r33188 | registrar, | |||
) | ||||
Augie Fackler
|
r43346 | from mercurial.hgweb import server | ||
Gregory Szorc
|
r32001 | |||
r33188 | configtable = {} | |||
configitem = registrar.configitem(configtable) | ||||
Augie Fackler
|
r43346 | configitem( | ||
b'badserver', b'closeafteraccept', default=False, | ||||
r33188 | ) | |||
Augie Fackler
|
r43346 | configitem( | ||
b'badserver', b'closeafterrecvbytes', default=b'0', | ||||
r33189 | ) | |||
Augie Fackler
|
r43346 | configitem( | ||
b'badserver', b'closeaftersendbytes', default=b'0', | ||||
r33190 | ) | |||
Augie Fackler
|
r43346 | configitem( | ||
b'badserver', b'closebeforeaccept', default=False, | ||||
r33191 | ) | |||
r33188 | ||||
Gregory Szorc
|
r32001 | # We can't adjust __class__ on a socket instance. So we define a proxy type. | ||
class socketproxy(object): | ||||
__slots__ = ( | ||||
'_orig', | ||||
'_logfp', | ||||
'_closeafterrecvbytes', | ||||
'_closeaftersendbytes', | ||||
) | ||||
Augie Fackler
|
r43346 | def __init__( | ||
self, obj, logfp, closeafterrecvbytes=0, closeaftersendbytes=0 | ||||
): | ||||
Gregory Szorc
|
r32001 | object.__setattr__(self, '_orig', obj) | ||
object.__setattr__(self, '_logfp', logfp) | ||||
object.__setattr__(self, '_closeafterrecvbytes', closeafterrecvbytes) | ||||
object.__setattr__(self, '_closeaftersendbytes', closeaftersendbytes) | ||||
def __getattribute__(self, name): | ||||
Gregory Szorc
|
r41607 | if name in ('makefile', 'sendall', '_writelog'): | ||
Gregory Szorc
|
r32001 | return object.__getattribute__(self, name) | ||
return getattr(object.__getattribute__(self, '_orig'), name) | ||||
def __delattr__(self, name): | ||||
delattr(object.__getattribute__(self, '_orig'), name) | ||||
def __setattr__(self, name, value): | ||||
setattr(object.__getattribute__(self, '_orig'), name, value) | ||||
Gregory Szorc
|
r41607 | def _writelog(self, msg): | ||
msg = msg.replace(b'\r', b'\\r').replace(b'\n', b'\\n') | ||||
object.__getattribute__(self, '_logfp').write(msg) | ||||
object.__getattribute__(self, '_logfp').write(b'\n') | ||||
object.__getattribute__(self, '_logfp').flush() | ||||
Gregory Szorc
|
r32001 | def makefile(self, mode, bufsize): | ||
f = object.__getattribute__(self, '_orig').makefile(mode, bufsize) | ||||
logfp = object.__getattribute__(self, '_logfp') | ||||
Augie Fackler
|
r43346 | closeafterrecvbytes = object.__getattribute__( | ||
self, '_closeafterrecvbytes' | ||||
) | ||||
closeaftersendbytes = object.__getattribute__( | ||||
self, '_closeaftersendbytes' | ||||
) | ||||
Gregory Szorc
|
r32001 | |||
Augie Fackler
|
r43346 | return fileobjectproxy( | ||
f, | ||||
logfp, | ||||
closeafterrecvbytes=closeafterrecvbytes, | ||||
closeaftersendbytes=closeaftersendbytes, | ||||
) | ||||
Gregory Szorc
|
r32001 | |||
Gregory Szorc
|
r41607 | def sendall(self, data, flags=0): | ||
remaining = object.__getattribute__(self, '_closeaftersendbytes') | ||||
# No read limit. Call original function. | ||||
if not remaining: | ||||
result = object.__getattribute__(self, '_orig').sendall(data, flags) | ||||
self._writelog(b'sendall(%d) -> %s' % (len(data), data)) | ||||
return result | ||||
if len(data) > remaining: | ||||
newdata = data[0:remaining] | ||||
else: | ||||
newdata = data | ||||
remaining -= len(newdata) | ||||
result = object.__getattribute__(self, '_orig').sendall(newdata, flags) | ||||
Augie Fackler
|
r43346 | self._writelog( | ||
b'sendall(%d from %d) -> (%d) %s' | ||||
% (len(newdata), len(data), remaining, newdata) | ||||
) | ||||
Gregory Szorc
|
r41607 | |||
object.__setattr__(self, '_closeaftersendbytes', remaining) | ||||
if remaining <= 0: | ||||
self._writelog(b'write limit reached; closing socket') | ||||
object.__getattribute__(self, '_orig').shutdown(socket.SHUT_RDWR) | ||||
raise Exception('connection closed after sending N bytes') | ||||
return result | ||||
Gregory Szorc
|
r32001 | # We can't adjust __class__ on socket._fileobject, so define a proxy. | ||
class fileobjectproxy(object): | ||||
__slots__ = ( | ||||
'_orig', | ||||
'_logfp', | ||||
'_closeafterrecvbytes', | ||||
'_closeaftersendbytes', | ||||
) | ||||
Augie Fackler
|
r43346 | def __init__( | ||
self, obj, logfp, closeafterrecvbytes=0, closeaftersendbytes=0 | ||||
): | ||||
Gregory Szorc
|
r32001 | object.__setattr__(self, '_orig', obj) | ||
object.__setattr__(self, '_logfp', logfp) | ||||
object.__setattr__(self, '_closeafterrecvbytes', closeafterrecvbytes) | ||||
object.__setattr__(self, '_closeaftersendbytes', closeaftersendbytes) | ||||
def __getattribute__(self, name): | ||||
Gregory Szorc
|
r41605 | if name in ('_close', 'read', 'readline', 'write', '_writelog'): | ||
Gregory Szorc
|
r32001 | return object.__getattribute__(self, name) | ||
return getattr(object.__getattribute__(self, '_orig'), name) | ||||
def __delattr__(self, name): | ||||
delattr(object.__getattribute__(self, '_orig'), name) | ||||
def __setattr__(self, name, value): | ||||
setattr(object.__getattribute__(self, '_orig'), name, value) | ||||
def _writelog(self, msg): | ||||
Gregory Szorc
|
r41604 | msg = msg.replace(b'\r', b'\\r').replace(b'\n', b'\\n') | ||
Gregory Szorc
|
r32001 | |||
object.__getattribute__(self, '_logfp').write(msg) | ||||
Gregory Szorc
|
r41604 | object.__getattribute__(self, '_logfp').write(b'\n') | ||
Matt Harbison
|
r32021 | object.__getattribute__(self, '_logfp').flush() | ||
Gregory Szorc
|
r32001 | |||
Gregory Szorc
|
r41605 | def _close(self): | ||
# Python 3 uses an io.BufferedIO instance. Python 2 uses some file | ||||
# object wrapper. | ||||
if pycompat.ispy3: | ||||
orig = object.__getattribute__(self, '_orig') | ||||
if hasattr(orig, 'raw'): | ||||
orig.raw._sock.shutdown(socket.SHUT_RDWR) | ||||
else: | ||||
self.close() | ||||
else: | ||||
self._sock.shutdown(socket.SHUT_RDWR) | ||||
Gregory Szorc
|
r32001 | def read(self, size=-1): | ||
remaining = object.__getattribute__(self, '_closeafterrecvbytes') | ||||
# No read limit. Call original function. | ||||
if not remaining: | ||||
result = object.__getattribute__(self, '_orig').read(size) | ||||
Augie Fackler
|
r43346 | self._writelog( | ||
b'read(%d) -> (%d) (%s) %s' % (size, len(result), result) | ||||
) | ||||
Gregory Szorc
|
r32001 | return result | ||
origsize = size | ||||
if size < 0: | ||||
size = remaining | ||||
else: | ||||
size = min(remaining, size) | ||||
result = object.__getattribute__(self, '_orig').read(size) | ||||
remaining -= len(result) | ||||
Augie Fackler
|
r43346 | self._writelog( | ||
b'read(%d from %d) -> (%d) %s' | ||||
% (size, origsize, len(result), result) | ||||
) | ||||
Gregory Szorc
|
r32001 | |||
object.__setattr__(self, '_closeafterrecvbytes', remaining) | ||||
if remaining <= 0: | ||||
Gregory Szorc
|
r41604 | self._writelog(b'read limit reached, closing socket') | ||
Gregory Szorc
|
r41605 | self._close() | ||
Gregory Szorc
|
r32001 | # This is the easiest way to abort the current request. | ||
raise Exception('connection closed after receiving N bytes') | ||||
return result | ||||
def readline(self, size=-1): | ||||
remaining = object.__getattribute__(self, '_closeafterrecvbytes') | ||||
# No read limit. Call original function. | ||||
if not remaining: | ||||
result = object.__getattribute__(self, '_orig').readline(size) | ||||
Augie Fackler
|
r43346 | self._writelog( | ||
b'readline(%d) -> (%d) %s' % (size, len(result), result) | ||||
) | ||||
Gregory Szorc
|
r32001 | return result | ||
origsize = size | ||||
if size < 0: | ||||
size = remaining | ||||
else: | ||||
size = min(remaining, size) | ||||
result = object.__getattribute__(self, '_orig').readline(size) | ||||
remaining -= len(result) | ||||
Augie Fackler
|
r43346 | self._writelog( | ||
b'readline(%d from %d) -> (%d) %s' | ||||
% (size, origsize, len(result), result) | ||||
) | ||||
Gregory Szorc
|
r32001 | |||
object.__setattr__(self, '_closeafterrecvbytes', remaining) | ||||
if remaining <= 0: | ||||
Gregory Szorc
|
r41604 | self._writelog(b'read limit reached; closing socket') | ||
Gregory Szorc
|
r41605 | self._close() | ||
Gregory Szorc
|
r32001 | # This is the easiest way to abort the current request. | ||
raise Exception('connection closed after receiving N bytes') | ||||
return result | ||||
def write(self, data): | ||||
remaining = object.__getattribute__(self, '_closeaftersendbytes') | ||||
# No byte limit on this operation. Call original function. | ||||
if not remaining: | ||||
Gregory Szorc
|
r41604 | self._writelog(b'write(%d) -> %s' % (len(data), data)) | ||
Gregory Szorc
|
r32001 | result = object.__getattribute__(self, '_orig').write(data) | ||
return result | ||||
if len(data) > remaining: | ||||
newdata = data[0:remaining] | ||||
else: | ||||
newdata = data | ||||
remaining -= len(newdata) | ||||
Augie Fackler
|
r43346 | self._writelog( | ||
b'write(%d from %d) -> (%d) %s' | ||||
% (len(newdata), len(data), remaining, newdata) | ||||
) | ||||
Gregory Szorc
|
r32001 | |||
result = object.__getattribute__(self, '_orig').write(newdata) | ||||
object.__setattr__(self, '_closeaftersendbytes', remaining) | ||||
if remaining <= 0: | ||||
Gregory Szorc
|
r41604 | self._writelog(b'write limit reached; closing socket') | ||
Gregory Szorc
|
r41605 | self._close() | ||
Gregory Szorc
|
r32001 | raise Exception('connection closed after sending N bytes') | ||
return result | ||||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r32001 | def extsetup(ui): | ||
# Change the base HTTP server class so various events can be performed. | ||||
# See SocketServer.BaseServer for how the specially named methods work. | ||||
class badserver(server.MercurialHTTPServer): | ||||
def __init__(self, ui, *args, **kwargs): | ||||
self._ui = ui | ||||
super(badserver, self).__init__(ui, *args, **kwargs) | ||||
Pulkit Goyal
|
r39459 | recvbytes = self._ui.config(b'badserver', b'closeafterrecvbytes') | ||
Gregory Szorc
|
r41604 | recvbytes = recvbytes.split(b',') | ||
Joerg Sonnenberger
|
r37516 | self.closeafterrecvbytes = [int(v) for v in recvbytes if v] | ||
Pulkit Goyal
|
r39459 | sendbytes = self._ui.config(b'badserver', b'closeaftersendbytes') | ||
Gregory Szorc
|
r41604 | sendbytes = sendbytes.split(b',') | ||
Joerg Sonnenberger
|
r37516 | self.closeaftersendbytes = [int(v) for v in sendbytes if v] | ||
Gregory Szorc
|
r32001 | # Need to inherit object so super() works. | ||
class badrequesthandler(self.RequestHandlerClass, object): | ||||
def send_header(self, name, value): | ||||
# Make headers deterministic to facilitate testing. | ||||
if name.lower() == 'date': | ||||
value = 'Fri, 14 Apr 2017 00:00:00 GMT' | ||||
elif name.lower() == 'server': | ||||
value = 'badhttpserver' | ||||
Augie Fackler
|
r43346 | return super(badrequesthandler, self).send_header( | ||
name, value | ||||
) | ||||
Gregory Szorc
|
r32001 | |||
self.RequestHandlerClass = badrequesthandler | ||||
# Called to accept() a pending socket. | ||||
def get_request(self): | ||||
Pulkit Goyal
|
r39459 | if self._ui.configbool(b'badserver', b'closebeforeaccept'): | ||
Gregory Szorc
|
r32001 | self.socket.close() | ||
# Tells the server to stop processing more requests. | ||||
self.__shutdown_request = True | ||||
# Simulate failure to stop processing this request. | ||||
raise socket.error('close before accept') | ||||
Gregory Szorc
|
r41604 | if self._ui.configbool(b'badserver', b'closeafteraccept'): | ||
Gregory Szorc
|
r32001 | request, client_address = super(badserver, self).get_request() | ||
request.close() | ||||
raise socket.error('close after accept') | ||||
return super(badserver, self).get_request() | ||||
# Does heavy lifting of processing a request. Invokes | ||||
# self.finish_request() which calls self.RequestHandlerClass() which | ||||
# is a hgweb.server._httprequesthandler. | ||||
def process_request(self, socket, address): | ||||
# Wrap socket in a proxy if we need to count bytes. | ||||
Joerg Sonnenberger
|
r37516 | if self.closeafterrecvbytes: | ||
closeafterrecvbytes = self.closeafterrecvbytes.pop(0) | ||||
else: | ||||
closeafterrecvbytes = 0 | ||||
if self.closeaftersendbytes: | ||||
closeaftersendbytes = self.closeaftersendbytes.pop(0) | ||||
else: | ||||
closeaftersendbytes = 0 | ||||
Gregory Szorc
|
r32001 | |||
if closeafterrecvbytes or closeaftersendbytes: | ||||
Augie Fackler
|
r43346 | socket = socketproxy( | ||
socket, | ||||
self.errorlog, | ||||
closeafterrecvbytes=closeafterrecvbytes, | ||||
closeaftersendbytes=closeaftersendbytes, | ||||
) | ||||
Gregory Szorc
|
r32001 | |||
return super(badserver, self).process_request(socket, address) | ||||
server.MercurialHTTPServer = badserver | ||||