I am testing a minimal web server in SBCL using usocket:
(defun create-server (port)
(let* ((socket (usocket:socket-listen *hostname* port))
(connection (usocket:socket-accept socket :element-type 'character)))
(unwind-protect
(with-open-stream (stream (usocket:socket-stream connection))
(progn
(format stream *htmlstring*)
(finish-output stream)))
(progn
(format t "Closing sockets~%")
(usocket:socket-close connection)
(usocket:socket-close socket)))))
It accepts any GET request to the web server on the specified port (by not handling it), and respons with:
HTTP/1.1 200 OK
Content-Type: text/html
Connection: close
Content-Length: 64
<!DOCTYPE HTML><html><body><h1>Valid Response</h1></body></html>
I admit I am abusing the protocol with this setup, but in my opinion it should technically work, and it does so both on a physically remote server and localhost with command line tools like curl
and wget
, as well as with Firefox and Chrome on MacOS and Edge on Windows, but not Safari. Safari simply can't establish a connection with the error message:
Safari Can’t Connect to the Server
Safari can’t open the page “mysite.net:8080” because Safari can’t connect to the server “mysite.net”.
I have tried binding *hostname*
to the name address, ipv4 address, and all ipv4 and ipv6 interfaces. Safari Technology Preview could at one point render the response from the remote server, but failed on further tries. I suspect Safari sends multiple requests (maybe tries https first - server is http), and the server closes the connection before Safari is ready to handle the response, but I really don't know. So my question is this: What is the absolutely minimal requirement for Safari to handle the response from a web server? It seems to be more sensitive or restrictive than all other tools and browsers.
I ran tcpdump port 8080
for Chrome (working), Firefox (working) and Safari (not working), and while I can't see any obvious error (beyond my abilities), it seems to support the notion that it is a premature closed connection issue. I also notice there is no GET request for Safari, which is present in Chrome and Firefox. For clarity I have renamed timestamps ts
and the client and server names client
and host
respectively.
ts IP client > host: Flags [SEW], seq 534012909, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 1451219412 ecr 0,sackOK,eol], length 0
ts IP host > client: Flags [S.E], seq 657848768, ack 534012910, win 65160, options [mss 1460,sackOK,TS val 3784271166 ecr 1451219412,nop,wscale 7], length 0
ts IP client > host: Flags [.], ack 1, win 2058, options [nop,nop,TS val 1451219440 ecr 3784271166], length 0
ts IP client > host: Flags [P.], seq 1:524, ack 1, win 2058, options [nop,nop,TS val 1451219440 ecr 3784271166], length 523: HTTP: GET / HTTP/1.1
ts IP host > client: Flags [P.], seq 1:150, ack 1, win 510, options [nop,nop,TS val 3784271195 ecr 1451219440], length 149: HTTP: HTTP/1.1 200 OK
ts IP host > client: Flags [F.], seq 150, ack 1, win 510, options [nop,nop,TS val 3784271195 ecr 1451219440], length 0
ts IP client > host: Flags [.], ack 150, win 2056, options [nop,nop,TS val 1451219468 ecr 3784271195], length 0
ts IP client > host: Flags [.], ack 151, win 2056, options [nop,nop,TS val 1451219468 ecr 3784271195], length 0
ts IP host > client: Flags [R], seq 657848769, win 0, length 0
ts IP client > host: Flags [F.], seq 524, ack 151, win 2056, options [nop,nop,TS val 1451219468 ecr 3784271195], length 0
ts IP host > client: Flags [R], seq 657848919, win 0, length 0
ts IP host > client: Flags [R], seq 657848919, win 0, length 0
ts IP client > host: Flags [SEW], seq 1710339549, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 3288928666 ecr 0,sackOK,eol], length 0
ts IP host > client: Flags [R.], seq 0, ack 1710339550, win 0, length 0
ts IP client > host: Flags [SEW], seq 626413754, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 3625754178 ecr 0,sackOK,eol], length 0
ts IP host > client: Flags [S.E], seq 1881453667, ack 626413755, win 65160, options [mss 1460,sackOK,TS val 3784031794 ecr 3625754178,nop,wscale 7], length 0
ts IP client > host: Flags [.], ack 1, win 2058, options [nop,nop,TS val 3625754209 ecr 3784031794], length 0
ts IP host > client: Flags [P.], seq 1:150, ack 1, win 510, options [nop,nop,TS val 3784031824 ecr 3625754209], length 149: HTTP: HTTP/1.1 200 OK
ts IP host > client: Flags [F.], seq 150, ack 1, win 510, options [nop,nop,TS val 3784031824 ecr 3625754209], length 0
ts IP client > host: Flags [.], ack 150, win 2056, options [nop,nop,TS val 3625754239 ecr 3784031824], length 0
ts IP client > host: Flags [.], ack 151, win 2056, options [nop,nop,TS val 3625754239 ecr 3784031824], length 0
ts IP client > host: Flags [P.], seq 1:409, ack 151, win 2056, options [nop,nop,TS val 3625754895 ecr 3784031824], length 408: HTTP: GET / HTTP/1.1
ts IP client > host: Flags [F.], seq 409, ack 151, win 2056, options [nop,nop,TS val 3625754895 ecr 3784031824], length 0
ts IP host > client: Flags [R], seq 1881453818, win 0, length 0
ts IP host > client: Flags [R], seq 1881453818, win 0, length 0
ts IP client > host: Flags [SEW], seq 2577772978, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 1558328301 ecr 0,sackOK,eol], length 0
ts IP host > client: Flags [S.E], seq 4186102721, ack 2577772979, win 65160, options [mss 1460,sackOK,TS val 3784121701 ecr 1558328301,nop,wscale 7], length 0
ts IP client > host: Flags [.], ack 1, win 2058, options [nop,nop,TS val 1558328335 ecr 3784121701], length 0
ts IP host > client: Flags [P.], seq 1:150, ack 1, win 510, options [nop,nop,TS val 3784121729 ecr 1558328335], length 149: HTTP: HTTP/1.1 200 OK
ts IP host > client: Flags [F.], seq 150, ack 1, win 510, options [nop,nop,TS val 3784121729 ecr 1558328335], length 0
ts IP client > host: Flags [.], ack 151, win 2056, options [nop,nop,TS val 1558328364 ecr 3784121729], length 0
ts IP client > host: Flags [F.], seq 1, ack 151, win 2056, options [nop,nop,TS val 1558328364 ecr 3784121729], length 0
ts IP host > client: Flags [.], ack 2, win 510, options [nop,nop,TS val 3784121758 ecr 1558328364], length 0
ts IP client > host: Flags [SEW], seq 3648579004, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 2532260604 ecr 0,sackOK,eol], length 0
ts IP host > client: Flags [R.], seq 0, ack 3648579005, win 0, length 0
ts IP client > host: Flags [SEW], seq 650543579, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 1158948454 ecr 0,sackOK,eol], length 0
ts IP host > client: Flags [R.], seq 0, ack 650543580, win 0, length 0
ts IP client > host: Flags [SEW], seq 698782494, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 772224008 ecr 0,sackOK,eol], length 0
ts IP host > client: Flags [R.], seq 0, ack 698782495, win 0, length 0
Removing (usocket:socket-close socket)
enables Safari to render the response. It will do so automatically wihout actively reloading the page. It introduces a slew of other issues with the server that must be resolved. I will post a working solution when these are weeded out.
Turns out Usocket provides macros for both the listener and connections that weeds out most of the aforementioned issues. You just need to make sure the http-request and response can be handled by both the client and the server. You can't use usocket's character encoding (UTF-8 on SBCL) for http-requests (ISO 8859-1/Latin-1), so you need to make sure the connection element-type
is 8-bit, and layer usocket's binary stream into a http-compliant character stream. This can easily be done using flexi-streams
with a proper external-format
.
(require :usocket)
(require :flexi-streams)
(defun web-server (port)
(usocket:with-socket-listener (socket "mysite.net" port)
(loop
(usocket:with-connected-socket (connection (usocket:socket-accept socket :element-type '(unsigned-byte 8)))
(let ((stream (flexi-streams:make-flexi-stream (usocket:socket-stream connection)
:external-format :latin-1)))
(html-response stream))))))
Safari won't render the response unless you provide content-length
of the response html, but that's the only absolutely necessary requirement.
(defun html-response (stream)
(princ "HTTP/1.1 200 OK
Content-Length: 64
<!DOCTYPE HTML><html><body><h1>Valid Response</h1></body></html>" stream)
(force-output stream))
The next natural step would be to actually handle the request. One note here is that Safari will insist on trying a TLS-handshake even if you explicitly use http, so whenever you specify a new request-target in Safari, your host will receive an encrypted request in addition to a non-encrypted one. None of the other browsers do that.