Flusso dei dati
La classe Proxy non fa altro che ereditare da HTTPChannel ed impostare requestFactory=ProxyRequest:
class Proxy(HTTPChannel):
requestFactory = ProxyRequest
La classe HTTPChannel è un ricevitore di richieste: in essa viene creata la coda delle richieste, e il callback lineReceived(self, line) viene chiamato ogni volta che viene ricevuta una linea, creando una request se la linea è la “prima ricevuta”, oppure completando una request se è una linea successiva.
Quando tutta la request è stata ricevuta dal client, viene eseguito il metodo allContentReceived(): questo a sua volta chiama il metodo request.requestReceived(command, path, version) della richiesta, il quale vedremo in seguito eseguirà la richiesta.
Quando la richiesta avrà finito, essa chiamerà il metodo requestDone di HTTPChannel: se la connessione del client non è persistente verrà chiusa.
class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin):
....
requestFactory = Request
....
def __init__(self):
# the request queue
self.requests = []
....
def lineReceived(self, line):
if self.__first_line:
.... # fa qualche pulizia di dati precedenti
request = self.requestFactory(self, len(self.requests))
self.requests.append(request)
self.__first_line = 0
....
elif line == b'':
....
self.allHeadersReceived()
if self.length == 0:
self.allContentReceived()
....
elif line[0] in b' \t':
....
else:
....
self.__header = line
....
def allContentReceived(self):
command = self._command
path = self._path
version = self._version
....
req = self.requests[-1] # prende la richiesta e
req.requestReceived(command, path, version) # chiama il suo metodo
....
def requestDone(self, request):
"""
Called by first request in queue when it is done.
"""
if request != self.requests[0]: raise TypeError
del self.requests[0]
if self.persistent:
....
else:
self.transport.loseConnection()
A questo punto bisogna ricordare che Proxy (erede di HTTPChannel) ha la proprietà requestFactory impostata a ProxyRequest.
La classe ProxyRequest eredita da Request e ne implementa il metodo astratto process(), oltre a dichiarare il protocollo da adoperare per eseguire la richiesta:
class ProxyRequest(Request):
protocols = {'http': ProxyClientFactory}
ports = {'http': 80}
....
def process(self):
.... # varie inizializzazioni
port = self.ports[protocol]
class_ = self.protocols[protocol]
s = self.content.read()
clientFactory = class_(self.method, rest, self.clientproto, headers,
s, self)
self.reactor.connectTCP(host, port, clientFactory)
Nella classe genitrice Request ci sono alcuni metodi adoperati da HTTPChannel:
class Request:
....
def requestReceived(self, command, path, version):
# inizializzazioni varie
self.process()
....
def process(self):
pass
....
def notifyFinish(self):
self.notifications.append(Deferred())
return self.notifications[-1]
def finish(self):
if self._disconnected:
raise RuntimeError(
"Request.finish called on a request after its connection was lost; "
"use Request.notifyFinish to keep track of this.")
....
self.finished = 1
def write(self, data):
if self.finished:
raise RuntimeError('Request.write called on a request after '
'Request.finish was called.')
if not self.startedWriting: # intanto invia la parte HEAD
self.startedWriting = 1
version = self.clientproto
l = []
l.append(
version + b" " +
intToBytes(self.code) + b" " +
networkString(self.code_message) + b"\r\n")
....
l.append(b"\r\n")
self.transport.writeSequence(l)
....
# if this is a "HEAD" request, we shouldn't return any data
if self.method == b"HEAD":
self.write = lambda data: None
return
self.sentLength = self.sentLength + len(data)
if data:
if self.chunked:
self.transport.writeSequence(toChunk(data))
else:
self.transport.write(data)
....
def connectionLost(self, reason):
"""
There is no longer a connection for this request to respond over.
Clean up anything which can't be useful anymore.
"""
self._disconnected = True
self.channel = None
if self.content is not None:
self.content.close()
for d in self.notifications:
d.errback(reason)
self.notifications = []
A questo punto la situazione è la seguente:
- Proxy(HTTPChannel) riceve la connessione con lineReceived e crea una ProxyRequest(Request), inserendola nella sua coda
- Quando la request è completa, sempre in lineReceived viene chiamato allContentReceived
- allContentReceived chiama ProxyRequest.requestReceived
- requestReceived chiama ProxyRequest.process()
- ProxyRequest.process() crea un clientFactory e lo connette alla destinazione con self.reactor.connectTCP(host, port, clientFactory)
Si è visto che ProxyRequest.process() istanzia una ProxyClientFactory e la connette al server di destinazione della richiesta, per ricevere la risposta.
La factory esegue la connessione e imposta il transport, poi crea il protocollo ProxyClient che esegue la comunicazione vera e propria con il server.
Quando ProxyClient viene collegato al server, viene chiamato il suo callback connectionMade che invia headers e dati attraverso il transport.
Quando il server risponde, ci sono diversi handlers che ricevono i dati.
In particolare, handleResponseEnd() esegue la “pulizia” finale chiamando ProxyRequest.finish() e chiudendo la connessione con il server.
Attenzione: handleResponseEnd viene chiamato da HTTPClient.connectionLost().
class ProxyClient(HTTPClient):
_finished = False
....
def connectionMade(self):
self.sendCommand(self.command, self.rest)
for header, value in self.headers.items():
self.sendHeader(header, value)
self.endHeaders()
self.transport.write(self.data)
def handleHeader(self, key, value):
.... # si può riusare per processare ogni header ricevuto
def handleResponsePart(self, buffer):
self.father.write(buffer) # usa il metodo write di Request
def handleResponseEnd(self):
if not self._finished:
self._finished = True
self.father.finish() # chiama il metodo finish di Request
self.transport.loseConnection() #chiude la connessione
Gestione delle connessioni
Dal lato browser troviamo Proxy e ProxyRequest.
Proxy eredita da Protocol: il metodo connectionLost() è chiamato quando il browser si disconnette dal proxy, e notifica l’ evento a tutte le ProxyRequests ancora in coda
def connectionLost(self, reason):
self.setTimeout(None)
for request in self.requests:
request.connectionLost(reason)
La ProxyRequest.connectionLost fa le pulizie: imposta _disconnected=True, cancella il puntatore al canale di comunicazione con il client, lancia gli errbacks delle notifiche.
def connectionLost(self, reason):
self._disconnected = True
self.channel = None
if self.content is not None:
self.content.close()
for d in self.notifications:
d.errback(reason)
self.notifications = []
Dal lato opposto, quello che comunica con il server di destinazione, troviamo ProxyClient, che eredita da HTTPClient.
Se il server chiude la comunicazione, viene eseguito il metodo HTTPClient.connectionLost, che a sua volta chiama handleResponseEnd, implementato da ProxyClient:
def handleResponseEnd(self):
if not self._finished:
self._finished = True
self.father.finish()
self.transport.loseConnection()
Per controllare questo comportamento, basta imitare ciò che avviene nel codice sorgente:
basta cioè fare un test su self._finished
Due volte handleResponseEnd
ProxyClient riceve i dati e in alcuni casi (non so bene quali) esegue una prima volta handleResponseEnd().
Poi però c’è la disconnessione dal server: HTTPClient.connectionLost() chiama self.handleResponseEnd una seconda volta.