Rack은 Ruby 기반의 웹 애플리케이션 개발을 위한 인터페이스를 제공하는 소프트웨어다. Rack의 가장 간단한 응용은 웹서버의 요청을 받아서 웹 프레임워크로 전달하고 응답을 웹서버로 전달하는 미들웨어 소프트웨어의 개발이다. Rack는 웹 서버로의 요청을 처리해서 웹 프레임워크로 전달하고, 웹 프레임워크의 응답을 처리해서 웹 서버로 전달하기 위한 일련의 API들을 제공한다.
Sinatra같은 웹 프레임워크는 Rack을 내장하고 있는데, 이 Rack를 이용해서 웹 애플리케이션과 웹 서버를 연결할 수 있다. Rack은 WEBrick, Mongrel 등의 웹 서버를 연결하기 위한 handler를 포함하고 있다. 또한 Sinatra와 Rails등을 연결하기 위한 adapter를 가지고 있다.
아래는 Rack 애플리케이션의 구조다.
Rack은 Handler를 통해서 웹서버와 연결을 하고, 다른 한편으로는 Adapter를 통해서 웹 프레임워크와 연결을 한다. Handler와 Adapter 사이에는 Middleware가 있어서, 이 둘 사이의 정보교환을 중계한다. Middle는 정보교환을 중계하면서, 동시에 정보를 처리해서 전달할 수도 있다.
Rack이 있는 덕분에 개발자는 Merb, Camping, Sinatra, Ramaze, Mayeric, Halcyon 같은 다양한 종류의 웹 프레임워크와 WEBrick, Mongrel, Thin, Ebb등의 웹 서버와 간단하게 연결할 수 있다.
Rack 애플리케이션은 call 메서드로 호출되는 루비 객체로 environment라는 하나의 매개변수만 넘겨받는다. environment은 hash 값인데 (반드시 hash를 true로 해야 한다.), HTTP 요청에 대한 모든 정보를 가지고 있다. Racp 애플리케이션은 environment의 값을 읽어서 처리한후 3개의 값을 배열 형태로 반환한다. 이 값은 status code와 header, 그리고 body로 이들을 이용해서 HTTP 응답을 만든다. status code로 HTTP 응답 코드(200 OK, 404 Not Found 등의)를 만들고 header로 HTTP 헤더를 설정한다. Body는 클라이언트에게 보내는 추가적인 정보다. Header의 경우 반드시 hash 형태로 넘겨야 한다.
Rack는 Sinatra와 Ruby on Rails를 비롯한 거의 모든 루비 웹 프레임워크와 라이브러리에서 사용하고 있다.
Rack app 개발
Rack는 웹 프레임워크와 함께 사용하지만, Rack 그자체로도 웹 애플리케이션의 개발이 가능하다. 웹 프레임워크와 함께 사용하더라도 Rack의 기능을 사용할 필요가 있으니, Rack 그자체를 배우는 것도 의미가 있다.
Hello World
Rack으로 개발한 Hello world를 출력하는 웹 애플리케이션 이다.
require 'rack'
app = lambda do |env|
body = "Hello, World!"
[200, {"Content-Type" => "text/plain", "Content-Length" => body.length.to_s}, [body]]
end
Rack::Handler::WEBrick.run my_rack_proc
Rack gem을 이용한 개발
Rack gem은 Rack 애플리케이션 개발을 도와주기 위한 클래스들을 포함하고 있다. 여기에는 요청, 응답, 쿠키, 세션, 미들웨어 관련 클래스들을 포함한다.
우선 rack을 설치한다.
# gem install rack
Rack Handler
웹 서버와 연결하기 위한 handler를 만든다. Rack gem은 다양한 종류의 웹 서버들을 지원하고 있다.
>> require 'rack'
=> true
>> my_rack_proc = lambda { |env| [200, {"content-type"=>"text/plain", "X-Code"=>"yundream"}, ["Hello. The time is #{Time.now}\n"]] }
=> #<Proc:0x000000018e3ea0@(irb):2 (lambda)>
>> Rack::Handler::WEBrick.run my_rack_proc
[2013-09-24 16:24:01] INFO WEBrick 1.3.1
[2013-09-24 16:24:01] INFO ruby 1.9.3 (2012-04-20) [x86_64-linux]
[2013-09-24 16:24:01] WARN TCPServer Error: Address already in use - bind(2)
[2013-09-24 16:24:01] INFO WEBrick::HTTPServer#start: pid=3817 port=8080
클라이언트 요청이 올경우 handler가 호출할 proc 객체를 만들었다. HTTP 요청은 env로 전달이 된다. HTTP 응답은 배열에 담았다. Status code는 200, 헤더정보에 content-type과 X-Code를 추가했으며, Body로 "Hello. The time is ..."를 출력하게 했다.
curl로 테스트..
# curl localhost:8080 -D header.txt
Hello. The time is 2013-09-24 16:42:03 +0900
# cat header.txt
HTTP/1.1 200 OK
Content-Type: text/plain
X-Code: yundream
Server: WEBrick/1.3.1 (Ruby/1.9.3/2012-04-20)
Date: Tue, 24 Sep 2013 07:42:03 GMT
Content-Length: 45
Lambda 대신 메서드를 만들어서 호출할 수도 있다. 개인적으로 메서드를 만들어서 돌리는게 깔끔한 것 같다.
def my_method env
pp env
[200,
{"content-type"=>"text/plain", "X-Code"=>"yundream"},
["Hello. The time is #{Time.now}\n"]]
end
Rack::Handler::WEBrick.run method(:my_method), :Port => 9876, :Host=> "0.0.0.0", :debug => true
Rackup 이용하기
rackup을 이용하면, rack 애플리케이션을 쉽게 실행할 수 있다. rackup은 설정파일을 기반으로 작동하는 프로그램이다. 다음은 rackup 설정파일이다.
# cat config.ru
run lambda { |env| [200, {"content-type"=>"text/plain", "X-Code"=>"yundream"}, ["Hello. The time is #{Time.now}"]] }
설정파일에는 run이 있는데, run뒤에 있는 lambda 객체에 대한 .call 메서드를 호출한다.
rackup을 이용해서 rack 애플리케이션을 실행해보자.
# rackup config.ru
>> Thin web server (v1.5.1 codename Straight Razor)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:9292, CTRL+C to stop
127.0.0.1 - - [24/Sep/2013 20:50:28] "GET / HTTP/1.1" 200 - 0.0006
127.0.0.1 - - [24/Sep/2013 20:50:28] "GET /favicon.ico HTTP/1.1" 200 - 0.0006
기본 바인드 주소는 "0.0.0.0:9292"다. 바인드 주소를 "0.0.0.0:8080"으로 바꿔보자.
# rackup config.ru -o "0.0.0.0" -p 8080
>> Thin web server (v1.5.1 codename Straight Razor)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:8080, CTRL+C to stop
편하다.
run에는 람다함수 외에 클래스를 호출하도록 할 수 있다.
# cat my_app.rb
class MyApp
def call env
[200, {"Content-Type" => "text/html"}, ["Hello Rack Participants"]]
end
end
# cat config.ru
require './my_app'
run MyApp.new
rack 애플리케이션을 실행해보자.
>> Thin web server (v1.5.1 codename Straight Razor)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:9292, CTRL+C to stop
Rack::Request, Rack::Response
프레임워크를 이용하지 않은 상태에서 웹 애플리케이션이나 미들웨어를 개발할 경우 Rack이 제공하는 helper를 이용해서 자신만의 애플리케이션을 만들 수 있다.
Rack::Request : 유저 요청을 파싱하고 멀티파트 문서를 제어한다.
Rack::Response : cookie를 포함한 HTTP 응답을 만든다.
Rqeust, Response를 이용해서 유저 요청을 처리하고 응답하는 애플리케이션을 만들었다.
class MyRequest
def call(env)
req = Rack::Request.new(env)
name = req.params['name']
text = req.params['text']
Rack::Response.new.finish do |res|
res['Content-Type'] = 'text/plain'
res.status = 200
str = "Parameters sent: name - #{name} | text - #{text}"
res.write str
end
end
end
Parameters sent: name - Satish Talim | text - This course is awesome
Middleware 개발
미들웨어는 웹 서버와 프레임워크 사이에서 통신을 중계하는 역할을 한다. 서버에서 넘어온 HTTP 요청을 필요에 따라 변경해서 프레임워크로 넘기고, 프레임워크의 응답역시 미리 처리 한다음 웹 서버로 넘기는 일을 한다.
Rack::Reloader
애플리케이션을 수정하면, rackup 프로세스를 새로 시작해야 한다. reloader를 이용하면 프로세스의 재시작 없이 변경내용을 즉시 적용할 수 있다.
# cat config.ru
require './my_app'
use Rack::Reloader
run MyApp.new
my_app.rb의 body 텍스트를 변경해보자. 프로세스 재시작없이 변경내용이 적용된걸 확인할 수 있을 거다. 내부적으로 stat(2) 시스템콜을 호출해서 파일의 변경 여부를 확인한 후 코드를 새로 읽는 방식이기 때문에 성능 저하를 걱정하지 않아도 된다.
미들웨어 개발하기
유저 응답의 body 부분을 수정 해서 웹 애플리케이션에 전송하는 간단한 미들웨어를 만들어보려한다. 이를 위해서 MyRackMiddleware라는 미들웨어 클래스를 만든다.
# cat myrackmiddleware.rb
class MyRackMiddleware
def initialize(appl)
@appl = appl
end
def call(env)
status, headers, body = @appl.call(env)
append_s = "... greetings from RubyLearning!!"
new_body = ""
body.each { |string| new_body << " " << string}
new_body << " " << append_s
[status, headers, [new_body]]
end
end
rack 애플리케이션이다.
# cat my_app.rb
class MyApp
def call env
[200, {"Content-Type" => "text/html"}, ["Hello Rack Participants from across the globe"]]
end
end
config.ru
# cat config.ru
require './my_app'
require './myrackmiddleware'
use Rack::Reloader
use MyRackMiddleware
run MyApp.new
애플리케이션을 실행해보자.
# rakup config.ru
브라우저로 테스트를 해보면, MyApp의 응답이 미들웨어를 거치면서 수정된걸 확인할 수 있다.
Hello Rack Participants from across the globe... greetings from RubyLearning!!
다른 미들웨어 예제
유저가 /hello를 요청할 경우 미들웨어가 대신 응답을 만드는 코드다.
module MyMiddleware
class Hello
def initialize(app)
@app = app
end
def call(env)
if env['PATH_INFO'] == '/hello'
[200, {"Content-Type" => "text/plain"}, ["Hello from the middleware!"]]
else
@app.call(env)
end
end
end
end
config.ru
require './my_app'
require './myrackmiddleware'
use MyMiddleware::Hello
run Proc.new{|env| [200, {"Content-Type" => "text/plain"}, ['Try accessing visiting /hello']] }
애플리케이션을 실행 한드음 localhost:9292로 접근하면, "Try accessing visiting /hello"를 출력한다. localhost:9292/hello를 요청하면 "Hello from the middleware"를 출력한다.
Using Lobster
Rack은 데모용으로 사용할 수 있는 "Lobster"이라는 이름의 조그마한 웹 애플리케이션을 가지고 있다.
require 'rack/lobster'
run Rack::Lobster.new
심심풀이로 한번 테스트해보자.
Rack::Builder
웹 애플리케이션은 여러 개의 소프트웨어 스택으로 이루어진다. 스택의 제일 밑에는 웹 애플리케이션이 있을 테고, 그 위에 미들웨어가 있고, 그 위에 웹서버가 있다. 로깅을 위한 소프트웨어가 들어갈 수도 있다. Rack::Builder를 이용해서, 여러 단계의 소프트웨어 스택을 하나로 묶을 수 있다.
테스트에 사용할 간단한 웹 애플리케이션이다.
# cat config.ru
rack_time = Proc.new { |env| [200, {"Content-Type" => "text/plain"}, ["Hello. The time is #{Time.now}"]] }
Rack::Handler::WEBrick.run rack_time, :Port => 9292
rackup으로 실행 한 후 웹 브라우저로 http://localhost:9292를 요청하면 아래와 같은 메시지를 확인할 수 있다.
Hello. The time is 2011-12-06 11:19:49 +0530
Rack::Builder를 이용해서, 애플리케이션 스택에 웹 애플리케이션을 추가해보자.
rack_time = Proc.new { |env| [200, {"Content-Type" => "text/plain"}, ["Hello. The time is #{Time.now}"]] }
builder = Rack::Builder.new do
run rack_time
end
Rack::Handler::WEBrick.run builder, :Port => 9292
Builder를 이용해서 Logger를 추가했다.
require 'logger'
rack_time = Proc.new { |env| [200, {"Content-Type" => "text/plain"}, ["Hello. The time is #{Time.now}"]] }
builder = Rack::Builder.new do
use Rack::CommonLogger
Logger.new('rack.log')
run rack_time
end
Rack::Handler::WEBrick.run builder, :Port => 9292
애플리케이션을 실행하면 rack.log 파일이 만들어진다.
# cat rack.log
# Logfile created on 2013-09-28 15:10:24 +0900 by logger.rb/31641
Rack::Builder#map을 이용하면 url 기반으로 로직을 만들 수 있다.
require 'logger'
rack_app = Rack::Builder.new do
use Rack::CommonLogger
Logger.new('rack.log')
map "/" do
run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["This is public page"]] }
end
map "/secret" do
use Rack::Auth::Basic, "Restricted Area" do |user, password|
user == 'super' && password == 'secretsauce'
end
map "/" do
run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["This is a secret page"]] }
end
map "/files" do
run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["Here are the secret files"]] }
end
end
end
Rack::Handler::WEBrick.run rack_app, :Port => 9292
http://localhost:9292/secret 를 실행하면, HTTP 인증창이 뜬다.
Sinatra에서 Rack 활용
Sinatra 와 Rack 미들웨어
Sinatra는 Rack을 기반으로 하는 웹 프레임워크로, 루비 웹 프레임워크가 가져야할 최소한의 표준 인터페이스를 제공한다. Sinatra의 가장 흥미로운 기능은 미들웨어의 개발을 지원한다는 점일 것이다. 개발자는 웹 서버와 애플리케이션을 모니터링하고 이 둘 사이의 HTTP 요청과 응답을 수정하고 제어할 수 있다.
Sinatra는 탑 레벨의 use 메서드를 이용해서 (쉽게) Rack 미들웨어를 만들 수 있다.
Sinatra Rack 애플리케이션 테스트를 위해서 racksinatra 디렉토리를 만들자. 이제 sinatra를 설치한다.
# gem install sinatra
Sinatra를 위한 Rack 미들웨어를 만들어보자. 이 미들웨어는 HTTP요청에서 응답까지 걸린시간을 측정해서 콘솔에 출력하는 일을 한다.
# cat rackmiddleware.rb
class RackMiddleware
def initialize(appl)
@appl = appl
end
def call(env)
start = Time.now
status, headers, body = @appl.call(env) # call our Sinatra app
stop = Time.now
puts "Response Time: #{stop-start}" # display on console
[status, headers, body]
end
end
Rack 미들웨어를 이용하도록 sinatra 애플리케이션을 수정했다.
# my_sinatra.rb
require 'sinatra'
require './rackmiddleware'
use RackMiddleware
get '/' do
'Welcome to all'
end
Sinatra 애플리케이션을 실행하고 웹 브라우저로 테스트를 해보자.
ruby my_sinatra.rb
== Sinatra/1.4.2 has taken the stage on 4567 for development with backup from Thin
>> Thin web server (v1.5.1 codename Straight Razor)
>> Maximum connections set to 1024
>> Listening on localhost:4567, CTRL+C to stop
Response Time: 0.010941526
127.0.0.1 - - [05/Oct/2013 12:42:18] "GET / HTTP/1.1" 200 14 0.0114
Response Time: 0.00055726
응답까지 걸린시간을 출력하는 걸 확인할 수 있다.
Sinatra 와 Rack HTTP 기반 인증
Rack을 이용하면 Sinatra 애플리케이션에 HTTP 기반 인증을 (간단하게)추가할 수 있다.
require 'sinatra'
use Rack::Auth::Basic, "Restricted Area" do |user, password|
user == 'yundream' && password == '12345a'
end
get '/' do
"This is a secret page"
end
get '/files' do
"Here are the secret files"
end
Contents
Rack에 대해서
Rack app 개발
Hello World
Rack gem을 이용한 개발
Rack Handler
Rackup 이용하기
Rack::Request, Rack::Response
Middleware 개발
Rack::Reloader
미들웨어 개발하기
다른 미들웨어 예제
Using Lobster
Rack::Builder
Sinatra에서 Rack 활용
Sinatra 와 Rack 미들웨어
Sinatra 와 Rack HTTP 기반 인증
Rack 응용
Rack proxy
참고
히스토리
Recent Posts
Archive Posts
Tags