` 深度指南:400 Bad Request 响应方法的使用、源码原理与自定义扩展)
Sails 框架res.badRequest()深度指南400 Bad Request 响应方法的使用、源码原理与自定义扩展【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails导读res.badRequest()是 SailsRealtime MVC Framework for Node.js内置的响应方法之一用于向客户端返回 HTTP 400Bad Request响应明确告知请求无效——通常意味着请求携带了非法参数、非法请求头或试图执行应用逻辑不支持的操作。本文以 docs/reference/res/res.badRequest.md 为骨架结合本仓库中 lib/hooks/responses 的默认实现与 Blueprint Actions 源码系统讲解该方法的调用方式、默认行为、底层执行分支、自定义覆盖机制以及它在 Sails 脚手架路由Blueprints中被自动调用的完整链路。读完本文你将能在自己的控制器中熟练使用res.badRequest()并能够按需覆写它保持应用内错误响应的一致性与可维护性。res.badRequest()是什么res.badRequest()用于向客户端发送 HTTP 400Bad Request响应。400 属于 4xx 客户端错误类状态码语义是请求本身存在问题服务端无法处理。Sails 文档对此方法的定位是该方法用于向客户端返回 400Bad Request响应表明该请求无效。这通常意味着请求包含无效的参数或请求头或者它试图执行你的应用逻辑不支持的操作。在 Sails 应用中最典型的触发场景包括参数校验失败、必填字段缺失、数值超出允许范围、唯一性约束冲突如重复的用户名或邮箱、Waterline 查询条件criteria书写错误等。基本用法res.badRequest()是挂载在res对象上的方法可以在控制器Controller动作或自定义策略Policy中直接调用// 无参数调用返回默认的 400 响应体 return res.badRequest();或者// 携带数据调用返回的响应体会包含你提供的数据 return res.badRequest(data);调用时通常配合return关键字使用——因为该方法是terminal终结性方法执行完毕后请求处理流程即告结束return可以防止后续代码继续执行导致重复响应。默认行为详解与原文档描述一致res.badRequest()的默认行为包含两点核心约定响应状态码被设置为 400Sails 将传入的错误data以 JSON 形式发送如果未提供data则发送默认响应体——字符串Bad Request。不过翻阅本仓库中该方法的默认实现 lib/hooks/responses/defaults/badRequest.js你会发现默认行为实际上包含更精细的分支逻辑完整还原如下module.exports function badRequest(data) { // 通过 this 上下文获取 req 和 res由 responses 钩子注入 var req this.req; var res this.res; // 通过 req 获取 sails 应用实例 var sails req._sails; // 若提供了 data以 verbose 级别记录日志 if (!_.isUndefined(data)) { sails.log.verbose(Sending 400 (Bad Request) response: \n, data); } // 设置状态码为 400 res.status(400); // 未提供 data直接发送状态码 默认文本 if (_.isUndefined(data)) { return res.sendStatus(400); } if (_.isError(data)) { // 如果 data 是 Error 实例且没有自定义的 .toJSON() // 则用 util.inspect() 序列化后发送否则 res.json() 会把它变成空对象 {} if (!_.isFunction(data.toJSON)) { // 生产环境下不输出堆栈避免泄露内部信息 if (process.env.NODE_ENV production) { return res.sendStatus(400); } // 非生产环境以文本形式返回错误 inspect 结果 return res.send(util.inspect(data)); } } // 其余情况以 JSON 形式发送 data return res.json(data); };从源码中可以提炼出以下与使用直接相关的细节调用方式触发分支实际响应res.badRequest()data为undefined状态码 400响应体为默认文本Bad Requestres.badRequest(obj)普通对象状态码 400res.json(obj)返回 JSONres.badRequest(string)字符串状态码 400以 JSON 形式返回该字符串res.badRequest(err)Error 实例无自定义toJSON非生产环境_.isError(data)分支状态码 400util.inspect(err)的文本内容res.badRequest(err)Error 实例无自定义toJSONNODE_ENV production生产环境保护分支状态码 400仅返回默认文本不泄露堆栈res.badRequest(err)Error 实例带自定义toJSON正常 JSON 分支状态码 400返回err.toJSON()序列化结果值得特别注意的是Error 实例的两条安全分支普通res.json()序列化一个没有自定义toJSON的 Error 时只会得到空字典{}因此默认实现改用util.inspect()而为了避免在生产环境意外暴露stack堆栈信息当NODE_ENV production时则干脆退化为res.sendStatus(400)。如果你希望生产环境也返回结构化的错误信息可以为传入的 Error 显式实现toJSON方法或者直接覆写badRequest响应模块见下文自定义与覆盖一节。完整示例原文档给出了一个非常实用的参数校验场景——当请求参数超出业务允许范围时返回 400if ( req.param(amount) 123 ) return res.badRequest( Transaction limit exceeded. Please try again with an amount less than $123. ); }实际响应如下HTTP 状态码 400Content-Type 为application/jsonTransaction limit exceeded. Please try again with an amount less than $123.再举一个使用 Error 对象 自定义toJSON的进阶示例让客户端能拿到结构化错误码// api/controllers/shop/buy.js module.exports { friendlyName: Buy item, inputs: { amount: { type: number, required: true } }, exits: { limitExceeded: { responseType: badRequest } }, fn: async function (inputs, exits) { if (inputs.amount 123) { const err new Error(Transaction limit exceeded.); err.code E_LIMIT_EXCEEDED; err.toJSON () ({ code: err.code, message: err.message }); throw err; } // ...业务逻辑 return exits.success(); } };注上述exits: { limitExceeded: { responseType: badRequest } }的写法利用了 Sails 动作Actions的 responseType 机制属于res.badRequest()在动作层面的延伸用法具体可参考 docs/reference/application/sails.registerAction.md 与 docs/concepts/Helpers/Helpers.md 中关于 exits 的定义。自定义与覆盖从api/responses/badRequest.js说起原文档强调与其它内置自定义响应模块一样res.badRequest()的行为是可定制的。它执行的是应用中api/responses/badRequest.js定义的响应方法如果你的应用不存在badRequest.jsSails 会隐式使用默认行为。默认实现如何被加载与注入响应Response钩子的加载逻辑位于 lib/hooks/responses/index.js关键流程如下加载用户自定义响应loadModules调用sails.modules.loadResponses()实现见 lib/hooks/moduleloader/index.js后者使用includeAll.optional扫描sails.config.paths.responses默认即api/responses/目录下所有非md/txt的.js文件文件名作为响应方法名。保留键冲突检查若用户自定义响应名与view、status、set、get、cookie、clearCookie、redirect、location、charset、send、json、jsonp、type、format、attachment、sendfile、download、links、locals、render等保留键冲突会抛出Err.invalidCustomResponse致命错误。合并默认实现通过_.defaults(responseDefs, { ok: ..., negotiate: ..., notFound: ..., serverError: ..., forbidden: ..., badRequest: require(./defaults/badRequest) })把用户自定义响应与核心内置默认响应合并——用户定义优先缺失的键才使用默认值。绑定到res钩子的routes.before[all /*]中间件遍历sails.middleware.responses将每个响应方法bind到{ req, res }上下文后挂载到res对象上这正是默认实现里通过this.req/this.res取到请求与响应对象的原因。自定义badRequest.js的写法在 Sails 应用中创建api/responses/badRequest.js即可完全接管该方法的逻辑。例如统一返回带error字段的 JSON 结构// api/responses/badRequest.js module.exports function badRequest(data) { var req this.req; var res this.res; var sails req._sails; // 统一错误结构兼容字符串 / 对象 / Error var payload { error: Bad Request, details: (data data.message) ? data.message : data }; sails.log.verbose(Sending custom 400 (Bad Request) response: \n, payload); return res.status(400).json(payload); };通过路由目标语法绑定响应除了在控制器中显式调用Sails 还允许把路由直接绑定到某个命名响应。响应钩子通过监听route:typeUnknown事件见 lib/hooks/responses/onRoute.js教会路由系统理解{ response: xxx }目标语法// config/routes.js module.exports.routes { get /api/legacy-endpoint: { response: badRequest } };当请求命中该地址时Sails 会自动调用res.badRequest()。若绑定的响应名称不存在Sails 会记录一条错误日志并忽略该绑定不会崩溃。Blueprint ActionsSails 何时自动调用res.badRequest()原文档明确提到当请求携带非法参数时该方法会被 [Blueprint Actions] 自动调用。这一点在本仓库的脚手架路由源码中得到了充分印证。以 lib/hooks/blueprints/actions/find.js 为例.exec(function (err, matchingRecords) { if (err) { // 区分 Waterline 产生的 UsageError如非法查询条件与其他意外错误 switch (err.name) { case UsageError: return res.badRequest(formatUsageError(err, req)); default: return res.serverError(err); } } // ... });同样的模式遍布全部蓝图动作create、findOne、update、destroy、add、remove、replace、populate分别见 lib/hooks/blueprints/actions/create.js、lib/hooks/blueprints/actions/findOne.js、lib/hooks/blueprints/actions/update.js、lib/hooks/blueprints/actions/destroy.js、lib/hooks/blueprints/actions/add.js、lib/hooks/blueprints/actions/remove.js、lib/hooks/blueprints/actions/replace.js、lib/hooks/blueprints/actions/populate.js。归纳出两条自动触发规则UsageError来自 Waterline 的用法错误例如非法查询条件criteria蓝图动作统一调用res.badRequest(formatUsageError(err, req))AdapterError且code E_UNIQUE唯一性约束冲突如创建了重复的记录在create与update动作中同样返回 400 而不是 500见 create.js。formatUsageError的实现见 lib/hooks/blueprints/formatUsageError.js它为 Waterline 的 UsageError 附加toJSON方法输出包含code与details字段的结构化 JSON同时在非生产环境额外附带一条面向开发者的排错提示如建议检查模型属性与客户端请求数据是否匹配生产环境则输出更简洁的消息避免泄露实现细节。这也是为什么res.badRequest(err)传入带toJSON的 Error 时会走默认实现中的正常res.json(data)分支——错误对象已经自描述。使用注意事项terminal 方法res.badRequest()会终结当前请求是给定请求处理流程中应用运行的最后一行业务代码因此文档与官方约定始终建议使用return res.badRequest(...)的形式即使 JavaScript 中return之后仍有语句执行也会在方法内部结束。可被覆盖与res.ok()、res.serverError()等其它用户态响应方法一样res.badRequest()可以在应用的api/responses/badRequest.js中被覆写或改造覆写后 Sails 会优先使用你的实现详见上文加载合并流程。覆写时请留意 lib/hooks/responses/index.js 中列出的保留键不要用保留名定义响应。生产环境堆栈保护默认实现刻意避免在生产环境NODE_ENV production向客户端泄露 Error 的stack若需在生产环境返回结构化错误请为 Error 提供自定义toJSON或覆写响应模块。Blueprint 自动触发当使用 Sails 默认的 Blueprint Actions 且请求参数非法UsageError或触发唯一性冲突E_UNIQUE时无需编写任何代码res.badRequest()就会被自动调用。与相邻响应方法的分工400 只描述客户端请求有误。若错误源于服务端自身的异常应使用res.serverError()对应 500资源不存在用res.notFound()404权限不足用res.forbidden()403。这些默认实现同位于 lib/hooks/responses/defaults 目录行为风格一致便于统一维护。小结res.badRequest()看似只是发送一个 400 响应但其背后承载着 Sails 在错误响应一致性上的设计默认实现涵盖了无参数、普通数据、Error 对象、生产环境安全等多种分支通过api/responses/badRequest.js可以零成本定制全局错误结构而 Blueprint Actions 在UsageError与E_UNIQUE场景下的自动调用则让基于脚手架搭建的应用天然具备规范的参数校验反馈。理解这条从控制器调用 → 响应钩子注入 → 模块加载合并 → 蓝图自动触发的完整链路将帮助你在实际项目中写出更可靠、更一致、更易维护的错误处理代码。【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考