Ruby 从Sinatra内部呼叫Sinatra

Ruby 从Sinatra内部呼叫Sinatra,ruby,sinatra,Ruby,Sinatra,我有一个基于Sinatra的REST服务应用程序,我想从一个路由中调用其中一个资源,有效地将一个资源与另一个资源组合起来。例如 get '/someresource' do otherresource = get '/otherresource' # do something with otherresource, return a new resource end get '/otherresource' do # etc. end 重定向将不起作用,因为我需要对第二个资源进行

我有一个基于Sinatra的REST服务应用程序,我想从一个路由中调用其中一个资源,有效地将一个资源与另一个资源组合起来。例如

get '/someresource' do
  otherresource = get '/otherresource'
  # do something with otherresource, return a new resource
end

get '/otherresource' do
  # etc.
end

重定向将不起作用,因为我需要对第二个资源进行一些处理并从中创建一个新的资源。显然,我可以a)使用RestClient或其他客户机框架,或者b)构造我的代码,使otherresource的所有逻辑都在一个方法中,只需调用该方法,如果我可以使用Sinatra的DSL重新使用Sinatra内部的资源,感觉会更干净。

我可以通过快速而肮脏的机架请求并直接调用Sinatra(机架应用程序)应用程序来破解一些东西。虽然不漂亮,但它很管用。请注意,最好将生成此资源的代码提取到helper方法中,而不是执行类似的操作。但这是可能的,而且可能有比这更好、更干净的方法

#!/usr/bin/env ruby
require 'rubygems'
require 'stringio'
require 'sinatra'

get '/someresource' do
  resource = self.call(
    'REQUEST_METHOD' => 'GET',
    'PATH_INFO' => '/otherresource',
    'rack.input' => StringIO.new
  )[2].join('')

  resource.upcase
end

get '/otherresource' do
  "test"
end

如果你想知道更多关于幕后发生的事情,我已经写了一些关于Rack基础知识的文章,你可以阅读。有和。

这可能适用于您的情况,也可能不适用于您的情况,但当我需要创建这样的路线时,我通常会尝试以下方法:

%w(main other).each do |uri|
  get "/#{uri}" do
    @res = "hello"
    @res.upcase! if uri == "other"
    @res
  end
end
另一种选择(我知道这并没有回答您的实际问题)是将您的公共代码(甚至模板渲染)放在助手方法中,例如:

helpers do
  def common_code( layout = true )
    @title = 'common'
    erb :common, :layout => layout
  end
end

get '/foo' do
  @subtitle = 'foo'
  common_code
end

get '/bar' do
  @subtitle = 'bar'
  common_code
end

get '/baz' do
  @subtitle = 'baz'
  @common_snippet = common_code( false )
  erb :large_page_with_common_snippet_injected
end
在此基础上,我需要支持在
lib/public
中获取静态文件以及查询参数和cookie(用于维护经过身份验证的会话)。我还选择在非200响应上引发异常(并在调用函数中处理它们)

如果您在
Sinatra/base.rb
中跟踪Sinatra的
self.call
方法,它会获取一个
env
参数并使用它构建一个函数,因此您可以深入其中查看支持哪些参数

我不记得return语句的所有条件(我认为Ruby2做了一些更改),所以可以根据您的需求进行调整

下面是我正在使用的函数:

  def get_route url
    fn = File.join(File.dirname(__FILE__), 'public'+url)
    return File.read(fn) if (File.exist?fn)

    base_url, query = url.split('?')
    begin
      result = self.call('REQUEST_METHOD' => 'GET',
                         'PATH_INFO' => base_url,
                         'QUERY_STRING' => query,
                         'rack.input' => StringIO.new,
                         'HTTP_COOKIE' => @env['HTTP_COOKIE'] # Pass auth credentials
                         )
    rescue Exception=>e
      puts "Exception when fetching self route: #{url}"
      raise e
    end
    raise "Error when fetching self route: #{url}" unless result[0]==200 # status

    return File.read(result[2].path) if result[2].is_a? Rack::File
    return result[2].join('') rescue result[2].to_json
  end

Sinatra的文档涵盖了这一点-基本上,您使用底层
机架
接口的
调用
方法:

触发另一条路线 有时候,传球并不是你想要的 您希望获得调用其他路线的结果。简单使用 呼吁实现这一目标:


谢谢,比起使用RestClient,我更喜欢这个。使用这种技术的包装器方法,如local_get、local_post、local_put和local_delete,将是对Sinatra框架的一个很好的补充。这个答案不会保留环境,包括用户的会话。这里有一个类似的答案(但看起来更糟糕的黑客攻击)来保护环境:Ruby 2.1.0,sinatra 1.4.4,我得到了#
未定义的方法'join',啊,当结果是一个由
/public/
提供的文件时,显然会发生这种情况,解决方法是:
返回文件。如果结果[2],则读取(结果[2]。路径)。是不是?Rack::File
不是我想要的,但这是一种有用的技术。谢谢
get '/foo' do
  status, headers, body = call env.merge("PATH_INFO" => '/bar')
  [status, headers, body.map(&:upcase)]
end

get '/bar' do
  "bar"
end