656947c82f
Compared to the existing Resend tests, the new ResendProxy tests have the advantage that they cover one additional error case: loss of connection before the server has a chance to send the full reply. This happened to be one case that the syncevo-http-server wasn't handling properly. This failure cannot be simulated inside client-test because the TransportAgent API doesn't provide enough control. The implementation uses a twisted HTTP proxy which intercepts data transfer at three different points (before sending, after sending and before receiving, after receiving) and drops the client connection. The script listens on four different ports, numbered consecutively, with the first one not introducing errors, and all others representing one failure. The test must set and unset proxy information in the underlying transport; this patch makes that possible in SoupTransportAgent::setProxy(). To use the new tests, run "proxy.py <port>", then set CLIENT_TEST_RESEND_PROXY=<port>.
103 lines
3.8 KiB
Python
103 lines
3.8 KiB
Python
from twisted.internet import reactor
|
|
from twisted.web import http
|
|
from twisted.web.proxy import Proxy, ProxyRequest, ProxyClient, ProxyClientFactory
|
|
from twisted.python import log
|
|
import sys
|
|
|
|
class ContentWrapper:
|
|
def __init__(self, content, parent):
|
|
self.content = content
|
|
self.parent = parent
|
|
|
|
def seek(self, a, b):
|
|
self.content.seek(a,b)
|
|
|
|
def read(self):
|
|
s = self.content.read()
|
|
if True:
|
|
log.msg("interrupt before send")
|
|
# This exception will abort contacting the server and appear as
|
|
# "Unhandled Error" in the proxy log. The proxy client will get
|
|
# an empty reply.
|
|
raise EOFError()
|
|
return s
|
|
|
|
def close(self):
|
|
self.content.close()
|
|
|
|
class MyProxyClient(ProxyClient):
|
|
def __init__(self, command, rest, version, headers, data, father):
|
|
ProxyClient.__init__(self, command, rest, version, headers, data, father)
|
|
|
|
def connectionMade(self):
|
|
ProxyClient.connectionMade(self)
|
|
log.msg("message sent")
|
|
# interrupt now before server can reply?
|
|
if self.father.mode == MyProxyRequest.INTERRUPT_AFTER_SEND:
|
|
log.msg("interrupt after sending")
|
|
# finish writing, but never read
|
|
self.transport.loseConnection()
|
|
# Be nice and report a real error back to the proxy client.
|
|
self.father.setResponseCode(501, "Gateway error")
|
|
self.father.responseHeaders.addRawHeader("Content-Type", "text/plain")
|
|
self.father.write("connection intentionally interrupted after sending and before receiving")
|
|
|
|
class MyProxyClientFactory(ProxyClientFactory):
|
|
protocol = MyProxyClient
|
|
|
|
def __init__(self, command, rest, version, headers, data, father):
|
|
ProxyClientFactory.__init__(self, command, rest, version, headers, data, father)
|
|
|
|
class MyProxyRequest(ProxyRequest):
|
|
protocols = {"http": MyProxyClientFactory}
|
|
INTERRUPT_BEFORE_SEND = 1
|
|
INTERRUPT_AFTER_SEND = 2
|
|
INTERRUPT_AFTER_RECEIVE = 3
|
|
baseport = 10000
|
|
|
|
def __init__(self, channel, queued, reactor=reactor):
|
|
ProxyRequest.__init__(self, channel, queued, reactor)
|
|
self.mode = channel.transport.server.port - self.baseport
|
|
|
|
def process(self):
|
|
log.msg("mode is", self.mode)
|
|
|
|
# override read() method so that we can influence the original
|
|
# process() without having to copy it; just replacing
|
|
# the read method inside the existing content instance
|
|
# would be easier, but turned out to be impossible (read-only
|
|
# attribute)
|
|
if self.mode == self.INTERRUPT_BEFORE_SEND:
|
|
# ContentWrapper will raise exception instead of delivering data
|
|
self.content = ContentWrapper(self.content, self)
|
|
ProxyRequest.process(self)
|
|
|
|
def write(self, content):
|
|
log.msg("reply:", content)
|
|
if self.mode == self.INTERRUPT_AFTER_RECEIVE:
|
|
# TODO: suppress original headers
|
|
# Original headers already sent to proxy client, but we
|
|
# can still suppress the actual data and close the
|
|
# connection to simulate a failure.
|
|
log.msg("interrupt after receive")
|
|
ProxyRequest.write(self, "")
|
|
self.transport.loseConnection()
|
|
else:
|
|
ProxyRequest.write(self, content)
|
|
|
|
class MyProxy(Proxy):
|
|
requestFactory = MyProxyRequest
|
|
|
|
if __name__ == '__main__':
|
|
log.startLogging(sys.stdout)
|
|
|
|
f = http.HTTPFactory()
|
|
f.protocol = MyProxy
|
|
reactor.listenTCP(MyProxyRequest.baseport + 0, f)
|
|
reactor.listenTCP(MyProxyRequest.baseport + MyProxyRequest.INTERRUPT_BEFORE_SEND, f)
|
|
reactor.listenTCP(MyProxyRequest.baseport + MyProxyRequest.INTERRUPT_AFTER_SEND, f)
|
|
reactor.listenTCP(MyProxyRequest.baseport + MyProxyRequest.INTERRUPT_AFTER_RECEIVE, f)
|
|
|
|
reactor.run()
|
|
|