提问人:suchislife 提问时间:8/8/2023 更新时间:9/14/2023 访问量:232
JSON-RPC v2 API。HTTP 层。API 响应的适当 HTTP 层状态代码是什么?
JSON-RPC v2 API. HTTP Layer. What are the appropriate HTTP Layer status codes for API responses?
问:
我一直在开发一个 Deno 脚本,该脚本使用 Deno.serve() 实现 JSON-RPC v2 API,该 API 最近已稳定。虽然我已经设置了基本的 CRUD 操作和错误处理,但我不确定我应该在不同场景中使用的 HTTP 状态代码。
我上网发现,在 JSON-RPC 2.0 中,协议本身并没有严格定义特定 HTTP 状态码的使用。相反,它侧重于 JSON 有效负载,以传达成功和错误状态。但是,当 JSON-RPC 通过 HTTP 传输时,有一些常见的约定:
- 200 OK:这是 JSON-RPC 响应最常用的状态代码,既适用于成功调用,也适用于导致 JSON-RPC 错误的调用。成功调用和错误之间的区别是在 JSON 有效负载本身中使用 and 字段进行的。
result
error
- 500 内部服务器错误:当出现阻止处理 JSON-RPC 请求的服务器错误时,可以使用此功能。这是服务器端问题的一般指示。
- 404 Not Found:虽然方法未找到错误(通常与有效负载中的错误通信)不常见,但如果找不到 JSON-RPC 端点本身,则可以使用此状态。
200 OK
-32601 Method not found
- 400 错误请求:这可用于格式错误的请求,其中 JSON 无效或请求不符合 JSON-RPC 格式。
我使用了以下状态代码:
- 成功操作 200
- 400 表示客户端错误(如无效参数)
- 404 表示未找到的路线
- 501 表示未实现的方法
有人可以澄清或提供哪些状态代码适用于不同的 JSON-RPC v2 方案的参考吗?真的只有这4个吗?
201、204、401、403、405、415、503呢?这是我对HTTP被用作传输层以及我过度思考语义感到困惑的部分......
代码如下:
// Configuration (read-only!)
const config = {
rpc: {
service: {
hostname: "0.0.0.0",
port: 3000,
routes: {
root: "/service",
service: "/service/v2",
status: "/service/v2/status",
docs: "/service/v2/docs",
},
},
},
} as const;
// Config Type definition
type Config = typeof config;
// DataStore Type definition
type DataStore = {
[key: string]: any;
};
// Params Type definitions
type CreateParams = {
id: string;
name?: string;
age?: number;
};
type ReadParams = {
id: string;
name?: string;
age?: number;
};
type ListParams = {
startIndex: number;
endIndex: number;
};
type UpdateParams = {
id: string;
name?: string;
age?: number;
};
type DeleteParams = {
id: string;
name?: string;
age?: number;
};
// Data store for CRUD operations
const DATA_STORE: DataStore = {};
// CRUD methods
const methods = {
// METHOD -> CREATE
create: (params: CreateParams) => {
const { id } = params;
if (DATA_STORE[id]) {
throw new Error("ID already exists");
}
DATA_STORE[id] = params;
return [{ success: true }];
},
// METHOD -> READ
read: (params: ReadParams) => {
const { id } = params;
if (!DATA_STORE[id]) {
throw new Error("ID not found");
}
return [DATA_STORE[id]];
},
// METHOD -> LIST
list: (params: ListParams) => {
const { startIndex, endIndex } = params;
if (startIndex === undefined || endIndex === undefined) {
throw new Error(
"Both startIndex and endIndex are required for the list method",
);
}
return Object.entries(DATA_STORE)
.slice(startIndex, endIndex + 1)
.map(([key, value]) => ({ [key]: value }));
},
// METHOD -> UPDATE
update: (params: UpdateParams) => {
const { id } = params;
if (!DATA_STORE[id]) {
throw new Error("ID not found");
}
DATA_STORE[id] = params;
return [{ success: true }];
},
// METHOD -> DELETE
delete: (params: DeleteParams) => {
const { id } = params;
if (!DATA_STORE[id]) {
throw new Error("ID not found");
}
delete DATA_STORE[id];
return [{ success: true }];
},
};
// REQUEST -> HANDLER
async function handler(request: Request): Promise<Response> {
const reqUrl = request.url as string;
const { pathname } = new URL(reqUrl);
if (pathname !== config.rpc.service.routes.service) {
return new Response("HTTP 404: Not Found", { status: 404 });
}
const reqBody = await request.json();
const { method, params } = reqBody;
switch (method) {
// CASE -> method.create()
case "create":
try {
const result = methods.create(params[0]);
const createSuccess = {
jsonrpc: "2.0",
id: "request-id",
result: [
{
success: true,
},
],
};
return new Response(JSON.stringify(createSuccess), { status: 200 });
} catch (error) {
const createError = {
jsonrpc: "2.0",
id: "request-id",
error: {
code: -32000,
// message: "Error message describing the nature of the error",
message: error.message,
data: "Optional data about the error",
},
};
return new Response(JSON.stringify(createError), { status: 400 });
}
// CASE -> method.read()
case "read":
try {
const result = methods.read(params[0]);
const readSuccess = {
jsonrpc: "2.0",
id: "request-id",
result: result,
};
return new Response(
JSON.stringify(readSuccess),
{ status: 200 },
);
} catch (error) {
const readError = {
jsonrpc: "2.0",
id: "request-id",
error: {
code: -32000,
// message: "Error message describing the nature of the error",
message: error.message,
data: "Optional data about the error",
},
};
return new Response(JSON.stringify(readError), { status: 400 });
}
// CASE -> method.list()
case "list":
try {
const result = methods.list(params[0]);
const listSuccess = {
jsonrpc: "2.0",
id: "request-id",
result: result,
};
return new Response(JSON.stringify(listSuccess), { status: 200 });
} catch (error) {
const listError = {
jsonrpc: "2.0",
id: "request-id",
error: {
code: -32000,
// message: "Error message describing the nature of the error",
message: error.message,
data: "Optional data about the error",
},
};
return new Response(JSON.stringify(listError), { status: 400 });
}
// CASE -> method.update()
case "update":
try {
const result = methods.update(params[0]);
const updateSuccess = {
jsonrpc: "2.0",
id: "request-id",
result: [
{
success: true,
},
],
};
return new Response(JSON.stringify(updateSuccess), { status: 200 });
} catch (error) {
const updateError = {
jsonrpc: "2.0",
id: "request-id",
error: {
code: -32000,
// message: "Error message describing the nature of the error",
message: error.message,
data: "Optional data about the error",
},
};
return new Response(JSON.stringify(updateError), { status: 400 });
}
// CASE -> method.delete()
case "delete":
try {
const result = methods.delete(params[0]);
const deleteSuccess = {
jsonrpc: "2.0",
id: "request-id",
result: result,
};
return new Response(JSON.stringify(deleteSuccess), { status: 200 });
} catch (error) {
const deleteError = {
jsonrpc: "2.0",
id: "request-id",
error: {
code: -32000,
// message: "Error message describing the nature of the error",
message: error.message,
data: "Optional data about the error",
},
};
return new Response(JSON.stringify(deleteError), { status: 400 });
}
default:
// CASE -> method unknown
const rpcMethodError = {
jsonrpc: "2.0",
id: "request-id",
error: {
code: -32000,
// message: "Error message describing the nature of the error",
message: "RPC Method not implemented.",
data: "Optional data about the error",
},
};
return new Response(JSON.stringify(rpcMethodError), { status: 501 });
}
}
// Setup and start the Deno server
Deno.serve({
port: config.rpc.service.port,
hostname: config.rpc.service.hostname,
onListen({ port, hostname }) {
console.log(
`%cDeno v${Deno.version.deno} : Typescript v${Deno.version.typescript} : V8 v${Deno.version.v8}
Application: Deno JSON RPCv2 Server based on OpenRPC specification
Permissions: --allow-net=${hostname}:${port}
Gateway URL: http://${hostname}:${port}
Root: http://${hostname}:${port}${config.rpc.service.routes.root}
Service: http://${hostname}:${port}${config.rpc.service.routes.service}
Status: http://${hostname}:${port}${config.rpc.service.routes.status}
Docs: http://${hostname}:${port}${config.rpc.service.routes.docs}`,
"color: #7986cb",
);
},
onError(error: unknown) {
console.error(error);
return new Response("HTTP 500: Internal Server Error", {
status: 500,
headers: { "content-type": "text/plain" },
});
},
}, handler);
答:
0赞
Neil Mayhew
9/14/2023
#1
最佳做法是将 200 状态代码用于由方法本身生成的所有错误,而不是由 http 服务器尝试调用该方法生成的错误。因此,例如,尝试创建已存在的资源将是 200 状态,而 HTTP 身份验证问题将是 403。
当然,如果你对代码进行结构化,以便将方法处理程序抽象到一个单独的层中,而处理程序则直接处理 http 的细节,这要容易得多。换言之,方法处理程序应该与传输无关,至少在概念上是这样。
以下是我找到的一些参考资料:
这两者的要点是,非 200 代码用于指示 HTTP 传输存在问题,而不是用于与方法调用的语义相关的错误。
下面是 JSON-RPC 网站“历史”部分的文档:
这与我上面概述的原则大致一致。
评论
1赞
suchislife
9/14/2023
男人。。。我想你只是通过我过度思考的头骨得到了这一点。传输层。每当我有任何关于偏离 200 响应的想法时,我都会问自己,错误是否发生在传输层?著名的。
评论