tl;dr 如何通过对 config.ru
进行自定义,让单个 Sinatra 应用程序在不同服务器上以不同的方式启动?
背景
我有一个使用 Sinatra 编写的 Web 应用程序,该应用程序在不同的服务器上运行。目前,这些服务器的代码库是 fork 的,因为它们的(离散)部分的工作方式存在一些重要的差异。例如:
- 一台服务器通过 Intranet LDAP 服务器对用户进行身份验证,而另一台服务器则使用更简单的本地数据库表查找。
- 一台服务器使用外部 cron 作业定期更新一些统计信息,而另一台(基于 Windows)服务器使用内部休眠线程。
- 一台服务器将某些元数据存储在本地表中,而另一台服务器则通过屏幕抓取从外部 Wiki 中提取元数据(!)。
...等等。
我希望完全共享这些代码库(单个 Git 存储库)。我设想每台服务器都会有一个略有不同的配置文件,导致应用程序以不同的方式启动。
废弃的解决方案
我可以根据环境变量更改应用程序的行为。由于行为上的变化并不多,我不想隐藏环境变量中的设置。
我可以创建自己的“server-settings.rb”文件,该文件对于每台计算机都是唯一的,在我的app.rb
中需要它,然后更改配置那里。然而,这似乎可能是在重新发明轮子。我已经为每台服务器拥有一个名为config.ru
的文件。我不应该使用这个吗?
当前代码
当前该应用程序的我的config.ru
很简单:
require ::File.join( ::File.dirname(__FILE__), 'app' )
run MyApp.new
它需要的 app.rb
本质上是:
require 'sinatra'
require_relative 'helpers/login' # customized for LDAP lookup on this server
class MyApp < Sinatra::Application
use Rack::Session::Cookie, key:'foo.bar', path:'/', secret:'ohnoes'
set :protection, except: [:path_traversal, :session_hijacking]
configure :production do
# run various code that depends on server settings, e.g.
Snapshotter.start # there is no cron on this machine, so we do it ourselves
end
configure :development do
# run various code that depends on server settings
end
end
问题
我想让 config.ru
名副其实,并且看起来像这样:
require ::File.join( ::File.dirname(__FILE__), 'app' )
run MyApp.new( auth: :ldap, snapshot:false, metadata: :remote_wiki, … )
如何修改我的应用程序,以根据 config.ru
提供的设置更改其配置行为?或者这是对 config.ru
的滥用,试图将其用于完全错误的事情?
最佳答案
当我开始阅读这个问题时,我脑海中浮现的第一个答案是“环境变量”,但你立刻就放弃了:)
我将混合使用您的可以和所需的结果代码,因为这就是我构建事物的方式......
因为我希望能够更轻松地测试我的应用程序,所以我将大部分 Ruby 从 config.ru 中取出并放入单独的 config.rb
中。文件并将 config.ru 保留为引导文件。所以我的标准骨架是:
config.ru
# encoding: UTF-8
require 'rubygems'
require 'bundler'
Bundler.setup
root = File.expand_path File.dirname(__FILE__)
require File.join( root , "./app/config.rb" )
# everything was moved into a separate module/file to make it easier to set up tests
map "/" do
run APP_NAME.app
end
应用程序/config.rb
# encoding: utf-8
require_relative File.expand_path(File.join File.dirname(__FILE__), "../lib/ext/warn.rb")
require_relative "./init.rb" # config
require_relative "./main.rb" # routes and helpers
require 'encrypted_cookie'
# standard cookie settings
COOKIE_SETTINGS = {
:key => 'usr',
:path => "/",
:expire_after => 86400, # In seconds, 1 day
:secret => ENV["LLAVE"],
:httponly => true
}
module APP_NAME # overall name of the app
require 'rack/ssl' # force SSL
require 'rack/csrf'
if ENV["RACK_ENV"] == "development"
require 'pry'
require 'pry-nav'
end
# from http://devcenter.heroku.com/articles/ruby#logging
$stdout.sync = true
ONE_MONTH = 60 * 60 * 24 * 30
def self.app
Rack::Builder.app do
cookie_settings = COOKIE_SETTINGS
# more security if in production
cookie_settings.merge!( :secure => true ) if ENV["RACK_ENV"] == "production"
# AES encryption of cookies
use Rack::Session::EncryptedCookie, cookie_settings
if ENV["RACK_ENV"] == "production"
use Rack::SSL, :hsts => {:expires => ONE_MONTH}
end
# to stop XSS
use Rack::Csrf, :raise => true unless ENV["RACK_ENV"] == "test"
run App # the main Sinatra app
end
end # self.app
end # APP_NAME
我这样做的最初原因是为了让应用程序能够轻松地按照规范运行:
shared_context "All routes" do
include Rack::Test::Methods
let(:app){ APP_NAME.app }
end
但对我来说,将此代码与应用程序代码的其余部分一起保留是有意义的,可以这么说,因为我可以将东西捆绑在一起,运行其他应用程序等。我已将其用于 conditionally load different examples into the specs在一些项目中(它有助于减少重复工作并检查示例是否真的有效),所以我不明白为什么你不能使用它来有条件地加载配置。
这样,您就可以选择在 config.ru 中使用条件来决定要使用哪个 config.rb 文件,或者在 config.rb 中使用环境变量来决定 self.app
的定义使用,或将选项哈希传递给 self.app...
根据您的设置,我将重命名 APP_NAME
模块至MyApp
,以及 Sinatra 类 App
(因为我经常会有一个运行前端和 API 的网站,因此 Sinatra 类按其功能(应用程序、API 等)命名,并包装在以该网站命名的模块中)并最终得到:
config.ru
map "/" do
run MyApp.app( auth: :ldap, snapshot:false, metadata: :remote_wiki )
end
配置.rb
def self.app( opts={} )
opts = DEFAULT_OPTIONS.merge opts
# …
run App
end
看看其他人如何解决这个问题会很有趣。
关于ruby - 使用rackup文件自定义配置应用程序实例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15060230/