在 “雲端分散式架構下的編程語言: ELIXIR; PATTERN MATCHING ” 一文中, function parse 將 request 信息轉化為一沒有 response body 的 map。

response body; resp_body; 為空字串。

def parse(request) do
    # TODO: Parse the request string into a map:
    [method, path, _] =
    request
    |> String.split("\n")
    |> List.first
    |> String.split(" ")

    %{ method: method, path: path, resp_body: ""}
  end

在本篇文章中, 我們將完成 function route; 加入內容到 response body; resp_body。

我們先來交流下 map。

關於 map 大家應該都不陌生; elixir 的 map 也是一個由 key:value 所組成的結構。

map conv:

iex(23)> conv = %{ method: "GET", path: "/wildthings", resp_body: " "} 
%{method: "GET", path: "/wildthings", resp_body: " "}

讀取 map conv 中的 key “method” 的值; 因為, key “method” 是 atom; :method

iex(24)> conv[:method]
"GET" 

讀取 map conv 中的 key “path” 的值; 因為, key “path” 是 atom; :path

iex(25)> conv[:path]  
"/wildthings"

當然, elixir 有更方便的方式讀取 key “path”, key “method” 的值 :

iex(27)> conv.path
"/wildthings"
iex(28)> conv.method
"GET"

elixir map 的讀取, 在作法上與其他的編程語言沒有什麼差別。

所以, 在 elixir 中, 我們是否也可以直接將某個值加入到 elixir map 中的某個 key? 例如:加入 String “Bears” 到 map conv 的 key resp_body 中 ?

iex(29)> conv[:resp_body] = "Bears"
** (CompileError) iex:29: cannot invoke remote function Access.get/2 inside match

elixir 出現編譯上的錯誤。為何 ?

道理很簡單: elixir 是函數式編程, 所以, elixir 所有的變數、結構; 當然包括 map; 都是不可以變更的

map conv 先前已經 bind 到一個 resp_body 為空字串的 map:

conv = %{ method: "GET", path: "/wildthings", resp_body: " "} 

所以, 當然就不可以直接去更改 resp_body 為其它的字串。

問題是:函數式編程的不可變更, 是一個很好的編程方式; 尤其是在多進程、多線程的情況下; 但, 當我們確實必需要在已存在的結構; 如: map; 做變更時, 要怎麼做 ?

elixir 使用: Map.put

  • Map.put 會生成新的 map, 存入我們所想要更改的 key 值。而原來的 map 並不做任何的變更。
  • 假如, 真的有必要需更改到原來 map 的 key 值, 那可使用等號 (=); rebind 新的 map 到原來的 map。

Map.put(conv, :resp_body, “Bears”); Map.put 並不會直接的將 String “Bears” 寫入到 map conv 中, 而是根據 map conv 生成另一個相同結構的 map, 並將 String “Bears” 寫入到新生成的 map 中。

所以, map conv 中的 resp_body 的值並沒有發生任何的改變; 還是空字串。

iex(29)> Map.put(conv, :resp_body, "Bears")
%{method: "GET", path: "/wildthings", resp_body: "Bears"}
iex(30)> conv
%{method: "GET", path: "/wildthings", resp_body: " "}

假如, 真的有必要需更改到 map conv 的 key 值, 那可使用等號 (=); rebind 新的 map 到 map conv。

iex(31)> conv = Map.put(conv, :resp_body, "Bears")
%{method: "GET", path: "/wildthings", resp_body: "Bears"}
iex(32)> conv
%{method: "GET", path: "/wildthings", resp_body: "Bears"}

elixir 也提供了個簡潔的語法; 生成新的 map, 寫入新的 key 值到新的 map, rebind 新的 map 到原來的 map。

iex(33)> conv = %{ conv | resp_body: "Bears, Lions, Tigers"}
%{method: "GET", path: "/wildthings", resp_body: "Bears, Lions, Tigers"}
iex(34)> conv
%{method: "GET", path: "/wildthings", resp_body: "Bears, Lions, Tigers"}

我們現在已經知道了如何完成 function route:

def route(conv) do
    # TODO: Create a new map that also has the response body:
    %{ conv | resp_body: "Bears, Lions, Tigers"}

end

同時, 我們也可完成 function format_response; 讀取得 map conv 的值, 生成 response 的信息。

def format_response(conv) do
    # TODO: Use values in the map to create an HTTP response string:
    """
    HTTP/1.1 200 OK
    Content-Type: text/html
    Content-Length: #{String.length(conv.resp_body)}

    #{conv.resp_body}
    """
end

樣例代碼如下:

defmodule Servy.Handler do
def handle(request) do
request
|> parse
|> route
|> format_response

end

def parse(request) do
# TODO: Parse the request string into a map:
[method, path, _] =
request
|> String.split("\n")
|> List.first
|> String.split(" ")

%{ method: method, path: path, resp_body: ""}
end

def route(conv) do
# TODO: Create a new map that also has the response body:
%{ conv | resp_body: "Bears, Lions, Tigers"}

end

def format_response(conv) do
# TODO: Use values in the map to create an HTTP response string:
"""
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: #{String.length(conv.resp_body)}

#{conv.resp_body}
"""
end

end


request = """
GET /wildthings HTTP/1.1
Host: example.com
User-Agent: ExampleBrowser/1.0
Accept: */*

"""

response = Servy.Handler.handle(request)
IO.puts response

執行結果如下:

$ elixir lib/servy/handler.ex
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 20

Bears, Lions, Tigers

發表評論

您的電子郵箱地址不會被公開。 必填項已用*標註

此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據