Question

I am making a simple Clojure web-app to be deployed on Heroku consisting of one html and one css file. I created the file using the "lein new heroku MYAPP" command and am trying to modify it from a simple "hello world" to have it render an html file in another folder on startup. I have managed to get the html to load on a local host in my browser, but it is not being modified by the css when I do it. What do I need to change to get the css to modify the html to have it render properly in the browser and then deploy to heroku?

html:

<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="style.css" />
    </head>

    <body>
        <img id="sun" src="http://goo.gl/dEEssP">
        <div id='earth-orbit'>
            <img id="earth" src="http://goo.gl/o3YWu9">
        </div>
    </body>
</html>

project.clj

(defproject solar_system "1.0.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://solar_system.herokuapp.com"
  :license {:name "FIXME: choose"
            :url "http://example.com/FIXME"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [compojure "1.1.1"]
                 [ring/ring-jetty-adapter "1.1.0"]
                 [ring/ring-devel "1.1.0"]
                 [ring-basic-authentication "1.0.1"]
                 [environ "0.2.1"]
                 [com.cemerick/drawbridge "0.0.6"]]
  :uberjar-name "solar_system-standalone.jar"
  :min-lein-version "2.0.0"
  :plugins [[environ/environ.lein "0.2.1"]]
  :hooks [environ.leiningen.hooks]
  :profiles {:production {:env {:production true}}})

web.clj:

(ns solar_system.web
  (:require [compojure.core :refer [defroutes GET PUT POST DELETE ANY]]
            [compojure.handler :refer [site]]
            [compojure.route :as route]
            [clojure.java.io :as io]
            [ring.middleware.stacktrace :as trace]
            [ring.middleware.session :as session]
            [ring.middleware.session.cookie :as cookie]
            [ring.adapter.jetty :as jetty]
            [ring.middleware.basic-authentication :as basic]
            [cemerick.drawbridge :as drawbridge]
            [environ.core :refer [env]]))

(defn- authenticated? [user pass]
  ;; TODO: heroku config:add REPL_USER=[...] REPL_PASSWORD=[...]
  (= [user pass] [(env :repl-user false) (env :repl-password false)]))

(def ^:private drawbridge
  (-> (drawbridge/ring-handler)
      (session/wrap-session)
      (basic/wrap-basic-authentication authenticated?)))

(defroutes app
  (ANY "/repl" {:as req}
       (drawbridge req))
  (GET "/" []
       {:status 200
        :headers {"Content-Type" "text/html"}
        :body (slurp (io/resource "index.html"))})  
  (ANY "*" []
       (route/not-found (slurp (io/resource "404.html")))))

(defn wrap-error-page [handler]
  (fn [req]
    (try (handler req)
         (catch Exception e
           {:status 500
            :headers {"Content-Type" "text/html"}
            :body (slurp (io/resource "500.html"))}))))

(defn -main [& [port]]
  (let [port (Integer. (or port (env :port) 5000))
        ;; TODO: heroku config:add SESSION_SECRET=$RANDOM_16_CHARS
        store (cookie/cookie-store {:key (env :session-secret)})]
    (jetty/run-jetty (-> #'app
                         ((if (env :production)
                            wrap-error-page
                            trace/wrap-stacktrace))
                         (site {:session {:store store}}))
                     {:port port :join? false})))

;; For interactive development:
;; (.stop server)
;; (def server (-main))

here is the directory tree. The html and css files are in resources (with the error.html files), web.clj is in src/solar_system and project.clj is in the root folder:

solar_system
├── resources
├── src
│   └── solar_system
├── target
│   ├── classes
│   └── stale
└── test
    └── solar_system
Was it helpful?

Solution

I don't know why the lein new heroku _ template doesn't do this for you.

Use compojure.route/resources to tell the handler where to look for static files.

(defroutes app
  (ANY "/repl" ...)
  (GET "/" [] ...)  
  (route/resources "/")
  (ANY "*" [] ...))

Now, if you visit http://example.com/style.css, it will expect resources/public/style.css.

Aside: It's better to serve static assets from resources/public/ rather than resources/ because you may want to have resources/secrets.txt without anybody being able to access it.

OTHER TIPS

You have a css file, but no route to serve it! Your browser is begging for a CSS file, but the server says "404, man, never heard of that file." You can either add another route like the one for index.html, or you can use compojure's resources or files route to serve all files in a directory.

As the others have said, you're missing a route to your css file.

(defroutes app
  (ANY "/repl" {:as req}
       (drawbridge req))
  (GET "/" []
       {:status 200
        :headers {"Content-Type" "text/html"}
        :body (slurp (io/resource "index.html"))})  
  (route/resources "/") ; special route for serving static files like css
                        ; default root directory is resources/public/
  (ANY "*" []
       (route/not-found (slurp (io/resource "404.html")))))

http://my-website.com/style.css should display your css file. If it doesn't, there's something wrong with your routes or your file isn't there. It expects resources/public/style.css. Make sure you are restarting your app and doing a cacheless refresh in your browser (shift F5 in most instances) to make sure there's nothing weird giving you weird results.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top