UNPKG

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