Skip to content
Permalink
Browse files

Fix Host parsing when circuits.web.Server is bound to a UNIX Socket

  • Loading branch information
prologic committed Jun 20, 2016
1 parent 1ac80b5 commit a1a84e163640622b0caf2043570fe33a1d5ea7bc
Showing with 289 additions and 49 deletions.
  1. +194 −0 :wq
  2. +28 −11 circuits/web/http.py
  3. +2 −1 circuits/web/url.py
  4. +11 −0 circuits/web/utils.py
  5. +25 −14 circuits/web/wrappers.py
  6. +1 −0 requirements-dev.txt
  7. +12 −20 tests/web/test_disps.py
  8. +16 −3 tests/web/test_servers.py
194 :wq
@@ -0,0 +1,194 @@
#!/usr/bin/env python


import pytest
from pytest import fixture

import os
import ssl
import sys
import tempfile
from os import path
from socket import gaierror

from circuits.web import Controller
from circuits import handler, Component
from circuits.web import BaseServer, Server


from .helpers import urlopen, URLError


if pytest.PYVER < (2, 7):
pytest.skip("This test requires Python=>2.7")


CERTFILE = path.join(path.dirname(__file__), "cert.pem")


# self signed cert
if pytest.PYVER >= (2, 7, 9):
SSL_CONTEXT = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
SSL_CONTEXT.verify_mode = ssl.CERT_NONE


@fixture
def tmpfile(request):
tmpdir = tempfile.mkdtemp()
filename = os.path.join(tmpdir, "test.sock")

return filename


class BaseRoot(Component):

channel = "web"

def request(self, request, response):
return "Hello World!"


class Root(Controller):

def index(self):
return "Hello World!"


class MakeQuiet(Component):

channel = "web"

def ready(self, event, *args):
event.stop()


def test_baseserver(manager, watcher):
server = BaseServer(0).register(manager)
MakeQuiet().register(server)
watcher.wait("ready")

BaseRoot().register(server)
watcher.wait("registered")

try:
f = urlopen(server.http.base)
except URLError as e:
if isinstance(e.reason, gaierror):
f = urlopen("http://127.0.0.1:9000")
else:
raise

s = f.read()
assert s == b"Hello World!"

server.unregister()
watcher.wait("unregistered")


def test_server(manager, watcher):
server = Server(0).register(manager)
MakeQuiet().register(server)
watcher.wait("ready")

Root().register(server)

try:
f = urlopen(server.http.base)
except URLError as e:
if isinstance(e.reason, gaierror):
f = urlopen("http://127.0.0.1:9000")
else:
raise

s = f.read()
assert s == b"Hello World!"

server.unregister()
watcher.wait("unregistered")


@pytest.mark.skipif((2,) < sys.version_info < (3, 4, 3),
reason="Context not implemented under python 3.4.3")
@pytest.mark.skipif(sys.version_info < (2, 7, 9),
reason="Context not implemented under python 2.7.9")
def test_secure_server(manager, watcher):
pytest.importorskip("ssl")

server = Server(0, secure=True, certfile=CERTFILE).register(manager)
MakeQuiet().register(server)
watcher.wait("ready")

Root().register(server)

try:
f = urlopen(server.http.base, context=SSL_CONTEXT)
except URLError as e:
if isinstance(e.reason, gaierror):
f = urlopen("http://127.0.0.1:9000")
else:
raise

s = f.read()
assert s == b"Hello World!"

server.unregister()
watcher.wait("unregistered")


def test_unixserver(manager, watcher, tmpfile):
if pytest.PLATFORM == "win32":
pytest.skip("Unsupported Platform")

server = Server(tmpfile).register(manager)
MakeQuiet().register(server)
assert watcher.wait("ready")

Root().register(server)

assert path.basename(server.host) == "test.sock"

try:
from uhttplib import import UnixHTTPConnection

client = uhttplib.UnixHTTPConnection(server.http.base)
client.request("GET", "/")
response = client.getresponse()
s = response.read()

assert s == b"Hello World!"
except ImportError:
pass

server.unregister()
watcher.wait("unregistered")


@pytest.mark.skipif((2, 7, 9) < sys.version_info < (3, 4, 3),
reason="Context not implemented under python 3.4.3")
@pytest.mark.skipif(sys.version_info < (2, 7, 9),
reason="Context not implemented under python 2.7.9")
def test_multi_servers(manager, watcher):
pytest.importorskip("ssl")

insecure_server = Server(0, channel="insecure")
secure_server = Server(
0,
channel="secure", secure=True, certfile=CERTFILE
)

server = (insecure_server + secure_server).register(manager)
MakeQuiet().register(server)
watcher.wait("ready")

Root().register(server)

f = urlopen(insecure_server.http.base)
s = f.read()
assert s == b"Hello World!"

f = urlopen(secure_server.http.base, context=SSL_CONTEXT)
s = f.read()
assert s == b"Hello World!"

