在 “云端分布式架构下的编程语言: 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来减少垃圾评论。了解我们如何处理您的评论数据