Can URI templates be used to match URIs to routes?

2019-03-25 16:49发布

问题:

Frameworks like ASP.NET or Nancy provide a syntax that can be used for specifying routes, such as:

MapRoute("/customers/{id}/invoices/{invoiceId}", ...)

In ASP.NET routes work in two directions. They can match a request URI such as /customers/32/invoices/19 to a route, and they can resolve parameters such as { id: 37, invoiceId: 19 } into a URI.

RFC 6570: URI Templates also defines a similar, though much richer, specification for URI's that are often used to resolve URI's. For example:

UriTemplate("/customers/{id}/invoices{/invoiceId}{?sort}", { id: 37, invoiceId: 19, sort: 'asc' } )
// returns:  /customers/37/invoices/19?sort=asc

My question is, can the syntax specified in RFC 6570 be used to match request URI's to routes? Is there a part of the syntax that would make it ambiguous to match a given URI to a given URI template? Are there any libraries that support matching a URI to a URI template?

回答1:

I suspect it would be very difficult. Certainly things like the prefix syntax would make it impossible to regenerate the original parameters.

For things like path segment expansion

 {/list*}           /red/green/blue

How would you know which parts of the path were literals and which parts were part of the parameter? There are lots of fairly freaky behavior in the URITemplate spec, I suspect even if it is possible to match, it would be fairly expensive.

Are you interested in doing this for the purposes of routing?



回答2:

It is simple regarding match but regarding resolve you need to replace the ASP.net part by the RFC 6570.

Unfortunately I am doing this in node with express js and this might not be helpful, but I am sure something like https://github.com/geraintluff/uri-templates (for resolving) is also available in ASP.

Here is some .js code to illustrate the rewriting of a hyperschema USING RFC 6570 to use with express js (the advantage of the use within schema is that you could also define regexes for your uri templates):

  var deref = require('json-schema-deref');
  var tv4 = require('tv4');
  var url = require('url');
  var rql = require('rql/parser');
  var hyperschema = {
  "$schema": "http://json-schema.org/draft-04/hyper-schema",
  "links": [
    {
      "href": "{/id}{/ooo*}{#q}",
      "method": "GET",
      "rel": "self",
      "schema": {
        "type": "object",
        "properties": {
          "params": {
            "type": "object",
            "properties": {
              "id": {"$ref": "#/definitions/id"}
            },
            "additionalProperties": false
          }
        },
        "additionalProperties": true
      }
    }
  ],
  "definitions": {
    "id": {
      "type": "string",
      "pattern": "[a-z]{0,3}"
    }
  }
}
// DOJO lang AND _
function getDottedProperty(object, parts, create) {
    var key;
    var i = 0;

    while (object && (key = parts[i++])) {
        if (typeof object !== 'object') {
            return undefined;
        }
        object = key in object ? object[key] : (create ? object[key] = {} : undefined);
    }

    return object;
}
function getProperty(object, propertyName, create) {
    return getDottedProperty(object, propertyName.split('.'), create);
}
function _rEscape(str) {
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function getPattern(k, ldo, customCat) {
  // ...* = explode = array
  // ...: = maxLength
  var key = ((k.slice(-1) === '*') ? k.slice(0,-1) : k).split(':')[0];
  var cat = (customCat) ? customCat : 'params'; // becomes default of customCat in TS
  var pattern = '';
  if (typeof ldo === 'object' && ldo.hasOwnProperty('schema')) {
    var res = getProperty(ldo.schema, ['properties',cat,'properties',key,'pattern'].join('.'));
    if (res) {
      console.log(['properties',cat,'properties',key,'pattern'].join('.'),res);
      return ['(',res,')'].join('');
    }
  }
  return pattern;
}
function ldoToRouter(ldo) {
  var expression = ldo.href.replace(/(\{\+)/g, '{') // encoding
    .replace(/(\{\?.*\})/g, '') // query
    .replace(/\{[#]([^}]*)\}/g, function(_, arg) {
      // crosshatch
      //console.log(arg);
      return ['(?:[/]*)?#:',arg,getPattern(arg,ldo,'anchor')].join('');
    })
    .replace(/\{([./])?([^}]*)\}/g, function(_, op, arg) {
      // path seperator
      //console.log(op, '::', arg, '::', ldo.schema);
      return [op,':',arg,getPattern(arg,ldo)].join('');
    });
    return {method: ldo.method.toLowerCase(), args:[expression]};
}

deref(hyperschema, function(err, fullSchema) {
  console.log('deref hyperschema:',JSON.stringify(fullSchema));
  var router = fullSchema.links.map(ldoToRouter);

  console.log('router:',JSON.stringify(router));
});