server.unregister()
watcher.wait("unregistered")
@@ -6,6 +6,7 @@


from io import BytesIO
from socket import socket

try:
from urllib.parse import quote
@@ -22,6 +23,7 @@

from . import wrappers
from .url import parse_url
from .utils import is_unix_socket
from .exceptions import HTTPException
from .events import request, response, stream
from .parsers import HttpParser, BAD_FIRST_LINE
@@ -39,7 +41,6 @@


class HTTP(BaseComponent):

"""HTTP Protocol Component
Implements the HTTP server protocol and parses and processes incoming
@@ -65,15 +66,7 @@ def __init__(self, server, encoding=HTTP_ENCODING, channel=channel):
self._server = server
self._encoding = encoding

url = "{0:s}://{1:s}{2:s}".format(
(server.secure and "https") or "http",
server.host or "0.0.0.0",
":{0:d}".format(server.port or 80)
if server.port not in (80, 443)
else ""
)
self.uri = parse_url(url)

self._uri = None
self._clients = {}
self._buffers = {}

@@ -93,10 +86,31 @@ def scheme(self):

@property
def base(self):
if not hasattr(self, "uri"):
if getattr(self, "uri", None) is None:
return
return self.uri.utf8().rstrip(b"/").decode(self._encoding)

@property
def uri(self):
if getattr(self, "_uri", None) is None:
return
return self._uri

@handler("ready", priority=1.0)
def _on_ready(self, server, bind):
if is_unix_socket(server.host):
url = server.host
else:
url = "{0:s}://{1:s}{2:s}".format(
(server.secure and "https") or "http",
server.host or "0.0.0.0",
":{0:d}".format(server.port or 80)
if server.port not in (80, 443)
else ""
)

self._uri = parse_url(url)

@handler("stream") # noqa
def _on_stream(self, res, data):
sock = res.request.sock
@@ -440,6 +454,9 @@ def _on_exception(self, *args, **kwargs):
req, res = fevent.value.parent.event.args[:2]
elif len(fevent.args[2:]) == 4:
req, res = fevent.args[2:]
elif len(fevent.args) == 2 and isinstance(fevent.args[0], socket):
req = wrappers.Request(fevent.args[0], server=self._server)
res = wrappers.Response(req, self._encoding, 500)
else:
return

@@ -213,7 +213,8 @@ def abspath(self):

def lower(self):
'''Lowercase the hostname'''
self._host = self._host.lower()
if self._host is not None:
self._host = self._host.lower()
return self

def sanitize(self):
@@ -4,8 +4,10 @@
"""

import re
import os
import zlib
import time
import stat
import struct
from math import sqrt
from io import TextIOWrapper
@@ -29,6 +31,15 @@
image_map_pattern = re.compile("^[0-9]+,[0-9]+$")


def is_unix_socket(path):
if not os.path.exists(path):
return False

mode = os.stat(path).st_mode

return stat.S_ISSOCK(mode)


def average(xs):
return sum(xs) * 1.0 / len(xs)

@@ -13,24 +13,29 @@
except ImportError:
from http.cookies import SimpleCookie # NOQA

try:
from email.utils import formatdate
formatdate = partial(formatdate, usegmt=True)
except ImportError:
from rfc822 import formatdate as HTTPDate # NOQA


from circuits.six import binary_type
from circuits.net.sockets import BUFSIZE


from .url import parse_url
from .headers import Headers
from ..six import binary_type
from .errors import httperror
from circuits.net.sockets import BUFSIZE
from .utils import is_unix_socket
from .constants import HTTP_STATUS_CODES, SERVER_VERSION


try:
unicode
except NameError:
unicode = str

try:
from email.utils import formatdate
formatdate = partial(formatdate, usegmt=True)
except ImportError:
from rfc822 import formatdate as HTTPDate # NOQA


def file_generator(input, chunkSize=BUFSIZE):
chunk = input.read(chunkSize)
@@ -176,7 +181,7 @@ def __init__(self, sock, method="GET", scheme="http", path="/",

if sock is not None:
name = sock.getpeername()
if name is not None:
if name:
self.remote = Host(*name)
else:
name = sock.getsockname()
@@ -206,11 +211,17 @@ def __init__(self, sock, method="GET", scheme="http", path="/",
self.host = host
self.port = port

base = "{0:s}://{1:s}{2:s}/".format(
self.scheme,
self.host,
":{0:d}".format(self.port) if self.port not in (80, 443) else ""
)
if is_unix_socket(self.host):
base = self.host
else:
base = "{0:s}://{1:s}{2:s}/".format(
self.scheme,
self.host,
":{0:d}".format(self.port)
if self.port not in (80, 443)
else ""
)

self.base = parse_url(base)

url = "{0:s}{1:s}{2:s}".format(
@@ -10,6 +10,7 @@ wheel
# Running Tests
tox
pytest
uhttplib
coveralls
pytest-cov

0 comments on commit a1a84e1

Please sign in to comment.
You can’t perform that action at this time.