-
Notifications
You must be signed in to change notification settings - Fork 74
/
farm.coffee
207 lines (169 loc) · 5.92 KB
/
farm.coffee
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
###
* Federated Wiki : Node Server
*
* Copyright Ward Cunningham and other contributors
* Licensed under the MIT license.
* https://github.com/fedwiki/wiki/blob/master/LICENSE.txt
###
# **farm.coffee**
# The farm module works by putting a bouncy host based proxy
# in front of servers that it creates
path = require 'path'
fs = require 'fs'
chokidar = require 'chokidar'
http = require 'http'
# socketio = require 'socket.io'
server = require 'wiki-server'
_ = require('lodash')
errorPage = require './error-page'
module.exports = exports = (argv) ->
# Map incoming hosts to their wiki's port
hosts = {}
# Keep an array of servers that are currently active
runningServers = []
if argv.allowed
if argv.allowed is '*'
# we will allow any wiki which we have a directory exists
wikiDir = argv.data
allowedHosts = fs.readdirSync(wikiDir, {withFileTypes: true})
.filter (item) -> item.isDirectory()
.map (item) -> item.name
# watch for new wiki directories being created (or deleted), and keep allowedHosts up to date
watcher = chokidar.watch wikiDir, {
persistent: true
ignoreInitial: true
depth: 0
}
watcher
.on 'addDir', (newWiki) ->
newWiki = path.basename(newWiki)
allowedHosts.push(newWiki)
.on 'unlinkDir', (delWiki) ->
delWiki = path.basename(delWiki)
_.pull(allowedHosts, delWiki)
else
# we have a list of wiki that are allowed
allowedHosts = argv.allowed
.split ',' # split up the coma seperated list of allowed hosts
.map (item) -> item.trim() # trim any whitespace padding from the items in the list
allowHost = (host) ->
hostDomain = host.split(':')[0]
if allowedHosts.includes(hostDomain)
return true
else
return false
if argv.wikiDomains
wikiDomains = Object.keys(argv.wikiDomains)
inWikiDomain = ''
allowDomain = (host) ->
hostDomain = host.split(':')[0]
possibleWikiDomain = []
wikiDomains.forEach((domain) ->
dotDomain = '.' + domain
if hostDomain is domain or hostDomain.endsWith(dotDomain)
possibleWikiDomain.push(domain)
)
if possibleWikiDomain.length > 0
inWikiDomain = possibleWikiDomain.reduce((a, b) ->
if a.length > b.length then a else b
)
return true
else
return false
else
allowDomain = () -> true
allow = (host) ->
# wikiDomains and allowed should both be optional
if argv.allowed
if allowHost(host)
# host is in the allowed list
if argv.wikiDomains
if allowDomain(host)
# host is within a defined wikiDomain
return true
else
# while host is in the allowed list, it is not within an allowed domain
return false
return true
else
# host is not within the allowed list
return false
if argv.wikiDomains
if allowDomain(host)
# host is within a defined wikiDomain
return true
else
# host is not within a defined wikiDomain
return false
# neither wikiDomain or allowed are configured
return true
farmServ = http.createServer (req, res) ->
if req.headers?.host
incHost = req.headers.host.split(':')[0]
else
res.statusCode = 400
res.end('Missing host header')
return
# If the host starts with "www." treat it the same as if it didn't
if incHost[0..3] is "www."
incHost = incHost[4..]
# if we already have a port for this host, forward the request to it.
if hosts[incHost]
hosts[incHost](req, res)
else
# check that request is for an allowed host
unless allow(incHost)
upHost = incHost.split('.').slice(1).join('.')
res.statusCode = 400
errorText = errorPage.render('Requested Wiki Does Not Exist','The wiki you are trying to access does not exist.',"You may visit <a href='//#{upHost}'>#{upHost}</a> for more information.")
res.end(errorText)
return
# Create a new options object, copy over the options used to start the
# farm, and modify them to make sense for servers spawned from the farm.
# do deep copy, needed for nested configurations - e.g. config of wiki domains
clone = (map) ->
copy = undefined
if map is null or typeof map isnt 'object'
return map
if map instanceof Array
copy = []
i = 0
len = map.length
while i < len
copy[i] = clone(map[i])
i++
return copy
if typeof map is 'object'
copy = {}
for attr of map
copy[attr] = clone(map[attr])
return copy
console.log "Unsupported:", typeof map
return
newargv = clone argv
newargv.data = if argv.data
path.join(argv.data, incHost.split(':')[0])
else
path.join(argv.root, 'data', incHost.split(':')[0])
newargv.url = "http://#{incHost}"
# apply wiki domain configuration, if defined
if inWikiDomain
newargv = _.assignIn newargv, newargv.wikiDomains[inWikiDomain]
newargv.wiki_domain = inWikiDomain
# Create a new server, add it to the list of servers, and
# once it's ready send the request to it.
local = server(newargv)
# local.io = io
hosts[incHost] = local
runningServers.push(local)
# patch in new neighbors
if argv.autoseed
neighbors = if argv.neighbors then argv.neighbors + ',' else ''
neighbors += Object.keys(hosts).join(',')
runningServers.forEach (server) ->
server.startOpts.neighbors = neighbors
local.once "owner-set", ->
local.emit 'running-serv', farmServ
hosts[incHost](req, res)
# io = socketio(farmServ)
runningFarmServ = farmServ.listen(argv.port, argv.host)