Files
platform/web/globals/gost.go

281 lines
6.8 KiB
Go

package globals
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"platform/web/core"
"strings"
)
var ErrGostNotFound = errors.New("gost resource not found")
func IsGostNotFound(err error) bool {
return errors.Is(err, ErrGostNotFound)
}
type GostClient interface {
ListChains() ([]*GostChainConfig, error)
GetChain(name string) (*GostChainConfig, error)
CreateChain(chain *GostChainConfig) error
DeleteChain(name string) error
SaveConfig() error
CreateService(service *GostServiceConfig) error
DeleteService(name string) error
CreateAuther(auther *GostAutherConfig) error
DeleteAuther(name string) error
CreateAdmission(admission *GostAdmissionConfig) error
DeleteAdmission(name string) error
}
type gostClient struct {
baseURL string
pathPrefix string
username string
password string
}
var GostInitializer = func(host string, port int, pathPrefix, username, password string) GostClient {
baseURL := strings.TrimSpace(host)
if !strings.Contains(baseURL, "://") {
baseURL = fmt.Sprintf("http://%s:%d", baseURL, port)
}
return &gostClient{
baseURL: strings.TrimRight(baseURL, "/"),
pathPrefix: normalizeGostPathPrefix(pathPrefix),
username: username,
password: password,
}
}
func NewGost(host string, port int, pathPrefix, username, password string) GostClient {
return GostInitializer(host, port, pathPrefix, username, password)
}
type GostChainConfig struct {
Name string `json:"name"`
Hops []GostHopConfig `json:"hops,omitempty"`
}
type GostHopConfig struct {
Name string `json:"name,omitempty"`
Nodes []GostNodeConfig `json:"nodes,omitempty"`
}
type GostNodeConfig struct {
Name string `json:"name,omitempty"`
Addr string `json:"addr"`
Connector GostConnectorConfig `json:"connector"`
Dialer GostDialerConfig `json:"dialer"`
}
type GostConnectorConfig struct {
Type string `json:"type"`
}
type GostDialerConfig struct {
Type string `json:"type"`
}
type GostServiceConfig struct {
Name string `json:"name"`
Addr string `json:"addr"`
Admission string `json:"admission,omitempty"`
Handler GostHandlerConfig `json:"handler"`
Listener GostListenerConfig `json:"listener"`
Recorders []GostRecorderConfig `json:"recorders,omitempty"`
}
type GostHandlerConfig struct {
Type string `json:"type"`
Chain string `json:"chain,omitempty"`
Auther string `json:"auther,omitempty"`
}
type GostListenerConfig struct {
Type string `json:"type"`
}
type GostRecorderConfig struct {
Name string `json:"name"`
Record string `json:"record"`
}
type GostAutherConfig struct {
Name string `json:"name"`
Auths []GostAuthConfig `json:"auths"`
}
type GostAuthConfig struct {
Username string `json:"username"`
Password string `json:"password"`
}
type GostAdmissionConfig struct {
Name string `json:"name"`
Whitelist bool `json:"whitelist"`
Matchers []string `json:"matchers"`
}
func (c *gostClient) GetChain(name string) (*GostChainConfig, error) {
body, err := c.get("/config/chains/" + url.PathEscape(name))
if err != nil {
return nil, err
}
if len(body) == 0 {
return &GostChainConfig{Name: name}, nil
}
var direct GostChainConfig
if err := json.Unmarshal(body, &direct); err == nil && direct.Name != "" {
return &direct, nil
}
var wrapper struct {
Data *GostChainConfig `json:"data"`
}
if err := json.Unmarshal(body, &wrapper); err == nil && wrapper.Data != nil && wrapper.Data.Name != "" {
return wrapper.Data, nil
}
return &GostChainConfig{Name: name}, nil
}
func (c *gostClient) ListChains() ([]*GostChainConfig, error) {
body, err := c.get("/config/chains")
if err != nil {
return nil, err
}
if len(body) == 0 {
return nil, nil
}
var resp struct {
Data struct {
Count int `json:"count"`
List []*GostChainConfig `json:"list"`
} `json:"data"`
}
if err := json.Unmarshal(body, &resp); err != nil {
return nil, fmt.Errorf("parse gost chain list failed %s: %w", string(body), err)
}
return resp.Data.List, nil
}
func (c *gostClient) CreateChain(chain *GostChainConfig) error {
return c.create("/config/chains", chain)
}
func (c *gostClient) DeleteChain(name string) error {
return c.delete("/config/chains/" + url.PathEscape(name))
}
func (c *gostClient) SaveConfig() error {
return c.create("/config", nil)
}
func (c *gostClient) CreateService(service *GostServiceConfig) error {
return c.create("/config/services", service)
}
func (c *gostClient) DeleteService(name string) error {
return c.delete("/config/services/" + url.PathEscape(name))
}
func (c *gostClient) CreateAuther(auther *GostAutherConfig) error {
return c.create("/config/authers", auther)
}
func (c *gostClient) DeleteAuther(name string) error {
return c.delete("/config/authers/" + url.PathEscape(name))
}
func (c *gostClient) CreateAdmission(admission *GostAdmissionConfig) error {
return c.create("/config/admissions", admission)
}
func (c *gostClient) DeleteAdmission(name string) error {
return c.delete("/config/admissions/" + url.PathEscape(name))
}
func (c *gostClient) create(path string, payload any) error {
_, err := c.request(http.MethodPost, path, payload)
return err
}
func (c *gostClient) get(path string) ([]byte, error) {
body, err := c.request(http.MethodGet, path, nil)
if err != nil {
return nil, err
}
return body, nil
}
func (c *gostClient) delete(path string) error {
_, err := c.request(http.MethodDelete, path, nil)
return err
}
func (c *gostClient) request(method string, path string, payload any) ([]byte, error) {
var bodyReader io.Reader
if payload != nil {
data, err := json.Marshal(payload)
if err != nil {
return nil, err
}
bodyReader = bytes.NewReader(data)
}
req, err := http.NewRequest(method, c.endpoint(path), bodyReader)
if err != nil {
return nil, err
}
req.SetBasicAuth(c.username, c.password)
if payload != nil {
req.Header.Set("Content-Type", "application/json")
}
resp, err := core.Fetch(req)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode == http.StatusBadRequest {
return nil, fmt.Errorf("%w: %s", ErrGostNotFound, string(body))
}
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
return nil, fmt.Errorf("gost api %s %s failed: %d %s", method, path, resp.StatusCode, string(body))
}
return body, nil
}
func (c *gostClient) endpoint(path string) string {
return c.baseURL + c.pathPrefix + path
}
func normalizeGostPathPrefix(prefix string) string {
prefix = strings.TrimSpace(prefix)
if prefix == "" {
return ""
}
if !strings.HasPrefix(prefix, "/") {
prefix = "/" + prefix
}
return strings.TrimRight(prefix, "/")
}