🧠 本文不只是教学代码,而是帮助你理解「Erlang 写 Web 服务」的底层原理,用最野性但真实的方式。
你可能已经习惯了 Java 的 Spring Boot、Node.js 的 Express、Python 的 Flask —— 但这些都有框架。而这次,我们直接用 Erlang 写 HTTP 服务,不依赖 cowboy、mochiweb 或 yaws,完全裸写 socket 层,硬核程度拉满。
👩💻 目标:
- 写一个能处理 GET 和 POST 请求的 Web 服务器
- 路由分发基本 API 请求路径
- 接收 JSON(模拟即可)
- 返回标准 HTTP 响应
建立最原始的 TCP 服务
Erlang 的 gen_tcp 模块就是我们所有操作的起点:
start() ->
{ok, ListenSocket} = gen_tcp:listen(8080, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]),
loop_accept(ListenSocket).
loop_accept(ListenSocket) ->
{ok, Socket} = gen_tcp:accept(ListenSocket),
spawn(fun() -> handle(Socket) end),
loop_accept(ListenSocket).
- 监听本地 8080 端口
- 接收到连接后,spawn 一个进程专门处理这个 Socket(这就是 Erlang 并发威力!)
解析 HTTP 请求(简单模拟)
真正的 HTTP 协议超复杂,这里我们做「最小可运行子集」:
handle(Socket) ->
{ok, Data} = gen_tcp:recv(Socket, 0),
Request = binary_to_list(Data),
io:format("Request: ~s~n", [Request]),
Response = route(Request),
gen_tcp:send(Socket, Response),
gen_tcp:close(Socket).
现在的重点是 route/1
—— 我们来构造最基础的路由逻辑。
基于请求构建路由
route(Request) ->
%% 基于最简单的方式分析请求头第一行
case string:tokens(Request, " \r\n") of
["GET", "/", _] ->
ok("Hello from Erlang REST API!");
["GET", "/ping", _] ->
ok("pong");
["POST", "/echo", _ | _] ->
ok("echo placeholder");
_ ->
not_found()
end.
构造 HTTP 响应(手撸)
ok(Body) ->
list_to_binary(
"HTTP/1.1 200 OK\r\n" ++
"Content-Type: text/plain\r\n" ++
"Content-Length: " ++ integer_to_list(length(Body)) ++ "\r\n\r\n" ++
Body
).
not_found() ->
list_to_binary(
"HTTP/1.1 404 Not Found\r\n" ++
"Content-Length: 0\r\n\r\n"
).
完整代码
%%%-------------------------------------------------------------------
%%% @doc 超轻量级 HTTP 服务器,无框架,无依赖,纯 Erlang socket 实现
%%%-------------------------------------------------------------------
-module(simple_http_server).
-export([start/0]).
start() ->
{ok, ListenSocket} = gen_tcp:listen(8080, [
binary,
{packet, 0},
{active, false},
{reuseaddr, true}
]),
io:format("🚀 Server started at http://localhost:8080~n"),
loop(ListenSocket).
loop(ListenSocket) ->
{ok, Socket} = gen_tcp:accept(ListenSocket),
spawn(fun() -> handle(Socket) end),
loop(ListenSocket).
handle(Socket) ->
case gen_tcp:recv(Socket, 0) of
{ok, Data} ->
Request = binary_to_list(Data),
io:format("📥 Incoming Request:\n~s~n", [Request]),
Response = dispatch(Request),
gen_tcp:send(Socket, Response),
gen_tcp:close(Socket);
{error, closed} ->
gen_tcp:close(Socket);
{error, Reason} ->
io:format("❌ Error: ~p~n", [Reason]),
gen_tcp:close(Socket)
end.
dispatch(Request) ->
case string:tokens(Request, " \r\n") of
["GET", "/", _] ->
ok("🌈 Welcome to Erlang REST API");
["GET", "/ping", _] ->
ok("pong 🏓");
["POST", "/echo", _ | _] ->
ok("🔁 Echo endpoint reached (body parsing TBD)");
["GET", Path, _] ->
case is_static(Path) of
true -> ok("[static] no content");
false -> not_found()
end;
_ ->
not_found()
end.
is_static(Path) ->
lists:any(fun(Ext) -> string:ends_with(Path, Ext) end, [".css", ".js", ".less", ".png", ".jpg"]).
ok(Body) ->
list_to_binary(
"HTTP/1.1 200 OK\r\n" ++
"Content-Type: text/plain; charset=utf-8\r\n" ++
"Content-Length: " ++ integer_to_list(length(Body)) ++ "\r\n\r\n" ++
Body
).
not_found() ->
list_to_binary(
"HTTP/1.1 404 Not Found\r\n" ++
"Content-Type: text/plain\r\n" ++
"Content-Length: 13\r\n\r\n404 Not Found"
).
测试一下
erl
然后
Erlang/OTP 27 [erts-15.2.6] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]
Eshell V15.2.6 (press Ctrl+G to abort, type help(). for help)
1> c(simple_http_server).
{ok,simple_http_server}
2> simple_http_server:start().
运行一下
curl http://localhost:8080/ # Hello from Erlang REST API
curl http://localhost:8080/ping # pong
curl -X POST http://localhost:8080/echo
你到底做了什么?
✅ 你构建了一个最小可运行的 HTTP Server(支持 GET/POST)
✅ 没有用任何框架,全靠你自己解析请求、构造响应
✅ 所有逻辑都是原生进程,不靠 gen_server,也没 OTP 套路
其实很多人觉得 Erlang 写 Web 太麻烦、过时了,但只要你理解其并发模型和分布式能力,这种原始的 socket 编程可以成为你探索更强大后端架构的基石
Hey everyone! We’re launching your special Dev.to drop for all verified Dev.to authors. Claim your rewards here to see if you qualify (for verified Dev.to users only). – Dev.to Community Support