NancyFx route seems broken if value has encoded forward slash - url-routing

I have the following route:
Get["/details/{id:int}/{token}"]
It works as expected, except for when the token part includes an encoded forward slash ("/" = "%2f").
Example that throws a 404:
/details/1/QeH7oMezCNS3y%2fASnD4dGw%3d%3d
Example that works fine (no "/" included):
/details/1/QeH7oMezCNS3yASnD4dGw%3d%3d
If this is by design, are there any other characters I need to handle?

You need to use Greedy Segment (*) in your route:
Get["/details/{id:int}/{token*}"]

This is most likely a known bug to do with double encoding.

I had the same problem and solved it differenty by writing my own URLencode and URLdecode functions which replace all "/" by another char which is never used in the token (e.g. ';') and then to HttpUtility.URLencode() on the edited token (the decoder function the other way around).
The Greedy Segment solution did not work easily, since I had this token in the middle of the route, not at the end, and other similar routes which might interfere with the Greedy Segment.
public static string UrlEncode(string path)
{
return HttpUtility.UrlEncode(path.Replace(Path.DirectorySeparatorChar, Variables.PATH_SEP));
}
public static string UrlDecode(string path)
{
return HttpUtility.UrlDecode(path).Replace(Variables.PATH_SEP, Path.DirectorySeparatorChar);
}

Related

Calling a camel route using Producer Template

My use case is based on the rest controller input I need to fetch or move files from different source system to destination system.
Route :-
#Component
public class MoveFile extends RouteBuilder {
#override
public void configure() throws Exception {
from("file:tmp/${header.inPath}")
.to("file:/tmp${header.outPath}?fileName=${header.fileName}")
.setBody().constant("File - ${header.inPath}/${header.fileName} Moved Succesfully")
}
}
My rest controller will pass the jobName along the getMapping to invoke this specific route inPath , outPath and File Names
#Resource(name=RouteProperties)
private Prosperties props;
#GetMapping("/runJob/{jobToInvoke}")
public String runJob (#PathVariable final String jobToInvoke){
String inPath=props.getProperty("inPath"+jobToInvoke)
String outPath=props.getProperty("outPath"+jobToInvoke)
String fileName=props.getProperty("fileName"+jobToInvoke)
String jobStatus = ProducerTemplate.withHeader("inPath",inPath)
.
.
.to(??)
.request(String.class)
}
I need help to use Producer Template to pass the properties using to ?
I tried some search on the google, but there is an example available in youtube (link) , But in that Video it is calling uri , (Direct:sendMessage) and from in the route also has that.
How to handle in this scenario ?
Thanks in Advance
A route beginning with a direct: endpoint can be invoked programmatically from Java code. In the route, the pollEnrich component invokes a consumer endpoint to read a file and replace the exchange message body with the file contents.
from("direct:start")
.pollEnrich().simple("file:/tmp?fileName=${header.inPath}")
.toD("file:/tmp?fileName=${header.outPath}")
.setBody().simple("File - ${header.inPath} Moved Successfully");
To invoke the route from Java code:
String jobStatus = producerTemplate.withHeader("inPath", inPath)
.withHeader("outPath", outPath)
.to("direct:start")
.request(String.class);
I don't know if these dynamic file URIs in from work, but at least the Camel File documentation states
Also, the starting directory must not contain dynamic expressions with
${ } placeholders. Again use the fileName option to specify the
dynamic part of the filename.
So the docs are suggesting to change
from("file:tmp/${header.inPath}")
into
from("file:tmp?fileName=${header.inPath}")
The fileName option can be a relative path (not just a filename).
If that change works for you, your question becomes obsolete because the route URI is no more dynamic.
.withHeader("inPath",inPath)
.to("file:tmp")

Camel: how to aggregate files based on exchange in pattern

