diff --git a/doc-src/scripting/inlinescripts.html b/doc-src/scripting/inlinescripts.html
index 19ae89a1e..7ab1c1013 100644
--- a/doc-src/scripting/inlinescripts.html
+++ b/doc-src/scripting/inlinescripts.html
@@ -36,6 +36,11 @@ Called when a client initiates a connection to the proxy. Note that
a connection can correspond to multiple HTTP requests.
+### serverconnect(ScriptContext, ServerConnection)
+
+Called when the proxy initiates a connection to the target server. Note that
+a connection can correspond to multiple HTTP requests.
+
### request(ScriptContext, Flow)
Called when a client request has been received. The __Flow__ object is
diff --git a/examples/stub.py b/examples/stub.py
index 42b2935a9..78cbfcf2a 100644
--- a/examples/stub.py
+++ b/examples/stub.py
@@ -14,6 +14,13 @@ def clientconnect(ctx, client_connect):
"""
ctx.log("clientconnect")
+def serverconnect(ctx, server_connection):
+ """
+ Called when the proxy initiates a connection to the target server. Note that a
+ connection can correspond to multiple HTTP requests
+ """
+ ctx.log("serverconnect")
+
def request(ctx, flow):
"""
Called when a client request has been received.
diff --git a/libmproxy/proxy.py b/libmproxy/proxy.py
index 3098aff4a..609ffb623 100644
--- a/libmproxy/proxy.py
+++ b/libmproxy/proxy.py
@@ -133,18 +133,25 @@ class ProxyHandler(tcp.BaseHandler):
self.server_conn = None
tcp.BaseHandler.__init__(self, connection, client_address, server)
- def get_server_connection(self, cc, scheme, host, port, sni):
+ def get_server_connection(self, cc, scheme, host, port, sni, request=None):
"""
When SNI is in play, this means we have an SSL-encrypted
connection, which means that the entire handler is dedicated to a
single server connection - no multiplexing. If this assumption ever
breaks, we'll have to do something different with the SNI host
variable on the handler object.
+
+ `conn_info` holds the initial connection's parameters, as the
+ hook might change them. Also, the hook might require an initial
+ request to figure out connection settings; in this case it can
+ set require_request, which will cause the connection to be
+ re-opened after the client's request arrives.
"""
sc = self.server_conn
if not sni:
sni = host
- if sc and (scheme, host, port, sni) != (sc.scheme, sc.host, sc.port, sc.sni):
+ conn_info = (scheme, host, port, sni)
+ if sc and (conn_info != sc.conn_info or (request and sc.require_request)):
sc.terminate()
self.server_conn = None
self.log(
@@ -159,6 +166,13 @@ class ProxyHandler(tcp.BaseHandler):
if not self.server_conn:
try:
self.server_conn = ServerConnection(self.config, scheme, host, port, sni)
+
+ # Additional attributes, used if the server_connect hook
+ # needs to change parameters
+ self.server_conn.request = request
+ self.server_conn.require_request = False
+
+ self.server_conn.conn_info = conn_info
self.channel.ask(self.server_conn)
self.server_conn.connect()
except tcp.NetLibError, v:
@@ -223,7 +237,7 @@ class ProxyHandler(tcp.BaseHandler):
# the case, we want to reconnect without sending an error
# to the client.
while 1:
- sc = self.get_server_connection(cc, scheme, host, port, self.sni)
+ sc = self.get_server_connection(cc, scheme, host, port, self.sni, request=request)
sc.send(request)
if sc.requestcount == 1: # add timestamps only for first request (others are not directly affected)
request.tcp_setup_timestamp = sc.tcp_setup_timestamp
diff --git a/libmproxy/script.py b/libmproxy/script.py
index d1b714dbb..f8a0d085e 100644
--- a/libmproxy/script.py
+++ b/libmproxy/script.py
@@ -78,7 +78,7 @@ def concurrent(fn):
r = getattr(flow, fn.func_name)
_handle_concurrent_reply(fn, r, [ctx, flow])
return _concurrent
- elif fn.func_name in ["clientconnect", "clientdisconnect", "serverconnect"]:
+ elif fn.func_name in ["clientconnect", "serverconnect", "clientdisconnect"]:
def _concurrent(ctx, conn):
_handle_concurrent_reply(fn, conn, [ctx, conn])
return _concurrent
diff --git a/test/scripts/all.py b/test/scripts/all.py
index e6da7e51f..7d30d7571 100644
--- a/test/scripts/all.py
+++ b/test/scripts/all.py
@@ -3,6 +3,10 @@ def clientconnect(ctx, cc):
ctx.log("XCLIENTCONNECT")
log.append("clientconnect")
+def serverconnect(ctx, cc):
+ ctx.log("XSERVERCONNECT")
+ log.append("serverconnect")
+
def request(ctx, r):
ctx.log("XREQUEST")
log.append("request")
diff --git a/test/scripts/concurrent_decorator.py b/test/scripts/concurrent_decorator.py
index c1c2651e8..8e1320067 100644
--- a/test/scripts/concurrent_decorator.py
+++ b/test/scripts/concurrent_decorator.py
@@ -1,6 +1,17 @@
import time
from libmproxy.script import concurrent
+
+@concurrent
+def clientconnect(context, cc):
+ context.log("clientconnect")
+
+
+@concurrent
+def serverconnect(context, sc):
+ context.log("serverconnect")
+
+
@concurrent
def request(context, flow):
time.sleep(0.1)
@@ -16,16 +27,6 @@ def error(context, err):
context.log("error")
-@concurrent
-def clientconnect(context, cc):
- context.log("clientconnect")
-
-
@concurrent
def clientdisconnect(context, dc):
- context.log("clientdisconnect")
-
-
-@concurrent
-def serverconnect(context, sc):
- context.log("serverconnect")
\ No newline at end of file
+ context.log("clientdisconnect")
\ No newline at end of file
diff --git a/test/test_dump.py b/test/test_dump.py
index 3b79c7214..3d375f161 100644
--- a/test/test_dump.py
+++ b/test/test_dump.py
@@ -30,6 +30,9 @@ class TestDumpMaster:
resp = tutils.tresp(req)
resp.content = content
m.handle_clientconnect(cc)
+ sc = proxy.ServerConnection(m.o, req.scheme, req.host, req.port, None)
+ sc.reply = mock.MagicMock()
+ m.handle_serverconnection(sc)
m.handle_request(req)
f = m.handle_response(resp)
cd = flow.ClientDisconnect(cc)
@@ -153,6 +156,7 @@ class TestDumpMaster:
scripts=[[tutils.test_data.path("scripts/all.py")]], verbosity=0, eventlog=True
)
assert "XCLIENTCONNECT" in ret
+ assert "XSERVERCONNECT" in ret
assert "XREQUEST" in ret
assert "XRESPONSE" in ret
assert "XCLIENTDISCONNECT" in ret
diff --git a/test/test_flow.py b/test/test_flow.py
index 9844e0fde..c614960ba 100644
--- a/test/test_flow.py
+++ b/test/test_flow.py
@@ -1,7 +1,7 @@
import Queue, time, os.path
from cStringIO import StringIO
import email.utils
-from libmproxy import filt, flow, controller, utils, tnetstring
+from libmproxy import filt, flow, controller, utils, tnetstring, proxy
import tutils
@@ -575,6 +575,10 @@ class TestFlowMaster:
req = tutils.treq()
fm.handle_clientconnect(req.client_conn)
assert fm.scripts[0].ns["log"][-1] == "clientconnect"
+ sc = proxy.ServerConnection(None, req.scheme, req.host, req.port, None)
+ sc.reply = controller.DummyReply()
+ fm.handle_serverconnection(sc)
+ assert fm.scripts[0].ns["log"][-1] == "serverconnect"
f = fm.handle_request(req)
assert fm.scripts[0].ns["log"][-1] == "request"
resp = tutils.tresp(req)
diff --git a/test/test_script.py b/test/test_script.py
index 766cfb8c3..ad2296efd 100644
--- a/test/test_script.py
+++ b/test/test_script.py
@@ -103,11 +103,11 @@ class TestScript:
f.error = tutils.terr(f.request)
f.reply = f.request.reply
- print s.run("response", f)
- print s.run("error", f)
- print s.run("clientconnect", f)
- print s.run("clientdisconnect", f)
- print s.run("serverconnect", f)
+ s.run("clientconnect", f)
+ s.run("serverconnect", f)
+ s.run("response", f)
+ s.run("error", f)
+ s.run("clientdisconnect", f)
time.sleep(0.1)
assert ctx.count == 5