maxwell 让你写http请求更加简单。

项目来源

工作中经常需要用到http客户端,使用过httppoison, httpotion,但都用起来都太过于麻烦,重复的工作(比如:每一个请求都要自己关注如何encode&decode json)做多了就觉得繁琐。然后就使用一段时间的tesla,着实让我开心了一阵子。可以把上面encode&decode json的过程都使用middleware的方式隐藏起来。
使用tesla的过程中也发现了一些不足地方(比如我使用ibrowse adapter,结果却没有实现send_file, send_stream接口),有时会想有没有可能把这些不方便的地方再抽象,让http请求写得更简洁和优雅?
所以就有了maxwell

设计思路

http请求过程是从外界去拿数据,或都把数据传给外界的过程。
本质是与外界的交流。
http请求一般流程
繁琐的过程在于,要怎么去序列化req_body式(json,xml,multipart…),然后再拿到结果时判断status, 然后再根据resp_header进行反序列化req_body.
这些处理逻辑都是有共性的,不需要每次都自己来做,所以可以简化抽象为
http请求简化流程

我应该关注的:增删查改资源,有没有成功,成功后我想要的资源能够直接使用。

用法示例

httpbin.org是用专门用来测试HTTP Request & Response 服务的测试网站。

defmodule Client do
#generate 4 function get/1, get!/1 post/1 post!/1 function
use Maxwell.Builder, ~w(get post)a
middleware Maxwell.Middleware.BaseUrl, "http://httpbin.org"
middleware Maxwell.Middleware.Headers, %{"Content-Type" => "application/json"}
middleware Maxwell.Middleware.Opts, [connect_timeout: 5000, recv_timeout: 10000]
middleware Maxwell.Middleware.Json
adapter Maxwell.Adapter.Hackney
@doc """
curl -i http://httpbin.org/ip
HTTP/1.1 200 OK
...
Content-Type: application/json
Content-Length: 33
...
{
"origin": "183.240.19.100"
}
Get origin ip
"""
def get_ip() do
new
|> put_path("/ip")
|> get!
|> get_resp_body("origin")
end
@doc """
Post whole file once
curl -X POST -i -T ./mix.exs http://httpbin.org/post
HTTP/1.1 100 Continue
HTTP/1.1 200 OK
...
Content-Type: application/json
Content-Length: 1738
...
{
...
"data": { "...mix.exs file content..."},
...
},
...
}
Client.post_file_once("./mix.exs")
"""
def post_file_once(filepath) do
new
|> put_path("/post")
|> put_req_body({:file, filepath})
|> post!
|> get_resp_body("data")
end
@doc """
Post whole file by chunked
curl -X POST -i -T ./mix.exs http://httpbin.org/post
HTTP/1.1 100 Continue
HTTP/1.1 200 OK
...
Content-Type: application/json
Content-Length: 1738
...
{
...
"data": { "...mix.exs file content..."},
...
},
...
}
Client.post_file_chunked("./mix.exs")
"""
def post_file_chunked(filepath) do
new
|> put_path("/post")
|> put_req_header("transfer_encoding", "chunked")
|> put_req_body({:file, filepath})
|> post!
|> get_resp_body("data")
end
@doc """
Post by stream
["1", "2", "3"] |> Stream.map(fn(x) -> List.duplicate(x, 2) end) |> Client.post_stream
"""
def post_stream(stream) do
new
|> put_path("/post")
|> put_req_body(stream)
|> post!
|> get_resp_body("data")
end
@doc """
Post multipart form
curl -i --form file=@"./mix.exs" http://httpbin.org/post
HTTP/1.1 100 Continue
HTTP/1.1 200 OK
...
Content-Type: application/json
Content-Length: 1901
...
{
"files": {
"file": "{...file content...}"
},
"headers": {
...
"Content-Type": "multipart/form-data; boundary=------------------------fb4e956786dda52e",
...
},
...
}
Client.post_multipart_form({:multipart, [{:file, "./mix.exs"}]})
"""
def post_multipart_form(multipart) do
new
|> put_path("/post")
|> put_req_body(multipart)
|> post!
|> get_resp_body(["files", "file"])
end
end

put_* get_*辅助函数都来源于Maxwell.Conn,这些函数的抽象都是参照的plug实现的。
总体来看,可以让你的http 请求逻辑写起来非常直观清晰。用管道做成一条龙。

测试及文档

  1. 因为adapter是提供通用接口的,测试用例有共性,所以用Macro写了一个adapter的测试模板
  2. 使用mock测试自己写的adapter。
  3. 完整的测试,coverage达到100%
  4. 完整的文档

maxwell与tesla的相同与差别

相同:

两者都支持自定义middleware来处理请求。都有httpc,ibrowse,hackney adapter。

差别:

  1. 关于传递动态参数:maxwell是put_* get_*实现,
    tesla是Tesla.build_client,在我看来,前者更简洁清楚。
  2. maxwell的所有adapter都支持send_file, send_stream, send_multipart,tesla只有hackney支持。
  3. maxwell支持只定义所需要的http方法(默认为所有方法)。

    #generate 4 function get/1, get!/1 post/1 post!/1 function
    use Maxwell.Builder, ~w(get post)a

    tesla会直接build所有的方法,且没有!类别的函数,如果你有代码洁癖,这根本就不能忍~

    #generate get put post head delete trace option function
    use Tesla.Builder
  4. maxwell代码比tesla简洁明了,且也没有用MFA。欢迎来找茬。

参考资料

  1. introducing tesla-the flexible HTTP client for Elixir