Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safari render HTML as received

Tags:

http

safari

When I load a html page I have a 5 strings written about a second apart.

<br>1</br>
...... 1 second ......
<br>2</br>
...... 1 second ......
<br>3</br>
...... 1 second ......
<br>4</br>
...... 1 second ......
<br>5</br>
...... 1 second ......

--- end request ---

Chromium and Firefox both load and display the first br then the next as received. (Firefox requires a content encoding however). But Safari refuses to display any of the tags until the request is ended.

Chromium seems to just do it.

Firefox first needs to determine the content encoding https://bugzilla.mozilla.org/show_bug.cgi?id=647203

But Safari seems to just refuse. Is a different response code or header needed? I try setting explicitly the content type to text/html. Didn't work.

I have confirmed in Wireshark that the strings are being sent a second apart, i.e they are not being cached and sent all at once.

I have also confirmed this occurs if I go through localhost or I use my public ip address.

I have tried content-length and keep alive, former just closes request automatically, latter seems to have no effect.

Headers and Responses from Wireshark

Firefox (working)

GET /pe HTTP/1.1
Host: 127.0.01:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:42.0) Gecko/20100101 Firefox/42.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Cache-Control: max-age=0

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Date: Tue, 10 Nov 2015 17:10:20 GMT
Connection: keep-alive
Content-Type: text/html; charset=utf-8
Server: TwistedWeb/13.2.0

1f
<html>
<title>PE</title>
<body>
2e

<br> This is the 1th time I've written. </br>
2e

<br> This is the 2th time I've written. </br>
2e

<br> This is the 3th time I've written. </br>
2e

<br> This is the 4th time I've written. </br>
2e

<br> This is the 5th time I've written. </br>
8

</body>
8

</html>
0

Safari (not working)

GET /pe HTTP/1.1
Host: 127.0.0.01:8080
Accept-Encoding: gzip, deflate
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/601.1.56 (KHTML, like Gecko) Version/9.0 Safari/601.1.56
Accept-Language: en-us
DNT: 1
Connection: keep-alive

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Date: Tue, 10 Nov 2015 17:12:55 GMT
Connection: keep-alive
Content-Type: text/html; charset=utf-8
Server: TwistedWeb/13.2.0

1f
<html>
<title>PE</title>
<body>
2e

<br> This is the 1th time I've written. </br>
2e

<br> This is the 2th time I've written. </br>
2e

<br> This is the 3th time I've written. </br>
2e

<br> This is the 4th time I've written. </br>
2e

<br> This is the 5th time I've written. </br>
8

</body>
8

</html>
0

Demo

import twisted
from twisted.python import log
import sys

log.startLogging(sys.stdout)
from twisted.web.server import Site, NOT_DONE_YET
from twisted.web.resource import Resource
from twisted.internet import reactor



class PersistantExample(Resource):
    '''Gives an example of a persistant request'''
    # does not exist on Safari until stopping browser / ending connection

    isLeaf = True
    def render_GET(self, request):
        log.msg("Ooooh a render request")
        # schedule the reoccuring thing (this could be something else like a deferred result)

        reactor.callLater(1.1, self.keeps_going, request, 0) # 1.1 seconds just to show it can take floats

        # firefox require the char set see https://bugzilla.mozilla.org/show_bug.cgi?id=647203
        request.responseHeaders.addRawHeader("Content-Type",
        "text/html; charset=utf-8") # set the MIME header (charset needed for firefox)

        # this will cause the connection to keep only open for x length
        # (only helpful if the length is known, also does NOT make it render right away)
        # request.responseHeaders.addRawHeader("Content-Length", "150")

        request.write("<html>\n<title>PE</title>\n<body>")
        return NOT_DONE_YET


    def keeps_going(self, request, i):
        log.msg("I'm going again....")
        i = i + 1
        request.write("\n<br> This is the %sth time I've written. <br>" % i) ## probably not best to use <br> tag

        if i < 5:
            reactor.callLater(1.1, self.keeps_going, request, i) # 1.1 seconds just to show it can take floats
        if i >= 5 and not request.finished:
            log.msg("Done")
            request.write("\n</body>")
            request.write("\n</html>")
            # safari will only render when finished
            request.finish()


class Root(Resource):
    isLeaf = False
    def render_GET(self, request):
        return "<html><body>Demo is <a href=\"pe\">here</a></body></html>"




class Site(Site):
    pass



root = Root()
pe = PersistantExample()
site = Site(root)

root.putChild("", root)
root.putChild("index", root)
root.putChild("pe", pe)

# listen
if __name__ == "__main__":
    reactor.listenTCP(8080, site)
    reactor.run()
like image 588
Zimm3r Avatar asked Nov 01 '15 16:11

Zimm3r


2 Answers

Are you trying this on Safari for Windows? If yes then it not officially supported any more. If you are try to solve this on Safari for Mac please create a demo or share the link if you already have.

I can test and help you over this.

like image 114
Shahzad Fateh Ali Avatar answered Nov 09 '22 00:11

