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
Related
My clojure/reagent project was initiated using a static resources/public/index.html, where src/test/client.cljs provides the rendering function.
Now I would like to switch the server side to a dynamic renderer (in order to provide a REST API to the client).
This is the structure of the project:
test
|___src
| |___test
| |___core.clj
| |___client.cljs
|___resources
| |___public
| |___index.html
| |___style.css
|___project.clj
And the main files:
;; src/test/core.clj
(ns test.core
(:use compojure.core)
(:require
[ring.adapter.jetty :as jetty]
[clostache.parser :as clostache]
[compojure.route :as route]
[compojure.handler]
[clojure.data.json :as json]
))
(def home
"<!DOCTYPE html>
<html>
<head>
<meta charset=\"UTF-8\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<link href=\"style.css\" rel=\"stylesheet\" type=\"text/css\">
<link rel=\"icon\" href=\"https://clojurescript.org/images/cljs-logo-icon-32.png\">
</head>
<body>
<div id=\"container\"></div>
<script src=\"/client.js\" type=\"text/javascript\"></script>
</body>
</html>")
(defn handler [request]
(if (and (= :get (:request-method request))
(= "/" (:uri request)))
{:status 200
:headers {"Content-Type" "text/html"}
:body home}
{:status 404
:headers {"Content-Type" "text/plain"}
:body "Not Found"}))
;; src/test/client.cljs
(ns test.client
(:require
[reagent.dom :as rdom]))
(defn test-container []
[:div
[:h1 "Test ring-handler"]
])
(defn ^:export run []
(rdom/render [test-container] (js/document.getElementById "container")))
;; project.clj
(defproject test-ring-handler "0.1.2-SNAPSHOT"
:min-lein-version "2.0.0"
:description "test-ring-handler"
:license {:name "GNU GPL v3+"
:url "http://www.gnu.org/licenses/gpl-3.0.en.html"}
:dependencies [[org.clojure/clojure "1.11.1"]
[org.clojure/clojurescript "1.11.54"]
[reagent "1.1.1"]
[cljsjs/react "18.0.0-rc.0-0"]
[cljsjs/react-dom "18.0.0-rc.0-0"]
[ring "1.9.5"]
[ring/ring-core "1.9.5"]
[figwheel "0.5.20"]
]
:plugins [[lein-cljsbuild "1.1.7"]
[lein-figwheel "0.5.19"]
]
:resource-paths ["resources" "target"]
:clean-targets ^{:protect false} [:target-path]
:profiles {:dev {:cljsbuild
{:builds {:client
{:figwheel {:on-jsload "test.client/run"}
:compiler {:main "test.client"
:optimizations :none}}}}}}
:figwheel {:repl false
;; :http-server-root "public"
:ring-handler test.core/handler
}
:cljsbuild {:builds {:client
{:source-paths ["src"]
:compiler {:output-dir "target/public/client"
:asset-path "client"
:output-to "target/public/client.js"
:main "test.core"}}}}
)
When the :ring-handler option is commented, everything is compiled correctly and client.cljs correctly renders the title "Test ring-handler".
If :ring-handler is activated, the compilation fails:
$ lein figwheel
Figwheel: Validating the configuration found in project.clj
Figwheel: Configuration Valid ;)
java.lang.IllegalArgumentException: unable to require the namespace of the handler test.core/handler for :ring-handler
at figwheel_sidecar.utils$illegal_argument.invokeStatic(utils.clj:53)
at figwheel_sidecar.utils$illegal_argument.doInvoke(utils.clj:52)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at figwheel_sidecar.components.figwheel_server$create_initial_state$fn__19826.invoke(figwheel_server.clj:323)
...
What is the syntax to use so that the server requests are redirected to test.core/handler?
I am trying to implement request end point authentication. For that I want to access accessToken value from request headers.
My GET request end Point is
CURL Command
curl -X GET \
'http://localhost:3000/hello?id=10' \
-H 'accesskey: 23423sfsdfsdfsfg' \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-H 'postman-token: f69b34e6-4888-ec31-5fbc-b734e176571b' \
-d '{
"artwork": {id" : 1}
}'
HTTP Command
GET /hello?id=10 HTTP/1.1
Host: localhost:3000
Content-Type: application/json
accessKey: 23423sfsdfsdfsfg
Cache-Control: no-cache
Postman-Token: b974719d-5e1d-4d68-e910-e9ca50562b2f
My Code for GET Method Implementation
(defapi app
(GET ["/hello/:id", :id #"[0-9]+" ] [id]
(log/info "Function begins from here")
(def artworkData (logic/artwork-id (->> id (re-find #"\d+") Long/parseLong)))
(def data (if (not-empty artworkData)
{:data artworkData :status 200}
{:data [] :status 201}))
(ok data)))
I want to fetch accessKey: 23423sfsdfsdfsfg from request header.
Is there any way to get the value and use in my GET Method?
I am using POSTMAN to test all API end points.
Compojure has custom destructuring syntax (i.e., different from Clojure proper) for the parameters. You can bind the whole request map using keyword :as
(defapi app
(GET ["/hello/:id", :id #"[0-9]+" ] [id :as request]
If you want only request headers, the following should work
(defapi app
(GET ["/hello/:id", :id #"[0-9]+" ] [id :as {:headers headers}]
Note that this still allows you to bind path parameter id.
The Compojure Sweet API functions like [compojure.api.sweet :refer [defroutes GET PUT context]] let us bind the whole request or bind select headers. In the snippet below [:as request] makes the whole request available to me.
(GET
"/download/:id"
[:as request]
:header-params [{x-http-request-id :- X-Http-Request-Id nil}]
:path-params [id :- (describe String "The encoded id of the image")]
:summary "Download the image bytes"
:description "This endpoint responds 307 - Temporary Redirect to a cacheable presigned S3 URL for the actual bytes."
(let [http-response (->> request
walk/keywordize-keys
util/extract-base-url
(transform/generate-resource-url (util/decode-key id))
status/temporary-redirect)
expire-time (-> 3 hours from-now coerce/to-date ring-time/format-date)]
(log/infof "x-http-request-id is %s" x-http-request-id)
(response/header http-response "Expires" expire-time)))
The vector beginning :header-params [{x-http-request-id :- X-Http-Request-Id nil}] makes the value of the "X-HTTP-REQUEST-ID" header in the request available to my function directly as x-http-request-id.
The squiglies thing {...} makes the presence of x-http-request-id header optional in the request.
The :- X-Http-Request-Id nil stuff gives it a Schema which is defined somewhere else like (s/defschema X-Http-Request-Id (rss/describe String "Request ID for tracing calls")).
Once you've got those kids bound to names you just work with the names. The compojure folks don't do a great job at documenting everything you can do there. Poke around their examples and you'll find stuff like this.
I have figured out solution to the issue. Please check solution here.
(ns clojure-dauble-business-api.core
(:require [compojure.api.sweet :refer :all]
[ring.util.http-response :refer :all]
[clojure-dauble-business-api.logic :as logic]
[clojure.tools.logging :as log]
[clojure-dauble-business-api.domain.artwork]
[cheshire.core :as json])
(:import [clojure_dauble_business_api.domain.artwork Artwork]))
(defapi app
(GET ["/hello/:id", :id #"[0-9]+"] [id :as request]
(log/info "Function begins from here" request)
(def jsonString (json/generate-string (get-in request [:headers])))
(log/info "Create - Access Key is " (get-in (json/parse-string jsonString true) [:accesskey]))
(def artworkData (logic/artwork-id (->> id (re-find #"\d+") Long/parseLong)))
(def data (if (not-empty artworkData)
{:data artworkData :status 200}
{:data [] :status 201})))
I don't think it is smart way.
Can you anybody look into my solution and tell me Is there another way to get accesskey?
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]))
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))
I'm trying to get started with Clojure and Clojurescript by implementing a simple web app. Things are going pretty good so far and reading from different tutorials I've come up with the code below:
core.clj:
(ns myapp.core
(:require [compojure.core :as compojure]
[compojure.handler :as handler]
[compojure.route :as route]
[myapp.controller :as controller]))
(compojure/defroutes app-routes
(compojure/GET "/" [] controller/index)
(route/resources "/public")
(route/not-found "Not Found"))
(def app
(handler/site app-routes))
controller.clj:
(ns myapp.controller
(:use ring.util.response)
(:require [myapp.models :as model]
[myapp.templates :as template]))
(defn index
"Index page handler"
[req]
(->> (template/home-page (model/get-things)) response))
templates.clj:
(ns myapp.templates
(:use net.cgrand.enlive-html)
(:require [myapp.models :as model]))
(deftemplate home-page "index.html" [things]
[:li] (clone-for [thing things] (do->
(set-attr 'data-id (:id thing))
(content (:name thing)))))
The problem is I can't display non-ascii characters on the page and I don't know how to set HTTP headers on a page.
I see solutions like this but I simply can't figure out where place them in my code:
(defn app [request]
{:status 200
:headers {"Content-Type" "text/plain"}
:body "Hello World"})
P.S: Any suggestions about style and/or code organization are welcome.
Use ring.util.response:
(require '[ring.util.response :as r])
Then on your index function:
(defn index
"Index page handler"
[req]
(-> (r/response (->> (template/home-page (model/get-things)) response))
(r/header "Content-Type" "text/html; charset=utf-8")))
You can chain other actions on the response such as set-cookie and whatnot:
(defn index
"Index page handler"
[req]
(-> (r/response (->> (template/home-page (model/get-things)) response))
(r/header "Content-Type" "text/html; charset=utf-8")
(r/set-cookie "your-cookie-name"
"" {:max-age 1
:path "/"})))