tahoe-lafs/src/allmydata/protocol_switch.py

119 lines
3.8 KiB
Python
Raw Normal View History

"""
Support for listening with both HTTP and Foolscap on the same port.
"""
from typing import Optional, Tuple
from twisted.internet.protocol import Protocol
from twisted.internet.interfaces import ITransport
2022-06-23 12:41:47 -04:00
from twisted.internet.ssl import CertificateOptions, PrivateCertificate
from twisted.web.server import Site
from twisted.protocols.tls import TLSMemoryBIOFactory
from foolscap.negotiate import Negotiation
from .storage.http_server import HTTPServer
2022-06-23 07:59:43 -04:00
from .storage.server import StorageServer
2022-06-22 10:23:23 -04:00
class PretendToBeNegotiation(type):
"""😱"""
def __instancecheck__(self, instance):
return (instance.__class__ == self) or isinstance(instance, Negotiation)
class FoolscapOrHttp(Protocol, metaclass=PretendToBeNegotiation):
"""
Based on initial query, decide whether we're talking Foolscap or HTTP.
Pretends to be a ``foolscap.negotiate.Negotiation`` instance.
"""
2022-06-22 10:23:23 -04:00
2022-06-23 12:43:46 -04:00
# These three will be set by a subclass in update_foolscap_or_http_class()
# below.
2022-06-23 07:59:43 -04:00
swissnum: bytes
2022-06-23 12:41:47 -04:00
certificate: PrivateCertificate
2022-06-23 07:59:43 -04:00
storage_server: StorageServer
def __init__(self, *args, **kwargs):
2022-06-23 12:41:01 -04:00
self._foolscap: Negotiation = Negotiation(*args, **kwargs)
self._buffer: bytes = b""
def __setattr__(self, name, value):
2022-06-22 10:23:23 -04:00
if name in {
"_foolscap",
"_buffer",
"transport",
"__class__",
}:
object.__setattr__(self, name, value)
else:
setattr(self._foolscap, name, value)
def __getattr__(self, name):
return getattr(self._foolscap, name)
2022-06-23 12:43:46 -04:00
def _convert_to_negotiation(self):
"""
2022-06-23 12:44:17 -04:00
Convert self to a ``Negotiation`` instance.
2022-06-23 12:43:46 -04:00
"""
self.__class__ = Negotiation # type: ignore
self.__dict__ = self._foolscap.__dict__
def initClient(self, *args, **kwargs):
2022-06-22 10:23:23 -04:00
# After creation, a Negotiation instance either has initClient() or
2022-06-23 12:43:46 -04:00
# initServer() called. Since this is a client, we're never going to do
# HTTP, so we can immediately become a Negotiation instance.
assert not self._buffer
self._convert_to_negotiation()
2022-06-22 10:23:23 -04:00
return self.initClient(*args, **kwargs)
def dataReceived(self, data: bytes) -> None:
2022-06-23 12:41:01 -04:00
"""Handle incoming data.
2022-06-23 12:41:01 -04:00
Once we've decided which protocol we are, update self.__class__, at
which point all methods will be called on the new class.
"""
self._buffer += data
if len(self._buffer) < 8:
return
2022-06-22 10:23:23 -04:00
# Check if it looks like a Foolscap request. If so, it can handle this
# and later data:
if self._buffer.startswith(b"GET /id/"):
2022-06-23 12:43:46 -04:00
transport = self.transport
buf = self._buffer
self._convert_to_negotiation()
self.makeConnection(transport)
self.dataReceived(buf)
return
else:
certificate_options = CertificateOptions(
privateKey=self.certificate.privateKey.original,
certificate=self.certificate.original,
)
http_server = HTTPServer(self.storage_server, self.swissnum)
factory = TLSMemoryBIOFactory(
certificate_options, False, Site(http_server.get_resource())
)
2022-06-23 12:41:47 -04:00
assert self.transport is not None
protocol = factory.buildProtocol(self.transport.getPeer())
protocol.makeConnection(self.transport)
protocol.dataReceived(self._buffer)
2022-06-23 12:41:01 -04:00
self.__class__ = protocol.__class__
self.__dict__ = protocol.__dict__
2022-06-23 07:59:43 -04:00
def create_foolscap_or_http_class():
class FoolscapOrHttpWithCert(FoolscapOrHttp):
pass
return FoolscapOrHttpWithCert
def update_foolscap_or_http_class(cls, certificate, storage_server, swissnum):
cls.certificate = certificate
cls.storage_server = storage_server
cls.swissnum = swissnum