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 |
|
13 | var http = require('http')
|
14 | , utils = require('./utils')
|
15 | , debug = require('debug')('connect:dispatcher');
|
16 |
|
17 | // prototype
|
18 |
|
19 | var app = module.exports = {};
|
20 |
|
21 | // environment
|
22 |
|
23 | var 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 |
|
62 | app.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 |
|
102 | app.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 |
|
227 | app.listen = function(){
|
228 | var server = http.createServer(this);
|
229 | return server.listen.apply(server, arguments);
|
230 | };
|