UNPKG

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