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, RackMiddleware, Timeout

Constant Summary collapse

VERBOSE_MIDDLEWARES =
[Rack::CommonLogger, Rack::ShowExceptions].freeze

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, main_route: "/api", storage: {}, threads: 1, roby_execute: true, middlewares: VERBOSE_MIDDLEWARES, **thin_options) ⇒ 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.

  • api (Grape::API) (defaults to: REST::API)

    used to route requests.

  • prefix (String)

    for routes. e.g. host:port/main_route/ping



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

def initialize(app,
               host: "0.0.0.0",
               port: Roby::Interface::DEFAULT_REST_PORT,
               api: REST::API,
               main_route: "/api",
               storage: {},
               threads: 1,
               roby_execute: true,
               middlewares: VERBOSE_MIDDLEWARES,
               **thin_options)

    @app = app
    @host = host
    @main_route = main_route
    @interface = Interface.new(app)
    @wait_start = Concurrent::IVar.new

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

        map main_route do
            middlewares.each { |m| use m }
            run api
        end
    end
    @server = Thin::Server.new(
        host, port, rack_app, signals: false, **thin_options
    )
    @server.threaded = (threads != 1)
    @server.threadpool_size = threads
    @server.silent = true
    if @server.backend.respond_to?(:port)
        @original_port = port
        @port = port if port != 0
    else
        @original_port = @port = nil
    end
end

Instance Attribute Details

#appObject (readonly)

The application that is being exposed by this server



16
17
18
# File 'lib/roby/interface/rest/server.rb', line 16

def app
  @app
end

#hostString (readonly)

The host the server is bound to

Returns:

  • (String)


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

def host
  @host
end

#main_routeString (readonly)

The path under which this API will be available, e.g. /api

Returns:

  • (String)


26
27
28
# File 'lib/roby/interface/rest/server.rb', line 26

def main_route
  @main_route
end

Class Method Details

.attach_api_to_interface(api, interface, storage = {}, roby_execute: true) ⇒ 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



116
117
118
119
120
121
122
# File 'lib/roby/interface/rest/server.rb', line 116

def self.attach_api_to_interface(
    api, interface, storage = {}, roby_execute: true
)
    RackMiddleware.new(
        api, interface, storage, roby_execute: roby_execute
    )
end

.server_alive?(host, port, main_route: "/api") ⇒ 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



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/roby/interface/rest/server.rb', line 236

def self.server_alive?(host, port, main_route: "/api")
    test_value = rand(10)
    returned_value = RestClient.get(
        "http://#{host}:#{port}#{main_route}/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, Errno::EADDRNOTAVAIL
    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



144
145
146
147
148
149
150
151
152
153
154
# File 'lib/roby/interface/rest/server.rb', line 144

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:



208
209
210
211
212
213
214
215
216
217
# File 'lib/roby/interface/rest/server.rb', line 208

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

#port(timeout: 0) ⇒ Object

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:



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

def port(timeout: 0)
    return @port if @port
    return unless @original_port

    wait_start(timeout: timeout)
    @port = @wait_start.value!
end

#running?Boolean

Whether the server is running

Returns:

  • (Boolean)


137
138
139
# File 'lib/roby/interface/rest/server.rb', line 137

def running?
    @server_thread&.alive?
end

#server_alive?Boolean

Returns:

  • (Boolean)


224
225
226
227
228
# File 'lib/roby/interface/rest/server.rb', line 224

def server_alive?
    return false unless @wait_start.complete?

    self.class.server_alive?("localhost", port, main_route: main_route)
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



131
132
133
134
# File 'lib/roby/interface/rest/server.rb', line 131

def start(wait_timeout: 5)
    @server_thread = create_thin_thread(@server, @wait_start)
    wait_start(timeout: wait_timeout) if wait_timeout != 0
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:



197
198
199
200
# File 'lib/roby/interface/rest/server.rb', line 197

def stop(join_timeout: 10)
    EventMachine.next_tick { @server.stop! }
    join(timeout: join_timeout) if join_timeout != 0
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:



184
185
186
187
188
189
# File 'lib/roby/interface/rest/server.rb', line 184

def wait_start(timeout: 10)
    @wait_start.wait(timeout)
    return if @wait_start.complete?

    raise Timeout, "timed out while waiting for the server to start"
end