2021-01-01(Fri)

항목

내용

학습 날짜

2021-01-01(금)

학습 시간

09:00~22:00

학습 범위 및 주제

루비온레일즈 컨트롤러

학습 목표

퍼펙트 루비온레일즈 컨트롤러 챕터를 독파한다.

동료 학습 방법

eunhkim, sanam, yohlee, jujeong님과 원격회의로 궁금한 점을 나눔

상세 학습 내용

책을 훑으며 아래처럼 간단하게 메모를 진행했다.

컨트롤러 개발

요청 정보 추출 - params 메서드

클라이언트에서 전달된 요청 정보에 params[:<매개 변수 이름>]의 형식으로 접근할 수 있다.

params 메서드에서 추출할 수 있는 요청 정보는 아래와 같다.

종류

설명

포스트 데이터

<form method="POST">로 정의된 입력 양식에서 전달된 값

쿼리 정보

URL 끝에 "?"이 붙고, "<키 값>=<값>&.."형식으로 지정된 정보

라우트 매개 변수

라우트에서 정의한 매개 변수("/books/1"에서 "1" 부분)

params 메서드로 배열을 전달할 때는 다음과 같이 키 이름의 뒤에 를 붙여줘야한다.

~/ctrl/para_array?category[]=rails&category[]=ruby

대량 할당 취약성을 피하는 방법

대량 할당(Mass Assignment)는 액티브 레코드에 있는 기본적인 기능 중 하나로, 모델의 필드를 한꺼번에 설정하는 것을 의미한다.

만약 아래처럼 new, update_attributes 등의 메서드에 "<필드 이름>: <값>" 형태로 구성된 해시를 전달하면 해당 속성을 사용해 객체를 설정하는 방법을 쓴다고 가정해보자.

@book = Book.new(params[:book])
@book = Book.find(params[:id])
@book.update_attributes(params[:book])

이 경우 우리 의도와 달리 params에 role 등의 권한과 관련된 필드를 수정하는 해시가 포함되면 보안문제가 발생할 소지가 크다. 이를 대량 할당 취약성이라고 부른다.

Rails는 이를 막기 위해 StrongParameters를 제공한다.

StrongParameters

StrongParameters는 대량 할당 취약성에 대한 화이트 리스트 대책 방법이다. 필드 값을 일괄 설정하기 전에 설정해도 괜찮은 값을 명시적으로 입력해 주는 것이 필요하다.

스캐폴딩으로 앱을 만들었을 때 컨트롤러에 포함되는 params.require(model).permit(attr, ..) 이 StrongParameters이다.

def book_params
  params.require(:book).permit(:isbn, :title, :price, :publish, "cd")
end

요청의 다양한 정보 추출

한편 headers 메서드를 사용하면 요청에 포함된 헤더를 쉽게 추출할 수 있다.

def req_head
  render text: request.headers['User-Agent']
end

업로드된 파일을 추추할 떄도 params 메서드를 사용할 수 있다. 이 경우 params 메서드는 업로드된 파일을 객체로 리턴한다.

1) 파일 시스템에 파일을 업로드하는 경우 예시

폼에 multipart 옵션을 지정해서 파일을 업로드하자. 그 뒤 아래처럼 받으면 된다.

def upload_process
  file = params[:upfile]
  name = file.original_filename
  perms = ['.jpg', 'jpeg', '.gif', '.png'] # 사용가능한 확장자 정의
  if !perms.include?(File.extname(name).downcase)
    result = '이미지 파일만 업로드해주세요!'
  elif file.size > 1.megabyte
    result = '1MB 이하의 파일만 업로드해주세요!'
  else
    File.open("public/docs/#{name}", 'wb') { |f| f.write(file.read) }
    result = "#{name.toutf8}를 업로드했습니다."
  end
    render text: result
end

2) 데이터베이스에 파일을 업로드하는 경우 예시

