12/08/2018, 14:39

Unicorn nói chuyện với nginx bằng cách nào - giới thiệu về unix sockets trong ruby

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ì ...

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 acceptvà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?
")

    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) != "
"
              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.

0