UNPKG

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