用 Python 來執行 Ruby 的我,
與撿到的 Puma 和 Rack
蔡孟穎 (Meng-Ying Tsai)
- 又名文月、八盤
- 現職為普通的後端工程師
- ❤: 喝淺焙咖啡、唱日卡、嚐甜食
一年 365 天歡迎餵食,請多指教 ヽ(●´∀`●)ノ
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/5824810/52417160_2131728573562551_6746436428424544256_n.jpg)
動手寫個簡單的 CGI script
CGI 的問題與各種衍生後輩
回來看 Rack 是什麼,它怎麼運作的
server serve 動態頁面的老祖先 CGI
簡單的 Rack Server 示範
用 Python 來執行 Ruby 的我,
與撿到的 Puma 和 Rack
用 Python Server 跑 CGI Script!
Python 在這裡其實不是很重要 :P
Rack 是什麼?Rack 如何連接 Rack App 和 Web Server
在談談怎麼用 python 跑 ruby 以前...
我們先來回到過去...我小時候
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8734437/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8734442/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8734471/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8734473/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8752884/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8734440/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8734444/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8734447/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8734445/pasted-from-clipboard.png)
那個年代的網站?
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8734240/截圖_2021-07-04_下午12.30.36.png)
www.lis.ntu.edu.tw 2001/2/5 (WayBackMachine)
- gif 網頁素材
- 江湖在走,跑馬燈要有
- 靜態頁面、影像
想跟使用者互動?
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8734337/截圖_2021-06-27_下午4.13.21.png)
CGI (Common Gateway Interface)
How to serve dynamic content?
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 跑 Ruby
就是用 python 跑 web server,執行 ruby CGI script 啦(標題詐騙
BTW 先說要用 python 跑 ruby 其實可以直接這樣:
import os
os.system("ruby hello.rb")
python http.server 也是用類似這樣的方式
來執行 CGI script
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
# ...
DEMO TIME owo/
從 DEMO 中可以發現
- server 用 ENV 的方式把 query_string 等資訊傳給 CGI 程式
- CGI 程式 STDOUT http response , server 回傳
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8754384/pasted-from-clipboard.png)
Do not use CGI?!
1. 效能瓶頸
Request
Request
Request
Web server
CGI Shell
CGI Shell
CGI Shell
2. DOS
3.too low level
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8754172/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8754173/pasted-from-clipboard.png)
自己從頭寫整個 CGI Script
找有完整功能、架構的 web framework
4. 重造輪子
- 改善效能
- 加速開發時程
PHP-CGI
ASP
WSGI
JSP
FastCGI
ASP(Active Static Pages)
- scripting on the server: <% ... %> 的內容會被 ASP 直譯器執行
- 內建 Objects (Application, Session...)
FastCGI(Fast Common Gateway Interface)
CGI 一個 process 處理一個 request 太慢,
FastCGI 有 pool 的機制,
一個 long-lived process serve 多個 request
WSGI(Python Web Server Gateway Interface)
讓 server 跟 framework 的大家都符合一個規範
我以為可以拿來用的 server
我使用的 framework
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8734478/pasted-from-clipboard.png)
我
WSGI(Python Web Server Gateway Interface)
可以執行 WSGI app 的 server
一團符合 WSGI 的 python script
🤝
讓 server 跟 framework 的大家都符合一個規範
回到我撿到的 Puma & Rack
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8754179/pasted-from-clipboard.png)
web server for Ruby & Rack
還有其他可以跑 Rack App 的 Server 只是先以他為例
Ruby web frameworks
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8754180/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8754182/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8754183/pasted-from-clipboard.png)
a ruby web server interface
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8754196/pasted-from-clipboard.png)
Puma
Rails / Sinatra / ...
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8734410/pasted-from-clipboard.png)
Rack?
![](https://s3.amazonaws.com/media-p.slid.es/uploads/427531/images/8734411/pasted-from-clipboard.png)
回去看看 WSGI 的模式,我們可以理解成這樣...
可以執行 Rack app 的 server
一個 based on Rack
的 web app
🤝
但 Rack 是個 gem?
more than a interface, Rack 還提供了一些:
- middleware eg. Rack::Config
- helpers eg. Rack::Request
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 CLI
$ puma
- rackup 時會用 Rack::Handler::Puma 跑起來
# /lib/puma/rack_default.rb
module Rack::Handler
def self.default(options = {})
Rack::Handler::Puma
end
end
puma 在哪裡 app.call(@env)?
CLI
Handler
OR
Launcher
Single < Runner
Binder
- entrypoint
- config
CLI
Handler
OR
Launcher
Cluster < Runner
Worker
Worker
Worker
Binder
- entrypoint
- config
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 Server DEMO!
首先會需要一個 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
- 開 TCPServer 偵聽
- 拿取資料,處理 Http Request
- 處理 Rack App 需要的 env
- app.call(@env) 拿回 [status, headers, body]
- 處理 Http Response,傳送資料
- 關閉連線
總結
CGI
非常簡單的 interface
Rack
效能問題
不要重複造輪子
...
Thanks for listening!
用 Python 來執行 Ruby 的我,與撿到的 Puma 和 Rack
By Meng-Ying Tsai
用 Python 來執行 Ruby 的我,與撿到的 Puma 和 Rack
- 702