-
Notifications
You must be signed in to change notification settings - Fork 36
/
Copy pathmapper.js
141 lines (117 loc) · 3.51 KB
/
mapper.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
var d = require('describe-property');
var escapeRegExp = require('../utils/escapeRegExp');
function byMostSpecific(a, b) {
return (b.path.length - a.path.length) || ((b.host || '').length - (a.host || '').length);
}
/**
* A middleware that provides host and/or location-based routing. Modifies
* the `basename` connection variable for all downstream apps such that only
* the portion relevant for dispatch remains in `pathname`.
*
* app.use(mach.mapper, {
*
* 'http://example.com/images': function (conn) {
* // The hostname used in the request was example.com, and
* // the URL path started with "/images". If the request was
* // GET /images/avatar.jpg, then conn.pathname is /avatar.jpg
* },
*
* '/images': function (conn) {
* // The URL path started with "/images"
* }
*
* });
*
* This function may also be used outside of the context of a middleware
* stack to create a standalone app. You can either provide mappings one
* at a time:
*
* var app = mach.mapper();
*
* app.map('/images', function (conn) {
* // ...
* });
*
* Or all at once:
*
* var app = mach.mapper({
*
* '/images': function (conn) {
* // ...
* }
*
* });
*
* Note: Dispatch is done in such a way that the longest paths are tried first
* since they are the most specific.
*/
function createMapper(app, map) {
// Allow mach.mapper(map)
if (typeof app === 'object') {
map = app;
app = null;
}
var mappings = [];
function mapper(conn) {
var hostname = conn.hostname;
var pathname = conn.pathname;
var mapping, match, remainingPath;
for (var i = 0, len = mappings.length; i < len; ++i) {
mapping = mappings[i];
// Try to match the hostname.
if (mapping.hostname && mapping.hostname !== hostname)
continue;
// Try to match the path.
if (!(match = pathname.match(mapping.pattern)))
continue;
// Skip if the remaining path doesn't start with a "/".
remainingPath = match[1];
if (remainingPath.length > 0 && remainingPath[0] !== '/')
continue;
conn.basename += mapping.path;
return conn.call(mapping.app);
}
return conn.call(app);
}
Object.defineProperties(mapper, {
/**
* Adds a new mapping that runs the given app when the location used in the
* request matches the given location.
*/
map: d(function (location, app) {
var hostname, path;
// If the location is a fully qualified URL use the host as well.
var match = location.match(/^https?:\/\/(.*?)(\/.*)/);
if (match) {
hostname = match[1].replace(/:\d+$/, ''); // Strip the port.
path = match[2];
} else {
path = location;
}
if (path.charAt(0) !== '/')
throw new Error('Mapping path must start with "/", was "' + path + '"');
path = path.replace(/\/$/, '');
var pattern = new RegExp('^' + escapeRegExp(path).replace(/\/+/g, '/+') + '(.*)');
mappings.push({
hostname: hostname,
path: path,
pattern: pattern,
app: app
});
mappings.sort(byMostSpecific);
}),
/**
* Sets the given app as the default for this mapper.
*/
run: d(function (downstreamApp) {
app = downstreamApp;
})
});
// Allow app.use(mach.mapper, map)
if (typeof map === 'object')
for (var location in map)
if (map.hasOwnProperty(location))
mapper.map(location, map[location]);
return mapper;
}
module.exports = createMapper;