UNPKG

5.34 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 , assert = require('assert')
16 , utils = require('./utils');
17
18// environment
19
20var env = process.env.NODE_ENV || 'development';
21
22/**
23 * Initialize a new `Server` with the given `middleware`.
24 *
25 * Examples:
26 *
27 * var server = connect.createServer(
28 * connect.favicon()
29 * , connect.logger()
30 * , connect.static(__dirname + '/public')
31 * );
32 *
33 * @params {Array} middleware
34 * @return {Server}
35 * @api public
36 */
37
38var Server = exports.Server = function HTTPServer(middleware) {
39 this.stack = [];
40 middleware.forEach(function(fn){
41 this.use(fn);
42 }, this);
43 http.Server.call(this, this.handle);
44};
45
46/**
47 * Inherit from `http.Server.prototype`.
48 */
49
50Server.prototype.__proto__ = http.Server.prototype;
51
52/**
53 * Utilize the given middleware `handle` to the given `route`,
54 * defaulting to _/_. This "route" is the mount-point for the
55 * middleware, when given a value other than _/_ the middleware
56 * is only effective when that segment is present in the request's
57 * pathname.
58 *
59 * For example if we were to mount a function at _/admin_, it would
60 * be invoked on _/admin_, and _/admin/settings_, however it would
61 * not be invoked for _/_, or _/posts_.
62 *
63 * This is effectively the same as passing middleware to `connect.createServer()`,
64 * however provides a progressive api.
65 *
66 * Examples:
67 *
68 * var server = connect.createServer();
69 * server.use(connect.favicon());
70 * server.use(connect.logger());
71 * server.use(connect.static(__dirname + '/public'));
72 *
73 * If we wanted to prefix static files with _/public_, we could
74 * "mount" the `static()` middleware:
75 *
76 * server.use('/public', connect.static(__dirname + '/public'));
77 *
78 * This api is chainable, meaning the following is valid:
79 *
80 * connect.createServer()
81 * .use(connect.favicon())
82 * .use(connect.logger())
83 * .use(connect.static(__dirname + '/public'))
84 * .listen(3000);
85 *
86 * @param {String|Function} route or handle
87 * @param {Function} handle
88 * @return {Server}
89 * @api public
90 */
91
92Server.prototype.use = function(route, handle){
93 this.route = '/';
94
95 // default route to '/'
96 if ('string' != typeof route) {
97 handle = route;
98 route = '/';
99 }
100
101 // wrap sub-apps
102 if ('function' == typeof handle.handle) {
103 var server = handle;
104 server.route = route;
105 handle = function(req, res, next) {
106 server.handle(req, res, next);
107 };
108 }
109
110 // wrap vanilla http.Servers
111 if (handle instanceof http.Server) {
112 handle = handle.listeners('request')[0];
113 }
114
115 // normalize route to not trail with slash
116 if ('/' == route[route.length - 1]) {
117 route = route.substr(0, route.length - 1);
118 }
119
120 // add the middleware
121 this.stack.push({ route: route, handle: handle });
122
123 // allow chaining
124 return this;
125};
126
127/**
128 * Handle server requests, punting them down
129 * the middleware stack.
130 *
131 * @api private
132 */
133
134Server.prototype.handle = function(req, res, out) {
135 var writeHead = res.writeHead
136 , stack = this.stack
137 , removed = ''
138 , index = 0;
139
140 function next(err) {
141 var layer, path, c;
142 req.url = removed + req.url;
143 req.originalUrl = req.originalUrl || req.url;
144 removed = '';
145
146 layer = stack[index++];
147
148 // all done
149 if (!layer || res.headerSent) {
150 // but wait! we have a parent
151 if (out) return out(err);
152
153 // error
154 if (err) {
155 var msg = 'production' == env
156 ? 'Internal Server Error'
157 : err.stack || err.toString();
158
159 // output to stderr in a non-test env
160 if ('test' != env) console.error(err.stack || err.toString());
161
162 // unable to respond
163 if (res.headerSent) return req.socket.destroy();
164
165 res.statusCode = 500;
166 res.setHeader('Content-Type', 'text/plain');
167 if ('HEAD' == req.method) return res.end();
168 res.end(msg);
169 } else {
170 res.statusCode = 404;
171 res.setHeader('Content-Type', 'text/plain');
172 if ('HEAD' == req.method) return res.end();
173 res.end('Cannot ' + req.method + ' ' + utils.escape(req.originalUrl));
174 }
175 return;
176 }
177
178 try {
179 path = parse(req.url).pathname;
180 if (undefined == path) path = '/';
181
182 // skip this layer if the route doesn't match.
183 if (0 != path.indexOf(layer.route)) return next(err);
184
185 c = path[layer.route.length];
186 if (c && '/' != c && '.' != c) return next(err);
187
188 // Call the layer handler
189 // Trim off the part of the url that matches the route
190 removed = layer.route;
191 req.url = req.url.substr(removed.length);
192
193 // Ensure leading slash
194 if ('/' != req.url[0]) req.url = '/' + req.url;
195
196 var arity = layer.handle.length;
197 if (err) {
198 if (arity === 4) {
199 layer.handle(err, req, res, next);
200 } else {
201 next(err);
202 }
203 } else if (arity < 4) {
204 layer.handle(req, res, next);
205 } else {
206 next();
207 }
208 } catch (e) {
209 if (e instanceof assert.AssertionError) {
210 console.error(e.stack + '\n');
211 next(e);
212 } else {
213 next(e);
214 }
215 }
216 }
217 next();
218};
\No newline at end of file