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

ยูนิคอร์นคุยกับ nginx อย่างไร - บทนำเกี่ยวกับซ็อกเก็ตยูนิกซ์ใน Ruby

โดยทั่วไปแล้วแอปพลิเคชันเซิร์ฟเวอร์ Ruby จะใช้ร่วมกับเว็บเซิร์ฟเวอร์เช่น nginx เมื่อผู้ใช้ร้องขอหน้าจากแอป Rails ของคุณ nginx จะมอบหมายคำขอไปยังแอปพลิเคชันเซิร์ฟเวอร์ แต่มันทำงานอย่างไรกันแน่? nginx คุยกับยูนิคอร์นอย่างไร?

หนึ่งในตัวเลือกที่มีประสิทธิภาพมากที่สุดคือการใช้ซ็อกเก็ตยูนิกซ์ มาดูกันว่ามันทำงานอย่างไร! ในโพสต์นี้ เราจะเริ่มต้นด้วยพื้นฐานของซ็อกเก็ต และจบลงด้วยการสร้างแอปพลิเคชันเซิร์ฟเวอร์อย่างง่ายของเราเองที่พร็อกซี nginx

ยูนิคอร์นคุยกับ nginx อย่างไร - บทนำเกี่ยวกับซ็อกเก็ตยูนิกซ์ใน Ruby ซ็อกเก็ตช่วยให้โปรแกรมสามารถพูดคุยกันราวกับว่าพวกเขากำลังเขียนหรืออ่านจากไฟล์ ในตัวอย่างนี้ Unicorn จะสร้างซ็อกเก็ตและตรวจสอบการเชื่อมต่อ จากนั้น Nginx สามารถเชื่อมต่อกับซ็อกเก็ตและพูดคุยกับยูนิคอร์นได้

ซ็อกเก็ตยูนิกซ์คืออะไร

ซ็อกเก็ต Unix ช่วยให้โปรแกรมหนึ่งพูดคุยกับอีกโปรแกรมหนึ่งในลักษณะที่คล้ายกับการทำงานกับไฟล์ เป็นประเภทของ IPC หรือการสื่อสารระหว่างกระบวนการ

เพื่อให้สามารถเข้าถึงได้ผ่านซ็อกเก็ต ก่อนอื่นโปรแกรมของคุณจะสร้างซ็อกเก็ตและ "บันทึก" ลงในดิสก์ เช่นเดียวกับไฟล์ จะตรวจสอบซ็อกเก็ตสำหรับการเชื่อมต่อขาเข้า เมื่อได้รับก็จะใช้วิธี IO มาตรฐานในการอ่านและเขียนข้อมูล

Ruby มีทุกสิ่งที่คุณต้องการในการทำงานกับยูนิกซ์ซ็อกเก็ตผ่านคลาสต่างๆ:

  • UNIX*เซิร์ฟเวอร์ * - สร้างซ็อกเก็ต บันทึกลงในดิสก์ และให้คุณตรวจสอบการเชื่อมต่อใหม่ได้

  • UNIX*ซ็อกเก็ต * - เปิดซ็อกเก็ตที่มีอยู่สำหรับ IO

หมายเหตุ:  มีซ็อกเก็ตประเภทอื่นอยู่ ซ็อกเก็ต TCP ที่โดดเด่นที่สุด แต่โพสต์นี้เกี่ยวข้องกับซ็อกเก็ตยูนิกซ์เท่านั้น คุณจะบอกความแตกต่างได้อย่างไร? ซ็อกเก็ต Unix มีชื่อไฟล์

ซ็อกเก็ตที่ง่ายที่สุด

เราจะดูสองโปรแกรมเล็กๆ น้อยๆ กัน

อย่างแรกคือ "เซิร์ฟเวอร์" มันเพียงแค่สร้างอินสแตนซ์ของ UnixServer class แล้วใช้ server.accept เพื่อรอการเชื่อมต่อ เมื่อได้รับการเชื่อมต่อก็จะแลกเปลี่ยนคำทักทาย

เป็นที่น่าสังเกตว่าทั้ง accept และ readline เมธอดจะบล็อกการทำงานของโปรแกรมจนกว่าจะได้รับสิ่งที่รอคอย

require "socket"

server = UNIXServer.new('/tmp/simple.sock')

