Class: Roby::Interface::REST::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/roby/interface/rest/server.rb

Overview

A thin-based server class that provides the REST API in-process

Defined Under Namespace

Classes: InvalidServer, Timeout

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, host: '0.0.0.0', port: Roby::Interface::DEFAULT_REST_PORT, api: REST::API) ⇒ Server

Create a new server

Parameters:

  • the (Roby::Application)

    application this server will be exposing

  • host (String) (defaults to: '0.0.0.0')

    the host the server should bind to. Either an IP for a TCP server, or a path to a UNIX socket

  • port (Integer) (defaults to: Roby::Interface::DEFAULT_REST_PORT)

    the port the server should bind to if it is a TCP server. Set to zero to auto-allocate. Ignored if ‘host’ is the path to a UNIX socket.



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
# File 'lib/roby/interface/rest/server.rb', line 32

def initialize(app, host: '0.0.0.0', port: Roby::Interface::DEFAULT_REST_PORT,
               api: REST::API)
    @app = app
    @host = host
    @interface = Interface.new(app)
    @wait_start = Concurrent::IVar.new

    api = self.class.attach_api_to_interface(api, @interface)
    rack_app = Rack::Builder.new do
        yield(self) if block_given?

        map '/api' do
            run api
        end
    end
    @server = Thin::Server.new(host, port, rack_app, signals: false)
    @server.silent = true
    if @server.backend.respond_to?(:port)
        @original_port = port
        if port != 0
            @port = port
        end
    else
        @original_port = @port = nil
    end
end

Instance Attribute Details

#appObject (readonly)

The application that is being exposed by this server



7
8
9
# File 'lib/roby/interface/rest/server.rb', line 7

def app
  @app
end

#hostString (readonly)

The host the server is bound to

Returns:

  • (String)


12
13
14
# File 'lib/roby/interface/rest/server.rb', line 12

def host
  @host
end

#port(timeout: 0) ⇒ Object (readonly)

The server port

If the port given to #initialize was zero, the method will return the actual port only if the server is fully started. Set timeout to a value greater than zero or nil to synchronize on it.

Parameters:

  • timeout (Integer, nil) (defaults to: 0)

    how long the method is allowed to wait for the server to start, in case the original port was set to zero

Raises:



21
22
23
# File 'lib/roby/interface/rest/server.rb', line 21

def port
  @port
end

Class Method Details

.attach_api_to_interface(api, interface) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Helper method that transforms a Grape API class so that it gets an #interface accessor that provides the interface object the API is meant to work on



64
65
66
67
68
69
70
71
72
73
# File 'lib/roby/interface/rest/server.rb', line 64

def self.attach_api_to_interface(api, interface)
    storage = Hash.new
    Class.new do
        define_method(:call) do |env|
            env['roby.interface'] = interface
            env['roby.storage'] = storage
            api.call(env)
        end
    end.new
end

.server_alive?(host, port) ⇒ Boolean

Tests whether the server is actually alive

It accesses the ‘ping’ endpoint and verifies its result

Returns:

  • (Boolean)

Raises:

  • InvalidServer if there is a server at the expected host and port, but not a Roby REST server



195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/roby/interface/rest/server.rb', line 195

def self.server_alive?(host, port)
    test_value = rand(10)
    returned_value = RestClient.
        get("http://#{host}:#{port}/api/ping", params: { value: test_value })
    if test_value != Integer(returned_value)
        raise InvalidServer, "unexpected server answer to 'ping', expected #{test_value} but got #{returned_value}"
    end
    true
rescue Errno::ECONNREFUSED => e
    false
rescue RestClient::Exception => e
    raise InvalidServer, "unexpected server answer to 'ping': #{e}"
end

Instance Method Details

#create_thin_thread(server, sync) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Create the underlying thread that starts the thin server



97
98
99
100
101
102
103
104
105
106
107
# File 'lib/roby/interface/rest/server.rb', line 97

def create_thin_thread(server, sync)
    Thread.new do
        server.backend.start do
            if server.backend.respond_to?(:port)
                sync.set(server.backend.port)
            else
                sync.set(nil)
            end
        end
    end
end

#join(timeout: nil) ⇒ Object

Waits for the server to stop

Parameters:

  • timeout (Numeric, nil) (defaults to: nil)

    how long the method is allowed to block waiting for the thread to stop. Set to zero to not wait at all, and nil to wait forever

Raises:



166
167
168
169
170
171
172
173
174
# File 'lib/roby/interface/rest/server.rb', line 166

def join(timeout: nil)
    if timeout
        if !@server_thread.join(timeout)
            raise Timeout, "timed out while waiting for the server to stop"
        end
    else
        @server_thread.join
    end
end

#running?Boolean

Whether the server is running

Returns:

  • (Boolean)


90
91
92
# File 'lib/roby/interface/rest/server.rb', line 90

def running?
    @server_thead && @server_thread.alive?
end

#server_alive?Boolean

Returns:

  • (Boolean)


181
182
183
184
185
186
187
# File 'lib/roby/interface/rest/server.rb', line 181

def server_alive?
    if !@wait_start.complete?
        return false
    else
        self.class.server_alive?('localhost', port)
    end
end

#start(wait_timeout: 5) ⇒ Object

Starts the server

Parameters:

  • wait_timeout (Numeric, nil) (defaults to: 5)

    how long the method should wait for the server to be started and functional. Set to zero to not wait at all, and nil to wait forever

Raises:

  • (Timeout)

    if wait_timeout is non-zero and the timeout was reached while waiting for the server to start



82
83
84
85
86
87
# File 'lib/roby/interface/rest/server.rb', line 82

def start(wait_timeout: 5)
    @server_thread = create_thin_thread(@server, @wait_start)
    if wait_timeout != 0
        wait_start(timeout: wait_timeout)
    end
end

#stop(join_timeout: 10) ⇒ Object

Asks the server to stop

Parameters:

  • join_timeout (Numeric, nil) (defaults to: 10)

    how long the method is allowed to block waiting for the thread to stop. Set to zero to not wait at all, and nil to wait forever

Raises:



153
154
155
156
157
158
# File 'lib/roby/interface/rest/server.rb', line 153

def stop(join_timeout: 10)
    EventMachine.next_tick { @server.stop! }
    if join_timeout != 0
        join(timeout: join_timeout)
    end
end

#wait_start(timeout: 10) ⇒ Object

Wait for the server to be properly booted

Parameters:

  • timeout (Numeric, nil) (defaults to: 10)

    how long the method is allowed to block waiting for the server to start. Set to zero to not wait at all and nil to wait forever

Raises:



140
141
142
143
144
145
# File 'lib/roby/interface/rest/server.rb', line 140

def wait_start(timeout: 10)
    @wait_start.wait(timeout)
    if !@wait_start.complete?
        raise Timeout, "timed out while waiting for the server to start"
    end
end