Skip to content

Instantly share code, notes, and snippets.

@godfat
Last active December 17, 2015 21:39
Show Gist options
  • Save godfat/5676447 to your computer and use it in GitHub Desktop.
Save godfat/5676447 to your computer and use it in GitHub Desktop.
require 'rest-core'
module RestCore
middleware = Class.new
middleware.module_eval do
def initialize app
@app = app
end
def call env, &k
puts "Calling"
@app.call(env, &k)
end
end
Circular = Builder.client do
use middleware
end
Circular::Middleware = middleware
end
puts RC::Circular
puts RC::Circular::Middleware
puts RC::Circular.new.head('http://example.com/')
require 'rest-core'
module RestCore
middleware = Class.new
middleware.module_eval do
def initialize app
@app = app
end
def call env, &k
puts env['secret']
@app.call(env, &k)
end
end
Secret = Builder.client(:secret) do
use middleware
end
Secret::Middleware = middleware
end
puts RC::Secret.new(:secret => 'mission').head('http://example.com/')
require 'rest-core/middleware'
require 'rest-core/util/hmac'
class RestCore::Signature
def self.members; [:secret]; end
include RestCore::Middleware
def call env, &k
uri = request_uri(env)
sig = Hmac.sha1(secret(env), uri)
app.call(env.merge(REQUEST_QUERY => {},
REQUEST_PATH => "#{uri}&Signature=#{sig}", &k))
end
end
@lulalala
Copy link

有點不了解 request_uri ,自己 debug 時有看到回傳只有 query param,也有看到整個完整 url 的情形。是為什麼呢?

@godfat
Copy link
Author

godfat commented May 31, 2013

可能是因為 DefaultSite?
DefaultSite 的行為是調整 REQUEST_PATH, 經過或還沒經過 DefaultSite 會影響 REQUEST_PATH
https://github.com/cardinalblue/rest-core/blob/master/lib/rest-core/middleware/default_site.rb#L8-L16

  def call env, &k
    path = if env[REQUEST_PATH].to_s.start_with?('http')
             env[REQUEST_PATH]
           else
             "#{site(env)}#{env[REQUEST_PATH]}"
           end

    app.call(env.merge(REQUEST_PATH => path), &k)
  end

同時 request_uri 會把 REQUEST_PATHREQUEST_QUERY 串起來:
https://github.com/cardinalblue/rest-core/blob/master/lib/rest-core/middleware.rb#L55-L64

  def request_uri env
    # compacting the hash
    if (query = (env[REQUEST_QUERY] || {}).select{ |k, v| v }).empty?
      env[REQUEST_PATH].to_s
    else
      q = if env[REQUEST_PATH] =~ /\?/ then '&' else '?' end
      "#{env[REQUEST_PATH]}#{q}#{percent_encode(query)}"
    end
  end
  public :request_uri

@lulalala
Copy link

了解,因為SHA1得套在query param,我來改改看用 percent_encode 然後手動 append 到後面試試看。

@godfat
Copy link
Author

godfat commented May 31, 2013

應該是可以直接用 request_uri, 最後再 append 上去即可,除非他要的不是直接的 uri 而是別的。不過你上次給我看的版本是直接用,所以照 signature.rb 那個應該是可以?

@lulalala
Copy link

規格是所有 query parameter 集合起來取 SHA1(也就是 "a=1&b=2" 這字串作 SHA1)

我好像做好了,不過有個地方會有問題(secret 只抓到 nil)

https://gist.github.com/lulalala/8640d4db131ad46f8b63

@godfat
Copy link
Author

godfat commented May 31, 2013

喔喔,我忘記 query 不包含 path...

你碰到的 nil 正是我昨天修的 bug.
其實 Builder.client(:api_key) 那邊不用給 :secret,
因為他會自動去 middleware 裡面撈出來用
這樣給反而是重複了。新版中,修正這個問題,
就算重複也不致於產生 nil

@godfat
Copy link
Author

godfat commented May 31, 2013

@lulalala
Copy link

lulalala commented Jun 3, 2013

