原文链接: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 的方法。