Seongsiks

Being A DevOpser. Powered by
Obtvse, highlight.js, theme toc Creative Commons License
Seongsiks Twitter Github Email
DevOps Ruby On Rails Chef Projects Misc Movies & Drama ME

Rails4-7 Streaming

Overview

네 그렇습니다!! 이제 Rails에서 Streaming을 구현할 수 있습니다. 같이 일하는 동료분중에 node.js를 잘하시는 분이 있어서, 서버의 로그를 실시간으로 웹화면에 Streaming하는것을 보고 감동 받은 적이 있는데.. 이제 node.js없이도 같은 것을 해볼 수 있겠군요.. ^^ 빨리빨리 살펴봅시다.

ActionController::Live

너무나 간단합니다. 그냥 Controller에 한줄을 추가하면 Streaming을 할 수 있는 능력이 우리 컨트롤러에 부여됩니다.

class ItemsController < ApplicationController
  include ActionController::Live
end

그러나 이 기능을 사용하려면 멀티스레드 서버가 필요합니다. 저는 [Unicorn]의 빅팬이었는데요. Unicorn은 기본적으로 30초 이상 worker가 응답하지 않으면 해당 worker을 죽이고 다시 worker를 만듭니다. 그래서 streaming이 30초 이상 이어지면 해당 worker을 죽여서 streaming이 끊어지게 만듭니다. Streaming을 하려면 PUMA, Rainbow, thin중 하나를 사용해야합니다.

자 ActionController::Live을 include한 후, 실제 액션은 다음과 같이 만듭니다.

 def events
   response.header['Content-Type'] = "text/event-stream"
   3.times {
     response.stream.write "Hello!"
     sleep 1
   }
   response.stream.close
 end

여기서 중요한 부분은 마지막에 response.stream.close로 Stream을 반드시 닫아줘야 한다는 것과 response의 헤더를 반드시 #write메소드를 호출하기 전에 "text/event-stream"으로 설정해야한다는 것입니다. 스트림을 닫지 않으면 커넥션이 평생 닫히지 않습니다.

 # File name : app/views/owners/show.html.erb
 <ul id="item"></ul>

view가 위처럼 있다고 가정합니다. 여기에 Stream을 받아줄 javascript을 작성합니다.

# File name: app/assets/javascripts/owners.js
$(document).ready(intialize);
function initialize() {
  var source = new EventSource('/items/events');
  source.addEventListener('message', update);
}

function update(event) {
  var item = $('<li>').text(event.data);
  $('#items').append(item);
}

Redis를 이용한 Live Streaming

redis를 Messaging System으로 이용해서 redis event을 브라우져에 보낼 수 있습니다.

def events
  response.header['Content-Type'] = "text/event-stream"
  redis = Redis.new
  redis.subscribe('item.create') do |on|
    on.message do |event, data|
      response.stream.write("data: #{data}\n\n")
    end
  end
  response.stream.close
end

Turbolink

우리가 anchor 태그를 클릭하면 당연히 화면은 한번 다시 로드됩니다. 그래서 약간 깜박임을 느낄 수 있는데, Turbolink를 사용하면 이제 그런 깜박임이 없어집니다. 어떻게 가능할까요?
Rails4부터는 Default!!로 링크는 이 Turbolink를 사용할게 되는데요. 사용자가 링크를 클릭하게 되면 ajax로 페이지를 가져오고 현재 화면에서 title 태그body태그의 내용만 바꿔치기 하게 됩니다. 그리고 다른 모든 asset들은 다시 요청되지 않습니다.

그럼 실제로 asset이 변경됐을때는 어떻게 하나요? 아래처럼 하세요

<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>

난 그런거 싫어요 한다면?

# File name : Gemfile
gem 'turbolinks'
# File name : assets/javascript/application.js
//= require turbolinks

위에 내용을 지워버리면 됩니다.

그런데요? 저는 이 블로그를 만드는데도 그랬지만 웬만한 자바스크립트는 모두 $(document).ready()안에 넣습니다. DOM이 완전히 렌더 되지 않은 상태에서 자바스크립트가 실행되는게 저는 웬지 꺼림직해서요. 그런데 이 Turbolink와 ready()이벤트는 서로 상성이 좋지 않다는 군요.
왜냐면 기본적으로 Turbolink는 화면을 리로드하지 않음으로써 속도 향상을 얻는데, ready()이벤트는 화면이 리로드되지 않으면 발생하지 않으니깐요. 이 문제를 해결해 보겠습니다.

Turbolinks Events

 // File name : assets/javascripts/owner.js
function initialize() {
  $('#owner_active').click( function() {
    alert(this.checked);
  });
}
$(document).ready(initialize);
$(document).on('page:load', initia lize);

page:load 이벤트는 turbolink가 발생시켜주는 이벤트로 turbolink을 통해서 페이지가 로드 되었을때 발생하게 됩니다.

jquery-turbolinks gem

만약에 jquery를 사용하고 있다면 gem을 이용해서 간단하게 처리 가능합니다. 저는 이 방법을 쓰렵니다.

# File name : Gemfile
gem 'jquery-turbolinks'
------------------------------------------------
# File name : app/assets/javascripts/application.js
#= require jquery
#= require jquery.turbolinks
#= require jquery_ujs
#= require turbolinks

이 부분에서 중요한것은 application.js파일에서 jquery를 require한 바로 다음에 넣어줘야 한다는 겁니다. 이 gem은 page:load이벤트가 발생한다음 jquery의 ready()이벤트도 항상 발생하도록 해줍니다.

Other Turbolinks Events

계속해서 page:load이벤트만 얘기했는데요. 이것 말고도 다른 turbolink의 이벤트 들이 있습니다. 페이지가 로딩되는동안 바람개비를 돌릴려고 한다고 가정해 볼까요?

// File name : assets/javascripts/owner.js
$(document).on('page:fetch', function() {
  $('#vane').show();
});

$(document).on('page:change', function() {
  $('#vane').hide();
});

page:fetch는 페이지를 요청했을때, page:change는 가져온 페이지로 화면을 바꾸고 났을때 발생하는 이벤트 입니다. Turbolink가 눌러졌을때 발생하는 이벤트를 순서대로 보면 다음과 같습니다.

  • page:fetch : 페이지를 가져오기 시작할때
  • page:receive : 페이지를 가져오기는 완료했고, 아직 파싱하지 않은 상태
  • page:load : 페이지 가져오기를 완료했을때
  • page:change : 새로 가져온 페이지 내용으로 화면을 바꾸는것을 완료했을때

Selective Disablling

그런데 이 특정 링크는 좀.. 다 리로드 했으면 좋겠다 할때는 다음과 같이 사용하시면 됩니다.

<%= link_to 'Requests', request_path, "data-no-turbolink" => true %>

그리고 부모 Element가 data-no-turbolink property가 true로 되어 있으면 하위 모든 링크도 모두 비활성화 됩니다.

<div id="navigation" data-no-turbolink="true">
  <%= link_to 'Show', @request %> |
  <%= link_to, 'Back', request_path %>
</div>

자 이렇게 Rails4의 새로운 기능과 deprecated 된 메소드들을 소개하는 시리즈 포스트는 마지막입니다. 이번 포스트를 정리하면서 Streaming 기능은 너무 기대되네요. 이번 기회에 이 블로그에 채팅기능을 붙여볼까 생각하고 있습니다. 붙인 다음 그 과정도 공유할 수 있으면 같이 공유하겠습니다.


[이전 Rails4-6 Etags n Cache]
comments powered by Disqus
Back to Ruby On Rails