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:
And here it is on a subpage:
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.
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?