Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Injection of livereload.js fails when head section is too long #242

Open
jenssss opened this issue Jan 22, 2021 · 0 comments
Open

Injection of livereload.js fails when head section is too long #242

jenssss opened this issue Jan 22, 2021 · 0 comments

Comments

@jenssss
Copy link

jenssss commented Jan 22, 2021

When serving an html file with a long head section, python-livereload fails to inject the livereload.js script into the html.

This seems to be caused by the injector chopping the page into bytes chunks of length 65536. The injector only acts on the first of these chunks by injecting the script before the </head> tag, but if this tag falls outside of the first chunk, this fails. See

class LiveScriptInjector(web.OutputTransform):
def __init__(self, request):
super(LiveScriptInjector, self).__init__(request)
def transform_first_chunk(self, status_code, headers, chunk, finishing):
if HEAD_END in chunk:
chunk = chunk.replace(HEAD_END, self.script + HEAD_END)
if 'Content-Length' in headers:
length = int(headers['Content-Length']) + len(self.script)
headers['Content-Length'] = str(length)
return status_code, headers, chunk

MVE

The following python generates two html files, one of which causes the bug, the one does not.

page = """<!doctype html>
<html lang=en>
  <head>
    <meta charset=utf-8>
    <title>tester</title>
    <!-- {header_fill} -->
  </head>
  <body>
    <p>Hi</p>
  </body>
</html>
"""

nl = 65536
n = nl - 113
print(page.format(header_fill='a'*n), file=open("longer_page.html", "w"))
n = nl - 114
print(page.format(header_fill='a'*n), file=open("long_page.html", "w"))

After running this code in python, run livereload . and open the two files long_page.html and longer_page.html in the generated server session. The first file includes the livereload.js script (see developer tools of the browser used), but the second one does not.

Failed solution attempt

The web.OutputTransform class that LiveScriptInjector extends contains a transform_chunk method (the web.OutputTransform class comes from tornado.web) . I tried monkey patching this with

from livereload.server import LiveScriptInjector, HEAD_END
from livereload import Server

def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes:
    if HEAD_END in chunk: 
         chunk = chunk.replace(HEAD_END, self.script + HEAD_END) 
    return chunk

LiveScriptInjector.transform_chunk = transform_chunk

server = Server()
server.watch("longer_page.html")
server.serve(root='.')

but I got the following error

Uncaught exception GET /longer_page.html (127.0.0.1)
    HTTPServerRequest(protocol='http', host='127.0.0.1:5500', method='GET', uri='/longer_page.html', version='HTTP/1.1', remote_ip='127.0.0.1')
    Traceback (most recent call last):
      File "/home/jens/miniconda3/envs/test-livereload/lib/python3.9/site-packages/tornado/web.py", line 1704, in _execute
        result = await result
      File "/home/jens/miniconda3/envs/test-livereload/lib/python3.9/site-packages/tornado/web.py", line 2648, in get
        await self.flush()
      File "/home/jens/miniconda3/envs/test-livereload/lib/python3.9/site-packages/tornado/web.py", line 1102, in flush
        return self.request.connection.write(chunk)
      File "/home/jens/miniconda3/envs/test-livereload/lib/python3.9/site-packages/tornado/http1connection.py", line 499, in write
        self._pending_write = self.stream.write(self._format_chunk(chunk))
      File "/home/jens/miniconda3/envs/test-livereload/lib/python3.9/site-packages/tornado/http1connection.py", line 475, in _format_chunk
        raise httputil.HTTPOutputError(
    tornado.httputil.HTTPOutputError: Tried to write more data than Content-Length

Workaround

As a temporary workaround, the LiveScriptInjector class can be monkey patched to inject the code after the opening <head> tag, rather than before the closing </head> tag.

from livereload.server import LiveScriptInjector

HEAD_START = b'<head>'

def transform_first_chunk(self, status_code, headers, chunk, finishing):
    if HEAD_START in chunk:
        chunk = chunk.replace(HEAD_START, HEAD_START+self.script)
        if 'Content-Length' in headers:
            length = int(headers['Content-Length']) + len(self.script)
            headers['Content-Length'] = str(length)
    return status_code, headers, chunk

LiveScriptInjector.transform_first_chunk = transform_first_chunk
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant