htty-rack 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/MIT-LICENSE.rdoc +9 -0
- data/README.rdoc +40 -0
- data/Rakefile +11 -0
- data/VERSION +1 -0
- data/bin/htty-rack +7 -0
- data/config.ru +1 -0
- data/htty-rack.gemspec +18 -0
- data/lib/htty/rack.rb +31 -0
- data/lib/htty/rack/cli.rb +27 -0
- data/lib/htty/rack/commands/app.rb +40 -0
- data/lib/htty/rack/commands/config.rb +40 -0
- data/lib/htty/rack/commands/irb.rb +36 -0
- data/lib/htty/rack/commands/require.rb +35 -0
- data/lib/htty/rack/request.rb +462 -0
- data/lib/htty/rack/requests_util.rb +53 -0
- data/lib/htty/rack/session.rb +11 -0
- data/test/rack-htty_test.rb +7 -0
- data/test/test_helper.rb +1 -0
- metadata +97 -0
data/.gitignore
ADDED
data/MIT-LICENSE.rdoc
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Source code for _htty-rack_ is copyright (c) 2010 Florian Gilcher[mailto:florian.gilcher@asquera.de].
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
6
|
+
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
8
|
+
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
_________________________
|
2
|
+
< no fancy ASCII art here >
|
3
|
+
-------------------------
|
4
|
+
\ ^__^
|
5
|
+
\ (oo)\_______
|
6
|
+
(__)\ )\/\
|
7
|
+
||----w |
|
8
|
+
|| ||
|
9
|
+
|
10
|
+
htty-rack[http://www.github.com/Asquera/htty-rack] is an extension to htty[http://htty.github.com] that switches the HTTP backend in favour of a Rack application. For that purpose, it also provides a set of commands for controlling which rack application is used etc.
|
11
|
+
|
12
|
+
= Warning
|
13
|
+
|
14
|
+
Alpha. No Features guaranteed.
|
15
|
+
|
16
|
+
= Installation
|
17
|
+
|
18
|
+
Use rubygems:
|
19
|
+
|
20
|
+
$ gem install htty-rack
|
21
|
+
|
22
|
+
= Usage
|
23
|
+
|
24
|
+
htty-rack uses a different executable than htty. Start htty-rack using:
|
25
|
+
|
26
|
+
$ htty-rack <config-file>
|
27
|
+
|
28
|
+
If no config (*.ru) file is given, htty-rack will recursively descend to find a file called "config.ru".
|
29
|
+
|
30
|
+
= Examples
|
31
|
+
|
32
|
+
Beyond the examples described on the htty[http://htty.github.com] page, htty-rack supports a few other commands:
|
33
|
+
|
34
|
+
* app <app> will change the rack appliation in use. The argument must be a ruby constant.
|
35
|
+
* config <file> will load a new rackup file and use the application therein.
|
36
|
+
* irb starts irb so that you can manipulate the environment.
|
37
|
+
|
38
|
+
= Thanks
|
39
|
+
|
40
|
+
* Nils Jonsson for writing htty and making this project possible
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.5
|
data/bin/htty-rack
ADDED
data/config.ru
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
run lambda {|env| [200,{"Content-Length" => "4"}, ["test"]]}
|
data/htty-rack.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "htty-rack"
|
5
|
+
s.version = File.read("VERSION")
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.authors = ["Florian Gilcher"]
|
8
|
+
s.email = ["florian.gilcher@asquera.de"]
|
9
|
+
s.homepage = "http://www.github.com/Asquera/htty-rack"
|
10
|
+
s.summary = "htty-rack is htty for rack applications"
|
11
|
+
s.description = "A CLI for rack applications based on htty."
|
12
|
+
|
13
|
+
s.add_dependency "htty", ">= 1.0.0"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
17
|
+
s.require_path = 'lib'
|
18
|
+
end
|
data/lib/htty/rack.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
Dir.glob "#{File.dirname __FILE__}/rack/*.rb" do |f|
|
2
|
+
require File.expand_path("#{File.dirname __FILE__}/rack/" +
|
3
|
+
File.basename(f, '.rb'))
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'rack'
|
7
|
+
HTTY::CLI::Commands::Help.register_additional_category("Rack")
|
8
|
+
HTTY::CLI::Commands::Help.register_additional_category("Debug")
|
9
|
+
|
10
|
+
module HTTY
|
11
|
+
module Rack
|
12
|
+
VERSION = File.read("#{File.dirname __FILE__}/../../VERSION").chomp
|
13
|
+
|
14
|
+
def self.build_app(filename = nil)
|
15
|
+
config_file_name = File.basename(filename || find_config_file)
|
16
|
+
config_file = File.read(config_file_name)
|
17
|
+
app = ::Rack::Builder.new { instance_eval(config_file) }.to_app
|
18
|
+
[config_file_name, app]
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.find_config_file
|
22
|
+
if Dir.glob("config.ru").length > 0
|
23
|
+
File.join(Dir.pwd,"config.ru")
|
24
|
+
elsif Dir.pwd != "/"
|
25
|
+
Dir.chdir("..") { find_config_file }
|
26
|
+
else
|
27
|
+
raise "Cannot find config.ru"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module HTTY::Rack
|
2
|
+
class CLI < HTTY::CLI
|
3
|
+
|
4
|
+
attr_accessor :config_file_name
|
5
|
+
|
6
|
+
def initialize(command_line_arguments, session_class = HTTY::Rack::Session)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def prompt
|
11
|
+
last_request = session.requests.last
|
12
|
+
if last_request.app.respond_to? :name
|
13
|
+
display = last_request.app.name
|
14
|
+
else
|
15
|
+
display = last_request.app_file
|
16
|
+
end
|
17
|
+
|
18
|
+
strong(display + ":" +
|
19
|
+
session.requests.last.send(:path_query_and_fragment)) +
|
20
|
+
normal('> ')
|
21
|
+
end
|
22
|
+
|
23
|
+
def command_folders
|
24
|
+
super + ["#{File.dirname __FILE__}/commands"]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class HTTY::CLI::Commands::App < HTTY::CLI::Command
|
2
|
+
|
3
|
+
# Returns the name of a category under which help for the _app_ command
|
4
|
+
# should appear.
|
5
|
+
def self.category
|
6
|
+
'Rack'
|
7
|
+
end
|
8
|
+
|
9
|
+
# Returns the arguments for the command-line usage of the _app_ command.
|
10
|
+
def self.command_line_arguments
|
11
|
+
'constant'
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the help text for the _app_ command.
|
15
|
+
def self.help
|
16
|
+
'Sets the active app'
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the extended help text for the _app_ command.
|
20
|
+
def self.help_extended
|
21
|
+
'Sets the app to the constant provided. The app must be required first.' +
|
22
|
+
'This operation does not clear cookies.'
|
23
|
+
end
|
24
|
+
|
25
|
+
# Performs the _app_ command.
|
26
|
+
def perform
|
27
|
+
unless arguments.length == 1
|
28
|
+
raise ArgumentError,
|
29
|
+
"wrong number of arguments (#{arguments.length} for 1)"
|
30
|
+
end
|
31
|
+
|
32
|
+
app = Module.const_get(arguments.first)
|
33
|
+
|
34
|
+
add_request_if_has_response do |request|
|
35
|
+
request.app = app
|
36
|
+
request
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class HTTY::CLI::Commands::Config < HTTY::CLI::Command
|
2
|
+
|
3
|
+
include HTTY::CLI::CookieClearingCommand
|
4
|
+
|
5
|
+
# Returns the name of a category under which help for the _config_ command
|
6
|
+
# should appear.
|
7
|
+
def self.category
|
8
|
+
'Rack'
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns the arguments for the command-line usage of the _config_ command.
|
12
|
+
def self.command_line_arguments
|
13
|
+
'config'
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the help text for the _config_ command.
|
17
|
+
def self.help
|
18
|
+
'Loads a new config'
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the extended help text for the _config_ command.
|
22
|
+
def self.help_extended
|
23
|
+
'Loads a config (.ru-File) and sets the built class as the' +
|
24
|
+
'active application. This operation clears cookies.'
|
25
|
+
end
|
26
|
+
|
27
|
+
# Performs the _config_ command.
|
28
|
+
def perform
|
29
|
+
unless arguments.length == 1
|
30
|
+
raise ArgumentError,
|
31
|
+
"wrong number of arguments (#{arguments.length} for 1)"
|
32
|
+
end
|
33
|
+
|
34
|
+
add_request_if_has_response do |request|
|
35
|
+
request.app_file, request.app = HTTY::Rack::build_app arguments.first
|
36
|
+
request
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class HTTY::CLI::Commands::Irb < HTTY::CLI::Command
|
2
|
+
|
3
|
+
# Returns the name of a category under which help for the _irb_ command
|
4
|
+
# should appear.
|
5
|
+
def self.category
|
6
|
+
'Debug'
|
7
|
+
end
|
8
|
+
|
9
|
+
# Returns the arguments for the command-line usage of the _irb_ command.
|
10
|
+
def self.command_line_arguments
|
11
|
+
''
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the help text for the _irb_ command.
|
15
|
+
def self.help
|
16
|
+
'Starts irb'
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the extended help text for the _irb_ command.
|
20
|
+
def self.help_extended
|
21
|
+
'Starts irb to let you modify the environment. Use it like IRB, using' +
|
22
|
+
'#exit brings you right back.'
|
23
|
+
end
|
24
|
+
|
25
|
+
# Performs the _irb_ command.
|
26
|
+
def perform
|
27
|
+
unless arguments.length == 0
|
28
|
+
raise ArgumentError,
|
29
|
+
"wrong number of arguments (#{arguments.length} for 0)"
|
30
|
+
end
|
31
|
+
require 'irb'
|
32
|
+
|
33
|
+
IRB::start(__FILE__)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class HTTY::CLI::Commands::Require < HTTY::CLI::Command
|
2
|
+
|
3
|
+
# Returns the name of a category under which help for the _require_ command
|
4
|
+
# should appear.
|
5
|
+
def self.category
|
6
|
+
'Rack'
|
7
|
+
end
|
8
|
+
|
9
|
+
# Returns the arguments for the command-line usage of the _require_ command.
|
10
|
+
def self.command_line_arguments
|
11
|
+
'file'
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the help text for the _require_ command.
|
15
|
+
def self.help
|
16
|
+
'requires a file'
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the extended help text for the _require_ command.
|
20
|
+
def self.help_extended
|
21
|
+
'allows you to require a file. Works like rubys require and allows you' +
|
22
|
+
'to load apps.'
|
23
|
+
end
|
24
|
+
|
25
|
+
# Performs the _require_ command.
|
26
|
+
def perform
|
27
|
+
unless arguments.length == 1
|
28
|
+
raise ArgumentError,
|
29
|
+
"wrong number of arguments (#{arguments.length} for 1)"
|
30
|
+
end
|
31
|
+
|
32
|
+
require arguments.first
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,462 @@
|
|
1
|
+
|
2
|
+
# Defines HTTY::Request.
|
3
|
+
|
4
|
+
require 'pathname'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
module HTTY; end
|
8
|
+
|
9
|
+
# Encapsulates an HTTP(S) request.
|
10
|
+
class HTTY::Rack::Request < HTTY::Payload
|
11
|
+
attr_accessor :app, :app_file
|
12
|
+
|
13
|
+
COOKIES_HEADER_NAME = 'Cookie'
|
14
|
+
|
15
|
+
METHODS_SENDING_BODY = [:post, :put]
|
16
|
+
|
17
|
+
# Returns a URI authority (a combination of userinfo, host, and port)
|
18
|
+
# corresponding to the specified _components_ hash. Valid _components_ keys
|
19
|
+
# include:
|
20
|
+
#
|
21
|
+
# * <tt>:userinfo</tt>
|
22
|
+
# * <tt>:host</tt>
|
23
|
+
# * <tt>:port</tt>
|
24
|
+
def self.build_authority(components)
|
25
|
+
userinfo_and_host = [components[:userinfo],
|
26
|
+
components[:host]].compact.join('@')
|
27
|
+
all = [userinfo_and_host, components[:port]].compact.join(':')
|
28
|
+
return nil if (all == '')
|
29
|
+
all
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns a combination of a URI path, query, and fragment, corresponding to
|
33
|
+
# the specified _components_ hash. Valid _components_ keys include:
|
34
|
+
#
|
35
|
+
# * <tt>:path</tt>
|
36
|
+
# * <tt>:query</tt>
|
37
|
+
# * <tt>:fragment</tt>
|
38
|
+
def self.build_path_query_and_fragment(components)
|
39
|
+
path = components[:path]
|
40
|
+
query = components[:query] ? "?#{components[:query]}" : nil
|
41
|
+
fragment = components[:fragment] ? "##{components[:fragment]}" : nil
|
42
|
+
all = [path, query, fragment].compact.join
|
43
|
+
return nil if (all == '')
|
44
|
+
all
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns a URI corresponding to the specified _components_ hash, or raises
|
48
|
+
# URI::InvalidURIError. Valid _components_ keys include:
|
49
|
+
#
|
50
|
+
# * <tt>:scheme</tt>
|
51
|
+
# * <tt>:userinfo</tt>
|
52
|
+
# * <tt>:host</tt>
|
53
|
+
# * <tt>:port</tt>
|
54
|
+
# * <tt>:path</tt>
|
55
|
+
# * <tt>:query</tt>
|
56
|
+
# * <tt>:fragment</tt>
|
57
|
+
def self.build_uri(components)
|
58
|
+
scheme = (components[:scheme] || 'http') + '://'
|
59
|
+
authority = build_authority(components)
|
60
|
+
path_query_and_fragment = build_path_query_and_fragment(components)
|
61
|
+
path_query_and_fragment ||= '/' if authority
|
62
|
+
URI.parse([path_query_and_fragment].join)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns a URI corresponding to the specified _address_, or raises
|
66
|
+
# URI::InvalidURIError.
|
67
|
+
def self.parse_uri(address)
|
68
|
+
address = '0.0.0.0' if address.nil? || (address == '')
|
69
|
+
|
70
|
+
scheme_missing = false
|
71
|
+
if (address !~ /^[a-z]+:\/\//) && (address !~ /^mailto:/)
|
72
|
+
scheme_missing = true
|
73
|
+
address = 'http://' + address
|
74
|
+
end
|
75
|
+
|
76
|
+
scheme,
|
77
|
+
userinfo,
|
78
|
+
host,
|
79
|
+
port,
|
80
|
+
registry, # Not used by HTTP
|
81
|
+
path,
|
82
|
+
opaque, # Not used by HTTP
|
83
|
+
query,
|
84
|
+
fragment = URI.split(address)
|
85
|
+
|
86
|
+
scheme = nil if scheme_missing
|
87
|
+
path = nil if (path == '')
|
88
|
+
|
89
|
+
unless scheme
|
90
|
+
scheme = (port.to_i == URI::HTTPS::DEFAULT_PORT) ? 'https' : 'http'
|
91
|
+
end
|
92
|
+
|
93
|
+
build_uri :scheme => scheme,
|
94
|
+
:userinfo => userinfo,
|
95
|
+
:host => host,
|
96
|
+
:port => port,
|
97
|
+
:path => path,
|
98
|
+
:query => query,
|
99
|
+
:fragment => fragment
|
100
|
+
end
|
101
|
+
|
102
|
+
protected
|
103
|
+
|
104
|
+
def self.clear_cookies_if_host_changes(request)
|
105
|
+
previous_host = request.uri.host
|
106
|
+
yield
|
107
|
+
request.cookies_remove_all unless request.uri.host == previous_host
|
108
|
+
request
|
109
|
+
end
|
110
|
+
|
111
|
+
public
|
112
|
+
|
113
|
+
# Returns the HTTP method of the request, if any.
|
114
|
+
attr_reader :request_method
|
115
|
+
|
116
|
+
# Returns the response received for the request, if any.
|
117
|
+
attr_reader :response
|
118
|
+
|
119
|
+
# Returns the URI of the request.
|
120
|
+
attr_reader :uri
|
121
|
+
|
122
|
+
# Initializes a new HTTY::Request with a #uri corresponding to the specified
|
123
|
+
# _address_.
|
124
|
+
def initialize(address = "/")
|
125
|
+
super({:headers => [['User-Agent', "htty/#{HTTY::VERSION}"]]})
|
126
|
+
@uri = self.class.parse_uri(address)
|
127
|
+
establish_content_length
|
128
|
+
end
|
129
|
+
|
130
|
+
def initialize_copy(source) #:nodoc:
|
131
|
+
super
|
132
|
+
@response = @response.dup if @response
|
133
|
+
@uri = @uri.dup
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns +true+ if _other_request_ is equivalent to the request.
|
137
|
+
def ==(other_request)
|
138
|
+
return false unless super(other_request)
|
139
|
+
return false unless other_request.kind_of?(self.class)
|
140
|
+
(other_request.response == response) && (other_request.uri == uri)
|
141
|
+
end
|
142
|
+
alias :eql? :==
|
143
|
+
|
144
|
+
# Establishes a new #uri corresponding to the specified _address_. If the host
|
145
|
+
# of the _address_ is different from the host of #uri, then #cookies are
|
146
|
+
# cleared.
|
147
|
+
def address(address)
|
148
|
+
uri = self.class.parse_uri(address)
|
149
|
+
if response
|
150
|
+
dup = dup_without_response
|
151
|
+
return self.class.clear_cookies_if_host_changes(dup) do
|
152
|
+
dup.uri = uri
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
self.class.clear_cookies_if_host_changes self do
|
157
|
+
@uri = uri
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Sets the body of the request.
|
162
|
+
def body_set(body)
|
163
|
+
return dup_without_response.body_set(body) if response
|
164
|
+
|
165
|
+
@body = body ? body.to_s : nil
|
166
|
+
establish_content_length
|
167
|
+
end
|
168
|
+
|
169
|
+
# Clears the body of the request.
|
170
|
+
def body_unset
|
171
|
+
body_set nil
|
172
|
+
end
|
173
|
+
|
174
|
+
# Makes an HTTP +CONNECT+ request using the path of #uri.
|
175
|
+
def connect!
|
176
|
+
request! :connect
|
177
|
+
end
|
178
|
+
|
179
|
+
# Appends to #cookies using the specified _name_ (required) and _value_
|
180
|
+
# (optional).
|
181
|
+
def cookie_add(name, value=nil)
|
182
|
+
return dup_without_response.cookie_add(name, value) if response
|
183
|
+
|
184
|
+
cookies_string = HTTY::CookiesUtil.cookies_to_string(cookies +
|
185
|
+
[[name.to_s, value]])
|
186
|
+
if cookies_string
|
187
|
+
@headers[COOKIES_HEADER_NAME] = cookies_string
|
188
|
+
else
|
189
|
+
@headers.delete COOKIES_HEADER_NAME
|
190
|
+
end
|
191
|
+
self
|
192
|
+
end
|
193
|
+
|
194
|
+
# Removes the last element of #cookies having the specified _name_.
|
195
|
+
def cookie_remove(name)
|
196
|
+
return dup_without_response.cookie_remove(name) if response
|
197
|
+
|
198
|
+
# Remove just one matching cookie from the end.
|
199
|
+
rejected = false
|
200
|
+
new_cookies = cookies.reverse.reject do |cookie_name, cookie_value|
|
201
|
+
if !rejected && (cookie_name == name)
|
202
|
+
rejected = true
|
203
|
+
else
|
204
|
+
false
|
205
|
+
end
|
206
|
+
end.reverse
|
207
|
+
|
208
|
+
cookies_string = HTTY::CookiesUtil.cookies_to_string(new_cookies)
|
209
|
+
if cookies_string
|
210
|
+
@headers[COOKIES_HEADER_NAME] = cookies_string
|
211
|
+
else
|
212
|
+
@headers.delete COOKIES_HEADER_NAME
|
213
|
+
end
|
214
|
+
self
|
215
|
+
end
|
216
|
+
|
217
|
+
# Returns an array of the cookies belonging to the request.
|
218
|
+
def cookies
|
219
|
+
HTTY::CookiesUtil.cookies_from_string @headers[COOKIES_HEADER_NAME]
|
220
|
+
end
|
221
|
+
|
222
|
+
# Removes all #cookies.
|
223
|
+
def cookies_remove_all
|
224
|
+
return dup_without_response.cookies_remove_all if response
|
225
|
+
|
226
|
+
@headers.delete COOKIES_HEADER_NAME
|
227
|
+
self
|
228
|
+
end
|
229
|
+
|
230
|
+
# Sets #cookies according to the _Set-Cookie_ header of the specified
|
231
|
+
# _response_, or raises either HTTY::NoResponseError or
|
232
|
+
# HTTY::NoSetCookieHeaderError.
|
233
|
+
def cookies_use(response)
|
234
|
+
raise HTTY::NoResponseError unless response
|
235
|
+
|
236
|
+
cookies_header = response.headers.detect do |name, value|
|
237
|
+
name == HTTY::Response::COOKIES_HEADER_NAME
|
238
|
+
end
|
239
|
+
unless cookies_header && cookies_header.last
|
240
|
+
raise HTTY::NoSetCookieHeaderError
|
241
|
+
end
|
242
|
+
header_set COOKIES_HEADER_NAME, cookies_header.last
|
243
|
+
end
|
244
|
+
|
245
|
+
# Makes an HTTP +DELETE+ request using the path of #uri.
|
246
|
+
def delete!
|
247
|
+
request! :delete
|
248
|
+
end
|
249
|
+
|
250
|
+
# Establishes a new #uri according to the _Location_ header of the specified
|
251
|
+
# _response_, or raises either HTTY::NoResponseError or
|
252
|
+
# HTTY::NoLocationHeaderError.
|
253
|
+
def follow(response)
|
254
|
+
raise HTTY::NoResponseError unless response
|
255
|
+
|
256
|
+
location_header = response.headers.detect do |name, value|
|
257
|
+
name == 'Location'
|
258
|
+
end
|
259
|
+
unless location_header && location_header.last
|
260
|
+
raise HTTY::NoLocationHeaderError
|
261
|
+
end
|
262
|
+
address location_header.last
|
263
|
+
end
|
264
|
+
|
265
|
+
# Establishes a new #uri with the specified _fragment_.
|
266
|
+
def fragment_set(fragment)
|
267
|
+
rebuild_uri :fragment => fragment
|
268
|
+
end
|
269
|
+
|
270
|
+
# Establishes a new #uri without a fragment.
|
271
|
+
def fragment_unset
|
272
|
+
fragment_set nil
|
273
|
+
end
|
274
|
+
|
275
|
+
# Makes an HTTP +GET+ request using the path of #uri.
|
276
|
+
def get!
|
277
|
+
request! :get
|
278
|
+
end
|
279
|
+
|
280
|
+
# Makes an HTTP +HEAD+ request using the path of #uri.
|
281
|
+
def head!
|
282
|
+
request! :head
|
283
|
+
end
|
284
|
+
|
285
|
+
# Appends to #headers or changes the element of #headers using the specified
|
286
|
+
# _name_ and _value_.
|
287
|
+
def header_set(name, value)
|
288
|
+
return dup_without_response.header_set(name, value) if response
|
289
|
+
|
290
|
+
name = name.to_s
|
291
|
+
if value.nil?
|
292
|
+
@headers.delete name
|
293
|
+
return self
|
294
|
+
end
|
295
|
+
|
296
|
+
@headers[name] = value.to_s
|
297
|
+
self
|
298
|
+
end
|
299
|
+
|
300
|
+
# Removes the element of #headers having the specified _name_.
|
301
|
+
def header_unset(name)
|
302
|
+
header_set name, nil
|
303
|
+
end
|
304
|
+
|
305
|
+
# Returns an array of the headers belonging to the payload. If
|
306
|
+
# _include_content_length_ is +false+, then a 'Content Length' header will be
|
307
|
+
# omitted. If _include_content_length_ is not specified, then it will be
|
308
|
+
# +true+ if #request_method is an HTTP method for which body content is
|
309
|
+
# expected.
|
310
|
+
def headers(include_content_length=
|
311
|
+
METHODS_SENDING_BODY.include?(request_method))
|
312
|
+
unless include_content_length
|
313
|
+
return super().reject do |name, value|
|
314
|
+
name == 'Content-Length'
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
super()
|
319
|
+
end
|
320
|
+
|
321
|
+
# Removes all #headers.
|
322
|
+
def headers_unset_all
|
323
|
+
return dup_without_response.headers_unset_all if response
|
324
|
+
|
325
|
+
@headers.clear
|
326
|
+
self
|
327
|
+
end
|
328
|
+
|
329
|
+
# Establishes a new #uri with the specified _host_.
|
330
|
+
def host_set(host)
|
331
|
+
rebuild_uri :host => host
|
332
|
+
end
|
333
|
+
|
334
|
+
# Makes an HTTP +OPTIONS+ request using the path of #uri.
|
335
|
+
def options!
|
336
|
+
request! :options
|
337
|
+
end
|
338
|
+
|
339
|
+
# Makes an HTTP +PATCH+ request using the path of #uri.
|
340
|
+
def patch!
|
341
|
+
request! :patch
|
342
|
+
end
|
343
|
+
|
344
|
+
# Establishes a new #uri with the specified _path_ which may be absolute or
|
345
|
+
# relative.
|
346
|
+
def path_set(path)
|
347
|
+
absolute_path = (Pathname.new(uri.path) + path).to_s
|
348
|
+
rebuild_uri :path => absolute_path
|
349
|
+
end
|
350
|
+
|
351
|
+
# Establishes a new #uri with the specified _port_.
|
352
|
+
def port_set(port)
|
353
|
+
rebuild_uri :port => port
|
354
|
+
end
|
355
|
+
|
356
|
+
# Makes an HTTP +POST+ request using the path of #uri.
|
357
|
+
def post!
|
358
|
+
request! :post
|
359
|
+
end
|
360
|
+
|
361
|
+
# Makes an HTTP +PUT+ request using the path of #uri.
|
362
|
+
def put!
|
363
|
+
request! :put
|
364
|
+
end
|
365
|
+
|
366
|
+
# Establishes a new #uri, with the specified _value_ for the query-string
|
367
|
+
# parameter specified by _name_.
|
368
|
+
def query_set(name, value)
|
369
|
+
query = uri.query ? "&#{uri.query}&" : ''
|
370
|
+
parameter = Regexp.new("&#{Regexp.escape name}=.+?&")
|
371
|
+
if query =~ parameter
|
372
|
+
new_query = value.nil? ?
|
373
|
+
query.gsub(parameter, '&') :
|
374
|
+
query.gsub(parameter, "&#{name}=#{value}&")
|
375
|
+
else
|
376
|
+
new_query = value.nil? ? query : "#{query}#{name}=#{value}"
|
377
|
+
end
|
378
|
+
new_query = new_query.gsub(/^&/, '').gsub(/&$/, '')
|
379
|
+
new_query = nil if (new_query == '')
|
380
|
+
rebuild_uri :query => new_query
|
381
|
+
end
|
382
|
+
|
383
|
+
# Establishes a new #uri, without the query-string parameter specified by
|
384
|
+
# _name_.
|
385
|
+
def query_unset(name)
|
386
|
+
query_set name, nil
|
387
|
+
end
|
388
|
+
|
389
|
+
# Establishes a new #uri without a query string.
|
390
|
+
def query_unset_all
|
391
|
+
rebuild_uri :query => nil
|
392
|
+
end
|
393
|
+
|
394
|
+
# Establishes a new #uri with the specified _scheme_.
|
395
|
+
def scheme_set(scheme)
|
396
|
+
rebuild_uri :scheme => scheme
|
397
|
+
end
|
398
|
+
|
399
|
+
# Makes an HTTP +TRACE+ request using the path of #uri.
|
400
|
+
def trace!
|
401
|
+
request! :trace
|
402
|
+
end
|
403
|
+
|
404
|
+
# Establishes a new #uri with the specified _userinfo_.
|
405
|
+
def userinfo_set(userinfo)
|
406
|
+
rebuild_uri :userinfo => userinfo
|
407
|
+
end
|
408
|
+
|
409
|
+
# Establishes a new #uri without userinfo.
|
410
|
+
def userinfo_unset
|
411
|
+
userinfo_set nil
|
412
|
+
end
|
413
|
+
|
414
|
+
protected
|
415
|
+
|
416
|
+
def dup_without_response
|
417
|
+
request = self.dup
|
418
|
+
request.response = nil
|
419
|
+
request.instance_variable_set '@request_method', nil
|
420
|
+
request
|
421
|
+
end
|
422
|
+
|
423
|
+
def establish_content_length
|
424
|
+
header_set 'Content-Length', body.to_s.length
|
425
|
+
end
|
426
|
+
|
427
|
+
def path_query_and_fragment
|
428
|
+
self.class.build_path_query_and_fragment :path => uri.path,
|
429
|
+
:query => uri.query,
|
430
|
+
:fragment => uri.fragment
|
431
|
+
end
|
432
|
+
|
433
|
+
def rebuild_uri(changed_components)
|
434
|
+
return dup_without_response.rebuild_uri(changed_components) if response
|
435
|
+
|
436
|
+
components = URI::HTTP::COMPONENT.inject({}) do |result, c|
|
437
|
+
result.merge c => uri.send(c)
|
438
|
+
end
|
439
|
+
self.class.clear_cookies_if_host_changes self do
|
440
|
+
@uri = self.class.build_uri(components.merge(changed_components))
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
attr_writer :response
|
445
|
+
|
446
|
+
attr_writer :uri
|
447
|
+
|
448
|
+
private
|
449
|
+
|
450
|
+
def authority
|
451
|
+
self.class.build_authority :userinfo => uri.userinfo,
|
452
|
+
:host => uri.host,
|
453
|
+
:port => uri.port
|
454
|
+
end
|
455
|
+
|
456
|
+
def request!(method)
|
457
|
+
request = response ? dup_without_response : self
|
458
|
+
request.instance_variable_set '@request_method', method
|
459
|
+
HTTY::Rack::RequestsUtil.send method, request
|
460
|
+
end
|
461
|
+
|
462
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rack/test'
|
2
|
+
|
3
|
+
module HTTY::Rack
|
4
|
+
class RequestsUtil
|
5
|
+
class RackApp
|
6
|
+
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def app
|
12
|
+
@app
|
13
|
+
end
|
14
|
+
|
15
|
+
include Rack::Test::Methods
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
%w(get post put delete option trace).each do |v|
|
20
|
+
define_method(v) do |request|
|
21
|
+
request(request) do |app|
|
22
|
+
a = RackApp.new(app)
|
23
|
+
|
24
|
+
a.send(v,
|
25
|
+
request.send(:path_query_and_fragment),
|
26
|
+
Hash[*request.headers])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
def self.http_response_to_status(http_response)
|
34
|
+
[http_response.status,
|
35
|
+
Rack::Utils::HTTP_STATUS_CODES[http_response.status]]
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def self.request(request)
|
40
|
+
http_response = yield request.app
|
41
|
+
headers = []
|
42
|
+
http_response.headers.each do |*h|
|
43
|
+
headers << h
|
44
|
+
end
|
45
|
+
request.send :response=,
|
46
|
+
HTTY::Response.new(:status => http_response_to_status(http_response),
|
47
|
+
:headers => headers,
|
48
|
+
:body => http_response.body)
|
49
|
+
request
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'riot'
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: htty-rack
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 5
|
9
|
+
version: 0.0.5
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Florian Gilcher
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-09-24 00:00:00 +02:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: htty
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 0
|
31
|
+
- 0
|
32
|
+
version: 1.0.0
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
description: A CLI for rack applications based on htty.
|
36
|
+
email:
|
37
|
+
- florian.gilcher@asquera.de
|
38
|
+
executables:
|
39
|
+
- htty-rack
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files: []
|
43
|
+
|
44
|
+
files:
|
45
|
+
- .gitignore
|
46
|
+
- MIT-LICENSE.rdoc
|
47
|
+
- README.rdoc
|
48
|
+
- Rakefile
|
49
|
+
- VERSION
|
50
|
+
- bin/htty-rack
|
51
|
+
- config.ru
|
52
|
+
- htty-rack.gemspec
|
53
|
+
- lib/htty/rack.rb
|
54
|
+
- lib/htty/rack/cli.rb
|
55
|
+
- lib/htty/rack/commands/app.rb
|
56
|
+
- lib/htty/rack/commands/config.rb
|
57
|
+
- lib/htty/rack/commands/irb.rb
|
58
|
+
- lib/htty/rack/commands/require.rb
|
59
|
+
- lib/htty/rack/request.rb
|
60
|
+
- lib/htty/rack/requests_util.rb
|
61
|
+
- lib/htty/rack/session.rb
|
62
|
+
- test/rack-htty_test.rb
|
63
|
+
- test/test_helper.rb
|
64
|
+
has_rdoc: true
|
65
|
+
homepage: http://www.github.com/Asquera/htty-rack
|
66
|
+
licenses: []
|
67
|
+
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
version: "0"
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
requirements: []
|
90
|
+
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 1.3.7
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: htty-rack is htty for rack applications
|
96
|
+
test_files: []
|
97
|
+
|