Skip to content
Sirius-Dev edited this page Jun 17, 2025 · 5 revisions

Proxy Service

This document describes the Proxy Service, a GoLang-based service designed to manage incoming HTTP requests, route them based on flexible configurations, and handle responses efficiently.

Table of Contents


What is Proxy Service

The Proxy Service is a GoLang application designed for managing incoming HTTP requests.

It uses a Proxy Config to define API requests and specifies where and under what conditions to direct them.

The Proxy Service then performs the following steps:

  1. Receives the request.
  2. Optionally verifies authorization cookies.
  3. Adds request_context (with IP, user-agent, etc.) and other configurable parameters to the request body.
  4. Applies request balancing rules to distribute requests evenly among multiple servers.
  5. Calls the necessary service, such as a NodeJS application or a PostgreSQL function.
  6. Processes the response. If the response contains Proxy Tasks (in the proxy:[] section), it executes these tasks, either synchronously (waiting for a response) or in the background.

Key Features

  1. Flexible interaction between various services.
  2. Load balancing across servers.
  3. Multi-threaded transaction execution.
  4. Background mode support ensures fast client response.
  5. Support for EventStream for server-client communication.
  6. Collector for gathering data from multiple transactions with subsequent single transaction updates.
  7. Task scheduler with multiple operating modes.
  8. Enhanced security through supporting a single entry point to services. For example, to work with a database, it's sufficient to create a user with permissions only to run one function, without access to tables.
  9. Collects metrics for further visualization via Grafana and Prometheus.

PostgreSQL Use Case

With the Proxy Service, you can develop powerful high-load systems with business logic residing in the database. This approach ensures high performance: data is processed where it resides.

The use of Collectors allows processing up to 1 million transactions with data updates per second.

To implement this approach, the following steps are necessary:

  1. Develop a data architecture that accounts for asynchronous updates – so that data is updated not directly via INSERT/UPDATE, but through Collectors.
  2. Split long-running transactions into multiple steps using Proxy Tasks.
  3. Implement external calls (e.g., to ChatGPT) also via Proxy Tasks.

Installation

Configuration

Examples of configurations can be found in the "config" folder.

The main sections of the config are:

  1. database
  2. server
  3. worker
  4. collectors
  5. metrics

Supported Services

Service Name Keys Description
db apicall string - name of the function in the database that the Proxy Service calls Execute a request in the database
http method string - request method
url string - request URL
headers object - Headers
body string|object - request body
timeout string - timeout (e.g., "30s", but not more than 600s)
Execute an HTTP request
eventstream es_id string - connection identifier
payload string - request body
es_list array - array of connection identifiers
Send a message to the client via EventStream
file Write file:
savePath string - path to save the file with body content
body string - file content to save at savePath
filename string - filename for Content-Disposition header
Read file:
readPath string - path to read the file
headers object - object with headers, including Content-Type
Read/write a file
metrics measurer string - metric collector type
metric string - metric name
labels object - set of labels for the metric
value float - metric value
Send value to metrics
collector collector string - collector name
data object - data to send to the Uniproxy collector
Send data to the Uniproxy collector
command command []string - array of command arguments to execute on the server Executes a CLI command on the server, as the system user running the proxy

Request from Proxy Service Format

{
  "api_call": "<http API path>",
  "request_context": {
    "ip": "<ip_address>",
    "ua": "<user_agent>"
  },
  "<any other keys>": "..."
}

Response for Proxy Service Format

{
  "error": {
    "code": "<http code>",
    "message": "<any text message>"
  },
  "http_response": {
    "http_code": "<http code>",
    "header": {
      "<object with headers>": "..."
    },
    "body": {
      "<object with API response>": "..."
    }
  },
  "proxy_tasks": [
    "<array with Proxy Tasks>"
  ],
  "proxy_options": {
    "rollback": "1 = rollback a database transaction | 0 (default)"
  }
}

Proxy Tasks[]

An array of tasks for the Proxy Service. Tasks can be executed sequentially, waiting for a response, or in parallel, in background mode.

Each task is described by the following parameters:

{
  "service": "<service type as described above>",
  "data": {
    "api_call": "<API url to call>",
    "<any parameters for the API request>": "..."
  },
  "options": {
    "pauseBefore": "<period to pause before the task, e.g. “10ms”. 0 = no pause>",
    "pauseAfter": "<period to pause after the task, e.g. “10ms”. 0 = no pause>",
    "timeout": "<timeout to wait for response from service, e.g. “5s”>",
    "responseTo": "<key to save the service response>",
    "collectedTo": "<key in request to send collected responses>",
    "toClient": "<true = direct response to client without any transformation>",
    "taskGroup": "\"self\" (default) | \"nowait\" | \"<group key>\""
  }
}

taskGroup Options:

  • self: Wait for response and return to client (if it's the last "self" task).
  • nowait: Do not wait for a response, run in a separate goroutine.
  • <group key>: Run all tasks with the same <group key> in parallel goroutines and wait for all responses.

Examples and useful cases

Worker Types

  • task_ticker: Periodic task execution at a fixed interval, regardless of when the previous task finished (task initiator - worker).
  • task_wait: Periodic task execution at a fixed interval, taking into account the completion of the previous task (task initiator - worker).
  • task_idle_wait: If the proxyTasks[] array is empty, it waits for a specified period. If not empty, it executes the task immediately again (task initiator - worker).
  • notify: Listener for PostgreSQL database notifications (pg_notify calls) (task initiator - database).

Core Components

  • Main service: Contains a set of processes, within which taskers are launched, and within those, workers are launched.
  • Worker: Each worker runs a channel into which the tasker throws tasks for execution.
  • Tasker: A thread within a process that puts periodic tasks (requests to the database) into a channel (which listeners listen to) for execution.
  • Listener: Listens to the channel (where the tasker puts tasks) and executes the received task.

Middlewares

Upload Middleware Example

{
  "name": "upload",
  "folder": "/files",
  "mimext": {
    "image/*": "jpg",
    "text/csv": "csv",
    "image/jpg": "jpg",
    "image/png": "png",
    "image/jpeg": "jpg",
    "text/plain": "txt",
    "image/jpe?g": "jpg",
    "application/pdf": "pdf",
    "application/vnd": {
      "openxmlformats-officedocument": {
        "wordprocessingml": {
          "document": "doc"
        }
      }
    },
    "application/zip": "zip",
    "application/gzip": "gz",
    "application/x-7z-compressed": "7z"
  },
  "source": "file",
  "fileMode": 777,
  "destination": "folder",
  "nameTemplate": "user-file-*.$ext",
  "requestField": "upload"
}

File Manager

The Proxy Service interacts with two main folders:

  • /files: Folder for internal files, used for data upload/download.
  • /public: Folder for public static files, images, scripts, JSON config files from the DB, and others.

Reading Files

To send a required file to the client, simply complete the transaction with the following Proxy Tasks[]:

[
  {
    "data": {
      "readPath": "/files/a.txt"
    },
    "options": {
      "taskGroup": "self"
    },
    "service": "file"
  }
]

Writing Files

To write data to a required file, simply complete the transaction with the following Proxy Tasks[]:

{
  "data": {
    "savePath": "/files/a.txt",
    "body": "<textual content>",
    "body_base64": "<base64 content, only if body is not specified>"
  },
  "options": {
    "taskGroup": "self"
  },
  "service": "file"
}