嗨抱歉週末沒有看。
剛剛我試了一下,好像還是 nil。
喔還有 irb 在下這段指令過後,
過一段時間�就會突然冒出 ruby-2.0.0-p0/lib/ruby/2.0.0/irb/input-method.rb:152:in `readline': execution expired (Timeout::Error)
不知道你有遇過嗎?

@godfat
Copy link
Author

godfat commented Jun 3, 2013

看了一下,發現因為少打一個 *, 在 Client.new 那邊誤傳了 array 進去

def self.new *args, &block
  Client.new *args, &block
  #          ^^
end

因此 api_keysecret 沒設進去。改成這樣可動:

c = RestCore::YahooBuy.new
c.api_key = 'api_key'
c.secret  = 'secret'

我自己是都用 rib, 目前沒碰過那個狀況
我試試看能不能在 irb 下重現?看起來不應該有這個錯

@godfat
Copy link
Author

godfat commented Jun 3, 2013

你在什麼情況下會碰到?試了一下試不出來

@lulalala
Copy link

lulalala commented Jun 4, 2013

啊謝謝,我碰到的情況是忘了用 * 號時,c.get('getCurrTime')報錯後,console我就有五秒鐘可以作任何事,之後就突然冒出這訊息結束 console。

2.0.0-p0 :003 > c.get('getCurrTime')TypeError: no implicit conversion of nil into String
    from /Code/rest-more/rest-core/lib/rest-core/util/hmac.rb:17:in `digest'
