Skip to content

Commit

Permalink
Fix Host parsing when circuits.web.Server is bound to a UNIX Socket (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
prologic committed Jun 21, 2016
1 parent a0db5ba commit 50b562f
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 49 deletions.
39 changes: 28 additions & 11 deletions circuits/web/http.py
Expand Up @@ -6,6 +6,7 @@


from io import BytesIO
from socket import socket

try:
from urllib.parse import quote
Expand All @@ -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
Expand All @@ -39,7 +41,6 @@


class HTTP(BaseComponent):

"""HTTP Protocol Component
Implements the HTTP server protocol and parses and processes incoming
Expand All @@ -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

This comment has been minimized.

Copy link
@spaceone

spaceone Jun 21, 2016

Contributor

This is a regression. One cannot access server.base anymore before starting the component tree.

self._clients = {}
self._buffers = {}

Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion circuits/web/url.py
Expand Up @@ -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()

This comment has been minimized.

Copy link
@spaceone

spaceone Jun 21, 2016

Contributor

I would not change a external lib just to make handling easier. Why not just adjust the URI to add a empty host if it's a unix socket:
http:////some/path/to/unix/socket

return self

def sanitize(self):
Expand Down
11 changes: 11 additions & 0 deletions circuits/web/utils.py
Expand Up @@ -4,8 +4,10 @@
"""

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


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

This comment has been minimized.

Copy link
@spaceone

spaceone Jun 21, 2016

Contributor

This fails if I add socket-file called '127.0.0.1' into the current dir and connect to a IPV4 socket at 127.0.0.1…

return False

mode = os.stat(path).st_mode

return stat.S_ISSOCK(mode)


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

Expand Down
36 changes: 22 additions & 14 deletions circuits/web/wrappers.py
Expand Up @@ -13,24 +13,28 @@
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 .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)
Expand Down Expand Up @@ -176,11 +180,12 @@ def __init__(self, sock, method="GET", scheme="http", path="/",

if sock is not None:
name = sock.getpeername()
if name is not None:
self.remote = Host(*name)
else:
name = sock.getsockname()
self.remote = Host(name, "", name)
try:
ip, port = name
name = None
except ValueError: # AF_UNIX
ip, port = None, None
self.remote = Host(ip, port, name)

cookie = self.headers.get("Cookie")
if cookie is not None:
Expand Down Expand Up @@ -209,8 +214,11 @@ def __init__(self, sock, method="GET", scheme="http", path="/",
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 ""
":{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(
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.txt
Expand Up @@ -10,6 +10,7 @@ wheel
# Running Tests
tox
pytest
uhttplib
coveralls
pytest-cov

Expand Down
32 changes: 12 additions & 20 deletions tests/web/test_disps.py
@@ -1,22 +1,18 @@
#!/usr/bin/env python

from circuits.core.manager import Manager
from circuits.core.handlers import handler
from circuits.core.components import BaseComponent

from circuits.core.handlers import handler
from circuits.web import BaseServer, Controller
from circuits.core.components import BaseComponent
from circuits.web.dispatchers.dispatcher import Dispatcher


from .helpers import urlopen, urljoin


class PrefixingDispatcher(BaseComponent):

"""Forward to another Dispatcher based on the channel."""

def __init__(self, channel):
super(PrefixingDispatcher, self).__init__(channel=channel)

@handler("request", priority=1.0)
def _on_request(self, event, request, response):
path = request.path.strip("/")
Expand Down Expand Up @@ -49,24 +45,20 @@ def index(self):
return "Hello from site 2!"


def test_disps():

manager = Manager()

server1 = BaseServer(0, channel="site1")
server1.register(manager)
PrefixingDispatcher(channel="site1").register(server1)
Dispatcher(channel="site1").register(server1)
def test_disps(manager, watcher):
server1 = BaseServer(0, channel="site1").register(manager)
PrefixingDispatcher(channel="site1").register(manager)
Dispatcher(channel="site1").register(manager)
Root1().register(manager)
assert watcher.wait("ready", channel=server1.channel)

server2 = BaseServer(("localhost", 0), channel="site2")
server2.register(manager)
PrefixingDispatcher(channel="site2").register(server2)
Dispatcher(channel="site2").register(server2)
server2 = BaseServer(("localhost", 0), channel="site2").register(manager)
PrefixingDispatcher(channel="site2").register(manager)
Dispatcher(channel="site2").register(manager)
Root2().register(manager)
assert watcher.wait("ready", channel=server2.channel)

DummyRoot().register(manager)
manager.start()

f = urlopen(server1.http.base, timeout=3)
s = f.read()
Expand Down
19 changes: 16 additions & 3 deletions tests/web/test_servers.py
Expand Up @@ -56,8 +56,9 @@ def index(self):

class MakeQuiet(Component):

@handler("ready", channel="*", priority=1.0)
def _on_ready(self, event, *args):
channel = "web"

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


Expand Down Expand Up @@ -140,12 +141,24 @@ def test_unixserver(manager, watcher, tmpfile):

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

Root().register(server)

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

try:
from uhttplib import UnixHTTPConnection

client = 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")

Expand Down

0 comments on commit 50b562f

Please sign in to comment.