第一章:VSCode配置Go环境:99%开发者忽略的3个致命陷阱及修复方案
Go扩展与语言服务器的版本错配
VSCode官方Go扩展(golang.go)已弃用旧版gopls集成方式,但许多教程仍指导安装过时的go-outline或手动配置gopls二进制路径。错误做法会导致代码跳转失效、无法识别go.mod依赖。正确操作是:卸载所有非官方Go相关扩展,仅保留最新版golang.go(v0.38+),并确保gopls由扩展自动管理——执行以下命令强制更新:
# 删除手动安装的gopls(避免冲突)
rm -f $(go env GOPATH)/bin/gopls
# 让VSCode自动下载匹配当前Go版本的gopls
# (重启VSCode后,状态栏右下角显示"gopls (v0.14.x)"即成功)
GOPATH与Go Modules共存引发的模块解析失败
当GOPATH环境变量被显式设置且项目位于$GOPATH/src/下时,即使项目含go.mod,gopls仍可能降级为GOPATH模式,导致import路径解析错误、无法识别本地模块。验证方法:在VSCode终端运行go env GOPATH,若输出非空值且项目不在$GOPATH/src/内,则必现此问题。修复方案:彻底移除GOPATH环境变量,改用模块化工作流:
- 在用户级shell配置中注释掉
export GOPATH=... - 重启终端与VSCode
- 新建项目直接执行
go mod init example.com/myapp(无需$GOPATH)
工作区设置覆盖全局Go配置
VSCode工作区(.vscode/settings.json)中若存在"go.gopath"或"go.toolsGopath"字段,将强制覆盖用户设置,导致多项目环境不一致。常见错误配置:
{
"go.gopath": "/old/path", // ❌ 覆盖全局,且路径可能已失效
"go.toolsGopath": "/tmp/tools" // ❌ 手动指定工具路径,干扰自动管理
}
✅ 正确做法:删除工作区中所有go.*键,统一交由全局设置(Settings > Extensions > Go > Gopls: Args)控制。必要时仅通过"go.useLanguageServer": true显式启用LSP。
| 陷阱现象 | 快速诊断命令 | 根本修复动作 |
|---|---|---|
| import路径标红无提示 | go list -m all 2>/dev/null |
清空GOPATH,确认go env GO111MODULE=on |
| Ctrl+Click跳转到源码失败 | gopls version(检查是否运行中) |
卸载旧扩展,重启VSCode触发自动安装 |
| 保存后自动格式化异常 | go fmt ./...(对比VSCode行为) |
删除.vscode/settings.json中全部go.*项 |
第二章:Go开发环境基石:PATH、GOPATH与GOBIN的隐式冲突
2.1 深度解析Go 1.16+模块化后GOPATH的废弃逻辑与残留影响
Go 1.16 起,GOPATH 不再参与模块感知型构建流程,但其环境变量仍被部分工具链隐式读取。
模块模式下的路径决策逻辑
# Go 工具链实际执行的路径解析优先级(伪代码逻辑)
if GO111MODULE=on && go.mod exists:
use module-aware mode (ignore GOPATH/src for import resolution)
else:
fallback to GOPATH/src (legacy behavior)
该逻辑表明:
GOPATH仅在模块未启用或go.mod缺失时生效;模块启用后,GOROOT和当前目录go.mod成为唯一权威源。
常见残留影响场景
go install命令仍会将二进制写入$GOPATH/bin(即使模块启用)go list -f '{{.Dir}}' package在非模块目录下仍回退至$GOPATH/srcGOCACHE和GOPROXY独立于GOPATH,无依赖关系
| 场景 | 是否受 GOPATH 影响 | 说明 |
|---|---|---|
go build(含 go.mod) |
❌ 否 | 完全基于模块图解析 |
go install hello@latest |
⚠️ 部分 | 输出路径仍由 $GOPATH/bin 决定 |
go get(模块启用) |
❌ 否 | 依赖下载至 $GOCACHE,不触碰 $GOPATH/src |
graph TD
A[go command invoked] --> B{GO111MODULE=on?}
B -->|Yes| C[Search for go.mod upward]
C -->|Found| D[Use module graph: ignore GOPATH/src]
C -->|Not found| E[Fall back to GOPATH/src]
B -->|No| E
2.2 PATH中多版本Go二进制混杂导致vscode-go插件静默降级的实证复现
复现场景构建
在 PATH 中并存 go1.21.6(/usr/local/go/bin)与 go1.22.3(~/go/bin),且后者位于前者之前:
# 查看实际生效的 go 版本
$ echo $PATH | tr ':' '\n' | grep -E "(local|go)"
/usr/local/go/bin # go1.21.6
~/go/bin # go1.22.3 ← 实际优先命中
$ which go
~/go/bin/go
$ go version
go version go1.22.3 darwin/arm64
此处
vscode-go插件启动时仅调用go version,未校验$GOROOT或go env GOROOT,误将1.22.3视为“兼容主版本”,但其内部依赖的gopls@v0.14.3实际要求go1.21+且不兼容1.22.3的调试协议变更,导致插件回退至gopls@v0.13.4—— 无提示、无日志、功能降级。
降级路径验证
| 触发条件 | 插件行为 | 可观测现象 |
|---|---|---|
go version ≥ 1.22.0 |
自动降级 gopls |
跳转定义失效、无 hover 类型信息 |
GOROOT 未显式配置 |
忽略 go env GOROOT |
gopls 启动日志显示 version=0.13.4 |
graph TD
A[vscode-go 启动] --> B[执行 go version]
B --> C{go version ≥ 1.22.0?}
C -->|是| D[静默选用旧版 gopls]
C -->|否| E[加载匹配版 gopls]
D --> F[功能受限:无语义高亮/诊断延迟]
2.3 GOBIN未显式配置引发go install生成工具无法被VSCode识别的调试链路断裂
当 GOBIN 未显式设置时,go install 默认将二进制写入 $GOPATH/bin(Go 1.18+ 为 $HOME/go/bin),但 VSCode 的 Go 扩展仅自动扫描 PATH 中的可执行文件。
环境变量优先级链
GOBIN>GOPATH/bin>PATH搜索路径- 若
GOBIN未设且$HOME/go/bin不在PATH中,VSCode 无法定位gopls、dlv等工具
验证步骤
# 检查当前 GOBIN 和 PATH 是否包含默认 bin 路径
go env GOBIN # 输出空字符串即未设置
echo $PATH | grep -o "$HOME/go/bin" # 若无输出,则链路断裂
此命令验证
GOBIN是否生效及PATH是否覆盖默认安装位置。go env GOBIN返回空表示依赖隐式路径;grep失败说明 VSCode 启动时无法发现工具。
推荐修复方案
- ✅ 显式设置:
export GOBIN=$HOME/go/bin - ✅ 注入 PATH:
export PATH=$GOBIN:$PATH - ❌ 忽略:依赖
go install默认行为(不可靠)
| 状态 | VSCode 工具检测 | 调试器启动 |
|---|---|---|
GOBIN 未设 + PATH 缺失 |
❌ | ❌ |
GOBIN 显式 + PATH 包含 |
✅ | ✅ |
graph TD
A[go install cmd] --> B{GOBIN set?}
B -- Yes --> C[Write to GOBIN]
B -- No --> D[Write to $HOME/go/bin]
C & D --> E{In PATH?}
E -- Yes --> F[VSCode finds tool]
E -- No --> G[Debug chain broken]
2.4 实战:通过go env -w与shell profile双校验构建可重现的纯净Go执行环境
构建可重现的 Go 环境需同时约束运行时配置与启动上下文,缺一不可。
为什么单靠 go env -w 不够?
go env -w 写入的是 $HOME/go/env(Go 1.21+)或 $GOROOT/misc/bash/go-env.bash,但 shell 启动时仍可能被 GOROOT/GOPATH 环境变量覆盖,尤其在 CI 或多用户共享终端场景中。
双校验机制设计
- 第一层(Go 内部):用
go env -w固化关键变量; - 第二层(Shell 层):在
~/.bashrc或~/.zshrc中显式导出并校验一致性。
# 推荐写法:先清空再写入,避免残留污染
go env -w GOROOT="" GOPATH="$HOME/go-prod" GOBIN="$HOME/go-prod/bin"
go env -w GO111MODULE=on GOPROXY=https://proxy.golang.org,direct
此命令强制清除
GOROOT(交由go install自动发现),锁定模块代理与模块模式,并将工作区隔离至go-prod。GOBIN独立设置可防止误用系统bin目录。
校验一致性脚本(放入 profile)
# 在 ~/.zshrc 末尾追加:
if [[ "$(go env GOPATH)" != "$HOME/go-prod" ]]; then
echo "⚠️ go env 与 profile 不一致!正在重载..." >&2
go env -w GOPATH="$HOME/go-prod"
fi
关键变量双源对照表
| 变量 | go env -w 设置位置 |
Shell profile 显式导出 | 是否必须双设 |
|---|---|---|---|
GOPATH |
✅ $HOME/go/env |
✅ export GOPATH=... |
是 |
GOPROXY |
✅ | ❌(go env 优先) | 否 |
PATH |
❌(go 不管理) | ✅ export PATH=$GOPATH/bin:$PATH |
是 |
graph TD
A[Shell 启动] --> B[加载 ~/.zshrc]
B --> C[执行 go env -w 校验逻辑]
C --> D{GOPATH 一致?}
D -->|否| E[自动修正 go env]
D -->|是| F[进入纯净 Go 环境]
2.5 验证方案:编写诊断脚本自动检测PATH/GOPATH/GOBIN三者一致性与权限冲突
核心检测逻辑
诊断脚本需同时验证三重约束:
- 路径存在性:
GOPATH和GOBIN是否为真实目录 - 可写性:当前用户对
GOBIN具备写权限(避免go install失败) - PATH 包含性:
GOBIN必须出现在PATH中,且优先级高于其他 Go 工具链路径
诊断脚本(Bash)
#!/bin/bash
GOBIN=$(go env GOBIN 2>/dev/null)
GOPATH=$(go env GOPATH 2>/dev/null)
PATH_DIRS=($(echo $PATH | tr ':' '\n'))
echo "✅ GOBIN: $GOBIN"
echo "✅ GOPATH: $GOPATH"
echo "🔍 PATH contains GOBIN: $(printf '%s\n' "${PATH_DIRS[@]}" | grep -q "^$GOBIN$" && echo "YES" || echo "NO")"
echo "🔒 GOBIN writable: $( [ -w "$GOBIN" ] && echo "YES" || echo "NO")"
逻辑说明:
go env安全获取环境值(忽略错误输出);tr ':' '\n'将 PATH 拆为行便于精确匹配;-w检测写权限而非仅存在性,覆盖 NFS/mount 权限陷阱。
常见冲突模式
| 冲突类型 | 表现 | 修复建议 |
|---|---|---|
| GOBIN 不在 PATH | command not found |
export PATH="$GOBIN:$PATH" |
| GOBIN 不可写 | permission denied |
chmod u+w $GOBIN 或改用 ~/go/bin |
graph TD
A[启动诊断] --> B{GOBIN 存在?}
B -->|否| C[报错:GOBIN 未设置]
B -->|是| D{GOBIN 在 PATH?}
D -->|否| E[警告:PATH 缺失 GOBIN]
D -->|是| F{GOBIN 可写?}
F -->|否| G[阻断:权限不足]
第三章:VSCode-Go插件核心机制失效的三大表征与根因定位
3.1 “Go: Install/Update Tools”命令卡死背后的gopls依赖下载代理与checksum校验失败分析
当 VS Code 的 Go 扩展执行 Go: Install/Update Tools 时,若卡在 gopls 安装阶段,常见根因是模块校验失败或代理不可达。
校验失败典型日志
go install golang.org/x/tools/gopls@latest
# → verifying golang.org/x/tools/gopls@v0.15.2: checksum mismatch
# downloaded: h1:abc123... (from sum.golang.org)
# go.sum: h1:def456...
该错误表明本地 go.sum 记录的哈希值与 sum.golang.org 返回的不一致——可能因中间代理篡改、缓存污染或 Go 环境未配置 GOPROXY。
关键环境变量对照表
| 变量 | 推荐值 | 作用 |
|---|---|---|
GOPROXY |
https://proxy.golang.org,direct |
指定模块代理链,direct 为兜底直连 |
GOSUMDB |
sum.golang.org |
启用校验数据库,禁用则设为 off(仅调试用) |
代理链失效流程
graph TD
A[VS Code 调用 go install] --> B{GOPROXY 配置?}
B -- 无/错误 --> C[尝试直连 proxy.golang.org]
B -- 有效 --> D[请求代理获取 module + sum]
C & D --> E[校验 sum.golang.org 签名]
E -- 失败 --> F[阻塞并重试 → 卡死]
临时绕过校验(仅限离线调试):
export GOSUMDB=off
go install golang.org/x/tools/gopls@latest
⚠️ GOSUMDB=off 会跳过所有模块完整性校验,生产环境严禁使用。
3.2 IntelliSense失效但无报错——gopls进程崩溃却未触发VSCode重启策略的监控盲区
当 gopls 进程异常退出(如 SIGSEGV 或 OOM kill),VSCode 的 LanguageClient 仅监听 exit code === 0 的显式终止,对静默崩溃(如进程消失但 exit code 未被捕获)缺乏心跳探测。
数据同步机制
VSCode 通过 stdio 与 gopls 通信,依赖 onExit 事件触发重启。但若进程被内核强制终止,Node.js 的 child_process 可能无法可靠捕获 exit 事件。
# 查看 gopls 实际存活状态(非 VSCode 声明状态)
ps aux | grep '[g]opls' | awk '{print $2, $11}'
此命令绕过 VSCode UI 状态缓存,直接验证 OS 层进程存在性;
$2为 PID,$11显示启动命令路径,可识别多实例冲突或 stale 进程。
监控盲区对比
| 检测方式 | 能捕获崩溃? | 是否默认启用 | 延迟 |
|---|---|---|---|
onExit 事件 |
❌(静默退出) | ✅ | 即时 |
process.kill() 后检查 |
✅ | ❌(需手动) | |
| TCP 心跳探活 | ✅ | ❌ | 可配 |
graph TD
A[gopls 启动] --> B[VSCode 注册 onExit]
B --> C{进程是否正常 exit?}
C -->|是| D[触发重启]
C -->|否| E[进入监控盲区]
E --> F[IntelliSense 持续失效]
3.3 go.mod文件变更后符号索引延迟更新,源于workspace trust与language server缓存策略误配
数据同步机制
当 go.mod 文件被修改(如添加 require github.com/gin-gonic/gin v1.9.1),Go LSP(如 gopls)本应触发增量索引重建,但实际常延迟数秒至数十秒——根源在于 VS Code 的 workspace trust 状态未主动通知语言服务器。
缓存策略冲突点
- workspace trust 为
untrusted时,VS Code 默认禁用部分后台服务,包括gopls的watchFileChanges能力 gopls却仍启用cache.dir本地磁盘缓存,且默认cache.invalidateOnModChange=false
验证与修复配置
// .vscode/settings.json(需在 trusted workspace 中生效)
{
"gopls": {
"build.experimentalWorkspaceModule": true,
"cache.invalidateOnModChange": true
}
}
该配置强制 gopls 在检测到 go.mod mtime 变更时清空模块解析缓存并重载 go list -m all 结果。否则,符号跳转仍将指向旧版本的 types.Info。
| 配置项 | 默认值 | 启用后效果 |
|---|---|---|
cache.invalidateOnModChange |
false |
触发 invalidateCache() + loadPackage 重同步 |
build.experimentalWorkspaceModule |
false |
启用基于 go.work 的多模块联合索引 |
graph TD
A[go.mod 修改] --> B{workspace trusted?}
B -->|Yes| C[gopls watchEvent → invalidate cache]
B -->|No| D[忽略 fsnotify, 仅轮询 checkModTime]
C --> E[重建 symbol index]
D --> F[延迟 5s+ 后被动 reload]
第四章:模块化项目中的路径幻觉:Workspace、Module与File System的三重割裂
4.1 多module workspace下vscode-go错误推导主module路径导致go.test不执行的案例还原
环境复现步骤
- 在 VS Code 中打开含
app/(主 module)和shared/(独立 module)的多 module 工作区 app/go.mod:module github.com/example/appshared/go.mod:module github.com/example/shared
关键错误现象
VS Code 的 vscode-go 扩展将 shared/ 误判为主 module,导致 go.test 命令在 app/ 内执行时被注入错误 -modfile=shared/go.mod 参数。
# 实际触发的错误命令(日志截取)
go test -modfile=/path/to/shared/go.mod ./...
逻辑分析:
vscode-go依赖gopls的workspaceFolders推导GOMOD路径,当 workspace 同时包含多个go.mod且未显式配置"go.toolsEnvVars"时,其按字典序选取首个匹配目录,而非当前编辑文件所在 module。
修复方案对比
| 方案 | 操作 | 生效范围 |
|---|---|---|
设置 go.gopath + go.goroot |
显式指定主 module 根路径 | 全局 workspace |
添加 .vscode/settings.json |
"go.toolsEnvVars": {"GOMOD": "/path/to/app/go.mod"} |
当前 workspace |
graph TD
A[打开多 module workspace] --> B{vscode-go 扫描 go.mod}
B --> C[按路径排序取首个]
C --> D[错误绑定为 GOMOD]
D --> E[go.test 使用错误 modfile]
4.2 使用相对路径导入(./pkg)触发gopls类型检查绕过与go list元数据解析失败的底层机制
当模块内使用 import "./pkg" 这类非规范相对路径时,gopls 会跳过该导入包的类型检查——因其不满足 Go 的导入路径语义约束(必须为模块路径或标准库路径)。
根本原因:go list 的路径规范化拦截
go list -json ./pkg 在调用时被 cmd/go 内部拒绝,返回错误:
# 错误输出示例
go list: invalid import path: "./pkg"
该校验发生在 load.PackagesFromArgs 阶段,早于 gopls 的 snapshot.Load 流程。
影响链路
gopls依赖go list输出构建PackageGraph./pkg导致go list返回空/错误 →snapshot中缺失该包元数据 → 类型推导中断、符号不可见
| 组件 | 行为 |
|---|---|
go list |
直接拒绝相对路径,不生成 JSON |
gopls |
缺失包快照,跳过分析 |
go build |
同样报错,行为一致 |
graph TD
A[import “./pkg”] --> B[go list -json ./pkg]
B --> C{路径校验失败?}
C -->|是| D[返回 error]
C -->|否| E[输出 package JSON]
D --> F[gopls 忽略该 import]
4.3 go.work文件未被VSCode感知引发跨module跳转失效的注册时机与reload触发条件
Go语言服务器的workspace初始化流程
Go extension 启动时,gopls 通过 workspace/didChangeConfiguration 和 workspace/didChangeWatchedFiles 事件感知 go.work 变更。但初始加载仅扫描 .vscode/settings.json 中显式配置的 go.gopath 或 go.toolsEnvVars,不主动枚举根目录下的 go.work。
reload触发的关键条件
以下任一操作可触发 gopls 重新解析 workspace:
- 手动执行
Developer: Reload Window - 修改
go.work后保存,且文件已被 VSCode 文件监听器捕获(需启用"files.watcherInclude": {"**/go.work": true}) - 在命令面板运行
Go: Restart Language Server
gopls对go.work的注册逻辑
// gopls/internal/lsp/cache/session.go#L212
func (s *Session) loadWorkspace(ctx context.Context, folder protocol.WorkspaceFolder) error {
// 注意:仅当folder.URI指向含go.work的目录,或显式设置GOWORK环境变量时才调用
if workFile := findGoWork(folder.URI.Filename()); workFile != "" {
return s.loadWorkFile(ctx, workFile) // ✅ 此处才注册multi-module视图
}
return s.loadSingleModule(ctx, folder.URI.Filename())
}
该逻辑说明:若工作区根目录未包含 go.work,即使子目录存在,gopls 也不会自动上溯查找——导致跨 module 符号跳转失效。
| 触发场景 | 是否触发 reload | 原因 |
|---|---|---|
新建 go.work 并保存 |
否 | 文件监听默认忽略 go.work(watcher 配置缺失) |
修改 go.work 后手动 Reload Window |
是 | 强制重建 session,重走 loadWorkspace 流程 |
在设置中添加 "go.gowork": "auto" |
否 | 该配置项实际未被 gopls v0.14+ 支持 |
graph TD
A[VSCode 启动] --> B{gopls 初始化}
B --> C[读取 workspace folder URI]
C --> D{URI 目录下存在 go.work?}
D -->|是| E[调用 loadWorkFile → 注册 multi-module]
D -->|否| F[回退为单 module 模式 → 跳转失效]
4.4 实战:基于go list -json + vscode settings.json动态生成workspace-aware launch.json模板
Go 工作区调试配置常因模块路径、主包位置变化而失效。手动维护 launch.json 易出错且不可复用。
核心思路
- 利用
go list -json -f '{{.ImportPath}} {{.Dir}}' ./...扫描所有包,精准定位main包; - 解析
.vscode/settings.json中的go.toolsEnvVars和go.gopath等上下文; - 模板化生成
launch.json,自动适配当前 workspace 结构。
关键命令示例
go list -json -f '{{if eq .Name "main"}}{"package":"{{.ImportPath}}","dir":"{{.Dir}}"}{{end}}' ./...
此命令过滤出所有
main包,输出 JSON 片段;-f模板中{{.Name}}判断入口包,{{.Dir}}提供可执行文件构建路径,确保program字段绝对可靠。
输出字段映射表
| launch.json 字段 | 来源 | 说明 |
|---|---|---|
program |
{{.Dir}}/main.go |
主包所在目录(需补全路径) |
env |
settings.json 中定义 |
复用用户环境变量配置 |
graph TD
A[go list -json] --> B{过滤 main 包}
B --> C[提取 Dir/ImportPath]
C --> D[读取 settings.json]
D --> E[合并生成 launch.json]
第五章:终极防护体系:自动化验证、CI集成与团队标准化落地
构建可复现的安全验证流水线
在某金融客户的核心支付网关项目中,我们基于OpenAPI 3.0规范自动生成217个边界测试用例,并嵌入GitLab CI的security-test阶段。每次PR提交触发以下流程:openapi-validator → fuzzing-engine(afl++定制版)→ runtime-behavior-monitor(eBPF hook)。流水线平均耗时4分12秒,拦截了13类未授权资源访问漏洞,包括路径遍历绕过和JWT密钥泄漏风险。
CI/CD中的策略即代码实践
采用OPA(Open Policy Agent)将安全策略声明为Rego语言规则,实现策略版本化与灰度发布:
package security.http
default allow = false
allow {
input.method == "POST"
input.path == "/api/v1/transfer"
input.headers["X-Auth-Token"]
io.jwt.decode_verify(input.headers["X-Auth-Token"], {"cert": data.certs.payment_ca})[_]
count(input.body.amount) > 0
input.body.amount < 50000.00
}
该策略与Argo CD同步部署,策略变更通过policy-review审批门禁,确保所有环境策略一致性。
团队标准化落地的三阶推进模型
| 阶段 | 关键动作 | 工具链支撑 | 覆盖率提升 |
|---|---|---|---|
| 启动期 | 安全检查清单强制嵌入Jira模板 | Jira Automation + Confluence API | 需求文档合规率从32%→89% |
| 深化期 | IDE插件自动注入安全注释与校验断言 | VS Code Extension + SonarQube Plugin | 开发阶段漏洞检出率+64% |
| 成熟期 | 生产流量镜像至沙箱环境执行实时策略验证 | Envoy Proxy + Istio Mirror + OPA Gatekeeper | 线上误报率降至0.3% |
跨职能协作机制设计
建立“安全左移作战室”,每周同步三类数据看板:① CI流水线各环节失败根因分布(饼图),② 安全策略拒绝日志TOP10(含原始请求Payload脱敏样本),③ 开发者修复时效热力图(按模块/人员维度)。运维团队通过Kubernetes Event Webhook自动创建Sev-1级安全事件工单,平均响应时间压缩至8.3分钟。
实时反馈闭环系统
在前端应用中集成轻量级SDK,当用户触发高危操作(如导出超限数据)时,前端立即调用/v1/policy/evaluate端点进行实时鉴权,并根据OPA返回的decision_id动态渲染提示文案与操作按钮状态。该机制上线后,内部审计发现的越权操作下降92%,且所有策略决策均记录至Apache Kafka主题security-audit-log供溯源分析。
文档即防护的实践范式
Confluence空间启用自动同步功能,所有API契约变更、策略版本更新、CI流水线配置修改均触发文档快照存档。每个策略页面底部嵌入Mermaid时序图,可视化展示该策略在完整请求生命周期中的介入时机:
sequenceDiagram
participant C as Client
participant G as API Gateway
participant O as OPA
participant S as Service
C->>G: POST /transfer
G->>O: policy.evaluate(request)
O-->>G: {allow:true, decision_id:"p-2024-087"}
G->>S: Forward with X-Decision-ID header
S->>C: 200 OK 