Ruby 애플리케이션 서버는 일반적으로 nginx와 같은 웹 서버와 함께 사용됩니다. 사용자가 Rails 앱에서 페이지를 요청하면 nginx는 요청을 애플리케이션 서버에 위임합니다. 하지만 정확히 어떻게 작동합니까? nginx는 유니콘과 어떻게 대화하나요?
가장 효율적인 옵션 중 하나는 유닉스 소켓을 사용하는 것입니다. 그들이 어떻게 작동하는지 봅시다! 이 게시물에서는 소켓의 기본부터 시작하여 nginx에 의해 프록시되는 간단한 애플리케이션 서버를 만드는 것으로 끝납니다.
소켓을 사용하면 마치 파일에 쓰거나 파일을 읽는 것처럼 프로그램이 서로 대화할 수 있습니다. 이 예에서 Unicorn은 소켓을 만들고 연결을 모니터링합니다. Nginx는 소켓에 연결하여 Unicorn과 대화할 수 있습니다.
유닉스 소켓이란 무엇입니까?
유닉스 소켓은 파일 작업과 유사한 방식으로 한 프로그램이 다른 프로그램과 통신할 수 있도록 합니다. 일종의 IPC 또는 프로세스 간 통신입니다.
소켓을 통해 액세스할 수 있도록 프로그램은 먼저 소켓을 만들고 파일처럼 디스크에 '저장'합니다. 들어오는 연결에 대해 소켓을 모니터링합니다. 수신하면 표준 IO 방법을 사용하여 데이터를 읽고 씁니다.
Ruby는 몇 가지 클래스를 통해 유닉스 소켓으로 작업하는 데 필요한 모든 것을 제공합니다.
-
UNIX*서버 * - 소켓을 만들어 디스크에 저장하고 새 연결을 모니터링할 수 있습니다.
-
UNIX*소켓 * - IO용 기존 소켓을 엽니다.
참고: 다른 종류의 소켓이 있습니다. 특히 TCP 소켓. 그러나 이 포스트는 유닉스 소켓만을 다룹니다. 차이점을 어떻게 알 수 있습니까? 유닉스 소켓에는 파일 이름이 있습니다.
가장 단순한 소켓
우리는 두 개의 작은 프로그램을 살펴볼 것입니다.
첫 번째는 "서버"입니다. 단순히 UnixServer
의 인스턴스를 생성합니다. 클래스를 선택한 다음 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
시연하려면 먼저 서버를 실행해야 합니다. 그런 다음 클라이언트를 실행합니다. 아래에서 결과를 볼 수 있습니다.
간단한 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
이제 /tmp/socktest.sock
에 있는 소켓으로 요청을 전달하도록 nginx를 구성하면 nginx가 보내는 데이터를 볼 수 있습니다. (걱정하지 마세요. 잠시 후에 구성에 대해 논의하겠습니다.)
웹 요청을 하면 nginx는 다음 데이터를 내 작은 서버로 보냅니다.
정말 멋진! 몇 가지 추가 헤더가 추가된 일반적인 HTTP 요청입니다. 이제 실제 앱 서버를 구축할 준비가 되었습니다. 하지만 먼저 nginx 구성에 대해 논의하겠습니다.
Nginx 설치 및 구성
개발 머신에 nginx가 아직 설치되어 있지 않다면 잠시 시간을 내어 지금 설치하십시오. homebrew를 통해 OSX에서 정말 쉽습니다.
brew install nginx
이제 /tmp/socktest.sock
라는 소켓을 통해 localhost:2048에 대한 요청을 업스트림 서버로 전달하도록 nginx를 구성해야 합니다. . 그 이름은 특별한 것이 아닙니다. 웹 서버에서 사용하는 소켓 이름과 일치하기만 하면 됩니다.
이 구성을 /tmp/nginx.conf
에 저장할 수 있습니다. 그런 다음 nginx -c /tmp/nginx.conf
명령으로 nginx를 실행합니다. 로드합니다.
# 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.
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로 이동할 수 있습니다. 요청이 내 앱으로 전송됩니다. 그리고 응답은 브라우저에서 렌더링됩니다. 멋지네요!
HTTP 요청은 간단한 앱 서버에 의해 STDOUT에 기록됩니다.
그리고 여기에 우리 노력의 영광스러운 열매가 있습니다. 보다! 타임스탬프!
서버는 브라우저에 표시되는 타임스탬프를 반환합니다.