Sinatra เป็นเฟรมเวิร์กเว็บของ Ruby
เหมือนพี่เรลเลย…
มาสำรวจว่าซินาตราทำงานอย่างไร :
- จะเกิดอะไรขึ้นเมื่อคุณกำหนดให้ Sinatra ในโครงการของคุณ
- การจับคู่เส้นทางทำงานอย่างไร
- คำขอและการตอบกลับมีการประมวลผลอย่างไร
คำถามมากมาย แต่เวลาน้อย…
ไม่มีปัญหา!
ฉันทำงานหนักเพื่อคุณและรวบรวมบทความนี้เพื่อตอบคำถามเหล่านี้เพื่อให้คุณเรียนรู้ได้เร็วขึ้น!
การเริ่มต้นซินาตร้า
ทุกอย่างเริ่มต้นด้วยไฟล์เดียว:sinatra.rb
.
ไฟล์ทั้งหมดนี้ต้องใช้ main.rb
, ไม่น่าตื่นเต้นใช่ไหม
ตอนนี้น่าสนใจยิ่งขึ้น!
ภายใน main.rb
คุณจะพบข้อกำหนดสำหรับ base.rb
และคุณจะพบรหัสสำหรับการแยกวิเคราะห์ตัวเลือก (พอร์ต สภาพแวดล้อม โหมดเงียบ ฯลฯ)
ซินาตราใช้ optparse
, จากไลบรารีมาตรฐานของ Ruby
คุณจะพบอะไรอีกที่นี่
ลองดูที่ at_exit
. นี้ บล็อก:
at_exit { Application.run! if $!.nil? && Application.run? }
นี่เป็นโค้ดเล็กน้อยที่จะรันเมื่อโปรแกรมสิ้นสุด
สิ่งที่เกิดขึ้นคือรหัสทั้งหมดของคุณจะถูกอ่านโดย Ruby &เนื่องจากคุณไม่มีลูป สลีป หรืออะไรทำนองนั้นที่โปรแกรมของคุณจะจบลงอย่างเป็นธรรมชาติ
…แต่ก่อนจะจบ at_exit
บล็อกจะทริกเกอร์!
จากนั้น Sinatra จะเข้ามาแทนที่ &เริ่มเว็บเซิร์ฟเวอร์เพื่อให้สามารถจัดการคำขอได้
นี่คือรหัสที่ใช้ :
begin start_server(handler, server_settings, handler_name, &block) rescue Errno::EADDRINUSE $stderr.puts "== Someone is already performing on port #{port}!" raise end # Part of base.rb `run!` method
โอ้ และสิ่งที่สำคัญอีกอย่างเกิดขึ้นที่นี่:
extend Sinatra::Delegator
Sinatra::Delegator
เป็นโมดูลที่กำหนดวิธีการ Sinatra DSL เช่น get
, post
&set
.
นั่นคือเหตุผลที่คุณทำได้:
get '/' do puts "Hello World!" end
ซินาตราขยายmain
.ทั่วโลก วัตถุด้วยโมดูลนี้
คำขอและการดำเนินการตอบสนอง
ตกลง ดังนั้น ณ จุดนี้ เรามีเซิร์ฟเวอร์ที่ทำงานอยู่พร้อมที่จะยอมรับการเชื่อมต่อใหม่
แต่จะเกิดอะไรขึ้นเมื่อได้รับการเชื่อมต่อใหม่
Sinatra ก็เหมือนกับ Rails และเว็บเฟรมเวิร์กอื่นๆ ของ Ruby ที่ใช้ Rack เพื่อจัดการเนื้อหาระดับล่างทั้งหมด
แร็คคาดว่าจะมี call
วิธีการที่มีอยู่ในใบสมัครของคุณ นั่นเป็นเพียงวัตถุที่คุณมอบให้กับ Rack เมื่อคุณเริ่มต้นใช้งาน
ในกรณีของ Sinatra วัตถุนี้คือ Sinatra::Base
ชั้นเรียน
นี่คือวิธีการ :
# Rack call interface. def call!(env) @env = env @request = Request.new(env) @response = Response.new invoke { dispatch! } invoke { error_block!(response.status) } unless @env['sinatra.error'] @response.finish end # Modified version of Sinatra's call method (for clarity)
ดูเหมือนว่าเราต้องตรวจสอบ dispatch!
วิธีถัดไปเพื่อแสดงวิธีจัดการคำขอ
นี่คือวิธีการนั้น :
def dispatch! invoke do static! if settings.static? && (request.get? || request.head?) filter! :before route! end rescue ::Exception => boom invoke { handle_exception!(boom) } ensure filter! :after unless env['sinatra.static_file'] end # Edited down to the important parts
คำขอแบ่งเป็น 4 ขั้นตอน :
- ไฟล์สแตติกจะถูกตรวจสอบก่อน นี่คือไฟล์เช่น css, js และรูปภาพ การตั้งค่านี้จะเปิดใช้งานโดยค่าเริ่มต้นหากมีไดเรกทอรีชื่อ "สาธารณะ"
- ตัวกรองก่อนทำงาน
- การจับคู่เส้นทาง
- ตัวกรองหลังทำงาน
ตอนนี้เราสามารถเจาะลึกลงไปในแต่ละขั้นตอนเพื่อดูว่าจะเกิดอะไรขึ้นในรายละเอียดเพิ่มเติม
การแสดงไฟล์สแตติก
static!
วิธีการค่อนข้างง่าย:
def static!(options = {}) return if (public_dir = settings.public_folder).nil? path = File.expand_path("#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}" ) return unless File.file?(path) cache_control(*settings.static_cache_control) if settings.static_cache_control? send_file(path, options) end
รหัสนี้จะตรวจสอบว่ามีไฟล์ที่ร้องขอหรือไม่ จากนั้นจะตั้งค่าส่วนหัว HTTP ของ “การควบคุมแคช”
ในบรรทัดสุดท้ายเรียก send_file
และตรงตามชื่อ 🙂
ก่อนกรอง
ตัวกรอง A before อนุญาตให้คุณเรียกใช้โค้ดก่อนที่จะพยายามค้นหาเส้นทางที่ตรงกัน
นี่คือวิธีการเพิ่มตัวกรอง:
# Define a before filter. # Runs before all requests within the same context as route handlers # and may access/modify the request and response. @filters = {:before => [], :after => []} def before(path = /.*/, **options, &block) add_filter(:before, path, options, &block) end def after(path = /.*/, **options, &block) add_filter(:after, path, options, &block) end def add_filter(type, path = /.*/, **options, &block) filters[type] << compile!(type, path, block, options) end
อย่างที่คุณเห็น filters
เป็นเพียงแฮชที่มีสองคีย์ หนึ่งคีย์สำหรับตัวกรองแต่ละประเภท
แต่อะไรคือ compile!
?
เมธอดนี้ส่งคืนอาร์เรย์ที่มีองค์ประกอบ 3 แบบ ได้แก่ รูปแบบ อาร์เรย์ของเงื่อนไข และ wrapper
วิธีการเดียวกันนี้ใช้ในการสร้างเส้นทาง (เมื่อคุณใช้ get
หรือ post
บล็อค):
def get(path, opts = {}, &block) route('GET', path, opts, &block) end def route(verb, path, options = {}, &block) signature = compile!(verb, path, block, options) (@routes[verb] ||= []) << signature signature end # Methods edited for clarity
จากนี้ เราจะเรียนรู้ว่าตัวกรองของ Sinatra ทำงานและทำงานเหมือนกับเส้นทาง
การจับคู่เส้นทาง
ขั้นตอนต่อไปในรอบการประมวลผลคำขอคือการจับคู่เส้นทาง:
def route!(base = settings, pass_block = nil) routes = base.routes[@request.request_method] routes.each do |pattern, conditions, block| process_route(pattern, conditions) route_eval end route_missing end # Edited method
รหัสนี้ครอบคลุมทุกเส้นทางที่ตรงกับวิธีการขอ (get
, post
ฯลฯ)
การจับคู่เส้นทางเกิดขึ้นภายใน process_route
วิธีการ:
def process_route(pattern, keys, conditions, block = nil, values = []) route = @request.path_info route = '/' if route.empty? and not settings.empty_path_info? return unless match = pattern.match(route) end
โดยที่ pattern
เป็นนิพจน์ทั่วไป
หากเส้นทางตรงกับทั้งเส้นทาง &เงื่อนไขแล้ว route_eval
จะถูกเรียกซึ่งประเมินบล็อก (เนื้อความของ get
. ของคุณ) / post
เส้นทาง) และสิ้นสุดกระบวนการจับคู่เส้นทาง
# Run a route block and throw :halt with the result. def route_eval throw :halt, yield end
สิ่งนี้ใช้ catch
. ที่ผิดปกติ / throw
กลไกควบคุมการไหล
ฉันขอแนะนำว่าไม่เห็นด้วยกับสิ่งนี้ เพราะอาจทำให้สับสนมากในการปฏิบัติตามขั้นตอนของโค้ด แต่การดูตัวอย่างการใช้งานคุณลักษณะนี้ในโลกแห่งความเป็นจริงนั้นเป็นเรื่องที่น่าสนใจ
การสร้างการตอบสนอง
ขั้นตอนสุดท้ายของรอบคำขอคือการเตรียมการตอบกลับ
แล้วผลตอบรับจะไปไหน?
invoke
วิธีการรวบรวมการตอบสนองเช่นนี้:
res = catch(:halt) { yield }
ผลลัพธ์นี้ถูกกำหนดให้กับเนื้อหาการตอบสนองโดยใช้ body
วิธีการ:
body(res)
ทีนี้ถ้าเรามองย้อนกลับไปที่ที่เราเริ่มต้น call
เมธอด เราจะพบโค้ดบรรทัดนี้:
@response.finish
สิ่งนี้เรียก finish
วิธีการใน @response
ซึ่งเป็น Rack::Response
วัตถุ
กล่าวอีกนัยหนึ่ง การดำเนินการนี้จะทริกเกอร์การตอบกลับเพื่อส่งไปยังไคลเอ็นต์
โบนัส:วิธีการตั้งค่าทำงานอย่างไร
วิธีการตั้งค่านี้เป็นส่วนหนึ่งของ DSL (Domain-Specific Language) ของ Sinatra และช่วยให้คุณตั้งค่าตัวเลือกการกำหนดค่าได้ทุกที่ในแอปพลิเคชัน Sinatra
ตัวอย่าง :
set :public_folder, '/var/www'
ทุกครั้งที่ใช้ set
Sinatra สร้าง 3 วิธี (ผ่าน metaprogramming):
define_singleton("#{option}=", setter) if setter define_singleton(option, getter) if getter define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?"
3 วิธีคือ (ด้วย public_folder
ดังตัวอย่าง):
- public_folder
- public_folder=
- public_folder?
เมธอดนี้จะเรียกเมธอด setter ด้วย (public_folder=
) หากมีอยู่แล้ว:
if respond_to?("#{option}=") && !ignore_setter return __send__("#{option}=", value) end
โปรดจำไว้ว่า metaprogramming นั้นไม่ฟรี ดังนั้นฉันจะใช้ options
กัญชา. คุณไม่จำเป็นต้องใช้วิธีแฟนซีเหล่านั้น
สรุป
คุณได้เรียนรู้ว่า Sinatra ได้รับการเริ่มต้นอย่างไร จัดการกับคำขออย่างไร และขั้นตอนต่างๆ ที่ต้องใช้จนกว่าจะมีการตอบสนอง สิ่งนี้จะช่วยให้คุณเรียนรู้เคล็ดลับของ Ruby และเข้าใจ Sinatra มากขึ้น!
อย่าลืม แชร์โพสต์นี้ ร่วมกับนักพัฒนา Ruby คนอื่นๆ เพื่อให้พวกเขาได้เรียนรู้จากมันเช่นกัน 🙂