diff --git a/examples/sinatra/explorer.rb b/examples/sinatra/explorer.rb new file mode 100644 index 000000000..fb0d74816 --- /dev/null +++ b/examples/sinatra/explorer.rb @@ -0,0 +1,509 @@ +#!/usr/bin/env ruby + +# INSTALL +# sudo gem install sinatra liquid +# RUN +# ruby examples/sinatra/buzz_api.rb + +root_dir = File.expand_path("../../..", __FILE__) +lib_dir = File.expand_path("./lib", root_dir) + +$LOAD_PATH.unshift(lib_dir) +$LOAD_PATH.uniq! + +require 'rubygems' +begin + require 'sinatra' + require 'liquid' + require 'signet/oauth_1/client' + require 'google/api_client' +rescue LoadError + STDERR.puts "Missing dependencies." + STDERR.puts "sudo gem install sinatra liquid signet google-api-client" + exit(1) +end + +CSS = <<-CSS +/* http://meyerweb.com/eric/tools/css/reset/ */ +/* v1.0 | 20080212 */ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, font, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td { + margin: 0; + padding: 0; + border: 0; + outline: 0; + font-size: 100%; + vertical-align: baseline; + background: transparent; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} + +/* remember to define focus styles! */ +:focus { + outline: 0; +} + +/* remember to highlight inserts somehow! */ +ins { + text-decoration: none; +} +del { + text-decoration: line-through; +} + +/* tables still need 'cellspacing="0"' in the markup */ +table { + border-collapse: collapse; + border-spacing: 0; +} + +/* End Reset */ + +body { + color: #555555; + background-color: #ffffff; + font-family: 'Helvetica', 'Arial', sans-serif; + font-size: 18px; + line-height: 27px; + padding: 27px 72px; +} +p { + margin-bottom: 27px; +} +h1 { + font-style: normal; + font-variant: normal; + font-weight: normal; + font-family: 'Helvetica', 'Arial', sans-serif; + font-size: 36px; + line-height: 54px; + margin-bottom: 0px; +} +h2 { + font-style: normal; + font-variant: normal; + font-weight: normal; + font-family: 'Monaco', 'Andale Mono', 'Consolas', 'Inconsolata', 'Courier New', monospace; + font-size: 14px; + line-height: 27px; + margin-top: 0px; + margin-bottom: 54px; + letter-spacing: 0.1em; + text-transform: none; + text-shadow: rgba(204, 204, 204, 0.75) 0px 1px 0px; +} +#output h3 { + font-style: normal; + font-variant: normal; + font-weight: bold; + font-size: 18px; + line-height: 27px; + margin: 27px 0px; +} +#output h3:first-child { + margin-top: 0px; +} +ul, ol, dl { + margin-bottom: 27px; +} +li { + margin: 0px 0px; +} +form { + float: left; + display: block; +} +form label, form input, form textarea { + font-family: 'Monaco', 'Andale Mono', 'Consolas', 'Inconsolata', 'Courier New', monospace; + display: block; +} +form label { + margin-bottom: 5px; +} +form input { + width: 300px; + font-size: 14px; + padding: 5px; +} +form textarea { + height: 150px; + min-height: 150px; + width: 300px; + min-width: 300px; + max-width: 300px; +} +#output { + font-family: 'Monaco', 'Andale Mono', 'Consolas', 'Inconsolata', 'Courier New', monospace; + display: inline-block; + margin-left: 27px; + padding: 27px; + border: 1px dotted #555555; + width: 1120px; + max-width: 100%; + min-height: 600px; +} +#output pre { + overflow: auto; +} +a { + color: #000000; + text-decoration: none; + border-bottom: 1px dotted rgba(112, 56, 56, 0.0); +} +a:hover { + -webkit-transition: all 0.3s linear; + color: #703838; + border-bottom: 1px dotted rgba(112, 56, 56, 1.0); +} +p a { + border-bottom: 1px dotted rgba(0, 0, 0, 1.0); +} +h1, h2 { + color: #000000; +} +h3, h4, h5, h6 { + color: #333333; +} +.block { + display: block; +} +button { + margin-bottom: 72px; + padding: 7px 11px; + font-size: 14px; +} +CSS + +JAVASCRIPT = <<-JAVASCRIPT + var uriTimeout = null; + $(document).ready(function () { + $('#output').hide(); + var rpcName = $('#rpc-name').text().trim(); + var serviceId = $('#service-id').text().trim(); + var getParameters = function() { + var parameters = {}; + var fields = $('.parameter').parents('li'); + for (var i = 0; i < fields.length; i++) { + var input = $(fields[i]).find('input'); + var label = $(fields[i]).find('label'); + if (input.val() && input.val() != "") { + parameters[label.text()] = input.val(); + } + } + return parameters; + } + var updateOutput = function (event) { + var request = $('#request').text().trim(); + var response = $('#response').text().trim(); + if (request != '' || response != '') { + $('#output').show(); + } else { + $('#output').hide(); + } + } + var handleUri = function (event) { + updateOutput(event); + if (uriTimeout) { + clearTimeout(uriTimeout); + } + uriTimeout = setTimeout(function () { + $.ajax({ + "url": "/template/" + serviceId + "/" + rpcName + "/", + "data": getParameters(), + "dataType": "text", + "ifModified": true, + "success": function (data, textStatus, xhr) { + updateOutput(event); + if (textStatus == 'success') { + $('#uri-template').html(data); + if (uriTimeout) { + clearTimeout(uriTimeout); + } + } + } + }); + }, 350); + } + var getResponse = function (event) { + $.ajax({ + "url": "/response/" + serviceId + "/" + rpcName + "/", + "type": "POST", + "data": getParameters(), + "dataType": "html", + "ifModified": true, + "success": function (data, textStatus, xhr) { + if (textStatus == 'success') { + $('#response').text(data); + } + updateOutput(event); + } + }); + } + var getRequest = function (event) { + $.ajax({ + "url": "/request/" + serviceId + "/" + rpcName + "/", + "type": "GET", + "data": getParameters(), + "dataType": "html", + "ifModified": true, + "success": function (data, textStatus, xhr) { + if (textStatus == 'success') { + $('#request').text(data); + updateOutput(event); + getResponse(event); + } + } + }); + } + var transmit = function (event) { + $('#request').html(''); + $('#response').html(''); + handleUri(event); + updateOutput(event); + getRequest(event); + } + $('form').submit(function (event) { event.preventDefault(); }); + $('button').click(transmit); + $('.parameter').keyup(handleUri); + $('.parameter').blur(handleUri); + }); +JAVASCRIPT + +def client + @client ||= Google::APIClient.new( + :service => 'buzz', + :authorization => Signet::OAuth1::Client.new( + :temporary_credential_uri => + 'https://www.google.com/accounts/OAuthGetRequestToken', + :authorization_uri => + 'https://www.google.com/buzz/api/auth/OAuthAuthorizeToken', + :token_credential_uri => + 'https://www.google.com/accounts/OAuthGetAccessToken', + :client_credential_key => 'anonymous', + :client_credential_secret => 'anonymous' + ) + ) +end + +def service(service_name, service_version) + unless service_version + service_version = client.latest_service(service_name).version + end + client.discovered_service(service_name, service_version) +end + +get '/template/:service/:method/' do + service_name, service_version = params[:service].split("-", 2) + method = service(service_name, service_version).to_h[params[:method].to_s] + parameters = method.parameters.inject({}) do |accu, parameter| + accu[parameter] = params[parameter.to_sym] if params[parameter.to_sym] + accu + end + uri = Addressable::URI.parse( + method.uri_template.partial_expand(parameters).pattern + ) + template_variables = method.uri_template.variables + query_parameters = method.normalize_parameters(parameters).reject do |k, v| + template_variables.include?(k) + end + if query_parameters.size > 0 + uri.query_values = (uri.query_values || {}).merge(query_parameters) + end + # Normalization is necessary because of undesirable percent-escaping + # during URI template expansion + return uri.normalize.to_s.gsub('%7B', '{').gsub('%7D', '}') +end + +get '/request/:service/:method/' do + service_name, service_version = params[:service].split("-", 2) + method = service(service_name, service_version).to_h[params[:method].to_s] + parameters = method.parameters.inject({}) do |accu, parameter| + accu[parameter] = params[parameter.to_sym] if params[parameter.to_sym] + accu + end + body = '' + request = client.generate_request( + method, parameters.merge("pp" => "1"), body, [], {:signed => false} + ) + method, uri, headers, body = request + merged_body = StringIO.new + body.each do |chunk| + merged_body << chunk + end + merged_body.rewind + <<-REQUEST.strip +#{method} #{uri} HTTP/1.1 + +#{(headers.map { |k,v| "#{k}: #{v}" }).join('\n')} + +#{merged_body.string} +REQUEST +end + +post '/response/:service/:method/' do + require 'rack/utils' + service_name, service_version = params[:service].split("-", 2) + method = service(service_name, service_version).to_h[params[:method].to_s] + parameters = method.parameters.inject({}) do |accu, parameter| + accu[parameter] = params[parameter.to_sym] if params[parameter.to_sym] + accu + end + body = '' + response = client.execute( + method, parameters.merge("pp" => "1"), body, [], {:signed => false} + ) + status, headers, body = response + status_message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] + merged_body = StringIO.new + body.each do |chunk| + merged_body << chunk + end + merged_body.rewind + <<-RESPONSE.strip +#{status} #{status_message} + +#{(headers.map { |k,v| "#{k}: #{v}" }).join("\n")} + +#{merged_body.string} +RESPONSE +end + +get '/explore/:service/' do + service_name, service_version = params[:service].split("-", 2) + service_version = service(service_name, service_version).version + variables = { + "css" => CSS, + "service_name" => service_name, + "service_version" => service_version, + "methods" => service(service_name, service_version).to_h.keys.sort + } + Liquid::Template.parse(<<-HTML).render(variables) + + + + {{service_name}} + + + +

{{service_name}}

+ + + + HTML +end + +get '/explore/:service/:method/' do + service_name, service_version = params[:service].split("-", 2) + service_version = service(service_name, service_version).version + method = service(service_name, service_version).to_h[params[:method].to_s] + variables = { + "css" => CSS, + "javascript" => JAVASCRIPT, + "http_method" => (method.description['httpMethod'] || 'GET'), + "service_name" => service_name, + "service_version" => service_version, + "method" => params[:method].to_s, + "required_parameters" => + method.required_parameters, + "optional_parameters" => + method.optional_parameters.sort, + "template" => method.uri_template.pattern + } + Liquid::Template.parse(<<-HTML).render(variables) + + + + {{service_name}} - {{method}} + + + + + +

+ + {{service_name}}-{{service_version}} + +

+

{{method}}

+

{{http_method}} {{template}}

+
+ + +
+
+

Request

+

+      

Response

+

+    
+ + + HTML +end + +get '/favicon.ico' do + require 'httpadapter' + HTTPAdapter.transmit( + ['GET', 'http://www.google.com/favicon.ico', [], ['']], + HTTPAdapter::NetHTTPRequestAdapter + ) +end