joeldev / jlroutes Goto Github PK
View Code? Open in Web Editor NEWURL routing library for iOS with a simple block-based API
License: BSD 3-Clause "New" or "Revised" License
URL routing library for iOS with a simple block-based API
License: BSD 3-Clause "New" or "Revised" License
If my url is like
app://webview?url=http://images.domain.net/assets/gif/domain/push.html?kod=213"
in JLRoutes_URLParameterDictionary method
NSString *paramValue = pair.count == 2 ? pair[1] : @"";
for in this case ,my pair count 3 so
paramValue returns @"" but I need paramValue http://images.domain.net/assets/gif/domain/push.html?kod=213
This one gets me from time to time, as the process of routing a URL ends up adding a route elsewhere, and the outcome is always "fun".
routeURL:withParameters:executeRouteBlock:
uses fast enumeration of self.routes
to match for a valid route handler.
However, while this enumeration is happening, register
or remove
methods may be called which will modify the mutable array of self.routes.
I suggest that in routeURL:withParameters:executeRouteBlock:
you first copy self.routes to an immutable NSArray and use that to enumerate the routes contained.
You'd be protected against the dreaded Collection <__NSArrayM: 0x17405dc70> was mutated while being enumerated.
error.
Thank you. Keep up the good work,
Brian M. Criscuolo
warmwinds software
NSURL *url = [NSURL URLWithString:@"My://8888.com/go/main?tab=member_cente"];
if (![[UIApplication sharedApplication] canOpenURL: url]) return NO;
[[UIApplication sharedApplication] openURL:url];
[ [JLRoutes routesForScheme:@"My"] addRoute:@"/8888.com/go/main" handler:^BOOL(NSDictionary *parameters) {
// 这里不执行
return YES;
}];
以下代码拷贝自上面,只是将8888.com改成了8888,但上面的无法进入block,下面的就可以
NSURL *url = [NSURL URLWithString:@"My://8888/go/main?tab=member_cente"];
if (![[UIApplication sharedApplication] canOpenURL: url]) return NO;
[[UIApplication sharedApplication] openURL:url];
[ [JLRoutes routesForScheme:@"My"] addRoute:@"/8888/go/main" handler:^BOOL(NSDictionary *parameters) {
// 但这个就执行
return YES;
}];
有什么办法解决吗?
pass value can not use chinese ?
I am pretty sure that most of what someone would want to implement related to x-callback-url can be achieved without any additional features in JLRoutes. However, it would be interesting to think about if some x-callback-url extensions could be added to make it easier to use JLRoutes for implementing it.
I'm having problems with quoting slash (/) and plus (+). I looked at the code a bit; the problem seems to be pathComponents doing some unquoting. I think this is why Apple have now introduced NSURLComponents, but that doesn't help if you want to be compatible with older versions. Here's a test case you can drop into your test .m file to demonstrate:
- (void)testPercentEncoding {
/*
from http://en.wikipedia.org/wiki/Percent-encoding
! # $ & ' ( ) * + , / : ; = ? @ [ ]
%21 %23 %24 %26 %27 %28 %29 %2A %2B %2C %2F %3A %3B %3D %3F %40 %5B %5D
*/
[self route:@"tests://user/view/joel%21levin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel!levin"});
[self route:@"tests://user/view/joel%23levin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel#levin"});
[self route:@"tests://user/view/joel%24levin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel$levin"});
[self route:@"tests://user/view/joel%26levin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel&levin"});
[self route:@"tests://user/view/joel%27levin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel'levin"});
[self route:@"tests://user/view/joel%28levin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel(levin"});
[self route:@"tests://user/view/joel%29levin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel)levin"});
[self route:@"tests://user/view/joel%2Alevin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel*levin"});
/*
Fails: tries to match 'joel levin' with 'joel+levin'
*/
[self route:@"tests://user/view/joel%2Blevin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel+levin"});
[self route:@"tests://user/view/joel%2Clevin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel,levin"});
/*
Fails: tries to match (null) with 'joel/levin'
*/
[self route:@"tests://user/view/joel%2Flevin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel/levin"});
[self route:@"tests://user/view/joel%3Alevin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel:levin"});
[self route:@"tests://user/view/joel%3Blevin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel;levin"});
[self route:@"tests://user/view/joel%3Dlevin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel=levin"});
[self route:@"tests://user/view/joel%3Flevin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel?levin"});
[self route:@"tests://user/view/joel%40levin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel@levin"});
[self route:@"tests://user/view/joel%5Blevin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel[levin"});
[self route:@"tests://user/view/joel%5Dlevin"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
JLValidateParameter(@{@"userID": @"joel]levin"});
}
I have a deeplink where the URL uses the "+" symbol to indicate spaces. Not only in the path, but also in the query part.
For example:
...myBaseURL/invitations/hXVzNLTySmb4TGDL2yxR/accept?invitation_email=daan%40example.com&list_name=Test2+%F0%9F%98%82+%2BHans&actor_name=Daan+De+Boer
Now the NSDictionary *parameters in the handler still contain those "+" symbols instead of being replaced with spaces. So I added this code in my application:continueUserActivity:restorationHandler: method:
NSString *changedURL = [[userActivity.webpageURL absoluteString] stringByReplacingOccurrencesOfString:@"+" withString:@"%20"]; NSURL *outputURL = [NSURL URLWithString:changedURL]; return [JLRoutes routeURL:outputURL];
In my opinion JLRoutes should take care of this, or am I missing something?
I have this route setup:
[JLRoutes addRoute:@"/playeractivation" handler:^BOOL(NSDictionary *parameters) {...}
The incoming URL (universal links) is this: https://websitename.rs/[email protected]&code=123142
Everything goes until line 518 in JLRoutes.m :
if (URL.host.length > 0 && ![URL.host isEqualToString:@"localhost"]) {
// For backward compatibility, handle scheme://path/to/ressource as if path was part of the
// path if it doesn't look like a domain name (no dot in it)
pathComponents = [@[URL.host] arrayByAddingObjectsFromArray:pathComponents];
}
This piece of code picks up the websitename.rs
and adds it to the existing playeractivation
(which is the only path component).
Thus, when it later gets in line 173, to this:
BOOL componentCountEqual = patternComponents.count == components.count;
BOOL patternContainsWildcard = [patternComponents containsObject:@"*"];
if (componentCountEqual || patternContainsWildcard) {
That first boolean is NO, because components has two elements and patternComponents has only one (which is correct).
What's the purpose of that block above?
The comment says:
// For backward compatibility, handle scheme://path/to/ressource as if path was part of the
// path if it doesn't look like a domain name (no dot in it)
but it never actually checks for the existence of the .
in the URL.host, it just blindly adds it.
Hi
In this example the article is an optional parameter.
open/edition/:editionId
open/edition/:editionId/article/:articleId
In kohana in php I can setup optional stuff by adding some paranthesis. Like this
open/edition/:editionId(/article/:articleId)
It would be awesome if you switched to CMDQueryStringSerialization by @calebd 😄
For example:
key=1&key=2&text=hi
JLRoutes gives:
{ key=2, text=hi }
CMDQueryStringSerialization gives:
{ key=[1, 2], text=hi }
which is correct according to RFC number whatever. Currently, in all of my routes, I'm doing this:
NSString *queryString = [parameters[@"JLRouteURL"] query];
NSDictionary *params = [CMDQueryStringSerialization dictionaryWithQueryString:queryString];
One of the things that's still kind of unclear how to implement is the common pattern of handling a route by showing a new view controller (such as to view a user profile). A lot can be done to improve on that.
Curious as to why you have to fetch another dictionary out of the dictionary given? Is there other stuff in the root dict? If so, can it be described in the README? Seems like an arbitrary obstacle, so I'm sure you had good reason for it, just curious what it is.
Great lib! I'll be coming back to this very soon.
https://github.com/oenius/DarwinNativeRouter
Somehow, the JLRoutes have a better practice, and i'm looking into it now.
could you please give me example, how JLRoutes works with react native. i am aiming to route within the app (one view to other view)
:thing/path/:anotherthing(/new)(/anotherpath/:athirdthing)
Results in the unmatched handler being called for:
http://url.com/path/12345/anotherpath/54321
This path should be handled in the case where (/new) is optionally omitted, but it is not.
Defining the route as two separate routes manually including and not including /new
in the path results in expected behavior.
I defiend myapp://web/:URLString
URLString will be URLencoded before,
it would be myapp://web/http%3A%2F%2Fbaidu.com
but it's broken in pathComponents parsing
. The encoded url would be split.
See your code, pathComponents would be web, http:, baidu.com
JLRoutes.m line:370
// break the URL down into path components and filter out any leading/trailing slashes from it
NSArray *pathComponents = [(URL.pathComponents ?: @[]) filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"NOT SELF like '/'"]];
if ([URL.host rangeOfString:@"."].location == NSNotFound) {
// For backward compatibility, handle scheme://path/to/ressource as if path was part of the
// path if it doesn't look like a domain name (no dot in it)
pathComponents = [@[URL.host] arrayByAddingObjectsFromArray:pathComponents];
}
Do you have an official way in your mind of this ?
It seems the wildcard only works if it is at the end of a URL. Example: *.imgur.com routes everything through it regardless of what comes after.
Do you have any recommendation for how to use JLRoutes with navigation controllers?
Imagine you have something like a UserProfileViewController
, it takes a user_id param and you want to launch it from many different parts of the interface in your app. Sometimes it's presented modally, other times pushed onto a navigation controller.
You've got a route for /users/:user_id
, how would you recommend making UserProfileViewController get pushed from the top-most navigation controller?
For now I've added a param "useNavigationController," and added code in the route handler to try to find the top-most view controller and check if it's a navigation controller. This is not very elegant, though, and I realize it's brittle.
I've seen some other routing libraries take explicit navigation controller arguments in the call to handleRoute(https://github.com/aaronbrethorst/ABRouter#in-your-parent-view-controllers). I'm not sure that's the right way, either, though.
Have you encountered this or do you have any thoughts on this?
Here are some additional cases to consider for wildcarding. In one or two cases the failure may be my misunderstanding of how it should work. Probably the crashes shouldn't happen even if I am misunderstanding.
Thank you
Doug
- (void)testAdditionalWildcard1 {
// add a wildcard with a preceding path component
[JLRoutes addRoute:@"/xyz/wildcard/*" handler:^BOOL(NSDictionary *parameters) {
testsInstance.lastMatch = parameters;
return YES;
}];
/*
This causes parameters to be:
{
"" = wildcard;
JLRouteNamespace = JLRoutesGlobalNamespace;
JLRoutePattern = "/:";
JLRouteURL = "tests://wildcard";
}
consequently parameter count is 1 not 0
I don't know if that is right or not
*/
[self route:@"tests://wildcard"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(0);
}
- (void)testAdditionalWildcard2 {
// add a wildcard with a preceding path component
[JLRoutes addRoute:@"/xyz/wildcard/*" handler:^BOOL(NSDictionary *parameters) {
testsInstance.lastMatch = parameters;
return YES;
}];
/*
Crashes:
Test Case '-[JLRoutesTests testAdditionalWildcard]' started.
2014-03-08 08:37:31.806 otest[31234:303] *** Routing tests://xyz/wildcard
2014-03-08 08:37:31.806 otest[31234:303] [JLRoutes]: Trying to route URL tests://xyz/wildcard
2014-03-08 08:37:31.806 otest[31234:303] [JLRoutes]: Parsed query parameters: (null)
2014-03-08 08:37:31.807 otest[31234:303] [JLRoutes]: Parsed fragment parameters: (null)
2014-03-08 08:37:31.810 otest[31234:303] [JLRoutes]: URL path components: (
xyz,
wildcard
)
Unknown.m:0: error: -[JLRoutesTests testAdditionalWildcard] : *** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]
Test Case '-[JLRoutesTests testAdditionalWildcard]' failed (0.005 seconds).
*/
[self route:@"tests://xyz/wildcard"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
}
- (void)testAdditionalWildcard3 {
// add a wildcard with a preceding path component
[JLRoutes addRoute:@"/xyz/wildcard/*" handler:^BOOL(NSDictionary *parameters) {
testsInstance.lastMatch = parameters;
return YES;
}];
/*
Crashes:
Test Case '-[JLRoutesTests testAdditionalWildcard]' started.
2014-03-08 08:45:44.735 otest[31708:303] *** Routing tests://xyz/wildcard/
2014-03-08 08:45:44.735 otest[31708:303] [JLRoutes]: Trying to route URL tests://xyz/wildcard/
2014-03-08 08:45:44.735 otest[31708:303] [JLRoutes]: Parsed query parameters: (null)
2014-03-08 08:45:44.736 otest[31708:303] [JLRoutes]: Parsed fragment parameters: (null)
2014-03-08 08:45:44.739 otest[31708:303] [JLRoutes]: URL path components: (
xyz,
wildcard
)
Unknown.m:0: error: -[JLRoutesTests testAdditionalWildcard] : *** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]
Test Case '-[JLRoutesTests testAdditionalWildcard]' failed (0.005 seconds).
*/
[self route:@"tests://xyz/wildcard/"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
}
- (void)testAdditionalWildcard4 {
// add a wildcard with a preceding path component
[JLRoutes addRoute:@"/xyz/wildcard/*" handler:^BOOL(NSDictionary *parameters) {
testsInstance.lastMatch = parameters;
return YES;
}];
[self route:@"tests://xyz/wildcard/matches/with/extra/path/components"];
JLValidateAnyRouteMatched();
JLValidateParameterCount(1);
NSArray *wildcardMatches = @[@"matches", @"with", @"extra", @"path", @"components"];
JLValidateParameter(@{kJLRouteWildcardComponentsKey: wildcardMatches});
}
Hi Joel,
I would like to use the new features of JLRoutes, but CocoaPods' version of JLRoutes is 3 months older than the current version. I would like to avoid pointing my Podfile to a specific commit.
Can you add a new tag for the latest stable commit JLRoutes?
Thanks,
Trung
In JLRRouteRequest
:
NSURLComponents *components = [NSURLComponents componentsWithString:[self.URL absoluteString]];
if (components.host.length > 0 && ![components.host isEqualToString:@"localhost"]) {
// convert the host to "/" so that the host is considered a path component
NSString *host = [components.percentEncodedHost copy];
components.host = @"/";
components.percentEncodedPath = [host stringByAppendingPathComponent:(components.percentEncodedPath ?: @"")];
}
This doesn't allow for a support of a variety of hosts. For example, I've tested with:
https://www.mydomain.com/path/3
and https://www.mydomain-test.com/path/3
and these resolve to paths that are www.mydomain.com/path/3
and mydomain-test.com/path/3
, which means if I add a route for /path/:pathid
nothing matches, since it's trying to match on a path that includes the domain as well.
I haven't seen a workaround for this, but needing to support multiple hosts, this is a breaking change after updating from 1.6.3
.
I don't want to submit a PR in case there's a reason for this change, so would like to know why the path is overridden to include the host?
Hi, there is an issue with the latest podspec you pushed today (1.5) as you can see here: https://travis-ci.org/CocoaPods/Specs/builds/20388139
-> The specification defined in `JLRoutes/1.5/JLRoutes.podspec` could not be loaded.
[!] Invalid `JLRoutes.podspec` file: compile error
JLRoutes/1.5/JLRoutes.podspec:3: Invalid char `\342' in expression
JLRoutes/1.5/JLRoutes.podspec:3: Invalid char `\200' in expression
JLRoutes/1.5/JLRoutes.podspec:3: Invalid char `\234' in expression
JLRoutes/1.5/JLRoutes.podspec:3: Invalid char `\342' in expression
JLRoutes/1.5/JLRoutes.podspec:3: Invalid char `\200' in expression
JLRoutes/1.5/JLRoutes.podspec:3: Invalid char `\235' in expression
JLRoutes/1.5/JLRoutes.podspec:23: Invalid char `\342' in expression
JLRoutes/1.5/JLRoutes.podspec:23: Invalid char `\200' in expression
JLRoutes/1.5/JLRoutes.podspec:23: Invalid char `\234' in expression
JLRoutes/1.5/JLRoutes.podspec:23: Invalid char `\342' in expression
JLRoutes/1.5/JLRoutes.podspec:23: Invalid char `\200' in expression
JLRoutes/1.5/JLRoutes.podspec:23: Invalid char `\235' in expression. Updating CocoaPods might fix the issue.
# from JLRoutes/1.5/JLRoutes.podspec:30
# -------------------------------------------
# s.osx.deployment_target = '10.7'
> end
# -------------------------------------------
- JLRoutes (1.5)
in the function
- (void)addRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^)(NSDictionary *parameters))handlerBlock;
if you pass argument with /user/:userId(/post/:postId)(/reply/:replyId), it will register the following routes:
/user/:userId/post/:postId/reply/:replyId
/user/:userId/post/:postId/
/user/:userId/post/:postId/
/user/:userId
not:
/user/:userId/post/:postId/reply/:replyId
/user/:userId/post/:postId/
/user/:userId
I think the function can be written like this?:
- (void)addRoute:(NSString *)routePattern priority:(NSUInteger)priority handler:(BOOL (^)(NSDictionary *parameters))handlerBlock {
// if there's a pair of parenthesis, process optionals, trim the parenthesis, put it on trimmedRoute
NSString *trimmedRoute = routePattern;
// repeat until no parenthesis pair is found
while ([trimmedRoute rangeOfString:@")" options:NSBackwardsSearch].location > [trimmedRoute rangeOfString:@"(" options:NSBackwardsSearch].location) {
//Build route with the optionals
NSString *patternWithOptionals = [trimmedRoute stringByReplacingOccurrencesOfString:@"(" withString:@""];
patternWithOptionals = [patternWithOptionals stringByReplacingOccurrencesOfString:@")" withString:@""];
[self registerRoute:patternWithOptionals priority:priority handler:handlerBlock];
//Build route without optionals
NSRange rangeOfLastParentheses = [trimmedRoute rangeOfString:@"(" options:NSBackwardsSearch];
NSRange rangeToRemove = NSMakeRange(rangeOfLastParentheses.location, trimmedRoute.length - rangeOfLastParentheses.location);
NSString *patternWithLastOptionalRemoved = [trimmedRoute stringByReplacingCharactersInRange:rangeToRemove withString:@""];
//Remove any parenthesis for other optionals that might still be in the route
// NSString *patternWithoutOptionals = [patternWithLastOptionalRemoved stringByReplacingOccurrencesOfString:@"(" withString:@""];
// patternWithoutOptionals = [patternWithoutOptionals stringByReplacingOccurrencesOfString:@")" withString:@""];
// [self registerRoute:patternWithoutOptionals priority:priority handler:handlerBlock];
trimmedRoute = patternWithLastOptionalRemoved;
}
//Only register original route if trimmedRoute haven't been modified.
if (trimmedRoute == routePattern) {
[self registerRoute:routePattern priority:priority handler:handlerBlock];
}else{
[self registerRoute:trimmedRoute priority:priority handler:handlerBlock];
}
}
what do you think about it ?
Could JLRoutes add the regex match? Sometimes I want to register regex pattern like ^..test.com/. . Then I can handle the specific url like m.test.com, h5.test.com etc .
You should be using NSURL
's path
property instead trying to parse the URL's path from the absoluteString
. In your README you use myapp://user/view/joeldev as an example URL, with two slashes. With a valid URL parser, user
would be parsed as the host component and shouldn't be part of the path. A valid example would be either myapp:/user/view/joeldev
or myapp:///user/view/joeldev
to omit the host part, but such example wouldn't be matched.
Not using NSURL
prevents your lib from handling smart ad banners transmitted URL correctly. You often pass the full URL with domain of the site as parameter in such cases.
I can submit a pull request if you want.
JLRoutes should support per-scheme routes. This would allow for schemes to be used as namespaces. For example:
The URLs myapp-foo://some/route
and myapp-bar://some/route
could be handled in independent blocks, despite being the same route. Without this feature, you'd have to pull out the scheme from the URL and handle the two branches yourself.
This needs a good simple API that doesn't make the current API awkward, which is the only reason I'm writing up an issue for it now instead of just coding it up.
It would be nice if you could query JLRoutes for all the currently registered routes, and then generate, for example, a testing UI that provides easy links to all the routes in the app.
I have this url:
example://query?test=sp%E8cial
However JLRoutes is unable to parse the test
parameter because of the "%E8" character.
Is there any way to support special characters?
To workaround this issue, I had to modify the JLRoutes_URLDecodedString
method because it is returning NULL.
This is a bit difficult to explain so I'll just use examples. I have a route setup for /users/:user_id
which is triggered by the URL app://users/
but not app://users
. In the first case, the paramter for user_id
is /
. It would be nice to see /users
and /users/
be treated the same as neither pass the target route.
I’ve been playing around with the internals to see if I can get a hacky implementation going for what I want, but I’m not having much success.
My use case is as follows:
myapp://value1
myapp:///value2
myapp://value1/value2
I’m abusing the ‘domain’/host portion of the URL, but the point here is that value1
and value2
are always significant to me, even (especially) if one or the other is empty.
I was attempting to use this routing setup:
[JLRoutes addRoute: @"/:value1/:value2" handler: handlerBlock];
[JLRoutes addRoute: @"//:value2" handler: handlerBlock];
[JLRoutes addRoute: @"/:value1" handler: handlerBlock];
And work around any edges in my handler block, but the latter two routes don’t yield the results I thought they would. The extra slash is just being dropped, so the latter two routes are effectively the same thing, meaning when only one value is present, I don’t know which one it is.
What I’d like to be receiving from the first route, when one of the values is omitted, is NSNull
. (nil
is a no-go because the results are returned via NSArray
, of course.)
The roadblocks I’m hitting are:
+routeURL:withController:
, the URL.pathComponents
array is filtered of any elements containing just a slash. I’m not familiar enough with NSPredicate
to know how, but I believe a predicate/other solution which just removes preceding and trailing slashes, and not any element containing only a slash, would fulfill the requirements of the comment preceding that line ;), as well as enable my desired functionality, without breaking anything.URL.host
to the component array for compatibility would also need to be aware of this functionality./~:value
) to try to indicate to -parametersForURL:components:
that the value I was looking for can also be empty. Unfortunately, making it opt-in means that some more complexity may be needed in +routeURL:withController:
as part of the initial parsing.Hi
In general: how can I setup a route for a scheme by pattern?
More specifically: I want to have same routes for 'http' and 'https' (used internally), but I would like to not define the routes twice. Is that possible?
I recently shipped an update to my app, Simple In/Out, that uses your library to do the URL handling. I was manually doing it before, but your library was much easier to setup and maintain. So thank you very much. Feel free to list my app as using your library. As soon as I get my attribution page working, I will definitely list your work there. Thanks!
Given a JLRoutes
instance router
, the following lines of code result in no routes being added to the router:
router.addRoute("/:entityType/id/:id", priority: 20, handler: handler)
router.addRoute("/:entityType/open-modal/:id", priority: 10, handler: handler)
router.addRoute("/:entityType/:id", priority: 1, handler: handler)
This problem occurs in -addRoute:priority:handler:
, specifically:
if (priority == 0) {
[self.routes addObject:route];
} else {
NSArray *existingRoutes = self.routes;
NSUInteger index = 0;
for (_JLRoute *existingRoute in existingRoutes) {
if (existingRoute.priority < priority) {
[self.routes insertObject:route atIndex:index];
break;
}
index++;
}
}
Seeing that we're adding a route with a nonzero priority, we enter the else
part of that logic, which is supposed to iterate over existing routes and insert the new route just ahead of the first route of lower priority. But since this is the first route we're trying to add there's nothing in existingRoutes
, so we never enter the for
loop and the route is never added.
This could be fixed by changing the conditional to read if (priority == 0 || self.routes.count == 0)
.
The addRoute method is declared like:
+ (void)addRoute:(NSString *)routePattern handler:(BOOL (^__nullable)(NSDictionary<NSString *, NSString *> *parameters))handlerBlock;
However, the parameters it passes include:
kJLRouteNamespaceKey
which can be [NSNull null]
kJLRouteURLKey
which is an NSURL
These cause a crash when trying to convert into a [String: String]
(in Swift) that looks like…
* thread #1: tid = 0xbbf578, 0x000000010cb4855f libswiftCore.dylib`swift_bridgeNonVerbatimFromObjectiveC + 255, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0)
frame #0: 0x000000010cb4855f libswiftCore.dylib`swift_bridgeNonVerbatimFromObjectiveC + 255
frame #1: 0x000000010cb43f70 libswiftCore.dylib`deallocateBuffer value witness for Swift.LazyMapCollection + 16
frame #2: 0x000000010ce63da1 libswiftFoundation.dylib`Swift._forceBridgeFromObjectiveC <A> (Swift.AnyObject, A.Type) -> A + 385
frame #3: 0x000000010ce63b80 libswiftFoundation.dylib`static (extension in Foundation):Swift.Dictionary.(_forceBridgeFromObjectiveC (__ObjC.NSDictionary, result : inout Swift.Optional<Swift.Dictionary<A, B>>) -> ()).(closure #1) + 144
frame #4: 0x000000010ce60624 libswiftFoundation.dylib`partial apply forwarder for static (extension in Foundation):Swift.Dictionary.(_forceBridgeFromObjectiveC (__ObjC.NSDictionary, result : inout Swift.Optional<Swift.Dictionary<A, B>>) -> ()).(closure #1) + 100
frame #5: 0x000000010bd681e6 CoreFoundation`__65-[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:]_block_invoke + 102
frame #6: 0x000000010bd680af CoreFoundation`-[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:] + 159
frame #7: 0x000000010ce4fb4e libswiftFoundation.dylib`static (extension in Foundation):Swift.Dictionary._forceBridgeFromObjectiveC (__ObjC.NSDictionary, result : inout Swift.Optional<Swift.Dictionary<A, B>>) -> () + 558
frame #8: 0x000000010ce4f87b libswiftFoundation.dylib`Foundation._convertNSDictionaryToDictionary <A, B where A: Swift.Hashable> (Swift.Optional<__ObjC.NSDictionary>) -> Swift.Dictionary<A, B> + 75
frame #9: 0x00000001046ba40d Imgur`thunk + 77 at Routing.swift:0
* frame #10: 0x0000000108461954 JLRoutes`+[JLRoutes routeURL:withController:parameters:executeBlock:](self=JLRoutes, _cmd="routeURL:withController:parameters:executeBlock:", URL="aaaaaa -- mgur://imgur.com", routesController=0x00007f8b14d20ae0, parameters=0x0000000000000000, executeBlock=YES) + 2740 at JLRoutes.m:551
The key is definitely an NSString
but the values are not.
When calling canRouteURL
the unmatchedURLHandler
gets called if no handler can be found. In the unmatchedURLHandler
it's not possible to distinguish between a genuine attempt to open the URL or a check if the URL can be routed.
JLRoutes.m line 112:
variables[variableName] = [variableValue JLRoutes_URLDecodedString];
The static analyzer claims that variableValue could be nil
, which would lead to nil
being added to a dictionary, which is illegal.
The following URL: transit://routes?ll=45.5391,-73.5997 (as received by application:openURL:
)
Outputs a nil
parameters dictionary...
[JLRoutes addRoute:@"/routes" handler:^BOOL(NSDictionary *parameters) {
// parameters is nil
return YES;
}];
Am I missing something? The read me does say JLRoutes parses GET parameters.
Are there any known issues with iOS9?
Completely forgot to handle URL decoding. Oops.
Is it possible to switch between the possibility to keep the host as parameter?
This because this minor release affects all the app are using it with this behaviour.
I can suggest a pull request if you want.
In order to gracefully handle backwards compatibility, it would be great if you could add a route alias during the transitioning period between route changes.
So if in an old version the profile deeplinking route was /profile/:username
and in a later version is /user/:username
, one would have to have to support both routes in the transitioning period until the old route can be deprecated. This means that you would have to have the exact same handler for two different routes:
[JLRoutes addRoute:@"/profile/:username" handler:^BOOL(NSDictionary *parameters) {
// Show profile
return YES;
}];
[JLRoutes addRoute:@"/user/:username" handler:^BOOL(NSDictionary *parameters) {
// Show profile
return YES;
}];
Instead it would be nice to be able to do something like:
[JLRoutes addAlias:@"/profile/:username" forRoute:@"/user/:username"];
resulting in the /user/:username
handler being invoked for the /profile/:username
route.
I know that one could just factor this functionality out into an external method, but it would be a much appreciated feature nonetheless.
Hey guys,
Just writing to let you know that carthage works great with your repo setup, so it's a quick win to let people know they can use it.
All you have to do is add
github "joeldev/JLRoutes"
to your Cartfile
and bam, now this happens:
➜ finder-ios git:(master) ✗ carthage update
*** Cloning JLRoutes
*** Checking out JLRoutes at "1.5.5"
*** Building scheme "JLRoutes-iOS" in JLRoutes.xcodeproj
I am working on a Swift project for OSX.
I am creating a filter object and using it to determine if a URL matches a route.
In AppDelegate.swift
func applicationDidFinishLaunching(aNotification: NSNotification?)
{
let logging = Logging()
logging.addRoute("/banana")
server.filterChain.add(logging)
server.filterChain.add(ErrorPage())
server.filterChain.add(Nothing())
startServer(self)
}
In Filter.swift (base class for all filter objects)
func addRoute(route:String)
{
if !routes
{
var classname = NSString(UTF8String: object_getClassName(self))
routes = JLRoutes(forScheme: classname)
}
routes!.addRoute(route, handler: { parameters in return (true as ObjCBool)} )
}
Also from Filter.swift (base class for all filter objects)
func processFilter(connection:Connection)
{
if !routes || routes!.canRouteURL(connection.request!.URL)
{
processRequest(connection)
}
It looks like JLRoutes is getting the correct URL, but is not matching the route and returning NO.
Verbose logging from JLRoutes
2014-07-19 15:21:38.820 SwiftServeDemo[55655:303] [JLRoutes]: Could not find a matching route, returning NO
2014-07-19 15:21:38.821 SwiftServeDemo[55655:303] [JLRoutes]: Trying to route URL /banana -- http://localhost:3333/
2014-07-19 15:21:38.821 SwiftServeDemo[55655:303] [JLRoutes]: Parsed query parameters: (null)
2014-07-19 15:21:38.822 SwiftServeDemo[55655:303] [JLRoutes]: Parsed fragment parameters: (null)
2014-07-19 15:21:38.822 SwiftServeDemo[55655:303] [JLRoutes]: URL path components: (
localhost,
banana
)
2014-07-19 15:21:38.822 SwiftServeDemo[55655:303] [JLRoutes]: Could not find a matching route, returning NO
However, switching the pattern to the /* route does match.
2014-07-19 15:30:04.723 SwiftServeDemo[56074:303] [JLRoutes]: Successfully matched JLRoute /* (0)
2014-07-19 15:30:04.725 SwiftServeDemo[56074:303] [JLRoutes]: Trying to route URL /banana -- http://localhost:3333/
2014-07-19 15:30:04.725 SwiftServeDemo[56074:303] [JLRoutes]: Parsed query parameters: (null)
2014-07-19 15:30:04.725 SwiftServeDemo[56074:303] [JLRoutes]: Parsed fragment parameters: (null)
2014-07-19 15:30:04.725 SwiftServeDemo[56074:303] [JLRoutes]: URL path components: (
localhost,
banana
)
2014-07-19 15:30:04.726 SwiftServeDemo[56074:303] [JLRoutes]: Successfully matched JLRoute /* (0)
Any suggestions? Thanks much.
Currently all parsed params come in as strings. While that's useful a lot of the time, there are still probably some cases where the developer has to convert it to, for example, a number.
In the interest of helping people not have to write that extra bit of code, I'd like to find some nice way to encode types in the route pattern string.
Instead of:
/post/view/:postID
It could be (this format is by no means final and only meant to illustrate the concept):
/post/view/:[postID:number]
JLRoutes would see that postID is declared as a number and just give you an NSNumber for that key instead of an NSString.
There is probably some set list of types where this makes any sense at all whatsoever.
...aka, 'rake routes' but for JLRoutes.
JLRRouteRequest
Line 81
@property (nullable, copy) NSArray<NSURLQueryItem *> *queryItems NS_AVAILABLE(10_10, 8_0);
Cause Crash on device with iOS 7.x
Hi,
I think a lot of JLRoutes projects are using CircleCI, or similar to build builds
If you can attach the pre-compiled framework: JLRoutes.framework.zip to the release, this will save tons of CircleCI building time.
Thanks,
Wei
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.