September 8, 2024
By: 马海强

clojure-web中的resources

  1. Ring(一定要了解下)
    1. 案例一:dev模式可以,打包后找不到文件
    2. 案例二:写个接口把resources里的文件读出来
    3. 案例三:把静态文件让jar服务带起来
    4. 案例四:用jar代理本地指定目录下的任意文件
  2. 推荐资料

不管是springboot项目,还是我们每天都在玩的luminus项目,做web项目免不了处理一些资源文件,本篇介绍原因和常用案例。

Ring(一定要了解下)

Ring是一个Clojure Web程序库,有点类似与Java的servlet。 公司目前用的都是用luminus 框架new出来的web项目,也是用Ring把我们的代码编译到一个java servlet运行的,所以必须去了解(有中文版)。但是本文只说Resources的使用。

Ring框架里对静态资源的文档介绍确实不多,在静态资源(Static Resources)

案例一:dev模式可以,打包后找不到文件

一些新手经常会说:“我本地没问题”。是的,本地确实没问题,所谓的没问题的代码是这样的, 比如要拷贝resources里的一个文件

(with-open [in (io/input-stream (io/resource "abc/file.dat"))]   ;; resources/abc/file.dat
    (io/copy in (io/file "/path/to/your/target/path")))

这段代码本地是没问题,但是以jar方式运行就会找不到文件。

这要了解下打包干了啥,有点深。 简单说就是本地运行时确实这个路径下有这个文件。但是打成jar以后可以解压看看,这个路径不是你脑子的那个路径了。

  • 原因:jar本身就是一个文件,不是一个目录,代码中拼接的文件路径变成了xxx.jar!abc/file.dat,这是一个伪地址,解压下jar也可以看到,确实不是这样的,所以找不到路径。

那怎么写呢?就是用io/resource代替赤裸裸的路径,下面这个函数接收一个路径,只要是项目根目录下resouces里的地址,就不会有问题。

(defn- reader-excel-template
  "读入模板"
  [t-path]
  (let [resource (io/resource t-path)
        file-name (str (common-utils/uuid) ".xlsx")]
    (with-open [in (io/input-stream resource)]
      (io/copy in (io/file file-name)))
    (io/as-file file-name)))

案例二:写个接口把resources里的文件读出来

比如我们resources里有这些文件。

$ tree -c -L 3
.
├── doc
├── migrations
├── public
│   ├── img
│   │   ├── img_index.png
│   │   ├── img_index_icon.png
│   │   └── warning_clojure.png
└── sql
    ├── queries.sql

写一个接口:

["/download"
     {:get {:summary "downloads a file"
            :swagger {:produces ["image/png"]}
            :handler (fn [_]
                       {:status  200
                        :body    (-> "public/img/warning_clojure.png"
                                     (io/resource)
                                     (io/input-stream))})}}]

上面这个接口,可以是resources目录里的任意文件,只是我们按照常规放在了public目录里了。浏览器访问这个接口时,像pdf,图片,视频等浏览器会直接打开,其他不支持的格式,会开始下载。 如果格式明确,向让调用者知道类型,可以在handler指定类型,比如这个获取图片的,可以加:

  :headers {"Content-Type" "image/png"}

案例三:把静态文件让jar服务带起来

我们有个build好的前端项目,当然包括了index.html,js,css,images等。想要让jar启动时,作为server同时把这个前端项目,不需要使用nginx,python等。

  • 前端代码全部复制到resources/public目录下。 可能如下:
$ pwd
/Users/mahaiqiang/git/redcreation/chutian/src/clj-backend/resources/public

$ ll
total 352
-rw-r--r--  1 mahaiqiang  staff    96K Sep  8 16:00 256x256.icns
-rw-r--r--  1 mahaiqiang  staff   1.7K Sep  8 16:00 32x32.icns
-rw-r--r--  1 mahaiqiang  staff   1.3K Sep  8 16:00 asset-manifest.json
-rw-r--r--  1 mahaiqiang  staff   5.7K Sep  8 16:00 electron.js
-rw-r--r--  1 mahaiqiang  staff    40K Sep  8 16:00 icon.png
-rw-r--r--  1 mahaiqiang  staff   536B Sep  8 16:00 index.html
-rw-r--r--  1 mahaiqiang  staff   532B Sep  8 16:00 log.js
-rw-r--r--  1 mahaiqiang  staff   284B Sep  8 16:00 manifest.json
-rw-r--r--  1 mahaiqiang  staff   525B Sep  8 16:00 preload.js
-rw-r--r--  1 mahaiqiang  staff   1.8K Sep  8 16:00 product.js
drwxr-xr-x  5 mahaiqiang  staff   160B Sep  8 16:00 static

  • 配置handler,用warp-resource函数把静态资源加进来。
(:require [ring.middleware.resource :as resource])

(defn- async-aware-default-handler
  ([_] nil)
  ([_ respond _] (respond nil)))

;;静态资源放在resources/public/目录下.注意:index.html里引入的static前不能有斜线这个绝对路径
(defn- create-resource-handler []
  (resource/wrap-resource "/" {:root "resources/public"}))

(mount/defstate app-routes
  :start
  (ring/ring-handler
   (ring/router
    [(conj (base/service-routes)
           (base/swagger-routes)
           (api-public-routes)
           (api-routes)
           (api-callback-routes)
           (admin-routes)
           (admin-pulic-routes))])
   (ring/routes
    (create-resource-handler)
    (wrap-content-type (wrap-webjars async-aware-default-handler))
    (ring/create-default-handler))))

案例四:用jar代理本地指定目录下的任意文件

我们可能把本地一个目录当成了文件服务器,上传时上传到这个目录,上传以后查看当然也要能访问到。 Ring框架,也有静态资源(Static Resources),写了也不点进去看,搬过来吧.

Web应用经常需要服务静态内容,例如图片或者样式表.Ring提供了两个中间件函数来做这个事情.

一个是wrap-file.它服务来自当前文件系统一个目录的静态内容:

(use 'ring.middleware.file)
(def app
  (wrap-file your-handler "/var/www/public"))

另一个是wrap-resource.它服务来自JVM classpath下的静态内容:

(use 'ring.middleware.resource)
(def app
  (wrap-resource your-handler "public"))

改造下我们的项目,

(:require [ring.middleware.resource :as resource]
              [ring.middleware.webjars :refer [wrap-webjars]])

(mount/defstate app-routes
  :start
  (ring/ring-handler
   (ring/router
    [(conj (base/service-routes)
           (base/swagger-routes)
           (api-routes))])
   (ring/routes
    (wrap-file (ring/create-resource-handler {:path "/"})
               "/Users/mahaiqiang/Downloads/")  ;;把本地下载目录当成文件存储服务器
    (wrap-content-type (wrap-webjars (constantly nil)))
    (ring/create-default-handler))))

案例三和案例四可以同时使用,不冲突。

推荐资料

Ring 静态资源

Tags: web