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.
@@ -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
- expiry_date: expiry,
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
- attr_reader :comment, :comment_url, :domain, :expiry_date, :name, :path, :ports, :value, :spec_version
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
- @expiry_date = args.fetch(:expiry_date, nil)
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)
@@ -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
- # java_import "org.manticore.EntityConverter"
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 :headers, :code, :context, :request, :callback_result
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
- # @param body_handler_block [Proc] And optional block to by yielded to for handling this response
27
- def initialize(request, context, body_handler_block)
29
+ def initialize(client, request, context, &block)
30
+ @client = client
28
31
  @request = request
29
32
  @context = context
30
- @handler_block = body_handler_block
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 {http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/client/ResponseHandler.html#handleResponse(org.apache.http.HttpResponse) ResponseHandler#handleResponse}
34
- # @param response [Response] The underlying Java Response object
35
- def handle_response(response)
36
- @response = response
37
- @code = response.get_status_line.get_status_code
38
- @headers = Hash[* response.get_all_headers.flat_map {|h| [h.get_name.downcase, h.get_value]} ]
39
- if @handler_block
40
- @callback_result = @handler_block.call(self)
41
- else
42
- read_body
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
- self
59
+ @exception = ex
60
+ @handlers[:failure].call ex
61
+ execute_complete
45
62
  end
46
63
 
47
- # Fetch the final resolved URL for this response
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 read_body(&block)
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} (Did you forget to read #body from your block?)")
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 :body, :read_body
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
- (@headers["content-length"] || -1).to_i
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
- @context.get_cookie_store.get_cookies.inject({}) do |all, java_cookie|
96
- c = Cookie.from_java(java_cookie)
97
- all[c.name] ||= []
98
- all[c.name] << c
99
- all
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
- def encode(string, charset)
106
- return string if charset.nil?
107
- begin
108
- string.encode(charset)
109
- rescue Encoding::ConverterNotFoundError
110
- string.encode("utf-8")
111
- rescue
112
- string
113
- end
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
@@ -1,3 +1,3 @@
1
1
  module Manticore
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
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/async_response'
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