🧠 本文不只是教学代码,而是帮助你理解「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 编程可以成为你探索更强大后端架构的基石
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.