1 | path = require("path")
|
2 | fancyLog = require("fancy-log")
|
3 | colors = require("ansi-colors")
|
4 | mapStream = require("map-stream")
|
5 | http = require("http")
|
6 | https = require("https")
|
7 | fs = require("fs")
|
8 | connect = require("connect")
|
9 | serveStatic = require('serve-static')
|
10 | serveIndex = require('serve-index')
|
11 | liveReload = require("connect-livereload")
|
12 | send = require("send")
|
13 | tiny_lr = require("tiny-lr")
|
14 | apps = []
|
15 |
|
16 | class ConnectApp
|
17 | constructor: (options, startedCallback) ->
|
18 | @name = options.name || "Server"
|
19 | @port = options.port || "8080"
|
20 | @root = options.root || path.dirname(module.parent.id)
|
21 | @host = options.host || "localhost"
|
22 | @debug = options.debug || false
|
23 | @silent = options.silent || false
|
24 | @https = options.https || false
|
25 | @preferHttp1 = options.preferHttp1 || false;
|
26 | @livereload = options.livereload || false
|
27 | @middleware = options.middleware || undefined
|
28 | @startedCallback = startedCallback || () -> {};
|
29 | @serverInit = options.serverInit || undefined
|
30 | @fallback = options.fallback || undefined
|
31 | @index = options.index
|
32 | @oldMethod("open") if options.open
|
33 | @sockets = []
|
34 | @app = undefined
|
35 | @lr = undefined
|
36 | @state = "initializing"
|
37 | @run()
|
38 |
|
39 | run: ->
|
40 | if @state == "stopped"
|
41 | return
|
42 |
|
43 | @state = "starting"
|
44 | @log "Starting server..."
|
45 | @app = connect()
|
46 |
|
47 | @handlers().forEach (middleware) =>
|
48 | if typeof (middleware) is "object"
|
49 | @app.use middleware[0], middleware[1]
|
50 | else
|
51 | @app.use middleware
|
52 |
|
53 | @app.use serveIndex(if typeof @root == "object" then @root[0] else @root)
|
54 |
|
55 | if @https
|
56 |
|
57 |
|
58 |
|
59 | if typeof (@https) is 'boolean' || (!@https.key && !@https.pfx)
|
60 |
|
61 |
|
62 | if !(typeof (@https) is "object")
|
63 | @https = {}
|
64 |
|
65 | @https.key = fs.readFileSync __dirname + '/certs/server.key'
|
66 | @https.cert = fs.readFileSync __dirname + '/certs/server.crt'
|
67 | @https.ca = fs.readFileSync __dirname + '/certs/server.crt'
|
68 | @https.passphrase = 'gulp'
|
69 |
|
70 | http2 = undefined
|
71 | if !@preferHttp1
|
72 | try
|
73 | http2 = require('http2')
|
74 |
|
75 | if http2
|
76 | @https.allowHTTP1 = true
|
77 | @server = http2.createSecureServer(@https, @app)
|
78 | else
|
79 | @server = https.createServer(@https, @app)
|
80 |
|
81 | else
|
82 | @server = http.createServer @app
|
83 | if @serverInit
|
84 | @serverInit @server
|
85 | @server.listen @port, @host, (err) =>
|
86 | if err
|
87 | @log "Error on starting server: #{err}"
|
88 | else
|
89 | @log "#{@name} started http#{if @https then 's' else ''}://#{@host}:#{@port}"
|
90 |
|
91 | stoped = false
|
92 | sockets = []
|
93 |
|
94 | @server.on "close", =>
|
95 | if (!stoped)
|
96 | stoped = true
|
97 | @log "#{@name} stopped"
|
98 |
|
99 |
|
100 | @server.on "connection", (socket) =>
|
101 | @logDebug "Received incoming connection from #{socket.address().address}"
|
102 | @sockets.push socket
|
103 | socket.on "close", =>
|
104 | @sockets.splice @sockets.indexOf(socket), 1
|
105 |
|
106 | @server.on "request", (request, response) =>
|
107 | @logDebug "Received request #{request.method} #{request.url}"
|
108 |
|
109 | @server.on "error", (err) =>
|
110 | @log err.toString()
|
111 |
|
112 | stopServer = =>
|
113 | if (!stoped)
|
114 | @sockets.forEach (socket) =>
|
115 | socket.destroy()
|
116 |
|
117 | @server.close()
|
118 | if @livereload
|
119 | @lr.close()
|
120 | process.nextTick( ->
|
121 | process.exit(0)
|
122 | )
|
123 |
|
124 | process.on("SIGINT", stopServer)
|
125 | process.on("exit", stopServer)
|
126 |
|
127 | if @livereload
|
128 | tiny_lr.Server::error = ->
|
129 | if @https
|
130 | @lr = tiny_lr
|
131 | key: @https.key || fs.readFileSync __dirname + '/certs/server.key'
|
132 | cert: @https.cert || fs.readFileSync __dirname + '/certs/server.crt'
|
133 | else
|
134 | @lr = tiny_lr()
|
135 |
|
136 | @lr.listen @livereload.port
|
137 | @log "LiveReload started on port #{@livereload.port}"
|
138 | @state = "running";
|
139 | @log "Running server"
|
140 | @startedCallback()
|
141 |
|
142 | close: ->
|
143 | if @state == "running"
|
144 | @log "Stopping server"
|
145 | if @livereload
|
146 | @lr.close()
|
147 | @server.close()
|
148 | @state = "stopped"
|
149 | @log "Stopped server"
|
150 | else if @state == "stopped"
|
151 | @log "Server has already been stopped."
|
152 | else
|
153 | @log "Ignoring stop as server is in " + @state + " state."
|
154 |
|
155 | handlers: ->
|
156 | steps = if @middleware then @middleware.call(this, connect, @) else []
|
157 | if @livereload
|
158 | @livereload = {} if typeof @livereload is "boolean"
|
159 | @livereload.port = 35729 unless @livereload.port
|
160 | steps.unshift liveReload(@livereload)
|
161 | if @index is true then @index = "index.html"
|
162 | if typeof @root == "object"
|
163 | @root.forEach (path) ->
|
164 | steps.push serveStatic(path, {index: @index})
|
165 | else
|
166 | steps.push serveStatic(@root, {index: @index})
|
167 | if @fallback
|
168 | steps.push (req, res) =>
|
169 | fallbackPath = @fallback
|
170 |
|
171 | if typeof @fallback is "function"
|
172 | fallbackPath = @fallback(req, res)
|
173 |
|
174 | send(req, fallbackPath).pipe(res);
|
175 |
|
176 | return steps
|
177 |
|
178 | log: (text) ->
|
179 | if !@silent
|
180 | fancyLog colors.green(text)
|
181 |
|
182 | logWarning: (text) ->
|
183 | if !@silent
|
184 | fancyLog colors.yellow(text)
|
185 |
|
186 | logDebug: (text) ->
|
187 | if @debug
|
188 | fancyLog colors.blue(text)
|
189 |
|
190 | oldMethod: (type) ->
|
191 | text = 'does not work in gulp-connect v 2.*. Please read "readme" https://github.com/AveVlad/gulp-connect'
|
192 | switch type
|
193 | when "open" then @logWarning("Option open #{text}")
|
194 |
|
195 | module.exports =
|
196 | server: (options = {}, startedCallback = null) ->
|
197 | app = new ConnectApp(options, startedCallback)
|
198 | apps.push(app)
|
199 | app
|
200 | reload: ->
|
201 | mapStream (file, callback) ->
|
202 | apps.forEach (app) =>
|
203 | if app.livereload and typeof app.lr == "object"
|
204 | app.lr.changed body:
|
205 | files: file.path
|
206 | callback null, file
|
207 | serverClose: ->
|
208 | apps.forEach((app) -> do app.close)
|
209 | apps = []
|