I have a class to run my route; The input comes from a queue (which is filled by a route that does a query and inserts the rows as messages on the queue)
These messages each contain a few headers:
- pdu_id, basically a prefetch on the filename.
- pad: the path the files reside in
What is to happen: I want the files in the path named by their "pdu_id".* in a tar; After that a REST call is to be done to remove the documents source.
I know a route has a from; but basically I need a route with a dynamic "from", and as below code example shows, queueing froms doesn't do the trick.
The question is what to use instead; I could not find a similar thing, but it can be I didn't use the right google search; in which case I'm deeply sorry.
public class ToDeleteTarAndDeleteRoute extends RouteBuilder {
#Override
public void configure() throws Exception
{
from("broker1:todelete.message_ids.queue")
.from("file:///?fileName=${in.header.pad}${in.header.pdu_id}.*")
.aggregate(new TarAggregationStrategy())
.constant(true)
.completionFromBatchConsumer()
.eagerCheckCompletion()
.to("file:///?fileName=${in.header.pad}${in.header.pdu_id}.tar")
.log("${header.pdu_id} tarred")
.setHeader(Exchange.HTTP_METHOD, constant("DELETE"))
.setHeader("Connection", constant("Close"))
.enrich()
.simple("http:127.0.0.1/restfuldb${header.pdu_id}?httpClient.authenticationPreemptive=true")
.log("${header.pdu_id} tarred and deleted.");
}
}
Yes. Poll enrich can help you in doing it. You should use it something like this:
from("broker1:todelete.message_ids.queue")
.aggregationStrategy(new TarAggregationStrategy())
.pollEnrich()
.simple("file:///?fileName=${in.header.pad}/${in.header.pdu_id}.*")
.unmarshal().string()
.to("file:///?fileName=${in.header.pad}/${in.header.pdu_id}.tar")
.log("${header.pdu_id} tarred")
.setHeader(Exchange.HTTP_METHOD, constant("DELETE"))
.setHeader("Connection", constant("Close"))
.enrich()
.simple("http:127.0.0.1/restfuldb${header.pdu_id}?httpClient.authenticationPreemptive=true")
.log("${header.pdu_id} tarred and deleted.");
Currently the solution to the problem consisted of a few changes based on what #daBigBug answered.
pollEnrich's simple expression uses antInclude rather than fileName;
aggregate is put after pollenrich; as each batch is the set of files, rather than the input from the queue. The input from the queue only provides meta information based on which actions are to be taken.
aggregationStrategy() is not possible in a RouteBuilder; I used aggregate() instead.
I removed the unmarshal(); I don't see why this would be needed; the files can contain binary content.
from("broker1:todelete.message_ids.queue")
.pollEnrich()
.simple("file:${in.header.pad}?antInclude=${in.header.pdu_id}.*")
.aggregate(new TarAggregationStrategy())
.constant(true)
.completionFromBatchConsumer()
.eagerCheckCompletion()
.log("tarring to: ${header.pad}${header.pdu_id}.tar")
.setHeader(Exchange.FILE_NAME, simple("${header.pdu_id}.tar"))
.setHeader(Exchange.FILE_PATH, simple("${header.pad}"))
.to("file://ignored")
...(and the rest of the operations);
I now see the files are getting picked up and even placed in a tar; however, the filename of the tar is unexpected as is the location (it's placed in ./ignored); Also in the rest of the operation, it appears the exchange headers are lost.
If anyone can help figure out how to preserve the headers in a safe way... I'm much obliged. Should I use a new question for that, or should I rephrase the question.

PlaybackNearlyFinished and PlaybackFinished occur almost at the same time?

It turns out, that sometimes (not always btw but very frequently) PlaybackNearlyFinished and PlaybackFinished occur almost at the same time. What also confuses is that both of the events convey exactly the same offset that represents the very end of the stream:
When this happens the next stream scheduled in PlaybackNearlyFinished does not go - the playback just finishes.
Unless this is a bug in Alexa/Infrastructure, I can not figure out how to implement a playback for a playlist - there's just no way to reliably schedule the upcoming track...
Is there anything I could do in my code to make it work well?
I am using Echo Dot 2 gen., physically located in Europe, use java SDK, AWS Lambda, Dynamo DB.
Looks like it is figured out now - in order to queue the next stream properly one needs to come up with really unique stream tokens. This means that even the same file/url should be enqueued under a unique token.
In my example above, I used the index of the track in the playlist as an token. Once I solved it like below, everything started to work like a charm:
import org.apache.commons.lang3.RandomStringUtils;
public class TokenService {
public String createToken(int playbackPosition) {
String suffix = RandomStringUtils.randomAlphanumeric(16);
return String.valueOf(playbackPosition) + ":" + suffix;
}
public int tokenToPlaybackIndex(String token) {
String positionStr = token.split(":")[0];
return Integer.valueOf(positionStr);
}
}
Hope it helps somebody!

Use a file as input to unit tests

I think I have seen an elegant way to use a file as input for a unit test in apache camel but my google skills are failing me.
What I want is instead of:
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
<snip>...long real life xml that quickly fills up test files.</snip>";
template.sendBody("direct:create", xml);
What I think I have seen is something like
template.sendBody("direct:create", someCamelMetod("/src/data/someXmlFile.xml"));
Anybody knows where/if this is documented?
Edit:
Cross posted to http://camel.465427.n5.nabble.com/Use-a-file-as-input-to-unit-tests-td5722069.html
What I ended up doing was just creating a
private void readFile(String fileName) throws ... function
Still interested if anyone knows a nicer way though.
If I understand your question correctly you want to send an xml file as input to the route you want to test. My solution would be to use the adviseWith strategy which is a part of the Camel test support. Read about it here: http://camel.apache.org/testing.html
So, say that the route under test is something like this:
from("jms:myQueue")
.routeId("route-1")
.beanRef(myTransformationBean)
.to("file:outputDirectory");
in your test you can send xml into this route by replacing this from a file polling consumer.
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
replaceRouteFromWith("route-1", "file:myInputDirectory");
}
});
context.start();
Then you can put your input xml file in the myInputDirectory and it will be picket up and used as input to the route.
Not really, you have to do some minor work yourself. You know, reading a text file isn't that simple since you might want to know the encoding. In your first case (inline string), you're always using UTF-16. A file can be whatever, and you have to know it, since it won't tell you what encoding it is. Given you have UTF-8, you can do something like this:
public String streamToString(InputStream str){
Scanner scanner = new Scanner(is, "UTF-8").useDelimiter("\\A");
if (scanner.hasNext())
return scanner.next();
return "";
}
// from classpath using ObjectHelper from Camel.
template.sendBody("direct:create", streamToString(ObjectHelper.loadResourceAsStream("/src/data/someXmlFile.xml")));

