+CRLF = b'\r\n'
+
+Timeout = {'timeout': True}
+Hangup = {'hangup': True}
+HeartbeatTimeout = {'heartbeat_timeout': True, 'hangup': True}
+
+class ChunkDecodeError(Exception):
+ pass
+
+class EndOfStream(Exception):
+ pass
+
+range = range if PY_3_OR_HIGHER else xrange
+
+
+class HttpDeChunker(object):
+
+ def __init__(self):
+ self.buf = bytearray()
+
+ def extend(self, data):
+ self.buf.extend(data)
+
+ def read_chunks(self): # -> [bytearray]
+ chunks = []
+ buf = self.buf
+ while True:
+ header_end_pos = buf.find(CRLF)
+ if header_end_pos == -1:
+ break
+
+ header = buf[:header_end_pos]
+ data_start_pos = header_end_pos + 2
+ try:
+ chunk_len = int(header.decode('ascii'), 16)
+ except ValueError:
+ raise ChunkDecodeError()
+
+ if chunk_len == 0:
+ raise EndOfStream()
+
+ data_end_pos = data_start_pos + chunk_len
+
+ if len(buf) > data_end_pos + 2:
+ chunks.append(buf[data_start_pos:data_end_pos])
+ buf = buf[data_end_pos + 2:]
+ else:
+ break
+ self.buf = buf
+ return chunks
+
+
+class JsonDeChunker(object):
+
+ def __init__(self):
+ self.buf = u""
+ self.raw_decode = json.JSONDecoder().raw_decode
+
+ def extend(self, data):
+ self.buf += data
+
+ def read_json_chunks(self):
+ chunks = []
+ buf = self.buf
+ while True:
+ try:
+ buf = buf.lstrip()
+ res, ptr = self.raw_decode(buf)
+ buf = buf[ptr:]
+ chunks.append(res)
+ except ValueError:
+ break
+ self.buf = buf
+ return chunks
+
+
+class Timer(object):
+ def __init__(self, timeout):
+ # If timeout is None, we never expire.
+ self.timeout = timeout
+ self.reset()
+
+ def reset(self):
+ self.time = time.time()
+
+ def expired(self):
+ """
+ If expired, reset the timer and return True.
+ """
+ if self.timeout is None:
+ return False
+ elif time.time() - self.time > self.timeout:
+ self.reset()
+ return True
+ return False
+
+