Unicorn nói chuyện với nginx bằng cách nào - giới thiệu về unix sockets trong ruby
Bài đăng này đã không được cập nhật trong 3 năm
Tài liệu: How unicorn talks to nginx - an introduction to unix sockets in Ruby
Ruby application servers được sử dụng điển hình cùng với một web server giống như là nginx. Khi người dùng request một page từ ứng dụng rails của bạn, nginx ủy quyền request cho application server. Nhưng chính xác thì công việc này được thực hiện như thế nào? nginx nói chuyện với unicorn bằng cách nào?
Một trong những lựa chọn có hiệu quả nhất là sử dụng unix sockets. Hãy xem chúng làm việc như thế nào! Trong bài viết này chúng ta sẽ bắt đầu với cơ bản về sockets, và kết thúc bằng việc tạo ra một application server đơn giản được ủy quyền bởi nginx.
Sockets cho phép các chương trình nói chuyện với nhau như thể chúng đang viết hay đang đọc từ một file. Trong ví dụ này, Unicorn tạo ra một socket và điều chỉnh nó để kết nối. Nginx có thể sau đó kết nối tới socket và nói chuyện với Unicorn.
Unix socket là gì? Unix sockets cho phép một chương trình nói chuyện với một chương trình khác bằng cách tương tự như làm việc với các file. Chúng là một kiểu của IPC viết tắt của inter-process communication(hành động trao đổi dữ liệu giữa các tiến trình riêng biệt, sử dụng giao thức kết nối).
Để có thể truy cập thông qua một socket, đầu tiên chương trình của bạn cần tạo một socket và lưu tới ổ đĩa, giống như là một file. Chương trình điều chỉnh socket để kết nối đi vào. Khi chương trình tiếp nhận kết nối, nó sẽ sử dụng các phương thức IO chuẩn để đọc và viết dữ liệu.
Ruby cung cấp mọi thứ bạn cần để làm việc với unix sockets thông qua một cặp các lớp:
- UNIX Server: tạo một socket, lưu tới ổ đĩa, và cho phép bạn điều chỉnh để kết nối mới.
- UNIX Socket: mở các socket đã tồn tại cho IO
**Chú ý: ** tồn tại các loại socket khác, đáng chú ý nhất đó là TCP sockets. Nhưng trong bài viết này chỉ đề cập tới unix sockets. Bạn có biết sự khác biệt? Unix sockets có các tên file.
The Simplest Socket Chúng ta sẽ đi xem xét hai chương trình sau.
Đầu tiên là một "server". Đơn giản chỉ là tạo ra một instance của lớp UnixServer
, sau đó sử dụng server.accept
để đợi một kết nối. Khi tiếp nhận một kết nối, nó trao đổi một lời chào.
Đáng chú ý là cả hai phương thức accept
vàreadline
làm cho khối chương trình thực thi cho đến khi chúng nhận được những gì chúng đang đợi.
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
Như vậy chúng ta đã có một server. Bây giờ tất cả chúng ta cần là một client.
Trong ví dụ phía dưới, chúng ta mở một socket đã được tạo bằng server của chúng ta. Sau đó sử dụng các phương thức IO để gửi và tiếp nhận lời chào.
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
Để xem kết quả đầu tiên chúng ta cần chạy server. Sau đó chạy client. Kết quả bạn có thể nhìn thấy như hình bên dưới:
Ví dụ về giao tiếp đơn giản giữa UNIX socket client và UNIX socket server. Client ở bên trái còn server ở bên phải trên hình minh họa.
Giao tiếp với nginx
Bây giờ chúng ta đã biết cách tạo một unix socket "server" chúng ta có thể dễ dàng giao tiếp với nginx.
Tôi sẽ sửa code ở trên để in ra mọi thứ nhận được từ socket.
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
Bây giờ tôi cấu hình nginx để forward requests tới socket ở /tmp/socktest.sock
. Tôi có thể nhìn thấy những gì dữ liệu nginx đang gửi.
Khi tôi tạo ra một web request, nginx gửi dữ liệu sau đây tới server của tôi:
Nó chỉ là một HTTP request thông thường với một ít headers được thêm vào. Bây giờ chúng ta đã sẵn sàng xây dựng một app server thật. Nhưng đầu tiên, hãy nói qua về cấu hình nginx.
Cài đặt và cấu hình Nginx
Nếu bạn chưa cài đặt nginx trên development machines của mình, hãy bỏ ra vài phút để cài đặt nó ngay bây giờ. Nó thực sự dễ dàng trên OSX cài đặt thông qua homebrew:
brew install nginx
Bây giờ chúng ta cần cấu hình nginx để forward requests trên localhost:2048 tới một upstream server thông qua một socket được đặt tên là /tmp/socktest.sock
. Tên này không có bất kỳ điều gì đặc biệt. Nó chỉ là cần để phù hợp với tên socket được sử dụng bời web server của chúng ta.
Bạn có thể lưu cấu hình này tới /tmp/nginx.conf
và sau đó chạy nginx với câu lệnh nginx -c /tmp/nginx.conf
để load nó.
# 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 http://app;
}
}
}
Cấu hình này tạo ra nginx để chạy giống như một terminal app thông thường, không giống một deamon. Nó cũng viết tất cả các log tới stdout. Khi bạn chạy nginx nó nên nhìn giống như ảnh ở bên dưới:
Nginx đang chạy trong non-deamon mode.
A DIY Application Server
Bây giờ chúng ta đã biết làm cách nào kết nối nginx tới chương trình của chúng ta, nó là một vấn đề đơn giản để xây dựng một application server đơn giản. Khi nginx forward một request tới socket của chúng ta nó là một HTTP request chuẩn. Sau một ít việc làm tôi có thể xác định được rằng nếu socket trả về một HTTP response hợp lệ, thì nó sẽ được hiển thị trên trình duyệt.
Ứng dụng phía dưới lấy một request bất kỳ và hiển thị ra một timestamp.
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
Bây giờ bật nginx, và đi tới địa chỉ localhost:2048. Các request sẽ được gửi tới app của tôi và các response sẽ được render bởi trình duyệt.
HTTP request được log tới STDOUT bởi app server của chúng ta.
Và đây là kết quả :
Server trả về một timestamp và nó được hiển thị trong trình duyệt.
All rights reserved