Racket servlet提供静态文件服务

3

我想尝试使用Racket编写一些博客、个人网站或本地网络服务。我已经完成了这里的教程,然后我发现了这篇邮件列表条目。除此之外,我还使用了文档中的信息。基于URL的调度似乎比教程更容易,所以我选择了这种方式,并得到了以下代码:

#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)

到目前为止,这很好运作,除了在我导航到任何“子页面”时,我的静态目录中的任何文件,实际上是在“静态”目录的子目录中,即“css”无法找到。
我的意思是,“主页”将是像localhost:8000/indexlocalhost:8000/vocabulary这样的东西,而子页面将是像localhost:8000/vocabulary/something这样的页面。
似乎模板渲染出了问题,没有始终从应用程序目录的根目录访问静态目录,而是只查看localhost:8000/vocabulary/css/general.css,而当我转到任何“主要页面”时,它应该查看localhost:8000/css/general.css
这里是一个主页的截图:

enter image description here

而这是在子页面上的:

enter image description here

所以静态目录似乎会根据访问的URL而改变。起初,我以为自己终于明白了如何提供静态文件,但似乎并没有,我不知道如何以最好的方式解决这个问题。

这是我的目录结构:

/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

我该如何修复静态文件路径?

我希望能够在代码中简单地键入css/something.css,而无论是哪个应用程序或路由,它都应该提供文件,并且我不想根据我正在处理的代码路由更改包含路径。

我用于运行服务器的命令很简单:

racket blog.rkt

从项目的根目录开始。
2个回答

3

这里的问题不在于理解绝对路径与相对路径的概念、基本URL的概念,以及它们如何共同构成给定资源的完全合格URL。

RFC 3986 是关于如何构建和处理URL的参考文献。需要注意的是,它实际上使用统一资源标识符(URI)这个术语,而不是URL。它取代了先前使用旧的、已废弃的URL术语的文件。

但为了与原始问题保持一致,我将继续使用URL术语。

重要的是要理解RFC 3986第4.1节中相对引用的概念,它可以编写为相对路径或绝对路径。

在这里,解释URL的代理是您的浏览器,它只是使用RFC 3986作为URL工作原理的基础来处理您的服务器返回给它的URL。

一个完全合格的URL包括协议、主机域名以及端口号(如果需要),以及给出文档或资源位置的路径,相对于协议和主机域名。

所以在你的情况下,CSS文件的正确完全合格URL应该是http://localhost:8000/css/general.css

来自RFC 3986第3节 “http”和“localhost:8000”这两个部分分别被称为协议方案和授权组件,形成了您网站的主要标识符,“http:// localhost:8000”。该标识符后面的任何内容都称为路径组件,并且在查询或URL片段组件之前,它们分别以“?”或“#”开头。

路径组件旨在模拟UNIX文件路径约定并构建树形层次结构,使您能够相对于您网站的标识符表达某些给定资源的位置。您需要考虑一些规则。

相对引用解析在RFC 3986第5节中给出。关键概念是基本URL,它与相对引用一起用于构建完全合格的URL。

当您的浏览器访问位于http://localhost:8000/vocabulary/something的页面时,它会看到css/general.css,这是一个相对引用,写成相对路径。按照解析算法,基本URL被视为最初用于访问该页面的URL,即http://localhost:8000/vocabulary/something
由于末尾没有斜杠,因此在右侧最右边的斜杠右侧的路径子组件被删除,导致您得到http://localhost:8000/vocabulary/。然后将URL相对引用css/general.css附加到重写的基本URL路径的末尾,从而得到http://localhost:8000/vocabulary/css/general.css,当浏览器尝试检索不存在的资源时,会导致404错误。
如果浏览器看到的是/css/general.css,这是一个相对路径,但写成了绝对路径。基础URL仍然是http://localhost:8000/vocabulary/something。由于相对路径是绝对路径,所以在网站标识符右侧的整个路径都会被删除,只剩下http://localhost:8000。然后将相对引用附加到重写后的基本URL上,得到正确的完全限定URL:http://localhost:8000/css/general.css
请注意,如果您访问的页面具有以下URL:http://localhost:8000/vocabulary/something/,其中有一个尾随斜杠。那么任何相对路径引用(如css/general.css)都将合并构造出完整的URL:http://localhost:8000/vocabulary/something/css/general.css,因为按照算法,浏览器看到原始基本URL末尾有一个斜杠,不会删除最后一个路径子组件(或者删除位于尾随斜杠右侧的空子组件)。

一篇长而非常清晰、准确的解释。如果我可以接受多个答案,我也会接受这个。谢谢。 - Zelphir Kaltstahl

2

我觉得这是一个HTML问题,而不是Racket的问题。具体来说,看起来你需要将这些路径指定为绝对路径,而不是相对路径。因此,在您的代码中,您可能需要进行更改。

(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 (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.")))))

请注意css文件路径中的前导斜杠。

这个回答解决了你的问题吗?


哈!是的,它加载了!但我不明白为什么前导斜杠是绝对路径。这是Racket servlet策略吗?我认为绝对路径是/home/user/development/Racket/blog/.../style.css等等。网络服务器是否总是将带有前导斜杠的文件解释为项目绝对文件? - Zelphir Kaltstahl
1
你的网络浏览器是解释斜杠的工具。"/foo/bar" 的意思是“发送 /foo/bar”,而 "foo/bar" 的意思是“用 foo/bar 替换当前 URL 尾部并发送它”。 - Jay McCarthy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接