I'm just checking out vert.x, in particular the clojure examples. I'm trying to figure out how to skip a route after I've matched it. My use case is writing a static file handler. I'd like to put it first in the route chain (a route/get with a match-everything regex), and do a file exists check under some static file root, and if there is no file there, then I want to check for other defined routes that match.
I was hoping I could just return nil from the route handler function, but that ends up throwing a NullPointerException.
Related
Here is what dropwizard logs to the console in terms configured resources and their paths:
INFO [07:07:13.741] i.d.jersey.DropwizardResourceConfig: The following paths were found for the configured resources:
DELETE /apps/affiliate/internal/v1/templates/ (aff.affiliate.http.internal.AffiliateURLTemplatesInternalAPIEndpoint)
GET /apps/affiliate/internal/v1/templates/ (aff.affiliate.http.internal.AffiliateURLTemplatesInternalAPIEndpoint)
POST /apps/affiliate/internal/v1/templates/ (aff.affiliate.http.internal.AffiliateURLTemplatesInternalAPIEndpoint)
GET /apps/affiliate/v1/generate-url (aff.affiliate.http.AffiliateEndpoint)
GET /apps/affiliate/v1/redirect-search-url (aff.affiliate.http.AffiliateEndpoint)
GET /openapi.{type:json|yaml} (io.swagger.v3.jaxrs2.integration.resources.OpenApiResource)
GET /{path: apps/affiliate/v1/redirect|api/affiliate/v1/redirect} (aff.affiliate.http.RedirectEndpoint)
The problem is with the last path, specified as a regular expression.
My expectation is that it should trigger for incoming requests to both /apps/affiliate/v1/redirect and /api/affiliate/v1/redirect.
However, visiting /apps/affiliate/v1/redirect results in a 404, but visiting api/affiliate/v1/redirect results in a 200. How can I get my resource to respond to either of those paths?
The code is hard to provide but this is essentially the scaffolding (fwiw, all methods work/api works, I'm just having trouble having one of the methods respond to the regex (my actual problem)).
// AffiliateURLTemplatesInternalAPIEndpoint.kt
#Path("/apps/affiliate/internal/v1/templates")
#Produces(MediaType.APPLICATION_JSON)
public class AffiliateURLTemplatesInternalAPIEndpoint() : DropwizardResource() {
#GET
#Path("/")
public fun methodA()
#POST
#Path("/")
public fun methodB()
#DELETE
#Path("/")
public fun methodC()
}
// AffiliateEndpoint.kt
#Path("/apps/affiliate/v1")
class AffiliateEndpoint() : DropwizardResource() {
#GET
#Path("generate-url")
fun methodA()
#GET
#Path("redirect-search-url")
fun methodB()
// RedirectEndpoint.kt
#Path("/{path: apps/affiliate/v1/redirect|api/affiliate/v1/redirect}")
#Produces(MediaType.APPLICATION_JSON)
class RedirectEndpoint() : DropwizardResource() {
#GET
fun methodA()
The 404 is indeed being correctly returned.
Why? JAX-RS’ URL Matching Algorithm.
It was only after #Paul Samsotha asked me to paste my code that I finally realized the reason for the 404. 🤦
The Dropwizard/Jersey output I was relying on shows all the routes it found, but leaves out critical context about how paths have been structured in the code. Due to the way JAX-RS has implemented route matching sorting and precedence roles, code structure is essential in determining which routes will be triggered. So in this case the helpful output ended up being mostly misleading.
Read Section 3.7 - Matching Requests to Resource Methods of JAX-RS Spec 3.0 if you dare, but the answers are there.
Also, Chapter 4 of Bill Burke's RESTful Java with JAX-RS 2.0 gives great insight into route matching behavior. Unfortunately it doesn't go into clarifying an important distinction (the exact situation I got into) which is that you can't simply combine a resource and its methods paths (like the output) when applying JAX-RS url matching rules. Actually, I went through a bunch of JAX-RS write ups and none of them mentioned this actual distinction.
Instead you first try to find a match a root resource class, then look at resource methods. If you don't find a match either at the root or method level, you must return a 404.
Still, I found it to be a great resource at shining light on the spec and is much less intimidating that the spec.
Now to the actual explanation of the 404.
Jersey (which implements the JAX-RS spec), first collects all the paths associated with root resources:
/apps/affiliate/internal/v1/templates
/apps/affiliate/v1
/{path: apps/affiliate/v1/redirect|api/affiliate/v1/redirect}
It then applies its sorting and precedence logic according the spec (paraphrased from Burke's book):
The primary key of the sort is the number of literal characters in the full URI matching pattern. The sort is in descending order.
If two or more patterns rank similar in the first, then the secondary key of the sort is the number of template expressions embedded within the pattern. This sort is in descending order.
Finally, if two or more patterns rank similar in the second, then the tertiary key of the sort is the number of non-default template expressions. A default template expression is one that does not define a regular expression.
When an incoming GET request to /apps/affiliate/v1/redirect arrives, both
/apps/affiliate/v1
/{path: apps/affiliate/v1/redirect|api/affiliate/v1/redirect}
match, but the first pattern takes precedence because it has the greatest number of literal characters that match (18 vs 1).
Now that a root resource is selected, it looks at root resource's methods and compiles a list of available paths/http methods that match the incoming request. A bit of an extra detail, but for pattern matching purposes at the method level, the root resource's path will be concatenated to the resource method's path.
The following patterns are available to select from:
GET /apps/affiliate/v1/generate-url
GET /apps/affiliate/v1/redirect-search-url")
Since the request was a GET to /apps/affiliate/v1/redirect neither of the above routes match. Hence my 404 :(.
It makes complete sense, but I got into this rabbit hole because my assumptions about routing rules and precedence from experience working with other routing libraries did not align with the actual JAX-RS specs. I expected the library to have a master list for each and every method available (much like the initial output from Dropwizard/Jersey) and for each request to run through sorting and precedence rules on that master list. Alas, that is not the case.
I have a component that I want to be displayed on all the paths except for root path. So instead of providing all the paths to the Route component, I wrote this:
<Route exact path={/^\/.+$/} component={() =>
<div><img src="../../../assets/AndreyBogdanov2.jpg" className="me" /></div>
} />
That regex matches all the strings that have any character after the slash in the beginning. And it works as expected, however, in the console I see an error:
Warning: Failed prop type: Invalid prop `path` of type `regexp` supplied to `Route`, expected `string`.
in Route (created by App)
in App
in Router (created by BrowserRouter)
in BrowserRouter
Not that I'm complaining, but I would like to know, how come it works?
In the docs for the path prop it says that it needs to be any URL that path-to-regexp understands, and in the path-to-regexp's docs, it says that regex is a valid parameter. So basically, it's the component's PropTypes that throws the error when it encounters something other than a string.
It is because it can only accept a string value. You also need to insert something that path-to-regexp can read as described in React Router Training. You can look for how to write valid regexes in their repository here.
It works because the only thing Route does is to pass the object to path-to-regexp, which does support RegExp instances.
PropTypes based validation throws warnings, but doesn't stop the properties from actually being handled.
In this case this is probably a bug. PropType for path is not written correctly to match the documentation.
The only part of the Request class that I have found to contain relevant information is the request.tags map, specifically the ROUTE_PATTERN value. For example, in a route such as /api/admin/:org/list the route pattern is /api/admin/$org<[^/]+>/list. Is there a way to match request.path against this pattern and retrieve the "org" variable (and any other path variables)? Or any other way to retrieve this variable for that matter -- but, again: directly from the request object.
Please note that this needs to be generic; for instance, the route could have two parameters, or more, and the proposed method should be able to find all those when searched for.
I am working on a coldbox application where I would like to create a route that accepts 'n' number of path variables as one variable. Here is what I mean.
http://localhost/api/variable1/variable2/variable3/...
I would like to either be able to grab everything after /api as one path variable where I can split on / and get the values or be able to iterate over all variables after /api.
Is there a way to setup a Route to do this?
with(pattern="/api", handler="api")
.addRoute(pattern="/:variables", action="index")
.endWith();
Any ideas would be most appreciated. Thanks in advance.
As you probably know, the default routing paradigm is to do name value pairs like so:
http://localhost/api/name1/value1/name2/value2/name3/value3
There is no need to create a custom route for that as everything after the matched part of the route is broken up into name/value pairs and placed in the rc automatically.
Now, it sounds like you're wanting to only have values in your route. If you know the maximum number of variables you'll ever have, you could create a route of optional, incrementally-named variables.
addRoute(pattern="/:var1?/:var2?/:var3?/:var4?/:var5?", action="index")
Now, if you truly might have an unlimited number of variables, there is no way to do a route that will match that. What you CAN do is have your route match the /api bit and write an onRequestCapture interceptor that grabs the URL and does your own custom parsing on it. Note, you may need to remove the name/value pairs that ColdBox will try to put in the rc.
I will add a note of caution-- the only way for this to really work is for you to KNOW the order of the incoming variables ahead of time, and if you know that, there is no reason why you can't create a known route for it. Otherwise you're basically rebuilding the SES interceptor over again which is an anti-pattern called "inner platform effect"
http://wiki.coldbox.org/wiki/URLMappings.cfm#URL_Mappings
http://wiki.coldbox.org/wiki/Interceptors.cfm#Core_Interception_Points
http://en.wikipedia.org/wiki/Inner-platform_effect
I need to write a CI route so that it loads the right controller. What I want to do is write a route that excludes the "features" controller. Here are my routes (but the first one doesn't work).
$route['(\w{2})/(\w{2})/products/([\w]+!features)'] = "products/products/$3"; // folder/controller/method
$route['(\w{2})/(\w{2})/products/features/([\w]+)'] = "products/features/$3"; // folder/controller/method
What I want to have happen is the top line should load any controller that is not the features controller. But I am getting conflicts between the two lines. I've tried placing the "!features" in a couple of different places in the line, with and without quotes and I'm still getting either the features controller to load or one of the other methods in the products controller to load. But not both. Can someone help out here? Thanks.
Try changing the first line to:
$route['(\w{2})/(\w{2})/products/(?!features$)(\w+)'] = "products/products/$3";
(?!foo) is a negative lookahead, if fails if whatever is following it matches foo.
$ means end of string, and is there to make sure that features is not part of a longer word.