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

เชื่อมต่อ Unicorn กับ Nginx: คู่มือ Unix Sockets ใน Ruby

Ruby application servers มักใช้ร่วมกับ web server เช่น nginx เมื่อ user request หน้าจาก Rails app nginx จะส่ง request ให้ application server แต่ทำงานจริงอย่างไร? nginx คุยกับ unicorn ยังไง?

ตัวเลือกที่มีประสิทธิภาพคือใช้ unix sockets มาดูการทำงาน! โพสต์นี้เริ่มจากพื้นฐาน sockets จนสร้าง application server เองที่ถูก proxy โดย nginx

เชื่อมต่อ Unicorn กับ Nginx: คู่มือ Unix Sockets ใน Ruby Sockets ให้โปรแกรมคุยกันเหมือนเขียน/อ่านไฟล์ Unicorn สร้าง socket และรอ connections Nginx connect ไป socket และคุยกับ Unicorn

Unix Socket คืออะไร?

Unix sockets ให้โปรแกรมหนึ่งคุยกับอีกตัวหนึ่งแบบคล้ายทำงานกับไฟล์ เป็นประเภทของ IPC (inter-process communication)

เพื่อให้เข้าถึงได้ผ่าน socket โปรแกรมสร้าง socket และ "save" ไว้บน disk เหมือนไฟล์ มัน monitor socket รอ incoming connections เมื่อได้ connection ใช้ standard IO methods อ่าน/เขียนข้อมูล

Ruby มีทุกอย่างสำหรับ unix sockets ผ่านคลาสสองตัว:

  • UNIXServer - สร้าง socket, save ลง disk, monitor connections ใหม่
  • UNIXSocket - เปิด existing sockets สำหรับ IO

หมายเหตุ: มี sockets ชนิดอื่นๆ เช่น TCP sockets แต่โพสต์นี้เน้น unix sockets แยกกันยังไง? Unix sockets มีชื่อไฟล์

Socket เรียบง่ายที่สุด

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

ตัวแรกคือ "server" สร้าง instance ของ UnixServer ใช้ server.accept รอ connection เมื่อได้ connection แลกเปลี่ยน greeting

ควรทราบว่า accept และ readline ทั้งคู่ block execution จนกว่าจะได้สิ่งที่รอ


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

ตอนมี server ต้องการ client

ตัวอย่างด้านล่างเปิด socket ที่ server สร้าง ใช้ IO methods ปกติส่ง/รับ greeting


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

ทดสอบ: รัน server ก่อน แล้วรัน client ผลลัพธ์:

เชื่อมต่อ Unicorn กับ Nginx: คู่มือ Unix Sockets ใน Ruby ตัวอย่าง UNIX socket client/server interaction Client ด้านซ้าย Server ด้านขวา

Interface กับ nginx

ตอนรู้สร้าง unix socket "server" แล้ว interface กับ nginx ง่ายมาก

ไม่เชื่อ? ทดสอบ proof-of-concept เร็วๆ ปรับโค้ดข้างต้นให้ print ทุกอย่างที่รับจาก socket


require "socket"
# สร้าง socket และ "save" ลง filesystem
server = UNIXServer.new('/tmp/socktest.sock')
# รอ connection (โดย nginx)
socket = server.accept
# อ่านทุกอย่างจาก socket
while line = socket.readline
  puts line.inspect
end
socket.close

ตอนนี้ config nginx forward requests ไป socket ที่ /tmp/socktest.sock (config จะอธิบายต่อไป)

เวลาทำ web request nginx ส่งข้อมูลมาให้ server เล็กๆ นี้:

เชื่อมต่อ Unicorn กับ Nginx: คู่มือ Unix Sockets ใน Ruby

เก๋! มันแค่ HTTP request ธรรมดาพร้อม headers เพิ่มเติม ตอนนี้พร้อมสร้าง real app server แต่มาคุย nginx config ก่อน

ติดตั้งและ Config Nginx

ยังไม่มี nginx บน development machine ติดตั้งเลย ง่ายบน macOS ผ่าน homebrew:


brew install nginx

config nginx forward requests บน localhost:2048 ไป upstream server ผ่าน socket /tmp/socktest.sock ชื่อนี้ไม่พิเศษ แค่ต้อง match กับ socket name ของ web server

save config ไว้ /tmp/nginx.conf รัน 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 access log to stdout
  access_log /dev/stdout;
  # Tell nginx external server @app อยู่ที่ 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;
    # ถ้า path ไม่อยู่บน disk forward request ไป @app
    try_files $uri/index.html $uri @app;
    # Config options สำหรับ requests ที่ forward ไป @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;
    }
  }
}

Config นี้ทำให้ nginx รันเหมือน terminal app ไม่ใช่ daemon เขียน logs ไป stdout ตอนรัน nginx จะประมาณนี้:

เชื่อมต่อ Unicorn กับ Nginx: คู่มือ Unix Sockets ใน Ruby Nginx รันใน non-daemon mode

DIY Application Server

ตอนรู้ connect nginx กับโปรแกรมแล้ว สร้าง simple application server ง่ายมาก nginx forward request มาที่ socket เป็น standard HTTP request หลังลองผิดลองถูก พบว่าถ้า socket คืน valid HTTP response จะแสดงใน browser ได้

Application ด้านล่างรับ request ใดๆ แสดง timestamp


require "socket"

# Connection สร้าง socket และ accept 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 log incoming requests และ render view ตอบกลับ
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 ให้ 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

ตอนนี้ run nginx และสคริปต์ ไป localhost:2048 Requests ส่งไป app Responses render บน browser เก๋!

เชื่อมต่อ Unicorn กับ Nginx: คู่มือ Unix Sockets ใน Ruby HTTP requests log ไป STDOUT โดย simple app server

ผลงานของเรา: Timestamp!

เชื่อมต่อ Unicorn กับ Nginx: คู่มือ Unix Sockets ใน Ruby Server คืน timestamp แสดงบน browser