Serve static resources using Clojure's Ring - clojure

I am learning how to create web apps using Clojure's Ring. I'm trying to serve a static .html file which contains a reference to a .css file through a <link> tag in its head part. The .css file is in the same directory as the index.html file I'm trying to serve, however, the .css file is not being loaded (I get an error with a 500 status code with a reason phrase of:
Reponse map is nil
This is my code below:
(defroutes approutes
(GET "/" reqmap
(resource-response "index.html")))
(def server (run-jetty #'approutes {:join? false, :port 3000}))
What am I missing here? and how can I serve an html file that has references to other files (.css, .js, .jpeg, etc)? I've had some luck (although I can't completly explain why) using Ring's wrap-resource middleware in the ring.middleware.resource namespace although that function is to be used only when a request map matches a static resource (and as you can see, the route "/" doesn't match a resource per-se).
Thank you.

You need to add one bit of middleware that will take care of serving static files from a folder you can select, something like the following:
;; Add to your (ns :requires at the top of the file)
(:require [ring.middleware.resource :refer wrap-resource])
;; more of your existing code here...
(def app
(wrap-resource approutes "public")) ;; serve static files from "resources/public" in your project
(def server (run-jetty #'app {:join? false, :port 3000}))
This should be enough to get you going, so if you start your server, you should be able to open files in addresses such as http://localhost:3000/style.css which should be found in public/resources/style.css in your project. Any other static files should work. There's a guide in the Ring wiki in Github that explains two similar functions (middlewares) that you can use.
Next, in your index.html file you should be able to reference other files such as CSS files, like the following:
<html>
<head>
<link rel="stylesheet" href="css/style.css">
</head>
<!-- and son on... -->
Here's a sample project I wrote a while ago that does show the same ideas: https://github.com/dfuenzalida/antizer-demo
UPDATE
I've done a quick run from scratch, this should help you find what the problem is.
Created a new project with:
lein new app hello-ring
Actually, the name is a bit misleading because we'll be using both Ring and Compojure.
We'll update the file project.clj with the following:
(defproject hello-ring "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
:url "https://www.eclipse.org/legal/epl-2.0/"}
:dependencies [[org.clojure/clojure "1.10.0"]
[compojure "1.6.1"]
[ring/ring-core "1.6.3"]
[ring/ring-jetty-adapter "1.6.3"]]
:main ^:skip-aot hello-ring.core
:target-path "target/%s"
:profiles {:uberjar {:aot :all}})
Now let's edit the file src/hello_ring/core.clj, its contents should be like the following:
(ns hello-ring.core
(:require [ring.adapter.jetty :refer [run-jetty]]
[ring.middleware.resource :refer [wrap-resource]]
[ring.util.response :refer [resource-response]]
[compojure.core :refer [defroutes GET]])
(:gen-class))
(defroutes approutes
(GET "/" []
(resource-response "public/index.html")))
(def app
(-> approutes
(wrap-resource "public"))) ;; files from resources/public are served
(defn server []
(run-jetty app {:join? false, :port 3000}))
(defn -main [& args]
(server))
Finally let's create a couple of static resources. Create the folder structure resources/public/css and in the file resources/public/css/style.css enter the following:
body {
font-face: sans-serif;
padding-left: 20px;
}
... and a basic HTML file in resources/public/index.html with the following:
<html>
<head>
<title>It works!</title>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<h1>it works!</h1>
</body>
</html>
... that's it. The HTML file will attempt to load the CSS file. Save everything and check if it matches the following folder structure:
.
├── CHANGELOG.md
├── doc
│   └── intro.md
├── LICENSE
├── project.clj
├── README.md
├── resources
│   └── public
│   ├── css
│   │   └── style.css
│   └── index.html
├── src
│   └── hello_ring
│   └── core.clj
└── test
└── hello_ring
└── core_test.clj
Now you should be able to start the service from the command line with:
lein run
The output will look like the following:
$ lein run
2019-08-05 23:46:14.919:INFO::main: Logging initialized #1221ms
2019-08-05 23:46:16.281:INFO:oejs.Server:main: jetty-9.2.21.v20170120
2019-08-05 23:46:16.303:INFO:oejs.ServerConnector:main: Started ServerConnector#2c846d55{HTTP/1.1}{0.0.0.0:3000}
2019-08-05 23:46:16.303:INFO:oejs.Server:main: Started #2606ms
Connect to the server in http://0.0.0.0:3000/ ... You should see your page in the browser with the It works! message and a basic CSS reset. In the console you're most likely to see some exceptions because the browser attempts to load the file /favicon.ico from your server, which does not exist (you can create it as an empty file for now).
I hope this helps.

Related

Serving image files from src directory - 404 error

I created a site in Clojure, Pedestal and Boot Cljs. A tutorial I was using was this http://pedestal.io/guides/hello-world-content-types
Then I was trying to add an image to the site that was not shown in the tutorial. I put the image a455.jpg to src folder. I noticed that in a file build.boot a :resources-paths key is set to src folder.
s#lokal:~/Dropbox$ tree ~/Dropbox/clojure-boot-heists//home/s/Dropbox/clojure-boot-heists/
├── build.boot
├── favicon.ico
├── profile.boot
├── project.clj
├── src
│   ├── a455.jpg
│   ├── favicon.ico
│   └── main.clj
└── target
└── classes
--
s#lokal:~/Dropbox$ cat ~/Dropbox/clojure-boot-heists/build.boot
(set-env!
:resource-paths #{"src"}
:dependencies '[[onetom/boot-lein-generate "0.1.3" :scope "test"]
[io.pedestal/pedestal.service "0.5.1"]
[io.pedestal/pedestal.route "0.5.1"]
[io.pedestal/pedestal.jetty "0.5.1"]
[org.clojure/data.json "0.2.6"]
[org.slf4j/slf4j-simple "1.7.21"]])
(require 'boot.lein)
(boot.lein/generate)
s#lokal:~/Dropbox$
So the src folder in this application is something like a public folder in common web applications. Isn't it?
I tested it web browser, curl and Clojure response-for function. The site worked but without the image. Here you are curl results:
s#lokal:~/Dropbox$ curl -si -H "Accept: text/html" http://localhost:8890
HTTP/1.1 200 OK
Date: Mon, 14 Jan 2019 23:22:09 GMT
Strict-Transport-Security: max-age=31536000; includeSubdomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Content-Type: text/html
Transfer-Encoding: chunked
Server: Jetty(9.3.8.v20160314)
<!doctype html>
<html lang='pl'>
<head>
<meta charset='utf-8'>
<title>Hi world!</title>
</head>
<body>
<img src='a455.jpg'>
</body>
</html>
--
s#lokal:~/Dropbox$ curl -si -H "Accept: text/html" http://localhost:8890/a455.jpg
HTTP/1.1 404 Not Found
Date: Mon, 14 Jan 2019 23:22:27 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
Server: Jetty(9.3.8.v20160314)
And here you are the application
(ns main
(:require [clojure.data.json :as json]
[io.pedestal.http :as http]
[io.pedestal.http.route :as route]
[io.pedestal.http.content-negotiation :as conneg]))
(defn ok [body]
{:status 200 :body body})
(defn not-found []
{:status 404 :body "Not found\n"})
(defn main-for [nm]
(cond
(unmentionables nm) nil
(empty? nm) "<!doctype html>
<html lang='pl'>
<head>
<meta charset='utf-8'>
<title>Hi world!</title>
</head>
<body>
<img src='a455.jpg'>
</body>
</html>
"
:else (str "Hello, " nm "\n")))
(defn respond-main [request]
(let [nm (get-in request [:query-params :name])
resp (main-for nm)]
(if resp
(ok resp)
(not-found))))
(def supported-types ["text/html" "application/edn" "application/json" "text/plain"])
(def content-neg-intc (conneg/negotiate-content supported-types))
(defn accepted-type
[context]
(get-in context [:request :accept :field] "text/plain"))
(defn transform-content
[body content-type]
(case content-type
"text/html" body
"text/plain" body
"application/edn" (pr-str body)
"application/json" (json/write-str body)))
(defn coerce-to
[response content-type]
(-> response
(update :body transform-content content-type)
(assoc-in [:headers "Content-Type"] content-type)))
(def coerce-body
{:name ::coerce-body
:leave
(fn [context]
(cond-> context
(nil? (get-in context [:response :headers "Content-Type"]))
(update-in [:response] coerce-to (accepted-type context))))})
(def routes
(route/expand-routes
#{["/" :get [coerce-body content-neg-intc respond-main] :route-name :main]}))
(def service-map
{::http/routes routes
::http/type :jetty
::http/port 8890})
(defn start []
(http/start (http/create-server service-map)))
(defonce server (atom nil))
(defn start-dev []
(reset! server
(http/start (http/create-server
(assoc service-map
::http/join? false)))))
(defn stop-dev []
(http/stop #server))
(defn restart []
(stop-dev)
(start-dev))
How to make the image display on the site?
Typically images and other items are put in the ./resources folder. They may also be in subfolders, depending on the exact configuration in lein or boot.
Here is a sample lein project: http://git#gitlab.com:pedestal/hello.git
with a sample file ./resources/music.txt file. These are typically read with (:require [clojure.java.io :as io]) as follows:
(slurp (io/resource "music.txt"))
as you can see from the echo-intc in src/hello/core.clj. You can see the file access in action by issuing a lein run and then navigating your browser to localhost:8890

simple boot project not working with cljs and async

I have a very simple project. I created the simplest ever project with boot and cljs. It compiled successfully and I was able to see the correct log in the html. When I added a basic support for async I got the message:
No such namespace: clojure.core.async, could not locate clojure/core/async.cljs, clojure/core/async.cljc, or Closure namespace "clojure.core.async"
The project has just the following structure:
exemplo_cljs
html
index.html
src
exemplo_cljs
core.cljs
build.boot
The contents of index.html:
<!doctype html>
<html lang="en">
<head>
<title>Hello</title>
</head>
<body>
<h2>Hello</h2>
<script src="main.js"></script>
</body>
</html>
core.cljs
(ns exemplo-cljs.core
(:require [clojure.core.async :as async]))
(def exercise (async/chan))
;; enable cljs to print to the JS console of the browser
(enable-console-print!)
;; print to the console
(println "Hello, World 4!")
and build.boot
(set-env!
:source-paths #{"src"}
:resource-paths #{"html"}
:dependencies '[[adzerk/boot-cljs "1.7.170-3"]
[org.clojure/core.async "0.2.371"]])
(require '[adzerk.boot-cljs :refer [cljs]])
The original working project had the same basic structure except for the require and def of a channel in the core.cljs file and the dependency added in the build.boot
That's because in ClojureScript, core.async lives under cljs.core.async rather than clojure.core.async.
So you just need to change your ns form to:
(ns exemplo-cljs.core
(:require [cljs.core.async :as async]))

Including .clj file in ClojureScript project

I'm building a Node.js app in ClojureScript and testing out macros.
Directory structure:
├── project.clj
└── src
   └── lists
   ├── core.cljs
   └── lib.clj
project.clj:
(defproject lists "0.1.0-SNAPSHOT"
:source-paths ["src/"]
:dependencies [[org.clojure/clojure "1.7.0"]]
:plugins [[lein-cljsbuild "1.1.2"]]
:cljsbuild {:builds
[{:source-paths ["src"]
:compiler {:output-to "target/lists.js"
:optimizations :simple
:target :nodejs}}]})
src/lists/core.cljs:
(ns lists.core
(:require [lists.lib :as lib :include-macros true]))
(enable-console-print!)
(lib/defmain [& args]
(console.log "hello world"))
src/lists/lib.clj:
(ns lists.lib)
(defmacro defmain [& body]
`(set! *main-cli-fn* (fn ~#body)))
When I run lein cljsbuild once, I get a huge error traceback containing:
Caused by: clojure.lang.ExceptionInfo: No such namespace: lists.lib, could not locate lists/lib.cljs or lists/lib.cljc at line 1 src/lists/core.cljs {:file "src/lists/core.cljs", :line 1, :column 1, :tag :cljs/analysis-error}
The folder structure is right, and :source-paths is present in both the outer
defproject call and inner :cljsbuild :builds object. What's even weirder is
that sometimes it exits without printing anything. Anyone have any ideas?
cljsbuild is throwing an exception because it doesn't understand .clj files, it's looking for .cljs or .cljc files in the "src" directory. You have to rename lib.clj to lib.cljc check using cljc on the clojurescript wiki.

Clojure: refresh running web app when html files change

I've set up my project with lein-ring to allow hot code reload. It does work when I change any .clj file while the app is running...
How can I make it the same for change in any html, css and js files. (located in resources/public...)
Here is my project.clj set-up:
(defproject ...
:plugins [[lein-cljsbuild "1.0.4"]
[lein-ring "0.9.2"]]
:ring {:handler votepourca.server/handler
:auto-reload? true
:auto-refresh? true}
:resource-paths ["resources" "markup"]
:source-paths ["src/clj"]
...)
EDIT:
I am using Enlive, and apparently, it needs an extra ring wrapper to allow static file reloading: [com.akolov.enlive-reload "0.1.0"]
So in my server.clj/core.clj/handler.clj, I now have this and it works perfectly!
(:require
[ring.middleware.reload :refer [wrap-reload]]
[com.akolov.enlive-reload :refer [wrap-enlive-reload]])
...
(defn app [routes]
(-> routes
(wrap-params)
(wrap-reload)
(wrap-enlive-reload))))
Thank you to "Kolov" the author of this lib https://github.com/kolov/enlive-reload
Add :reload-paths in addition to :auto-reload?/:auto-refresh?. https://github.com/weavejester/lein-ring/blob/master/src/leiningen/ring/server.clj#L25

Lein ring server 404

I am setting up a web application building a route and handler with Ring and Compojure. Every time I try lein ring server I get a 404 Not Found. But I should see
Edit
After starting the server I am asked by IE to open or save the file. But Windows is not able to read the JSON file.
My project.clj looks like
(defproject web-viz
:dependencies [[org.clojure/clojure "1.4.0"]
[ring/ring-core "1.1.7"]
[ring/ring-jetty-adapter "1.1.7"]
[compojure "1.1.3"]
[hiccup "1.0.2"]]
:plugins [[lein-ring "0.8.3"]]
:ring {:handler web-viz.web/app})
and inside the src I have a file web.clj
(ns web-viz.web
(:require [compojure.route :as route]
[compojure.handler :as handler]
[clojure.string :as str])
(:use compojure.core
ring.adapter.jetty
[ring.middleware.content-type :only
(wrap-content-type)]
[ring.middleware.file :only (wrap-file)]
[ring.middleware.file-info :only
(wrap-file-info)]
[ring.middleware.stacktrace :only
(wrap-stacktrace)]
[ring.util.response :only (redirect)]))
(defroutes site-routes
(GET "/" [] (redirect "/data/census-race.json"))
(route/resources "/")
(route/not-found "Page not found"))
(def app (-> (handler/site site-routes)
(wrap-file "resources")
(wrap-file-info)
(wrap-content-type)))
There should be a file with the content above located at
web-viz/resources/public/data/census-race.json
You project is working for me. Here is how I structured the project
.
├── project.clj
├── resources
│   └── data
│   └── census-race.json
└── src
└── web_viz
└── web.clj
I don't see anything obviously wrong but the following looks unusual:
(def app (-> (handler/site site-routes)
(wrap-file "resources")
(wrap-file-info)
(wrap-content-type)))
From https://stackoverflow.com/a/22788463/894091:
You don't need any of the extra middleware like wrap-file,
wrap-file-info, or wrap-content-type, since compojure.route/resources
already does everything you need.
See if the following does the trick:
(def app
(handler/site app-routes))