swifter icon indicating copy to clipboard operation
swifter copied to clipboard

Matching Arbitrary Path

Open Mazyod opened this issue 5 years ago • 7 comments

Hello,

It's been great contributing small fixes to this library (Kudos to @Vkt0r), so I have a slightly more challenging problem now which I'd like to collaborate/contribute as well.

Basically, I have a requirement to make the router match against an arbitrary path. Here is the exact code I have in my project:

server["/mock-server/:mockpath"] = { request in

    guard let spec = self.mounting.spec(for: request) else {
        os_log("[404]: Request not found %@", request.path)
        return .notFound
    }

    var headers = spec.response.headers
        .filter { !badHeaders.contains($0.key.lowercased()) }
    headers["Content-Length"] = "\(spec.response.data.count)"

    return .raw(spec.response.code,
                "OK",
                headers,
                { try $0.write(spec.response.data) })
}

Basically, I use mock-server as an entry point to the mock server features, and :mockpath is captured and used to look up the mock response. I understand I can perhaps achieve the same result using query parameter mock_path=..., but I'd like the application being tested to be as identical as possible to the real world scenario. It is also more convenient for developers to treat the mock server in an identical manner using a path instead of a query.

So, to achieve that, I tried first to play with the wildcard * specifier, but wasn't successful in achieving this.

Unfortunately, due to time constraint, I ended up with a tiny hack that made it work, but at the expense of breaking other routing functionality, as you can see in the following commit:

https://github.com/Mazyod/swifter/commit/fd2c8e08737d69eed637ac5d3627aa5250d3ead4

My question becomes, is this a feature you're interested in? Do you have a general opinion about the direction or implementation in which this should be achieved that I can follow? Or would you be open to a proposal from me to potentially support this feature without breaking current routing logic?

Thanks!

Mazyod avatar May 03 '19 12:05 Mazyod

Hey, @Mazyod Thanks for bringing this up. You're completely right. the possibility to match arbitrary path was removed in 1.4.6. Let me explain a little more about the issue we were having allowing arbitrary paths or match all routes like /mock-server/:mockpath.

The first issue reported was in #355 with examples of routes like:

router.register("GET", path: "a/:id", handler: handler)
router.register("GET", path: "a/:id/c", handler: handler)

In that case, the routers were giving precedence to paths causing the handlers were resolved incorrectly, you can try it yourself in your fork with the failing tests you mentioned. With the arbitrary path, the router would resolve it with the first one even when the request is a/b/c as he matches everything and a route was specified with that purpose and IMHO that is wrong as we should have a better way of specifying a match-all route maybe a/* 🤔 (several servers are doing in that way).

Right now the wildcard is not used with that purpose unfortunately but it can be modified for that purpose with a little work. Nevertheless, it's very important to know when a match-all route is identified this would have precedence over all the rest of the routes!

I want to make more investigation related to the match-all route (I've some ideas around) in another server but if somebody has suggestions about it I would be very happy to hear it 😀.

Vkt0r avatar May 11 '19 19:05 Vkt0r

Hi,

Will there be support to match arbitrary paths in future versions? I'm staying on 1.4.5 until I can find a workaround..

Joebayld avatar Jul 08 '19 18:07 Joebayld

@Joebayld Yes, that's the plan! Please be careful with 1.4.5 as in cases of routes overlapping it can have issues.

Vkt0r avatar Jul 09 '19 14:07 Vkt0r

Hello, Any update on this ? Would be a really nice feature. Thank you

muskacirca avatar Nov 25 '19 09:11 muskacirca

I've been very busy, if somebody else wants to add the feature I'll be glad to review it.

Vkt0r avatar Jan 07 '20 15:01 Vkt0r

I'm having an issue with this too, we have paths like this:

/content/stream/:isbn/:path /content/stream/:isbn/:type/:path /content/stream/:isbn/:type/:subtype/:path

and we can't match them all, I think the last one ends up failing with 404's and not ever matching, and possibly others too. We originally just had the latter 2 paths and then definitely the last one was failing, but I had to add the first one as well, and now things are definitely messed up. Would love to see a fix for this!

cjmaroney01 avatar Feb 05 '20 14:02 cjmaroney01

Same issue here, I use the following workaround to expose folder and all its subfolders :

private func exposeFolderRecursively(httpServer: HttpServer, folderUrl: URL ){
        let rootLastPathComponent = folderUrl.lastPathComponent
        httpServer[rootLastPathComponent + "/:path"] = shareFilesFromDirectory(folderUrl.path)
        
        if let enumerator = FileManager.default.enumerator(at: folderUrl, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
            for case let subFolderUrl as URL in enumerator {
                do {
                    let fileAttributes = try subFolderUrl.resourceValues(forKeys:[.isDirectoryKey])
                    if fileAttributes.isDirectory! {
                        if let range = subFolderUrl.path.range(of: rootLastPathComponent){
                            let subPath = subFolderUrl.path[range.lowerBound...]
                            httpServer[subPath + "/:path"] = shareFilesFromDirectory(subFolderUrl.path)
                        }
                    }
                } catch {}
            }
        }
    }

fb64 avatar Dec 10 '20 14:12 fb64