Architecture & Routing

Glue2 is the central reverse proxy that sits in front of every HolaCloud service. All incoming traffic — whether from browser users, API clients, or internal services — passes through Glue2 before reaching its destination.

Glue2 Architecture

Reverse Proxy

Glue2 operates as a Layer-7 reverse proxy. It terminates TLS, inspects each request, authenticates the caller, and then forwards the request to the appropriate backend service. No external traffic reaches HolaCloud services directly; Glue2 is the single entry point.

1Client ──▶ Glue2 (auth check) ──▶ Backend Service
23              ├──▶ InceptionDB
4              ├──▶ Lambda
5              ├──▶ Files
6              ├──▶ Config
7              ├──▶ KVNode
8              ├──▶ Scheduler
9              └──▶ Logs

Virtual Host Routing

Glue2 routes requests based on the Host header. Each HolaCloud project is assigned a unique subdomain (<project>.hola.cloud), and Glue2 maps that subdomain to the project's backend service.

Built-in platform services use reserved subdomains:

Host Service
inceptiondb.hola.cloud InceptionDB
api.hola.cloud Public API (Lambda, Files, Config, etc.)
console.hola.cloud HolaCloud Console
auth.hola.cloud Authentication / Session service
logs.hola.cloud InstantLogs

When a request arrives for my-project.hola.cloud, Glue2 looks up the project with that slug, resolves its primary backend service, and forwards the request.

The Proxy Flow

  1. Client sends an HTTP request to <project>.hola.cloud.
  2. Glue2 terminates the TLS connection and reads the Host header.
  3. Glue2 checks authentication (session cookie, API key, or OAuth token). If the endpoint requires auth and none is provided, the request is rejected with 401 Unauthorized.
  4. Glue2 resolves the virtual host to a project ID and backend service target.
  5. Glue2 injects authentication and project headers into the request.
  6. The request is forwarded to the backend service.
  7. The backend response is streamed back to the client, and Glue2 logs the transaction.

Injected Headers

When forwarding to a backend, Glue2 injects:

Header Description
X-Glue-Authentication Plain JSON authentication context
X-Holacloud-Project-Id The UUID of the project derived from the virtual host
X-Holacloud-Tenant-Project-Id Tenant-scoped project ID for multi-tenant isolation
X-Forwarded-For Original client IP address
X-Forwarded-Proto Original protocol (http or https)

Backend services use these headers to identify the caller and enforce authorization. X-Glue-Authentication is plain JSON.

V0 Admin Endpoints

Glue2 exposes a set of admin endpoints under /v0/:

List Virtual Hosts

curl "https://api.hola.cloud/v0/virtualhosts" \
GET /v0/virtualhosts HTTP/1.1
Host: api.hola.cloud
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)
	}

	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',
]);

$response = curl_exec($ch);
if ($response === false) {
    throw new Exception(curl_error($ch));
}
curl_close($ch);

echo $response;
import requests

response = requests.request(
    "GET",
    "https://api.hola.cloud/v0/virtualhosts"
)

print(response.text)
const response = await fetch("https://api.hola.cloud/v0/virtualhosts", {
  method: "GET"
});

console.log(await response.text());
const response = await fetch("https://api.hola.cloud/v0/virtualhosts", {
  method: "GET"
});

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())
            .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"]

Traffic Stats

curl "https://api.hola.cloud/v0/stats" \
GET /v0/stats HTTP/1.1
Host: api.hola.cloud
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)
	}

	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',
]);

$response = curl_exec($ch);
if ($response === false) {
    throw new Exception(curl_error($ch));
}
curl_close($ch);

echo $response;
import requests

response = requests.request(
    "GET",
    "https://api.hola.cloud/v0/stats"
)

print(response.text)
const response = await fetch("https://api.hola.cloud/v0/stats", {
  method: "GET"
});

console.log(await response.text());
const response = await fetch("https://api.hola.cloud/v0/stats", {
  method: "GET"
});

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())
            .build();

        var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.body());
    }
}

Backend Health Status

curl "https://api.hola.cloud/v0/status" \
GET /v0/status HTTP/1.1
Host: api.hola.cloud
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)
	}

	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',
]);

$response = curl_exec($ch);
if ($response === false) {
    throw new Exception(curl_error($ch));
}
curl_close($ch);

echo $response;
import requests

response = requests.request(
    "GET",
    "https://api.hola.cloud/v0/status"
)

print(response.text)
const response = await fetch("https://api.hola.cloud/v0/status", {
  method: "GET"
});

console.log(await response.text());
const response = await fetch("https://api.hola.cloud/v0/status", {
  method: "GET"
});

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())
            .build();

        var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.body());
    }
}
1[
2  {
3    "id": "project-123",
4    "name": "My Project",
5    "host": "my-project.hola.cloud",
6    "status": 200,
7    "statusText": "200 OK"
8  }
9]

Next Steps

Comments

Leave a comment