Shahzad Fateh Ali


Ok in order for Safari to render the html at least 1024 bytes have to be written before it starts to render as received.

You can see a demo showing both a working and non working version below. A similar thing happens in Firefox (as it needs 1024 bytes to determine encoding) but you can set the encoding to bypass it in Firefox. Not sure if there is a way to do that in Safari.


+---------------------------------------------------+
| What you send  | How much you need                |
+---------------------------------------------------+
| Nothing        | 1024 bytes                       |
+---------------------------------------------------+
| Meta Charset   | 512 bytes                        |
+---------------------------------------------------+
| Header Charset | 512 bytes (not including header) |
+---------------------------------------------------+

import twisted
from twisted.python import log
import sys

log.startLogging(sys.stdout)
from twisted.web.server import Site, NOT_DONE_YET
from twisted.web.resource import Resource
from twisted.internet import reactor

import time


# max 133 
# 133 * 8 = 1064

html_string_working = "<p>" # 3 bytes
html_string_working += "I'm the version that works! I show up right away!" # 49 bytes
html_string_working += "<!---" # 5 bytes
html_string_working += " " * (1024 - (3+49+5+3+4)) # filler
html_string_working += "-->" # 3 bytes
html_string_working += "</p>" # 4 bytes

print len(html_string_working)


html_string_non_working = "<p>" # 3 bytes
html_string_non_working += "I'm the version that does not work! I don't show up right away!" # 63 bytes
html_string_non_working += "</p>" # 4 bytes
html_string_non_working += "<!---" # 5 bytes
html_string_non_working += " " * (1023 - (3+63+5+3+4)) # filler but one byte short (notice 1023 instead of 1024)
html_string_non_working += "-->" # 3 bytes


print len(html_string_non_working)

# charset maybe? Firefox won't start parsing until 1024
# unless it has charset specified see https://bugzilla.mozilla.org/show_bug.cgi?id=647203
# "Your script does not appear to declare the character encoding. When the character
# encoding is not declared, Gecko won't start parsing until it has received 1024
# bytes of HTTP payload."

# charset works but requires 512 bytes
html_string_charset = "<meta charset=utf-8>" # 20 bytes
html_string_charset += "<p>" # 3 bytes
html_string_charset += "I'm the meta charset version. I may work!" # 41 bytes
html_string_charset += "</p>" # 4 bytes
html_string_charset += "<!---" # 5 bytes
html_string_charset += " " * (512 - (20+3+41+5+3+4)) # filler but one byte short (512 bytes; 511 doesn't work)
html_string_charset += "-->" # 3 bytes

# what about in header form
# charset works but requires 512 bytes not including the headers (so same as meta version)
html_string_charset_headers = "<p>" # 3 bytes
html_string_charset_headers += "I'm the header charset version. I may work!" # 43 bytes
html_string_charset_headers += "</p>" # 4 bytes
html_string_charset_headers += "<!---" # 5 bytes
html_string_charset_headers += " " * (512 - (3+43+5+3+4)) # filler but one byte short (512 bytes; 511 doesn't work)
html_string_charset_headers += "-->" # 3 bytes


print len(html_string_charset_headers)


later = "<p> I'm written later. Now it suddenly will work because bytes >= 1024." # 71 bytes

class PersistantExample(Resource):
    '''Gives an example of a persistant request'''
    # does not exist on Safari until stopping browser / ending connection

    isLeaf = True
    def render_GET(self, request):
        log.msg("Ooooh a render request")
        # schedule the reoccuring thing (this could be something else like a deferred result)
        # firefox require the char set see https://bugzilla.mozilla.org/show_bug.cgi?id=647203

        # render working version or not
        try:
            w = int(request.args["w"][0])
        except:
            w = 1

        if w == 1:
            request.write(html_string_working)
        elif w == 2:
            request.write(html_string_non_working)
        elif w == 3:
            request.write(html_string_charset)
        elif w == 4:
            # 12 + 24 = 36 bytes but doesn't count towards 512 total
            request.responseHeaders.addRawHeader("Content-Type", "text/html; charset=utf-8")
            request.write(html_string_charset_headers)

        reactor.callLater(2, self.run_later, request)

        return NOT_DONE_YET


    def run_later(self, request):
        request.write(later)
        request.finish()





class Root(Resource):
    isLeaf = False
    def render_GET(self, request):
        return """<html><body>

        <p><a href="pe?w=1">Working version here</a></p>
        <p><a href="pe?w=2">Non working version here</a></p>
        <p><a href="pe?w=3">Meta charset version here</a></p>
        <p><a href="pe?w=4">Header charset version here</a></p>
        </body></html>"""



class Site(Site):
    pass



root = Root()
pe = PersistantExample()
site = Site(root)

root.putChild("", root)
root.putChild("index", root)
root.putChild("pe", pe)

# listen
if __name__ == "__main__":
    print "Running"
    reactor.listenTCP(8080, site)
    reactor.run()
like image 1
Zimm3r Avatar answered Nov 08 '22 22:11

Zimm3r