我对编程和长生不老药非常陌生。
我认为从 elixir 开始学习编程并不明智。我会从 python 或 ruby 开始,然后一两年后我会尝试 elixir。
您需要学习的第一件事是如何发布代码。在 google 上搜索如何在 stackoverflow 上发布代码。然后,你必须让你的缩进排成一行。您在使用计算机编程文本编辑器吗?如果没有,那么你必须得到一个。有很多免费的。我使用 vim,它安装在 Unix 之类的计算机上。您可以通过在终端窗口中输入 vimtutor 来学习如何使用 vim。
接下来,您的代码中有语法错误:
Agent.start_link(fn -> %{} end, name: :tmp_storage
end)
应该是:
Agent.start_link(fn -> %{} end, name: :tmp_storage)
您收到的警告是因为您的代码尝试执行以下操作:
def show do
IO.puts x
end
Elixir 和其他阅读该代码的人都会问:“x 到底是什么?”变量 x 永远不会在任何地方赋值,因此变量 x 不存在,并且您不能输出不存在的东西。你在这里做同样的事情:
with {:ok, _} <- Storage.set(key, value) do
send_resp(conn, 200, "getting the value")
else
_->
send_resp(conn, 404, "nothing")
end
你调用函数:
Storage.set(key, value)
但变量 key 和 value 从未被赋值,elixir(以及其他阅读该代码的人)想知道,“键和值到底是什么?”
这是函数的工作方式:
b.ex:
defmodule MyFuncs do
def show(x, y) do
IO.puts x
IO.puts y
end
end
defmodule MyWeb do
def go do
height = 10
width = 20
MyFuncs.show(height, width)
end
end
在 iex 中:
~/elixir_programs$ iex b.ex
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> MyWeb.go
10
20
:ok
iex(2)>
因此,在您的代码中,您需要编写如下内容:
post "/storage/set" do
key = "hello"
value = 10
with {:ok, _} <- Storage.set(key, value) do
send_resp(conn, 200, "Server saved the key and value.")
else
_->
send_resp(conn, 404, "nothing")
end
end
但是,这将为每个发布请求存储相同的键/值。大概,您想存储发送请求正文中发送的任何内容。你知道get请求和post请求的区别吗? get 请求将数据附加到 url 的末尾,而 post 请求在“请求正文”中发送数据,因此根据请求的类型,提取数据的过程也不同。
你在读什么教程?本教程:https://www.jungledisk.com/blog/2018/03/19/tutorial-a-simple-http-server-in-elixir/,向您展示如何从发布请求的正文中提取数据。发布请求正文中的数据只是一个字符串。如果字符串是 JSON 格式,那么您可以使用 Poison.decode!() 将字符串转换为 elixir 映射,这样您就可以轻松提取与您感兴趣的键关联的值。例如:
post "/storage/set" do
{:ok, body_string, conn} = read_body(conn)
body_map = Poison.decode!(body_string)
IO.inspect(body_map) #This outputs to terminal window where server is running
message = get_in(body_map, ["message"])
send_resp(
conn,
201,
"Server received: #{message}\n"
)
end
然后您可以在另一个终端窗口中使用以下 curl 命令向该路由发送 post 请求:
$ curl -v -H 'Content-Type: application/json' "http://localhost:8085/storage/set" -d '{"message": "hello world" }'
(-v => 详细输出,-H => 请求标头,-d => 数据)
现在,根据我所说的您上面的代码有问题,您应该想知道这一行:
{:ok, body_string, conn} = read_body(conn)
那条线路调用:
read_body(conn)
但是变量conn 没有在任何地方赋值。但是,Plug 不可见地创建了 conn 变量并为其赋值。
这是一个使用 Agent 存储发布请求数据的完整示例(按照我上面链接的教程):
simple_server
config/
lib/
simple_server/
application.ex
router.ex
storage.ex
test/
elixir 的约定是在lib/ 目录中有一个与您的项目同名的目录,在本例中为 simple_server,然后您为您定义的模块提供反映目录结构的名称。因此,在 router.ex 中您将定义一个名为 SimpleServer.Router 的模块,在 storage.ex 中您将定义一个名为 SimpleServer.Storage 的模块。但是,模块名称中的. 对elixir 没有什么特别的意义,因此如果您决定在文件lib/rocks.ex 中将模块命名为F.R.O.G.S,您将不会收到错误消息——并且您的代码可以正常工作。
router.ex:
defmodule SimpleServer.Router do
use Plug.Router
use Plug.Debugger
require Logger
plug(Plug.Logger, log: :debug)
plug(:match)
plug(:dispatch)
get "/storage/:key" do
resp_msg = case SimpleServer.Storage.get(key) do
nil -> "The key #{key} doesn't exist!\n"
val -> "The key #{key} has value #{val}.\n"
end
send_resp(conn, 200, resp_msg)
end
post "/storage/set" do
{:ok, body_string, conn} = read_body(conn)
body_map = Poison.decode!(body_string)
IO.inspect(body_map) #This outputs to terminal window where server is running
Enum.each(
body_map,
fn {key, val} -> SimpleServer.Storage.set(key,val) end
)
send_resp(
conn,
201,
"Server stored all key-value pairs\n"
)
end
match _ do
send_resp(conn, 404, "not found")
end
end
上面代码中首先要注意的是路由:
get "/storage/:key" do
这将匹配如下路径:
/storage/x
plug 将创建一个名为 key 的变量并将其赋值为“x”,如下所示:
key = "x"
另外,请注意,当您调用函数时:
width = 10
height = 20
show(width, height)
elixir看函数定义:
def show(x, y) do
IO.puts x
IO.puts y
end
并像这样匹配对 def 的函数调用:
show(width, height)
| |
V V
def show( x , y) do
...
end
并执行任务:
x = width
y = height
然后,您可以在函数内部使用 x 和 y 变量。在这一行:
Enum.each(
body_map,
# | | | | |
# V V V V V
fn {key, val} -> SimpleServer.Storage.set(key,val) end
)
Elixir 将调用匿名函数为key 和val 传递值,如下所示:
func("x", "10")
因此,在匿名函数的主体中,您可以使用变量key 和val:
SimpleServer.Storage.set(key,val)
因为变量 key 和 val 已经被赋值了。
storage.ex:
defmodule SimpleServer.Storage do
use Agent
def start_link(_args) do #<*** Note the change here
Agent.start_link(fn -> %{} end, name: :tmp_storage)
end
def set(key, value) do
Agent.update(
:tmp_storage,
fn(map) -> Map.put_new(map, key, value) end
)
end
def get(key) do
Agent.get(
:tmp_storage,
fn(map) -> Map.get(map, key) end
)
end
end
应用程序.ex:
defmodule SimpleServer.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
def start(_type, _args) do
# List all child processes to be supervised
children = [
Plug.Adapters.Cowboy.child_spec(scheme: :http, plug: SimpleServer.Router, options: [port: 8085]),
{SimpleServer.Storage, []}
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: SimpleServer.Supervisor]
Supervisor.start_link(children, opts)
end
end
mix.exs:
defmodule SimpleServer.MixProject do
use Mix.Project
def project do
[
app: :simple_server,
version: "0.1.0",
elixir: "~> 1.6",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {SimpleServer.Application, []}
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:poison, "~> 4.0"},
{:plug_cowboy, "~> 2.0"}
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
]
end
end
注意,如果您使用教程中指定的依赖项和版本,您会收到一些警告,包括警告:
~/elixir_programs/simple_server$ iex -S mix
...
...
12:48:57.767 [warn] Setting Ranch options together
with socket options is deprecated. Please use the new
map syntax that allows specifying socket options
separately from other options.
...这是 Plug 的问题。以下是我用来摆脱所有警告的依赖项和版本:
{:poison, "~> 4.0"},
{:plug_cowboy, "~> 2.0"}
此外,当您将应用程序列为依赖项时,您不再需要在 :extra_applications 列表中输入它。 Elixir 将在启动您的应用程序之前自动启动所有列为依赖项的应用程序。见:applications v. :extra_applications。
服务器启动后,您可以使用另一个终端窗口发送带有curl 的发布请求(或者您可以使用其他程序):
~$ curl -v -H 'Content-Type: application/json' "http://localhost:8085/storage/set" -d '{"x": "10", "y": "20" }
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8085 (#0)
> POST /storage/set HTTP/1.1
> Host: localhost:8085
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 23
>
* upload completely sent off: 23 out of 23 bytes
< HTTP/1.1 201 Created
< server: Cowboy
< date: Fri, 30 Nov 2018 19:22:23 GMT
< content-length: 34
< cache-control: max-age=0, private, must-revalidate
<
Server stored all key-value pairs
* Connection #0 to host localhost left intact
> 行是请求,< 行是响应。另外,在服务器运行的终端窗口中检查输出。
~$ curl -v http://localhost:8085/storage/z
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8085 (#0)
> GET /storage/z HTTP/1.1
> Host: localhost:8085
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< server: Cowboy
< date: Fri, 30 Nov 2018 19:22:30 GMT
< content-length: 25
< cache-control: max-age=0, private, must-revalidate
<
The key z doesn't exist!
* Connection #0 to host localhost left intact
.
~$ curl -v http://localhost:8085/storage/x
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8085 (#0)
> GET /storage/x HTTP/1.1
> Host: localhost:8085
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< server: Cowboy
< date: Fri, 30 Nov 2018 19:22:37 GMT
< content-length: 24
< cache-control: max-age=0, private, must-revalidate
<
The key x has value 10.
* Connection #0 to host localhost left intact