-
Notifications
You must be signed in to change notification settings - Fork 12
/
server.js
executable file
·167 lines (132 loc) · 5.18 KB
/
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#!/usr/bin/env node
'use strict'
var http = require('http')
, querystring = require('querystring')
, mime = require('mime')
, fs = require('fs')
, url = require('url')
, util = require('util')
, minimatch = require('minimatch')
, httpProxy = require('http-proxy')
, path = require('path')
, optimist = require('optimist')
var argv = optimist.
usage(
'This server providers a utility to wrap JavaScript\n' +
'with calls to earhorn$. Additionally, it lets earhorn$\n' +
'write changes to the file system.\n\n' +
'Example:\n\n' +
' ./server.js --port 8001 --pattern "**/*.js"\n\n' +
' This would add earhorn$ to every JavaScript file\n' +
' running from the current directory (not recommended\n' +
' on large codebases).').
describe('port', 'Port to run on.').
describe('cors', 'Access-Control-Allow-Origin header, if specified').
describe('pattern', 'Url pattern add earhorn$ calls to').
describe('patternFile', 'File with newline-delimited url patterns').
describe('verbose', 'Whether to enable verbose logging').
describe('folder', 'Folder to add earhorn$ calls to').
describe('runProxy', 'Whether to reverse proxy').
describe('proxyPort', 'Port of site to reverse proxy').
describe('proxyHost', 'Host of site to reverse proxy').
demand('port').
boolean('runProxy').
boolean('verbose').
default('folder', '.').
default('proxyHost', 'localhost').
argv
var patternFile = argv.patternFile
, pattern = argv.pattern
, cors = argv.cors
, port = +argv.port
, folder = argv.folder
, verbose = argv.verbose || false
, runProxy = argv.runProxy
, proxyHost = argv.proxyHost
, proxyPort = +argv.proxyPort
, proxy = new httpProxy.RoutingProxy()
, patterns = []
, earhornIndexAliases = [ "/earhorn/", "/earhorn/index.html" ]
, indexFilePath = __dirname + '/index.html'
if(patternFile && pattern) throw '--pattern and --patternFile both specified'
if(patternFile) patterns = fs.readFileSync(patternFile).split('\n')
else if(pattern) patterns = [pattern]
if(proxyPort && !runProxy) throw '--proxyPort specified but no --runProxy'
if(runProxy && !proxyPort) throw '--runProxy specified but no --proxyPort'
if(runProxy && !proxyHost) throw '--runProxy specified but no --proxyHost'
if(verbose) console.log(argv)
function handleError(err, res, code) {
console.error(err)
res.statusCode = code || 500
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(err))
}
var server = http.createServer(function (req, res) {
if(req.url === '/earhorn') {
res.statusCode = 302
res.setHeader('Location', '/earhorn/')
return res.end()
}
var parsedUrl = url.parse(req.url)
, query = querystring.parse(parsedUrl.query)
, pathname = parsedUrl.pathname
, isEarhornDir = !pathname.indexOf('/earhorn/')
, isIndex = earhornIndexAliases.indexOf(pathname) >= 0
var filePath = path.resolve(
isIndex ? indexFilePath :
isEarhornDir ? __dirname + '/..' + pathname :
folder + pathname)
var type = mime.getType(filePath)
, isJs = type === 'application/javascript'
var matches = patterns.filter(function(p) {
if(!isJs) return false
var match = minimatch(pathname, p)
if(verbose) console.log('testing match', pathname, p, ':', match)
return match
})
if(runProxy && !matches.length && !isIndex && !isEarhornDir) {
console.log(req.method, req.url, '=>', proxyHost + ':' + proxyPort)
return proxy.proxyRequest(req, res, { host: proxyHost, port: proxyPort })
}
console.log(req.method, req.url, '->', filePath)
if(pathname.indexOf('..') >= 0)
return handleError('ellipses in path are invalid', res)
if(req.method === 'PUT' && !isIndex && !isEarhornDir) {
var pendingError = null
, stream = fs.createWriteStream(filePath)
stream.on('close', function() {
if(pendingError)
writeError(pendingError)
else res.end()
})
stream.on('error', function(err) {
pendingError = err
})
req.pipe(stream)
} else if(req.method === 'HEAD' || req.method === 'GET')
fs.stat(filePath, function(err, stats) {
if(err) return handleError(err, res)
res.setHeader('Last-Modified', stats.mtime)
res.setHeader("Expires", "Sat, 01 Jan 2000 00:00:00 GMT")
res.setHeader("Cache-Control",
"no-store, no-cache, must-revalidate, max-age=0")
res.setHeader("Cache-Control", "post-check=0, pre-check=0")
res.setHeader("Pragma", "no-cache")
res.setHeader('Content-Type', type)
if(cors) res.setHeader('Access-Control-Allow-Origin', cors)
fs.readFile(filePath, function(err, data) {
if(err) return handleError(err, res)
if(matches.length && !isIndex && !isEarhornDir) {
var slashIndex = req.url.lastIndexOf('/')
, name = req.url.substring(slashIndex + 1)
, ref = 'http://' + req.headers.host + req.url
data = 'earhorn$("' + ref + '", true, function() {' + data + '})()'
}
res.setHeader('Content-Length', stats.size)
return req.method === 'HEAD' ? res.end() : res.end(data)
})
})
else writeError(req.method, res, 405) // Method not allowed.
})
server.listen(port)
console.log('Earhorn server listening on', port)