juxt / bidi Goto Github PK
View Code? Open in Web Editor NEWBidirectional URI routing
License: MIT License
Bidirectional URI routing
License: MIT License
calls to ex-info need a map. I will post a pull-request that provides a test and fixes the issue.
(Not saying it's wrong btw, just that I don't understand it!)
In the README, you say that if you feel the need to use ->WrapMiddleware
, 'you're probably doing it wrong'. I agree with the reasoning that it's good to split apart URI routing from request handling, but I usually wrap all my API routes with middleware like 'wrap-params' or 'wrap-restful-format'. It seems a bit overkill to apply those middleware to every handler individually. What would you recommend? Am I doing it wrong?
Cheers,
James
Hi,
This code throws an exception. Is this kind of (prefix) route supported?
(bidi/match-route
(bidi/compile-route
[["/base/path/" [#".+" :id]] {:get :match!}])
"/base/path/1"
:request-method :get)
java.lang.IllegalArgumentException: No implementation of method: :segment-regex-group of protocol: #'bidi.bidi/PatternSegment found for class: bidi.bidi.CompiledPrefix
core_deftype.clj:544 clojure.core/-cache-protocol-fn
bidi.clj:69 bidi.bidi/eval13078[fn]
core.clj:2557 clojure.core/map[fn]
LazySeq.java:40 clojure.lang.LazySeq.sval
LazySeq.java:49 clojure.lang.LazySeq.seq
RT.java:484 clojure.lang.RT.seq
core.clj:133 clojure.core/seq
core.clj:2551 clojure.core/map[fn]
LazySeq.java:40 clojure.lang.LazySeq.sval
LazySeq.java:49 clojure.lang.LazySeq.seq
RT.java:484 clojure.lang.RT.seq
core.clj:133 clojure.core/seq
protocols.clj:26 clojure.core.protocols/seq-reduce
protocols.clj:53 clojure.core.protocols/fn
protocols.clj:13 clojure.core.protocols/fn[fn]
core.clj:6287 clojure.core/reduce
bidi.clj:200 bidi.bidi/eval13310[fn]
bidi.clj:148 bidi.bidi/eval13210[fn]
bidi.clj:164 bidi.bidi/match-pair
bidi.clj:286 bidi.bidi/match-route
I have some code:
(defresource foo [id]
:available-media-types ["application/json"]
:allowed-methods [:get]
:handle-ok
(fn [ctx]
{:hello id}))
(def routes ["/foo/" {[:id] foo}])
(def app (-> (make-handler routes) (wrap-default api-default)))
This doesn't work as-is because something is trying to cast the foo
function to an associative structure, so this seemed like it might work:
(def routes ["/foo/" {[:id] (fn [req] (foo (get-in req [:route-params :id])}])
But (foo x)
produces a handler function that closes over x
, it seems since once again something was trying to cast a function to an associative structure.
What I ended up with was:
(def routes ["/foo/" {[:id] (fn [req] ((foo (get-in req [:route-params :id]) req)}])
Which is kind of messy, especially when compared to something like Compojure:
(defroutes routes (ANY "/foo/:id" [id] (foo id)))
Am I missing something?
Hi Malcolm,
Have been playing around a fair bit with Bidi over the Christmas break - really like the separation between routes and handlers, thanks!
Thought I'd share a gotcha when using the 2-arg arity of bidi.bidi/make-handler
- of course, may just be me not understanding Bidi's fully though! I had (something like) the following:
(:require [bidi.ring :as br])
(def routes
["" {"/" ::home-page-handler
"/js" (br/resources {:prefix "js"})}])
(def handlers
{::home-page-handler (fn [req]
(response "Hello world!"))})
(br/make-handler routes handlers)
This worked fine when requesting the "/" route, but failed with an NPE when trying to fetch anything under "/js", caused by ((handler-fn handler) ...)
- the handler-fn was returning nil.
Looking into it a bit more deeply - the 1-arg version of make-handler
defaults the handler-fn to 'identity', meaning that the handler returned by (br/resources ...)
was being returned as-is to serve the resources, so it seems like it's not possible to mix a handler map with the built-in bidi.ring handlers?
I ended up solving it by altering the make-handler
call, as follows:
(br/make-handler routes
(some-fn handlers
#(when (fn? %) %)))
to use my handlers when there was a match, but fall back on an 'identity-like' function when the routes already contained a request handler for the given route.
Feel free to do whatever you like with this issue - won't be offended if it's 'no action req'd, closed' - but if you've got any suggestions about how I can better use Bidi, I'd be very grateful!
Cheers,
James
Hi Malcolm,
Really enjoying using Bidi on CLJS btw, it makes links/routing much easier :)
Ran into a problem when my routes structure goes beyond a certain size - while cljs.core.PersistentArrayMap
satisfies the bidi.bidi/Matched
protocol, cljs.core.PersistentHashMap
s don't!
Have fixed for now by adding the following in my app, copying from the implementation for ArrayMap:
(extend-protocol bidi/Matched
cljs.core.PersistentHashMap
(resolve-handler [this m] (some #(bidi/match-pair % m) this))
(unresolve-handler [this m] (some #(bidi/unmatch-pair % m) this)))
Guess it's a case of duplicating the implemention in Bidi too, or do you know of a cleaner solution?
Cheers :)
James
Every Bidi example I saw was with a starting slash, like this:
(routes [_] ["/" {"index.html" ::index
"" (redirect ::index)}])
What about an homepage?
Say I'm going to www.my-website.com
, how can I match it?
Modifying the routes to something like this doesn't work:
(routes [_] ["" {"/" {"index.html" ::index}
"" (redirect ::index)}])
You can reproduce the problem with the project created with:
lein new modular my-website bootstrap-cover
(def routes ["https://" {["www.demo.com/?q=" :q] :api}])
(path-for routes :api :q (pr-str [1 2 3])) ; -> "https://www.demo.com/?q=[1 2 3]"
(match-route routes "https://www.demo.com/?q=[1 2 3]") ; -> nil
The README illustrates arity-1 make-handler with a route structure whose match results are keywords.
(let [r [["/foo/" [#".*" :stuff]] :foo]]
(println (bidi/match-route r "/foo/with/lots/of/extra/stuff"))
(bidi/compile-route r))
{:handler :foo, :route-params {:stuff with/lots/of/extra/stuff}}
IllegalArgumentException No implementation of method: :compile-segment of protocol: #'bidi.bidi/Compilable found for class: clojure.lang.PersistentVector clojure.core/-cache-protocol-fn (core_deftype.clj:541)
The following WILL redirect after a post:
(br/redirect-after-post my-handler)
However, I don't really see any use for this, as a post implies some actions with parameters.
Here is how one might naively use the redirect:
(fn [req]
;; do some stuff
(br/redirect-after-post my-handler))
Unfortunately, this returns an error:
bidi.ring.Redirect cannot be cast to clojure.lang.IFn
So, how does one get his hands on the parameters before the redirection?
I have not been successful at nesting routes within other routes:
(def foo-routes
["/" [["bar" :bar]]])
(def app-routes
["/" [["foo" foo-routes]
["blah" :blah]]])
(comment
;; These should both match, and return :bar (I think)
;; With bidi 0.14 the first succeeds, but the second throws IllegalArgumentException
(b/match-route foo-routes "/bar")
(b/match-route app-routes "/foo/bar"))
The exception throw is:
IllegalArgumentException No implementation of method: :match-pattern of protocol: #'bidi.bidi/Pattern found for class: java.lang.Character
Would it be possible to explicitly assign an "ID" to a route? Let's say you wanted more than the path for generating a request. Something like:
;; Here, the "ID" would be the :update-post keyword:
(def my-routes [["/posts" :id] {:post [:update-post :my-handler]}])
(request-for my-routes :update-post :id "the-post-id")
;; => {:request-method :post :path "/posts/the-post-id" :handler :my-handler}
This would allow you to programmatically construct a full request using the route structure, not just the path.
Although not strictly part of the ring spec, it would be nice if make-handler was composable with compojure's context
macro.
Now that Clojure 1.7.0 is released, we need to move from .cljx to .cljc files.
I have done the initial port on the cljc branch. However, I somehow can't get the tests to work which I believe will be an issue until the next Leiningen is released.
Lazily I'm hoping that time alone solves this issue, but we could move to something like boot.
Exception in thread "main" java.lang.IllegalAccessError: url-response does not exist
at clojure.core$refer.doInvoke(core.clj:3849)
at clojure.lang.RestFn.applyTo(RestFn.java:139)
at clojure.core$apply.invoke(core.clj:619)
at clojure.core$load_lib.doInvoke(core.clj:5394)
at clojure.lang.RestFn.applyTo(RestFn.java:142)
at clojure.core$apply.invoke(core.clj:619)
at clojure.core$load_libs.doInvoke(core.clj:5413)
If you accidentally create a nil handler it will cause path-for to blow up if it traverses it
(def routes
["/"
{"foo" nil)
"bar" :bar}])
(path-for routes :bar)
This blows up as it attempts to traverse the foo structure.
IllegalArgumentException No implementation of method: :unresolve-handler of protocol: #'bidi.bidi/Matched found for class: nil clojure.core/-cache-protocol-fn (core_deftype.clj:544)
Ideally bidi will blow up with a more helpful error message, indicating that the routes structure is wrong and the path it was traversing when it came across something unexpected.
Not sure if this is part of the design or not but I was surprised to find that bidi won't match a route if a previous route has the same path:
(bidi/match-route
[["users" "/" [#"[^/]+" :id]] {:delete :delete-handler}
["users" "/" [#"[^/]+" :id]] {:put :update-handler}] "users/1" :request-method :put)
^^ yields nil
Not that one would normally construct routes like that. I just stumbled on this while dynamically building routes from a mapping.
Compile warning when used with org.clojure/clojurescript "0.0-3308"
I came across a situation where I needed to create an api with a path like "/foo/by-email/[email protected]" where the email is a route param. It appears bidi does not let you do that currently without a regex specification of the email. Please see example below:
(deftest email-in-path-test
(testing "fails without regex"
(let [route [["/foo/" :email] :foo]]
(is (= (match-route route (path-for route :foo :email "[email protected]"))
{:handler :foo
:route-params {:email "[email protected]"}}))))
(testing "passes with regex"
(let [route [["/foo/" [#".+\@.+\..+" :email]] :foo]]
(is (= (match-route route (path-for route :foo :email "[email protected]"))
{:handler :foo
:route-params {:email "[email protected]"}})))))
Not sure if this is intended or just an oversight.
Using a regex to specify format is fine, but I'm using it together with yada and it thinks the regex is a path element.
I noticed that query parameters were removed in a commit in December. My requirement is that I need to be able to pass such parameters to be passed to all handlers without having to specify them in advance in the route definition.
It would be great if you could reintroduce the feature via a flag or expose an easy way to pre-parse the query parameters.
(def routes ["/" {"index.html" :index
"article.html" :article}])
(def compiled-routes (compile-route routes))
(path-for compiled-routes :index)
Throws
IllegalArgumentException No implementation of method: :unmatch-pattern of protocol: #'bidi.bidi/Pattern found for class: java.util.regex.Pattern clojure.core/-cache-protocol-fn (core_deftype.clj:544)
Using bidi 1.18.10.
Was thinking about using bidi as I need some routing for both the server and client sides of an app. When I looked at the dependency tree (lein deps tree
):
$ lein with-profile production deps :tree
I found this:
[bidi "1.19.0" :exclusions [[commons-codec]]]
[com.cemerick/url "0.1.1"]
[pathetic "0.5.0"]
[com.cemerick/clojurescript.test "0.0.4"]
[org.clojure/clojurescript "0.0-2371"]
[com.google.javascript/closure-compiler "v20140625"]
[args4j "2.0.26"]
[com.google.code.findbugs/jsr305 "1.3.9"]
[com.google.javascript/closure-compiler-externs "v20140625"]
[com.google.protobuf/protobuf-java "2.5.0"]
[org.json/json "20090211"]
[org.clojure/data.json "0.2.3"]
[org.clojure/google-closure-library "0.0-20140718-946a7d39"]
[org.clojure/google-closure-library-third-party "0.0-20140718-946a7d39"]
[org.mozilla/rhino "1.7R4"]
[ring/ring-core "1.2.1"]
[clj-time "0.4.4"]
[joda-time "2.1"]
[commons-fileupload "1.3"]
[commons-io "2.4"]
[ring/ring-codec "1.0.0"]
My main concern is making an uberjar, which, as a result of bidi, will contain all these things I'm not using (when running from an uberjar), including ClojureScript, clojurescript-test, ring, and so on. Should those be marked as dev dependencies or "provided" or something like that? I suppose I can just go with my own :exclusions
clauses, but it seems like the library itself shouldn't require those, right?
I can see you've got a bidi.ring
namespace (is there a clojure-doc generated somewhere?) which I suppose requires ring, but what happened to the "one thing well" story! ;) (I can't tell what make-handler
does. Return a function that takes a request and .... The routes data has to have function references for handlers, not keywords as shown in the readme example, right?
Anyway.... ;)
(def routes-test
["/" {(->Alternates ["xxx" "index" "a"]) :handler}])
(bidi/match-route routes-test "/index")
=> {:handler :handler}
however,
(def routes-test
["/" {(->Alternates ["xxx" "" "index" "a"]) :handler}])
(bidi/match-route routes-test "/index")
=> nil
and,
(bidi/match-route routes-test "/xxx")
=> {:handler :handler}
workaround,
(def routes-test
["/" {(->Alternates ["xxx" #"$" "index" "a"]) :handler}])
(bidi/match-route routes-test "/xxx")
(bidi/match-route routes-test "/index")
(bidi/match-route routes-test "/")
all => {:handler :handler}
Hi,
First of all, thanks for a fantastic library, really enjoying using bidi with Liberator. The bi-directionality and routes as data is great!
I encountered a few issues when defining a route structure that has the same handler occurring multiple times. I'm not sure whether this is because it's a bad idea to do that, but let me show you the example I'm using:
(def routes
["/" [["posts" 'posts-handler]
[["users/" :user "/posts"] 'posts-handler]]])
In this case, posts-handler
understands queries both for all posts (first route), and for a specific users posts (second route).
Given this route structure, I would like to be able to generate URLs both for all posts, and a specific users posts like so:
(path-for routes 'posts-handler)
;; => "/posts"
(path-for routes 'posts-handler :user "blahonga")
;; => "/users/blahonga/posts"
ie. if params are given, routes where the params occur should be preferred. It may even make sense to fail completely if all params supplied are not used. I'm not sure.
Currently the unmatching takes the first route matching the given handler. However, the first case in the above path-for
example actually blows up with clojure.lang.ExceptionInfo: Cannot form URI without a value given for :user parameter
(full stack trace below). I believe this is due to non-lazyness (or lazy-seq chunking), unresolve-handler
for PersistentVectors uses:
(first (keep #(match-pair % m) this))
Which seems to be not lazy for PersistentVectors
.
Full stack trace:
clojure.lang.ExceptionInfo: Cannot form URI without a value given for :user parameter
at clojure.core$ex_info.invoke (core.clj:4327)
bidi.bidi$eval28675$fn__28678.invoke (bidi.clj:56)
bidi.bidi$eval28612$fn__28624$G__28599__28631.invoke (bidi.clj:33)
bidi.bidi$eval28811$fn__28812$fn__28813.invoke (bidi.clj:133)
clojure.core$map$fn__4207.invoke (core.clj:2485)
clojure.lang.LazySeq.sval (LazySeq.java:42)
clojure.lang.LazySeq.seq (LazySeq.java:60)
clojure.lang.RT.seq (RT.java:484)
clojure.core$seq.invoke (core.clj:133)
clojure.core$apply.invoke (core.clj:617)
bidi.bidi$eval28811$fn__28812.invoke (bidi.clj:133)
bidi.bidi$eval28711$fn__28725$G__28700__28732.invoke (bidi.clj:80)
bidi.bidi$unmatch_pair.invoke (bidi.clj:152)
bidi.bidi$eval28898$fn__28899$fn__28900.invoke (bidi.clj:160)
clojure.core$keep$fn__6349.invoke (core.clj:6599)
clojure.lang.LazySeq.sval (LazySeq.java:42)
clojure.lang.LazySeq.seq (LazySeq.java:60)
clojure.lang.LazySeq.first (LazySeq.java:82)
clojure.lang.RT.first (RT.java:577)
clojure.core$first.invoke (core.clj:55)
bidi.bidi$eval28898$fn__28899.invoke (bidi.clj:160)
bidi.bidi$eval28754$fn__28768$G__28743__28775.invoke (bidi.clj:87)
bidi.bidi$unmatch_pair.invoke (bidi.clj:151)
bidi.bidi$path_for.doInvoke (bidi.clj:207)
clojure.lang.RestFn.invoke (RestFn.java:425)
bidi.bidi$eval32697.invoke (form-init1346470612871071144.clj:1)
clojure.lang.Compiler.eval (Compiler.java:6619)
clojure.lang.Compiler.eval (Compiler.java:6582)
clojure.core$eval.invoke (core.clj:2852)
clojure.main$repl$read_eval_print__6588$fn__6591.invoke (main.clj:259)
clojure.main$repl$read_eval_print__6588.invoke (main.clj:259)
clojure.main$repl$fn__6597.invoke (main.clj:277)
clojure.main$repl.doInvoke (main.clj:277)
clojure.lang.RestFn.invoke (RestFn.java:1096)
clojure.tools.nrepl.middleware.interruptible_eval$evaluate$fn__675.invoke (interruptible_eval.clj:56)
clojure.lang.AFn.applyToHelper (AFn.java:159)
clojure.lang.AFn.applyTo (AFn.java:151)
clojure.core$apply.invoke (core.clj:617)
clojure.core$with_bindings_STAR_.doInvoke (core.clj:1788)
clojure.lang.RestFn.invoke (RestFn.java:425)
clojure.tools.nrepl.middleware.interruptible_eval$evaluate.invoke (interruptible_eval.clj:41)
clojure.tools.nrepl.middleware.interruptible_eval$interruptible_eval$fn__716$fn__719.invoke (interruptible_eval.clj:171)
clojure.core$comp$fn__4154.invoke (core.clj:2330)
clojure.tools.nrepl.middleware.interruptible_eval$run_next$fn__709.invoke (interruptible_eval.clj:138)
clojure.lang.AFn.run (AFn.java:24)
java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1145)
java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:615)
java.lang.Thread.run (Thread.java:744)
The source shows a function tag.
It is easier to type (and much prettier) than ->TaggedMatch
.
Shouldn't its use be encouraged in the readme?
->TaggedMatch
feels like an implementation detail.
Edit: the same is true for ->Atlernates
.
You accepted a PR for the .travis.yml
file but never got round to enabling Travis CI for the repo, going to https://travis-ci.org/profile/juxt should guide you to enabling it, this will automatically run tests for pull requests, which is very handy!
Using bidi 1.10.2
I want to normalise uris wrt trailing slashes, with something like
[["programmes/" :programme-id "/projects/"] projects)]
[["programmes/" :programme-id "/projects"] (->Redirect 307 projects)]
but requests to programmes/21314/projects
fail with
clojure.lang.ExceptionInfo: No parameter found in params for key :programme-id {}
at clojure.core$ex_info.invoke(core.clj:4327)
at bidi.bidi$eval5392$fn__5393.invoke(bidi.clj:124)
at bidi.bidi$eval5309$fn__5332$G__5294__5339.invoke(bidi.clj:69)
at bidi.bidi$eval5541$fn__5542$fn__5543.invoke(bidi.clj:220)
at clojure.core$map$fn__4207.invoke(core.clj:2485)
I believe that on redirect bidi should somehow 'reset' the parsing of the uri params?
In using ->ResourcesMaybe
it seems that the mime-type for css isn't correctly guessed. In digging into the source, it looks like bidi uses wrap-file-info
from ring.middleware.file-info
. According to the discussion here: https://groups.google.com/forum/#!topic/ring-clojure/EmjsHw4QyEk. Use of wrap-file-info
with resources is problematic. In my case, bidi sets the mime-type to be 'text/plain' and I see the following error in the browser console 'Resource interpreted as Stylesheet but transferred with MIME type text/plain: '
ClojureScript:cljs.user> (bidi.bidi/match-route pp-web.routes/routes "/search")
{:handler :search}
ClojureScript:cljs.user> (bidi.bidi/match-route pp-web.routes/routes "/search?a=1")
nil
I see from searching the issues that matching on query-params isn't supported (as the handler ought to handle those not the router), but i expected it to still route properly ignoring the query param.
Hi there,
I'm just discovering your awesome library, and I have an architectural question about your usage in the real-world. Allow me to setup the problem with some theoretical code
(ns my.routes
(:require [my.controllers.authentication :as auth]
[cemerick.friend :as friend])
(def my-routes ["/logout" (wrap-middleware auth/logout friend/logout)
You can assume that I'm using bidi.ring/make-handler with my routes. Of course, route recognition and generation work marvelously. My question is one of coupling and indirection related to route generation (path-for). As I see it, there are two places where I might want to use path-for:
It's the latter which concerns me. While it's natural for handlers to know about views (they have to render some representation), the converse is generally considered undesirable. But, if I have symbolic references to functions in the "dispatch" / handler position in my route definitions, then I need something along the lines of:
(path-for auth-routes auth/logout)
in my view layer (Enlive snippets). Of course, we could solve the problem by introducing a layer of indirection such that instead of the bi-directional coupling being one of:
URL path <===> dispatch function
it could be:
URL path <===> *named route* / keyword <===> dispatch function
This would let us depend on an abstract name for route generation:
(path-for auth-routes :logout)
at the expense of complicating our route recognition where we would have to do two lookups to go from URL path to dispatch function. As this would mean opting out of your carefully written make-handler function, I naturally wanted to get your thoughts / advice and ask how you approach this problem in your production code.
Thanks both for the library and for reading this far.
Cheers!
The route definition BNF grammar claims a Matched
can be (among other options) either a RoutePair
or a vector of many RoutePair
s. The current implementation, however, doesn't allow the former:
(match-route "/index" ["/" ["index" :index]])
;; IllegalArgumentException No implementation of method: :match-pattern
;; of protocol: #'bidi.bidi/Pattern found for class: nil
while the latter is okay:
(match-route "/index" ["/" [["index" :index]]])
;;=> {:handler :index}
Given that RoutePair
s are vectors themselves, I think it's wiser to drop the convenience and always require the [ RoutePair+ ]
form โ or even [ RoutePair* ]
.
Occurs because matches are only implemented for PersistentArrayMap, see https://github.com/juxt/bidi/blob/master/src/bidi/bidi.cljx#L251 and https://github.com/juxt/bidi/blob/master/src/bidi/bidi.cljx#L282 and not implemented for PersistentHashMap.
I found this on the clourescript side, not sure if it occurs on the clojure side.
Hi,
I'm playing around with bidi and liking it so far! I've become slightly confused about the correct way to do redirects after posts. I'm using liberator and I want to be able to form the location header from my bidi route (typically my resources will redirect to themselves following a post request, to play nicely with browsers).
I think I want to use 'path-for' to get the path for my redirect but the problem I'm having is that my route definitions and handlers aren't visible inside my liberator resources. I'm not too familiar with ring or liberator so I'm probably missing something obvious. Would love a pointer or two!
Thanks.
This works fine:
(def app (-> ["/" (->Files {:dir files-path})]
(compile-route)
(make-handler)))
I have a test.txt
and my server responds with its contents when I do GET /
on it.
But this gives a 404:
(def files-path "/home/muhuk/tmp/")
(def routes (compile-route ["/" :files]))
(def handlers {:files (->Files {:dir files-path})})
(def app (make-handler routes handlers))
Also I am curious if this is expected behavior:
(bidi.bidi/path-for routes :files) ;; => "/"
(bidi.bidi/match-route routes "/") ;; => {:handler :files}
(bidi.bidi/match-route routes "/test.txt") ;; => nil
Shouldn't the third call resolve to {:handler :files :something-else "test.txt"}
?
Assuming I want to serve a limited number of static files, should I use Files
or would it make more sense to take the handler part of it and cook my own view?
;; This part:
(-> (fn [req] (url-response res))
(wrap-file-info (:mime-types options))
(wrap-content-type options)
(wrap-not-modified))
I'm passing UUIDs as (potentially, not always) deserialized by Transit, which has its own UUID type (with the same printed representation as cljs.core.UUID). Bidi fails to generate a route, saying that it's not compatible with the route type.
You can see that dnolen recently added checks inside of its uuid?
fn for both. I'm not sure what the answer here is (Bidi probably shouldn't have to be aware of every other UUID type), but it's certainly causing some problems locally.
Given a route that partially matches, I want to pass whatever remains after initial matching to a function.
More context: My app is backed by a a database, which stores documents by relative path -- say, app/index.html
is the name of an html document I want to return. Given two paths, /root/app/index.html
and /root/app/img/image.png
, I want to pass app/index.html
or app/img/image.png
in to a function to load them from the DB. So far, this seems totally impossible with bidi.
Thought one: just match it with a keyword:
> (bidi.bidi/match-route ["/" {"root/" {[:remaining] :handler}}] "/root/app/img/image.png")
;;=> nil
Okay, no dice. So thought two: what about a regex? This... is where things get real weird:
> (bidi.bidi/match-route ["/" {"root/" {[#"(?s).+":remaining] handler}}] "/root/app/img/image.png")
;;=> {:route-params {:remaining "g"}, :handler :handler}
;; Okay weird -- it only got the last letter. How about we use a capture group?
> (bidi.bidi/match-route ["/" {"root/" {[#"(?s)(.+)" :remaining] :handler}}] "/root/app/img/image.png")
;;=> {:route-params {:remaining "app/img/image.pn"}, :handler :handler}
The regex winds up receiving everything but the final letter. It looks like the string is being chomped before it's even passed to the regex:
> (bidi.bidi/match-route ["/" {"root/" {[#"(app/img/image\.png)" :remaining] :handler}}] "/root/app/img/image.png")
;;=> nil
> (bidi.bidi/match-route ["/" {"root/" {[#"(app/img/image\.pn)" :remaining] :handler}}] "/root/app/img/image.png")
;;=> {:route-params {:remaining "app/img/image.pn"}, :handler :handler}
I'm on bidi 1.23.1, Clojure 1.7.0. Any thoughts?
Hello,
First let me start by expressing my thanks for the time and effort spent creating bidi. We use enjoy bidi as part of our routing.
There is something I came to notice about route matching which I think that could be improved. Often one will have to create routes with an optional param like a page postfix so that paths such as "/foo" and "/foo/page-2" are recognised.
As far as I understand this can be achieved like so:
Without page:
user> (bidi/match-route ["/foo" :foo]
"/foo")
{:handler :foo}
With page:
user> (bidi/match-route [["/foo" "/page-" :page] :foo]
"/foo/page-1")
{:handler :foo, :params {:page "1"}}
Both combined:
user> (map (partial bidi/match-route ["/foo" {"" :foo
["/page-" :page] :foo}])
["/foo" "/foo/page-2"])
({:handler :foo} {:handler :foo, :params {:page "2"}})
However, since there is support for regex I tried to write a more compact version by using non-capturing groups to exclude irrelevant parts such "/page-" like so:
user> (map (partial bidi/match-route
[["/foo" [#"(?:/page-(\d+))?" :page]] :foo])
["/foo" "/foo/page-2"])
({:handler :foo, :params {:page ""}}
{:handler :foo, :params {:page "/page-2"}})
Apparently the capturing and non-capturing groups are ignored. What I was hoping for was something like the previous result:
({:handler :foo} {:handler :foo, :params {:page "2"}})
I'm not sure if it's worth the effort but such a change might be helpful in reducing certain redundancies when defining routes with optional path-params. What do you think?
Regards,
Roman
Hi Malcolm - is there a quick way (short of going through the Git history) of finding breaking changes between versions of bidi?
Cheers,
James
Hi,
I'd like to match a capture multiple path segments in a single param, a kind of wildcard:
(let [routes ["/" {"" status/status
"com/" {"api/" {[#"(.)*" :path] handle-wildcard}}}]]
(bidi/match-route routes "/com/api/proxy/what-ever/here"))
I would like the result of that to be:
{:handler handle-wildcard :params {:path "proxy/what-ever/here"}
Is this possible? Since everything is segmented, I can't think of how this would be done.
I am using boot instead of Leiningen, and it treats /resources directory as read-only by creating final artifacts in /target.
How could I serve index.html from /target/index.html with bidi?
This only serves index.html from /resources/public/:
(def routes
["" [["" (->Resources {:prefix "public/"})]]])
Resources, ResourcesMaybe and Files not documented
(def routes-test
["/" {(->Alternates ["index" "index-x" "index.html" "x-index.html" "index-x.html" "x.html"]) :handler
}])
(bidi/match-route routes-test "/index") => {:handler :handler}
(bidi/match-route routes-test "/index-x") => nil
(bidi/match-route routes-test "/index.html") => nil
(bidi/match-route routes-test "/x.html") => {:handler :handler}
(bidi/match-route routes-test "/x-index.html") => {:handler :handler}
(bidi/match-route routes-test "/index-x.html") => nil
This is half feature request, half knowledge base in case someone else runs into the same problem.
Simply adding bidi 1.21.0 or 1.20.3 to a project that uses ring 1.4.0 causes a Null Pointer Exception when you attempt to run ring or the repl. You don't need to be using bidi yet for the error to appear.
The exception will be logged along the lines of:
#error {
:cause nil
:via
[{:type clojure.lang.Compiler$CompilerException
:message java.lang.NullPointerException, compiling:(ring/util/http_response.clj:1068:1)
:at [clojure.lang.Compiler load Compiler.java 7239]}
{:type java.lang.NullPointerException
:message nil
:at [clojure.core$with_meta__4146 invoke core.clj 218]}]
:trace
[[clojure.core$with_meta__4146 invoke core.clj 218]
[potemkin.namespaces$import_def invoke namespaces.clj 67]
[clojure.lang.Var invoke Var.java 394]
[clojure.lang.AFn applyToHelper AFn.java 165]
[clojure.lang.Var applyTo Var.java 700]
...
]
}
The only namespace referenced from your project will likely be your handler, on line 1. Adding the dependency as [bidi "1.21.0" :exclusions [ring/ring-core]]
solved the compiler exception (although I've yet to integrate it).
Any method for making a route to catch all requests?
create project
lein new bidiresources
cd bidiresources
mkdir -p resources/public/
echo "hello world" > resources/public/index.html
projects.clj
(defproject bidiresources "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:main bidiresources.core
:dependencies [[org.clojure/clojure "1.7.0"]
[bidi "1.21.1"]
[org.immutant/web "2.1.1"]])
bidiresources/core.clj
(ns bidiresources.core
(:require
[bidi.ring :refer [make-handler resources]]
[immutant.web :as web])
(:gen-class))
(def req-handlers {:resources (resources {:prefix "public/"})})
(def routes ["/ui" (resources {:prefix "public/"})])
(def app (make-handler routes req-handlers))
(defn -main []
(web/run app))
Doing
curl -v http://localhost:8080/ui/index.html
Fails with:
java.lang.IllegalArgumentException: No implementation of method: :request of protocol: #'bidi.ring/Ring found for class: nil
at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:554) ~[clojure-1.7.0.jar:na]
at bidi.ring$eval2571$fn__2572$G__2562__2581.invoke(ring.clj:13) ~[na:na]
at bidi.ring$make_handler$fn__2601.invoke(ring.clj:39) ~[na:na]
at immutant.web.internal.undertow$create_http_handler$reify__6011.handleRequest(undertow.clj:135) ~[na:na]
at org.projectodd.wunderboss.web.undertow.async.websocket.UndertowWebsocket$2.handleRequest(UndertowWebsocket.java:104) ~[wunderboss-web-undertow-0.10.0.jar:na]
at io.undertow.server.session.SessionAttachmentHandler.handleRequest(SessionAttachmentHandler.java:68) ~[undertow-core-1.3.0.Beta9.jar:1.3.0.Beta9]
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:199) ~[undertow-core-1.3.0.Beta9.jar:1.3.0.Beta9]
at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:778) ~[undertow-core-1.3.0.Beta9.jar:1.3.0.Beta9]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) ~[na:1.8.0_66-internal]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) ~[na:1.8.0_66-internal]
at java.lang.Thread.run(Thread.java:745) ~[na:1.8.0_66-internal]
if I change
(def routes ["/ui" (resources {:prefix "public/"})])
for
(def routes ["/ui" :resources])
I get
java.lang.NullPointerException: Ring handler returned nil
at immutant.web.internal.undertow$create_http_handler$reify__6011.handleRequest(undertow.clj:138) ~[na:na]
at org.projectodd.wunderboss.web.undertow.async.websocket.UndertowWebsocket$2.handleRequest(UndertowWebsocket.java:104) ~[wunderboss-web-undertow-0.10.0.jar:na]
at io.undertow.server.session.SessionAttachmentHandler.handleRequest(SessionAttachmentHandler.java:68) ~[undertow-core-1.3.0.Beta9.jar:1.3.0.Beta9]
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:199) ~[undertow-core-1.3.0.Beta9.jar:1.3.0.Beta9]
at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:778) ~[undertow-core-1.3.0.Beta9.jar:1.3.0.Beta9]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) ~[na:1.8.0_66-internal]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) ~[na:1.8.0_66-internal]
at java.lang.Thread.run(Thread.java:745) ~[na:1.8.0_66-internal]
Matching with a regular expression does not seem to be working, unless I am misunderstanding something.
Two cases:
(match-route [[#"/x+" :x] :x-match] "/xxx") => {:handler :x-match, :route-params {:x "x"}}
Note that only one character from the "xxx" ended up in route-params.
(match-route [[#"/x+" :x] :x-match] "/x") => nil
This should match the single "x" in the path.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.