原文地址:https://testerhome.com/articles/22617

云服安全组需要放开对应的端口访问权限即可

 需要优化sft的stream.js文件内容,修改后最新文件如下:

  1 var util = require('util')
  2 var gm = require('gm').subClass({imageMagick: true})
  3 var Promise = require('bluebird')
  4 var syrup = require('@devicefarmer/stf-syrup')
  5 var WebSocket = require('ws')
  6 var uuid = require('uuid')
  7 var EventEmitter = require('eventemitter3')
  8 var split = require('split')
  9 var adbkit = require('@devicefarmer/adbkit')
 10 
 11 var logger = require('../../../../util/logger')
 12 var lifecycle = require('../../../../util/lifecycle')
 13 var bannerutil = require('./util/banner')
 14 var FrameParser = require('./util/frameparser')
 15 var FrameConfig = require('./util/frameconfig')
 16 var BroadcastSet = require('./util/broadcastset')
 17 var StateQueue = require('../../../../util/statequeue')
 18 var RiskyStream = require('../../../../util/riskystream')
 19 var FailCounter = require('../../../../util/failcounter')
 20 
 21 module.exports = syrup.serial()
 22   .dependency(require('../../support/adb'))
 23   .dependency(require('../../resources/minicap'))
 24   .dependency(require('../util/display'))
 25   .dependency(require('./options'))
 26   .define(function(options, adb, minicap, display, screenOptions) {
 27     var log = logger.createLogger('device:plugins:screen:stream')
 28 
 29     function FrameProducer(config) {
 30       EventEmitter.call(this)
 31       this.actionQueue = []
 32       this.runningState = FrameProducer.STATE_STOPPED
 33       this.desiredState = new StateQueue()
 34       this.output = null
 35       this.socket = null
 36       this.pid = -1
 37       this.banner = null
 38       this.parser = null
 39       this.frameConfig = config
 40       this.readable = false
 41       this.needsReadable = false
 42       this.failCounter = new FailCounter(3, 10000)
 43       this.failCounter.on('exceedLimit', this._failLimitExceeded.bind(this))
 44       this.failed = false
 45       this.readableListener = this._readableListener.bind(this)
 46     }
 47 
 48     util.inherits(FrameProducer, EventEmitter)
 49 
 50     FrameProducer.STATE_STOPPED = 1
 51     FrameProducer.STATE_STARTING = 2
 52     FrameProducer.STATE_STARTED = 3
 53     FrameProducer.STATE_STOPPING = 4
 54 
 55     FrameProducer.prototype._ensureState = function() {
 56       if (this.desiredState.empty()) {
 57         return
 58       }
 59 
 60       if (this.failed) {
 61         log.warn('Will not apply desired state due to too many failures')
 62         return
 63       }
 64 
 65       switch (this.runningState) {
 66       case FrameProducer.STATE_STARTING:
 67       case FrameProducer.STATE_STOPPING:
 68         // Just wait.
 69         break
 70       case FrameProducer.STATE_STOPPED:
 71         if (this.desiredState.next() === FrameProducer.STATE_STARTED) {
 72           this.runningState = FrameProducer.STATE_STARTING
 73           this._startService().bind(this)
 74             .then(function(out) {
 75               this.output = new RiskyStream(out)
 76                 .on('unexpectedEnd', this._outputEnded.bind(this))
 77               return this._readOutput(this.output.stream)
 78             })
 79             .then(function() {
 80               return this._waitForPid()
 81             })
 82             .then(function() {
 83               return this._connectService()
 84             })
 85             .then(function(socket) {
 86               this.parser = new FrameParser()
 87               this.socket = new RiskyStream(socket)
 88                 .on('unexpectedEnd', this._socketEnded.bind(this))
 89               return this._readBanner(this.socket.stream)
 90             })
 91             .then(function(banner) {
 92               this.banner = banner
 93               return this._readFrames(this.socket.stream)
 94             })
 95             .then(function() {
 96               this.runningState = FrameProducer.STATE_STARTED
 97               this.emit('start')
 98             })
 99             .catch(Promise.CancellationError, function() {
100               return this._stop()
101             })
102             .catch(function(err) {
103               return this._stop().finally(function() {
104                 this.failCounter.inc()
105                 this.emit('error', err)
106               })
107             })
108             .finally(function() {
109               this._ensureState()
110             })
111         }
112         else {
113           setImmediate(this._ensureState.bind(this))
114         }
115         break
116       case FrameProducer.STATE_STARTED:
117         if (this.desiredState.next() === FrameProducer.STATE_STOPPED) {
118           this.runningState = FrameProducer.STATE_STOPPING
119           this._stop().finally(function() {
120             this._ensureState()
121           })
122         }
123         else {
124           setImmediate(this._ensureState.bind(this))
125         }
126         break
127       }
128     }
129 
130     FrameProducer.prototype.start = function() {
131       log.info('Requesting frame producer to start')
132       this.desiredState.push(FrameProducer.STATE_STARTED)
133       this._ensureState()
134     }
135 
136     FrameProducer.prototype.stop = function() {
137       log.info('Requesting frame producer to stop')
138       this.desiredState.push(FrameProducer.STATE_STOPPED)
139       this._ensureState()
140     }
141 
142     FrameProducer.prototype.restart = function() {
143       switch (this.runningState) {
144       case FrameProducer.STATE_STARTED:
145       case FrameProducer.STATE_STARTING:
146         this.desiredState.push(FrameProducer.STATE_STOPPED)
147         this.desiredState.push(FrameProducer.STATE_STARTED)
148         this._ensureState()
149         break
150       }
151     }
152 
153     FrameProducer.prototype.updateRotation = function(rotation) {
154       if (this.frameConfig.rotation === rotation) {
155         log.info('Keeping %d as current frame producer rotation', rotation)
156         return
157       }
158 
159       log.info('Setting frame producer rotation to %d', rotation)
160       this.frameConfig.rotation = rotation
161       this._configChanged()
162     }
163 
164     FrameProducer.prototype.updateProjection = function(width, height) {
165       if (this.frameConfig.virtualWidth === width &&
166           this.frameConfig.virtualHeight === height) {
167         log.info(
168           'Keeping %dx%d as current frame producer projection', width, height)
169         return
170       }
171 
172       log.info('Setting frame producer projection to %dx%d', width, height)
173       this.frameConfig.virtualWidth = width
174       this.frameConfig.virtualHeight = height
175       this._configChanged()
176     }
177 
178     FrameProducer.prototype.nextFrame = function() {
179       var frame = null
180       var chunk
181 
182       if (this.parser) {
183         while ((frame = this.parser.nextFrame()) === null) {
184           chunk = this.socket.stream.read()
185           if (chunk) {
186             this.parser.push(chunk)
187           }
188           else {
189             this.readable = false
190             break
191           }
192         }
193       }
194 
195       return frame
196     }
197 
198     FrameProducer.prototype.needFrame = function() {
199       this.needsReadable = true
200       this._maybeEmitReadable()
201     }
202 
203     FrameProducer.prototype._configChanged = function() {
204       this.restart()
205     }
206 
207     FrameProducer.prototype._socketEnded = function() {
208       log.warn('Connection to minicap ended unexpectedly')
209       this.failCounter.inc()
210       this.restart()
211     }
212 
213     FrameProducer.prototype._outputEnded = function() {
214       log.warn('Shell keeping minicap running ended unexpectedly')
215       this.failCounter.inc()
216       this.restart()
217     }
218 
219     FrameProducer.prototype._failLimitExceeded = function(limit, time) {
220       this._stop()
221       this.failed = true
222       this.emit('error', new Error(util.format(
223         'Failed more than %d times in %dms'
224       , limit
225       , time
226       )))
227     }
228 
229     FrameProducer.prototype._startService = function() {
230       log.info('Launching screen service')
231       return minicap.run(util.format(
232           '-S -Q %d -P %s'
233         , options.screenJpegQuality
234         , this.frameConfig.toString()
235         ))
236         .timeout(10000)
237     }
238 
239     FrameProducer.prototype._readOutput = function(out) {
240       out.pipe(split()).on('data', function(line) {
241         var trimmed = line.toString().trim()
242 
243         if (trimmed === '') {
244           return
245         }
246 
247         if (/ERROR/.test(line)) {
248           log.fatal('minicap error: "%s"', line)
249           return lifecycle.fatal()
250         }
251 
252         var match = /^PID: (\d+)$/.exec(line)
253         if (match) {
254           this.pid = Number(match[1])
255           this.emit('pid', this.pid)
256         }
257 
258         log.info('minicap says: "%s"', line)
259       }.bind(this))
260     }
261 
262     FrameProducer.prototype._waitForPid = function() {
263       if (this.pid > 0) {
264         return Promise.resolve(this.pid)
265       }
266 
267       var pidListener
268       return new Promise(function(resolve) {
269           this.on('pid', pidListener = resolve)
270         }.bind(this)).bind(this)
271         .timeout(2000)
272         .finally(function() {
273           this.removeListener('pid', pidListener)
274         })
275     }
276 
277     FrameProducer.prototype._connectService = function() {
278       function tryConnect(times, delay) {
279         return adb.openLocal(options.serial, 'localabstract:minicap')
280           .timeout(10000)
281           .then(function(out) {
282             return out
283           })
284           .catch(function(err) {
285             if (/closed/.test(err.message) && times > 1) {
286               return Promise.delay(delay)
287                 .then(function() {
288                   return tryConnect(times - 1, delay * 2)
289                 })
290             }
291             return Promise.reject(err)
292           })
293       }
294       log.info('Connecting to minicap service')
295       return tryConnect(5, 100)
296     }
297 
298     FrameProducer.prototype._stop = function() {
299       return this._disconnectService(this.socket).bind(this)
300         .timeout(2000)
301         .then(function() {
302           return this._stopService(this.output).timeout(10000)
303         })
304         .then(function() {
305           this.runningState = FrameProducer.STATE_STOPPED
306           this.emit('stop')
307         })
308         .catch(function(err) {
309           // In practice we _should_ never get here due to _stopService()
310           // being quite aggressive. But if we do, well... assume it
311           // stopped anyway for now.
312           this.runningState = FrameProducer.STATE_STOPPED
313           this.emit('error', err)
314           this.emit('stop')
315         })
316         .finally(function() {
317           this.output = null
318           this.socket = null
319           this.pid = -1
320           this.banner = null
321           this.parser = null
322         })
323     }
324 
325     FrameProducer.prototype._disconnectService = function(socket) {
326       log.info('Disconnecting from minicap service')
327 
328       if (!socket || socket.ended) {
329         return Promise.resolve(true)
330       }
331 
332       socket.stream.removeListener('readable', this.readableListener)
333 
334       var endListener
335       return new Promise(function(resolve) {
336           socket.on('end', endListener = function() {
337             resolve(true)
338           })
339 
340           socket.stream.resume()
341           socket.end()
342         })
343         .finally(function() {
344           socket.removeListener('end', endListener)
345         })
346     }
347 
348     FrameProducer.prototype._stopService = function(output) {
349       log.info('Stopping minicap service')
350 
351       if (!output || output.ended) {
352         return Promise.resolve(true)
353       }
354 
355       var pid = this.pid
356 
357       function kill(signal) {
358         if (pid <= 0) {
359           return Promise.reject(new Error('Minicap service pid is unknown'))
360         }
361 
362         var signum = {
363           SIGTERM: -15
364         , SIGKILL: -9
365         }[signal]
366 
367         log.info('Sending %s to minicap', signal)
368         return Promise.all([
369             output.waitForEnd()
370           , adb.shell(options.serial, ['kill', signum, pid])
371               .then(adbkit.util.readAll)
372               .return(true)
373           ])
374           .timeout(2000)
375       }
376 
377       function kindKill() {
378         return kill('SIGTERM')
379       }
380 
381       function forceKill() {
382         return kill('SIGKILL')
383       }
384 
385       function forceEnd() {
386         log.info('Ending minicap I/O as a last resort')
387         output.end()
388         return Promise.resolve(true)
389       }
390 
391       return kindKill()
392         .catch(Promise.TimeoutError, forceKill)
393         .catch(forceEnd)
394     }
395 
396     FrameProducer.prototype._readBanner = function(socket) {
397       log.info('Reading minicap banner')
398       return bannerutil.read(socket).timeout(2000)
399     }
400 
401     FrameProducer.prototype._readFrames = function(socket) {
402       this.needsReadable = true
403       socket.on('readable', this.readableListener)
404 
405       // We may already have data pending. Let the user know they should
406       // at least attempt to read frames now.
407       this.readableListener()
408     }
409 
410     FrameProducer.prototype._maybeEmitReadable = function() {
411       if (this.readable && this.needsReadable) {
412         this.needsReadable = false
413         this.emit('readable')
414       }
415     }
416 
417     FrameProducer.prototype._readableListener = function() {
418       this.readable = true
419       this._maybeEmitReadable()
420     }
421 
422     function createServer() {
423       log.info('Starting WebSocket server on port %d', screenOptions.publicPort)
424 
425       var wss = new WebSocket.Server({
426         port: screenOptions.publicPort
427       , perMessageDeflate: false
428       })
429 
430       var listeningListener, errorListener
431       return new Promise(function(resolve, reject) {
432           listeningListener = function() {
433             return resolve(wss)
434           }
435 
436           errorListener = function(err) {
437             return reject(err)
438           }
439 
440           wss.on('listening', listeningListener)
441           wss.on('error', errorListener)
442         })
443         .finally(function() {
444           wss.removeListener('listening', listeningListener)
445           wss.removeListener('error', errorListener)
446         })
447     }
448 
449     return createServer()
450       .then(function(wss) {
451         var frameProducer = new FrameProducer(
452           new FrameConfig(display.properties, display.properties))
453         var broadcastSet = frameProducer.broadcastSet = new BroadcastSet()
454 
455         broadcastSet.on('nonempty', function() {
456           frameProducer.start()
457         })
458 
459         broadcastSet.on('empty', function() {
460           frameProducer.stop()
461         })
462 
463         broadcastSet.on('insert', function(id) {
464           // If two clients join a session in the middle, one of them
465           // may not release the initial size because the projection
466           // doesn't necessarily change, and the producer doesn't Getting
467           // restarted. Therefore we have to call onStart() manually
468           // if the producer is already up and running.
469           switch (frameProducer.runningState) {
470           case FrameProducer.STATE_STARTED:
471             broadcastSet.get(id).onStart(frameProducer)
472             break
473           }
474         })
475 
476         display.on('rotationChange', function(newRotation) {
477           frameProducer.updateRotation(newRotation)
478         })
479 
480         frameProducer.on('start', function() {
481           broadcastSet.keys().map(function(id) {
482             return broadcastSet.get(id).onStart(frameProducer)
483           })
484         })
485 
486         frameProducer.on('readable', function next() {
487           var frame = frameProducer.nextFrame()
488           if (frame) {
489             Promise.settle([broadcastSet.keys().map(function(id) {
490               return broadcastSet.get(id).onFrame(frame)
491             })]).then(next)
492           }
493           else {
494             frameProducer.needFrame()
495           }
496         })
497 
498         frameProducer.on('error', function(err) {
499           log.fatal('Frame producer had an error', err.stack)
500           lifecycle.fatal()
501         })
502 
503         wss.on('connection', function(ws) {
504           var id = uuid.v4()
505           var pingTimer
506 
507           function send(message, options) {
508             return new Promise(function(resolve, reject) {
509               switch (ws.readyState) {
510               case WebSocket.OPENING:
511                 // This should never happen.
512                 log.warn('Unable to send to OPENING client "%s"', id)
513                 break
514               case WebSocket.OPEN:
515                 // This is what SHOULD happen.
516                 ws.send(message, options, function(err) {
517                   return err ? reject(err) : resolve()
518                 })
519                 break
520               case WebSocket.CLOSING:
521                 // Ok, a 'close' event should remove the client from the set
522                 // soon.
523                 break
524               case WebSocket.CLOSED:
525                 // This should never happen.
526                 log.warn('Unable to send to CLOSED client "%s"', id)
527                 clearInterval(pingTimer)
528                 broadcastSet.remove(id)
529                 break
530               }
531             })
532           }
533 
534           function wsStartNotifier() {
535             return send(util.format(
536               'start %s'
537             , JSON.stringify(frameProducer.banner)
538             ))
539           }
540 
541           function wsPingNotifier() {
542             return send('ping')
543           }
544 
545           function wsFrameNotifier(frame) {
546             return send(frame, {
547               binary: true
548             })
549           }
550 
551           // Sending a ping message every now and then makes sure that
552           // reverse proxies like nginx don't time out the connection [1].
553           //
554           // [1] http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_read_timeout
555           pingTimer = setInterval(wsPingNotifier, options.screenPingInterval)
556 
557           ws.on('message', function(data) {
558             var match = /^(on|off|(size) ([0-9]+)x([0-9]+))$/.exec(data)
559             if (match) {
560               switch (match[2] || match[1]) {
561               case 'on':
562                 broadcastSet.insert(id, {
563                   onStart: wsStartNotifier
564                   , onFrame: (function (t) {
565                     var wait_time = 0;
566                     return function (frame) {
567                       wait_time++
568                       // return wsFrameNotifier(frame);
569                       if (wait_time < 30) {
570                         clearTimeout(t)
571 
572                         wait_time % 4 || gm(frame).quality(10).toBuffer(function (err, buffer) {
573                           wsFrameNotifier(buffer)
574                         })
575 
576                         t = setTimeout(function () {
577                           wait_time = 0
578                           wsFrameNotifier(frame)
579                         }, 100)
580                       }
581                     }
582                   })(0)
583                 })
584                 break
585               case 'off':
586                 broadcastSet.remove(id)
587                 // Keep pinging even when the screen is off.
588                 break
589               case 'size':
590                 frameProducer.updateProjection(
591                   Number(match[3]), Number(match[4]))
592                 break
593               }
594             }
595           })
596 
597           ws.on('close', function() {
598             clearInterval(pingTimer)
599             broadcastSet.remove(id)
600           })
601         })
602 
603         lifecycle.observe(function() {
604           wss.close()
605         })
606 
607         lifecycle.observe(function() {
608           frameProducer.stop()
609         })
610 
611         return frameProducer
612       })
613   })
View Code

 

posted on 2021-03-17 10:54  GSY921  阅读(1729)  评论(0编辑  收藏  举报