Class: Herb::Dev::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/herb/dev/server.rb

Defined Under Namespace

Classes: Client

Constant Summary collapse

DEFAULT_PORT =
8592
HANDSHAKE_TIMEOUT =
5

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(port: DEFAULT_PORT, project_path: nil) ⇒ Server

Returns a new instance of Server.



32
33
34
35
36
37
38
39
40
# File 'lib/herb/dev/server.rb', line 32

def initialize(port: DEFAULT_PORT, project_path: nil)
  @port = port
  @project_path = project_path
  @clients = [] #: Array[Client]
  @mutex = Mutex.new
  @server = nil
  @accept_thread = nil
  @entry = nil
end

Instance Attribute Details

#portObject (readonly)

Returns the value of attribute port.



28
29
30
# File 'lib/herb/dev/server.rb', line 28

def port
  @port
end

#project_pathObject (readonly)

Returns the value of attribute project_path.



28
29
30
# File 'lib/herb/dev/server.rb', line 28

def project_path
  @project_path
end

Class Method Details

.find_available_port(starting_port = DEFAULT_PORT) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/herb/dev/server.rb', line 108

def self.find_available_port(starting_port = DEFAULT_PORT)
  port = starting_port

  loop do
    return port if port_available?(port)

    port += 1
    break if port > starting_port + 100
  end

  nil
end

.port_available?(port) ⇒ Boolean

Returns:

  • (Boolean)


99
100
101
102
103
104
105
106
# File 'lib/herb/dev/server.rb', line 99

def self.port_available?(port)
  server = TCPServer.new("0.0.0.0", port)
  server.close

  true
rescue Errno::EADDRINUSE
  false
end

Instance Method Details

#broadcast(message) ⇒ Object



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
# File 'lib/herb/dev/server.rb', line 69

def broadcast(message)
  data = message.is_a?(String) ? message : JSON.generate(message)

  failed_clients = []
  clients_snapshot = @mutex.synchronize { @clients.dup }

  clients_snapshot.each do |client|
    frame = WebSocket::Frame::Outgoing::Server.new(version: client.version, data: data, type: :text)

    client.mutex.synchronize do
      client.socket.write(frame.to_s)
      client.socket.flush
    end
  rescue StandardError
    safely_close(client.socket)

    failed_clients << client
  end

  return unless failed_clients.any?

  @mutex.synchronize do
    failed_clients.each { |client| @clients.delete(client) }
  end
end

#client_countObject



95
96
97
# File 'lib/herb/dev/server.rb', line 95

def client_count
  @mutex.synchronize { @clients.size }
end

#startObject



42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/herb/dev/server.rb', line 42

def start
  @entry = ServerEntry.new(pid: Process.pid, port: @port, project: @project_path)
  @entry.save
  @server = TCPServer.new("0.0.0.0", @port)

  @accept_thread = Thread.new do
    loop do
      socket = @server.accept
      Thread.new(socket) { |s| handle_connection(s) }
    rescue IOError
      break
    end
  end
end

#stopObject



57
58
59
60
61
62
63
64
65
66
67
# File 'lib/herb/dev/server.rb', line 57

def stop
  @mutex.synchronize do
    @clients.each { |client| safely_close(client.socket) }
    @clients.clear
  end

  safely_close(@server)

  @accept_thread&.kill
  @entry&.remove
end