UNPKG

5.45 kBJavaScriptView Raw
1/*!
2 * Connect - HTTPServer
3 * Copyright(c) 2010 Sencha Inc.
4 * Copyright(c) 2011 TJ Holowaychuk
5 * MIT Licensed
6 */
7
8/**
9 * Module dependencies.
10 */
11
12var finalhandler = require('finalhandler');
13var http = require('http');
14var debug = require('debug')('connect:dispatcher');
15var parseUrl = require('parseurl');
16
17// prototype
18
19var app = module.exports = {};
20
21// environment
22
23var env = process.env.NODE_ENV || 'development';
24
25/* istanbul ignore next */
26var defer = typeof setImmediate === 'function'
27 ? setImmediate
28 : function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) }
29
30/**
31 * Utilize the given middleware `handle` to the given `route`,
32 * defaulting to _/_. This "route" is the mount-point for the
33 * middleware, when given a value other than _/_ the middleware
34 * is only effective when that segment is present in the request's
35 * pathname.
36 *
37 * For example if we were to mount a function at _/admin_, it would
38 * be invoked on _/admin_, and _/admin/settings_, however it would
39 * not be invoked for _/_, or _/posts_.
40 *
41 * Examples:
42 *
43 * var app = connect();
44 * app.use(connect.favicon());
45 * app.use(connect.logger());
46 * app.use(connect.static(__dirname + '/public'));
47 *
48 * If we wanted to prefix static files with _/public_, we could
49 * "mount" the `static()` middleware:
50 *
51 * app.use('/public', connect.static(__dirname + '/public'));
52 *
53 * This api is chainable, so the following is valid:
54 *
55 * connect()
56 * .use(connect.favicon())
57 * .use(connect.logger())
58 * .use(connect.static(__dirname + '/public'))
59 * .listen(3000);
60 *
61 * @param {String|Function|Server} route, callback or server
62 * @param {Function|Server} callback or server
63 * @return {Server} for chaining
64 * @api public
65 */
66
67app.use = function(route, fn){
68 // default route to '/'
69 if ('string' != typeof route) {
70 fn = route;
71 route = '/';
72 }
73
74 // wrap sub-apps
75 if ('function' == typeof fn.handle) {
76 var server = fn;
77 fn.route = route;
78 fn = function(req, res, next){
79 server.handle(req, res, next);
80 };
81 }
82
83 // wrap vanilla http.Servers
84 if (fn instanceof http.Server) {
85 fn = fn.listeners('request')[0];
86 }
87
88 // strip trailing slash
89 if ('/' == route[route.length - 1]) {
90 route = route.slice(0, -1);
91 }
92
93 // add the middleware
94 debug('use %s %s', route || '/', fn.name || 'anonymous');
95 this.stack.push({ route: route, handle: fn });
96
97 return this;
98};
99
100/**
101 * Handle server requests, punting them down
102 * the middleware stack.
103 *
104 * @api private
105 */
106
107app.handle = function(req, res, out) {
108 var stack = this.stack
109 , searchIndex = req.url.indexOf('?')
110 , pathlength = searchIndex !== -1 ? searchIndex : req.url.length
111 , fqdn = req.url[0] !== '/' && 1 + req.url.substr(0, pathlength).indexOf('://')
112 , protohost = fqdn ? req.url.substr(0, req.url.indexOf('/', 2 + fqdn)) : ''
113 , removed = ''
114 , slashAdded = false
115 , index = 0;
116
117 // final function handler
118 var done = out || finalhandler(req, res, {
119 env: env,
120 onerror: logerror
121 });
122
123 function next(err) {
124 var layer, path, c;
125
126 if (slashAdded) {
127 req.url = req.url.substr(1);
128 slashAdded = false;
129 }
130
131 req.url = protohost + removed + req.url.substr(protohost.length);
132 req.originalUrl = req.originalUrl || req.url;
133 removed = '';
134
135 // next callback
136 layer = stack[index++];
137
138 // all done
139 if (!layer) {
140 defer(done, err);
141 return;
142 }
143
144 try {
145 path = parseUrl(req).pathname;
146 if (undefined == path) path = '/';
147
148 // skip this layer if the route doesn't match.
149 if (0 != path.toLowerCase().indexOf(layer.route.toLowerCase())) return next(err);
150
151 c = path[layer.route.length];
152 if (c && '/' != c && '.' != c) return next(err);
153
154 // Call the layer handler
155 // Trim off the part of the url that matches the route
156 removed = layer.route;
157 req.url = protohost + req.url.substr(protohost.length + removed.length);
158
159 // Ensure leading slash
160 if (!fqdn && '/' != req.url[0]) {
161 req.url = '/' + req.url;
162 slashAdded = true;
163 }
164
165 debug('%s %s : %s', layer.handle.name || 'anonymous', layer.route, req.originalUrl);
166 var arity = layer.handle.length;
167 if (err) {
168 if (arity === 4) {
169 layer.handle(err, req, res, next);
170 } else {
171 next(err);
172 }
173 } else if (arity < 4) {
174 layer.handle(req, res, next);
175 } else {
176 next();
177 }
178 } catch (e) {
179 next(e);
180 }
181 }
182 next();
183};
184
185/**
186 * Listen for connections.
187 *
188 * This method takes the same arguments
189 * as node's `http.Server#listen()`.
190 *
191 * HTTP and HTTPS:
192 *
193 * If you run your application both as HTTP
194 * and HTTPS you may wrap them individually,
195 * since your Connect "server" is really just
196 * a JavaScript `Function`.
197 *
198 * var connect = require('connect')
199 * , http = require('http')
200 * , https = require('https');
201 *
202 * var app = connect();
203 *
204 * http.createServer(app).listen(80);
205 * https.createServer(options, app).listen(443);
206 *
207 * @return {http.Server}
208 * @api public
209 */
210
211app.listen = function(){
212 var server = http.createServer(this);
213 return server.listen.apply(server, arguments);
214};
215
216/**
217 * Log error using console.error.
218 *
219 * @param {Error} err
220 * @api public
221 */
222
223function logerror(err){
224 if (env !== 'test') console.error(err.stack || err.toString());
225}