puts "==== Waiting for connection"
socket = server.accept

puts "==== Got Request:"
puts socket.readline

puts "==== Sending Response"
socket.write("I read you loud and clear, good buddy!")

socket.close

ดังนั้นเราจึงมีเซิร์ฟเวอร์ ตอนนี้ทั้งหมดที่เราต้องการคือลูกค้า

ในตัวอย่างด้านล่าง เราเปิดซ็อกเก็ตที่สร้างโดยเซิร์ฟเวอร์ของเรา จากนั้นเราจะใช้วิธี IO ปกติในการส่งและรับคำทักทาย

require "socket"

socket = UNIXSocket.new('/tmp/simple.sock')

puts "==== Sending"
socket.write("Hello server, can you hear me?\n")

puts "==== Getting Response"
puts socket.readline 

socket.close

ในการสาธิต ก่อนอื่นเราต้องเปิดเซิร์ฟเวอร์ จากนั้นเราเรียกใช้ไคลเอนต์ คุณสามารถดูผลลัพธ์ด้านล่าง:

ยูนิคอร์นคุยกับ nginx อย่างไร - บทนำเกี่ยวกับซ็อกเก็ตยูนิกซ์ใน Ruby ตัวอย่างการโต้ตอบกับไคลเอ็นต์ซ็อกเก็ต/เซิร์ฟเวอร์ UNIX อย่างง่าย ลูกค้าอยู่ทางซ้าย เซิฟเวอร์อยู่ทางขวา

เชื่อมต่อกับ nginx

ตอนนี้เรารู้วิธีสร้าง "เซิร์ฟเวอร์" ซ็อกเก็ตยูนิกซ์แล้ว เราก็สามารถเชื่อมต่อกับ nginx ได้อย่างง่ายดาย

ไม่เชื่อฉัน? ลองทำการพิสูจน์แนวคิดอย่างรวดเร็ว ฉันจะปรับโค้ดด้านบนให้พิมพ์ทุกอย่างที่ได้รับจากซ็อกเก็ต

require "socket"

# Create the socket and "save it" to the file system
server = UNIXServer.new('/tmp/socktest.sock')

# Wait until for a connection (by nginx)
socket = server.accept

# Read everything from the socket
while line = socket.readline
  puts line.inspect
end

socket.close

ตอนนี้ถ้าฉันกำหนดค่า nginx เพื่อส่งต่อคำขอไปยังซ็อกเก็ตที่ /tmp/socktest.sock ฉันสามารถดูว่าข้อมูลที่ nginx กำลังส่งคืออะไร (ไม่ต้องกังวล เราจะหารือเกี่ยวกับการกำหนดค่าในไม่กี่นาที)

เมื่อฉันส่งคำขอทางเว็บ nginx จะส่งข้อมูลต่อไปนี้ไปยังเซิร์ฟเวอร์เล็กๆ ของฉัน:

ยูนิคอร์นคุยกับ nginx อย่างไร - บทนำเกี่ยวกับซ็อกเก็ตยูนิกซ์ใน Ruby

สวยเย็น! เป็นเพียงคำขอ HTTP ปกติที่มีการเพิ่มส่วนหัวพิเศษสองสามรายการ ตอนนี้เราพร้อมที่จะสร้างเซิร์ฟเวอร์แอปจริงแล้ว แต่ก่อนอื่น เรามาพูดถึงการกำหนดค่า nginx กันก่อน

การติดตั้งและกำหนดค่า Nginx

หากคุณยังไม่ได้ติดตั้ง nginx บนเครื่องพัฒนาของคุณ ใช้เวลาสักครู่แล้วดำเนินการทันที OSX เป็นเรื่องง่ายมากผ่าน homebrew:

brew install nginx

ตอนนี้ เราต้องกำหนดค่า nginx เพื่อส่งต่อคำขอบน localhost:2048 ไปยังเซิร์ฟเวอร์อัปสตรีมผ่านซ็อกเก็ตชื่อ /tmp/socktest.sock . ชื่อนั้นไม่ได้พิเศษอะไร เพียงแค่ต้องตรงกับชื่อซ็อกเก็ตที่ใช้โดยเว็บเซิร์ฟเวอร์ของเรา

