我正在尝试为elixir设置回显服务器,为了简单起见,它采用简单的HTML并仅显示它。我必须在服务器上做什么才能做到这一点?
我试图看看其他人如何为Elixir中的工作服务器(如https://www.jungledisk.com/blog/2018/03/19/tutorial-a-simple-http-server-in-elixir/)进行最小化构建,这似乎很有希望,但我仍然不知道如何在其中实现它。
提供的EchoServer
defmodule EchoServer do
require Logger
def accept(port) do
{:ok, socket} = :gen_tcp.listen(port,
[:binary, packet: :line, active: false, reuseaddr: true])
Logger.info "Accepting connections on port #{port}"
loop_acceptor(socket)
end
defp loop_acceptor(socket) do
{:ok, client} = :gen_tcp.accept(socket)
Task.start_link(fn -> serve(client) end)
loop_acceptor(socket)
end
defp serve(socket) do
socket |> read_line() |> write_line(socket)
:ok = :gen_tcp.close(socket)
end
defp read_line(socket) do
{:ok, data} = :gen_tcp.recv(socket, 0)
data
end
defp write_line(line, socket) do
:gen_tcp.send(socket, line)
end
def main(args \\ []) do
accept(9999)
end
end
答案 0 :(得分:2)
让我们弄清楚一些事情。服务器可以返回一些html,但是它是显示html的浏览器。例如,如果您在浏览器的地址栏中键入一个地址,这将导致浏览器向服务器发送请求,然后服务器将响应(在响应正文中包含一些html)发送回浏览器进行解析从响应中提取出来并解释为彩色文本,图片等,称为 呈现html 。
请注意html只是以某种方式设置格式的文本,例如<div>hello</div>
,因此对于服务器而言,返回html文件与返回文本文件没有什么不同。服务器实际上正在返回文件的内容-内容没有扩展名。您需要了解,所有文本都是通过“有线”发送的。
这是您需要做的:
创建一个侦听某个端口的tcp服务器,并在请求进入时返回一些html。
在浏览器的地址栏中,指定服务器正在侦听的地址(例如localhost
,这是地址127.0.0.1,这是您的计算机上的地址),以及指定服务器正在监听,例如http://localhost:3456
。
有一个很大的障碍:服务器和客户端(例如浏览器)之间无法通信的通用协议。问题是由文本发送到TCP套接字的方式引起的。当您将一些文本发送到tcp套接字时,您不知道文本是否会分解成块,也不清楚每个块将持续多长时间。数据可以作为一个块发送,或者数据可以切成十个块。这给数据的接收者带来了一个问题:接收者如何知道什么时候应该停止尝试从套接字读取数据,因为没有更多的数据了?
要解决该问题,客户端和服务器端都必须就 协议 达成共识,这是数据的商定格式,以便另一端可以轻松地解析数据,以及已达成一致的信号,即已到达数据末尾,以便另一端可以停止尝试从套接字读取数据。最简单的协议是发送方在完成数据发送后关闭其套接字。然后,接收方将尝试继续从套接字读取数据,直到收到套接字错误,然后才知道不再有数据了。
或者,协议可能是在数据中遇到单词“ end”时-接收器在读取“ end”时应停止尝试从套接字读取。但是,这是一个可能导致问题的示例:
msg = "I finished the end of the book. It was great!"
to_send = msg <> "end"
一旦接收方看到“我完成了结局”,接收方就会认为它已经到了消息的末尾,这只是消息的一部分。像**&&=>END1234<=!!**
这样的结束标记会更好。同样,标记数据结尾的协议可以是换行符(“ \ n”)。例如,侦听套接字选项packet: :line
进行了设置,以便:get_tcp.recv()
从套接字读取一行。
另一种协议是使用数据的前4个字节来指定一个整数,该整数是另一端随后应从套接字读取的字节数。接收器等待,直到从套接字读取了4个字节,然后接收器又读取了N个字节(前4个字节中包含的整数),一旦读取了另外N个字节,接收器便知道数据的结尾。 / p>
Web浏览器和服务器已同意的协议为http request
和http response
协议。您可以看到http请求和响应格式here的一些示例。因为您正在编码tcp服务器,所以可以通过完全忽略请求(这样就不必关心格式)来简化事情,并为传入的任何请求返回相同的响应。此外,这样做还无需弄清楚服务器应该停止尝试从套接字读取数据的时间。
您的tcp服务器发回的响应确实必须遵守http响应协议,因为您将需要浏览器来接收响应以查看呈现的html,即您想在浏览器的地址中键入http://localhost:33444
酒吧。
以下是修改后的回显服务器的示例,使其遵循http响应协议,并且在响应的正文中还返回了一些html:
~/elixir_programs/tcp_server$ tree .
.
├── page.html
├── resp_header.txt
└── s1.ex
s1.ex :
defmodule HtmlServer do
require Logger
def accept(port) do
{:ok, socket} = :gen_tcp.listen(
port,
[:binary,
packet: :line,
active: false,
reuseaddr: true]
)
Logger.info "Accepting connections on port #{port}"
loop_acceptor(socket)
end
defp loop_acceptor(socket) do
{:ok, client} = :gen_tcp.accept(socket)
Task.start_link(fn -> serve(client) end)
loop_acceptor(socket)
end
defp serve(socket) do
line = read_socket(socket) #blocks until something is read from the socket
IO.puts "[ME] Got some data! #{line}"
resp_header = File.read! "./resp_header.txt"
resp_body = File.read! "./page.html"
content_len = String.length(resp_body)
resp =
resp_header <>
"Content-Length: #{content_len}\n" <>
"\n" <>
resp_body
#IO.inspect resp
write_socket(resp, socket)
:ok = :gen_tcp.close(socket)
end
defp read_socket(socket) do
{:ok, data} = :gen_tcp.recv(socket, 0)
data
end
defp write_socket(data, socket) do
:gen_tcp.send(socket, data)
end
def start() do
accept(9999)
end
end
resp_header.txt :
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
page.html :
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello</title>
<style>
.greeting {
color: green;
}
</style>
</head>
<body>
<div class="greeting">Hello World</div>
</body>
</html>
在iex中:
~/elixir_programs/tcp_server$ iex s1.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)> HtmlServer.start
10:08:34.016 [info] Accepting connections on port 9999
然后打开浏览器窗口,并将以下地址粘贴到浏览器的地址栏中:
http://localhost:9999