4 # sudo gem install sinatra liquid
6 # ruby examples/sinatra/buzz_api.rb
8 root_dir = File.expand_path("../../..", __FILE__)
9 lib_dir = File.expand_path("./lib", root_dir)
11 $LOAD_PATH.unshift(lib_dir)
18 require 'signet/oauth_1/client'
19 require 'google/api_client'
21 STDERR.puts "Missing dependencies."
22 STDERR.puts "sudo gem install sinatra liquid signet google-api-client"
29 /* http://meyerweb.com/eric/tools/css/reset/ */
32 html, body, div, span, applet, object, iframe,
33 h1, h2, h3, h4, h5, h6, p, blockquote, pre,
34 a, abbr, acronym, address, big, cite, code,
35 del, dfn, em, font, img, ins, kbd, q, s, samp,
36 small, strike, strong, sub, sup, tt, var,
38 dl, dt, dd, ol, ul, li,
39 fieldset, form, label, legend,
40 table, caption, tbody, tfoot, thead, tr, th, td {
46 vertical-align: baseline;
47 background: transparent;
58 blockquote:before, blockquote:after,
64 /* remember to define focus styles! */
69 /* remember to highlight inserts somehow! */
71 text-decoration: none;
74 text-decoration: line-through;
77 /* tables still need 'cellspacing="0"' in the markup */
79 border-collapse: collapse;
87 background-color: #ffffff;
88 font-family: 'Helvetica', 'Arial', sans-serif;
100 font-family: 'Helvetica', 'Arial', sans-serif;
107 font-variant: normal;
109 font-family: 'Monaco', 'Andale Mono', 'Consolas', 'Inconsolata', 'Courier New', monospace;
114 letter-spacing: 0.1em;
115 text-transform: none;
116 text-shadow: rgba(204, 204, 204, 0.75) 0px 1px 0px;
120 font-variant: normal;
126 #output h3:first-child {
139 form label, form input, form textarea {
140 font-family: 'Monaco', 'Andale Mono', 'Consolas', 'Inconsolata', 'Courier New', monospace;
159 font-family: 'Monaco', 'Andale Mono', 'Consolas', 'Inconsolata', 'Courier New', monospace;
160 display: inline-block;
163 border: 1px dotted #555555;
173 text-decoration: none;
174 border-bottom: 1px dotted rgba(112, 56, 56, 0.0);
177 -webkit-transition: all 0.3s linear;
179 border-bottom: 1px dotted rgba(112, 56, 56, 1.0);
182 border-bottom: 1px dotted rgba(0, 0, 0, 1.0);
200 JAVASCRIPT = <<-JAVASCRIPT
201 var uriTimeout = null;
202 $(document).ready(function () {
204 var rpcName = $('#rpc-name').text().trim();
205 var serviceId = $('#service-id').text().trim();
206 var getParameters = function() {
208 var fields = $('.parameter').parents('li');
209 for (var i = 0; i < fields.length; i++) {
210 var input = $(fields[i]).find('input');
211 var label = $(fields[i]).find('label');
212 if (input.val() && input.val() != "") {
213 parameters[label.text()] = input.val();
218 var updateOutput = function (event) {
219 var request = $('#request').text().trim();
220 var response = $('#response').text().trim();
221 if (request != '' || response != '') {
227 var handleUri = function (event) {
230 clearTimeout(uriTimeout);
232 uriTimeout = setTimeout(function () {
234 "url": "/template/" + serviceId + "/" + rpcName + "/",
235 "data": getParameters(),
238 "success": function (data, textStatus, xhr) {
240 if (textStatus == 'success') {
241 $('#uri-template').html(data);
243 clearTimeout(uriTimeout);
250 var getResponse = function (event) {
252 "url": "/response/" + serviceId + "/" + rpcName + "/",
254 "data": getParameters(),
257 "success": function (data, textStatus, xhr) {
258 if (textStatus == 'success') {
259 $('#response').text(data);
265 var getRequest = function (event) {
267 "url": "/request/" + serviceId + "/" + rpcName + "/",
269 "data": getParameters(),
272 "success": function (data, textStatus, xhr) {
273 if (textStatus == 'success') {
274 $('#request').text(data);
281 var transmit = function (event) {
282 $('#request').html('');
283 $('#response').html('');
288 $('form').submit(function (event) { event.preventDefault(); });
289 $('button').click(transmit);
290 $('.parameter').keyup(handleUri);
291 $('.parameter').blur(handleUri);
296 @client ||= Google::APIClient.new(
298 :authorization => Signet::OAuth1::Client.new(
299 :temporary_credential_uri =>
300 'https://www.google.com/accounts/OAuthGetRequestToken',
301 :authorization_uri =>
302 'https://www.google.com/buzz/api/auth/OAuthAuthorizeToken',
303 :token_credential_uri =>
304 'https://www.google.com/accounts/OAuthGetAccessToken',
305 :client_credential_key => 'anonymous',
306 :client_credential_secret => 'anonymous'
311 def service(service_name, service_version)
312 unless service_version
313 service_version = client.latest_service_version(service_name).version
315 client.discovered_service(service_name, service_version)
318 get '/template/:service/:method/' do
319 service_name, service_version = params[:service].split("-", 2)
320 method = service(service_name, service_version).to_h[params[:method].to_s]
321 parameters = method.parameters.inject({}) do |accu, parameter|
322 accu[parameter] = params[parameter.to_sym] if params[parameter.to_sym]
325 uri = Addressable::URI.parse(
326 method.uri_template.partial_expand(parameters).pattern
328 template_variables = method.uri_template.variables
329 query_parameters = method.normalize_parameters(parameters).reject do |k, v|
330 template_variables.include?(k)
332 if query_parameters.size > 0
333 uri.query_values = (uri.query_values || {}).merge(query_parameters)
335 # Normalization is necessary because of undesirable percent-escaping
336 # during URI template expansion
337 return uri.normalize.to_s.gsub('%7B', '{').gsub('%7D', '}')
340 get '/request/:service/:method/' do
341 service_name, service_version = params[:service].split("-", 2)
342 method = service(service_name, service_version).to_h[params[:method].to_s]
343 parameters = method.parameters.inject({}) do |accu, parameter|
344 accu[parameter] = params[parameter.to_sym] if params[parameter.to_sym]
348 request = client.generate_request(
349 method, parameters.merge("pp" => "1"), body, [], {:signed => false}
351 method, uri, headers, body = request
352 merged_body = StringIO.new
358 #{method} #{uri} HTTP/1.1
360 #{(headers.map { |k,v| "#{k}: #{v}" }).join('\n')}
362 #{merged_body.string}
366 post '/response/:service/:method/' do
368 service_name, service_version = params[:service].split("-", 2)
369 method = service(service_name, service_version).to_h[params[:method].to_s]
370 parameters = method.parameters.inject({}) do |accu, parameter|
371 accu[parameter] = params[parameter.to_sym] if params[parameter.to_sym]
375 response = client.execute(
376 method, parameters.merge("pp" => "1"), body, [], {:signed => false}
378 status, headers, body = response
379 status_message = Rack::Utils::HTTP_STATUS_CODES[status.to_i]
380 merged_body = StringIO.new
386 #{status} #{status_message}
388 #{(headers.map { |k,v| "#{k}: #{v}" }).join("\n")}
390 #{merged_body.string}
394 get '/explore/:service/' do
395 service_name, service_version = params[:service].split("-", 2)
396 service_version = service(service_name, service_version).version
399 "service_name" => service_name,
400 "service_version" => service_version,
401 "methods" => service(service_name, service_version).to_h.keys.sort
403 Liquid::Template.parse(<<-HTML).render(variables)
407 <title>{{service_name}}</title>
408 <style type="text/css">
413 <h1>{{service_name}}</h1>
415 {% for method in methods %}
417 <a href="/explore/{{service_name}}-{{service_version}}/{{method}}/">
428 get '/explore/:service/:method/' do
429 service_name, service_version = params[:service].split("-", 2)
430 service_version = service(service_name, service_version).version
431 method = service(service_name, service_version).to_h[params[:method].to_s]
434 "javascript" => JAVASCRIPT,
435 "http_method" => (method.description['httpMethod'] || 'GET'),
436 "service_name" => service_name,
437 "service_version" => service_version,
438 "method" => params[:method].to_s,
439 "required_parameters" =>
440 method.required_parameters,
441 "optional_parameters" =>
442 method.optional_parameters.sort,
443 "template" => method.uri_template.pattern
445 Liquid::Template.parse(<<-HTML).render(variables)
449 <title>{{service_name}} - {{method}}</title>
450 <style type="text/css">
454 src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"
455 type="text/javascript">
457 <script type="text/javascript">
463 <a href="/explore/{{service_name}}-{{service_version}}/">
464 {{service_name}}-{{service_version}}
467 <h1 id="rpc-name">{{method}}</h1>
468 <h2>{{http_method}} <span id="uri-template">{{template}}</span></h2>
471 {% for parameter in required_parameters %}
473 <label for="param-{{parameter}}">{{parameter}}</label>
474 <input id="param-{{parameter}}" name="param-{{parameter}}"
475 class="parameter" type="text" />
478 {% for parameter in optional_parameters %}
480 <label for="param-{{parameter}}">{{parameter}}</label>
481 <input id="param-{{parameter}}" name="param-{{parameter}}"
482 class="parameter" type="text" />
485 {% if http_method != 'GET' %}
487 <label for="http-body">body</label>
488 <textarea id="http-body" name="http-body"></textarea>
492 <button>Transmit</button>
496 <pre id="request"></pre>
498 <pre id="response"></pre>
505 get '/favicon.ico' do
506 require 'httpadapter'
507 HTTPAdapter.transmit(
508 ['GET', 'http://www.google.com/favicon.ico', [], ['']],
509 HTTPAdapter::NetHTTPRequestAdapter