คุณสามารถบันทึกการกำหนดค่านี้ไปที่ /tmp/nginx.conf แล้วรัน nginx ด้วยคำสั่ง nginx -c /tmp/nginx.conf เพื่อโหลด

# Run nginx as a normal console program, not as a daemon
daemon off;

# Log errors to stdout
error_log /dev/stdout info;

events {} # Boilerplate

http {

  # Print the access log to stdout
  access_log /dev/stdout;

  # Tell nginx that there's an external server called @app living at our socket
  upstream app {
    server unix:/tmp/socktest.sock fail_timeout=0;
  }

  server {

    # Accept connections on localhost:2048
    listen 2048;
    server_name localhost;

    # Application root
    root /tmp;

    # If a path doesn't exist on disk, forward the request to @app
    try_files $uri/index.html $uri @app;

    # Set some configuration options on requests forwarded to @app
    location @app {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
      proxy_pass https://app;
    }

  }
}

การกำหนดค่านี้ทำให้ nginx ทำงานเหมือนแอปเทอร์มินัลทั่วไป ไม่ใช่เหมือนภูต นอกจากนี้ยังเขียนบันทึกทั้งหมดไปยัง stdout เมื่อคุณเรียกใช้ nginx ควรมีลักษณะดังนี้:

ยูนิคอร์นคุยกับ nginx อย่างไร - บทนำเกี่ยวกับซ็อกเก็ตยูนิกซ์ใน Ruby Nginx ทำงานในโหมดที่ไม่ใช่ภูต

เซิร์ฟเวอร์แอปพลิเคชัน DIY

ตอนนี้เราได้เห็นวิธีเชื่อมต่อ nginx กับโปรแกรมของเราแล้ว การสร้างแอปพลิเคชันเซิร์ฟเวอร์อย่างง่ายเป็นเรื่องง่าย เมื่อ nginx ส่งต่อคำขอไปยังซ็อกเก็ตของเรา จะเป็นคำขอ HTTP มาตรฐาน หลังจากแก้ไขเล็กน้อย ฉันสามารถระบุได้ว่าซ็อกเก็ตส่งคืนการตอบสนอง HTTP ที่ถูกต้อง ซ็อกเก็ตนั้นจะแสดงในเบราว์เซอร์หรือไม่

แอปพลิเคชันด้านล่างรับคำขอและแสดงการประทับเวลา

require "socket"

# Connection creates the socket and accepts new connections
class Connection

  attr_accessor :path

  def initialize(path:)
    @path = path
    File.unlink(path) if File.exists?(path)
  end

  def server
    @server ||= UNIXServer.new(@path)
  end

  def on_request
    socket = server.accept
    yield(socket)
    socket.close
  end
end


# AppServer logs incoming requests and renders a view in response
class AppServer

  attr_reader :connection
  attr_reader :view

  def initialize(connection:, view:)
    @connection = connection
    @view = view
  end

  def run
    while true
      connection.on_request do |socket|
        while (line = socket.readline) != "\r\n"
          puts line 
        end
        socket.write(view.render)
      end
    end
  end

end

# TimeView simply provides the HTTP response
class TimeView
  def render
%[HTTP/1.1 200 OK

The current timestamp is: #{ Time.now.to_i }

]
  end
end


AppServer.new(connection: Connection.new(path: '/tmp/socktest.sock'), view: TimeView.new).run

ตอนนี้ถ้าฉันเปิด nginx และสคริปต์ของฉัน ฉันสามารถไปที่ localhost:2048 คำขอจะถูกส่งไปยังแอพของฉัน และการตอบสนองจะแสดงโดยเบราว์เซอร์ เจ๋งมาก!

ยูนิคอร์นคุยกับ nginx อย่างไร - บทนำเกี่ยวกับซ็อกเก็ตยูนิกซ์ใน Ruby คำขอ HTTP ถูกบันทึกไปยัง STDOUT โดยเซิร์ฟเวอร์แอปธรรมดาของเรา

และนี่คือผลงานอันรุ่งโรจน์ของเรา ดูเถิด! ประทับเวลา!

ยูนิคอร์นคุยกับ nginx อย่างไร - บทนำเกี่ยวกับซ็อกเก็ตยูนิกซ์ใน Ruby เซิร์ฟเวอร์ส่งคืนการประทับเวลาที่แสดงในเบราว์เซอร์