aboutsummaryrefslogtreecommitdiffstats
path: root/nikola/plugins/command/serve.py
diff options
context:
space:
mode:
authorLibravatarUnit 193 <unit193@unit193.net>2021-02-03 19:17:00 -0500
committerLibravatarUnit 193 <unit193@unit193.net>2021-02-03 19:17:00 -0500
commit3a0d66f07b112b6d2bdc2b57bbf717a89a351ce6 (patch)
treea7cf56282e54f05785243bc1e903d6594f2c06ba /nikola/plugins/command/serve.py
parent787b97a4cb24330b36f11297c6d3a7a473a907d0 (diff)
New upstream version 8.1.2.upstream/8.1.2
Diffstat (limited to 'nikola/plugins/command/serve.py')
-rw-r--r--nikola/plugins/command/serve.py95
1 files changed, 55 insertions, 40 deletions
diff --git a/nikola/plugins/command/serve.py b/nikola/plugins/command/serve.py
index 0441c93..ede5179 100644
--- a/nikola/plugins/command/serve.py
+++ b/nikola/plugins/command/serve.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright © 2012-2015 Roberto Alsina and others.
+# Copyright © 2012-2020 Roberto Alsina and others.
# Permission is hereby granted, free of charge, to any
# person obtaining a copy of this software and associated
@@ -26,43 +26,33 @@
"""Start test server."""
-from __future__ import print_function
import os
+import sys
import re
+import signal
import socket
import webbrowser
-try:
- from BaseHTTPServer import HTTPServer
- from SimpleHTTPServer import SimpleHTTPRequestHandler
-except ImportError:
- from http.server import HTTPServer # NOQA
- from http.server import SimpleHTTPRequestHandler # NOQA
-
-try:
- from StringIO import StringIO
-except ImportError:
- from io import BytesIO as StringIO # NOQA
-
+from http.server import HTTPServer
+from http.server import SimpleHTTPRequestHandler
+from io import BytesIO as StringIO
from nikola.plugin_categories import Command
-from nikola.utils import get_logger, STDERR_HANDLER
+from nikola.utils import dns_sd
class IPv6Server(HTTPServer):
-
"""An IPv6 HTTPServer."""
address_family = socket.AF_INET6
class CommandServe(Command):
-
"""Start test server."""
name = "serve"
doc_usage = "[options]"
doc_purpose = "start the test webserver"
- logger = None
+ dns_sd = None
cmd_options = (
{
@@ -71,7 +61,7 @@ class CommandServe(Command):
'long': 'port',
'default': 8000,
'type': int,
- 'help': 'Port number (default: 8000)',
+ 'help': 'Port number',
},
{
'name': 'address',
@@ -79,7 +69,7 @@ class CommandServe(Command):
'long': 'address',
'type': str,
'default': '',
- 'help': 'Address to bind (default: 0.0.0.0 – all local IPv4 interfaces)',
+ 'help': 'Address to bind, defaults to all local IPv4 interfaces',
},
{
'name': 'detach',
@@ -107,13 +97,24 @@ class CommandServe(Command):
},
)
+ def shutdown(self, signum=None, _frame=None):
+ """Shut down the server that is running detached."""
+ if self.dns_sd:
+ self.dns_sd.Reset()
+ if os.path.exists(self.serve_pidfile):
+ os.remove(self.serve_pidfile)
+ if not self.detached:
+ self.logger.info("Server is shutting down.")
+ if signum:
+ sys.exit(0)
+
def _execute(self, options, args):
"""Start test server."""
- self.logger = get_logger('serve', STDERR_HANDLER)
out_dir = self.site.config['OUTPUT_FOLDER']
if not os.path.isdir(out_dir):
self.logger.error("Missing '{0}' folder?".format(out_dir))
else:
+ self.serve_pidfile = os.path.abspath('nikolaserve.pid')
os.chdir(out_dir)
if '[' in options['address']:
options['address'] = options['address'].strip('[').strip(']')
@@ -129,37 +130,47 @@ class CommandServe(Command):
httpd = OurHTTP((options['address'], options['port']),
OurHTTPRequestHandler)
sa = httpd.socket.getsockname()
- self.logger.info("Serving HTTP on {0} port {1}...".format(*sa))
+ if ipv6:
+ server_url = "http://[{0}]:{1}/".format(*sa)
+ else:
+ server_url = "http://{0}:{1}/".format(*sa)
+ self.logger.info("Serving on {0} ...".format(server_url))
+
if options['browser']:
- if ipv6:
- server_url = "http://[{0}]:{1}/".format(*sa)
- else:
- server_url = "http://{0}:{1}/".format(*sa)
+ # Some browsers fail to load 0.0.0.0 (Issue #2755)
+ if sa[0] == '0.0.0.0':
+ server_url = "http://127.0.0.1:{1}/".format(*sa)
self.logger.info("Opening {0} in the default web browser...".format(server_url))
webbrowser.open(server_url)
if options['detach']:
+ self.detached = True
OurHTTPRequestHandler.quiet = True
try:
pid = os.fork()
if pid == 0:
+ signal.signal(signal.SIGTERM, self.shutdown)
httpd.serve_forever()
else:
- self.logger.info("Detached with PID {0}. Run `kill {0}` to stop the server.".format(pid))
- except AttributeError as e:
+ with open(self.serve_pidfile, 'w') as fh:
+ fh.write('{0}\n'.format(pid))
+ self.logger.info("Detached with PID {0}. Run `kill {0}` or `kill $(cat nikolaserve.pid)` to stop the server.".format(pid))
+ except AttributeError:
if os.name == 'nt':
self.logger.warning("Detaching is not available on Windows, server is running in the foreground.")
else:
- raise e
+ raise
else:
+ self.detached = False
try:
+ self.dns_sd = dns_sd(options['port'], (options['ipv6'] or '::' in options['address']))
+ signal.signal(signal.SIGTERM, self.shutdown)
httpd.serve_forever()
except KeyboardInterrupt:
- self.logger.info("Server is shutting down.")
+ self.shutdown()
return 130
class OurHTTPRequestHandler(SimpleHTTPRequestHandler):
-
"""A request handler, modified for Nikola."""
extensions_map = dict(SimpleHTTPRequestHandler.extensions_map)
@@ -171,8 +182,7 @@ class OurHTTPRequestHandler(SimpleHTTPRequestHandler):
if self.quiet:
return
else:
- # Old-style class in Python 2.7, cannot use super()
- return SimpleHTTPRequestHandler.log_message(self, *args)
+ return super().log_message(*args)
# NOTICE: this is a patched version of send_head() to disable all sorts of
# caching. `nikola serve` is a development server, hence caching should
@@ -184,9 +194,9 @@ class OurHTTPRequestHandler(SimpleHTTPRequestHandler):
# Note that it might break in future versions of Python, in which case we
# would need to do even more magic.
def send_head(self):
- """Common code for GET and HEAD commands.
+ """Send response code and MIME header.
- This sends the response code and MIME headers.
+ This is common code for GET and HEAD commands.
Return value is either a file object (which has to be copied
to the outputfile by the caller unless the command was HEAD,
@@ -197,10 +207,12 @@ class OurHTTPRequestHandler(SimpleHTTPRequestHandler):
path = self.translate_path(self.path)
f = None
if os.path.isdir(path):
- if not self.path.endswith('/'):
+ path_parts = list(self.path.partition('?'))
+ if not path_parts[0].endswith('/'):
# redirect browser - doing basically what apache does
+ path_parts[0] += '/'
self.send_response(301)
- self.send_header("Location", self.path + "/")
+ self.send_header("Location", ''.join(path_parts))
# begin no-cache patch
# For redirects. With redirects, caching is even worse and can
# break more. Especially with 301 Moved Permanently redirects,
@@ -226,7 +238,7 @@ class OurHTTPRequestHandler(SimpleHTTPRequestHandler):
# transmitted *less* than the content-length!
f = open(path, 'rb')
except IOError:
- self.send_error(404, "File not found")
+ self.send_error(404, "File not found: {}".format(path))
return None
filtered_bytes = None
@@ -234,7 +246,7 @@ class OurHTTPRequestHandler(SimpleHTTPRequestHandler):
# Comment out any <base> to allow local resolution of relative URLs.
data = f.read().decode('utf8')
f.close()
- data = re.sub(r'<base\s([^>]*)>', '<!--base \g<1>-->', data, re.IGNORECASE)
+ data = re.sub(r'<base\s([^>]*)>', r'<!--base \g<1>-->', data, flags=re.IGNORECASE)
data = data.encode('utf8')
f = StringIO()
f.write(data)
@@ -242,7 +254,10 @@ class OurHTTPRequestHandler(SimpleHTTPRequestHandler):
f.seek(0)
self.send_response(200)
- self.send_header("Content-type", ctype)
+ if ctype.startswith('text/') or ctype.endswith('+xml'):
+ self.send_header("Content-Type", "{0}; charset=UTF-8".format(ctype))
+ else:
+ self.send_header("Content-Type", ctype)
if os.path.splitext(path)[1] == '.svgz':
# Special handling for svgz to make it work nice with browsers.
self.send_header("Content-Encoding", 'gzip')