Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

การวิเคราะห์อย่างรวดเร็วว่าซินาตราทำงานอย่างไร

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 ขั้นตอน :

  1. ไฟล์สแตติกจะถูกตรวจสอบก่อน นี่คือไฟล์เช่น css, js และรูปภาพ การตั้งค่านี้จะเปิดใช้งานโดยค่าเริ่มต้นหากมีไดเรกทอรีชื่อ "สาธารณะ"
  2. ตัวกรองก่อนทำงาน
  3. การจับคู่เส้นทาง
  4. ตัวกรองหลังทำงาน

ตอนนี้เราสามารถเจาะลึกลงไปในแต่ละขั้นตอนเพื่อดูว่าจะเกิดอะไรขึ้นในรายละเอียดเพิ่มเติม

การแสดงไฟล์สแตติก

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 คนอื่นๆ เพื่อให้พวกเขาได้เรียนรู้จากมันเช่นกัน 🙂