diff options
| author | Thomas Kriechbaumer <thomas@kriechbaumer.name> | 2016-05-13 08:38:00 -0500 | 
|---|---|---|
| committer | Thomas Kriechbaumer <thomas@kriechbaumer.name> | 2016-05-17 21:26:06 -0700 | 
| commit | 43ab9f7bd07e28c5d0d99b82f25c470db161eecd (patch) | |
| tree | dc17956dd9baa2be698a0099c5fda4a64b6b15a0 | |
| parent | e61014d20301b1b2ca403cd5a1d9e0ee5b2d8de9 (diff) | |
| download | mitmproxy-43ab9f7bd07e28c5d0d99b82f25c470db161eecd.tar.gz mitmproxy-43ab9f7bd07e28c5d0d99b82f25c470db161eecd.tar.bz2 mitmproxy-43ab9f7bd07e28c5d0d99b82f25c470db161eecd.zip | |
http2: properly handle connection errors
| -rw-r--r-- | mitmproxy/exceptions.py | 6 | ||||
| -rw-r--r-- | mitmproxy/protocol/http.py | 13 | ||||
| -rw-r--r-- | mitmproxy/protocol/http2.py | 31 | 
3 files changed, 37 insertions, 13 deletions
| diff --git a/mitmproxy/exceptions.py b/mitmproxy/exceptions.py index 52fd36d1..8f989063 100644 --- a/mitmproxy/exceptions.py +++ b/mitmproxy/exceptions.py @@ -50,6 +50,10 @@ class HttpProtocolException(ProtocolException):      pass +class Http2ProtocolException(ProtocolException): +    pass + +  class ServerException(ProxyException):      pass @@ -83,4 +87,4 @@ class ScriptException(ProxyException):  class FlowReadException(ProxyException): -    pass
\ No newline at end of file +    pass diff --git a/mitmproxy/protocol/http.py b/mitmproxy/protocol/http.py index fc181b5d..9cb35176 100644 --- a/mitmproxy/protocol/http.py +++ b/mitmproxy/protocol/http.py @@ -11,7 +11,7 @@ from netlib.http import Headers  from h2.exceptions import H2Error  from .. import utils -from ..exceptions import HttpProtocolException, ProtocolException +from ..exceptions import HttpProtocolException, Http2ProtocolException, ProtocolException  from ..models import (      HTTPFlow,      HTTPResponse, @@ -243,7 +243,7 @@ class HttpLayer(Layer):          try:              response = make_error_response(code, message)              self.send_response(response) -        except (NetlibException, H2Error): +        except (NetlibException, H2Error, Http2ProtocolException):              self.log(traceback.format_exc(), "debug")      def change_upstream_proxy_server(self, address): @@ -288,9 +288,9 @@ class HttpLayer(Layer):          try:              get_response() -        except NetlibException as v: +        except NetlibException as e:              self.log( -                "server communication error: %s" % repr(v), +                "server communication error: %s" % repr(e),                  level="debug"              )              # In any case, we try to reconnect at least once. This is @@ -304,6 +304,11 @@ class HttpLayer(Layer):              # > server detects timeout, disconnects              # > read (100-n)% of large request              # > send large request upstream + +            if isinstance(e, Http2ProtocolException): +                # do not try to reconnect for HTTP2 +                raise ProtocolException("First and only attempt to get response via HTTP2 failed.") +              self.disconnect()              self.connect()              get_response() diff --git a/mitmproxy/protocol/http2.py b/mitmproxy/protocol/http2.py index 140a94d4..f2f31b17 100644 --- a/mitmproxy/protocol/http2.py +++ b/mitmproxy/protocol/http2.py @@ -4,6 +4,7 @@ import threading  import time  from six.moves import queue +import traceback  import h2  import six  from h2.connection import H2Connection @@ -15,6 +16,7 @@ from netlib.utils import http2_read_raw_frame  from .base import Layer  from .http import _HttpTransmissionLayer, HttpLayer +from ..exceptions import ProtocolException, Http2ProtocolException  from .. import utils  from ..models import HTTPRequest, HTTPResponse @@ -55,7 +57,7 @@ class SafeH2Connection(H2Connection):      def safe_send_headers(self, is_zombie, stream_id, headers):          with self.lock:              if is_zombie():  # pragma: no cover -                return +                raise Http2ProtocolException("Zombie Stream")              self.send_headers(stream_id, headers.fields)              self.conn.send(self.data_to_send()) @@ -66,7 +68,7 @@ class SafeH2Connection(H2Connection):                  self.lock.acquire()                  if is_zombie():  # pragma: no cover                      self.lock.release() -                    return +                    raise Http2ProtocolException("Zombie Stream")                  max_outbound_frame_size = self.max_outbound_frame_size                  frame_chunk = chunk[position:position + max_outbound_frame_size]                  if self.local_flow_control_window(stream_id) < len(frame_chunk): @@ -79,7 +81,7 @@ class SafeH2Connection(H2Connection):                  position += max_outbound_frame_size          with self.lock:              if is_zombie():  # pragma: no cover -                return +                raise Http2ProtocolException("Zombie Stream")              self.end_stream(stream_id)              self.conn.send(self.data_to_send()) @@ -104,7 +106,7 @@ class Http2Layer(Layer):          self.active_conns.append(self.server_conn.connection)      def connect(self):  # pragma: no cover -        raise ValueError("CONNECT inside an HTTP2 stream is not supported.") +        raise Http2ProtocolException("HTTP2 layer should already have a connection.")      def set_server(self):  # pragma: no cover          raise NotImplementedError("Cannot change server for HTTP2 connections.") @@ -215,6 +217,7 @@ class Http2Layer(Layer):                          raw_frame = b''.join(http2_read_raw_frame(source_conn.rfile))                      except:                          # read frame failed: connection closed +                        # kill all streams                          for stream in self.streams.values():                              stream.zombie = time.time()                          return @@ -232,7 +235,7 @@ class Http2Layer(Layer):  class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread):      def __init__(self, ctx, stream_id, request_headers): -        super(Http2SingleStreamLayer, self).__init__(ctx) +        super(Http2SingleStreamLayer, self).__init__(ctx, name="Thread-Http2SingleStreamLayer-{}".format(stream_id))          self.zombie = None          self.client_stream_id = stream_id          self.server_stream_id = None @@ -335,7 +338,7 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread):          with self.server_conn.h2.lock:              # We must not assign a stream id if we are already a zombie.              if self.zombie:  # pragma: no cover -                return +                raise Http2ProtocolException("Zombie Stream")              self.server_stream_id = self.server_conn.h2.get_next_available_stream_id()              self.server_to_client_stream_ids[self.server_stream_id] = self.client_stream_id @@ -350,6 +353,8 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread):              self.server_stream_id,              message.body          ) +        if self.zombie:  # pragma: no cover +            raise Http2ProtocolException("Zombie Stream")      def read_response_headers(self):          self.response_arrived.wait() @@ -377,7 +382,7 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread):                      yield self.response_data_queue.get()                  return              if self.zombie:  # pragma: no cover -                return +                raise Http2ProtocolException("Zombie Stream")      def send_response_headers(self, response):          self.client_conn.h2.safe_send_headers( @@ -385,6 +390,8 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread):              self.client_stream_id,              response.headers          ) +        if self.zombie:  # pragma: no cover +            raise Http2ProtocolException("Zombie Stream")      def send_response_body(self, _response, chunks):          self.client_conn.h2.safe_send_body( @@ -392,6 +399,8 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread):              self.client_stream_id,              chunks          ) +        if self.zombie:  # pragma: no cover +            raise Http2ProtocolException("Zombie Stream")      def check_close_connection(self, flow):          # This layer only handles a single stream. @@ -404,5 +413,11 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread):      def run(self):          layer = HttpLayer(self, self.mode) -        layer() + +        try: +            layer() +        except ProtocolException as e: +            self.log(repr(e), "info") +            self.log(traceback.format_exc(), "debug") +          self.zombie = time.time() | 
