Perl Dancer trailing slash - perl

Using the Perl web application framework Dancer, I am having some problems with trailing slashes in the URL matching.
Say for example, I want to match the following URL, with an optional Id parameter:
get '/users/:id?' => sub
{
#Do something
}
Both /users/morgan and /users/ match. Though /users will not. Which does not seem very uniform. Since I would prefer, only matching the URL:s without the trailing slash:
/users/morgan and /users. How would I achieve that?

Another approach is to use a named sub - all the examples of Dancer code tend to use anonymous subs, but there's nothing that says it has to be anonymous.
get '/users' => \&show_users;
get '/users/:id' => \&show_users;
sub show_users
{
#Do something
}
Note that, due to the way Dancer does the route matching, this is order-dependent and, in my experience, I've had to list the routes with fewer elements first.

id will contains everything from /user/ on until an optional slash.
get qr{^/users/?(?<id>[^/]+)?$} => sub {
my $captures = captures;
if ( defined $captures->{id} ) {
return sprintf 'the id is: %s', $captures->{id};
}
else {
return 'global user page'
}
};

I know this is an old question, but I've recently solved this problem by using a Plack middleware. There are two of them you can choose from depending on whether you prefer URLs with trailing slashes or not:
Plack::Middleware::TrailingSlash
Plack::Middleware::TrailingSlashKiller
Using any of the middleware above should greatly simplify your core Dancer application code and unit tests since you do not need to handle both cases.
In addition, as mentioned by Dave Sherohman, you should definitely arrange your routes with the fewer elements first in order to match those first, especially if you use the TrailingSlash middleware to force trailing slashes.

Related

Exim getting random credential in exim.conf

