Menu

문서정보

목차

Rack에 대해서

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는 SinatraRuby 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
>> Rack::Handler.constants
=> [:CGI, :FastCGI, :Mongrel, :EventedMongrel, :SwiftipliedMongrel, :WEBrick, :LSWS, :SCGI, :Thin]

루비 표준라이브러리에서 제공하는 WEBrick 핸들러를 사용하기로 했다.
>> 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

Rack의 기본 서비스 포트는 8080이다. 다음과 같이 바인드 설정을 바꿀 수 있다.
Rack::Handler::WEBrick.run my_rack_proc, :Port => 9876, :Host=> "0.0.0.0"

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를 이용해서 자신만의 애플리케이션을 만들 수 있다. 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

애플리케이션을 실행한 후
# rackup config.ru

curl을 이용해서 테스트를 했다.
curl -d "name=Satish%20Talim&text=This%20course%20is%20awesome" localhost:9292
테스트 결과
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

Rack 응용

이것 저것 해보고 있다.

Rack proxy

참고

히스토리