
在 “云端分布式架构下的编程语言: ELIXIR; PATTERN MATCHING ” 一文中, function parse 将 request 信息转化为一没有 response body 的 map。
elixir 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