forked from github/pelican
Competing static site generators integrate the functionality of regenerating content and serving it into their main executable. In pelican this functionality used to be in an external script `develop_server.sh` which resides in the blog base directory. This has the disadvantage that changes in pelican can break the `develop_server.sh` scripts which will not automatically be upgraded together with pelican by package managers. Thus, pelican should integrate this functionality into its main executable. To this end, this commit removes `develop_server.sh` and adds three command line options to the pelican executable: * `-l/--listen` starts the HTTP server (`-s/--serve` was already taken) * `-p/--port` specifies the port to listen at * `-b/--bind` specifies the IP to bind to `--listen` and `--autoreload` can be used together to achieve the same effect that other static site generators offer: Serve files via HTTP while at the same time auto-generating the content. Since the `develop_server.sh` script was removed, pelican-quickstart looses the `develop` option. Since the `develop_server.sh` script was removed, the Makefile looses the `stopserver` target and the `devserver` target is replaced by running `pelican -l` in the foreground. Since pelican now offers the `--listen` option, the fabfile uses that instead of starting the socketserver itself.
141 lines
5 KiB
Python
141 lines
5 KiB
Python
# -*- coding: utf-8 -*-
|
|
from __future__ import print_function, unicode_literals
|
|
|
|
import argparse
|
|
import logging
|
|
import os
|
|
import posixpath
|
|
import ssl
|
|
import sys
|
|
|
|
try:
|
|
from magic import from_file as magic_from_file
|
|
except ImportError:
|
|
magic_from_file = None
|
|
|
|
from six.moves import BaseHTTPServer
|
|
from six.moves import SimpleHTTPServer as srvmod
|
|
from six.moves import urllib
|
|
|
|
|
|
def parse_arguments():
|
|
parser = argparse.ArgumentParser(
|
|
description='Pelican Development Server',
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
|
)
|
|
parser.add_argument("port", default=8000, type=int, nargs="?",
|
|
help="Port to Listen On")
|
|
parser.add_argument("server", default="", nargs="?",
|
|
help="Interface to Listen On")
|
|
parser.add_argument('--ssl', action="store_true",
|
|
help='Activate SSL listener')
|
|
parser.add_argument('--cert', default="./cert.pem", nargs="?",
|
|
help='Path to certificate file. ' +
|
|
'Relative to current directory')
|
|
parser.add_argument('--key', default="./key.pem", nargs="?",
|
|
help='Path to certificate key file. ' +
|
|
'Relative to current directory')
|
|
parser.add_argument('path', default=".",
|
|
help='Path to pelican source directory to serve. ' +
|
|
'Relative to current directory')
|
|
return parser.parse_args()
|
|
|
|
|
|
class ComplexHTTPRequestHandler(srvmod.SimpleHTTPRequestHandler):
|
|
SUFFIXES = ['', '.html', '/index.html']
|
|
RSTRIP_PATTERNS = ['', '/']
|
|
|
|
def translate_path(self, path):
|
|
# abandon query parameters
|
|
path = path.split('?', 1)[0]
|
|
path = path.split('#', 1)[0]
|
|
# Don't forget explicit trailing slash when normalizing. Issue17324
|
|
trailing_slash = path.rstrip().endswith('/')
|
|
path = urllib.parse.unquote(path)
|
|
path = posixpath.normpath(path)
|
|
words = path.split('/')
|
|
words = filter(None, words)
|
|
path = self.base_path
|
|
for word in words:
|
|
if os.path.dirname(word) or word in (os.curdir, os.pardir):
|
|
# Ignore components that are not a simple file/directory name
|
|
continue
|
|
path = os.path.join(path, word)
|
|
if trailing_slash:
|
|
path += '/'
|
|
return path
|
|
|
|
def do_GET(self):
|
|
# cut off a query string
|
|
if '?' in self.path:
|
|
self.path, _ = self.path.split('?', 1)
|
|
|
|
found = False
|
|
# Try to detect file by applying various suffixes and stripping
|
|
# patterns.
|
|
for rstrip_pattern in self.RSTRIP_PATTERNS:
|
|
if found:
|
|
break
|
|
for suffix in self.SUFFIXES:
|
|
if not hasattr(self, 'original_path'):
|
|
self.original_path = self.path
|
|
|
|
self.path = self.original_path.rstrip(rstrip_pattern) + suffix
|
|
path = self.translate_path(self.path)
|
|
|
|
if os.path.exists(path):
|
|
srvmod.SimpleHTTPRequestHandler.do_GET(self)
|
|
logging.info("Found `%s`.", self.path)
|
|
found = True
|
|
break
|
|
|
|
logging.info("Tried to find `%s`, but it doesn't exist.", path)
|
|
|
|
if not found:
|
|
# Fallback if there were no matches
|
|
logging.warning("Unable to find `%s` or variations.",
|
|
self.original_path)
|
|
|
|
def guess_type(self, path):
|
|
"""Guess at the mime type for the specified file.
|
|
"""
|
|
mimetype = srvmod.SimpleHTTPRequestHandler.guess_type(self, path)
|
|
|
|
# If the default guess is too generic, try the python-magic library
|
|
if mimetype == 'application/octet-stream' and magic_from_file:
|
|
mimetype = magic_from_file(path, mime=True)
|
|
|
|
return mimetype
|
|
|
|
|
|
class RootedHTTPServer(BaseHTTPServer.HTTPServer):
|
|
def __init__(self, base_path, *args, **kwargs):
|
|
BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
|
|
self.RequestHandlerClass.base_path = base_path
|
|
|
|
|
|
if __name__ == '__main__':
|
|
args = parse_arguments()
|
|
RootedHTTPServer.allow_reuse_address = True
|
|
try:
|
|
httpd = RootedHTTPServer(
|
|
(args.server, args.port),
|
|
ComplexHTTPRequestHandler)
|
|
if args.ssl:
|
|
httpd.socket = ssl.wrap_socket(
|
|
httpd.socket, keyfile=args.key,
|
|
certfile=args.cert, server_side=True)
|
|
except ssl.SSLError as e:
|
|
logging.error("Couldn't open certificate file %s or key file %s",
|
|
args.cert, args.key)
|
|
logging.error("Could not listen on port %s, server %s.",
|
|
args.port, args.server)
|
|
sys.exit(getattr(e, 'exitcode', 1))
|
|
|
|
logging.info("Serving at port %s, server %s.",
|
|
args.port, args.server)
|
|
try:
|
|
httpd.serve_forever()
|
|
except KeyboardInterrupt as e:
|
|
logging.info("Shutting down server.")
|
|
httpd.socket.close()
|