Search code examples
httpfirefoxlocalhostsbclusocket

How to make Firefox render HTML generated on a local server written in Common Lisp?


My goal is something akin to Chapter 12 of Land Of Lisp: using the usocket package in SBCL I want to write a local server that I can connect to with my browser. I started from a helpful example:

#!/usr/bin/sbcl --script

(load "~/quicklisp/setup.lisp")
(require :usocket)

(defparameter *sock* nil)
(defparameter *sock-listen* nil)
(defparameter *my-stream* nil)

(defun communi ()
  ;; bind socket
  (setf *sock* (usocket:socket-listen "127.0.0.1" 4123))

  ;; listen to incoming connections
  (setf *sock-listen* (usocket:socket-accept *sock* :element-type 'character))

  ; open stream for communication
  (setf *my-stream* (usocket:socket-stream *sock-listen*))

  ;; print message from client
  (format t "~a~%" (read *my-stream*))
  (force-output)

  ;; send answer to client
  (format *my-stream* "<html><body>Server will write something exciting here.</body></html>")
  (force-output *my-stream*))

;; call communication and close socket, no matter what
(unwind-protect (communi)
  (format t "Closing socket connection...~%")
  (usocket:socket-close *sock-listen*)
  (usocket:socket-close *sock*))

When I run this script from the command line (Ubuntu 22.04 LTS), I am able to connect to http://127.0.0.1:4123/ with Firefox. However, instead of rendering the HTML, Firefox only displays its source code:

<html><body>Server will write something exciting here.</body></html>

Question: How can I persuade Firefox to render the page instead of displaying the HTML source?

From the answers here and here I assume that I need to set text/html in the HTTP headers. This is corroborated by the curl output

dominik@computer:~$ curl 127.0.0.1:4123 --http0.9 --verbose
*   Trying 127.0.0.1:4123...
* Connected to 127.0.0.1 (127.0.0.1) port 4123 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:4123
> User-Agent: curl/8.1.2
> Accept: */*
> 
* Closing connection 0
<html><body>Server will write something exciting here.</body></html>

which is markedly different from

dominik@computer:~$ curl example.com -I
HTTP/1.1 200 OK
Accept-Ranges: bytes
Age: 256982
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Sun, 26 May 2024 18:05:14 GMT
Etag: "3147526947"
Expires: Sun, 02 Jun 2024 18:05:14 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECAcc (dcd/7D7F)
X-Cache: HIT
Content-Length: 1256

How can I set the content-type in the usocket package?


Solution

  • Since the server you are implementing is supposed to be a http server, you should supply the response headers then an empty line before the payload. Eg.

    (let ((content "<html><body>Server will write something exciting here.</body></html>"))
      (format *my-stream* 
              "HTTP/1.1 200 OK~%~&~
               content-type: text/html~%~&~
               content-length: ~D~%~&~
               ~%~&~
               ~a"
              (length content)
              content))
    

    None of the response headers are required, but as you have experienced they are needed for the browser to handle it as html.