kronk 1.0.3 → 1.1.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.
- data/{History.txt → History.rdoc} +22 -0
- data/Manifest.txt +14 -2
- data/README.rdoc +258 -0
- data/Rakefile +4 -0
- data/lib/kronk.rb +239 -102
- data/lib/kronk/diff.rb +2 -2
- data/lib/kronk/request.rb +146 -20
- data/lib/kronk/xml_parser.rb +1 -1
- data/test/mocks/200_response.json +28 -0
- data/test/mocks/200_response.plist +97 -0
- data/test/mocks/200_response.txt +23 -0
- data/test/mocks/200_response.xml +58 -0
- data/test/mocks/301_response.txt +16 -0
- data/test/mocks/302_response.txt +17 -0
- data/test/test_kronk.rb +142 -32
- data/test/test_request.rb +319 -12
- metadata +36 -20
- data/README.txt +0 -122
data/lib/kronk/diff.rb
CHANGED
@@ -100,7 +100,7 @@ class Kronk
|
|
100
100
|
"#{pad}#{key.inspect} => #{subdata}"
|
101
101
|
end
|
102
102
|
|
103
|
-
output << data_values.sort.join(",\n") << "\n"
|
103
|
+
output << data_values.sort.join(",\n") << "\n" unless data_values.empty?
|
104
104
|
output << "#{" " * indent}}"
|
105
105
|
|
106
106
|
when Array
|
@@ -112,7 +112,7 @@ class Kronk
|
|
112
112
|
"#{pad}#{ordered_data_string value, struct_only, indent + 1}"
|
113
113
|
end
|
114
114
|
|
115
|
-
output << data_values.join(",\n") << "\n"
|
115
|
+
output << data_values.join(",\n") << "\n" unless data_values.empty?
|
116
116
|
output << "#{" " * indent}]"
|
117
117
|
|
118
118
|
else
|
data/lib/kronk/request.rb
CHANGED
@@ -5,6 +5,7 @@ class Kronk
|
|
5
5
|
|
6
6
|
class Request
|
7
7
|
|
8
|
+
|
8
9
|
class NotFoundError < Exception; end
|
9
10
|
|
10
11
|
##
|
@@ -17,7 +18,10 @@ class Kronk
|
|
17
18
|
rdir = options[:follow_redirects]
|
18
19
|
rdir = rdir - 1 if Integer === rdir && rdir > 0
|
19
20
|
|
20
|
-
|
21
|
+
options = options.merge :follow_redirects => rdir,
|
22
|
+
:http_method => :get
|
23
|
+
|
24
|
+
retrieve_uri resp['Location'], options
|
21
25
|
end
|
22
26
|
|
23
27
|
|
@@ -34,16 +38,22 @@ class Kronk
|
|
34
38
|
# Returns the value from a url, file, or cache as a String.
|
35
39
|
# Options supported are:
|
36
40
|
# :data:: Hash/String - the data to pass to the http request
|
41
|
+
# :query:: Hash/String - the data to append to the http request path
|
37
42
|
# :follow_redirects:: Integer/Bool - number of times to follow redirects
|
43
|
+
# :user_agent:: String - user agent string or alias; defaults to 'kronk'
|
44
|
+
# :auth:: Hash - must contain :username and :password; defaults to nil
|
38
45
|
# :headers:: Hash - extra headers to pass to the request
|
39
46
|
# :http_method:: Symbol - the http method to use; defaults to :get
|
47
|
+
# :proxy:: Hash/String - http proxy to use; defaults to nil
|
40
48
|
|
41
49
|
def self.retrieve query, options={}
|
42
50
|
resp =
|
43
|
-
if
|
44
|
-
|
45
|
-
|
51
|
+
if IO === query || StringIO === query
|
52
|
+
retrieve_io query, options
|
53
|
+
elsif local?(query)
|
46
54
|
retrieve_file query, options
|
55
|
+
else
|
56
|
+
retrieve_uri query, options
|
47
57
|
end
|
48
58
|
|
49
59
|
begin
|
@@ -72,7 +82,7 @@ class Kronk
|
|
72
82
|
# Read http response from a file and return a HTTPResponse instance.
|
73
83
|
|
74
84
|
def self.retrieve_file path, options={}
|
75
|
-
Kronk.verbose "
|
85
|
+
Kronk.verbose "Reading file: #{path}\n"
|
76
86
|
|
77
87
|
options = options.dup
|
78
88
|
|
@@ -97,20 +107,47 @@ class Kronk
|
|
97
107
|
end
|
98
108
|
|
99
109
|
|
110
|
+
##
|
111
|
+
# Read the http response from an IO instance and return a HTTPResponse.
|
112
|
+
|
113
|
+
def self.retrieve_io io, options={}
|
114
|
+
Kronk.verbose "Reading IO..."
|
115
|
+
|
116
|
+
options = options.dup
|
117
|
+
|
118
|
+
resp = nil
|
119
|
+
|
120
|
+
begin
|
121
|
+
resp = Response.read_new io
|
122
|
+
|
123
|
+
rescue Net::HTTPBadResponse
|
124
|
+
io.rewind
|
125
|
+
resp = HeadlessResponse.new io.read
|
126
|
+
end
|
127
|
+
|
128
|
+
resp = follow_redirect resp, options if
|
129
|
+
follow_redirect? resp, options[:follow_redirects]
|
130
|
+
|
131
|
+
resp
|
132
|
+
end
|
133
|
+
|
134
|
+
|
100
135
|
##
|
101
136
|
# Make an http request to the given uri and return a HTTPResponse instance.
|
102
137
|
# Supports the following options:
|
103
138
|
# :data:: Hash/String - the data to pass to the http request
|
139
|
+
# :query:: Hash/String - the data to append to the http request path
|
104
140
|
# :follow_redirects:: Integer/Bool - number of times to follow redirects
|
141
|
+
# :user_agent:: String - user agent string or alias; defaults to 'kronk'
|
142
|
+
# :auth:: Hash - must contain :username and :password; defaults to nil
|
105
143
|
# :headers:: Hash - extra headers to pass to the request
|
106
144
|
# :http_method:: Symbol - the http method to use; defaults to :get
|
145
|
+
# :proxy:: Hash/String - http proxy to use; defaults to nil
|
107
146
|
#
|
108
147
|
# Note: if no http method is specified and data is given, will default
|
109
148
|
# to using a post request.
|
110
149
|
|
111
150
|
def self.retrieve_uri uri, options={}
|
112
|
-
Kronk.verbose "\nRetrieving URL: #{uri}#{options[:uri_suffix]}\n"
|
113
|
-
|
114
151
|
options = options.dup
|
115
152
|
http_method = options.delete(:http_method)
|
116
153
|
http_method ||= options[:data] ? :post : :get
|
@@ -127,32 +164,70 @@ class Kronk
|
|
127
164
|
##
|
128
165
|
# Make an http request to the given uri and return a HTTPResponse instance.
|
129
166
|
# Supports the following options:
|
130
|
-
# :data:: Hash/String - the data to pass to the http request
|
131
|
-
# :
|
167
|
+
# :data:: Hash/String - the data to pass to the http request body
|
168
|
+
# :query:: Hash/String - the data to append to the http request path
|
169
|
+
# :user_agent:: String - user agent string or alias; defaults to 'kronk'
|
170
|
+
# :auth:: Hash - must contain :username and :password; defaults to nil
|
132
171
|
# :headers:: Hash - extra headers to pass to the request
|
133
172
|
# :http_method:: Symbol - the http method to use; defaults to :get
|
173
|
+
# :proxy:: Hash/String - http proxy to use; defaults to nil
|
134
174
|
|
135
175
|
def self.call http_method, uri, options={}
|
136
176
|
suffix = options.delete :uri_suffix
|
137
177
|
|
138
|
-
uri
|
139
|
-
uri
|
178
|
+
uri = "#{uri}#{suffix}" if suffix
|
179
|
+
uri = URI.parse uri unless URI === uri
|
180
|
+
|
181
|
+
if options[:query]
|
182
|
+
query = build_query options[:query]
|
183
|
+
uri.query = [uri.query, query].compact.join "&"
|
184
|
+
end
|
140
185
|
|
141
186
|
data = options[:data]
|
142
|
-
data &&=
|
187
|
+
data &&= build_query data
|
188
|
+
|
189
|
+
options[:headers] ||= Hash.new
|
190
|
+
options[:headers]['User-Agent'] ||= get_user_agent options[:user_agent]
|
191
|
+
|
192
|
+
unless options[:headers]['Cookie'] || !use_cookies?(options)
|
193
|
+
cookie = Kronk.cookie_jar.get_cookie_header uri.to_s
|
194
|
+
options[:headers]['Cookie'] = cookie unless cookie.empty?
|
195
|
+
end
|
143
196
|
|
144
197
|
socket = socket_io = nil
|
145
198
|
|
146
|
-
|
199
|
+
proxy_addr, proxy_opts =
|
200
|
+
if Hash === options[:proxy]
|
201
|
+
[options[:proxy][:address], options[:proxy]]
|
202
|
+
else
|
203
|
+
[options[:proxy], {}]
|
204
|
+
end
|
205
|
+
|
206
|
+
http_class = proxy proxy_addr, proxy_opts
|
207
|
+
|
208
|
+
req = http_class.new uri.host, uri.port
|
209
|
+
req.use_ssl = true if uri.scheme =~ /^https$/
|
210
|
+
|
211
|
+
resp = req.start do |http|
|
147
212
|
socket = http.instance_variable_get "@socket"
|
148
213
|
socket.debug_output = socket_io = StringIO.new
|
149
214
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
215
|
+
req = VanillaRequest.new http_method.to_s.upcase,
|
216
|
+
uri.request_uri, options[:headers]
|
217
|
+
|
218
|
+
if options[:auth] && options[:auth][:username]
|
219
|
+
req.basic_auth options[:auth][:username],
|
220
|
+
options[:auth][:password]
|
221
|
+
end
|
222
|
+
|
223
|
+
Kronk.verbose "Retrieving URL: #{uri}\n"
|
224
|
+
|
225
|
+
http.request req, data
|
154
226
|
end
|
155
227
|
|
228
|
+
Kronk.cookie_jar.set_cookies_from_headers uri.to_s, resp.to_hash if
|
229
|
+
use_cookies? options
|
230
|
+
|
156
231
|
resp.extend Response::Helpers
|
157
232
|
|
158
233
|
r_req, r_resp, r_bytes = Response.read_raw_from socket_io
|
@@ -162,13 +237,49 @@ class Kronk
|
|
162
237
|
end
|
163
238
|
|
164
239
|
|
240
|
+
##
|
241
|
+
# Checks if cookies should be used and set.
|
242
|
+
|
243
|
+
def self.use_cookies? options
|
244
|
+
return !options[:no_cookies] if options.has_key? :no_cookies
|
245
|
+
Kronk.config[:use_cookies]
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
##
|
250
|
+
# Gets the user agent to use for the request.
|
251
|
+
|
252
|
+
def self.get_user_agent agent
|
253
|
+
agent && Kronk.config[:user_agents][agent.to_s] || agent ||
|
254
|
+
Kronk.config[:user_agents]['kronk']
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
##
|
259
|
+
# Return proxy http class.
|
260
|
+
# The proxy_opts arg can be a uri String or a Hash with the :address key
|
261
|
+
# and optional :username and :password keys.
|
262
|
+
|
263
|
+
def self.proxy addr, proxy_opts={}
|
264
|
+
return Net::HTTP unless addr
|
265
|
+
|
266
|
+
host, port = addr.split ":"
|
267
|
+
port ||= proxy_opts[:port] || 8080
|
268
|
+
|
269
|
+
user = proxy_opts[:username]
|
270
|
+
pass = proxy_opts[:password]
|
271
|
+
|
272
|
+
Kronk.verbose "Using proxy #{addr}\n" if host
|
273
|
+
|
274
|
+
Net::HTTP::Proxy host, port, user, pass
|
275
|
+
end
|
276
|
+
|
277
|
+
|
165
278
|
##
|
166
279
|
# Creates a query string from data.
|
167
280
|
|
168
281
|
def self.build_query data, param=nil
|
169
|
-
|
170
|
-
"Can't convert #{data.class} to query without a param name" unless
|
171
|
-
Hash === data || param
|
282
|
+
return data.to_s unless param || Hash === data
|
172
283
|
|
173
284
|
case data
|
174
285
|
when Array
|
@@ -191,5 +302,20 @@ class Kronk
|
|
191
302
|
"#{param}=#{data}"
|
192
303
|
end
|
193
304
|
end
|
305
|
+
|
306
|
+
|
307
|
+
##
|
308
|
+
# Allow any http method to be sent
|
309
|
+
|
310
|
+
class VanillaRequest
|
311
|
+
def self.new method, path, initheader=nil
|
312
|
+
klass = Class.new Net::HTTPRequest
|
313
|
+
klass.const_set "METHOD", method.to_s.upcase
|
314
|
+
klass.const_set "REQUEST_HAS_BODY", true
|
315
|
+
klass.const_set "RESPONSE_HAS_BODY", true
|
316
|
+
|
317
|
+
klass.new path, initheader
|
318
|
+
end
|
319
|
+
end
|
194
320
|
end
|
195
321
|
end
|
data/lib/kronk/xml_parser.rb
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
HTTP/1.1 200 OK
|
2
|
+
Server: nginx/0.6.39
|
3
|
+
Date: Fri, 03 Dec 2010 21:49:00 GMT
|
4
|
+
Content-Type: application/json; charset=utf-8
|
5
|
+
Connection: keep-alive
|
6
|
+
Keep-Alive: timeout=20
|
7
|
+
Status: 200 OK
|
8
|
+
ETag: "mock_etag"
|
9
|
+
X-Runtime: 45
|
10
|
+
Cache-Control: private, max-age=0, must-revalidate
|
11
|
+
|
12
|
+
{"original_request":{"id":"1234"},"request_id":"mock_rid",
|
13
|
+
"business":{"improvable":true,"longitude":-85.759586,"headings":["Pottery"],
|
14
|
+
"website":"http://example.com",
|
15
|
+
"listing_type":"free",
|
16
|
+
"description":{"general_info":"<p>A Paint Your Own Pottery Studios..</p>",
|
17
|
+
"payment_text":"DISCOVER, AMEX, VISA, MASTERCARD",
|
18
|
+
"slogan":"<p>Pottery YOU dress up</p>",
|
19
|
+
"additional_urls":[{"destination":"http://example.com",
|
20
|
+
"url_click":"http://example.com"}],
|
21
|
+
"op_hours":"Fri 1pm-7pm, Sat 10am-6pm, Sun 1pm-4pm, Appointments Available"},
|
22
|
+
"listing_id":"1234","phone":"6168055326",
|
23
|
+
"address":"3845 Rivertown Pkwy SW Ste 500",
|
24
|
+
"mappable":true,"rating_count":0,"rateable":true,
|
25
|
+
"impression_id":"mock_iid","zip":"49418","red_listing":false,"omit_phone":false,
|
26
|
+
"state":"MI","distance":0.0,"year_established":"1996","city":"Grandville",
|
27
|
+
"name":"Naked Plates","latitude":42.882561,"omit_address":false,"id":"1234",
|
28
|
+
"has_detail_page":true}}
|
@@ -0,0 +1,97 @@
|
|
1
|
+
HTTP/1.1 200 OK
|
2
|
+
Server: nginx/0.6.39
|
3
|
+
Date: Fri, 03 Dec 2010 21:49:00 GMT
|
4
|
+
Content-Type: application/x-plist; charset=utf-8
|
5
|
+
Connection: keep-alive
|
6
|
+
Keep-Alive: timeout=20
|
7
|
+
Status: 200 OK
|
8
|
+
ETag: "mock_etag"
|
9
|
+
X-Runtime: 45
|
10
|
+
Cache-Control: private, max-age=0, must-revalidate
|
11
|
+
|
12
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
13
|
+
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
14
|
+
<plist version="1.0">
|
15
|
+
<dict>
|
16
|
+
<key>business</key>
|
17
|
+
<dict>
|
18
|
+
<key>address</key>
|
19
|
+
<string>3845 Rivertown Pkwy SW Ste 500</string>
|
20
|
+
<key>city</key>
|
21
|
+
<string>Grandville</string>
|
22
|
+
<key>description</key>
|
23
|
+
<dict>
|
24
|
+
<key>additional_urls</key>
|
25
|
+
<array>
|
26
|
+
<dict>
|
27
|
+
<key>destination</key>
|
28
|
+
<string>http://example.com</string>
|
29
|
+
<key>url_click</key>
|
30
|
+
<string>http://example.com</string>
|
31
|
+
</dict>
|
32
|
+
</array>
|
33
|
+
<key>general_info</key>
|
34
|
+
<string><p>A Paint Your Own Pottery Studios..</p></string>
|
35
|
+
<key>op_hours</key>
|
36
|
+
<string>Fri 1pm-7pm, Sat 10am-6pm, Sun 1pm-4pm, Appointments Available</string>
|
37
|
+
<key>payment_text</key>
|
38
|
+
<string>DISCOVER, AMEX, VISA, MASTERCARD</string>
|
39
|
+
<key>slogan</key>
|
40
|
+
<string><p>Pottery YOU dress up</p></string>
|
41
|
+
</dict>
|
42
|
+
<key>distance</key>
|
43
|
+
<real>0.0</real>
|
44
|
+
<key>has_detail_page</key>
|
45
|
+
<true/>
|
46
|
+
<key>headings</key>
|
47
|
+
<array>
|
48
|
+
<string>Pottery</string>
|
49
|
+
</array>
|
50
|
+
<key>id</key>
|
51
|
+
<string>1234</string>
|
52
|
+
<key>impression_id</key>
|
53
|
+
<string>mock_iid</string>
|
54
|
+
<key>improvable</key>
|
55
|
+
<true/>
|
56
|
+
<key>latitude</key>
|
57
|
+
<real>42.882561</real>
|
58
|
+
<key>listing_id</key>
|
59
|
+
<string>1234</string>
|
60
|
+
<key>listing_type</key>
|
61
|
+
<string>free</string>
|
62
|
+
<key>longitude</key>
|
63
|
+
<real>-85.759586</real>
|
64
|
+
<key>mappable</key>
|
65
|
+
<true/>
|
66
|
+
<key>name</key>
|
67
|
+
<string>Naked Plates</string>
|
68
|
+
<key>omit_address</key>
|
69
|
+
<false/>
|
70
|
+
<key>omit_phone</key>
|
71
|
+
<false/>
|
72
|
+
<key>phone</key>
|
73
|
+
<string>6168055326</string>
|
74
|
+
<key>rateable</key>
|
75
|
+
<true/>
|
76
|
+
<key>rating_count</key>
|
77
|
+
<integer>0</integer>
|
78
|
+
<key>red_listing</key>
|
79
|
+
<false/>
|
80
|
+
<key>state</key>
|
81
|
+
<string>MI</string>
|
82
|
+
<key>website</key>
|
83
|
+
<string>http://example.com</string>
|
84
|
+
<key>year_established</key>
|
85
|
+
<string>1996</string>
|
86
|
+
<key>zip</key>
|
87
|
+
<string>49418</string>
|
88
|
+
</dict>
|
89
|
+
<key>original_request</key>
|
90
|
+
<dict>
|
91
|
+
<key>id</key>
|
92
|
+
<string>1234</string>
|
93
|
+
</dict>
|
94
|
+
<key>request_id</key>
|
95
|
+
<string>mock_rid</string>
|
96
|
+
</dict>
|
97
|
+
</plist>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
HTTP/1.1 200 OK
|
2
|
+
Date: Fri, 26 Nov 2010 16:16:08 GMT
|
3
|
+
Expires: -1
|
4
|
+
Cache-Control: private, max-age=0
|
5
|
+
Content-Type: text/html; charset=ISO-8859-1
|
6
|
+
Set-Cookie: PREF=ID=99d644506f26d85e:FF=0:TM=1290788168:LM=1290788168:S=VSMemgJxlmlToFA3; expires=Sun, 25-Nov-2012 16:16:08 GMT; path=/; domain=.google.com
|
7
|
+
Set-Cookie: NID=41=CcmNDE4SfDu5cdTOYVkrCVjlrGO-oVbdo1awh_p8auk2gI4uaX1vNznO0QN8nZH4Mh9WprRy3yI2yd_Fr1WaXVru6Xq3adlSLGUTIRW8SzX58An2nH3D2PhAY5JfcJrl; expires=Sat, 28-May-2011 16:16:08 GMT; path=/; domain=.google.com; HttpOnly
|
8
|
+
Server: gws
|
9
|
+
X-XSS-Protection: 1; mode=block
|
10
|
+
Transfer-Encoding: chunked
|
11
|
+
|
12
|
+
<!doctype html><html><head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"><title>Google</title><script>window.google={kEI:"SN3vTLbkBJLSpASAoaHKCg",kEXPI:"27820",kCSI:{e:"27820",ei:"SN3vTLbkBJLSpASAoaHKCg",expi:"27820"},ml:function(){},kHL:"en",time:function(){return(new Date).getTime()},log:function(b,d,
|
13
|
+
c){var a=new Image,e=google,g=e.lc,f=e.li;a.onerror=(a.onload=(a.onabort=function(){delete g[f]}));g[f]=a;c=c||"/gen_204?atyp=i&ct="+b+"&cad="+d+"&zx="+google.time();a.src=c;e.li=f+1},lc:[],li:0,Toolbelt:{}};
|
14
|
+
window.google.sn="webhp";var i=window.google.timers={};window.google.startTick=function(a,b){i[a]={t:{start:(new Date).getTime()},bfr:!(!b)}};window.google.tick=function(a,b,c){if(!i[a])google.startTick(a);i[a].t[b]=c||(new Date).getTime()};google.startTick("load",true);try{}catch(v){}
|
15
|
+
window.google.jsrt_kill=1;
|
16
|
+
var _gjwl=location;function _gjuc(){var e=_gjwl.href.indexOf("#");if(e>=0){var a=_gjwl.href.substring(e);if(a.indexOf("&q=")>0||a.indexOf("#q=")>=0){a=a.substring(1);if(a.indexOf("#")==-1){for(var c=0;c<a.length;){var d=c;if(a.charAt(d)=="&")++d;var b=a.indexOf("&",d);if(b==-1)b=a.length;var f=a.substring(d,b);if(f.indexOf("fp=")==0){a=a.substring(0,c)+a.substring(b,a.length);b=c}else if(f=="cad=h")return 0;c=b}_gjwl.href="/search?"+a+"&cad=h";return 1}}}return 0}function _gjp(){!(window._gjwl.hash&&
|
17
|
+
window._gjuc())&&setTimeout(_gjp,500)};
|
18
|
+
window._gjp && _gjp()</script><style id=gstyle>body{margin:0}#gog{padding:3px 8px 0}td{line-height:.8em}.gac_m td{line-height:17px}form{margin-bottom:20px}body,td,a,p,.h{font-family:arial,sans-serif}.h{color:#36c;font-size:20px}.q{color:#00c}.ts td{padding:0}.ts{border-collapse:collapse}em{font-weight:bold;font-style:normal}.lst{width:496px}.tiah{width:458px}input{font-family:inherit}a.gb1,a.gb2,a.gb3,a.gb4{color:#11c !important}#gbar,#guser{font-size:13px;padding-top:1px !important}#gbar{height:22px}#guser{padding-bottom:7px !important;text-align:right}.gbh,.gbd{border-top:1px solid #c9d7f1;font-size:1px}.gbh{height:0;position:absolute;top:24px;width:100%}@media all{.gb1{height:22px;margin-right:.5em;vertical-align:top}#gbar{float:left}}a.gb1,a.gb4{color:#00c !important}body{background:#fff;color:black}input{-moz-box-sizing:content-box}a{color:#11c;text-decoration:none}a:hover,a:active{text-decoration:underline}.fl a{color:#4272db}a:visited{color:#551a8b}a.gb1,a.gb4{text-decoration:underline}a.gb3:hover{text-decoration:none}#ghead a.gb2:hover{color:#fff!important}.ds{display:-moz-inline-box}.ds{border-bottom:solid 1px #e7e7e7;border-right:solid 1px #e7e7e7;display:inline-block;margin:3px 0 4px;margin-left:4px}.sblc{padding-top:5px}.sblc a{display:block;margin:2px 0;margin-left:13px;font-size:11px;}.lsbb{background:#eee;border:solid 1px;border-color:#ccc #999 #999 #ccc;height:30px;display:block}.lsb{background:url(/images/srpr/nav_logo27.png) bottom;font:15px arial,sans-serif;border:none;color:#000;cursor:pointer;height:30px;margin:0;outline:0;vertical-align:top}.lsb:active{background:#ccc}.lst:focus{outline:none}.ftl,#fll a{margin:0 12px}#addlang a{padding:0 3px}.gac_v div{display:none}.gac_v .gac_v2,.gac_bt{display:block!important}</style><script>google.y={};google.x=function(e,g){google.y[e.id]=[e,g];return false};</script></head><body bgcolor=#ffffff text=#000000 link=#0000cc vlink=#551a8b alink=#ff0000 onload="document.f.q.focus();if(document.images)new Image().src='/images/srpr/nav_logo27.png'" ><textarea id=csi style=display:none></textarea><iframe name=wgjf style=display:none></iframe><div id=ghead><div id=gbar><nobr><b class=gb1>Web</b> <a onclick=gbar.qs(this) href="http://www.google.com/imghp?hl=en&tab=wi" class=gb1>Images</a> <a onclick=gbar.qs(this) href="http://video.google.com/?hl=en&tab=wv" class=gb1>Videos</a> <a onclick=gbar.qs(this) href="http://maps.google.com/maps?hl=en&tab=wl" class=gb1>Maps</a> <a onclick=gbar.qs(this) href="http://news.google.com/nwshp?hl=en&tab=wn" class=gb1>News</a> <a onclick=gbar.qs(this) href="http://www.google.com/prdhp?hl=en&tab=wf" class=gb1>Shopping</a> <a href="http://mail.google.com/mail/?hl=en&tab=wm" class=gb1>Gmail</a> <a href="http://www.google.com/intl/en/options/" class=gb1 style="text-decoration:none"><u>more</u> »</a></nobr></div><div id=guser width=100%><nobr><span id=gbn class=gbi></span><span id=gbf class=gbf></span><span id=gbe><a href="/url?sa=p&pref=ig&pval=3&q=http://www.google.com/ig%3Fhl%3Den%26source%3Diglk&usg=AFQjCNFA18XPfgb7dKnXfKz7x7g1GDH1tg" class=gb4>iGoogle</a> | </span><a href="/preferences?hl=en" class=gb4>Settings</a> | <a href="https://www.google.com/accounts/Login?hl=en&continue=http://www.google.com/" class=gb4>Sign in</a></nobr></div><div class=gbh style=left:0></div><div class=gbh style=right:0></div></div> <center><br clear=all id=lgpd><div id=lga><img alt="Google" height=95 src="/intl/en_ALL/images/srpr/logo1w.png" width=275 id=logo style="padding:28px 0 14px" onload="window.lol&&lol()"><br><br></div><form action="/search" name=f><table cellpadding=0 cellspacing=0><tr valign=top><td width=25%> </td><td align=center nowrap><input name=hl type=hidden value=en><input name=source type=hidden value=hp><input type=hidden name=ie value="ISO-8859-1"><div class=ds style="height:32px;margin:4px 0"><input autocomplete="off" maxlength=2048 name=q class="lst" title="Google Search" value="" size=57 style="background:#fff;border:1px solid #ccc;border-bottom-color:#999;border-right-color:#999;color:#000;font:18px arial,sans-serif bold;height:25px;margin:0;padding:5px 8px 0 6px;vertical-align:top"></div><br style="line-height:0"><span class=ds ><span class=lsbb><input name=btnG type=submit value="Google Search" class=lsb></span></span><span class=ds><span class=lsbb><input name=btnI type=submit value="I'm Feeling Lucky" class=lsb></span></span></td><td nowrap width=25% align=left class=sblc><a href="/advanced_search?hl=en">Advanced Search</a><a href="/language_tools?hl=en">Language Tools</a></td></tr></table></form><div style="font-size:83%;min-height:3.5em"><br></div><div id=res></div><span id=footer><center id=fctr><div style="font-size:10pt"><div id=fll style="margin:19px auto 19px auto;text-align:center"><a href="/intl/en/ads/">Advertising Programs</a><a href="/services/">Business Solutions</a><a href="/intl/en/about.html">About Google</a></div></div><p style="color:#767676;font-size:8pt">© 2010 - <a href="/intl/en/privacy.html">Privacy</a></p></center></span> <div id=xjsd></div><div id=xjsi><script>if(google.y)google.y.first=[];google.dlj=function(b){window.setTimeout(function(){var a=document.createElement("script");a.src=b;document.getElementById("xjsd").appendChild(a)},0)};
|
19
|
+
if(google.y)google.y.first=[];if(!google.xjs){google.dstr=[];google.rein=[];if (google.timers && google.timers.load.t) {google.timers.load.t.xjsls=new Date().getTime();}google.dlj('/extern_js/f/CgJlbhICdXMgACswRTgALCswWjgALCswDjgALCswFzgALCswJzgALCswPDgALCswCjgAQC8sKzAWOAAsKzAlOM-IASwrMEA4ACwrMEE4ACwrME04ACwrMFQ4ACwrMGk4ACwrMBg4ACwrMCY4ACyAAiiQAiU/btDx_PEMAx4.js');google.xjs=1}google.neegg=1;google.mc = [];google.mc = google.mc.concat([[14,{}],[64,{}],[105,{}],[22,{"m_error":"\u003Cfont color=red\u003EError:\u003C/font\u003E The server could not complete your request. Try again in 30 seconds.","m_tip":"Click for more information"}],[84,{}]]);google.y.first.push(function(){var form=document.f||document.f||document.gs;google.ac.i(form,form.q,'','','',{a:1,l:1,o:1,sw:1});google.med&&google.med('init');google.History&&google.History.initialize('/')});if(google.j&&google.j.en&&google.j.xi){window.setTimeout(google.j.xi,0);google.fade=null;}</script></div><script>(function(){
|
20
|
+
var b,d,e,f;function g(a,c){if(a.removeEventListener){a.removeEventListener("load",c,false);a.removeEventListener("error",c,false)}else{a.detachEvent("onload",c);a.detachEvent("onerror",c)}}function h(a){f=(new Date).getTime();++d;a=a||window.event;var c=a.target||a.srcElement;g(c,h)}var i=document.getElementsByTagName("img");b=i.length;d=0;for(var j=0,k;j<b;++j){k=i[j];if(k.complete||typeof k.src!="string"||!k.src)++d;else if(k.addEventListener){k.addEventListener("load",h,false);k.addEventListener("error",
|
21
|
+
h,false)}else{k.attachEvent("onload",h);k.attachEvent("onerror",h)}}e=b-d;function l(){if(!google.timers.load.t)return;google.timers.load.t.ol=(new Date).getTime();google.timers.load.t.iml=f;google.kCSI.imc=d;google.kCSI.imn=b;google.kCSI.imp=e;google.timers.load.t.xjs&&google.report&&google.report(google.timers.load,google.kCSI)}if(window.addEventListener)window.addEventListener("load",l,false);else if(window.attachEvent)window.attachEvent("onload",l);google.timers.load.t.prt=(f=(new Date).getTime());
|
22
|
+
})();
|
23
|
+
</script>
|
@@ -0,0 +1,58 @@
|
|
1
|
+
HTTP/1.1 200 OK
|
2
|
+
Server: nginx/0.6.39
|
3
|
+
Date: Fri, 03 Dec 2010 21:49:00 GMT
|
4
|
+
Content-Type: application/xml; charset=utf-8
|
5
|
+
Connection: keep-alive
|
6
|
+
Keep-Alive: timeout=20
|
7
|
+
Status: 200 OK
|
8
|
+
ETag: "mock_etag"
|
9
|
+
X-Runtime: 45
|
10
|
+
Cache-Control: private, max-age=0, must-revalidate
|
11
|
+
|
12
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
13
|
+
<business_result>
|
14
|
+
<original_request>
|
15
|
+
<id>1234</id>
|
16
|
+
</original_request>
|
17
|
+
<request_id>mock_rid</request_id>
|
18
|
+
<business id="1234">
|
19
|
+
<description>
|
20
|
+
<general_info><p>A Paint Your Own Pottery Studios..</p></general_info>
|
21
|
+
<payment_text>DISCOVER, AMEX, VISA, MASTERCARD</payment_text>
|
22
|
+
<slogan><p>Pottery YOU dress up</p></slogan>
|
23
|
+
<additional_urls>
|
24
|
+
<additional_url>
|
25
|
+
<destination>http://example.com</destination>
|
26
|
+
<url_click>http://example.com</url_click>
|
27
|
+
</additional_url>
|
28
|
+
</additional_urls>
|
29
|
+
<op_hours>Fri 1pm-7pm, Sat 10am-6pm, Sun 1pm-4pm, Appointments Available</op_hours>
|
30
|
+
</description>
|
31
|
+
<improvable type="boolean">true</improvable>
|
32
|
+
<longitude type="float">-85.759586</longitude>
|
33
|
+
<headings>
|
34
|
+
<heading>Pottery</heading>
|
35
|
+
</headings>
|
36
|
+
<website>http://example.com</website>
|
37
|
+
<listing_type>free</listing_type>
|
38
|
+
<listing_id>1234</listing_id>
|
39
|
+
<phone>6168055326</phone>
|
40
|
+
<address>3845 Rivertown Pkwy SW Ste 500</address>
|
41
|
+
<mappable type="boolean">true</mappable>
|
42
|
+
<rating_count type="integer">0</rating_count>
|
43
|
+
<rateable type="boolean">true</rateable>
|
44
|
+
<impression_id>mock_iid</impression_id>
|
45
|
+
<zip>49418</zip>
|
46
|
+
<red_listing type="boolean">false</red_listing>
|
47
|
+
<omit_phone type="boolean">false</omit_phone>
|
48
|
+
<state>MI</state>
|
49
|
+
<distance type="float">0.0</distance>
|
50
|
+
<year_established>1996</year_established>
|
51
|
+
<city>Grandville</city>
|
52
|
+
<name>Naked Plates</name>
|
53
|
+
<latitude type="float">42.882561</latitude>
|
54
|
+
<omit_address type="boolean">false</omit_address>
|
55
|
+
<id>1234</id>
|
56
|
+
<has_detail_page type="boolean">true</has_detail_page>
|
57
|
+
</business>
|
58
|
+
</business_result>
|