데이터베이스에 크기가 큰 바이너리 데이터를 저장하는 것에 대해서는 장단점을 따져야한다.

  • 장점: 파일 접근 제약을 걸 때 데이터베이스의 기능을 그대로 활용할 수 있다.(파일 시스템으로 걸려면 번거로움.)

  • 단점: 데이터베이스 자체의 크기가 비대화된다.

응답

액션에서의 처리 결과를 출력하기 위한 메서드들이 있다. 편의상 응답 메서드라고 부르며 메모하자.

메서드

설명

render

템플릿 호출 또는 글자/스크립트 출력 등, 범용적인 출력

redirect_to

지정된 주소로 처리를 리다이렉트

send_file

지정된 파일을 출력

send_data

지정된 바이너리 데이터를 출력

render 메서드

액션에서 명시적으로 응답 메서드를 호출하지 않으면 자동으로 render 메서드가 호출되어 템플릿 파일을 실행하게 된다.

render 메서드는 템플릿을 호출하거나 응답을 인라인으로 설정하는 등 다양한 옵션을 가지고 있다. 후자의 경우 MVC 관점에서 어긋나니 주의하자. 디버그용으로만 쓸 것.

! 뷰에 yield 구가 하나만 있는데 render를 여러번 호출하면 에러가 발생하니 주의하자.

! render를 head 메서드처럼 응답 상태값을 받는 용도로도 쓸 수 있다.

redirect_to 메서드

redirect_to 메서드는 지정된 페이지로처리를 리다이렉트시킨다.

reirect_to url [, status=302]

# urls: 리다이렉트 대상 URL
# status: 상태 코드(숫자 또는 심볼)

메서드의 인자로 들어가는 url은 문자열 또는 해시 형식으로 지정하면 된다. 예시를 보자.

redirect_to 'http://www.wings.msn.to'           #절대 경로
redirect_to action: :index                      #같은 컨트롤러의 액션
redirect_to controller: :hello, action: :list   #다른 컨트롤러의 액션
redirect_to books_path                          #자동 생성된 뷰 헬퍼
redirect_to :back                               #이전 페이지

send_file 메서드

지정한 경로에 있는 파일을 읽어 들여 그 내용을 클라이언트에게 전송한다.

send_file path [, opts]
# path: 읽어 들일 파일의 경로
# opts: 동작 옵션

! 요청 정보로 파일 경로를 직접 지정하면 사용자가 서버 내부의 파일에 접근할 수도 있게되므로 굉장히 위험하다. 아래 같은 코드는 피하자.

send_file params[:path]

send_data 메서드

매개 변수로 바이너리 데이터를 받고 응답한다.

send_data data [,opts]
# data: 파일 경로
# opts: 동작 옵션

데이터베이스에서 바이너리 자료형을 추출하고 클라이언트에게 응답하는 경우 레코드를 추출하고, 콘텐츠 타입을 나타내는 필드(ctype 필드)를 매개 변수 type에, 바이너리 필드를 매개 변수 data에 전달해주면 된다.

logger 객체

logger 객체는 로그를 사용하기 위해 Rails가 제공하는 객체이다. 로그의 중요도에 따라 6개의 메서드를 제공하고 있다. 아래는 우선순위가 높은 순에서 낮은 순으로 정리한 메서드.

  1. unknown(msg): 알 수 없는 오류

  2. fatal(msg): 치명적인 오류

  3. error(msg): 오류

  4. warn(msg): 경고

  5. info(msg): 정보

  6. debug(msg): 디버그 정보

로그 출력 레벨을 변경하려면 development.rbconfig.log_level = :error 같은 내용을 추가하면 된다. 또한 filter_parameter_logging.rb 를 수정하여 패스워드 등의 파일을 로그에 기록되지 않도록 설정할 수도 있다.

HTML 이외의 응답 처리

추출한 모델의 내용을 XML 또는 JSON 형식으로 출력하는 것은 굉장히 간단하다.

render 메서드에 xml 또는 json 옵션을 지정하기만 하면 된다. 가령 xml 옵션을 지정하고 일반적인 모델을 넣어주면 아래 처리를 자동으로 수행한다.

  • to_xml 메서드로 모델을 XML 형식으로 변환

  • Content-Type 헤더를 "application/xml"로 지정

