Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.

Due to spam on this forum, all posts now need moderator approval.

 Entire forum ➜ Programming ➜ General ➜ Solving a problem with Safari web browser hanging

Solving a problem with Safari web browser hanging

It is now over 60 days since the last post. This thread is closed.     Refresh page


Posted by Nick Gammon   Australia  (23,166 posts)  Bio   Forum Administrator
Date Tue 12 Sep 2006 02:46 AM (UTC)

Amended on Tue 12 Sep 2006 03:37 AM (UTC) by Nick Gammon

Message
I have spent a couple of hours working out this problem, so this post is sharing my findings with the rest of the world, as it seems to be a bit of an ongoing issue with Safari - the web browser distributed with Apple Macintosh.

The problem

I had been developing some dynamic web pages using scripts documented on this page:

http://www.gammon.com.au/forum/?id=6498

They seemed to work perfectly when tested using Firefox (on the PC), however certain pages just "hung" when tested with Safari, which eventually gave this message:


Safari can't open the page.

Safari can't open the page "http://10.0.0.2/cgi-bin/cgiutils.cgi?form=events&action=Edit&eventid=1". The error was: "lost network connection" (NSURLErrorDomain:-1005) Please choose Report Bug to Apple from the Safari menu, note the error number, and describe what you did before you saw this message.


The problem only appeared to occur on POST pages (that is when you fill in a form and hit a "submit" button) rather than GET pages. A GET page occurs when you simply type in a URL in the address bar, or click on a hyperlink.

Now, in CGI scripts POST data is obtained by reading from stdin, as there is likely to be a lot of it. Compare this to GET data which is simply supplied in environment variables.

In fact, in Lua, I was obtaining the GET data like this:


s = os.getenv ("QUERY_STRING")


I obtained the cookie data like this:


s = os.getenv ("HTTP_COOKIE")


And I obtained the POST data like this:


s = io.read ("*a")


My first problem was that, after changing from Apache web server to thttpd, all attempts to access CGI files were hanging, because thttpd was not even supplying stdin data unless it was for a POST form. So, this fixed up that part:


if os.getenv ("REQUEST_METHOD") == "POST" then
  s = os.getenv ("HTTP_COOKIE")
end -- if POST


However I still had the problem with Safari, and POST data, even with that change in place.



Troubleshooting

Time to look at the difference between what Safari was doing, and what Firefox was doing, that caused one to work and not the other one.

After a bit of mucking around looking at the documentation for tcpdump, I worked out this:


$ tcpdump -X -s 0 'src host 10.0.0.3 and dst port 80'

0x0000   4500 00ff 8341 4000 8006 62b3 0a00 0003        E....A@...b.....
0x0010   0a00 0002 0c1c 2382 00e0 583e eec3 647f        ......#...X>..d.
0x0020   5018 2238 f8b1 0000 436f 6e74 656e 742d        P."8....Content-
0x0030   5479 7065 3a20 6170 706c 6963 6174 696f        Type:.applicatio
0x0040   6e2f 782d 7777 772d 666f 726d 2d75 726c        n/x-www-form-url
0x0050   656e 636f 6465 640d 0a43 6f6e 7465 6e74        encoded..Content
0x0060   2d4c 656e 6774 683a 2031 3433 0d0a 0d0a        -Length:.143....
0x0070   7375 6d6d 6172 793d 5061 7265 6e74 2d74        summary=Parent-t
0x0080   6561 6368 6572 2b6e 6967 6874 2665 7665        eacher+night&eve
0x0090   6e74 3d4d 616e 792b 7061 7265 6e74 732b        nt=Many+parents+
0x00a0   6174 7465 6e64 6564 2b74 6865 2b6e 6967        attended+the+nig
0x00b0   6874 2b61 6161 6262 6226 6576 656e 7464        ht+aaabbb&eventd
0x00c0   6174 653d 3230 3036 2d31 312d 3034 2b30        ate=2006-11-04+0
0x00d0   3025 3341 3030 2533 4130 3026 666f 726d        0%3A00%3A00&form
0x00e0   3d65 7665 6e74 7326 6576 656e 7469 643d        =events&eventid=
0x00f0   3126 6163 7469 6f6e 3d43 6861 6e67 65          1&action=Change


The address 10.0.0.3 was the PC with Firefox on it, and the "dst port 80" means attempts to access port 80 (the HTTP port).

That showed that the POST data was in the same packet as some of the HTTP headers. I reran it with the -w option, like this:


$ tcpdump -X -s 0 'src host 10.0.0.3 and dst port 80' -w firefox_file


Then edited this file, and cut out the unreadable stuff, to give the actual headers:


Host: 10.0.0.2:80
User-Agent: Mozilla/5.0 (Windows; U; WinNT4.0; en-GB; rv:1.7.12) Gecko/20050919 Firefox/1.0.7
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: en-gb,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://10.0.0.2:80/cgi-bin/cgiutils.cgi?form=events&action=Edit&eventid=1
Cookie: token=0959F66083E33D90FD91A333CCAF9D2965B1E0AAD1062A081A8C9839A505E8E3

---> new packet here <----

Content-Length: 143

