Elixir Phoenix : Router와 Controller
Router는 request URI를 어떤 모듈의 어떤 함수가 처리할지를 지정하는 모듈입니다.
router 모듈내에는 복수개의 pipeline을 둘수 있으며, pipeline은 http request를 받았을 때 해야하는 일련의 일들의 모임이라 생각할 수 있습니다.
위의 소스코드에서는 두개의 pineline이 있으며, :browser와 :api로 각각 html request와 json api 를 처리하는 경우에 대한 정의라고 생각하면 됩니다. 이에 대한 자세한 설명은 https://hexdocs.pm/phoenix/routing.html#pipelines을 참조하면 되며, 일반적인 경우 이에 대한 기능 추가는 하지 않아도 됩니다.
위의 코드에서 중요한 부분은
scope "/", HelloWebWeb do
pipe_through :browser
get "/", PageController, :index
get "/hello" , PageController, :hello
get "/now" , PageController, :now
get "/calc" , PageController, :calc
end
부분입니다. 이는 어떤 URI 에 대해 어떤 method 요청을 , 어떤 모듈의 어떤 함수가 처리할지를 지정합니다.
위의 예에서,
http의 GET method로 /hello URI 가 요청되는 경우, PageController의 :hello 함수를 호출하도록 지정한 것 입니다.
동일 PC에서 실행하는 경우에는 브라우저의 http://localhost:4000/hello 를 입력하면, PageController 모듈의 :hello 함수가 호출되고
:hello 함수의 결과값이 브라우저에 response로 전송됩니다.
위의 코드에서는 /hello, /now, /calc 에 대해 처리할 함수를 지정하였습니다.
다음은 PageController.ex의 소스코드 입니다.
코드에는 위의 router에서 지정한 함수들이 있습니다.
각 함수들은
def hello(conn,_param) do
html conn, """
<HTML>
<HEAD>
<TITLE> Hello !! </TITLE> </HEAD>
<BODY>
<h1> Hello Phoenix Web </h1>
</BODY>
</HTML>
"""
end
와 같이. conn 과 param을 인자로 받습니다.
인자 conn은 http의 request에 대한 정보와 response 정보를 가지는 map 구조체 입니다.
conn에는 request 정보로, 호출된 method 와 상대의 ip 주소, header 정보등 http request의 모든 정보를 가지고 있습니다.
controller 함수는 이 conn에 response 정보를 저장하여 반환하면, 그 결과가 클라이언트(브라우저)측에 전송되게 됩니다.
즉, controller 함수의 결과값은 항상 conn 이여야 합니다.
위의 코드에서 html conn, """ ... """ 은, Phoenix.Controller.html(conn, """...""") 함수를 호출하는 것 입니다.
Phoenix.Controller.html(conn, """...""") 를 html conn, """...""" 으로 호출할 수 있는 것은, 소스코드의 위에 있는
useHelloWebWeb, :controller이 있기 때문입니다. (자세한 내용은 본 글에서는 생략하며, 이의 내용을 몰라도 개발에 문제가 없습니다.)
Phoenix.Controller.html 함수는 두번째 파라미터로 주어진 데이터를 html 화하여 첫번째 파라이터인 conn에 response 정보를 저장하고 conn을 return 합니다 (response의 content-type: text/html). 이런 흐름이기 때문에 위의 hello 함수는 html response 정보가 저장된 conn을 return 하고, 그 정보가 클라이언트에게 전송됩니다.
conn은 Plug.Conn 타입이며, 이에 대한 상세한 정보는
https://hexdocs.pm/plug/Plug.Conn.htm
와
https://devhints.io/phoenix-conn를 참조하세요.
아래의 함수는 URI /now 에 대한 처리 함수로, 현재 시간을 보여주는 HTML을 응답으로 전송합니다.
def now(conn,_param) do
now = :calendar.local_time() |> NaiveDateTime.from_erl! |> NaiveDateTime.to_string
html conn, """
<HTML>
<HEAD>
<TITLE> Now </TITLE> </HEAD>
<BODY>
<h1> #{now} </h1>
</BODY>
</HTML>
"""
end
위의 hello 와 거의 비슷하지만, 현재 시간을 문자열로 만들고 이를 HTML 문자열내에 string interpolation으로 표현한 것 입니다.
이와 같이 string interpolation 을 통하여 HTML에 변수의 값이나 DB의 쿼리 결과값등을 넣을수 있지만, 이를 좀더 편하고 일반화한것이 Template와 View 모듈입니다. (이에 대한 설명은 다음글에 적겠습니다.)
세번째 함수, calc는 두수를 URI의 인자로 받아서 그 결과값을 JSON 타입(content-type: application/json)으로 반환하는 Rest API함수를 구현한 것입니다.
def calc(conn,%{"a" => valueA , "b" => valueB}) do
sumValue = String.to_integer(valueA) + String.to_integer(valueB)
json conn, %{result: :ok, sum: sumValue}
end
def calc(conn,_param) do
json conn, %{result: :error}
end
위의 함수는 http://localhost:4000/calc?a=1&b=2 와 같이 호출하면되며,
URI 인자 a=1&b=2 는 calc 함수의 두번째 인자인 param에 map의 형태로 전달됩니다.
함수 calc(conn,%{"a" => valueA , "b" => valueB}) 에서는
인자로 주어진 두값을 pattern matching으로 가져와서 , 두수의 합을 계산한후 그 결과를 JSON으로 반환합니다.
map에 주어진 인자값은 문자열이므로, 두수의 계산을 수행하기 전에 정수형으로 변환한 후 계산을 수행하도록 구현되어 있습니다.
Phoenix.Controller.html(conn, """...""") 이 HTML 결과를 반환하는 것에 반해,
Phoenix.Controller.json(conn, json_data) 는 결과값을 JSON type으로 반환 합니다.
와 같이, 결과의 content-type이 application/json으로 되어 있는 것을 확인할 수 있습니다.
json 함수는 두번째 인자로 JSON으로 변환될 수 있는 모든 자료형을 줄수 있습니다. 이는 List, Map 등이며, JSON 으로 변환될 수 있는 자료형이면, 내부에서 자동으로 JSON 형식으로 변환합니다.
같은 이름의 함수, calc
def calc(conn,%{"a" => valueA , "b" => valueB}) do
def calc(conn,_param) do
가 두개 있는 것은, URI 인자로 a,b가 주어지지 않은 경우에 대한 처리 목적입니다.
http://localhost:4000/calc?a=1&b=2 또는 http://localhost:4000/calc?a=1&b=2&c=3 와 같이
a와 b 가 주어진 경우 (또는 그 외의 값이 주어진 경우도 포함)에는 pattern matching %{"a" => valueA , "b" => valueB} 에 맞으므로
함수 calc(conn,%{"a" => valueA , "b" => valueB})가 호출되며,
http://localhost:4000/calc 또는 http://localhost:4000/calc?a=1 또는 http://localhost:4000/calc?b=2
와 같이 a와 b가 모두 주어지지 않는 모든 경우에 대해서는 calc(conn,_param)가 호출됩니다.
이와 같은 방식으로, pattern matching으로 Rest API 구현시 함수의 인자 체크를 쉽게 할 수 있습니다.
전체 코드는 https://github.com/BitzFlex/hello_web.git에서 받으실 수 있습니다.