그러나 이처럼 render 메서드에 json과 xml 옵션을 지정해서 JSON 또는 XML 형식의 응답을 생성하는 것은 편리하지만, 결과 생성은 뷰에서 처리한다는 MVC의 기본 정책에는 위반된다. 또한, json과 xml 옵션으로 모델을 출력하는 방식은 모델의 내용을 기계적으로 변환하는 것뿐이므로 원하는 형식을 만들어 내는 데는 문제가 있다.

템플릿으로 JSON과 XML 데이터 생성 - Jbuilder/Builder

ERB로 HTML 데이터를 생성하는 것처럼 템플릿을 기반으로 하여 JSON과 XML 데이터를 생성하는 것이 바람직하다. 이를 수애하는 것이 바로 JBuilder와 Builder 템플릿이다. 각각 JSON 데이터를 생성하는데, XML 데이터를 생성하는 데에 특화된 템플릿이다.

한편 Builder에 있는 뷰 헬퍼인 atom_feed 메서드를 사용하면 Atom 피드를 쉽게 생성할 수 있다.

멀티 포맷으로 출력 - response_to 메서드

Rails에서 멀티 포맷을 사용할 때는 ERB, Jbuilder, Builder, Ruby 등의 템프릿을 사용해 원하는 뷰를 여러 개 준비해두는 것이 기본이다.

하지만 아래 경우에는 respond_to 메서드를 이용해 간단하게 분기 처리를 할 수 있다.

  • 디버그 전용 오류 글자를 출력하고 싶은 경우

  • 템플릿을 준비할 것도 없는 경우

  • 각각의 형식에 따라 리다이렉트를 하고 싶은 경우

  • 헤더만 출력하고 싶은 경우

respond_to do |format|
  foramt.type { statements }
  ...
end

respond_to 메서드의 내부 블록에는 format.type 형식으로 원하는 형식 type을 적는다. 이후 statements 부분에는 형식에 따라 원하는 처리 코드를 입력한다.

이 때 respond_to 메서드에서 사용할 수 있는 형식은 Rails의 actions_dispatch/http/mime_types.rb에 정의되어 있다. 만약 기존에 정의된 형식 외에 다른 형식을 사용하고 싶은 경우에는 /config/initializers/mime_types.rb에서 아래 형식으로 포맷을 등록하면 된다.

Mime::Type.register "text/richtext", :rtf
Mime::Type.register_alias "text/html", :iphone

상태 관리

상태 관리란 여러 개의 페이지(액션) 사이에서 정보를 유지하기 위한 구조를 말한다. 상태 관리가 필요한 이유는 HTTP의 특성에 기인한다.

HTTP는 클라이언트로부터 요청을 받으면 서버에서 응답을 보내고 끝난다. 따라서 같은 클라이언트에서 요청을 여러 번 보내도 그러한 요청을 동일한 것으로 인식하지 못한다. 즉, HTTP는 무상태 프로토콜(Stateless protocol)로 상태를 유지하지 않는 프로토콜이다.

하지만 애플리케이션 전체 또는 특정한 기능에서 사용자가 누구인지라는 "상태"를 유지해야할 상황은 많다. 그렇지 않으면 페이지에 접근할 때마다 로그인을 해야하기 때문이다.

Rails는 원래 HTTP에 없는 상태 관리 기능을 애플리케이션에 보충하고 아래 기능을 제공하고 있다.

기능

설명

쿠키

브라우저에 저장되는 작은 글자 정보(Rails 이외의 환경에서도 사용할 수 있는 범용적인 형태의 관리 방법)

세션

쿠키, 캐시, 데이터베이스 등의 상태 정보를 저장하는 구조(가장 자주 사용하는 관리 방법)

플래시

현재와 다음 요청에서만 유지되는 특별한 형태의 세션 정보

물론 쿼리 정보 또는 숨겨진 필드 등의 기능도 상태 관리의 한 방법인데 이것들은 사용이 굉장히 한정적이므로 제외해본다.

