【问题标题】:How to turn a config.ru file into a single Rack application?如何将 config.ru 文件转换为单个 Rack 应用程序?
【发布时间】:2012-07-15 19:01:17
【问题描述】:

我的 config.ru 文件开始出现重复代码:

map '/route1' do
  run SampleApp.new
end

map '/route2' do
  run SampleApp.new
end

我想把这个 config.ru 文件变成它自己的 Rack 应用程序,所以我要做的就是:

map '/' do
  run MyApp.new
end

创建自己的 Rack 应用程序的正确方法是什么?具体来说,如何创建一个类,以便我可以在我的类中使用map 方法来定义一堆路由?


解决方案:

这是一个可行的解决方案:

class MyApp

  def initialize
    @app = Rack::Builder.new do
      # copy contents of your config.ru into this block
      map '/route1' do
        run SampleApp.new
      end

      map '/route2' do
        run SampleApp.new
      end
    end
  end

  def call(env)
    @app.call(env)
  end
end

我之前尝试过这个,但由于我试图将实例变量传递给 map 块,所以无法让它工作。例如:

def initialize
  @sample_app = SampleApp.new
  @app = Rack::Builder.new do
    map '/route1' do
      run @sample_app   # will not work
    end
  end
end

这不起作用的原因是传递给map的块是evaluated in the context of a Rack::Builder instance

但是,如果我传递一个局部变量,它将起作用:

def initialize
  sample_app = SampleApp.new
  @app = Rack::Builder.new do
    map '/route1' do
      run sample_app   # will work
    end
  end
end

【问题讨论】:

  • 只需在#call 方法中检查请求路径并添加条件语句
  • @injekt 你能提供一个例子吗?
  • 在我的回答中查看我的示例,如果有帮助,我可以添加更多内容,但这是最基本的

标签: ruby rack


【解决方案1】:

这是一个真的基本示例。您可能应该查看 Rack::Response 来处理响应而不是自己构建它,但它可以让您很好地了解基本 Rack 中间件的工作原理:

class MyApp
  def call(env)
    request = Rack::Request.new(env)
    headers = { 'Content-Type' => 'text/html' }

    case request.path
    when '/'
      [200, headers, ["You're at the root url!"]]
    when '/foo'
      [200, headers, ["You're at /foo!"]]
    else
      [404, headers, ["Uh oh, path not found!"]]
    end
  end
end

编辑:

将多个 Rack 应用程序合二为一:

class RootApp
  def call(env)
    [200, {'Content-Type' => 'text/html' }, ['Main root url']]
  end
end

class FooApp
  def call(env)
    [200, {'Content-Type' => 'text/html' }, ['Foo app url!']]
  end
end

class MyApp
  def initialize
    @apps = {}
  end

  def map(route, app)
    @apps[route] = app
  end

  def call(env)
    request = Rack::Request.new(env)

    if @apps[request.path]
      @apps[request.path].call(env)
    else
      [404, {'Content-Type' => 'text/html' }, ['404 not found']]
    end
  end
end

app = MyApp.new
app.map '/', RootApp.new
app.map '/foo', FooApp.new

run app

【讨论】:

  • 嗯...这很有帮助。但是,我需要在定义的每个路径上运行一个 Rack 应用程序。所以我认为这个解决方案不会奏效。你知道我该怎么做吗?
  • 那越来越近了。但是,马特的答案更接近我正在寻找的东西。请参阅my comment 他的回答
【解决方案2】:

我这样做:

class MyApp 
  def call(env)
    @env = env

    # REQUEST_URI is still encoded; split before decoding to allow encoded slashes
    @path = env['REQUEST_URI'].split('/')

    # REQUEST_URI starts with a slash, so delete blank first element
    @path.delete_at(0)

    @path.each_index do |i|
      @path[i]= CGI.unescape(@path[i])
    end

    route()
  end
end

然后route() 可以做任何它想要路由请求的事情,例如:

class MyApp 
  def route
    m = @env['REQUEST_METHOD']
    @section = @path.shift

    if not @section
      home()
    elsif @section == 'route1' and m == 'GET'
      route1()
    # else ...
    end
  end
end

【讨论】:

  • 我会推荐 Rack::Request 而不是自己拆分信息。它会处理所有负载并且看起来更好。
  • @injekt 呵呵,我刚刚浏览了the docs,不知道它会如何在这里发挥作用。当我需要处理查询字符串或 POST 表单数据时,我将它用于 params() 方法,但我没有看到任何有助于解析和路由路径的方法...
【解决方案3】:

config.ru 中使用的 DSL 在Rack::Builder 中定义。使用config.ru 时,文件的内容将传递给Builder 的实例以创建Rack 应用程序。您可以直接在代码中自己执行此操作。

例如,您可以获取现有config.ru 的内容,并从中创建一个新类:

require 'rack'

class MyApp

  def initialize
    @app = Rack::Builder.new do
      # copy contents of your config.ru into this block
      map '/route1' do
        run SampleApp.new
      end

      map '/route2' do
        run SampleApp.new
      end
    end
  end

  def call(env)
    @app.call(env)
  end
end

您需要call 方法,以便您的类是一个机架应用程序,但您可以将请求转发到您使用Builder 创建的应用程序。然后,您可以创建使用您的新应用的新 config.ru

require './my_app'

run MyApp.new

【讨论】:

  • 这几乎就是我想要做的事情,事实上,我已经尝试过这个事情。我遇到的问题是我想将实例变量从我的类传递到地图块中,但这不起作用,因为它们是在新的 Rack::Builder 实例的上下文中进行评估的。你知道我可以将Rack::Builder 子类化,这样我就不必实例化Rack::Builder 实例吗?
  • @Andrew 我不确定子类化是否可行,因为Rack::Builder 递归地创建instances of itself with self.class,我认为你不想妨碍它。
  • @Andrew 避免在Builder 的上下文中评估块的一种可能方法是首先创建构建器对象,然后显式调用该对象上的 DSL 方法,例如builder = Rack::Builder.new 后跟 builder.map '/route1' do ... 等。如果您想保留“普通”的 DSL 方法,您甚至可以使用 push this idea a bit further using the forwardable module
【解决方案4】:

使用 URLMap 怎么样?

app = Rack::URLMap.new(
  "/path1" => Path1App.new,
  "/path2" => Path2App.new
)

run app

【讨论】:

    猜你喜欢
    • 2013-07-23
    • 1970-01-01
    • 2013-02-27
    • 1970-01-01
    • 2018-04-01
    • 1970-01-01
    • 2013-06-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多