JSON-RPC v2 API。 HTTP 层。 API 响应的适当 HTTP 层状态代码是什么?

问题描述 投票:0回答:1

我一直在开发一个 Deno 脚本,该脚本使用 Deno.serve() 实现 JSON-RPC v2 API,该脚本最近已稳定下来。虽然我已经设置了基本的 CRUD 操作和错误处理,但我不确定应该在不同场景中使用的 HTTP 状态代码。

上网查了一下,在JSON-RPC 2.0中,协议本身并没有严格定义具体HTTP状态码的使用。相反,它专注于 JSON 有效负载来传达成功和错误状态。然而,当 JSON-RPC 通过 HTTP 传输时,有一些常见的约定:

  • 200 OK:这是 JSON-RPC 响应最常用的状态代码,既适用于成功调用,也适用于导致 JSON-RPC 错误的调用。成功调用和错误之间的区别是在 JSON 负载本身中使用
    result
    error
    字段进行的。
  • 500 内部服务器错误:当出现服务器错误导致 JSON-RPC 请求无法处理时,可以使用此错误。这是服务器端问题的一般指示。
  • 404 Not Found:虽然方法未找到错误并不常见(通常通过负载中的
    200 OK
    -32601 Method not found
    错误进行通信),但如果 JSON-RPC 端点本身不存在,则可以使用此状态。找到了。
  • 400 Bad Request:这可用于格式错误的请求,其中 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);
json typescript deno json-rpc
1个回答
0
投票

最佳实践是对方法本身生成的所有错误使用 200 状态代码,而不是 http 服务器尝试调用该方法生成的错误。因此,例如,尝试创建已存在的资源将是 200 状态,身份验证问题将是 403。

当然,如果您构建代码以便将方法处理程序抽象为与直接处理 http 细节的处理程序不同的单独层,那么这会容易得多。换句话说,方法处理程序应该是独立于传输的,至少在概念上是这样。

以下是我找到的一些参考资料:

这两者的要点是非 200 代码用于指示 HTTP 传输问题,而不是用于与方法调用语义相关的错误。

这是来自 JSON-RPC 网站“历史”部分的文档:

这与我上面概述的原则基本一致。

© www.soinside.com 2019 - 2024. All rights reserved.