Racket 编程入门

第 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 变成一个可以被网络调用的程序”

它做了三件事:

  1. 启动一个 HTTP 服务器
  2. 监听指定端口
  3. 把请求交给 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/hello
  • http://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 特化语言”, 它只是把网络请求接到了你的程序上。

八、下一步你可以学什么?

在你已经理解本文内容的前提下,后续路线会非常清晰:

  1. response/xexpr:用 S-expression 写 HTML
  2. 会话(session):状态是如何保存的
  3. continuation-based web:Racket 的独门绝技
  4. 把 Web Server 当成后端 API
  5. 用 Racket 驱动个人工具、内网服务、自动化系统

结语

如果你之前觉得 Web 开发“很乱、很杂、很重”, 那只是因为你一开始就被带进了“工具森林”。

Racket 提供的是另一条路:

先理解 Web 是什么,再决定你要不要复杂化它。

而一旦你理解了这一点, Web 就只是你程序世界里的 另一个输入输出接口 而已。