I have been trying to get perl subroutine value and substitution to get the required part of string from randomips subroutine in exim.conf. However when i use string substitution i get error as follow:
Here is what I am trying to achieve
I am trying to split string by colon and get first occurrence as "interface". I'll be using second occurrence as the "helo_data.
exim.pl
sub randomhosts {
#inet = ("x.x.x.1:hostname1.domain.com","x.x.x.2:hostname2.domain.com","x.x.x.3:hostname3.domain.com"
);
return $inet[int rand($#inet+1)];
}
exim.conf
dkim_remote_smtp:
driver = smtp
interface = "${perl{randomhosts}%:*}"
helo_data = "${sender_address_domain}"
Error I get is as follow:
"failed to expand "interface" option for dkim_remote_smtp transport: missing '}' after 'perl'".
Probably the syntax.
Any help?
The code that you are trying to copy was written by someone who doesn't know much about Perl. It includes this line:
return $inet[int rand($#inet+1)];
A Perl programmer would write this as
return $inet[rand #inet];
I think there are a couple of issues here - one with your Exim syntax and one with your Perl syntax.
Exim is giving you this error:
failed to expand "interface" option for dkim_remote_smtp transport: missing '}' after 'perl'
I don't know anything about calling Perl from Exim, but this page mentions a syntax like ${perl{foo}} (which is similar to the one used in the page you are copying from) and one like ${perl{foo}{argument}} for calling a subroutine and passing it an argument. Nowhere does it mention syntax like yours:
${perl{randomhosts}%:*}
I'm not sure where you have got that syntax from, but it seems likely that this is what is causing your first error.
In a comment, you say
I am stying to get first part of string before colon for each random array value for "interface" and part after colon for "helo_data"
It seems to me that Exim doesn't support this requirement. You would need to call the function twice to get the two pieces of information that you require. You might be able to do this in the Perl using something like state variables - but it would be far more complex than the code you currently have there.
Secondly, your Perl code has a syntax error, so even if Exim was able to call your code, it wouldn't work.
The code you're copying sets up #inet like this:
#inet = ("x.x.x.1", "x.x.x.2", "x.x.x.3", "x.x.x.4");
Your equivalent code is this:
#inet = (
"x.x.x.1:hostname1.domain.com",
"x.x.x.2:hostname2.domain.com,
x.x.x.3:hostname3.domain.com
);
I've reformatted it, to make the problems more obvious. You are missing a number of quote marks around the elements of the array. (Note: I see that while I have been writing this answer, you have fixed that.)
Update: Ok, here is some code to put into exim.pl that does what you want.
use feature qw[state];
sub randomhosts {
state $current;
my #inet = (
"x.x.x.1:hostname1.domain.com",
"x.x.x.2:hostname2.domain.com",
"x.x.x.3:hostname3.domain.com"
);
if ($_[0] eq 'generate') {
shift;
#{$current}{qw[ip host]} = split /:/, $inet[rand #inet];
}
return $current->{$_[0]};
}
It generates a new ip/host pair if its first argument is 'generate'. It will then return either the hostname or the ip address from the generated pair. I think you can probably call it from your Exim config file like this:
dkim_remote_smtp:
driver = smtp
interface = "${perl{randomhosts}{generate}{ip}}"
helo_data = "${perl{randomhosts}{host}}"
But I'm no expert in Exim, so that syntax might need tweaking.
First I would like to note I have not worked with exim so I cannot say what exactly you are trying to do and why you have done things exactly so.
In the link you posted, a method called 'randinet' is added to exim.pl and the interface line in exim.conf is replaced by
interface = "${perl{randinet}}"
You have implemented a 'randomhosts' method and replaced the interface line with
interface = "${perl{randomhosts}%:*}"
Now the parser complains about not finding the closing bracket. That is likely due to the symbols you felt free to add but the parser does not have the freedom to ignore.
I suggest you try
interface = "${perl{randomhosts}}"

How Can I parse a request uri containing specific word

I am trying to handle a request containing word "filter". For the time being, I am using the url as
http://localhost:9997/filter=....
and parsing by using pathPrefix(fiter)
But url will change and becomes like
http://localhost:9997/something../filter=
So here I can't take pathPrefix().
How can I handle this kind of path in routing so that any url containing "filter" keyword it can handle.
I am very much new to akka spray. Please let me know about your opinions. Thanks in advance
If the string you want to match will always be at the end you can use "pathSuffix" (it might be enough for what you want)
pathSuffix("filter") { ... }
But if it can be anywhere in the path, is not that straighforward. One way is using segments:
path( Segment / "filter" / Segment ){ (prefix, postfix) => {...} }
But it doesn't match if filter is at either end: "filter/blah/blah" nor "balh/blah/filter", but you can work around by matching with prefix and suffix. I suppose there's a better way, maybe with a custom directive.
Take a look at
http://spray.io/documentation/1.2.3/spray-routing/predefined-directives-alphabetically/#predefined-directives

Perl access to Amazon marketplace offers via the product API

I need to use the Amazon Product API via perl to get a list of third party new and used (marketplace) offers given an ASIN. I need to be able to see prices and whether each offer is fulfilled by amazon (eligible for prime/super saver shipping). I've scoured the Net::Amazon module, and I don't see any way to do this.
Has anyone done anything similar?
So I was looking in to this myself, and it looks like items offered by Amazon have the attribute 'IsEligibleForSuperSaverShipping' => '1' in the offers. So this would be something like:
my $ua = Net::Amazon->new(
associate_tag => 'derpderp',
token => 'morederp',
secret_key => 'herpaderp',
);
my $rsp = $ua->search( asin => 'B000FN65ZG' );
if ($rsp->is_success()) {
if (my $o = $rsp->{xmlref}->{Items}->{Offers}) {
foreach my $offer (keys %{ $o }) {
if ($o->{$offer}->{IsEligibleForSuperSaverShipping}) {
# This is offered by Amazon.com
} # if
} # foreach
}
else {
die "Error: ", $rsp->message(), "\n";
}
note, as of writing (04-Nov-13), that ASIN is fulfilled through Amazon; it may not be in the future.
The problem here is that Net::Amazon generates its accessors magically (and not using Class::Accessor, but it's old, so we can forgive it…). I am not sure what the correct accessor is for the individual offers within the {Items} element above. Reaching into your object is kind of fraught with peril, but in this case, finding the right accessor should not be so hard (given it is automagicaly generated) and barring that, I think you can feel comfortable reaching right into the object.
Also, you might suggest reaching out to the author's author, Mike Schilli, or current maintainer, Christopher Boumenot, might be worth doing, especially if this is something that's in the result from Amazon consistently and could just be added to the API. The problem with this is that the return from Amazon is kind of variable. Quoting the perldoc,
Methods vary, depending on the item returned from a query. Here's the most common ones. They're all accessors, meaning they can be used like Method() to retrieve the value or like Method($value) to set the value of the field.
This makes it tricky to assume that you can test the return for super-saver-shipping-ness because it may just not have that key in the structure returned.

In Sinatra, how to make filters dependent of request method?

I've been using filters in Sinatra the way it has been declared in the documentation: with no match string, with a match string or with a match regexp. It has been working fine til now. Now, I have a particular use case. Let's say I have this route:
/resources/1
According to REST, and depending of the request method, this can either be a GET method, PUT method or DELETE method. First question is: How to write filters that are only called when it is a GET request? (currently I'm letting all of them get filtered and only then I test the method. It works, but I don't like it). Second question, and more important: let's say a PUT request like this is triggered:
/resources/
This is of course wrong, because the PUT request has no resource id associated. I would like to know if there is something in Sinatra that enables me to do something like this:
before "/resources/", :method => :put do
error_message
end
just this possibility does not exist (before accepts only one argument). How could I achieve this result at best?
Actually, filters do take conditions. You don't have to use a condition though, you could use a conditional within the filter:
before "/path/" do
if request.request_method == "PUT"
# do something
end
end
If you want to use a condition, I think you'll need to write one, perhaps something like this:
set(:accepted_verbs) {|*verbs|
condition {
verbs.any?{|v| v == request.request_method }
}
}
before "/path/", :accepted_verbs => ["GET","POST"] do
# do something
end
before "/path/", :accepted_verbs => ["PUT"] do
# do something else
end
See conditions for more.

How do I create an absolute URL from two components, in Perl?

Suppose I have:
my $a = "http://site.com";
my $part = "index.html";
my $full = join($a,$part);
print $full;
>> http://site.com/index.html
What do I have to use as join, in order to get my snippet to work?
EDIT: I'm looking for something more general. What if a ends with a slash, and part starts with one? I'm sure in some module, someone has this covered.
I believe what you're looking for is URI::Split, e.g.:
use URI::Split qw(uri_join);
$uri = uri_join('http', 'site.com', 'index.html')
use URI;
URI->new("index.html")->abs("http://site.com")
will produce
"http://site.com/index.html"
URI->abs will take care of merging the paths properly following your uri specification,
so
URI->new("/bah")->abs("http://site.com/bar")
will produce
"http://site.com/bah"
and
URI->new("index.html")->abs("http://site.com/barf")
will produce
"http://site.com/barf/index.html"
and
URI->new("../uplevel/foo")->abs("http://site.com/foo/bar/barf")
will produce
"http://site.com/foo/uplevel/foo"
alternatively, there's a shortcut sub in URI namespace that I just noticed:
URI->new_abs($url, $base_url)
so
URI->new_abs("index.html", "http://site.com")
will produce
"http://site.com/index.html"
and so on.
No need for ‘join‘, just use string interpolation.
my $a = "http://site.com";
my $part = "index.html";
my $full = "$a/$part";
print $full;
>> http://site.com/index.html
Update:
Not everything requires a module. CPAN is wonderful, but restraint is needed.
The simple approach above works very well if you have clean inputs. If you need to handle unevenly formatted strings, you will need to normalize them somehow. Using a library in the URI namespace that meets your needs is probably the best course of action if you need to handle user input. If the variance is minor File::Spec or a little manual clean-up may be good enough for your needs.
my $a = 'http://site.com';
my #paths = qw( /foo/bar foo //foo/bar );
# bad paths don't work:
print join "\n", "Bad URIs:", map "$a/$_", #paths;
my #cleaned = map s:^/+::, #paths;
print join "\n", "Cleaned URIs:", map "$a/$_", #paths;
When you have to handle bad stuff like $path = /./foo/.././foo/../foo/bar; is when you want definitely want to use a library. Of course, this could be sorted out using File::Spec's cannonical path function.
If you are worried about bad/bizarre stuff in the URI rather than just path issues (usernames, passwords, bizarre protocol specifiers) or URL encoding of strings, then using a URI library is really important, and is indisputably not overkill.
You might want to take a look at this, an implementation of a function similar to Python's urljoin, but in Perl:
http://sveinbjorn.org/urljoin_function_implemented_using_Perl
As I am used to Java java.net.URL methods, I was looking for a similar way to concatenate URI without any assumption about scheme, host or port (in my case, it is for possibly complex Subversion URL):
http://site.com/page/index.html
+ images/background.jpg
=> http://site.com/page/images/background.jpg
Here is the way to do it in Perl:
use URI;
my $base = URI->new("http://site.com/page/index.html");
my $result = URI->new_abs("images/background.jpg", $base);