Sinatra帮助假冒请求

时间:2011-04-28 20:42:37

标签: ruby sinatra

摘要

Sinatra网络应用中,如何向应用程序发出虚拟请求并将响应正文作为文本返回?例如,这些路线......

get('/foo'){ "foo" }
get('/bar'){ "#{spoof_request '/foo'} - bar" }

...在使用网络浏览器请求“/ bar”时,应该会产生响应“foo - bar”。

动机

我的应用程序有一个代表错误条目的页面,其中包含有关该错误条目的大量详细信息:错误的版本是什么,它有多重要,哪些标记与之关联,错误分配给谁,等等

用户可以交互式编辑此页面上的各个数据。使用我的AJAXFetch jQuery插件,JavaScript使用AJAX交换页面的只读部分(例如,将此错误分配给的人的名称),使用HTML部分表单来编辑该部分。用户提交表单,AJAX对该字段的静态版本发出新请求。

为了成为DRY,我想要创建页面的Haml视图使用与创建单个静态片段时AJAX完全相同的请求。例如:

#notifications.section
  %h2 Email me if someone...
  .section-body= spoof_request "/partial/notifications/#{@bug.id}"

非正常工作代码

以下帮助定义spoof_request的助手在Sinatra 1.1.2下工作:

PATH_VARS = %w[ REQUEST_PATH PATH_INFO REQUEST_URI ]
def spoof_request( uri, headers=nil )
  new_env = env.dup 
  PATH_VARS.each{ |k| new_env[k] = uri.to_s } 
  new_env.merge!(headers) if headers
  call( new_env ).last.join 
end

然而,在Sinatra 1.2.3中,这不再适用。尽管将PATH_VARS中的每一个设置为所需的URI,但call( new_env )仍会导致Sinatra处理当前请求的路由,而不是指定的路径。 (这导致无限递归,直到堆栈级别最终达到最低点。)

此问题与Calling Sinatra from within Sinatra不同,因为该(旧)问题的已接受答案不会维护用户的会话。

3 个答案:

答案 0 :(得分:4)

我使用的代码比answer in the Sinatra README更复杂,但依赖于相同的机制。由于该版本中的错误,我的代码和README的答案都没有在1.2.3下工作。两者现在都在1.2.6下工作。

这是一个有效的简单助手的测试案例:

require 'sinatra'
helpers do
  def local_get(url)
    call(env.merge("PATH_INFO" => url)).last.join
  end
end
get("/foo"){ "foo - #{local_get '/bar'}" }
get("/bar"){ "bar" }

行动中:

phrogz$ curl http://localhost:4567/foo
foo - bar

答案 1 :(得分:1)

以下似乎可以在Sinatra 1.2.3下使用:

ENV_COPY  = %w[ REQUEST_METHOD HTTP_COOKIE rack.request.cookie_string
                rack.session rack.session.options rack.input]

# Returns the response body after simulating a request to a particular URL
# Maintains the session of the current user.
# Pass custom headers if you want to set or change them, e.g.
#
#  # Spoof a GET request, even if we happen to be inside a POST
#  html = spoof_request "/partial/assignedto/#{@bug.id}", 'REQUEST_METHOD'=>'GET'
def spoof_request( uri, headers=nil )
  new_env = env.slice(*ENV_COPY).merge({
    "PATH_INFO"    => uri.to_s,
    "HTTP_REFERER" => env["REQUEST_URI"]
  })
  new_env.merge!(headers) if headers
  call( new_env ).last.join 
end

其中Hash#slice定义为:

class Hash
  def slice(*keys)
    {}.tap{ |h| keys.each{ |k| h[k] = self[k] } }
  end
end

答案 2 :(得分:1)

感觉我错过了你想要做的事,但为什么不调用定义的方法?

get('/foo'){ "foo" }
get('/bar'){ "#{self.send("GET /foo")} - bar" }

顺便提一下,这是一个时髦的方法名称。不要问我为什么甚至允许它。

PS。这仅适用于1.2.3之前的版本。 DS。