第 7 部分 · 走向生产:工程化与实战
从零开始用 Racket 写一个 Web Server
Web 开发听起来是一堆陌生名词,但剥到本质就是一件事:一次请求,一次响应——和函数调用没有区别。这篇用 Racket 把这件事赤裸裸摆给你看。
很多人一听到 “Web 开发”,脑子里立刻冒出一串陌生名词:HTML、CSS、JavaScript、前端框架、后端框架……
如果你 没有任何前端经验,这往往是劝退的第一步。
但好消息是:Web 的本质其实非常简单,而 Racket 提供了一套极其适合“理解原理”的 Web Server 工具。
这篇文章的目标只有一个:
让你真正理解:一个 Web Server 到底在干什么,以及 Racket 是如何优雅地完成这件事的。
一、先别谈“网页”,我们谈“对话”
在最本质的层面,Web 并不神秘,它只是一次 远程函数调用。
你可以这样理解:
- 浏览器:
“我想要
/hello这个资源”
- 服务器:
“好,这是我算出来的结果”
一次请求 → 一次响应
这和函数非常像:
(request) -> response
Racket 的 Web Server 几乎就是把这件事 赤裸裸地摆在你面前。
二、你的第一个 Racket Web Server
1 准备环境
确保你已经安装了 Racket(8.x 以上)。
我们只用标准库,不需要安装任何第三方包。
2 最小可运行示例
新建一个文件:
#lang racket
(require web-server/servlet
web-server/servlet-env)
(define (start request)
"Hello, Racket Web Server!")
(serve/servlet start
#:port 8000
#:servlet-path "/"
#:launch-browser? #t)
运行这个文件。
你会看到浏览器自动打开,页面上显示:
Hello, Racket Web Server!
恭喜,你已经写了一个真正的 Web Server。
三、逐行理解:发生了什么?
我们来慢慢拆解,而不是一股脑地记 API。
1 start 是什么?
(define (start request)
"Hello, Racket Web Server!")
这是 整个 Web Server 的核心。
Racket Web Server 的设计思想非常 Lisp:
Web 程序 = 一个函数
- 输入:
request - 输出:一个可以作为响应的值
你甚至可以暂时忽略 request,把它当成一个“环境参数”。
2 为什么字符串就能当响应?
在很多语言里,你必须手动构造 HTTP Header、状态码等。
Racket 不这么折磨人。
当你返回:
"Hello"
Racket Web Server 会帮你做这些事:
- 设置 HTTP 状态码
200 - 设置合适的 Content-Type
- 把字符串变成响应体
这对初学者非常重要:你可以先理解逻辑,再理解协议细节。
3 serve/servlet 在干什么?
(serve/servlet start ...)
你可以把它理解为:
“请帮我把这个函数
start变成一个可以被网络调用的程序”
它做了三件事:
- 启动一个 HTTP 服务器
- 监听指定端口
- 把请求交给
start函数处理
四、Web Server ≈ 路由函数分发
真实的 Web 应用通常不止一个“入口”。
例如:
/→ 首页/hello→ 打招呼/time→ 当前时间
我们来手写一个 最直觉的路由系统。
1 读取请求路径
(require web-server/http)
(define (start request)
(define path
(url-path (request-uri request)))
(cond
[(equal? path '()) "Home"]
[(equal? path '("hello")) "Hello!"]
[(equal? path '("time"))
(format "Time: ~a" (current-seconds))]
[else "Not Found"]))
现在你可以访问:
http://localhost:8000/http://localhost:8000/hellohttp://localhost:8000/time
你会发现一件很重要的事:
Web 路径在 Racket 里就是普通的数据结构(列表)
没有魔法,没有 DSL。
五、参数 = 函数参数
我们试试 URL 参数:
http://localhost:8000/hello?name=Racket
1 读取参数
(require web-server/http)
(define (start request)
(define bindings (request-bindings request))
(define name
(or (binding-value (assq 'name bindings))
#"world"))
(format "Hello, ~a!"
(bytes->string/utf-8 name)))
你刚刚完成了:
- 参数解析
- 默认值处理
- 字节 → 字符串转换
而且全是 普通的 Racket 表达式。
六、HTML 不是字符串拼接(但你可以先这么做)
很多教程一上来就引入模板系统,这对新手非常不友好。
先用字符串,完全没问题:
(define (start request)
"<html>
<body>
<h1>Hello Racket</h1>
<p>This page is generated by Racket.</p>
</body>
</html>")
这已经是一个完整网页了。
等你理解了 Web 的“函数本质”,再学习 HTML 生成 DSL,会非常自然。
七、Racket Web Server 的“哲学优势”
如果你来自 C / 嵌入式/函数式背景,你会发现:
1 没有框架强迫你学习“套路”
- 没有 Controller
- 没有 Model
- 没有神秘注解
只有:
函数 + 数据
2 Web 程序就是普通程序
你可以:
- 递归
- 高阶函数
- 闭包
- 状态机
- continuation(这是 Racket 的大杀器)
Racket Web Server 不是一个“Web 特化语言”, 它只是把网络请求接到了你的程序上。
八、下一步你可以学什么?
在你已经理解本文内容的前提下,后续路线会非常清晰:
- response/xexpr:用 S-expression 写 HTML
- 会话(session):状态是如何保存的
- continuation-based web:Racket 的独门绝技
- 把 Web Server 当成后端 API
- 用 Racket 驱动个人工具、内网服务、自动化系统
结语
如果你之前觉得 Web 开发“很乱、很杂、很重”, 那只是因为你一开始就被带进了“工具森林”。
Racket 提供的是另一条路:
先理解 Web 是什么,再决定你要不要复杂化它。
而一旦你理解了这一点, Web 就只是你程序世界里的 另一个输入输出接口 而已。