쿠키 추출과 설정하기 - cookies 메서드

쿠키는 클라이언트 쪽에 저장되는 간단한 텍스트 파일이다. 쿠키를 사용하면 여러 페이지에 걸쳐 사용자를 식별하거나, 클라이언트 단위로 정보를 관리하는 것이 가능하다.

쿠키를 설정할 때는 cookies 메서드를 사용한다.

cookies 메서드에 설정 가능한 옵션과 함께 알아둬야할 사실을 메모하자면 아래와 같다.

  • values, expires는 사실상 반드시 입력해야 하는 옵션이다. expires 옵션을 생략하면 사용자의 브라우저가 닫힐 때 쿠키도 함께 사라진다는 점에서 주의해야한다.

  • domain과 path는 쿠키가 유효한 도메인과 경로를 설정한다. 도메인 이름을 공유하는 서버를 이용하는 경우, 쿠키가 다른 사용자에게 유출될 수 있으므로 path는 반드시 지정해둬야 한다.

  • secure는 통신을 암호화할 경우에는 true로 입력한다. 이렇게 하면 암호화되지 않은 페이지가 혼재하는 경우에도 암호화된 쿠키는 송신되지 않으므로 안전하다.

  • httponly는 HTTP 통신으로만 접근할 수 있는 HTTP 쿠키를 유효화한다. 이렇게 하면 자바스크립트로부터 쿠키에 접근할 수 없게 되므로 크로스 사이트 스크립팅 취약성을 이용한 쿠키 도청을 막을 수 있다.

permanentencrypted 메서드를 사용하면 영속화 쿠키와 암호화 쿠키를 생성할 수 있다.

영속화 쿠키는 정확히 말하면 유효 기간이 20년으로 설정된 쿠키이다. expire 옵션이 지정된 경우에도 permanent 설정이 우선된다. 편리하지만 유효 기간이 긴 쿠키는 보안적인 측면에서 좋지 않으므로 남용하면 안된다.

암호화 쿠키를 사용하면 쿠키를 보다 안전하게 사용할 수 있다. 이 때 쿠키 암호화나 해독에 사용하는 토큰을 config/initializers/secret_token.rbRails.application.config.secret_key_base 매개 변수로 설정해둬야한다.

아래 처럼 작성하면 영속성 쿠키와 암호화 쿠키를 동시에 설정할 수 있다.

cookies.permanent.encrypted[:email] = { value: ...}

세션 사용 - session 메서드

페이지끼리의 정보를 공유하기 위해 쿠키 외에도 세션이라는 것을 제공한다. 세션이란, 사용자(클라이언트) 단위로 정보를 관리하기 위한 기능으로 최근의 프레임워크는 대부분 지원하고 있는 기능이다.

Rails의 세션은 기본적으로 쿠키에 모든 정보를 저장하므로 표준 형태에서는 쿠키와 거의 차이가 없다. 하지만 세션에서는 설정을 변경해서 저장 대상(데이터 저장소)을 변경할 수 있다는 장점이 있다. 브라우저가 열려있는 동안 데이터를 유지하고 싶은 경우에는 일단 세션을 우선적으로 사용하자.

세션 정보를 저장하는 데이터 저장소는 아래와 같다.

저장 대상

설명

쿠키(CookieStore)

클라이언트 사이드 클라이언트의 쿠키로 세션을 저장(기본). 고속으로 동작하지만 클라이언트 측에 저장하므로 비밀 정보 저장에는 적합하지 않음. 저장 사이즈도 4KB로 제한됨.

캐시(CacheStore)

서버 사이드 애플리케이션 캐시에 세션을 저장. 그다지 중요하지 않은 수명이 짧은 데이터를 저장할 때 사용.

데이터베이스(ActiveRecordStore)

서버 사이드 액티브 레코드로 접근할 수 있는 데이터베이스에 세션을 저장. 서버에 저장하므로 비밀 정보를 저장할 때 적합함.

