strongloop / strong-remoting

Communicate between objects in servers, mobile apps, and other servers.
www.strongloop.com
Other
105 stars 93 forks source link

Implement a faster REST router #282

Closed gunjpan closed 7 years ago

gunjpan commented 8 years ago

Faster REST router implementation - iteration 1 Includes:

Excludes:

Connect to https://github.com/strongloop-internal/scrum-loopback/issues/730

bajtos commented 8 years ago

@gunjpan please review my comments in our earlier conversations (e.g. in the Spike/POC pull request) and add all unit-tests that I mentioned there. It's ok to add them as "it.skip" initially, I just want to make sure we don't forget about any of them.

bajtos commented 8 years ago

@gunjpan on more task from our earlier conversations: please add the test suite that will run the same code (tests) against both express.Router and RestRouter. The sooner you do that, the sooner we can start adding tests for edge cases like https://github.com/strongloop/strong-remoting/pull/282#discussion_r51957509.

richardpringle commented 8 years ago

I want to propose a different name for Trie and trie.js. Maybe something a little more descriptive like RouteTrie, thoughts?

When I see Trie I immediately assume that the class is simply the trie data structure and is not necessarily related to routing.

bajtos commented 8 years ago

Maybe something a little more descriptive like RouteTrie, thoughts?

It may be better to treat the fact that we use Trie as an implementation detail and don't leak it in the name at all. Then we can call the class RouteTable or RouterLookup or something along those lines. Thoughts?

richardpringle commented 8 years ago

@bajtos, is there any reason not to be transparent? Someone from the community might see it and have a better idea...

I'm not picky about that detail though, I like RouteTable as well.

bajtos commented 8 years ago

cc @raymondfeng PTAL too

bajtos commented 8 years ago

is there any reason not to be transparent? Someone from the community might see it and have a better idea...

So what will happen if we decide to change the implementation later in the future and use a different data structure? Will we have a RouteTrie that's implemented using red-black tree (for example)? Will we rework all code that is using (calling) this class, even though there was no API change beyond the name?

That's why I am proposing to use a name that describes how the class is intended to be used (to look up routes), not how it is implemented (via Trie now). The former should stay the same even in case of changes in the future, the latter will not stay the same.

richardpringle commented 8 years ago

@bajtos, this is still very much a work in progress and there are still a few changes that need to be made that you had originally recommended for @gunjpan, but I would like you to please review before I continue to work on this.

I would also like your opinion on any more test cases that should be added.

richardpringle commented 8 years ago

@gunjpan @davidcheung, could you also please take a look.

bajtos commented 8 years ago

this is still very much a work in progress and there are still a few changes that need to be made that you had originally recommended for @gunjpan, but I would like you to please review before I continue to work on this.

I am not sure what exactly to look out for during review. I did a quick check to see if there is anything obvious and left few comments above.

I would also like your opinion on any more test cases that should be added.

For me, it's most important to have an integration test suite to verify that the new fast router behaves exactly same as the default express router.

I think the best approach is to create a parameterized suite that will be run against the new fastRouter and against the Express router. For example, something along these lines:

describe('fast router', function() {
  testRouter(FastRouter);
});

describe('express Router', function() {
  testRouter(express.Router);
});

function testRouter(Router) {
  var router, request;
  before(setupHttpServer);
  after(stopHttpServer);

  // individual tests calling the provided `router` under the hood
  it('...', function(done) {
    router.get('/', function(req, res, next) {
      // ...
    });
    request.get('/')
      .expect(..., done);
  });

  function setupHttpServer(done) {
    router = new Router();
    // create http server handling requests via router
    // create supertest instance invoking this server
  }
}

As for test cases, we should test all HTTP verbs, then different URL paths (/, path with one segment, path with multiple segments, path with a trailing /, check how double/triple slash is handled - at the start //api, in the middle /api//cars, and at the end /api/cars//). Check how all is handled - what is the priority compared to specific verbs like get/post. Look at express source code dealing with path parameters and add test cases to verify we correctly handle edge cases.

bajtos commented 8 years ago

We should also verify that when you register a handler for a given verb+path, other endpoints will not trigger the handler.

Verify the priority of paths with path parameters vs. paths with static names, think about GET /api/cars/:id vs. GET /api/cars/findOne: /api/cars/1 should be routed to findById(), /api/cars/findOne should be routed to findOne().

richardpringle commented 8 years ago

@bajtos

I am not sure what exactly to look out for

I wanted you to look out for the things that you found. It's better to catch those mistakes early instead of having to do a big re-factor at the end. It's a much faster process to review as I move forward instead of one big repeating review/re-factor cycle at the end.

richardpringle commented 8 years ago

Also, I keep getting the same failure for multiple tests on Node 0.10 and 0.12

     TypeError: undefined is not a function
      at Trie.find (lib/trie.js:9:2363)
      at Function.RestRouter.handle (lib/rest-router.js:9:1870)
      at router (lib/rest-router.js:9:688)
      at corsHandler (lib/rest-adapter.js:9:13002)
      at lib/rest-adapter.js:9:11460
      at test/phase-handlers.test.js:20:30
      at HTTPParser.parserOnIncoming [as onIncoming] (_http_server.js:492:12)
      at HTTPParser.parserOnHeadersComplete (_http_common.js:111:23)
      at Socket.socketOnData (_http_server.js:343:22)
      at readableAddChunk (_stream_readable.js:163:16)
      at Socket.Readable.push (_stream_readable.js:126:10)
      at TCP.onread (net.js:540:20)
bajtos commented 8 years ago

Few more things to test: values of path parameters should be url-decoded, i.e. /api/cars/a+b should produce value 'a b', similarly for % based sequences.

bajtos commented 8 years ago

Also, I keep getting the same failure for multiple tests on Node 0.10 and 0.12

That's weird, because lib/trie.js:9 is this now:

Trie.prototype.add = function(route, handler) {

@richardpringle can you reproduce the problem locally on your machine too? (I personally use and recommend nvm for quick & easy switching between node versions.)

bajtos commented 7 years ago

@gunjpan I am re-assigning this back to you. Please ping me when there patch is ready for another round of review.

gunjpan commented 7 years ago

@bajtos : Thank you. I may need to spend some time to see what richard did, rebase and see which way to go. Will update you when done. Thanks.

richardpringle commented 7 years ago

@gunjpan, let me know if you need any help!

bajtos commented 7 years ago

I am closing this patch for now, we can re-open it when the time to work on this feature comes again.