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): ...@@ -41,10 +41,19 @@ def issubpathof(subpath, path):
class Recipe(GenericBaseRecipe): 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): def install(self):
parameters = { parameters = {
'host': self.options['host'], 'address': self.options['address'],
'port': int(self.options['port']),
'cwd': self.options['base-path'], 'cwd': self.options['base-path'],
'log-file': self.options['log-file'], 'log-file': self.options['log-file'],
'cert-file': self.options.get('cert-file', ''), 'cert-file': self.options.get('cert-file', ''),
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from six.moves.SimpleHTTPServer import SimpleHTTPRequestHandler from six.moves.SimpleHTTPServer import SimpleHTTPRequestHandler
from six.moves.BaseHTTPServer import HTTPServer from six.moves.socketserver import TCPServer
import ssl import ssl
import os import os
import logging import logging
from netaddr import valid_ipv4, valid_ipv6
import socket import socket
import cgi, errno import cgi, errno
...@@ -89,9 +88,6 @@ class ServerHandler(SimpleHTTPRequestHandler): ...@@ -89,9 +88,6 @@ class ServerHandler(SimpleHTTPRequestHandler):
self.respond(200, type=self.headers['Content-Type']) self.respond(200, type=self.headers['Content-Type'])
self.wfile.write(b"Content written to %s" % str2bytes(filename)) self.wfile.write(b"Content written to %s" % str2bytes(filename))
class HTTPServerV6(HTTPServer):
address_family = socket.AF_INET6
def run(args): def run(args):
...@@ -100,8 +96,7 @@ def run(args): ...@@ -100,8 +96,7 @@ def run(args):
logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
filename=args['log-file'] ,level=logging.INFO) filename=args['log-file'] ,level=logging.INFO)
port = args['port'] address = args['address']
host = args['host']
cwd = args['cwd'] cwd = args['cwd']
os.chdir(cwd) os.chdir(cwd)
...@@ -110,12 +105,17 @@ def run(args): ...@@ -110,12 +105,17 @@ def run(args):
Handler.base_path = cwd Handler.base_path = cwd
Handler.restrict_write = not args['allow-write'] Handler.restrict_write = not args['allow-write']
if valid_ipv6(host): try:
server = HTTPServerV6 host, port = address
else: family, _, _, _, _ = socket.getaddrinfo(host, port)[0]
server = HTTPServer 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' scheme = 'http'
if 'cert-file' in args and 'key-file' in args and \ if 'cert-file' in args and 'key-file' in args and \
os.path.exists(args['cert-file']) and os.path.exists(args['key-file']): os.path.exists(args['cert-file']) and os.path.exists(args['key-file']):
...@@ -125,5 +125,5 @@ def run(args): ...@@ -125,5 +125,5 @@ def run(args):
certfile=args['cert-file'], certfile=args['cert-file'],
keyfile=args['key-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() httpd.serve_forever()
...@@ -3,6 +3,7 @@ import os ...@@ -3,6 +3,7 @@ import os
import shutil import shutil
import tempfile import tempfile
import unittest import unittest
import socket
import subprocess import subprocess
import time import time
...@@ -24,12 +25,16 @@ class SimpleHTTPServerTest(unittest.TestCase): ...@@ -24,12 +25,16 @@ class SimpleHTTPServerTest(unittest.TestCase):
self.wrapper = os.path.join(self.install_dir, 'server') self.wrapper = os.path.join(self.install_dir, 'server')
self.process = None self.process = None
def setUpRecipe(self, opt=()): def setUpRecipe(self, opt=None):
host, port = os.environ['SLAPOS_TEST_IPV4'], 9999 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 = { options = {
'base-path': self.base_path, 'base-path': self.base_path,
'host': host,
'port': port,
'log-file': os.path.join(self.install_dir, 'simplehttpserver.log'), 'log-file': os.path.join(self.install_dir, 'simplehttpserver.log'),
'wrapper': self.wrapper, 'wrapper': self.wrapper,
} }
...@@ -39,7 +44,6 @@ class SimpleHTTPServerTest(unittest.TestCase): ...@@ -39,7 +44,6 @@ class SimpleHTTPServerTest(unittest.TestCase):
options=options, options=options,
name='simplehttpserver', name='simplehttpserver',
) )
self.server_url = 'http://{host}:{port}'.format(host=host, port=port)
def startServer(self): def startServer(self):
self.assertEqual(self.recipe.install(), self.wrapper) self.assertEqual(self.recipe.install(), self.wrapper)
...@@ -48,16 +52,39 @@ class SimpleHTTPServerTest(unittest.TestCase): ...@@ -48,16 +52,39 @@ class SimpleHTTPServerTest(unittest.TestCase):
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
) )
for i in range(16): if self.server_url:
try: def check_connection():
resp = requests.get(self.server_url) resp = requests.get(self.server_url)
break self.assertIn('Directory listing for /', resp.text)
except requests.exceptions.ConnectionError: ConnectionError = requests.exceptions.ConnectionError
time.sleep(i * .1) cleanup = None
else: else:
self.fail( address = self.recipe.options['address']
'server did not start.\nout: %s error: %s' % self.process.communicate()) s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.assertIn('Directory listing for /', resp.text) 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 return self.server_url
def tearDown(self): def tearDown(self):
...@@ -177,3 +204,15 @@ class SimpleHTTPServerTest(unittest.TestCase): ...@@ -177,3 +204,15 @@ class SimpleHTTPServerTest(unittest.TestCase):
self.assertEqual(resp.status_code, requests.codes.forbidden) self.assertEqual(resp.status_code, requests.codes.forbidden)
with open(indexpath) as f: with open(indexpath) as f:
self.assertEqual(f.read(), indexcontent) 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