0.10
diff --git a/.travis.yml b/.travis.yml
index d2c7552312..062bb77b50 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,8 +3,7 @@ python:
   - "2.7"
 # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
 install: 
-  - "pip install --upgrade git+https://github.com/mitmproxy/netlib.git"
-  - "pip install --upgrade git+https://github.com/mitmproxy/pathod.git"
+  - "pip install --upgrade git+https://github.com/mitmproxy/netlib.git@0.10"
   - "pip install -r requirements.txt --use-mirrors"
   - "pip install -r test/requirements.txt --use-mirrors"
 # command to run tests, e.g. python setup.py test
diff --git a/libmproxy/dump.py b/libmproxy/dump.py
index e76ea1ce33..53e07a7a14 100644
--- a/libmproxy/dump.py
+++ b/libmproxy/dump.py
@@ -31,6 +31,9 @@ class Options(object):
         "stickyauth",
         "verbosity",
         "wfile",
+        "replay_ignore_content",
+        "replay_ignore_params",
+        "replay_not_found"
     ]
     def __init__(self, **kwargs):
         for k, v in kwargs.items():
@@ -73,6 +76,9 @@ def __init__(self, server, options, filtstr, outfile=sys.stdout):
             self.filt = filt.parse(filtstr)
         else:
             self.filt = None
+   
+        if options.replay_not_found: 
+            self.not_found_filt = filt.parse(options.replay_not_found)
 
         if options.stickycookie:
             self.set_stickycookie(options.stickycookie)
@@ -101,7 +107,9 @@ def __init__(self, server, options, filtstr, outfile=sys.stdout):
                 self._readflow(options.server_replay),
                 options.kill, options.rheaders,
                 not options.keepserving,
-                options.nopop
+                options.nopop,
+                options.replay_ignore_params,
+                options.replay_ignore_content
             )
 
         if options.client_replay:
diff --git a/libmproxy/flow.py b/libmproxy/flow.py
index 76ca4f4752..8343b69995 100644
--- a/libmproxy/flow.py
+++ b/libmproxy/flow.py
@@ -943,12 +943,12 @@ def tick(self, master, testing=False):
 
 
 class ServerPlaybackState:
-    def __init__(self, headers, flows, exit, nopop):
+    def __init__(self, headers, flows, exit, nopop, ignore_params, ignore_content):
         """
             headers: Case-insensitive list of request headers that should be
             included in request-response matching.
         """
-        self.headers, self.exit, self.nopop = headers, exit, nopop
+        self.headers, self.exit, self.nopop, self.ignore_params, self.ignore_content = headers, exit, nopop, ignore_params, ignore_content 
         self.fmap = {}
         for i in flows:
             if i.response:
@@ -963,14 +963,29 @@ def _hash(self, flow):
             Calculates a loose hash of the flow request.
         """
         r = flow.request
+
+        _, _, path, _, query, _ = urlparse.urlparse(r.get_url())
+        queriesArray = urlparse.parse_qsl(query)
+
+        filtered = []
+        for p in queriesArray:
+          if p[0] not in self.ignore_params:
+            filtered.append(p)
+
         key = [
             str(r.host),
             str(r.port),
             str(r.scheme),
             str(r.method),
-            str(r.path),
-            str(r.content),
+            str(path),
         ]
+        if not self.ignore_content:
+            key.append(str(r.content))
+
+        for p in filtered:
+            key.append(p[0])
+            key.append(p[1])
+
         if self.headers:
             hdrs = []
             for i in self.headers:
@@ -1375,6 +1390,8 @@ def __init__(self, server, state):
         self.stream = None
         self.apps = AppRegistry()
 
+        self.not_found_filt = None        
+
     def start_app(self, host, port, external):
         if not external:
             self.apps.add(
@@ -1452,12 +1469,15 @@ def start_client_playback(self, flows, exit):
     def stop_client_playback(self):
         self.client_playback = None
 
-    def start_server_playback(self, flows, kill, headers, exit, nopop):
+    def start_server_playback(self, flows, kill, headers, exit, nopop, ignore_params, ignore_content):
         """
             flows: List of flows.
             kill: Boolean, should we kill requests not part of the replay?
