routing

Keeping Routing Simple and Separated with Koa Router

Debugging an error when using koa-router module that multiple routes were invoked on a single HTTP request:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var app = require('koa')();
var router = require('koa-router')();
router.use(function *(next) {
this.body = {};
yield next;
});
router.get('/users', function *(next) {
this.body['/users'] = true;
yield next;
});
router.get('/users/:id?', function *(next) {
this.body['/users/:id?'] = true;
yield next;
});
app.use(router.routes());
app.listen(3000);

When making a request to get a list of users, it will match both routes:

1
2
3
4
5
6
7
8
9
10
$ http :3000/users
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 34
Content-Type: application/json; charset=utf-8
{
"/users": true,
"/users/:id?": true
}

Because in the second route :id is optional, and since there is yield next statement in the first route, the second route will be executed subsequently.

The cause of the bug is the design of the routing GET /users/:id?. The correct routes are:

  • GET /users: Retrieve a list (array) of users.
  • GET /users/:id: Retrieve a single user object.

The question mark should not be appended to the end of id parameter, which effectively marking the route to perform both duties. It’s better to keep them simple and keep them separated.

Add a Custom Domain to the Specific Module of Google App Engine Managed VMs

I would like to add a custom domain to a module other than the default one in Google App Engine Managed VMs. For example, I have the following sub-domains:

1
2
3
4
Custom domain names | SSL support | Record type | Data | Alias
------------------- | ----------- | ----------- | -------------------- | -----
api.example.com | none | CNAME | ghs.googlehosted.com | api
admin.example.com | none | CNAME | ghs.googlehosted.com | admin

listed in the application console:

https://console.developers.google.com/project/[project]/appengine/settings/domains

The domain api.example.com points to the default module. And I would like the other domain admin.example.com to point to the module admin. However, by merely adding the custom domain in the settings, both api.example.com and admin.example.com are pointed to the same module: the default module. Then how to point the custom domain and route the traffic to the admin module? The answer is on the dispatch file. But first need to understand the concept of modules in Google App Engine.

Google App Engine Module Hierarchy
Source: Google App Engine Docs

The above chart illustrates the architecture of a Google App Engine application:

  • Application: “An App Engine application is made up of one or more modules”. [1]
  • Module: “Each module consists of source code and configuration files. The files used by a module represents a version of the module. When you deploy a module, you always deploy a specific version of the module.” [1] “All module-less apps have been converted to contain a single default module.” [1] Every application has a single default module.
  • Version: “A particular module/version will have one or more instances.”
  • Instance: “Each instance runs its own separate executable.”

Another concept is the resource sharing:

  • “App Engine Modules let developers factor large applications into logical components that can share stateful services and communicate in a secure fashion.” [1]
  • “Stateful services (such as Memcache, Datastore, and Task Queues) are shared by all modules in an application.” [1]

Remember that Google Cloud Platform is project based. If there are a web application and a mobile application, and they are both making requests to another application, the API. Then there should be three projects. However, in this case, both api and admin share the same services, such as the same database and file storage. We should put them together in the same application as separate modules.

With that in mind, how to route requests to a specific module? “Every module, version, and instance has its own unique URI (for example, v1.my-module.my-app.appspot.com). Incoming user requests are routed to an instance of a particular module/version according to URL addressing conventions and an optional customized dispatch file.” [1] Since a custom domain is used, we have to use the customized dispatch file. And this is done by creating a dispatch file dispatch.yml to route requests based on URL patterns.

1
2
3
4
5
6
7
8
# Dispatch
# ========
---
dispatch:
- url: '*/favicon.ico'
module: default
- url: 'admin.example.com/*'
module: admin

The application field is not necessary, otherwise, you will get:

1
2
WARNING: The [application] field is specified in file [/home/chao/example/dispatch.yml].
This field is not used by gcloud and should be removed.

Glob characters (such as asterisk) can be used, and support up to 10 routing rules.

Deploy the dispatch file is simple:

1
$ gcloud preview app deploy dispatch.yml

And it should require almost no time to wait. Once it is ready, the requests sent to admin.example.com will be routed properly to the admin module.