mirror of
https://github.com/mkdocs/mkdocs.git
synced 2026-03-27 18:08:31 +07:00
Move livereload endpoint under the mount path (#2740)
Fix live reloading if using subpath in site_url. This is relevant when using a proxy. Inject parametrized script directly into html. Co-authored-by: Oleh Prypin <oleh@pryp.in>
This commit is contained in:
committed by
GitHub
parent
dc35569ade
commit
fafdcc240e
@@ -8,6 +8,7 @@ import pathlib
|
||||
import posixpath
|
||||
import re
|
||||
import socketserver
|
||||
import string
|
||||
import threading
|
||||
import time
|
||||
import warnings
|
||||
@@ -16,6 +17,30 @@ import wsgiref.simple_server
|
||||
import watchdog.events
|
||||
import watchdog.observers.polling
|
||||
|
||||
_SCRIPT_TEMPLATE = """
|
||||
var livereload = function(epoch, requestId) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.onloadend = function() {
|
||||
if (parseFloat(this.responseText) > epoch) {
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
var launchNext = livereload.bind(this, epoch, requestId);
|
||||
if (this.status === 200) {
|
||||
launchNext();
|
||||
} else {
|
||||
setTimeout(launchNext, 3000);
|
||||
}
|
||||
};
|
||||
req.open("GET", "${mount_path}livereload/" + epoch + "/" + requestId);
|
||||
req.send();
|
||||
|
||||
console.log('Enabled live reload');
|
||||
}
|
||||
livereload(${epoch}, ${request_id});
|
||||
"""
|
||||
_SCRIPT_TEMPLATE = string.Template(_SCRIPT_TEMPLATE)
|
||||
|
||||
|
||||
class _LoggerAdapter(logging.LoggerAdapter):
|
||||
def process(self, msg, kwargs):
|
||||
@@ -176,26 +201,25 @@ class LiveReloadServer(socketserver.ThreadingMixIn, wsgiref.simple_server.WSGISe
|
||||
# https://github.com/bottlepy/bottle/blob/f9b1849db4/bottle.py#L984
|
||||
path = environ["PATH_INFO"].encode("latin-1").decode("utf-8", "ignore")
|
||||
|
||||
m = re.fullmatch(r"/livereload/([0-9]+)/[0-9]+", path)
|
||||
if m:
|
||||
epoch = int(m[1])
|
||||
start_response("200 OK", [("Content-Type", "text/plain")])
|
||||
|
||||
def condition():
|
||||
return self._visible_epoch > epoch
|
||||
|
||||
with self._epoch_cond:
|
||||
if not condition():
|
||||
# Stall the browser, respond as soon as there's something new.
|
||||
# If there's not, respond anyway after a minute.
|
||||
self._log_poll_request(environ.get("HTTP_REFERER"), request_id=path)
|
||||
self._epoch_cond.wait_for(condition, timeout=self.poll_response_timeout)
|
||||
return [b"%d" % self._visible_epoch]
|
||||
|
||||
if path == "/js/livereload.js":
|
||||
file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "livereload.js")
|
||||
elif path.startswith(self.mount_path):
|
||||
if path.startswith(self.mount_path):
|
||||
rel_file_path = path[len(self.mount_path):]
|
||||
|
||||
m = re.fullmatch(r"livereload/([0-9]+)/[0-9]+", rel_file_path)
|
||||
if m:
|
||||
epoch = int(m[1])
|
||||
start_response("200 OK", [("Content-Type", "text/plain")])
|
||||
|
||||
def condition():
|
||||
return self._visible_epoch > epoch
|
||||
|
||||
with self._epoch_cond:
|
||||
if not condition():
|
||||
# Stall the browser, respond as soon as there's something new.
|
||||
# If there's not, respond anyway after a minute.
|
||||
self._log_poll_request(environ.get("HTTP_REFERER"), request_id=path)
|
||||
self._epoch_cond.wait_for(condition, timeout=self.poll_response_timeout)
|
||||
return [b"%d" % self._visible_epoch]
|
||||
|
||||
if path.endswith("/"):
|
||||
rel_file_path += "index.html"
|
||||
# Prevent directory traversal - normalize the path.
|
||||
@@ -235,17 +259,20 @@ class LiveReloadServer(socketserver.ThreadingMixIn, wsgiref.simple_server.WSGISe
|
||||
)
|
||||
return wsgiref.util.FileWrapper(file)
|
||||
|
||||
@classmethod
|
||||
def _inject_js_into_html(cls, content, epoch):
|
||||
def _inject_js_into_html(self, content, epoch):
|
||||
try:
|
||||
body_end = content.rindex(b"</body>")
|
||||
except ValueError:
|
||||
body_end = len(content)
|
||||
# The page will reload if the livereload poller returns a newer epoch than what it knows.
|
||||
# The other timestamp becomes just a unique identifier for the initiating page.
|
||||
return (
|
||||
b'%b<script src="/js/livereload.js"></script><script>livereload(%d, %d);</script>%b'
|
||||
% (content[:body_end], epoch, _timestamp(), content[body_end:])
|
||||
script = _SCRIPT_TEMPLATE.substitute(
|
||||
mount_path=self.mount_path, epoch=epoch, request_id=_timestamp()
|
||||
)
|
||||
return b"%b<script>%b</script>%b" % (
|
||||
content[:body_end],
|
||||
script.encode(),
|
||||
content[body_end:],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
function livereload(epoch, requestId) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.onloadend = function() {
|
||||
if (parseFloat(this.responseText) > epoch) {
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
var launchNext = livereload.bind(this, epoch, requestId);
|
||||
if (this.status === 200) {
|
||||
launchNext();
|
||||
} else {
|
||||
setTimeout(launchNext, 3000);
|
||||
}
|
||||
};
|
||||
req.open("GET", "/livereload/" + epoch + "/" + requestId);
|
||||
req.send();
|
||||
|
||||
console.log('Enabled live reload');
|
||||
}
|
||||
@@ -65,7 +65,7 @@ def do_request(server, content):
|
||||
|
||||
|
||||
SCRIPT_REGEX = (
|
||||
r'<script src="/js/livereload.js"></script><script>livereload\([0-9]+, [0-9]+\);</script>'
|
||||
r'<script>[\S\s]+?livereload\([0-9]+, [0-9]+\);\s*</script>'
|
||||
)
|
||||
|
||||
|
||||
@@ -334,23 +334,18 @@ class BuildTests(unittest.TestCase):
|
||||
_, output = do_request(server, "GET /%E6%B5%8B%E8%AF%952/index.html")
|
||||
self.assertRegex(output, fr"^<body>bbb{SCRIPT_REGEX}</body>$")
|
||||
|
||||
@tempdir()
|
||||
def test_serves_js(self, site_dir):
|
||||
with testing_server(site_dir) as server:
|
||||
for mount_path in "/", "/sub/":
|
||||
server.mount_path = mount_path
|
||||
|
||||
headers, output = do_request(server, "GET /js/livereload.js")
|
||||
self.assertIn("function livereload", output)
|
||||
self.assertEqual(headers["_status"], "200 OK")
|
||||
self.assertEqual(headers.get("content-type"), "application/javascript")
|
||||
|
||||
@tempdir()
|
||||
def test_serves_polling_instantly(self, site_dir):
|
||||
with testing_server(site_dir) as server:
|
||||
_, output = do_request(server, "GET /livereload/0/0")
|
||||
self.assertTrue(output.isdigit())
|
||||
|
||||
@tempdir()
|
||||
def test_serves_polling_from_mount_path(self, site_dir):
|
||||
with testing_server(site_dir, mount_path="/test/f*o") as server:
|
||||
_, output = do_request(server, "GET /test/f*o/livereload/0/0")
|
||||
self.assertTrue(output.isdigit())
|
||||
|
||||
@tempdir()
|
||||
@tempdir()
|
||||
def test_serves_polling_after_event(self, site_dir, docs_dir):
|
||||
|
||||
Reference in New Issue
Block a user