Is there any documentation about routing template format for undertow. I want to setup handlers like this:
/ or /index.html -> Use handler 1
Anything else -> Use handler 2
I tried this one, bu did not work:
Handlers.routing()
.add("GET", "/", handler1)
.add("GET", "/index.html", handler1)
.add("GET", "/*", handler2)
Any idea?
There are a couple of ways to achieve this:
1) Basic approach: PathHandler
Handlers.path()
.addExactPath("/path1", handler1)
.addPrefixPath("/path2", handler2);
The handler1 will match only on /path1 (or /path1/).
The handler2 will match on /path2, /path2/ and everything else that starts with /path2/.
2) Route approach: RoutingHandler
If you use a RoutingHandler, you have the option to easily extract variables from the paths. That's convenient for building REST APIs for example (note the usage of the convenience get method on the RoutingHandler).
Handlers.routing().get("/{test}/*", exchange -> {
PathTemplateMatch pathMatch = exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY);
String itemId1 = pathMatch.getParameters().get("test"); // or exchange.getQueryParameters().get("test")
String itemId2 = pathMatch.getParameters().get("*"); // or exchange.getQueryParameters().get("*")
}))
The * parameter can match anything (like a path for instance a/b/c).
In order to use the * parameter, you need an actual named parameter defined before in the route template (test in my example).
Note that the parameters defined in your route template will be available together with the query parameters (exchange.getQueryParameters()). This is default behavior. If you do not want it, you can create your routing handler like this: Handlers.routing(false).get(...) and then retrieve the parameters from the exchange's attachments.
For any route that is not matched by your routing handler, you can use the fallbackHandler available in the RoutingHandler.
Handlers.routing()
.get("/", handler1)
.get("/index.html", handler1)
.setFallbackHandler(handler2);
By default the fallbackHandler simply returns an empty response body with a 404 status code. The handler2 will be matching any other requests, not only GET requests.
Comprehensive Example
You can of course combine PathHandler and RoutingHandler to fit your needs.
Here is a small example of a more realistic setup:
Undertow.builder().addHttpListener(8080, "0.0.0.0")
.setHandler(Handlers.path()
// REST API path
.addPrefixPath("/api", Handlers.routing()
.get("/customers", exchange -> {...})
.delete("/customers/{customerId}", exchange -> {...})
.setFallbackHandler(exchange -> {...}))
// Redirect root path to /static to serve the index.html by default
.addExactPath("/", Handlers.redirect("/static"))
// Serve all static files from a folder
.addPrefixPath("/static", new ResourceHandler(
new PathResourceManager(Paths.get("/path/to/www/"), 100))
.setWelcomeFiles("index.html"))
).build().start();
This application also serves static files from your file system. This is handy to serve a javascript application or static html files for instance.
Related
I'm making an URL shortener with the Javalin framework and have this endpoint set up:
app.routes(()->{
path("",()->{
get("/:id", ctx->{
//do stuff
ctx.redirect("somewhere.com");
});
});
});
Problem is when I need to serve a javascript file to load into my html files. It tries to load from http://localhost:7000/qrcode.min.js but ends up going to the endpoint mentioned above. From what I read in the documentation this is normal behaviour, Javalin first runs the endpoint handler and then (if it doesn't find an endpoint) runs the file handler.
So how can I fix this? should I define a GET request at "/qrcode.min.js"?, I dont think the javalin context handler has a function that lets me return a .js file.
As Matt already suggested in a comment, it would be way cleaner if you'd prefix either path. That way, you could have /r/:id (or /u/:id with "u" for "URL") and the static files would not get in your way, or you could prefix your static files with e.g. /static/, or even just /s/ for brevity, and your shortened URLs would not get in your way.
If you, however, prefer to stick with your current scheme, you can simply filter out JavaScript files (or any other non-id request) in the handler and instead provide the file (however, if you previously had auto-generated ETags, you'd lose caching if you don't want to handle that yourself).
The latter solution would look like so:
app.routes (() -> {
path ("", () -> {
get ("/:id", ctx -> {
String id = ctx.pathParam ("id");
if (id.endsWith (".js")) {
String resourcePath = "your/classpath/resources/folder/" + id;
try {
InputStream resultStream = Thread.currentThread ()
.getContextClassLoader ()
.getResourceAsStream (resourcePath);
if (resultStream == null)
throw new NullPointerException ("Script not found");
ctx.contentType ("application/javascript");
ctx.result (resultStream);
} catch (NullPointerException e) { // script does not exist
e.printStackTrace (); // for development only!
ctx.status (404);
}
return;
}
// do stuff
ctx.redirect ("somewhere.com");
});
});
});
Depending on your preference, you can also handle the resultStream == null case where my code is currently throwing an NPE to be caught by the outer try/catch and omit the try/catch completely.
Setting the Content-Type is essential so that the browser knows that you're actually responding with JavaScript code. Also, I'm typically using Thread.currentThread ().getContextClassLoader () because we'd want the resource to be resolved based upon the current HTTP handler thread, which could, in theory, have a different class path/class loader than the class we're currently in.
Please note that, as stated above, this will not support client-side caching as the handler simply ignores all ETag headers sent with the request* and instead respond with the complete file which, with many requests in a short amount of time and large scripts, will certainly put way more stress on your disks and CPUs.
Thus, I'd actually recommend to prefix the static files route and let Javalin/Jetty handle all the caching and files magic.
* Actually, the header sent by the client is If-None-Match most of the time. The server would respond with an ETag to allow for caching in the browser.
I'm trying to filter the list of instance based on the machine type. However this doesn't seem to work.
Compute.Instances.List request = computeService.instances().list("project-name","us-central1-a" );
request.setFilter("(machinetype = zones/us-central1-a/machineTypes/n1-standard-1)");
InstanceList instanceList = request.execute();
List<Instance> instances = instanceList.getItems();
The response is empty even though, I have an instance that match the filter! (when I remove the filter it gets the instance.)
[chaker#cbenhamed:~]$ gcloud compute instances list
NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS
foo-bar-worker-n1-standard-1-65304152130-zfq us-central1-a n1-standard-1 true 10.240.0.2 00.000.00.255 RUNNING
According to the documentation, the filter parameter should work in this case. Because, first the machineType is in the root of the Instance object. And second that's the right form of the machineType argument
Full or partial URL of the machine type resource to use for this instance, in the format: zones/zone/machineTypes/machine-type. This is provided by the client when the instance is created.
I tried to inspect HTTP requests made by gcloud
gcloud compute instances list --filter="machineType:n1-standard-1" --log-http
But it turned out that it gets the whole list (across all zones!) and filter them locally!
It seems to be a misunderstanding, the documentation describes the machineType as an argument of the Response body no as a filter. So in this case you can't use a partial URL also you can only use the following comparison operators =, !=, >, or < which none of them works as a like.
I think the only way to use this filter is using the full URL just as Oleksandr Bushkovskyi commented:
machineType="https://www.googleapis.com/compute/v1/projects/[PROJECT]/zones/[ZONE]/machineTypes/[MACHINE_TYPE]"
I have a need to extract a field parsed from a "complex" response header and use that value later in the test.
It seems that the "header" keyword in Karate is set up for setting request headers, not parsing response headers.
Is there a way to add a custom step definition maintaining access to the scenario variable stores? It appears the variable stores are private in the StepDefs class, and there doesn't seem to be a way to extend it easily.
You can get access to the response headers. Please look at the documentation for responseHeaders.
That said, the match header short-cut is most likely what you are looking for.
Karate's philosophy is that you never need to write custom step-definitions.
edit: some examples, sounds like you just need to do some string manipulation of the Location header ? You can freely mix JS code into Karate expressions.
* def location = responseHeaders['Location'][0]
# assume location = 'foo?bar=baz'
* def bar = location.substring(location.indexOf('bar=') + 4)
I want to create a URL structure for my Vert.x Web powered API that makes it clear how some entities are "contained" inside other entities and how you "traverse the entity path" to find child entities, so I'm thinking of using something like this to address a "grand child" (I don't expect anything deeper than a grand child):
GET /entity/:parent_id/sub-entity/:child_id/subsub-entity/:grandchild_id
So normally my Router configuration will look something like this:
router.get("/entity/:parent_id/sub-entity/:child_id/subsub-entity/:grandchild_id")
.handler(r -> {
HttpServerRequest req = r.request();
Minipart p = Entities.get(req.getParameter("parent_id"))
.getPart(req.getParameter("child_id"))
.getMinipart(req.getParameter("grandchild_id"));
// do something with p
});
When I add a lot of operations (each entity class at each level has catalog and create operations, and each level entity instance has get, update and delete operations, as well as a few other tidbits), my router class gets really large.
I was thinking of using sub-routers to offload the sub-entity management down the line, so the Entities Router configuration might do:
router.mountSubRouter("/entity/:parent_id/sub-entity", PartsRouter.init(vertx));
and then PartsRouter can do:
router.get("/:child_id").handler(r -> {
String parentEntityId = r.request().getParameter("parent_id");
Entity parent = Entities.get(parentEntityId);
String myid = r.request().getParameter("child_id");
Part myself = parent.getPart(myid);
// do something with myself
});
But when I do try that and try to access the sub-router operations, I get a 404 error from Vert.x...
Update:
Apparently Vert.x explicitly does not support this - it threw an exception that my wrapper code just logged and ignored, saying:
java.lang.IllegalArgumentException: Can't use patterns in subrouter mounts
So, is there another way to achieve what I'm trying to do (split a large router configuration into a proper class hierarchy)?
I can image 2 ways of solving your problem:
The first one would be to have a initial handler that processes the common part of the request and calls next so the following one will continue where the first stopped e.g.:
router.route("/entity/:parent_id/sub-entity", ctx -> {
// common part here...
ctx.next();
});
And then:
router.route("/entity/:parent_id/sub-entity/:child_id", ctx -> {
String parentEntityId = r.request().getParameter("parent_id");
Entity parent = Entities.get(parentEntityId);
String myid = r.request().getParameter("child_id");
Part myself = parent.getPart(myid);
// do something with myself
});
Alternatively you can use internal redirects, so you handle the initial code as before but instead of calling next() you redirect to another URL. In that case you should store in the context what you want to reuse since the request will be restarted in the new location.
I have defined a ftp:inbound-channel-adapter which will be triggered by a job and a custom filter which checks if filename matches a pattern and also if it was not processed before for the particular job.
So this requires that the jobname be available to the FTP filter. Is there a way by which this can be passed to the filter? Also is there a way to add jobname as a header to messages emitted by the ftp adapter?
<int-ftp:inbound-channel-adapter
local-directory="${data.dir}" session-factory="ftpClientFactory"
channel="ftpOutputChannel" remote-directory="${ftp.data.directory}"
filter="ftpFilter">
<int:poller fixed-rate="50000" />
<!--This will be replaced by a trigger job -->
</int-ftp:inbound-channel-adapter>
<beans:bean class="com.example.ftp.FtpFilter"
id="ftpFilter"></beans:bean>
com.example.ftp.FtpFilter
public class FtpFilter implements FileListFilter<FTPFile> {
public List<FTPFile> filterFiles(FTPFile[] files) {
String validRegex = FileFilterStrategy.getValidTarGzRegex();
for (FTPFile file : files) {
String name = file.getName();
if(name.matches(validRegex) && !isProcessed(jobName, name)){
retval.add(file);
}
}
return retval ;
}
}
Yes, just get a reference to the filter and change some property on it.
There's currently no way to modify the headers in the way you describe; you could play some tricks in the filter using a ThreadLocal.
However, for these "event driven" scenarios, I generally recommend using an outbound gateway to fetch the file(s) rather than the inbound channel adapter.
It gives you much more control - you can get (or mget) a pattern, or ls the remote directory and decide yourself which file(s) to get.
Also see the ftp gateway sample app which uses ls, get and rm gateways.