whiskey 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +24 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +8 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +74 -0
- data/ROADMAP.md +13 -0
- data/Rakefile +24 -0
- data/bin/whiskey +17 -0
- data/lib/whiskey.rb +16 -0
- data/lib/whiskey/command.rb +30 -0
- data/lib/whiskey/command/build.rb +89 -0
- data/lib/whiskey/command/start_server.rb +15 -0
- data/lib/whiskey/command/templates/Gemfile +10 -0
- data/lib/whiskey/command/templates/LICENSE.txt +22 -0
- data/lib/whiskey/command/templates/Procfile +1 -0
- data/lib/whiskey/command/templates/Thorfile +4 -0
- data/lib/whiskey/command/templates/db/seed.rb +15 -0
- data/lib/whiskey/command/templates/gitignore +17 -0
- data/lib/whiskey/command/templates/lib/action.rb +3 -0
- data/lib/whiskey/command/templates/lib/controls.rb +7 -0
- data/lib/whiskey/command/templates/lib/controls/router_control.rb +19 -0
- data/lib/whiskey/command/templates/lib/controls/world_control.rb +6 -0
- data/lib/whiskey/command/templates/lib/example.rb +7 -0
- data/lib/whiskey/command/templates/lib/models.rb +3 -0
- data/lib/whiskey/command/templates/lib/models/account.rb +6 -0
- data/lib/whiskey/command/templates/lib/models/actor.rb +6 -0
- data/lib/whiskey/command/templates/lib/models/place.rb +6 -0
- data/lib/whiskey/command/templates/ruby-gemset +1 -0
- data/lib/whiskey/command/templates/ruby-version +1 -0
- data/lib/whiskey/command/templates/server.rb +20 -0
- data/lib/whiskey/server.rb +81 -0
- data/lib/whiskey/server/action.rb +22 -0
- data/lib/whiskey/server/configuration.rb +9 -0
- data/lib/whiskey/server/connection.rb +51 -0
- data/lib/whiskey/server/control.rb +33 -0
- data/lib/whiskey/server/cycle.rb +35 -0
- data/lib/whiskey/server/deserializer.rb +21 -0
- data/lib/whiskey/server/error.rb +79 -0
- data/lib/whiskey/server/handler.rb +17 -0
- data/lib/whiskey/server/interpretor.rb +32 -0
- data/lib/whiskey/server/receiver.rb +19 -0
- data/lib/whiskey/server/responder.rb +19 -0
- data/lib/whiskey/server/router.rb +45 -0
- data/lib/whiskey/server/serializer.rb +22 -0
- data/lib/whiskey/version.rb +3 -0
- data/spec/lib/command/build_spec.rb +5 -0
- data/spec/lib/command/start_server_spec.rb +5 -0
- data/spec/lib/command_spec.rb +5 -0
- data/spec/lib/server/configuration_spec.rb +5 -0
- data/spec/lib/server/connection_spec.rb +43 -0
- data/spec/lib/server/cycle_spec.rb +23 -0
- data/spec/lib/server/deserializer_spec.rb +11 -0
- data/spec/lib/server/error_spec.rb +5 -0
- data/spec/lib/server/handler_spec.rb +5 -0
- data/spec/lib/server/interpretor_spec.rb +5 -0
- data/spec/lib/server/receiver_spec.rb +5 -0
- data/spec/lib/server/responder_spec.rb +15 -0
- data/spec/lib/server/serializer_spec.rb +11 -0
- data/spec/lib/server_spec.rb +31 -0
- data/spec/spec_helper.rb +9 -0
- data/test/lib/whiskey/server/connection_test.rb +42 -0
- data/test/lib/whiskey/server/cycle_test.rb +16 -0
- data/test/lib/whiskey/server/handler_test.rb +18 -0
- data/whiskey.gemspec +39 -0
- metadata +383 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module Controls
|
2
|
+
module RouteControl
|
3
|
+
include Whiskey::Server::Control
|
4
|
+
|
5
|
+
class ListAction < Action
|
6
|
+
include RouteControl
|
7
|
+
|
8
|
+
def initialize(parameters)
|
9
|
+
@routes = Controls.controls.map(&:to_route)
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_hash
|
13
|
+
{
|
14
|
+
routes: @routes
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= values.name %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= values.ruby %>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "bundler"
|
2
|
+
|
3
|
+
if defined?(Bundler)
|
4
|
+
ENV["WHISKEY_ENVIRONMENT"] ||= "development"
|
5
|
+
Bundler.require
|
6
|
+
Bundler.require ENV["WHISKEY_ENVIRONMENT"].to_sym
|
7
|
+
end
|
8
|
+
|
9
|
+
require "whiskey/server"
|
10
|
+
|
11
|
+
unless $LOAD_PATH.include?(File.expand_path("../lib", __FILE__))
|
12
|
+
$:.unshift File.expand_path("../lib", __FILE__)
|
13
|
+
end
|
14
|
+
|
15
|
+
require "<%= values.name %>"
|
16
|
+
|
17
|
+
Whiskey::Server.configure do |config|
|
18
|
+
config.host = ENV["WHISKEY_HOST"] || ENV["HOST"] || "0.0.0.0"
|
19
|
+
config.port = ENV["WHISKEY_PORT"] || ENV["PORT"] || 9000
|
20
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require "whiskey"
|
2
|
+
require "celluloid/io"
|
3
|
+
require_relative "server/configuration"
|
4
|
+
require_relative "server/handler"
|
5
|
+
require_relative "server/connection"
|
6
|
+
require_relative "server/cycle"
|
7
|
+
require_relative "server/receiver"
|
8
|
+
require_relative "server/deserializer"
|
9
|
+
require_relative "server/interpretor"
|
10
|
+
require_relative "server/responder"
|
11
|
+
require_relative "server/serializer"
|
12
|
+
require_relative "server/error"
|
13
|
+
require_relative "server/control"
|
14
|
+
require_relative "server/action"
|
15
|
+
require_relative "server/router"
|
16
|
+
|
17
|
+
module Whiskey
|
18
|
+
class Server
|
19
|
+
include Celluloid::IO
|
20
|
+
|
21
|
+
# This method is the start point for the server, allowing the user
|
22
|
+
# to pass in settings for the server. It can be done like this:
|
23
|
+
#
|
24
|
+
# Whiskey::Server.configure(host: "localhost", port: 4222)
|
25
|
+
#
|
26
|
+
# or with a block:
|
27
|
+
#
|
28
|
+
# Whiskey::Server.configure do |config|
|
29
|
+
# config.host = "localhost"
|
30
|
+
# config.port = 4222
|
31
|
+
# end
|
32
|
+
def self.configure(options = {}, &block)
|
33
|
+
@@configuration = Configuration.new(options)
|
34
|
+
instance_exec(@@configuration, &block) if block_given?
|
35
|
+
start
|
36
|
+
end
|
37
|
+
|
38
|
+
# This method starts the server according to the configuration object
|
39
|
+
# and then follows the instructions set by Celluloid-Io examples.
|
40
|
+
def self.start
|
41
|
+
supervise(@@configuration.host, @@configuration.port)
|
42
|
+
trap("INT") { supervisor.terminate; exit }
|
43
|
+
sleep
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :server
|
47
|
+
|
48
|
+
def initialize(host, port)
|
49
|
+
Whiskey.logger.info "Initializing a server at #{@@configuration}..."
|
50
|
+
@server = TCPServer.new(host, port)
|
51
|
+
Whiskey.logger.info "Successfully bound host and port!"
|
52
|
+
async.run
|
53
|
+
end
|
54
|
+
|
55
|
+
def run
|
56
|
+
loop { async.handle_connection server.accept }
|
57
|
+
end
|
58
|
+
|
59
|
+
def finalizer(callback)
|
60
|
+
server.close if server
|
61
|
+
end
|
62
|
+
|
63
|
+
def handle_connection(socket)
|
64
|
+
connection = Connection.new(socket)
|
65
|
+
Whiskey.logger.info "New connection created from #{connection.id}"
|
66
|
+
begin
|
67
|
+
Handler.new(connection).handle
|
68
|
+
rescue => connection_error
|
69
|
+
Whiskey.logger.error connection_error
|
70
|
+
ensure
|
71
|
+
# If the connection has been broken, this might not work: Errno::EPIPE
|
72
|
+
begin
|
73
|
+
socket.write Serializer.new(Error.new(:internal_server_error).to_hash).data
|
74
|
+
rescue => socket_error
|
75
|
+
Whiskey.logger.error socket_error
|
76
|
+
end
|
77
|
+
socket.close
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Whiskey
|
2
|
+
class Server
|
3
|
+
class Action
|
4
|
+
def self.to_verb
|
5
|
+
case name.demodulize
|
6
|
+
when "ListAction" then "PULL"
|
7
|
+
when "CreateAction" then "PUSH"
|
8
|
+
when "ShowAction" then "PULL"
|
9
|
+
when "UpdateAction" then "PUSH"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(*args)
|
14
|
+
raise "Not yet implemented"
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_hash
|
18
|
+
raise "Not yet implemented"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Whiskey
|
2
|
+
class Server
|
3
|
+
class Connection
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
attr_reader :socket
|
7
|
+
|
8
|
+
def initialize(socket)
|
9
|
+
@socket = socket
|
10
|
+
end
|
11
|
+
|
12
|
+
def id
|
13
|
+
"#{ip}:#{port}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def process
|
17
|
+
write cycle
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def cycle
|
23
|
+
Cycle.new(incoming).tap(&:interpret!).output
|
24
|
+
end
|
25
|
+
|
26
|
+
def incoming
|
27
|
+
readpartial(4096)
|
28
|
+
end
|
29
|
+
|
30
|
+
def port
|
31
|
+
peeraddr[1]
|
32
|
+
end
|
33
|
+
|
34
|
+
def ip
|
35
|
+
peeraddr[3]
|
36
|
+
end
|
37
|
+
|
38
|
+
def write(*args)
|
39
|
+
socket.write(*args)
|
40
|
+
end
|
41
|
+
|
42
|
+
def readpartial(*args)
|
43
|
+
socket.readpartial(*args)
|
44
|
+
end
|
45
|
+
|
46
|
+
def peeraddr(*args)
|
47
|
+
socket.peeraddr(*args)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Whiskey
|
2
|
+
class Server
|
3
|
+
module Control
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def to_resource
|
10
|
+
name.demodulize.downcase.gsub(/control/, '')
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_route
|
14
|
+
{
|
15
|
+
to_resource => actions.map(&:to_verb)
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def controls
|
20
|
+
constants.select do |constant|
|
21
|
+
constant.to_s.end_with?("Control")
|
22
|
+
end.map(&method(:const_get))
|
23
|
+
end
|
24
|
+
|
25
|
+
def actions
|
26
|
+
constants.select do |constant|
|
27
|
+
constant.to_s.end_with?("Action")
|
28
|
+
end.map(&method(:const_get))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Whiskey
|
2
|
+
class Server
|
3
|
+
class Cycle
|
4
|
+
attr_reader :input
|
5
|
+
attr_reader :response
|
6
|
+
|
7
|
+
def initialize(input)
|
8
|
+
@input = deserialized(input)
|
9
|
+
end
|
10
|
+
|
11
|
+
def interpret!
|
12
|
+
@response ||= Interpretor.new(input).response
|
13
|
+
end
|
14
|
+
|
15
|
+
def output
|
16
|
+
serialized(response)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def deserialized(input)
|
22
|
+
receiver = Receiver.new(input)
|
23
|
+
if receiver.valid?
|
24
|
+
receiver.deserialize
|
25
|
+
else
|
26
|
+
@response = Error.new(:bad_request).to_hash
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def serialized(response)
|
31
|
+
Responder.new(response).serialize
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Whiskey
|
2
|
+
class Server
|
3
|
+
class Deserializer
|
4
|
+
attr_reader :data
|
5
|
+
|
6
|
+
def initialize(raw)
|
7
|
+
begin
|
8
|
+
@data = MultiJson.load(raw.chomp!)
|
9
|
+
@valid = true
|
10
|
+
rescue Exception => error
|
11
|
+
Whiskey.logger.error error
|
12
|
+
@valid = false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid?
|
17
|
+
@valid
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Whiskey
|
2
|
+
class Server
|
3
|
+
class Error
|
4
|
+
def initialize(code)
|
5
|
+
@payload = send(code)
|
6
|
+
Whiskey.logger.error(to_s)
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_hash
|
10
|
+
{
|
11
|
+
status: status,
|
12
|
+
error: message
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def message
|
17
|
+
"#{title} - #{body}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def status
|
21
|
+
@payload[:status]
|
22
|
+
end
|
23
|
+
|
24
|
+
def title
|
25
|
+
@payload[:title]
|
26
|
+
end
|
27
|
+
|
28
|
+
def body
|
29
|
+
@payload[:body]
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
"#{status} #{message}"
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def bad_request
|
39
|
+
{
|
40
|
+
title: "BAD REQUEST",
|
41
|
+
body: "The request cannot be fulfilled due to bad syntax",
|
42
|
+
status: 400
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def unprocessable_entity
|
47
|
+
{
|
48
|
+
title: "UNPROCESSABLE ENTITY",
|
49
|
+
body: "The request was well-formed but was unable to be followed due to semantic errors.",
|
50
|
+
status: 422
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def not_found
|
55
|
+
{
|
56
|
+
title: "RESOURCE NOT FOUND",
|
57
|
+
body: "The requested resource could not be found but may be available again in the future. Subsequent requests by the client are permissible.",
|
58
|
+
status: 404
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def internal_server_error
|
63
|
+
{
|
64
|
+
title: "INTERNAL SERVER ERROR",
|
65
|
+
body: "The server encountered an unexpected condition which prevented it from fulfilling the request.",
|
66
|
+
status: 500
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def method_not_allowed
|
71
|
+
{
|
72
|
+
title: "Method Not Allowed",
|
73
|
+
body: "The method specified in the Request-Line is not allowed for the resource identified by the Request-URI. The response MUST include an Allow header containing a list of valid methods for the requested resource.",
|
74
|
+
status: 405
|
75
|
+
}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|