Sinatra帮助假冒请求

摘要

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

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

…当使用Web浏览器请求“/ bar”时,应该产生响应“foo – bar”。

动机

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

用户可以交互地编辑该页面上的各个数据。 使用我的AJAXFetch jQuery插件,JavaScript使用AJAX交换页面的只读部分(例如,将此bug分配给的人的名称),使用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处理当前请求的路由,而不是指定的路径。 (这导致无限递归,直到堆栈级别最终达到最低点。)

这个问题与Sinatra内部的呼叫Sinatra不同,因为对该(旧)问题的接受答案不能维持用户的会话。

我使用的代码比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 

以下似乎在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, eg # # # 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 

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

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

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

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