+            ignore_params: list of parameters to ignore in server replay
+            ignore_content: true if request content should be ignored in server replay
+            not_found: return 404 instead of go for the page for filtered traffic in server replay
         """
-        self.server_playback = ServerPlaybackState(headers, flows, exit, nopop)
+        self.server_playback = ServerPlaybackState(headers, flows, exit, nopop, ignore_params, ignore_content)
         self.kill_nonreplay = kill
 
     def stop_server_playback(self):
@@ -1485,6 +1505,27 @@ def do_server_playback(self, flow):
             return True
         return None
 
+    def return_not_found(self, flow):
+        """
+            This method should be called by child classes in the handle_request
+            handler. Returns a hardcoded 404
+        """
+        if self.server_playback:
+            response = Response(flow.request,
+                        [1,1],
+                        404, "Not found",
+                        ODictCaseless([["Content-Type","text/html"]]),
+                        "Not Found",
+                        None)
+            response._set_replay()
+            flow.response = response
+            if self.refresh_server_playback:
+                response.refresh()
+            flow.request.reply(response)
+            return True
+        return None
+
+
     def tick(self, q):
         if self.client_playback:
             e = [
@@ -1533,12 +1574,13 @@ def process_new_request(self, f):
             f.request.anticache()
         if self.anticomp:
             f.request.anticomp()
-
         if self.server_playback:
             pb = self.do_server_playback(f)
             if not pb:
                 if self.kill_nonreplay:
                     f.kill(self)
+                elif self.not_found_filt and f.match(self.not_found_filt):
+                    self.return_not_found(f)
                 else:
                     f.request.reply()
 
diff --git a/mitmdump b/mitmdump
index 49d129d6a6..9348b825a2 100755
--- a/mitmdump
+++ b/mitmdump
@@ -18,6 +18,24 @@ if __name__ == '__main__':
         action="store_true", dest="keepserving", default=False,
         help="Continue serving after client playback or file read. We exit by default."
     )
+    parser.add_argument(
+        "--replay-ignore-content",
+        action="store_true", dest="replay_ignore_content", default=False,
+        help="Ignore request's content while searching for a saved flow to replay"
+    )
+    parser.add_argument(
+        "--replay-ignore-param",
+        action="append", dest="replay_ignore_params", type=str,
+        help="Request's parameters to be ignored while searching for a saved flow to replay"
+           "Can be passed multiple times."
+    )
+    parser.add_argument(
+        "--replay-not-found",
+        action="append", dest="replay_not_found", type=str,
+        help="Make the proxy return 404 (not found) if a request matching filters is not found in saved flows"
+           "Can be passed multiple times."
+    )
+
     parser.add_argument('args', nargs=argparse.REMAINDER)
 
     options = parser.parse_args()
@@ -40,7 +58,14 @@ if __name__ == '__main__':
         dumpopts = dump.Options(**cmdline.get_common_options(options))
     except cmdline.OptionException, v:
         parser.error(v.message)
+
     dumpopts.keepserving = options.keepserving
+    dumpopts.replay_ignore_content = options.replay_ignore_content
+    dumpopts.replay_ignore_params = options.replay_ignore_params
+    if options.replay_not_found:
+        dumpopts.replay_not_found = " ".join(options.replay_not_found) 
+    else:
+        dumpopts.replay_not_found = None
 
     if options.args:
         filt = " ".join(options.args)
diff --git a/test/test_flow.py b/test/test_flow.py
index f9198f0c63..5ed866d1db 100644
--- a/test/test_flow.py
+++ b/test/test_flow.py
@@ -107,7 +107,7 @@ def test_tick(self):
 
 class TestServerPlaybackState:
     def test_hash(self):
-        s = flow.ServerPlaybackState(None, [], False, False)
+        s = flow.ServerPlaybackState(None, [], False, False, None, False)
         r = tutils.tflow()
         r2 = tutils.tflow()
 
@@ -119,7 +119,7 @@ def test_hash(self):
         assert s._hash(r) != s._hash(r2)
 
     def test_headers(self):
-        s = flow.ServerPlaybackState(["foo"], [], False, False)
+        s = flow.ServerPlaybackState(["foo"], [], False, False, None, False)
         r = tutils.tflow_full()
         r.request.headers["foo"] = ["bar"]
         r2 = tutils.tflow_full()
@@ -133,6 +133,45 @@ def test_headers(self):
         r2 = tutils.tflow_full()
         assert s._hash(r) == s._hash(r2)
 
+    def test_ignore_params(self):
+        s = flow.ServerPlaybackState(None, [], False, False, ["param1", "param2"], False)
+        r = tutils.tflow_full()
+        r.request.path="/test?param1=1"
+        r2 = tutils.tflow_full()
+        r2.request.path="/test"
+        assert s._hash(r) == s._hash(r2)
+        r2.request.path="/test?param1=2"
+        assert s._hash(r) == s._hash(r2)
+        r2.request.path="/test?param2=1"
+        assert s._hash(r) == s._hash(r2)
+        r2.request.path="/test?param3=2"
+        assert not s._hash(r) == s._hash(r2)
+
+    def test_ignore_content(self):
+        s = flow.ServerPlaybackState(None, [], False, False, None, False)
+        r = tutils.tflow_full()
+        r2 = tutils.tflow_full()
+
+        r.request.content = "foo"
+        r2.request.content = "foo"
+        assert s._hash(r) == s._hash(r2)
+        r2.request.content = "bar"
+        assert not s._hash(r) == s._hash(r2)
+
+        #now ignoring content
+        s = flow.ServerPlaybackState(None, [], False, False, None, True)
+        r = tutils.tflow_full()
+        r2 = tutils.tflow_full()
+        r.request.content = "foo"
+        r2.request.content = "foo"
+        assert s._hash(r) == s._hash(r2)
+        r2.request.content = "bar"
+        assert s._hash(r) == s._hash(r2)
+        r2.request.content = ""
+        assert s._hash(r) == s._hash(r2)
+        r2.request.content = None
+        assert s._hash(r) == s._hash(r2)
+
     def test_load(self):
         r = tutils.tflow_full()
         r.request.headers["key"] = ["one"]
@@ -140,7 +179,7 @@ def test_load(self):
         r2 = tutils.tflow_full()
         r2.request.headers["key"] = ["two"]
 
-        s = flow.ServerPlaybackState(None, [r, r2], False, False)
+        s = flow.ServerPlaybackState(None, [r, r2], False, False, None, False)
         assert s.count() == 2
         assert len(s.fmap.keys()) == 1
 
@@ -161,7 +200,7 @@ def test_load_with_nopop(self):
         r2 = tutils.tflow_full()
         r2.request.headers["key"] = ["two"]
 
-        s = flow.ServerPlaybackState(None, [r, r2], False, True)
+        s = flow.ServerPlaybackState(None, [r, r2], False, True, None, False)
 
         assert s.count() == 2
         s.next_flow(r)
@@ -671,7 +710,7 @@ def test_client_playback(self):
         f = tutils.tflow_full()
         pb = [tutils.tflow_full(), f]
         fm = flow.FlowMaster(None, s)
-        assert not fm.start_server_playback(pb, False, [], False, False)
+        assert not fm.start_server_playback(pb, False, [], False, False, None, False)
         assert not fm.start_client_playback(pb, False)
 
         q = Queue.Queue()
@@ -695,16 +734,16 @@ def test_server_playback(self):
         fm.refresh_server_playback = True
         assert not fm.do_server_playback(tutils.tflow())
 
-        fm.start_server_playback(pb, False, [], False, False)
+        fm.start_server_playback(pb, False, [], False, False, None, False)
         assert fm.do_server_playback(tutils.tflow())
 
-        fm.start_server_playback(pb, False, [], True, False)
+        fm.start_server_playback(pb, False, [], True, False, None, False)
         r = tutils.tflow()
         r.request.content = "gibble"
         assert not fm.do_server_playback(r)
         assert fm.do_server_playback(tutils.tflow())
 
-        fm.start_server_playback(pb, False, [], True, False)
+        fm.start_server_playback(pb, False, [], True, False, None, False)
         q = Queue.Queue()
         fm.tick(q)
         assert controller.should_exit
@@ -719,13 +758,28 @@ def test_server_playback_kill(self):
         pb = [f]
         fm = flow.FlowMaster(None, s)
         fm.refresh_server_playback = True
-        fm.start_server_playback(pb, True, [], False, False)
+        fm.start_server_playback(pb, True, [], False, False, None, False)
 
         f = tutils.tflow()
         f.request.host = "nonexistent"
         fm.process_new_request(f)
         assert "killed" in f.error.msg
 
+    def test_server_playback_return_not_found(self):
+        s = flow.State()
+        f = tutils.tflow()
+        f.response = tutils.tresp(f.request)
+        pb = [f]
+        fm = flow.FlowMaster(None, s)
+        fm.refresh_server_playback = True
+        fm.start_server_playback(pb, False, [], False, False, None, False)
+        fm.not_found_filt = filt.parse("~d www.example.org")
+
+        f = tutils.tflow()
+        f.request.host = "www.example.org"
+        fm.process_new_request(f)
+        assert 404 == f.response.code
+
     def test_stickycookie(self):
         s = flow.State()
         fm = flow.FlowMaster(None, s)

Hi, I've added 3 replay features to version 0.10:
--replay_ignore_content: tells mitmdump to ignore request content in flow matching
--replay-ignore-param: (can be passed multiple times) creates a list of query parameters that will be ignored in flow matching (useful for dealing with sites that add random numbers or request counts as part of the request)
--replay-not-found: (can be passed multiple times) takes a filter expression as argument and makes proxy return not found (http code:404) for flows not found in saved ones but matching the selected filters instead of making the request to the real server.
