Erlang 从零写一个 HTTP REST API 服务

🧠 本文不只是教学代码,而是帮助你理解「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 模块就是我们所有操作的起点:

erlang 复制代码
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 协议超复杂,这里我们做「最小可运行子集」:

erlang 复制代码
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 ------ 我们来构造最基础的路由逻辑。


基于请求构建路由

erlang 复制代码
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 响应(手撸)

erlang 复制代码
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"
    ).

完整代码

erlang 复制代码
%%%-------------------------------------------------------------------
%%% @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"
    ).

测试一下

bash 复制代码
erl

然后

bash 复制代码
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().

运行一下

bash 复制代码
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 编程可以成为你探索更强大后端架构的基石

相关推荐
王德印2 小时前
工作踩坑之导入数据库报错:Got a packet bigger than ‘max_allowed_packet‘ bytes
java·数据库·后端·mysql·云原生·运维开发
Cache技术分享2 小时前
327. Java Stream API - 实现 joining() 收集器:从简单到进阶
前端·后端
颜酱2 小时前
滑动窗口算法通关指南:从模板到实战,搞定LeetCode高频题
javascript·后端·算法
重生之后端学习2 小时前
994. 腐烂的橘子
java·开发语言·数据结构·后端·算法·深度优先
honeymoose2 小时前
Webjars 导入到 SpringBoot 项目
后端
何中应3 小时前
虚拟机内的系统无法解析外网域名
linux·运维·后端
code_YuJun3 小时前
JavaWeb 日程管理项目
后端
漫霂3 小时前
Redis在Spring Boot中的应用
java·后端