【问题标题】:Sinatra helper to fake a requestSinatra 助手伪造请求
【发布时间】:2011-04-28 20:42:36
【问题描述】:

总结

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

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 不同,因为该(旧)问题的已接受答案不会维持用户的会话。

【问题讨论】:

    标签: ruby sinatra


    【解决方案1】:

    我使用的代码比answer in the Sinatra 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
    

    【讨论】:

      【解决方案2】:

      在 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
      

      【讨论】:

        【解决方案3】:

        感觉好像我错过了你想要做的事情,但为什么不直接调用定义的方法呢​​?

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

        顺便说一句,这是一个时髦的方法名称。不要问我为什么允许这样做。

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

        【讨论】:

        • @Jonas 与 Ramaze 不同,Sinatra 没有为每条路由定义一个方法(AFAIK)。运行上面以require "sinatra" 为前缀的代码并请求“/bar”,您将得到NoMethodError - undefined method 'GET /foo' for #<Sinatra::Application:0x2e31640>
        • 它确实可以在我的机器上运行。我现在正在将 Sinatra 更新到最新版本,看看是否有任何变化。
        • 我的gem 命令出了点问题,因为它仍在尝试sudo gem install sinatra。我又看了一下源码,我不得不承认我在跟踪它时遇到了一些麻烦,而且看起来所有的路由都是作为方法添加的。
        • @Jonas 是的,我在代码中看到了相同的外观。但是,写get('/methods'){ self.methods.sort.join("<br>") } 并请求它,根本没有带有“GET”的方法。
        • @Jonas Pity;我对这个解决方案的简单性感到兴奋,因为这就是我过去在 Ramaze 下做同样事情的方式。我添加了一个似乎有效的答案,在维护会话的同时在内部欺骗请求。可悲的是,我对 Sinatra 或 Rack 的内部没有足够的经验,不知道我是否应该为速度或正确性保留更多的环境。但至少它起作用了。
        猜你喜欢
        • 2021-10-17
        • 2014-06-21
        • 1970-01-01
        • 2012-04-17
        • 2012-08-14
        • 2013-02-28
        • 2013-07-17
        • 2011-02-13
        • 2014-08-31
        相关资源
        最近更新 更多