cf-uaac 1.3.1 → 1.3.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +5 -0
- data/LICENSE.TXT +12737 -0
- data/NOTICE.TXT +10 -0
- data/Rakefile +0 -6
- data/bin/uaas +51 -2
- data/cf-uaac.gemspec +2 -2
- data/lib/cli/base.rb +14 -11
- data/lib/cli/client_reg.rb +2 -2
- data/lib/cli/config.rb +5 -5
- data/lib/cli/group.rb +6 -4
- data/lib/cli/runner.rb +5 -4
- data/lib/cli/token.rb +11 -9
- data/lib/cli/version.rb +2 -1
- data/lib/stub/scim.rb +9 -9
- data/lib/stub/server.rb +44 -38
- data/lib/stub/uaa.rb +18 -15
- data/spec/group_spec.rb +1 -1
- data/spec/http_spec.rb +3 -2
- data/spec/spec_helper.rb +3 -3
- data/spec/token_spec.rb +1 -1
- metadata +32 -7
data/NOTICE.TXT
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
Cloud Foundry 2012.02.03 Beta
|
2
|
+
Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
|
3
|
+
|
4
|
+
This product is licensed to you under the Apache License, Version 2.0 (the "License").
|
5
|
+
You may not use this product except in compliance with the License.
|
6
|
+
|
7
|
+
This product includes a number of subcomponents with
|
8
|
+
separate copyright notices and license terms. Your use of these
|
9
|
+
subcomponents is subject to the terms and conditions of the
|
10
|
+
subcomponent's license, as noted in the LICENSE file.
|
data/Rakefile
CHANGED
@@ -10,7 +10,6 @@
|
|
10
10
|
# subcomponent's license, as noted in the LICENSE file.
|
11
11
|
#
|
12
12
|
|
13
|
-
require "rdoc/task"
|
14
13
|
require "rspec/core/rake_task"
|
15
14
|
require "bundler/gem_tasks" # only available in bundler >= 1.0.15
|
16
15
|
require "ci/reporter/rake/rspec"
|
@@ -27,11 +26,6 @@ RSpec::Core::RakeTask.new("test") do |t|
|
|
27
26
|
t.pattern = "spec/**/*_spec.rb"
|
28
27
|
end
|
29
28
|
|
30
|
-
RDoc::Task.new do |rd|
|
31
|
-
rd.rdoc_files.include("lib/**/*.rb")
|
32
|
-
rd.rdoc_dir = "doc"
|
33
|
-
end
|
34
|
-
|
35
29
|
task :ci => [:pre_coverage, :rcov_reports, "ci:setup:rspec", :test]
|
36
30
|
task :cov => [:pre_coverage, :test, :view_coverage]
|
37
31
|
task :coverage => [:pre_coverage, :test]
|
data/bin/uaas
CHANGED
@@ -2,6 +2,55 @@
|
|
2
2
|
|
3
3
|
$:.unshift File.expand_path File.join __FILE__, '..', '..', 'lib'
|
4
4
|
require 'stub/uaa'
|
5
|
+
require 'cli/base'
|
6
|
+
require 'cli/version'
|
7
|
+
|
8
|
+
module CF::UAA
|
9
|
+
|
10
|
+
Util.default_logger(:trace)
|
11
|
+
|
12
|
+
class ServerTopic < Topic
|
13
|
+
topic "", "run"
|
14
|
+
desc "version", "Display version" do say "version #{CLI_VERSION}" end
|
15
|
+
define_option :trace, "--[no-]trace", "-t", "display extra verbose debug information"
|
16
|
+
define_option :debug, "--[no-]debug", "-d", "display debug information"
|
17
|
+
define_option :help, "--[no-]help", "-h", "display helpful information"
|
18
|
+
define_option :version, "--[no-]version", "-v", "show version"
|
19
|
+
|
20
|
+
desc "help [topic|command...]", "Display summary or details of command or topic" do |*args|
|
21
|
+
args.empty? ? say_help : say_command_help(args)
|
22
|
+
end
|
23
|
+
|
24
|
+
define_option :port, "--port <number>", "-p", "port for server to listen on"
|
25
|
+
define_option :root, "--root <path>", "-r", "root path of UAA resources, e.g. /uaa"
|
26
|
+
desc "run", "Run the UAA server", :port, :root do
|
27
|
+
CF::UAA::StubUAA.new(port: (opts[:port] || "8080").to_i, root: opts[:root],
|
28
|
+
logger: Util.default_logger).run
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
class ServerCli < BaseCli
|
34
|
+
@overview = "UAA Stub Server Command Line\nProvides partial uaa server support suitable for testing uaac."
|
35
|
+
@topics = [ServerTopic]
|
36
|
+
@global_options = [:help, :version, :debug, :trace]
|
37
|
+
@input, @output = $stdin, $stdout
|
38
|
+
|
39
|
+
def self.handle_bad_command(args, msg)
|
40
|
+
@output.puts "\n#{msg}"
|
41
|
+
run args.unshift("help")
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.preprocess_options(args, opts)
|
46
|
+
return args.replace(["version"]) if opts[:version]
|
47
|
+
return args.unshift("help") if args.empty? || opts[:help] && args[0] != "version"
|
48
|
+
Util.default_logger(opts[:trace]? :trace: opts[:debug]? :debug: :warn, @output)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
exit CF::UAA::ServerCli.run ? 0 : 1
|
5
56
|
|
6
|
-
CF::UAA::Util.default_logger(:trace)
|
7
|
-
CF::UAA::StubUAA.new.run('localhost', 8080)
|
data/cf-uaac.gemspec
CHANGED
@@ -38,10 +38,10 @@ Gem::Specification.new do |s|
|
|
38
38
|
s.add_development_dependency "simplecov"
|
39
39
|
s.add_development_dependency "simplecov-rcov"
|
40
40
|
s.add_development_dependency "ci_reporter"
|
41
|
-
s.add_runtime_dependency "cf-uaa-lib", ">= 1.3.1"
|
41
|
+
s.add_runtime_dependency "cf-uaa-lib", ">= 1.3.3", "<= 1.3.3"
|
42
42
|
s.add_runtime_dependency "highline"
|
43
43
|
s.add_runtime_dependency "eventmachine"
|
44
44
|
s.add_runtime_dependency "launchy"
|
45
45
|
s.add_runtime_dependency "em-http-request", ">= 1.0.0.beta.3"
|
46
|
-
|
46
|
+
s.add_runtime_dependency "json_pure"
|
47
47
|
end
|
data/lib/cli/base.rb
CHANGED
@@ -13,6 +13,7 @@
|
|
13
13
|
|
14
14
|
require 'highline'
|
15
15
|
require 'optparse'
|
16
|
+
require 'json/pure'
|
16
17
|
|
17
18
|
module CF; module UAA end end
|
18
19
|
|
@@ -56,10 +57,10 @@ class Topic
|
|
56
57
|
@highline = HighLine.new(input, output)
|
57
58
|
end
|
58
59
|
|
59
|
-
def ask(prompt)
|
60
|
-
def ask_pwd(prompt)
|
61
|
-
def say(msg)
|
62
|
-
def gripe(msg)
|
60
|
+
def ask(prompt) @highline.ask("#{prompt}: ") end
|
61
|
+
def ask_pwd(prompt) @highline.ask("#{prompt}: ") { |q| q.echo = '*' } end
|
62
|
+
def say(msg) @output.puts(msg); msg end
|
63
|
+
def gripe(msg) @output.puts(msg) end
|
63
64
|
def opts; @options end
|
64
65
|
|
65
66
|
def terminal_columns
|
@@ -232,16 +233,15 @@ class BaseCli
|
|
232
233
|
attr_accessor :overview, :topics, :global_options
|
233
234
|
end
|
234
235
|
|
235
|
-
def self.preprocess_options(args, opts)
|
236
|
-
def self.
|
236
|
+
def self.preprocess_options(args, opts) end # to be implemented in subclass
|
237
|
+
def self.handle_bad_command(args, msg) end # to be implemented in subclass
|
237
238
|
|
238
239
|
def self.run(args = ARGV)
|
239
240
|
@input ||= $stdin
|
240
241
|
@output ||= $stdout
|
241
|
-
@option_defs = {}
|
242
242
|
@output.string = "" if @output.respond_to?(:string)
|
243
243
|
args = args.split if args.respond_to?(:split)
|
244
|
-
@parser = OptionParser.new
|
244
|
+
@option_defs, @parser, orig = {}, OptionParser.new, args
|
245
245
|
opts = @topics.each_with_object({}) do |tpc, o|
|
246
246
|
tpc.option_defs.each do |k, optdef|
|
247
247
|
@parser.on(*optdef) { |v| o[k] = v }
|
@@ -257,11 +257,14 @@ class BaseCli
|
|
257
257
|
if v[:argc] == -1
|
258
258
|
# variable args, leave args alone
|
259
259
|
elsif args.length > v[:argc]
|
260
|
-
|
261
|
-
return nil
|
260
|
+
return handle_bad_command(orig, "Too many command line parameters given.")
|
262
261
|
elsif args.length < v[:argc]
|
263
262
|
(v[:argc] - args.length).times { args << nil }
|
264
263
|
end
|
264
|
+
opts.keys.each do |o|
|
265
|
+
next if v[:options].include?(o) || @global_options.include?(o)
|
266
|
+
return handle_bad_command(orig, "Invalid option: #{o}")
|
267
|
+
end
|
265
268
|
return tpc.new(self, opts, @input, @output).send(k, *args)
|
266
269
|
end
|
267
270
|
end
|
@@ -269,7 +272,7 @@ class BaseCli
|
|
269
272
|
rescue Exception => e
|
270
273
|
@output.puts "#{File.basename($0)} error", "#{e.class}: #{e.message}", (e.backtrace if opts[:trace])
|
271
274
|
ensure
|
272
|
-
puts @output.string if opts[:trace] && @print_on_trace
|
275
|
+
puts @output.string if opts[:trace] && @print_on_trace && @output.respond_to?(:string)
|
273
276
|
end
|
274
277
|
|
275
278
|
end
|
data/lib/cli/client_reg.rb
CHANGED
@@ -63,7 +63,7 @@ class ClientCli < CommonCli
|
|
63
63
|
pp scim_request { |cr|
|
64
64
|
opts[:client_id] = clientname(name)
|
65
65
|
opts[:secret] = verified_pwd("New client secret", opts[:secret])
|
66
|
-
defaults = opts[:clone] ? Util.hash_keys!(cr.get(opts[:clone]), :
|
66
|
+
defaults = opts[:clone] ? Util.hash_keys!(cr.get(opts[:clone]), :sym) : {}
|
67
67
|
defaults.delete(:client_id)
|
68
68
|
cr.add(:client, client_info(defaults))
|
69
69
|
}
|
@@ -73,7 +73,7 @@ class ClientCli < CommonCli
|
|
73
73
|
:del_attrs, :interact do |name|
|
74
74
|
pp scim_request { |cr|
|
75
75
|
opts[:client_id] = clientname(name)
|
76
|
-
orig = Util.hash_keys!(cr.get(:client, opts[:client_id]), :
|
76
|
+
orig = Util.hash_keys!(cr.get(:client, opts[:client_id]), :sym)
|
77
77
|
info = client_info(orig)
|
78
78
|
info.any? { |k, v| v != orig[k] } ? cr.put(:client, info) :
|
79
79
|
gripe("Nothing to update. Use -i for interactive update.")
|
data/lib/cli/config.rb
CHANGED
@@ -22,7 +22,7 @@ class Config
|
|
22
22
|
|
23
23
|
def self.config; @config ? @config.dup : {} end
|
24
24
|
def self.loaded?; !!@config end
|
25
|
-
def self.yaml; YAML.dump(Util.hash_keys(@config, :
|
25
|
+
def self.yaml; YAML.dump(Util.hash_keys(@config, :str)) end
|
26
26
|
def self.target?(tgt) tgt if @config[tgt = subhash_key(@config, tgt)] end
|
27
27
|
|
28
28
|
# if a yaml string is provided, config is loaded from the string, otherwise
|
@@ -48,12 +48,12 @@ class Config
|
|
48
48
|
else # file doesn't exist, make sure we can write it now
|
49
49
|
File.open(@config_file, 'w') { |f| f.write("--- {}\n\n") }
|
50
50
|
end
|
51
|
-
Util.hash_keys!(@config, :
|
51
|
+
Util.hash_keys!(@config, :sym)
|
52
52
|
@context = current_subhash(@config[@target][:contexts]) if @target = current_subhash(@config)
|
53
53
|
end
|
54
54
|
|
55
55
|
def self.save
|
56
|
-
File.open(@config_file, 'w') { |f| YAML.dump(Util.hash_keys(@config, :
|
56
|
+
File.open(@config_file, 'w') { |f| YAML.dump(Util.hash_keys(@config, :str), f) } if @config_file
|
57
57
|
true
|
58
58
|
end
|
59
59
|
|
@@ -70,7 +70,7 @@ class Config
|
|
70
70
|
raise ArgumentError, "target not set" unless @target
|
71
71
|
return unless hash and !hash.empty?
|
72
72
|
raise ArgumentError, "'contexts' is a reserved key" if hash.key?(:contexts)
|
73
|
-
@config[@target].merge! Util.hash_keys(hash, :
|
73
|
+
@config[@target].merge! Util.hash_keys(hash, :sym)
|
74
74
|
save
|
75
75
|
end
|
76
76
|
|
@@ -111,7 +111,7 @@ class Config
|
|
111
111
|
def self.add_opts(hash)
|
112
112
|
raise ArgumentError, "target and context not set" unless @target && @context
|
113
113
|
return unless hash and !hash.empty?
|
114
|
-
@config[@target][:contexts][@context].merge! Util.hash_keys(hash, :
|
114
|
+
@config[@target][:contexts][@context].merge! Util.hash_keys(hash, :sym)
|
115
115
|
save
|
116
116
|
end
|
117
117
|
|
data/lib/cli/group.rb
CHANGED
@@ -41,15 +41,17 @@ class GroupCli < CommonCli
|
|
41
41
|
end
|
42
42
|
|
43
43
|
desc "group delete [name]", "Delete group" do |name|
|
44
|
-
pp scim_request { |ua|
|
44
|
+
pp scim_request { |ua|
|
45
45
|
ua.delete(:delete, ua.id(:group, gname(name)))
|
46
|
-
"success"
|
46
|
+
"success"
|
47
47
|
}
|
48
48
|
end
|
49
49
|
|
50
50
|
def id_set(objs)
|
51
|
-
objs.each_with_object(Set.new) {|o, s|
|
52
|
-
|
51
|
+
objs.each_with_object(Set.new) {|o, s|
|
52
|
+
id = o.is_a?(String)? o: (o["id"] || o["value"] || o["memberid"])
|
53
|
+
raise BadResponse, "no id found in response of current members" unless id
|
54
|
+
s << id
|
53
55
|
}
|
54
56
|
end
|
55
57
|
|
data/lib/cli/runner.rb
CHANGED
@@ -25,16 +25,17 @@ class Cli < BaseCli
|
|
25
25
|
@topics = [MiscCli, InfoCli, TokenCli, UserCli, GroupCli, ClientCli]
|
26
26
|
@global_options = [:help, :version, :debug, :trace, :config]
|
27
27
|
|
28
|
-
def self.configure(config_file = "", input = $stdin, output = $stdout,
|
28
|
+
def self.configure(config_file = "", input = $stdin, output = $stdout,
|
29
29
|
print_on_trace = false)
|
30
30
|
@config_file, @input, @output = config_file, input, output
|
31
31
|
@print_on_trace = print_on_trace
|
32
32
|
self
|
33
33
|
end
|
34
34
|
|
35
|
-
def self.
|
36
|
-
@output.puts "\
|
37
|
-
run
|
35
|
+
def self.handle_bad_command(args, msg)
|
36
|
+
@output.puts "\n#{msg}"
|
37
|
+
run args.unshift("help")
|
38
|
+
nil
|
38
39
|
end
|
39
40
|
|
40
41
|
def self.preprocess_options(args, opts)
|
data/lib/cli/token.rb
CHANGED
@@ -27,7 +27,7 @@ class TokenCatcher < Stub::Base
|
|
27
27
|
Config.target_value(:token_target))
|
28
28
|
tkn = secret ? ti.authcode_grant(server.info.delete(:uri), data) :
|
29
29
|
ti.implicit_grant(server.info.delete(:uri), data)
|
30
|
-
server.info.update(Util.hash_keys!(tkn.info, :
|
30
|
+
server.info.update(Util.hash_keys!(tkn.info, :sym))
|
31
31
|
reply.text "you are now logged in and can close this window"
|
32
32
|
rescue TargetError => e
|
33
33
|
reply.text "#{e.message}:\r\n#{Util.json_pretty(e.info)}\r\n#{e.backtrace}"
|
@@ -71,7 +71,8 @@ class TokenCli < CommonCli
|
|
71
71
|
|
72
72
|
def issuer_request(client_id, secret = nil)
|
73
73
|
update_target_info
|
74
|
-
yield TokenIssuer.new(Config.target.to_s, client_id, secret,
|
74
|
+
yield TokenIssuer.new(Config.target.to_s, client_id, secret,
|
75
|
+
:token_target => Config.target_value(:token_endpoint))
|
75
76
|
rescue Exception => e
|
76
77
|
complain e
|
77
78
|
end
|
@@ -99,7 +100,7 @@ class TokenCli < CommonCli
|
|
99
100
|
ti.implicit_grant_with_creds(creds, opts[:scope]).info
|
100
101
|
}
|
101
102
|
return gripe "attempt to get token failed\n" unless token && token["access_token"]
|
102
|
-
tokinfo = TokenCoder.decode(token["access_token"],
|
103
|
+
tokinfo = TokenCoder.decode(token["access_token"], verify: false)
|
103
104
|
Config.context = tokinfo["user_name"]
|
104
105
|
Config.add_opts(user_id: tokinfo["user_id"])
|
105
106
|
Config.add_opts token
|
@@ -140,8 +141,9 @@ class TokenCli < CommonCli
|
|
140
141
|
|
141
142
|
def use_browser(client_id, secret = nil)
|
142
143
|
catcher = Stub::Server.new(TokenCatcher,
|
143
|
-
Util.default_logger(debug? ? :debug : trace? ? :trace : :info),
|
144
|
-
client_id: client_id, client_secret: secret
|
144
|
+
logger: Util.default_logger(debug? ? :debug : trace? ? :trace : :info),
|
145
|
+
info: {client_id: client_id, client_secret: secret},
|
146
|
+
port: opts[:port]).run_on_thread
|
145
147
|
uri = issuer_request(client_id, secret) { |ti|
|
146
148
|
secret ? ti.authcode_uri("#{catcher.url}/authcode", opts[:scope]) :
|
147
149
|
ti.implicit_uri("#{catcher.url}/callback", opts[:scope])
|
@@ -154,7 +156,7 @@ class TokenCli < CommonCli
|
|
154
156
|
sleep 5
|
155
157
|
print "."
|
156
158
|
end
|
157
|
-
Config.context = TokenCoder.decode(catcher.info[:access_token],
|
159
|
+
Config.context = TokenCoder.decode(catcher.info[:access_token], verify: false)[:user_name]
|
158
160
|
Config.add_opts catcher.info
|
159
161
|
say_success secret ? "authorization code" : "implicit"
|
160
162
|
return unless opts[:vmc]
|
@@ -190,9 +192,9 @@ class TokenCli < CommonCli
|
|
190
192
|
if opts[:client] && opts[:secret]
|
191
193
|
pp Misc.decode_token(Config.target, opts[:client], opts[:secret], token, ttype)
|
192
194
|
else
|
193
|
-
|
194
|
-
|
195
|
-
info = TokenCoder.decode(token, seckey, pubkey, seckey || pubkey)
|
195
|
+
seckey = opts[:key] || (Config.target_value(:signing_key) if Config.target_value(:signing_alg) !~ /rsa$/i)
|
196
|
+
pubkey = opts[:key] || (Config.target_value(:signing_key) if Config.target_value(:signing_alg) =~ /rsa$/i)
|
197
|
+
info = TokenCoder.decode(token, skey: seckey, pkey: pubkey, verify: !!(seckey || pubkey))
|
196
198
|
say seckey || pubkey ? "\nValid token signature\n\n": "\nNote: no key given to validate token signature\n\n"
|
197
199
|
pp info
|
198
200
|
end
|
data/lib/cli/version.rb
CHANGED
data/lib/stub/scim.rb
CHANGED
@@ -57,11 +57,11 @@ class StubScim
|
|
57
57
|
:entitlements, :roles, :x509certificates, :name, :addresses,
|
58
58
|
:authorizations, :groups].to_set,
|
59
59
|
client: [*COMMON_ATTRS, :client_id, :client_secret, :authorities,
|
60
|
-
:authorized_grant_types, :scope, :auto_approved_scope,
|
60
|
+
:authorized_grant_types, :scope, :auto_approved_scope,
|
61
61
|
:access_token_validity, :refresh_token_validity, :redirect_uri].to_set,
|
62
62
|
group: [*COMMON_ATTRS, :displayname, :members, :owners, :readers].to_set }
|
63
63
|
VISIBLE_ATTRS = {user: Set.new(LEGAL_ATTRS[:user] - HIDDEN_ATTRS),
|
64
|
-
client: Set.new(LEGAL_ATTRS[:client] - HIDDEN_ATTRS),
|
64
|
+
client: Set.new(LEGAL_ATTRS[:client] - HIDDEN_ATTRS),
|
65
65
|
group: Set.new(LEGAL_ATTRS[:group] - HIDDEN_ATTRS)}
|
66
66
|
ATTR_NAMES = LEGAL_ATTRS.each_with_object(Set.new) { |(k, v), s|
|
67
67
|
v.each {|a| s << a.to_s }
|
@@ -76,15 +76,15 @@ class StubScim
|
|
76
76
|
attr if ATTR_NAMES.include?(attr) && !HIDDEN_ATTRS.include?(attr = attr.to_sym)
|
77
77
|
end
|
78
78
|
|
79
|
-
def remove_attrs(stuff, attrs = HIDDEN_ATTRS)
|
79
|
+
def remove_attrs(stuff, attrs = HIDDEN_ATTRS)
|
80
80
|
attrs.each { |a| stuff.delete(a.to_s) }
|
81
81
|
stuff
|
82
82
|
end
|
83
83
|
|
84
|
-
def valid_id?(id, rtype)
|
85
|
-
id && (t = @things_by_id[id]) && (rtype.nil? || t[:rtype] == rtype)
|
84
|
+
def valid_id?(id, rtype)
|
85
|
+
id && (t = @things_by_id[id]) && (rtype.nil? || t[:rtype] == rtype)
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
def ref_by_name(name, rtype) @things_by_name[rtype.to_s + name.downcase] end
|
89
89
|
|
90
90
|
def ref_by_id(id, rtype = nil)
|
@@ -134,10 +134,10 @@ class StubScim
|
|
134
134
|
end
|
135
135
|
|
136
136
|
def input(stuff)
|
137
|
-
thing = Util.hash_keys(stuff.dup, :
|
137
|
+
thing = Util.hash_keys(stuff.dup, :sym)
|
138
138
|
REFERENCES.each {|a|
|
139
139
|
next unless thing[a]
|
140
|
-
thing[a] = thing[a].each_with_object(Set.new) { |r, s|
|
140
|
+
thing[a] = thing[a].each_with_object(Set.new) { |r, s|
|
141
141
|
s << (r.is_a?(Hash)? r[:value] : r )
|
142
142
|
}
|
143
143
|
}
|
@@ -146,7 +146,7 @@ class StubScim
|
|
146
146
|
thing[a] = thing[a].each_with_object({}) { |v, o|
|
147
147
|
v = {value: v} unless v.is_a?(Hash)
|
148
148
|
# enforce values are unique by type and value
|
149
|
-
k =
|
149
|
+
k = Util.encode_form(t: [v[:type], v: v[:value]]).downcase
|
150
150
|
o[k] = v
|
151
151
|
}
|
152
152
|
}
|
data/lib/stub/server.rb
CHANGED
@@ -89,8 +89,7 @@ class Reply
|
|
89
89
|
def initialize(status = 200) @status, @headers, @cookies, @body = status, {}, [], "" end
|
90
90
|
def to_s
|
91
91
|
reply = "HTTP/1.1 #{@status} OK\r\n"
|
92
|
-
headers["server"] = "stub server"
|
93
|
-
headers["date"] = DateTime.now.httpdate
|
92
|
+
headers["server"], headers["date"] = "stub server", DateTime.now.httpdate
|
94
93
|
headers["content-length"] = body.bytesize
|
95
94
|
headers.each { |k, v| reply << "#{k}: #{v}\r\n" }
|
96
95
|
@cookies.each { |c| reply << "Set-Cookie: #{c}\r\n" }
|
@@ -168,8 +167,14 @@ class Base
|
|
168
167
|
@server, @request, @reply, @match = server, Request.new, Reply.new, nil
|
169
168
|
end
|
170
169
|
|
170
|
+
def default_route; reply_in_kind(404, error: "path not handled") end
|
171
|
+
|
171
172
|
def process
|
172
173
|
@reply = Reply.new
|
174
|
+
if server.root
|
175
|
+
return default_route unless request.path.start_with?(server.root)
|
176
|
+
request.path.slice!(0..server.root.length - 1)
|
177
|
+
end
|
173
178
|
@match, handler = self.class.find_route(request)
|
174
179
|
server.logger.debug "processing request to path #{request.path} for route #{@match ? @match.regexp : 'default'}"
|
175
180
|
send handler
|
@@ -177,7 +182,7 @@ class Base
|
|
177
182
|
server.logger.debug "replying to path #{request.path} with #{reply.body.length} bytes of #{reply.headers['content-type']}"
|
178
183
|
#server.logger.debug "full reply is: #{reply.body.inspect}"
|
179
184
|
rescue Exception => e
|
180
|
-
server.logger.debug "exception
|
185
|
+
server.logger.debug "exception processing request: #{e.message}"
|
181
186
|
server.trace { e.backtrace }
|
182
187
|
reply_in_kind 500, e
|
183
188
|
end
|
@@ -190,10 +195,6 @@ class Base
|
|
190
195
|
end
|
191
196
|
end
|
192
197
|
|
193
|
-
def default_route
|
194
|
-
reply_in_kind(404, error: "path not handled")
|
195
|
-
end
|
196
|
-
|
197
198
|
end
|
198
199
|
|
199
200
|
#------------------------------------------------------------------------------
|
@@ -216,38 +217,62 @@ module Connection
|
|
216
217
|
end
|
217
218
|
end
|
218
219
|
|
219
|
-
|
220
|
+
#------------------------------------------------------------------------------
|
220
221
|
class Server
|
221
|
-
|
222
|
+
|
223
|
+
private
|
224
|
+
|
225
|
+
def done
|
226
|
+
fail unless @connections.empty?
|
227
|
+
EM.stop if @em_thread && EM.reactor_running?
|
228
|
+
@connections, @status, @sig, @em_thread = [], :stopped, nil, nil
|
229
|
+
sleep 0.1 unless EM.reactor_thread? # give EM a chance to stop
|
230
|
+
logger.debug EM.reactor_running?? "server done but EM still running": "server really done"
|
231
|
+
end
|
232
|
+
|
233
|
+
def initialize_connection(conn)
|
234
|
+
logger.debug "starting connection"
|
235
|
+
fail unless EM.reactor_thread?
|
236
|
+
@connections << conn
|
237
|
+
conn.req_handler, conn.comm_inactivity_timeout = @req_handler.new(self), 30
|
238
|
+
end
|
239
|
+
|
240
|
+
public
|
241
|
+
|
242
|
+
attr_reader :host, :port, :status, :logger, :root
|
222
243
|
attr_accessor :info
|
223
244
|
def url; "http://#{@host}:#{@port}" end
|
224
245
|
def trace(msg = nil, &blk); logger.trace(msg, &blk) if logger.respond_to?(:trace) end
|
225
246
|
|
226
|
-
def initialize(req_handler,
|
227
|
-
@req_handler
|
247
|
+
def initialize(req_handler, options)
|
248
|
+
@req_handler = req_handler
|
249
|
+
@logger = options[:logger] || Logger.new($stdout)
|
250
|
+
@info = options[:info]
|
251
|
+
@host = options[:host] || "localhost"
|
252
|
+
@init_port = options[:port] || 0
|
253
|
+
@root = options[:root]
|
228
254
|
@connections, @status, @sig, @em_thread = [], :stopped, nil, nil
|
229
255
|
end
|
230
256
|
|
231
|
-
def start
|
257
|
+
def start
|
232
258
|
raise ArgumentError, "attempt to start a server that's already running" unless @status == :stopped
|
233
|
-
@host = hostname
|
234
259
|
logger.debug "starting #{self.class} server #{@host}"
|
235
260
|
EM.schedule do
|
236
|
-
@sig = EM.start_server(@host,
|
261
|
+
@sig = EM.start_server(@host, @init_port, Connection) { |c| initialize_connection(c) }
|
237
262
|
@port = Socket.unpack_sockaddr_in(EM.get_sockname(@sig))[0]
|
238
|
-
logger.
|
263
|
+
logger.info "#{self.class} server started at #{url}"
|
239
264
|
end
|
240
265
|
@status = :running
|
241
266
|
self
|
242
267
|
end
|
243
268
|
|
244
|
-
def run_on_thread
|
269
|
+
def run_on_thread
|
245
270
|
raise ArgumentError, "can't run on thread, EventMachine already running" if EM.reactor_running?
|
246
271
|
logger.debug { "starting eventmachine on thread" }
|
247
272
|
cthred = Thread.current
|
248
273
|
@em_thread = Thread.new do
|
249
274
|
begin
|
250
|
-
EM.run { start
|
275
|
+
EM.run { start; cthred.run }
|
251
276
|
logger.debug "server thread done"
|
252
277
|
rescue Exception => e
|
253
278
|
logger.debug { "unhandled exception on stub server thread: #{e.message}" }
|
@@ -260,10 +285,10 @@ class Server
|
|
260
285
|
self
|
261
286
|
end
|
262
287
|
|
263
|
-
def run
|
288
|
+
def run
|
264
289
|
raise ArgumentError, "can't run, EventMachine already running" if EM.reactor_running?
|
265
290
|
@em_thread = Thread.current
|
266
|
-
EM.run { start
|
291
|
+
EM.run { start }
|
267
292
|
logger.debug "server and event machine done"
|
268
293
|
end
|
269
294
|
|
@@ -286,25 +311,6 @@ class Server
|
|
286
311
|
done if @status != :running && @connections.empty?
|
287
312
|
end
|
288
313
|
|
289
|
-
private
|
290
|
-
|
291
|
-
def done
|
292
|
-
fail unless @connections.empty?
|
293
|
-
EM.stop if @em_thread && EM.reactor_running?
|
294
|
-
@connections, @status, @sig, @em_thread = [], :stopped, nil, nil
|
295
|
-
sleep 0.1 unless EM.reactor_thread? # give EM a chance to stop
|
296
|
-
logger.debug EM.reactor_running? ?
|
297
|
-
"server done but EM still running" : "server really done"
|
298
|
-
end
|
299
|
-
|
300
|
-
def initialize_connection(conn)
|
301
|
-
logger.debug "starting connection"
|
302
|
-
fail unless EM.reactor_thread?
|
303
|
-
@connections << conn
|
304
|
-
conn.req_handler = @req_handler.new(self)
|
305
|
-
conn.comm_inactivity_timeout = 30
|
306
|
-
end
|
307
|
-
|
308
314
|
end
|
309
315
|
|
310
316
|
end
|