Commit 75ec1e98 authored by Xavier Thompson's avatar Xavier Thompson

simplehttpserver: Add options for unix socket

Add 'socketpath' and 'abstract' options for path based unix sockets
and abstract unix sockets respectively.
parent ad174685
......@@ -41,10 +41,19 @@ def issubpathof(subpath, path):
class Recipe(GenericBaseRecipe):
def __init__(self, buildout, name, options):
host, port, socketpath, abstract = (
options.get(k) for k in ('host', 'port', 'socketpath', 'abstract'))
oneof = host, socketpath, abstract
if sum(bool(v) for v in oneof) != 1 or bool(host) != bool(port):
raise UserError("Specify one of (host, port) | socketpath | abstract")
address = (host, int(port)) if host else socketpath or '\0' + abstract
options['address'] = address
return GenericBaseRecipe.__init__(self, buildout, name, options)
def install(self):
parameters = {
'host': self.options['host'],
'port': int(self.options['port']),
'address': self.options['address'],
'cwd': self.options['base-path'],
'log-file': self.options['log-file'],
'cert-file': self.options.get('cert-file', ''),
......
# -*- coding: utf-8 -*-
from six.moves.SimpleHTTPServer import SimpleHTTPRequestHandler
from six.moves.BaseHTTPServer import HTTPServer
from six.moves.socketserver import TCPServer
import ssl
import os
import logging
from netaddr import valid_ipv4, valid_ipv6
import socket
import cgi, errno
......@@ -89,9 +88,6 @@ class ServerHandler(SimpleHTTPRequestHandler):
self.respond(200, type=self.headers['Content-Type'])
self.wfile.write(b"Content written to %s" % str2bytes(filename))
class HTTPServerV6(HTTPServer):
address_family = socket.AF_INET6
def run(args):
......@@ -100,8 +96,7 @@ def run(args):
logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
filename=args['log-file'] ,level=logging.INFO)
port = args['port']
host = args['host']
address = args['address']
cwd = args['cwd']
os.chdir(cwd)
......@@ -110,12 +105,17 @@ def run(args):
Handler.base_path = cwd
Handler.restrict_write = not args['allow-write']
if valid_ipv6(host):
server = HTTPServerV6
else:
server = HTTPServer
try:
host, port = address
family, _, _, _, _ = socket.getaddrinfo(host, port)[0]
except ValueError:
family = socket.AF_UNIX
class Server(TCPServer):
allow_reuse_address = 1 # for tests, HTTPServer in stdlib sets it too
address_family = family
httpd = server((host, port), Handler)
httpd = Server(address, Handler)
scheme = 'http'
if 'cert-file' in args and 'key-file' in args and \
os.path.exists(args['cert-file']) and os.path.exists(args['key-file']):
......@@ -125,5 +125,5 @@ def run(args):
certfile=args['cert-file'],
keyfile=args['key-file'])
logging.info("Starting simple http server at %s://%s:%s" % (scheme, host, port))
logging.info("Starting simple http server at %s", address)
httpd.serve_forever()
......@@ -3,6 +3,7 @@ import os
import shutil
import tempfile
import unittest
import socket
import subprocess
import time
......@@ -24,12 +25,16 @@ class SimpleHTTPServerTest(unittest.TestCase):
self.wrapper = os.path.join(self.install_dir, 'server')
self.process = None
def setUpRecipe(self, opt=()):
host, port = os.environ['SLAPOS_TEST_IPV4'], 9999
def setUpRecipe(self, opt=None):
opt = opt or {}
if not 'socketpath' in opt and not 'abstract' in opt:
opt['host'] = host = os.environ['SLAPOS_TEST_IPV4']
opt['port'] = port = 9999
self.server_url = 'http://{host}:{port}'.format(host=host, port=port)
else:
self.server_url = None
options = {
'base-path': self.base_path,
'host': host,
'port': port,
'log-file': os.path.join(self.install_dir, 'simplehttpserver.log'),
'wrapper': self.wrapper,
}
......@@ -39,7 +44,6 @@ class SimpleHTTPServerTest(unittest.TestCase):
options=options,
name='simplehttpserver',
)
self.server_url = 'http://{host}:{port}'.format(host=host, port=port)
def startServer(self):
self.assertEqual(self.recipe.install(), self.wrapper)
......@@ -48,16 +52,39 @@ class SimpleHTTPServerTest(unittest.TestCase):
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
for i in range(16):
try:
if self.server_url:
def check_connection():
resp = requests.get(self.server_url)
break
except requests.exceptions.ConnectionError:
time.sleep(i * .1)
self.assertIn('Directory listing for /', resp.text)
ConnectionError = requests.exceptions.ConnectionError
cleanup = None
else:
self.fail(
'server did not start.\nout: %s error: %s' % self.process.communicate())
self.assertIn('Directory listing for /', resp.text)
address = self.recipe.options['address']
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
def check_connection():
s.connect(address)
ConnectionError = socket.error
cleanup = lambda: s.close()
try:
for i in range(16):
try:
check_connection()
break
except ConnectionError:
time.sleep(i * .1)
else:
# Kill process in case it did not crash
# otherwise .communicate() may hang forever.
self.process.terminate()
self.process.wait()
self.fail(
"Server did not start\n"
"out: %s\n"
"err: %s"
% self.process.communicate())
finally:
if cleanup:
cleanup()
return self.server_url
def tearDown(self):
......@@ -177,3 +204,15 @@ class SimpleHTTPServerTest(unittest.TestCase):
self.assertEqual(resp.status_code, requests.codes.forbidden)
with open(indexpath) as f:
self.assertEqual(f.read(), indexcontent)
def test_socketpath(self):
socketpath = os.path.join(self.install_dir, 'http.sock')
self.setUpRecipe({'socketpath': socketpath})
self.assertEqual(socketpath, self.recipe.options['address'])
self.startServer()
def test_abstract(self):
abstract = os.path.join(self.install_dir, 'abstract.http')
self.setUpRecipe({'abstract': abstract})
self.assertEqual('\0' + abstract, self.recipe.options['address'])
self.startServer()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment