Permalink
Browse files

Merge pull request #18 from jterrace/fix-http

Substitute HTTP Host header when proxying and fix HTTP socket reading.
  • Loading branch information...
2 parents c6e286e + 0540125 commit b097f63c546efb27d772f821c1608f31f5dcdcde @tarekziade tarekziade committed Mar 7, 2013
Showing with 37 additions and 53 deletions.
  1. +1 −1 setup.py
  2. +28 −45 vaurien/protocols/http.py
  3. +3 −1 vaurien/proxy.py
  4. +3 −5 vaurien/tests/test_proxy.py
  5. +2 −1 vaurien/util.py
View
@@ -3,7 +3,7 @@
install_requires = ['cornice', 'gevent', 'statsd-client', 'vaurienclient',
- 'greenlet']
+ 'greenlet', 'http-parser']
try:
import argparse # NOQA
View
@@ -1,14 +1,15 @@
import re
+try:
+ from http_parser.parser import HttpParser
+except ImportError:
+ from http_parser.pyparser import HttpParser
+
from vaurien.protocols.base import BaseProtocol
from vaurien.util import chunked
-RE_LEN = re.compile('Content-Length: (\d+)', re.M | re.I)
-RE_KEEPALIVE = re.compile('Connection: Keep-Alive')
-RE_MEMCACHE_COMMAND = re.compile('(.*)\r\n')
-
-EOH = '\r\n\r\n'
+HOST_REPLACE = re.compile(r'\r\nHost: .+\r\n')
CRLF = '\r\n'
@@ -20,49 +21,31 @@ class Http(BaseProtocol):
def _handle(self, source, dest, to_backend):
buffer_size = self.option('buffer')
- # Getting the HTTP query
- data = self._get_data(source)
-
- if not data:
- self._abort_handling(to_backend, dest)
- return False
-
- # sending it to the backend
- dest.sendall(data)
-
- # Receiving the response
- buffer = self._get_data(dest, buffer_size)
-
- source.sendall(buffer)
-
- # Reading the HTTP Headers
- while EOH not in buffer:
+ # Getting the HTTP query and sending it to the backend.
+ parser = HttpParser()
+ while not parser.is_message_complete():
+ data = self._get_data(source, buffer_size)
+ if not data:
+ self._abort_handling(to_backend, dest)
+ return False
+ nparsed = parser.execute(data, len(data))
+ assert nparsed == len(data)
+ data = HOST_REPLACE.sub('\r\nHost: %s\r\n'
+ % self.proxy.backend, data)
+ dest.sendall(data)
+
+ # Getting the HTTP response and sending it back to the source.
+ parser = HttpParser()
+ while not parser.is_message_complete():
data = self._get_data(dest, buffer_size)
- buffer += data
+ if not data:
+ self._abort_handling(to_backend, dest)
+ return False
+ nparsed = parser.execute(data, len(data))
+ assert nparsed == len(data)
source.sendall(data)
- # keep alive header ?
- keep_alive = RE_KEEPALIVE.search(buffer) is not None
-
- # content-length header - to see if we need to suck more
- # data.
- match = RE_LEN.search(buffer)
- if match:
- resp_len = int(match.group(1))
- left_to_read = resp_len - len(buffer)
- if left_to_read > 0:
- for chunk in chunked(left_to_read, buffer_size):
- data = self._get_data(dest, chunk)
- buffer += data
- source.sendall(data)
- else:
- # embarrassing...
- # just sucking until recv() returns ''
- while True:
- data = self._get_data(dest, buffer_size)
- if data == '':
- break
- source.sendall(data)
+ keep_alive = parser.should_keep_alive()
# do we close the client ?
if not keep_alive and not self.option('keep_alive'):
View
@@ -25,6 +25,7 @@ def __init__(self, proxy, backend, protocol='tcp', behaviors=None,
logger.info('Starting the Chaos TCP Server')
parsed_proxy = parse_address(proxy)
+ self.backend = backend
dest = parse_address(backend)
backlog = cfg.get('backlog', 8192)
StreamServer.__init__(self, parsed_proxy, backlog=backlog, **kwargs)
@@ -58,6 +59,7 @@ def __init__(self, proxy, backend, protocol='tcp', behaviors=None,
args = settings['args']
self.handler.update_settings(extract_settings(args, 'protocol',
self.protocol))
+ self.handler.proxy = self
logger.info('Options:')
logger.info('* proxies from %s to %s' % (proxy, backend))
@@ -68,7 +70,7 @@ def __init__(self, proxy, backend, protocol='tcp', behaviors=None,
logger.info('* async_mode: %d' % self.async_mode)
def _create_connection(self):
- conn = create_connection(self.dest)
+ conn = create_connection(self.dest, timeout=self.timeout)
if self.async_mode:
conn.setblocking(0)
return conn
@@ -9,7 +9,6 @@
_PROXY = 'http://localhost:8000'
-_REQCONFIG = {'verbose': StringIO()}
class TestSimpleProxy(unittest.TestCase):
@@ -41,17 +40,16 @@ def test_proxy(self):
# let's do a few simple request first to make sure the proxy works
self.assertEqual(self.client.get_behavior(), 'dummy')
for i in range(10):
- res = requests.get(_PROXY, config=_REQCONFIG)
+ res = requests.get(_PROXY)
self.assertEqual(res.status_code, 200)
# now let's add a bit of havoc
with self.client.with_behavior('blackout'):
# oh look we broke it
- self.assertRaises(requests.ConnectionError, requests.get, _PROXY,
- config=_REQCONFIG)
+ self.assertRaises(requests.ConnectionError, requests.get, _PROXY)
self.assertEqual(self.client.get_behavior(), 'blackout')
# we should be back to normal
self.assertEqual(self.client.get_behavior(), 'dummy')
- res = requests.get(_PROXY, config=_REQCONFIG)
+ res = requests.get(_PROXY)
self.assertEqual(res.status_code, 200)
View
@@ -5,6 +5,7 @@
from gevent.socket import gethostbyname
from gevent.socket import error
+from gevent.socket import wait_read
from gevent import sleep
@@ -189,7 +190,7 @@ def get_data(sock, buffer=1024):
except error, e:
if e.args[0] not in (EWOULDBLOCK, EAGAIN):
raise
- sleep(0)
+ wait_read(sock.fileno(), timeout=sock.gettimeout())
def extract_settings(args, prefix, name):

0 comments on commit b097f63

Please sign in to comment.