세션 처리는 아래 과정을 거친다.

  1. 첫 번째 요청 (클라이언트 -> 서버)

  2. 세션 ID를 발급 (서버 -> 클라이언트)

  3. 두 번째 이후부터의 요청 (클라이언트 -> 서버)

  4. 세션 ID를 키로 세션 정보를 추출 (서버)

  5. 세션 정보를 기반으로 생성된 결과를 출력 (서버 -> 클라이언트)

포인트는 서버 사이트에서 세션을 관리할 때도 키는 쿠키를 사용한다는 점이다. 이렇게 클라이언트에게 발행되는 쿠키를 세션 ID라고 부른다. 서버 측에서는 클라이언트로부터 발송된 세션 ID를 키로 사용자를 식별하고, 해당 사용자의 세션 정보를 추출한다.

flash 메서드

리다이렉트 처리를 전후로 일시적으로만 데이터를 저장하고 싶을 때 쓴다.

예를 들어 데이터를 추가 또는 수정하고 그 결과를 리다이렉트 대상의 화면에서 "~의 저장에 성공했습니다."와 같이 표시하고 싶은 경우!

플래시는 redirect_to 메서드와 함께 사용해야한다. 아래처럼 redirect_to 메서드에 notice 옵션으로 지정한 문자열은 리다이렉트 대상에 플래시로 전달된다.

format.html { redirect_to book, notice: 'Book was successfully created.'}

필터

필터는 액션 메서드가 실행되기 이전과 이후에 부가 처리를 위해서 사용한다. 여러 액션에 공통적인 처리를 액션마다 개별적으로 작성할 필요 없이, 한꺼번에 작성할 수 있다.

before, after, around 필터를 사용하자.

필터는 정의된 컨트롤러 또는 해당 컨트롤러에서 파생된 컨트롤러에 적용된다. 부모 클래스 -> 자식 클래스 순서로 필터가 실행된다. 물론 skip_xxxx_action 메서드로 부모 컨트롤러로부터 전달된 필터를 제거할 수도 있다.

애플리케이션 공통 기능 정의 - Application 컨트롤러

Application 컨트롤러는 모든 컨트롤러의 부모 클래스가 되며, 모든 컨트롤러의 근원이 된다는 의미에서 루트 컨트롤러라고도 불린다.

원칙적으로 Application 컨트롤러에는 애플리케이션의 공통 기능만 작성하는 것이 좋다.

예시

  • 각각의 컨트롤러에서 사용할 헬퍼메서드

  • 모든(또는 대부분) 컨트롤러에서 이용하는 필터

  • 애플리케이션의 공통 설정

공통 필터로 로그인 기능을 구현하는 것도 좋은 예시다. Application 컨트롤러의 before 필터에 check_logined 를 설정하면 모든 컨트롤러에서 인증 기능을 사용할 수 있다. 이 때, login_controller에서만큼은 skip_before_action :check_logined하도록 해야한다. 그렇지 않으면 로그인 페이지에서 로그인 페이지로 무한 리다이렉트 될 수 있으므로 주의해야한다.

애플리케이션 전체 또는 여러 개의 컨트롤러나 모델에서 공통으로 사용하는 로직이 있다면 이런 공통 로직을 두는 표준 장소가 있다. 공통 로직을 모듈로 만들어 아래 경로에 넣어두자.

  • app/controllers/concerns

  • app/models/concerns

    학습 내용에 대한 개인적인 총평

    Rails 공식문서 튜토리얼에서 이해가 안 가는 부분을 토론을 통해 답을 구해보려하였으나, 결국 우리끼리 논의해서는 가설만 세울 수 있을 뿐, 실제 애플리케이션 구현하는 과정에서 검증해야한다는 점만 인식했습니다.

    하루하루 조금이라도 자라는 것. 새해가 되었어도 해야될 것은 마찬가지 입니다. 다만 가장으로서 취업을 해야하기 때문에 슬슬 이력서, 코딩테스트 등을 준비해야겠네요.

    다음 학습 계획

    • 루비온레일즈

Last updated