Nim 完全指南 / 18 Web 开发
第 18 章:Web 开发
18.1 Web 框架概览
| 框架 | 特点 | 适用场景 |
|---|
| Jester | 轻量、Sinatra 风格 | 小型 API、微服务 |
| Prologue | 全功能、Django 风格 | 中大型 Web 应用 |
| HTTPbeast | 高性能 HTTP 服务器 | 高并发服务 |
| Mummy | 多线程 HTTP | 生产级服务 |
18.2 Jester 入门
安装
基本应用
import jester
routes:
get "/":
resp "Hello, World!"
get "/hello/@name":
resp "Hello, " & @"name" & "!"
post "/api/data":
let body = request.body
resp Http200, "Received: " & body, "text/plain"
get "/json":
resp %*{"message": "Hello", "status": "ok"}
路由与参数
import jester, json, strutils
routes:
# 路径参数
get "/user/@id":
let userId = @"id"
resp %*{"id": userId, "name": "User " & userId}
# 查询参数
get "/search":
let q = request.params.getOrDefault("q", "")
let limit = parseInt(request.params.getOrDefault("limit", "10"))
resp %*{"query": q, "limit": limit}
# 正则路由
get re"/api/v(\d+)/users":
let version = request.matches[0]
resp %*{"version": version}
# 多种 HTTP 方法
get "/items":
resp %*{"items": ["a", "b", "c"]}
post "/items":
let data = parseJson(request.body)
resp Http201, %*{"created": data}
put "/items/@id":
resp %*{"updated": @"id"}
delete "/items/@id":
resp Http204
# 静态文件
get "/static/@path":
sendStaticFile(@"path")
# 通配符
get "*":
resp Http404, "Not Found"
中间件与错误处理
import jester, times
routes:
before:
echo &"[{now()}] {request.reqMethod} {request.path}"
after:
response.headers["X-Powered-By"] = "Nim+Jester"
get "/":
resp "OK"
error Exception:
resp Http500, "Internal Server Error"
18.3 Prologue 入门
安装
基本应用
import prologue
import prologue/middlewares
proc hello(ctx: Context) {.async.} =
resp "<h1>Hello, Prologue!</h1>"
proc jsonApi(ctx: Context) {.async.} =
resp jsonResponse(%*{"message": "Hello", "status": "ok"})
proc getUser(ctx: Context) {.async.} =
let userId = ctx.getPathParams("id")
resp jsonResponse(%*{"id": userId, "name": "User " & userId})
proc createUser(ctx: Context) {.async.} =
let body = ctx.getRequestBody()
let data = parseJson(body)
resp Http201, jsonResponse(%*{"created": data})
let app = newApp()
app.addRoute("/", hello, HttpGet)
app.addRoute("/api/hello", jsonApi, HttpGet)
app.addRoute("/user/{id}", getUser, HttpGet)
app.addRoute("/user", createUser, HttpPost)
app.run()
路由组
import prologue
proc listUsers(ctx: Context) {.async.} =
resp jsonResponse(%*{"users": []})
proc getUser(ctx: Context) {.async.} =
resp jsonResponse(%*{"id": ctx.getPathParams("id")})
proc createUser(ctx: Context) {.async.} =
resp Http201, jsonResponse(%*{"created": true})
let app = newApp()
# 路由组
let apiRouter = newRouter("/api")
apiRouter.addRoute("/users", listUsers, HttpGet)
apiRouter.addRoute("/users/{id}", getUser, HttpGet)
apiRouter.addRoute("/users", createUser, HttpPost)
app.addRouter(apiRouter)
app.run()
中间件
import prologue
import prologue/middlewares
proc loggingMiddleware(handler: HandlerAsync): HandlerAsync =
result = proc(ctx: Context) {.async.} =
echo &"[{now()}] {ctx.request.reqMethod} {ctx.request.path}"
await handler(ctx)
echo &" Status: {ctx.response.code}"
proc corsMiddleware(handler: HandlerAsync): HandlerAsync =
result = proc(ctx: Context) {.async.} =
ctx.response.headers["Access-Control-Allow-Origin"] = "*"
ctx.response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE"
await handler(ctx)
let app = newApp()
app.use(loggingMiddleware)
app.use(corsMiddleware)
app.run()
18.4 HTTPbeast
安装
高性能 HTTP 服务
import httpbeast, json
proc onRequest(req: Request) {.gcsafe.} =
case req.path.get()
of "/":
req.send(Http200, "Hello from HTTPbeast!")
of "/json":
req.send(Http200, $(%*{"message": "OK"}), "application/json")
of "/echo":
if req.httpMethod.get() == HttpPost:
req.send(Http200, req.body.get())
else:
req.send(Http405, "Method Not Allowed")
else:
req.send(Http404, "Not Found")
let settings = initSettings(port = Port(8080), numThreads = 4)
run(onRequest, settings)
18.5 JSON API 实战
RESTful API
import jester, json, sequtils, tables, strutils
type
Todo = object
id: int
title: string
completed: bool
var todos = initTable[int, Todo]()
var nextId = 1
routes:
get "/api/todos":
let items = toSeq(todos.values).mapIt(%*{
"id": it.id,
"title": it.title,
"completed": it.completed
})
resp %*{"data": items, "total": items.len}
post "/api/todos":
let body = parseJson(request.body)
let todo = Todo(
id: nextId,
title: body["title"].getStr(),
completed: false
)
todos[todo.id] = todo
inc nextId
resp Http201, %*{"data": todo}
get "/api/todos/@id":
let id = parseInt(@"id")
if todos.hasKey(id):
let todo = todos[id]
resp %*{"data": todo}
else:
resp Http404, %*{"error": "Not found"}
put "/api/todos/@id":
let id = parseInt(@"id")
if todos.hasKey(id):
let body = parseJson(request.body)
todos[id] = Todo(
id: id,
title: body{"title"}.getStr(todos[id].title),
completed: body{"completed"}.getBool(todos[id].completed)
)
resp %*{"data": todos[id]}
else:
resp Http404, %*{"error": "Not found"}
delete "/api/todos/@id":
let id = parseInt(@"id")
if todos.hasKey(id):
todos.del(id)
resp Http204
else:
resp Http404, %*{"error": "Not found"}
18.6 实战示例
🏢 场景:JWT 认证
import jester, json, times, strutils
const SECRET = "my-secret-key"
proc base64Encode(s: string): string =
# 简化的 Base64 编码
import std/base64
encode(s)
proc createToken(userId: string, expiresInHours = 24): string =
let header = base64Encode($ %*{"alg": "HS256", "typ": "JWT"})
let payload = base64Encode($ %*{
"sub": userId,
"iat": epochTime().int,
"exp": (epochTime() + expiresInHours.float * 3600).int
})
header & "." & payload & ".signature"
proc authMiddleware(request: Request): string =
let auth = request.headers.getOrDefault("Authorization", "")
if not auth.startsWith("Bearer "):
return ""
auth[7..^1]
routes:
post "/api/login":
let body = parseJson(request.body)
let username = body["username"].getStr()
let password = body["password"].getStr()
if username == "admin" and password == "password":
let token = createToken(username)
resp %*{"token": token}
else:
resp Http401, %*{"error": "Invalid credentials"}
get "/api/protected":
let token = authMiddleware(request)
if token.len == 0:
resp Http401, %*{"error": "Unauthorized"}
else:
resp %*{"message": "Access granted", "token": token}
本章小结
| 框架 | 安装 | 特点 |
|---|
| Jester | nimble install jester | 轻量、Sinatra 风格 |
| Prologue | nimble install prologue | 全功能、中间件 |
| HTTPbeast | nimble install httpbeast | 高性能 |
| Mummy | nimble install mummy | 多线程 |
练习
- 使用 Jester 实现一个 CRUD API
- 使用 Prologue 创建带中间件的 Web 应用
- 实现 JWT 认证系统
扩展阅读
← 上一章:外部函数接口 | 下一章:测试与质量 →