一年 365 天歡迎餵食,請多指教 ヽ(●´∀`●)ノ
動手寫個簡單的 CGI script
CGI 的問題與各種衍生後輩
回來看 Rack 是什麼,它怎麼運作的
server serve 動態頁面的老祖先 CGI
簡單的 Rack Server 示範
用 Python Server 跑 CGI Script!
Python 在這裡其實不是很重要 :P
Rack 是什麼?Rack 如何連接 Rack App 和 Web Server
我們先來回到過去...我小時候
www.lis.ntu.edu.tw 2001/2/5 (WayBackMachine)
Browser
Web Server
Program
business logic
connect to DB
Browser
Web Server
CGI Program
query_string,
request_method...
Content-Type: text/plain
Hello World!
How CGI Works?
就是用 python 跑 web server,執行 ruby CGI script 啦(標題詐騙
import os
os.system("ruby hello.rb")
def run_cgi(self):
# ...
if self.have_fork:
# Unix -- fork as we should
# ...
if pid != 0:
# Parent
# ...
# Child
try:
# ...
os.execve(scriptfile, args, env)
else:
# Non-Unix -- use subprocess
# ...
Request
Request
Request
Web server
CGI Shell
CGI Shell
CGI Shell
自己從頭寫整個 CGI Script
找有完整功能、架構的 web framework
CGI 一個 process 處理一個 request 太慢,
FastCGI 有 pool 的機制,
一個 long-lived process serve 多個 request
讓 server 跟 framework 的大家都符合一個規範
我以為可以拿來用的 server
我使用的 framework
我
可以執行 WSGI app 的 server
一團符合 WSGI 的 python script
🤝
讓 server 跟 framework 的大家都符合一個規範
web server for Ruby & Rack
還有其他可以跑 Rack App 的 Server 只是先以他為例
Ruby web frameworks
a ruby web server interface
Puma
Rails / Sinatra / ...
Rack?
可以執行 Rack app 的 server
一個 based on Rack
的 web app
🤝
more than a interface, Rack 還提供了一些:
Browser
Puma
How Rack Works?
Rails / Sinatra/ ...
Puma
Rails / Sinatra/ ...
App.call(env)
[200,
{'Content-Type' => 'text/plain'},
['hello world!']]
Rack
Rack
Rails / Sinatra/ ...
A Rack application is a Ruby object (not a class) that responds to call
. It takes exactly one argument, the environment and returns an Array of exactly three values: The status, the headers, and the body.
# lib/sinatra/base.rb:928
def call(env)
dup.call!(env)
end
def call!(env)
# ...
@request = Request.new(env) # class Request < Rack::Request
@response = Response.new
# ...
@response.finish
end
# lib/sinatra/base.rb:172
def finish
# ...
[status, headers, result]
end
必須要有 call 方法
固定回傳格式
以 Sinatra 為例
The environment must be an unfrozen instance of Hash that includes CGI-like headers. The application is free to modify the environment. The environment is required to include these variables (adopted from PEP333), except when they'd be empty, but see below.
# in an Rack App
request = Rack::Request.new(env)
if request.request_method == 'GET'
# ...
end
# rack /lib/rack/request.rb:57
def get_header(name)
@env[name]
end
# rack lib/rack/request.rb:151
def request_method; get_header(REQUEST_METHOD) end
可以使用 Rack gem 提供了 Rack::Request:
Rack
Puma
# in a config.ru
run MyApp
$ rackup
在 Rack App 專案的 config.ru:
可以用 rackup 把 app 跑起來
Handlers usually are activated by calling MyHandler.run(myapp)
A second optional hash can be passed to include server-specific configuration.
lib/rack/handler.rb:9
要把 Puma 跑起來:
$ puma
# /lib/puma/rack_default.rb
module Rack::Handler
def self.default(options = {})
Rack::Handler::Puma
end
end
CLI
Handler
OR
Launcher
Single < Runner
Binder
CLI
Handler
OR
Launcher
Cluster < Runner
Worker
Worker
Worker
Binder
Server
Thread_pool
Server
Server
loop ,放 client 到 thread pool
handle_request
@app.call(@env)
process_client
Puma 會呼叫 app 的 call method
# lib/puma/request.rb:76
status, headers, res_body = @thread_pool.with_force_shutdown do
@app.call(env)
end
首先會需要一個 Rack App
class MyApp
def self.call(env)
[200, { 'Content-Type' => 'text/plain' }, 'hello']
end
end
還會需要一個 Handler
class SimpleServerHandler
def self.run(app, **options)
# 先不處理 environment, pid, AccessLog, config, etc.
@server = SimpleServer.new(app, server_name: options[:Host], port: options[:Port])
@server.start
end
end
module Rack::Handler
def self.default(options = {})
SimpleServerHandler
end
end
非常簡單的 interface
效能問題
不要重複造輪子
...