Search code examples
servletsracketstatic-files

Racket servlet serve static files


I want to try and code some blog or personal website or local web service in Racket. I've done the tutorial here and then I found this mailing list entry. In addition to that, I used the information from the documenation. URL based dispatch seems way easier than the tutorial, so I went for that and got the following code:

#lang racket

(provide/contract
  (start (-> request? response?)))

(require
  web-server/templates
  web-server/servlet-env
  web-server/servlet
  web-server/dispatch)

;; =====
;; STATE
;; =====
(define (get-vocabulary-for-topic topic)
  ;; for now always returns the same
  (list
    (list "sich fuer eine Person entscheiden" "xuǎnzé" "32" "选择")
    (list "teilnehmen" "cānyù" "14" "参与")
    (list "die Wahl" "dàxuǎn" "43" "大选")))

;; ======================
;; APPS HANDLING REQUESTS
;; ======================

(define (vocabulary-app request topic)
  (response/full
    200 #"OK"
    (current-seconds) TEXT/HTML-MIME-TYPE
    empty
    (list (string->bytes/utf-8 (render-vocabulary-page topic)))))

(define (vocabulary-overview-app request)
  (response/xexpr
    `(html
       (head (title "Vocabulary Overview")
             (link ((rel "stylesheet") (href "css/general.css") (type "text/css"))))
       (body (p "This is an overview of vocabulary pages.")))))

(define (overview-app request)
  (response/full
    200 #"OK"
    (current-seconds) TEXT/HTML-MIME-TYPE
    empty
    (list (string->bytes/utf-8 (render-overview-page)))))

;; ===============
;; RENDERING STUFF
;; ===============

(define (render-vocabulary-page topic)
  (let
    ([vocabulary (get-vocabulary-for-topic topic)])
    (let
      ([content (render-vocabulary-table vocabulary)]
        [page-title "Vocabulary"]
        [special-css-imports
          (render-css-include "css/vocabulary-table.css")]
        [special-js-imports ""]
        [header ""]
        [footer ""]
        [navigation ""])
      (include-template
        "templates/base.html"))))

(define (render-vocabulary-table vocabulary)
  (let
    ([table-headers (list "German" "Pinyin" "Tones" "Chinese")]
      [word-list vocabulary])
    (include-template "templates/vocabulary-table.html")))

(define (render-overview-page)
  (let
    ([content
       (let
         ([subpage-titles (list "vocabulary")])
         (include-template "templates/overview.html"))]
      [page-title "Overview"]
      [special-css-imports ""]
      [special-js-imports ""]
      [header ""]
      [footer ""]
      [navigation ""])
    (include-template
      "templates/base.html")))

(define (render-css-include path)
  (let
    ([path path])
    (include-template "templates/css-link.html")))


;; ====================
;; ROUTES MANAGING CODE
;; ====================
(define (start request)
  ;; for now only calling the dispatch
  ;; we could put some action here, which shall happen before dispatch
  (blog-dispatch request))

(define-values (blog-dispatch blug-url)
  (dispatch-rules
    [("index") overview-app]
    [("vocabulary") vocabulary-overview-app]
    [("vocabulary" (string-arg)) vocabulary-app]))

(define (respond-unknown-file req)
  (let
    ([content (include-template "templates/unknown-file.html")]
      [page-title "unknown file"]
      [special-css-imports ""]
      [special-js-imports ""]
      [header ""]
      [footer ""]
      [navigation ""])
    (response/full
      404 #"ERROR"
      (current-seconds) TEXT/HTML-MIME-TYPE
      empty
      (list
        (string->bytes/utf-8
          (include-template "templates/base.html"))))))

;; ===========================
;; ADDED FOR RUNNING A SERVLET
;; ===========================
(serve/servlet
  start
  #:servlet-path "/index"  ; default URL
  #:extra-files-paths (list (build-path (current-directory) "static"))  ; directory for static files
  #:port 8000 ; the port on which the servlet is running
  #:servlet-regexp #rx""
  #:launch-browser? true  ; should racket show the servlet running in a browser upon startup?
  ;; #:quit? false  ; ???
  #:listen-ip false  ; the server will listen on ALL available IP addresses, not only on one specified
  #:server-root-path (current-directory)
  #:file-not-found-responder respond-unknown-file)

This works fine so far, except that any file in my static directory, actually in subdirectories of the static directory, namely css cannot be found, when I navigate to any "subpages".

What I mean by that is, that a "main page" would be something like localhost:8000/index or localhost:8000/vocabulary and a subpage would be a page like localhost:8000/vocabulary/something.

It seems that the template rendering gets it wrong, does not always access the static directory from the root of the application directory, but instead only looks at localhost:8000/vocabulary/css/general.css, while it should look at localhost:8000/css/general.css, which it does, when I go to any "main page".

Here is a screenshot of this on a main page:

enter image description here

And here it is on a subpage:

enter image description here

So the static directory seems to change depending on what URL is visited. At first I thought I had finally understood how to serve static files, but it seems I have not and I don't know how to fix the problem in the best way.

Here is my directory structure:

/home/xiaolong/development/Racket/blog2
  - static/
    - css/
      general.css
      vocabulary-table.css
    + img/
    + js/
  - templates/
    base.html
    css-link.html
    js-link.html
    overview.html
    unknown-file.html
    vocabulary-table.html
  blog-demo.rkt
  blog.rkt

How can I fix the paths for static files?

I want to be able to simply type css/something.css in the code and no matter which app or route, it should serve the files and I don't want to change the include path depending on what route I am treating in the code.

The command I use for running the server is simply:

racket blog.rkt

from the root directory of the project.


Solution

  • It sure looks to me like this is an HTML problem, not a racket problem. Specifically, it looks like you need to specify these as absolute paths, not as relative ones. So, in your code, you might want to change

    (define (vocabulary-overview-app request)
      (response/xexpr
        `(html
           (head (title "Vocabulary Overview")
                 (link ((rel "stylesheet") (href "css/general.css") (type "text/css"))))
           (body (p "This is an overview of vocabulary pages.")))))
    

    to

    (define (vocabulary-overview-app request)
      (response/xexpr
        `(html
           (head (title "Vocabulary Overview")
                 (link ((rel "stylesheet") (href "/css/general.css") (type "text/css"))))
           (body (p "This is an overview of vocabulary pages.")))))
    

    (note the leading slash in the path to the css file)

    Does this answer your question?