manticore 0.2.1-java → 0.3.0-java
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 +4 -4
- checksums.yaml.gz.sig +1 -0
- data/CHANGELOG.md +31 -0
- data/README.md +164 -41
- data/gem-public_cert.pem +21 -0
- data/lib/manticore/client/proxies.rb +57 -0
- data/lib/manticore/client.rb +78 -63
- data/lib/manticore/cookie.rb +23 -3
- data/lib/manticore/response.rb +138 -36
- data/lib/manticore/stubbed_response.rb +91 -0
- data/lib/manticore/version.rb +1 -1
- data/lib/manticore.rb +15 -5
- data/manticore.gemspec +6 -0
- data/spec/manticore/client_proxy_spec.rb +92 -0
- data/spec/manticore/client_spec.rb +122 -37
- data/spec/manticore/cookie_spec.rb +23 -4
- data/spec/manticore/response_spec.rb +8 -0
- data/spec/manticore/stubbed_response_spec.rb +44 -0
- data/spec/spec_helper.rb +3 -0
- data.tar.gz.sig +0 -0
- metadata +33 -4
- metadata.gz.sig +0 -0
- data/lib/manticore/async_response.rb +0 -88
data/lib/manticore/cookie.rb
CHANGED
@@ -29,7 +29,7 @@ module Manticore
|
|
29
29
|
comment: cookie.get_comment,
|
30
30
|
comment_url: cookie.getCommentURL,
|
31
31
|
domain: cookie.get_domain,
|
32
|
-
|
32
|
+
expires: expiry,
|
33
33
|
name: cookie.get_name,
|
34
34
|
path: cookie.get_path,
|
35
35
|
ports: cookie.get_ports.to_a,
|
@@ -40,13 +40,33 @@ module Manticore
|
|
40
40
|
)
|
41
41
|
end
|
42
42
|
|
43
|
-
|
43
|
+
def self.from_set_cookie(value)
|
44
|
+
opts = {name: nil, value: nil}
|
45
|
+
value.split(";").each do |part|
|
46
|
+
k, v = part.split("=", 2).map(&:strip)
|
47
|
+
|
48
|
+
if opts[:name].nil?
|
49
|
+
opts[:name] = k
|
50
|
+
opts[:value] = v
|
51
|
+
end
|
52
|
+
|
53
|
+
case k.downcase
|
54
|
+
when "domain", "path"
|
55
|
+
opts[k.to_sym] = v
|
56
|
+
when "secure"
|
57
|
+
opts[:secure] = true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
Manticore::Cookie.new opts
|
61
|
+
end
|
62
|
+
|
63
|
+
attr_reader :comment, :comment_url, :domain, :expires, :name, :path, :ports, :value, :spec_version
|
44
64
|
|
45
65
|
def initialize(args)
|
46
66
|
@comment = args.fetch(:comment, nil)
|
47
67
|
@comment_url = args.fetch(:comment_url, nil)
|
48
68
|
@domain = args.fetch(:domain, nil)
|
49
|
-
@
|
69
|
+
@expires = args.fetch(:expires, nil)
|
50
70
|
@name = args.fetch(:name, nil)
|
51
71
|
@path = args.fetch(:path, nil)
|
52
72
|
@ports = args.fetch(:ports, nil)
|
data/lib/manticore/response.rb
CHANGED
@@ -14,40 +14,62 @@ module Manticore
|
|
14
14
|
include_package "org.apache.http.client"
|
15
15
|
include_package "org.apache.http.util"
|
16
16
|
include_package "org.apache.http.protocol"
|
17
|
-
|
17
|
+
java_import "org.apache.http.client.protocol.HttpClientContext"
|
18
|
+
java_import 'java.util.concurrent.Callable'
|
19
|
+
|
18
20
|
include ResponseHandler
|
21
|
+
include Callable
|
19
22
|
|
20
|
-
attr_reader :
|
23
|
+
attr_reader :context, :request, :callback_result, :called
|
21
24
|
|
22
25
|
# Creates a new Response
|
23
26
|
#
|
24
27
|
# @param request [HttpRequestBase] The underlying request object
|
25
28
|
# @param context [HttpContext] The underlying HttpContext
|
26
|
-
|
27
|
-
|
29
|
+
def initialize(client, request, context, &block)
|
30
|
+
@client = client
|
28
31
|
@request = request
|
29
32
|
@context = context
|
30
|
-
@
|
33
|
+
@handlers = {
|
34
|
+
success: block || Proc.new {|resp| resp.body },
|
35
|
+
failure: Proc.new {|ex| raise ex },
|
36
|
+
cancelled: Proc.new {},
|
37
|
+
complete: []
|
38
|
+
}
|
31
39
|
end
|
32
40
|
|
33
|
-
# Implementation of
|
34
|
-
#
|
35
|
-
def
|
36
|
-
|
37
|
-
@
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
41
|
+
# Implementation of Callable#call
|
42
|
+
# Used by Manticore::Client to invoke the request tied to this response. Users should never call this directly.
|
43
|
+
def call
|
44
|
+
raise "Already called" if @called
|
45
|
+
@called = true
|
46
|
+
begin
|
47
|
+
@client.execute @request, self, @context
|
48
|
+
execute_complete
|
49
|
+
return self
|
50
|
+
rescue Java::JavaNet::SocketTimeoutException, Java::OrgApacheHttpConn::ConnectTimeoutException, Java::OrgApacheHttp::NoHttpResponseException => e
|
51
|
+
ex = Manticore::Timeout.new(e.get_cause)
|
52
|
+
rescue Java::JavaNet::SocketException => e
|
53
|
+
ex = Manticore::SocketException.new(e.get_cause)
|
54
|
+
rescue Java::OrgApacheHttpClient::ClientProtocolException, Java::JavaxNetSsl::SSLHandshakeException, Java::OrgApacheHttpConn::HttpHostConnectException => e
|
55
|
+
ex = Manticore::ClientProtocolException.new(e.get_cause)
|
56
|
+
rescue Java::JavaNet::UnknownHostException => e
|
57
|
+
ex = Manticore::ResolutionFailure.new(e.get_cause)
|
43
58
|
end
|
44
|
-
|
59
|
+
@exception = ex
|
60
|
+
@handlers[:failure].call ex
|
61
|
+
execute_complete
|
45
62
|
end
|
46
63
|
|
47
|
-
|
64
|
+
def fire_and_forget
|
65
|
+
@client.executor.submit self
|
66
|
+
end
|
67
|
+
|
68
|
+
# Fetch the final resolved URL for this response. Will call the request if it has not been called yet.
|
48
69
|
#
|
49
70
|
# @return [String]
|
50
71
|
def final_url
|
72
|
+
call_once
|
51
73
|
last_request = context.get_attribute ExecutionContext.HTTP_REQUEST
|
52
74
|
last_host = context.get_attribute ExecutionContext.HTTP_TARGET_HOST
|
53
75
|
host = last_host.to_uri
|
@@ -55,7 +77,7 @@ module Manticore
|
|
55
77
|
URI.join(host, url.to_s)
|
56
78
|
end
|
57
79
|
|
58
|
-
# Fetch the body content of this response.
|
80
|
+
# Fetch the body content of this response. Will call the request if it has not been called yet.
|
59
81
|
# This fetches the input stream in Ruby; this isn't optimal, but it's faster than
|
60
82
|
# fetching the whole thing in Java then UTF-8 encoding it all into a giant Ruby string.
|
61
83
|
#
|
@@ -70,47 +92,127 @@ module Manticore
|
|
70
92
|
# end
|
71
93
|
#
|
72
94
|
# @return [String] Reponse body
|
73
|
-
def
|
95
|
+
def body(&block)
|
96
|
+
call_once
|
74
97
|
@body ||= begin
|
75
98
|
if entity = @response.get_entity
|
76
99
|
EntityConverter.new.read_entity(entity, &block)
|
77
100
|
end
|
78
101
|
rescue Java::JavaIo::IOException, Java::JavaNet::SocketException, IOError => e
|
79
|
-
raise StreamClosedException.new("Could not read from stream: #{e.message}
|
102
|
+
raise StreamClosedException.new("Could not read from stream: #{e.message}")
|
103
|
+
# ensure
|
104
|
+
# @request.release_connection
|
80
105
|
end
|
81
106
|
end
|
82
|
-
alias_method :
|
107
|
+
alias_method :read_body, :body
|
108
|
+
|
109
|
+
# Returns true if this response has been called (requested and populated) yet
|
110
|
+
def called?
|
111
|
+
!!@called
|
112
|
+
end
|
113
|
+
|
114
|
+
# Return a hash of headers from this response. Will call the request if it has not been called yet.
|
115
|
+
#
|
116
|
+
# @return [Array<string, obj>] Hash of headers. Keys will be lower-case.
|
117
|
+
def headers
|
118
|
+
call_once
|
119
|
+
@headers
|
120
|
+
end
|
121
|
+
|
122
|
+
# Return the response code from this request as an integer. Will call the request if it has not been called yet.
|
123
|
+
#
|
124
|
+
# @return [Integer] The response code
|
125
|
+
def code
|
126
|
+
call_once
|
127
|
+
@code
|
128
|
+
end
|
83
129
|
|
84
130
|
# Returns the length of the response body. Returns -1 if content-length is not present in the response.
|
85
131
|
#
|
86
132
|
# @return [Integer]
|
87
133
|
def length
|
88
|
-
(
|
134
|
+
(headers["content-length"] || -1).to_i
|
89
135
|
end
|
90
136
|
|
91
137
|
# Returns an array of {Manticore::Cookie Cookies} associated with this request's execution context
|
92
138
|
#
|
93
139
|
# @return [Array<Manticore::Cookie>]
|
94
140
|
def cookies
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
141
|
+
call_once
|
142
|
+
@cookies ||= begin
|
143
|
+
@context.get_cookie_store.get_cookies.inject({}) do |all, java_cookie|
|
144
|
+
c = Cookie.from_java(java_cookie)
|
145
|
+
all[c.name] ||= []
|
146
|
+
all[c.name] << c
|
147
|
+
all
|
148
|
+
end
|
100
149
|
end
|
101
150
|
end
|
102
151
|
|
152
|
+
# Set handler for success responses
|
153
|
+
# @param block Proc which will be invoked on a successful response. Block will receive |response, request|
|
154
|
+
#
|
155
|
+
# @return self
|
156
|
+
def on_success(&block)
|
157
|
+
@handlers[:success] = block
|
158
|
+
self
|
159
|
+
end
|
160
|
+
alias_method :success, :on_success
|
161
|
+
|
162
|
+
# Set handler for failure responses
|
163
|
+
# @param block Proc which will be invoked on a on a failed response. Block will receive an exception object.
|
164
|
+
#
|
165
|
+
# @return self
|
166
|
+
def on_failure(&block)
|
167
|
+
@handlers[:failure] = block
|
168
|
+
self
|
169
|
+
end
|
170
|
+
alias_method :failure, :on_failure
|
171
|
+
alias_method :fail, :on_failure
|
172
|
+
|
173
|
+
# Set handler for cancelled requests
|
174
|
+
# @param block Proc which will be invoked on a on a cancelled response.
|
175
|
+
#
|
176
|
+
# @return self
|
177
|
+
def on_cancelled(&block)
|
178
|
+
@handlers[:cancelled] = block
|
179
|
+
self
|
180
|
+
end
|
181
|
+
alias_method :cancelled, :on_cancelled
|
182
|
+
alias_method :cancellation, :on_cancelled
|
183
|
+
alias_method :on_cancellation, :on_cancelled
|
184
|
+
|
185
|
+
# Set handler for cancelled requests
|
186
|
+
# @param block Proc which will be invoked on a on a cancelled response.
|
187
|
+
#
|
188
|
+
# @return self
|
189
|
+
def on_complete(&block)
|
190
|
+
@handlers[:complete] = Array(@handlers[:complete]).compact + [block]
|
191
|
+
self
|
192
|
+
end
|
193
|
+
alias_method :complete, :on_complete
|
194
|
+
alias_method :completed, :on_complete
|
195
|
+
alias_method :on_completed, :on_complete
|
196
|
+
|
103
197
|
private
|
104
198
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
199
|
+
# Implementation of {http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/client/ResponseHandler.html#handleResponse(org.apache.http.HttpResponse) ResponseHandler#handleResponse}
|
200
|
+
# @param response [Response] The underlying Java Response object
|
201
|
+
def handleResponse(response)
|
202
|
+
@response = response
|
203
|
+
@code = response.get_status_line.get_status_code
|
204
|
+
@headers = Hash[* response.get_all_headers.flat_map {|h| [h.get_name.downcase, h.get_value]} ]
|
205
|
+
@callback_result = @handlers[:success].call(self)
|
206
|
+
nil
|
207
|
+
end
|
208
|
+
|
209
|
+
def call_once
|
210
|
+
call unless called?
|
211
|
+
@called = true
|
212
|
+
end
|
213
|
+
|
214
|
+
def execute_complete
|
215
|
+
@handlers[:complete].each &:call
|
114
216
|
end
|
115
217
|
end
|
116
218
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Manticore
|
2
|
+
# StubbedResponse is a special subclass of Response that may be used to test your code that uses Manticore
|
3
|
+
# without actually having Manticore make requests.
|
4
|
+
#
|
5
|
+
# @example Standard usage with Rspec
|
6
|
+
#
|
7
|
+
# response = Manticore::StubbedResponse.stub(body: "body", code: 200).call
|
8
|
+
# Manticore.should_receive(:get).with("http://www.google.com/").and_return(response)
|
9
|
+
# response.body.should == "body"
|
10
|
+
# response.code.should == 200
|
11
|
+
#
|
12
|
+
# @since 0.3.0
|
13
|
+
class StubbedResponse < Response
|
14
|
+
|
15
|
+
# Helper that instantiates a Response and stubs out its body, response code, etc
|
16
|
+
# @param stubs [Hash] Hash of parameters to stub. See #stub.
|
17
|
+
# @return [Manticore::StubbedResponse]
|
18
|
+
def self.stub(stubs = {})
|
19
|
+
new.stub(stubs)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(client = nil, request = nil, context = nil)
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
# Stub out a Manticore::RequestStub.
|
27
|
+
#
|
28
|
+
# @param stubs [Hash] Parameters to stub out
|
29
|
+
# @option stubs [String] body The body that should be returned
|
30
|
+
# @option stubs [Integer] code Response code to simulate
|
31
|
+
# @option stubs [Hash] headers Response headers as a hash
|
32
|
+
# @option stubs [Array<Manticore::Cookie>] cookies
|
33
|
+
# @return [Manticore::StubbedResponse] self
|
34
|
+
def stub(stubs)
|
35
|
+
if stubs.key? :cookies
|
36
|
+
stubs[:cookies].keys.each {|key| stubs[:cookies][key] = Array(stubs[:cookies][key]) }
|
37
|
+
end
|
38
|
+
stubs[:code] ||= 200
|
39
|
+
stubs[:body] ||= "" if stubs[:code] == 200
|
40
|
+
|
41
|
+
stubs[:headers] ||= {}
|
42
|
+
stubs[:headers] = Hash[*stubs[:headers].flat_map {|k, v| [k.downcase, v] }]
|
43
|
+
stubs[:headers]["content-length"] = stubs[:body].length if stubs.key?(:body)
|
44
|
+
@stubs = stubs
|
45
|
+
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
# Used by Manticore::Client to invoke the request tied to this response
|
50
|
+
def call
|
51
|
+
@called = true
|
52
|
+
handleResponse @stubs
|
53
|
+
end
|
54
|
+
|
55
|
+
# Simulates the final URL of a redirected request. Returns the value of headers["location"]
|
56
|
+
#
|
57
|
+
# @return [String] The final URL
|
58
|
+
def final_url
|
59
|
+
@headers["location"]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the stubbed body of this response.
|
63
|
+
def body
|
64
|
+
@body
|
65
|
+
end
|
66
|
+
alias_method :read_body, :body
|
67
|
+
|
68
|
+
# Returns the stubbed cookies of this response. This is the union of cookies from the `:cookies`
|
69
|
+
# option key and any `set-cookie` headers passed.
|
70
|
+
def cookies
|
71
|
+
@cookies
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def handleResponse(response)
|
77
|
+
raise response[:raises] if response.key?(:raises)
|
78
|
+
@body = response[:body]
|
79
|
+
@code = response[:code]
|
80
|
+
@headers = response[:headers]
|
81
|
+
@cookies = response[:cookies]
|
82
|
+
Array(@headers["set-cookie"]).each do |cookie|
|
83
|
+
c = Cookie.from_set_cookie(cookie)
|
84
|
+
@cookies[c.name] ||= []
|
85
|
+
@cookies[c.name] << c
|
86
|
+
end
|
87
|
+
@handlers[:success].call(self)
|
88
|
+
self
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/manticore/version.rb
CHANGED
data/lib/manticore.rb
CHANGED
@@ -1,9 +1,18 @@
|
|
1
1
|
require 'java'
|
2
|
+
require 'uri'
|
3
|
+
require 'cgi'
|
4
|
+
require 'cgi/cookie'
|
5
|
+
|
6
|
+
jars = ["httpcore-4.3.1", "httpclient-4.3.2-patched", "commons-logging-1.1.3", "commons-codec-1.6.jar"]
|
7
|
+
jars.each do |jar|
|
8
|
+
begin
|
9
|
+
require_relative "./jar/#{jar}"
|
10
|
+
rescue LoadError
|
11
|
+
raise "Unable to load #{jar}; is there another version of it in your classpath?"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
2
15
|
# 4.3.x
|
3
|
-
require_relative "./jar/httpcore-4.3.1"
|
4
|
-
require_relative "./jar/httpclient-4.3.2-patched"
|
5
|
-
require_relative "./jar/commons-logging-1.1.3"
|
6
|
-
require_relative "./jar/commons-codec-1.6.jar"
|
7
16
|
require_relative "./jar/manticore-ext"
|
8
17
|
|
9
18
|
org.manticore.Manticore.new.load(JRuby.runtime, false)
|
@@ -29,9 +38,10 @@ module Manticore
|
|
29
38
|
# Socket breaks, etc
|
30
39
|
class SocketException < ManticoreException; end
|
31
40
|
|
41
|
+
require_relative './manticore/client/proxies'
|
32
42
|
require_relative './manticore/client'
|
33
43
|
require_relative './manticore/response'
|
34
|
-
require_relative './manticore/
|
44
|
+
require_relative './manticore/stubbed_response'
|
35
45
|
require_relative './manticore/cookie'
|
36
46
|
require_relative './manticore/facade'
|
37
47
|
|
data/manticore.gemspec
CHANGED
@@ -19,6 +19,12 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
|
+
private_key = File.expand_path("~/.gemcert/gem-private_key.pem")
|
23
|
+
if File.exists? private_key
|
24
|
+
spec.signing_key = private_key
|
25
|
+
spec.cert_chain = ['gem-public_cert.pem']
|
26
|
+
end
|
27
|
+
|
22
28
|
spec.add_dependency "addressable", "~> 2.3"
|
23
29
|
spec.add_development_dependency "bundler", "~> 1.3"
|
24
30
|
spec.add_development_dependency "rake"
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Manticore::Client do
|
3
|
+
let(:client) { Manticore::Client.new }
|
4
|
+
|
5
|
+
describe Manticore::Client::StubProxy do
|
6
|
+
describe "#respond_with" do
|
7
|
+
it "should respond with a stubbed response" do
|
8
|
+
client.respond_with(body: "body", code: 200).get(local_server).on_success do |response|
|
9
|
+
response.should be_a Manticore::StubbedResponse
|
10
|
+
response.body.should == "body"
|
11
|
+
response.code.should == 200
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "for synchronous requests" do
|
16
|
+
it "should respond only stub the next subsequent response" do
|
17
|
+
stub = client.respond_with(body: "body", code: 200)
|
18
|
+
|
19
|
+
stub.get(local_server) do |response|
|
20
|
+
response.should be_a Manticore::StubbedResponse
|
21
|
+
end
|
22
|
+
|
23
|
+
stub.get(local_server) do |response|
|
24
|
+
response.should be_a Manticore::Response
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "for synchronous requests" do
|
30
|
+
it "should respond only stub the next subsequent response" do
|
31
|
+
stub = client.respond_with(body: "body", code: 200)
|
32
|
+
|
33
|
+
stub.async.get(local_server).on_success do |response|
|
34
|
+
response.should be_a Manticore::StubbedResponse
|
35
|
+
end
|
36
|
+
|
37
|
+
stub.async.get(local_server).on_success do |response|
|
38
|
+
response.should be_a Manticore::Response
|
39
|
+
end
|
40
|
+
|
41
|
+
client.execute!
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe Manticore::Client::AsyncProxy do
|
48
|
+
it "should not make a request until execute is called" do
|
49
|
+
anchor = Time.now.to_f
|
50
|
+
client.async.get("http://localhost:55441/?sleep=1.6")
|
51
|
+
(Time.now.to_f - anchor).should < 1.0
|
52
|
+
|
53
|
+
anchor = Time.now.to_f
|
54
|
+
client.execute!
|
55
|
+
(Time.now.to_f - anchor).should > 1.0
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should return the response object, which may then have handlers attached" do
|
59
|
+
response = client.async.get("http://localhost:55441/")
|
60
|
+
success = false
|
61
|
+
response.on_success do
|
62
|
+
success = true
|
63
|
+
end
|
64
|
+
|
65
|
+
client.execute!
|
66
|
+
success.should == true
|
67
|
+
end
|
68
|
+
|
69
|
+
it "can chain handlers" do
|
70
|
+
client.async.get("http://localhost:55441/").on_success {|r| r.code }
|
71
|
+
client.execute!.map(&:callback_result).should == [200]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe Manticore::Client::BackgroundProxy do
|
76
|
+
it "should not block execution" do
|
77
|
+
anchor = Time.now.to_f
|
78
|
+
future = client.background.get("http://localhost:55441/?sleep=1.5")
|
79
|
+
(Time.now.to_f - anchor).should < 1.0
|
80
|
+
|
81
|
+
response = future.get
|
82
|
+
(Time.now.to_f - anchor).should > 1.0
|
83
|
+
response.body.should match(/sleep=1.5/)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should return a future" do
|
87
|
+
response = client.background.get("http://localhost:55441/")
|
88
|
+
response.should be_a Java::JavaUtilConcurrent::FutureTask
|
89
|
+
response.get
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|