app.rb
require "rack"
handler = Rack::Handler::WEBrick
class Base
attr_accessor :route
attr_accessor :req
class << self
def route
@route ||= Hash["get" => {}]
end
def request
@req
end
def newrequest (env) # 这个 env 参数只能从 rackapp 的 call 中才能获取
@req = Rack::Request.new(env)
puts @req.params
end
def get(path, &block)
Base.route["get"][path] = block
end
end
end
class MyApp < Base
get "/" do
print request.inspect
"access request"
end
end
class RackApp
def call(env)
# req = Rack::Request.new(env)
path = env["PATH_INFO"]
method = env["REQUEST_METHOD"].downcase
Base.newrequest env
cb = Base.route[method][path]
resp = cb.call
[200, { "Content-Type" => "text/html" }, [resp]]
end
end
handler.run RackApp.new
ruby app.rb
http://localhost:8080/?ruby=rails
self.newquest 里面 可以打印参数 self.request 中打印参数是 nil
attr_accessor
宏是一个魔法,
attr_accessor req
会给你定义 写入req=
以及 读取this.req
方法,
实际上你已经在后续代码使用了,而你在调用self.request
时并未先写入req
的值,所以打印出来是nil
,
把req
想象成一个小柜子的话,你没有事先存放物品在里面,所以你从柜子里拿不到任何东西
attr_accessor 的用法是针对实例方法的,如果 Base 不想生成实例,只用类方法实现(写在 class<< self 中) req=的 setter 要带 env 参数 这个 env 参数只能从 rackapp 的 call 中才能获取 怎么样实现 类似 req= 的功能?
可以使用其他方法,
比如你代码的调用链是 A -> B -> C ,想在 B 中使用 无障碍使用 req,
则可以自己稍微调整下调用链,在 A -> B 之间插入 before_B 方法来处理 req=的赋值功能,变成 A -> before_B -> B -> C
元编程或者 Rack 中间件应该是有这种 API 的。
翻了下 webbrick 的源码,他自身定义了挺多常用变量了,可以参考
https://github.com/ruby/webrick/blob/master/lib/webrick/httprequest.rb
用 Base.class_variable_set :@req, Rack::Request.new(env)
报错 ERROR NameError: `@req' is not allowed as a class variable name
用 Base.class_variable_set :@@req, Rack::Request.new(env)
是可以的
但定义类的时侯 不管是实例方法 或者类方法 getter/setter @req 都是正常的,只是同一个名字,不同的 object_id 这算不算坑?
test.rb
class Test
attr_accessor :req
class << self
def setter val
@req = val
end
def getter
@req
end
def request
@req
end
end
end
#Test.class_variable_set :@req,"foo"
Test.setter "foo"
p Test.getter
p Test.request
t = Test.new
t.req = "bar"
p t.req
Ruby 支持五种类型的变量。
一般小写字母、下划线开头:变量(Variable)。
$开头:全局变量(Global variable)。
@开头:实例变量(Instance variable)。
@@开头:类变量(Class variable)类变量被共享在整个继承链中
大写字母开头:常数(Constant)。
类的 object_id 在运行过程中是始终一致的
回复顶楼问题,你想要的可能是:
Base.newrequest env
cb = Base.route[method][path]
resp = Base.instance_eval(&cb) # cb 在 Base 的实例环境下执行
但这个设计我觉得有问题,多线程环境下 Base 的实例变量会有并发冲突。
上面的例子 不考虑 实例的情况 就是自定义的类不用 new 的用法
比如 下面这段代码,Base/MyApp 都不运行 new 也正常读到 params 只是 block 中要写成 Base.request
require "rack"
handler = Rack::Handler::WEBrick
class Base
class << self
def route
@route ||= Hash["get" => {}]
end
def request
@req
end
def get(path, &block)
Base.route["get"][path] = block
end
end
end
class MyApp < Base
get "/" do
print Base.request.inspect # print request.inspect
"access request"
end
end
class RackApp
def call(env)
dup._call(env)
end
def _call(env)
# req = Rack::Request.new(env)
path = env["PATH_INFO"]
method = env["REQUEST_METHOD"].downcase
Base.instance_variable_set :@req, Rack::Request.new(env)
cb = Base.route[method][path]
resp = cb.call
[200, { "Content-Type" => "text/html" }, [resp]]
end
end
handler.run RackApp.new
你上面的做法,get 方法的 self 是 MyApp,所以直接执行 request.inspect 应该是空值,因为 MyApp 中没有初始化@route和@req。 后面的 call 方法里一直都是对 Base 的独有的类实例变量进行赋值,所以需要加 Base.request.inspect 才能取到值,因为全程 MyApp 都只是起到了初始化 Base 里的类实例变量的作用。
把 call 方法里的 Base 都改成 MyApp, get 方法里的 block 里应该就可以直接用 request.inspect 了 或者像@Rei大佬说的一样,用 Base 的环境运行 block 也行,至于并发隐患,我还不懂(好像是因为@req只有一个,所以会导致多个请求同时到来的时候,@req会混乱 )
Rack 只是接口,并发是交由 app server 实现,例如 puma 就是多线程模型。如果你要实现框架的话,要在框架层实现并发安全。
多线程的问题太多了,框架线程安全了,并不能保证所用的 gem 都是线程安全的 主流的浏览器多 Tab 浏览开始用的多线程,到最后都搞不定了内存问题,无一例外地都换成了多进程结构
以前 unicorn 流行就是因为进程模型简单,但是占内存。在 puma 稳定,并且主流 gem 都实现了线程安全之后,现在流行的就是 puma 多进程 + 多线程模型。未来有希望跟随 Ruby 发展用上 ractor + fiber 的并发模型,但还需要时间。
想说的是,如果你的框架不考虑线程安全问题,只能限定运行的 app server 用多进程模型。
额,这样么 ,如果多线程只是省内存,那怎么能加快程序的运行速度呢 ,因为我工作中使用多线程一般都是为了提高程序的运行速度,所以有此疑问
用 MyApp 是对的
require "rack"
handler = Rack::Handler::WEBrick
class Base
class << self
def route
@route ||= Hash["get" => {}]
end
def request
@req
end
def get(path, &block)
route["get"][path] = block
end
end
end
class MyApp < Base
get "/" do
print request.inspect
"access request"
end
get "/favicon.ico" do
File.open "favicon.ico",&:read
end
end
class RackApp
def call(env)
dup._call(env)
end
def _call(env)
path = env["PATH_INFO"]
method = env["REQUEST_METHOD"].downcase
MyApp.instance_variable_set :@req, Rack::Request.new(env)
cb = MyApp.route[method][path]
resp = cb.call
[200, { "Content-Type" => "text/html" }, [resp]]
end
end
handler.run RackApp.new
另外提高程序的运行速度是在特定的场景下有前提的 比如机器轻负荷并且任务单元互相独立没有依赖关系的情况 典型的象迅雷下载文件的场景,但你在一台重负荷的机器上用多线程安装 gem 并不会提高程序运行速度,反而可能降低速度。不要迷信一些书上的结论,可能写书的老先生是20年前 mfc 桌面软件流行的时候写的,现在的应用场景与以前完全不同了,在 web 环境下,response 是完全依赖 request 的,你不可能没解析完 request,就把 response 发出去。技术问题要看需求场景和实际数据,比如同样的机器多进程/多线程在机器负荷不同的情况下结果完全不同,高负荷的机器上,多进程的并发数是高于多线程的,如果你只会背书,除了说一些貌似正确的废话,面对实际问题,只会一脸蒙逼了
哦哦,大致明白 。 另外,这里其实可以不用 instance_variable_set 直接操作类实例变量
MyApp.instance_variable_set :@req, Rack::Request.new(env)
gem 里应该还比较常见,因为 attr_accessor 就是生成 get 和 set 方法的,所以当需要对类实例变量的 get 和 set 时就在类的 singleton_class 里用 attr_accessor 就行 (类比正常的 class 和实例的关系)https://github.com/rails/rails/blob/4ace047c91d450af17eb218b0ea0139a6ea6f731/railties/lib/rails.rb#L26
在 class << self 里面的类方法里面,有办法访问类的普通成员变量吗?aa 是定义在 A 下面的 class << self 只定义了 bb 比如 如果在 class << self 要访问 A下面 aa 怎么写?
class A
attr_accessor :aa
def initialize
@aa=10
end
def ten
puts self.object_id
dup.five
end
def five
puts self.object_id
@aa / 2
end
class << self
attr_accessor :bb
def getbb
@bb = 200
end
def getaa
@aa
end
end
end
a = A.new
p a.five
p a.ten
p a.five
p A.getbb
p A.getaa
你这个问题的意思是要在类方法里访问实例对象的实例变量,可以是可以,但是因为一个类会有很多实例,所以无法确定是要访问哪一个的实例对象的实例变量,感觉只能在类方法中新生成一个实例对象访问 aa 的默认值或者指定访问对象才行
class A
attr_accessor :aa
def initialize
@aa=10
end
class << A
attr_accessor :bb
def getaa
A.new.aa
end
def get_aa(obj)
obj.aa
end
end
end