...
    from /.rvm/rubies/ruby-2.0.0-p0/bin/irb:16:in `<main>'
2.0.0-p0 :004 > a = 1
 => 1
2.0.0-p0 :005 > b = 2
 => 2
2.0.0-p0 :006 > c = 3
 => 3
2.0.0-p0 :007 > /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/input-method.rb:152:in `readline': execution expired (Timeout::Error)
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/input-method.rb:152:in `gets'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb.rb:472:in `block (2 levels) in eval_input'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb.rb:624:in `signal_status'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb.rb:471:in `block in eval_input'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:190:in `call'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:190:in `buf_input'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:105:in `getc'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/slex.rb:206:in `match_io'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/slex.rb:76:in `match'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:290:in `token'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:266:in `lex'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:237:in `block (2 levels) in each_top_level_statement'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:233:in `loop'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:233:in `block in each_top_level_statement'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:232:in `catch'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:232:in `each_top_level_statement'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb.rb:488:in `eval_input'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb.rb:397:in `block in start'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb.rb:396:in `catch'
    from /.rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb.rb:396:in `start'
    from /.rvm/rubies/ruby-2.0.0-p0/bin/irb:16:in `<main>'

@godfat
Copy link
Author

godfat commented Jun 4, 2013

我知道是什麼原因了 O_o
這跟 irb 或 rib 無關,應該也算是 bug 吧 @@"
是我的 timeout 實作造成的
仔細想想這樣做是不太好...
我想想看要怎麼修比較好

@lulalala
Copy link

lulalala commented Jun 4, 2013

嗯辛苦了XD
想問一下概念問題,可以看看我的理解有沒有錯誤嗎:

middleware 的 members 有兩個功能,首先是用來在 Client Class 設立時預備 middleware 層級的變數用(如預設 base url)。不過他也有第二個功能,是開個介面能去抓 env 裡面的變數,所以像是 secret 這類 instance 層級的東西還是需要設立 members才有方便的介面跟env取值。

Builder.client() 裡面要丟進去的 symbols 那部份我讀不太懂。是不是代表宣告 client instance variable呢?要產生新的 client instance 時用 hash 丟這些參數進去就會存到特殊的地方而不是當作 query param,如 RC::Facebook 的 app_id?

這也讓我我不懂為何 Yahoo的 secret 不能放在 client builder 裡面,這是 client instance 層級的變數,在 Client.new 時傳入(跟 api_key 是一對的)。

謝謝解惑~

@lulalala
Copy link

lulalala commented Jun 4, 2013

啊然後想問,要是得把 api_key放在每個 request 中,這是那個 default_payload 還是 default_query 的範圍嗎?

@godfat
Copy link
Author

godfat commented Jun 4, 2013

對,一方面給一些方便的 methods 可用,另一方面則是讓 client 也可以記得一份可改變的設定(變數)

對,Builder.client 給的參數是額外需要記得的變數,確實如 RC::Facebookapp_id, 不過這裡並沒有例外,這個 app_id 也同樣會傳到 env 裡面,只是 middleware 裡面可能不會用到而已。

意思就是,在之前的版本裡(現已修好),如果 Builder.client 和 middleware 都給 secret, 則 client 實際上會有兩個 secret (!) 修正好的版本在所有的 attributes 中取 uniq, 因此 secret 會只剩一個。

這樣並不會造成任何問題(在新的版本裡),只是沒有意義而已。也可以寫成:

class Signature
  def initialize app; @app = app; end
  def call env, &k
    puts env['secret']
    app.call(&k)
  end
end
Client = RC::Builder.client(:secret){ use Signature }

也就是說,middleware 裡面就不自己記得一份 secret 了,一律靠 client 傳過去。這樣做的缺點是,使用 middleware 的人要自己記得這個 middleware 可能需要一份 secret 傳進去。

至於 api_key 如果要放在每個 request 中,那取決於要放在 query 或是 payload 甚或 headers 裡,
我會用對應的 middleware 並定義 default_query. 範例:

Client = RC::Builder.client(:api_key){ use RC::DefaultQuery }
class Client
  def default_query
    {:api_key => api_key}
  end
end

這樣 client 可記得 api_key, 同時會自動夾在 query 裡面。payload 或 headers 亦然。

@lulalala
Copy link

lulalala commented Jun 5, 2013

請問一下,你說的 dry run是原本就會丟出 timeout 囉?

2.0.0-p0 :002 > client = RC::Facebook.new
 => #<struct RestCore::Facebook timeout=10, site="https://gra..., headers={"Accept"=>"..., access_token=nil, log_method=nil, cache=nil, expires_in=600, error_handler=#<Proc:0x007..., error_detector=#<Proc:0x007..., json_response=true, defaults={:old_site=>..., data={}, app_id=nil, secret=nil, old_site="https://api...>
2.0.0-p0 :003 > client.request_full({RC::REQUEST_PATH => '/'}, client.dry)
 => {"REQUEST_METHOD"=>:get, "REQUEST_PATH"=>"https://graph.facebook.com//", "REQUEST_QUERY"=>{}, "REQUEST_PAYLOAD"=>{}, "REQUEST_HEADERS"=>{"Accept"=>"application/json", "Accept-Language"=>"en-us"}, "core.fail"=>[], "core.log"=>[#<struct RestCore::Event::Requested duration=0.010871, message="GET https://graph.facebook.com//">], "timeout"=>10, "site"=>"https://graph.facebook.com/", "headers"=>{"Accept"=>"application/json", "Accept-Language"=>"en-us"}, "access_token"=>nil, "log_method"=>nil, "cache"=>nil, "expires_in"=>600, "error_handler"=>#<Proc:0x007fd739686420@Code/rest-more/lib/rest-core/client/facebook.rb:18 (lambda)>, "error_detector"=>#<Proc:0x007fd7396864c0@Code/rest-more/lib/rest-core/client/facebook.rb:19 (lambda)>, "json_response"=>true, "defaults"=>{:old_site=>"https://api.facebook.com/"}, "data"=>{}, "app_id"=>nil, "secret"=>nil, "old_site"=>"https://api.facebook.com/", "async.callback"=>false, "async.timer"=>#<RestCore::Timeout::TimerThread:0x007fd7396a6dd8 @timeout=10, @error=#<Timeout::Error: execution expired>, @block=#<Proc:0x007fd7396a6d88@Code/rest-more/rest-core/lib/rest-core/middleware/timeout/timer_thread.rb:9 (lambda)>, @canceled=false, @thread=#<Thread:0x007fd7396a6d60 sleep>>, "RESPONSE_BODY"=>nil}
2.0.0-p0 :004 > .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/input-method.rb:152:in `readline': execution expired (Timeout::Error)
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/input-method.rb:152:in `gets'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb.rb:472:in `block (2 levels) in eval_input'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb.rb:624:in `signal_status'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb.rb:471:in `block in eval_input'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:190:in `call'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:190:in `buf_input'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:105:in `getc'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/slex.rb:206:in `match_io'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/slex.rb:76:in `match'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:290:in `token'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:266:in `lex'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:237:in `block (2 levels) in each_top_level_statement'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:233:in `loop'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:233:in `block in each_top_level_statement'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:232:in `catch'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb/ruby-lex.rb:232:in `each_top_level_statement'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb.rb:488:in `eval_input'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb.rb:397:in `block in start'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb.rb:396:in `catch'
    from .rvm/rubies/ruby-2.0.0-p0/lib/ruby/2.0.0/irb.rb:396:in `start'
    from .rvm/rubies/ruby-2.0.0-p0/bin/irb:16:in `<main>'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment