架构与路由
Glue2 是位于所有 HolaCloud 服务前面的中央反向代理。所有传入流量——无论是来自浏览器用户、API 客户端还是内部服务——在到达目的地之前都经过 Glue2。

反向代理
Glue2 作为第 7 层反向代理运行。它终止 TLS 连接,检查每个请求,验证调用者身份,然后将请求转发到相应的后端服务。没有外部流量直接到达 HolaCloud 服务;Glue2 是唯一的入口点。
1客户端 ──▶ Glue2(身份验证检查)──▶ 后端服务
2 │
3 ├──▶ InceptionDB
4 ├──▶ Lambda
5 ├──▶ Files
6 ├──▶ Config
7 ├──▶ KVNode
8 ├──▶ Scheduler
9 └──▶ Logs
基于虚拟主机的路由
Glue2 根据 Host 标头路由请求。每个 HolaCloud 项目被分配一个唯一的子域名(<项目>.hola.cloud),Glue2 将该子域名映射到项目的后端服务。
内置平台服务使用保留子域名:
| 主机 | 服务 |
|---|---|
inceptiondb.hola.cloud |
InceptionDB |
api.hola.cloud |
公共 API(Lambda、Files、Config 等) |
console.hola.cloud |
HolaCloud 控制台 |
auth.hola.cloud |
身份验证 / 会话服务 |
logs.hola.cloud |
InstantLogs |
当请求到达 my-project.hola.cloud 时,Glue2 查找具有该别名的项目,解析其主后端服务,并转发请求。
代理流程
- 客户端向
<项目>.hola.cloud发送 HTTP 请求。 - Glue2 终止 TLS 连接并读取
Host标头。 - Glue2 检查身份验证(会话 cookie、API 密钥或 OAuth 令牌)。如果端点需要身份验证而未提供,则请求被拒绝并返回
401 Unauthorized。 - Glue2 将虚拟主机解析为项目 ID 和后端服务目标。
- Glue2 将身份验证和项目标头注入请求。
- 请求被转发到后端服务。
- 后端响应被流式传回客户端,Glue2 记录事务。
注入的标头
转发到后端时,Glue2 注入:
| 标头 | 描述 |
|---|---|
X-Glue-Authentication |
纯 JSON 身份验证上下文 |
X-Holacloud-Project-Id |
从虚拟主机派生的项目 UUID |
X-Holacloud-Tenant-Project-Id |
用于多租户隔离的租户范围项目 ID |
X-Forwarded-For |
原始客户端 IP 地址 |
X-Forwarded-Proto |
原始协议(http 或 https) |
后端服务使用这些标头识别调用者并执行授权。X-Glue-Authentication 是纯 JSON。
V0 管理端点
Glue2 在 /v0/ 下公开一组管理端点:
列出虚拟主机
curl "https://api.hola.cloud/v0/virtualhosts" \
-H "Api-Key: 您的_API_KEY" \
-H "Api-Secret: 您的_API_SECRET"GET /v0/virtualhosts HTTP/1.1
Host: api.hola.cloud
Api-Key: 您的_API_KEY
Api-Secret: 您的_API_SECRET
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest("GET", "https://api.hola.cloud/v0/virtualhosts", nil)
if err != nil {
panic(err)
}
req.Header.Set("Api-Key", "您的_API_KEY")
req.Header.Set("Api-Secret", "您的_API_SECRET")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(responseBody))
}
<?php
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.hola.cloud/v0/virtualhosts',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Api-Key: 您的_API_KEY',
'Api-Secret: 您的_API_SECRET',
],
]);
$response = curl_exec($ch);
if ($response === false) {
throw new Exception(curl_error($ch));
}
curl_close($ch);
echo $response;
import requests
headers = {
"Api-Key": "您的_API_KEY",
"Api-Secret": "您的_API_SECRET",
}
response = requests.request(
"GET",
"https://api.hola.cloud/v0/virtualhosts",
headers=headers
)
print(response.text)
const response = await fetch("https://api.hola.cloud/v0/virtualhosts", {
method: "GET",
headers: {
"Api-Key": "您的_API_KEY",
"Api-Secret": "您的_API_SECRET"
}
});
console.log(await response.text());
const response = await fetch("https://api.hola.cloud/v0/virtualhosts", {
method: "GET",
headers: {
"Api-Key": "您的_API_KEY",
"Api-Secret": "您的_API_SECRET"
}
});
const text = await response.text();
console.log(text);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.hola.cloud/v0/virtualhosts"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.header("Api-Key", "您的_API_KEY")
.header("Api-Secret", "您的_API_SECRET")
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}1["my-project.hola.cloud", "api.my-project.hola.cloud"]
流量统计
curl "https://api.hola.cloud/v0/stats" \
-H "Api-Key: 您的_API_KEY" \
-H "Api-Secret: 您的_API_SECRET"GET /v0/stats HTTP/1.1
Host: api.hola.cloud
Api-Key: 您的_API_KEY
Api-Secret: 您的_API_SECRET
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest("GET", "https://api.hola.cloud/v0/stats", nil)
if err != nil {
panic(err)
}
req.Header.Set("Api-Key", "您的_API_KEY")
req.Header.Set("Api-Secret", "您的_API_SECRET")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(responseBody))
}
<?php
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.hola.cloud/v0/stats',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Api-Key: 您的_API_KEY',
'Api-Secret: 您的_API_SECRET',
],
]);
$response = curl_exec($ch);
if ($response === false) {
throw new Exception(curl_error($ch));
}
curl_close($ch);
echo $response;
import requests
headers = {
"Api-Key": "您的_API_KEY",
"Api-Secret": "您的_API_SECRET",
}
response = requests.request(
"GET",
"https://api.hola.cloud/v0/stats",
headers=headers
)
print(response.text)
const response = await fetch("https://api.hola.cloud/v0/stats", {
method: "GET",
headers: {
"Api-Key": "您的_API_KEY",
"Api-Secret": "您的_API_SECRET"
}
});
console.log(await response.text());
const response = await fetch("https://api.hola.cloud/v0/stats", {
method: "GET",
headers: {
"Api-Key": "您的_API_KEY",
"Api-Secret": "您的_API_SECRET"
}
});
const text = await response.text();
console.log(text);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.hola.cloud/v0/stats"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.header("Api-Key", "您的_API_KEY")
.header("Api-Secret", "您的_API_SECRET")
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}后端健康状态
curl "https://api.hola.cloud/v0/status" \
-H "Api-Key: 您的_API_KEY" \
-H "Api-Secret: 您的_API_SECRET"GET /v0/status HTTP/1.1
Host: api.hola.cloud
Api-Key: 您的_API_KEY
Api-Secret: 您的_API_SECRET
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest("GET", "https://api.hola.cloud/v0/status", nil)
if err != nil {
panic(err)
}
req.Header.Set("Api-Key", "您的_API_KEY")
req.Header.Set("Api-Secret", "您的_API_SECRET")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(responseBody))
}
<?php
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.hola.cloud/v0/status',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Api-Key: 您的_API_KEY',
'Api-Secret: 您的_API_SECRET',
],
]);
$response = curl_exec($ch);
if ($response === false) {
throw new Exception(curl_error($ch));
}
curl_close($ch);
echo $response;
import requests
headers = {
"Api-Key": "您的_API_KEY",
"Api-Secret": "您的_API_SECRET",
}
response = requests.request(
"GET",
"https://api.hola.cloud/v0/status",
headers=headers
)
print(response.text)
const response = await fetch("https://api.hola.cloud/v0/status", {
method: "GET",
headers: {
"Api-Key": "您的_API_KEY",
"Api-Secret": "您的_API_SECRET"
}
});
console.log(await response.text());
const response = await fetch("https://api.hola.cloud/v0/status", {
method: "GET",
headers: {
"Api-Key": "您的_API_KEY",
"Api-Secret": "您的_API_SECRET"
}
});
const text = await response.text();
console.log(text);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.hola.cloud/v0/status"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.header("Api-Key", "您的_API_KEY")
.header("Api-Secret", "您的_API_SECRET")
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}1{
2 "backends": [
3 {
4 "name": "inceptiondb",
5 "status": "healthy",
6 "latency_ms": 12
7 }
8 ]
9}
评论