summary=Parent-teacher+night&event=Many+parents+attended .. blah blah



Then I compared that (which worked) to the results from Safari (which didn't):


$ tcpdump -X -s 0 'src host 10.0.0.12 and dst port 80'

0x0000   4500 00c3 30ea 4000 4006 f53d 0a00 000c        E...0.@.@..=....
0x0010   0a00 0002 c08e 2382 ee9d 6892 092b 8b7b        ......#...h..+.{
0x0020   8018 ffff 2839 0000 0101 080a 32e2 085f        ....(9......2.._
0x0030   2f5a f875 7375 6d6d 6172 793d 5061 7265        /Z.usummary=Pare
0x0040   6e74 2d74 6561 6368 6572 2b6e 6967 6874        nt-teacher+night
0x0050   2665 7665 6e74 3d4d 616e 792b 7061 7265        &event=Many+pare
0x0060   6e74 732b 6174 7465 6e64 6564 2b74 6865        nts+attended+the
0x0070   2b6e 6967 6874 2b61 6161 6262 6226 6576        +night+aaabbb&ev
0x0080   656e 7464 6174 653d 3230 3036 2d31 312d        entdate=2006-11-
0x0090   3034 2b30 3025 3341 3030 2533 4130 3026        04+00%3A00%3A00&
0x00a0   666f 726d 3d65 7665 6e74 7326 6576 656e        form=events&even
0x00b0   7469 643d 3126 6163 7469 6f6e 3d43 6861        tid=1&action=Cha
0x00c0   6e67 65                                        nge


And, saving the data to file to see the difference:


Accept: */*
Accept-Language: en
Accept-Encoding: gzip, deflate
Cookie: token=B7B1AF1014D21E82581C1EEE33E584E202407F6598B9B967AA0F586E9FAB9E79
Referer: http://10.0.0.2:80/cgi-bin/cgiutils.cgi?form=events&action=Edit&eventid=1
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418.8 (KHTML, like Gecko) Safari/419.3
Content-Type: application/x-www-form-urlencoded
Content-Length: 143
Connection: keep-alive
Host: 10.0.0.2:80

---> new packet here <----

summary=Parent-teacher+night&event=Many+parents+attended .. blah blah




What does it all mean?

The one big difference here is that the Firefox request bundled up the POST data in the same packet as some of the other headers (in this case, Content-Length: 143) whereas Safari put it into a new packet.

Now, looking back at the code for thttpd, it seems that there is a big difference in how it handles the POST data in these 2 cases:


  • If some of the post data has already arrived (the Firefox case) it cannot simply let the CGI process redirect from stdin to the socket, because it would miss out on the first part. Thus it makes a special "interposer process" that buffers up the first part of the POST data, and then supplies the rest from the socket.

    Plus, and this is the important bit, it automatically stops supplying data when the "Content-Length" bytes have arrived (in my case 143 bytes). Thus my "read all from stdin" reaches end-of-file and my CGI program proceeds successfully.

  • If none of the post data has arrived (the Safari case) it simply redirects the incoming socket to be stdin for the CGI process, and lets the CGI process read it directly. However as the connection is marked "keep-alive" it does not terminate the input stream after 143 bytes, and the CGI program sits there indefinitely trying to read from stdin. Eventually thttpd kills the CGI process (after 30 seconds) and Safari gives an error message about "lost network connection".




The solution

What has worked for me is to make sure that, when reading the POST data, you stop after reading "Content-Length" bytes. This change worked for me:


local post_length = tonumber (os.getenv ("CONTENT_LENGTH")) or 0
if os.getenv ("REQUEST_METHOD") == "POST" and post_length > 0 then
  s = io.read (post_length)
end -- if post


Now the io.read is only reading the correct number of bytes, so it no longer hangs. :)

Problem solved.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,166 posts)  Bio   Forum Administrator
Date Reply #1 on Tue 12 Sep 2006 02:49 AM (UTC)
Message
The actual code in cgiutils.lua is slightly more complicated, if you are planning to paste the solution into that code, this is what I really used:


 -- posted data (from a form)
  
  local post_data = {}
  local post_length = tonumber (os.getenv ("CONTENT_LENGTH")) or 0
  if os.getenv ("REQUEST_METHOD") == "POST" and post_length > 0 then
    for _, v in ipairs (split (io.read (post_length), "&")) do
      assemble_value (v, post_data)
    end -- for
  end -- if post


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,166 posts)  Bio   Forum Administrator
Date Reply #2 on Wed 13 Sep 2006 02:36 AM (UTC)

Amended on Wed 13 Sep 2006 02:37 AM (UTC) by Nick Gammon

Message
Also, see RFC 2616:




8.1.2.1 Negotiation

...

In order to remain persistent, all messages on the connection MUST have a self-defined message length (i.e., one not defined by closure of the connection), as described in section 4.4.





This indicates that the correct procedure for determining the POST length is to use the message length field ("Content-Length" header) rather than waiting for the connection to close.

Thus you could argue that this is not a Safari bug, but that when Safari hangs it is a side-effect of incorrect CGI implementation.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


11,705 views.

It is now over 60 days since the last post. This thread is closed.     Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.