HTTP uri in a GTK# FileChooserDialog

Can GTK#'s FileChooserDialog be used as a unified file/URI dialog? I'd like it to accept http/https/ftp URIs without "rewriting" them (prepending local directory).
Even if I set LocalOnly=false and paste a http://.... uri into the text box inside the filechooser, I cannot get the original entry. Local directory is always prepended to the text.
I've done some research, and I don't think it's possible. At least not with the direct native C GTK+ API, which is what I tested.
In my testing, I always either got the local directory's path prepended to the http:// URI I had entered in the dialog, or I got back (null). I did call the get_uri() method, not just get_filename().
I also took a quick look, as a reference, at the GIMP application's File menu. As you probably know, GIMP provides the G in GTK+, so it can sometimes be used as a reference for ideas on how to use the toolkit. GIMP does not try to support URIs entered in the file chooser dialog, instead it has a dedicated Open Location command, that opens a simple dialog with just a GtkEntry.
I think you need to set local-only to FALSE and then use the GIO get_file ()/get_files () calls which return a GFile* accessible through the GIO File API and therefore through gvfs.
I found a solution / hack after all (in C#):
private string _extractUri(Widget wi) {
if (wi is Entry)
return ((wi as Entry).Text);
else if (wi is Container) {
foreach (Widget w in (wi as Container).Children) {
string x = _extractUri(w);
if (x!=null)
return x;
}
}
return null;
}
I'm not sure if that's always safe, but it worked for the standard FileChooserDialog. It will return the original string from the input field - even if standard Uri / File results are mangled.

Resources