kronk 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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>
|