Ruby 用 HTTP + JSON 直接访问 GraphQL API

Rei · 2025年02月26日 · 最后由 xw816 回复于 2025年02月26日 · 98 次阅读

原文链接:https://geeknote.net/Rei/posts/3191


有一天我需要访问 Fly 的 API 以支持自动签发 SSL 证书,Fly API 基于 GraphQL。我一向不太喜欢 GraphQL,精神洁癖让我不想增加一个 GraphQL Client 依赖。我想到 GraphQL 底层基于 HTTP 和 JSON,为何不直接访问接口?以下就是用 Ruby 实现过程。

Fly 前开发者认为“GraphQL 拖慢了所有人、所有事物的速度。” —— https://fly.io/blog/the-exit-interview-jp/

首先 Graph QL API 需要提供一个 endpoint,对于 Fly API 来说是 https://api.fly.io/graphql

ENDPOINT = "https://api.fly.io/graphql"

可以直接对 endpoint 发起 HTTP POST 请求,但会因为缺失验证信息返回错误:

uri = URI(@endpoint)
data = {}.to_json
headers = {
  "Content-Type" => "application/json"
}
response = Net::HTTP.post(uri, data, headers)
# => 验证错误

要增加验证信息需要传递一个 auth token,token 可以在 fly 的控制台获取:

uri = URI(@endpoint)
data = {}.to_json
headers = {
  "Content-Type" => "application/json",
  "Authorization" => "Bearer #{ENV[FLY_API_TOKEN]}"
}
response = Net::HTTP.post(uri, data, headers)
# => Query 错误

接下来要传递查询内容。GraphQL 查询的 JSON 格式为:

{
  query: "QUERY STRING...",
  variables: {
    var1: "value1"
  }
}

可以看到,query 是支持变量的,但是需要通过 variables 字段传递。例如 Fly 的查询证书接口的查询字符串为:

query($appName: String!) {
  app(name: $appName) {
    certificates {
      nodes {
        createdAt
        hostname
        clientStatus
      }
    }
  }
}

其中 $appName 是变量,那么查询的代码可以写作:

uri = URI(@endpoint)
data = {
  query: <<~EOF
    query($appName: String!) {
      app(name: $appName) {
        certificates {
          nodes {
            createdAt
            hostname
            clientStatus
          }
        }
      }
    }
  EOF
  ,
  variables: {
    appName: "my-app-name"
  }
}.to_json
headers = {
  "Content-Type" => "application/json",
  "Authorization" => "Bearer #{ENV[FLY_API_TOKEN]}"
}
response = Net::HTTP.post(uri, data, headers)
#=> json data...

返回的裸 JSON 内容是:

{
  "data": {
    "app": {
      "certificates": {
        "nodes": [
          {
            "createdAt": "2020-03-04T14:17:14Z",
            "hostname": "example.com",
            "clientStatus": "Ready"
          },
          {
            "createdAt": "2020-03-05T15:28:41Z",
            "hostname": "exemplum.com",
            "clientStatus": "Ready"
          }
        ]
      }
    }
  }
}

要注意的是,返回的 json data 放在 data 字段内,需要增加一层解析:

JSON.parse(response.body)["data"]

现在把上面的代码整理一下,放进一个类里:

# client = Graphql::Client.new("http://localhost:3000/graphql", { "Authorization" => "Bearer token..." })
# response = client.execute("query { ... }", { name: "value" })
class Graphql::Client
  def initialize(endpoint, headers = {})
    @endpoint = endpoint
    @headers = headers
  end

  def execute(query, variables = {})
    data = {
      query: query,
      variables: variables
    }.to_json

    headers = {
      "Content-Type" => "application/json"
    }.merge(@headers)

    uri = URI(@endpoint)

    response = Net::HTTP.post(uri, data, headers)

    JSON.parse(response.body)["data"]
  end
end

为了方便调用,我还封装了一个 Fly::Client 类:

class Fly::Client
  GraphqlClient = Graphql::Client.new(
    "https://api.fly.io/graphql",
    { "Authorization" => "Bearer #{ENV["FLY_API_TOKEN"]}" }
  )

  def self.get_certs
    query = <<~GRAPHQL
      query($appName: String!) {
        app(name: $appName) {
          certificates {
            nodes {
              createdAt
              hostname
              clientStatus
            }
          }
        }
      }
    GRAPHQL

    variables = {
      appName: ENV["FLY_APP_NAME"]
    }

    GraphqlClient.execute(query, variables)
  end

  # ...
end

通过这个类可以方便的调用 Fly API:

Fly::Client.get_certs
# => json data ...

以上就是用 HTTP + JSON 直接访问 GraphQL API 的方法。

謝謝分享

需要 登录 后方可回复, 如果你还没有账号请 注册新账号