working commit

This commit is contained in:
2026-05-29 19:07:59 +02:00
parent 4e2c548e97
commit c1f85e87c9
23 changed files with 543 additions and 342 deletions
+10
View File
@@ -0,0 +1,10 @@
package config
const (
confdir = "/home/ziggi/Projects/mproxy/etc/mproxy"
rundir = "/home/ziggi/Projects/mproxy/tmp/run"
logdir = "/home/ziggi/Projects/mproxy/tmp/log"
datadir = "/home/ziggi/Projects/mproxy/tmp/data"
version = "0.0.1"
srvname = "mproxyd"
)
+2 -2
View File
@@ -4,10 +4,10 @@
package handler
import (
"net/http"
"sync"
"io"
"net"
"net/http"
"sync"
"mproxy/app/router"
)
+2 -4
View File
@@ -4,9 +4,9 @@
package handler
import (
"net/http"
"io"
"context"
"io"
"net/http"
"time"
"mproxy/app/router"
@@ -16,14 +16,12 @@ func (hand *Handler) PlainCall(rctx *router.Context) {
hostaddr := rctx.GetHost()
hand.logg.Debugf("Hostaddr: [%s]", hostaddr)
ctx := rctx.GetContext()
ctx, _ = context.WithTimeout(ctx, 5*time.Second)
reqMethod := rctx.Request.Method
reqUrl := rctx.URL().String()
reqBody := rctx.Request.Body
req, err := http.NewRequestWithContext(ctx, reqMethod, reqUrl, reqBody)
if err != nil {
hand.logg.Errorf("Create request error: %v", err)
+3 -3
View File
@@ -8,8 +8,8 @@ import (
"mproxy/app/servoper"
)
func (hand *Handler) SendHello(rctx *router.Context) {
params := &servoper.SendHelloParams{}
res, _ := hand.seop.SendHello(params)
func (hand *Handler) GetHello(rctx *router.Context) {
params := &servoper.GetHelloParams{}
res, _ := hand.seop.GetHello(params)
hand.SendResult(rctx, res)
}
+1 -1
View File
@@ -63,7 +63,7 @@ func (svc *Service) Build() error {
svc.rout.Use(router.NewCorsMiddleware())
svc.rout.Use(svc.hand.AuthMiddleware)
svc.rout.Get(`/v3/api/service/hello`, svc.hand.SendHello)
svc.rout.Get(`/v3/api/service/hello`, svc.hand.GetHello)
svc.rout.Connect(``, svc.hand.ConnectTo)
+4 -4
View File
@@ -3,16 +3,16 @@
*/
package servoper
type SendHelloParams struct{}
type GetHelloParams struct{}
type SendHelloResult struct {
type GetHelloResult struct {
Message string `json:"message"`
Alive bool `json:"alive"`
}
func (oper *Operator) SendHello(param *SendHelloParams) (*SendHelloResult, error) {
func (oper *Operator) GetHello(param *GetHelloParams) (*GetHelloResult, error) {
var err error
res := &SendHelloResult{
res := &GetHelloResult{
Alive: true,
Message: "hello",
}
+64
View File
@@ -0,0 +1,64 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*/
package servcmd
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
const (
uuidRegex = `^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`
defaultHostname = "localhost:1025"
)
func NewServiceUtil() *ServiceUtil {
return &ServiceUtil{}
}
type ServiceUtil struct {
getHelloParams GetHelloParams
commonServiceParams CommonServiceParams
}
type CommonServiceParams struct {
Username string
Password string
Hostname string
Timeout uint64
SkipTLSVerify bool
}
func (util *ServiceUtil) Makeservcmds() *cobra.Command {
var subCmd = &cobra.Command{
Use: "service",
Short: "Service operations",
}
const defaultTimeout uint64 = 10
subCmd.PersistentFlags().StringVarP(&util.commonServiceParams.Username, "user", "U", util.commonServiceParams.Username, "Username")
subCmd.PersistentFlags().StringVarP(&util.commonServiceParams.Password, "pass", "P", util.commonServiceParams.Password, "Password")
subCmd.PersistentFlags().StringVarP(&util.commonServiceParams.Hostname, "host", "X", defaultHostname, "Hostname")
subCmd.PersistentFlags().Uint64VarP(&util.commonServiceParams.Timeout, "timeout", "T", defaultTimeout, "Operation timeout")
subCmd.PersistentFlags().BoolVarP(&util.commonServiceParams.SkipTLSVerify, "skipVerify", "S", true, "Skip server certificate verify")
subCmd.MarkFlagsRequiredTogether("user", "pass")
vi := viper.New()
vi.SetEnvPrefix("mproxy")
vi.BindEnv("user")
vi.BindEnv("pass")
util.commonServiceParams.Username = vi.GetString("user")
util.commonServiceParams.Password = vi.GetString("pass")
// GetService
var getservcmd = &cobra.Command{
Use: "get [user:pass@]hostname[:port]",
Short: "Get service hello",
Args: cobra.ExactArgs(1),
Run: util.GetHello,
}
subCmd.AddCommand(getservcmd)
return subCmd
}
+46
View File
@@ -0,0 +1,46 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*/
package servcmd
import (
"context"
"time"
"github.com/spf13/cobra"
"mproxy/pkg/servcli"
)
// GetHello
type GetHelloParams struct {
Address string
}
func (util *ServiceUtil) GetHello(cmd *cobra.Command, args []string) {
util.getHelloParams.Address = args[0]
res, err := util.getHello(&util.commonServiceParams, &util.getHelloParams)
printResponse(res, err)
}
type GetHelloResult struct{}
func (util *ServiceUtil) getHello(common *CommonServiceParams, params *GetHelloParams) (*GetHelloResult, error) {
var err error
res := &GetHelloResult{}
timeout := time.Duration(common.Timeout) * time.Second
ctx, _ := context.WithTimeout(context.Background(), timeout)
ref, err := servcli.ParsePath(params.Address)
if err != nil {
return res, err
}
ref.SetUserinfo(common.Username, common.Password)
mw := servcli.NewBasicAuthMiddleware(ref.Userinfo())
cli := servcli.NewClient(nil, mw)
err = cli.GetHello(ctx, ref.Raw())
if err != nil {
return res, err
}
return res, err
}
+27
View File
@@ -0,0 +1,27 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*/
package servcmd
import (
"fmt"
"sigs.k8s.io/yaml"
)
func printResponse(res any, err error) {
type Response struct {
Error bool `json:"error" yaml:"error"`
Message string `json:"message,omitempty" yaml:"message,omitempty"`
Result any `json:"result,omitempty" yaml:"result,omitempty"`
}
resp := Response{}
if err != nil {
resp.Error = true
resp.Message = err.Error()
} else {
resp.Result = res
}
respBytes, _ := yaml.Marshal(resp)
fmt.Printf("---\n%s\n", string(respBytes))
}
+6 -1
View File
@@ -8,6 +8,8 @@ import (
"os"
"path/filepath"
"mproxy/cmd/mproxyctl/servcmd"
"github.com/spf13/cobra"
)
@@ -28,12 +30,15 @@ func (util *Util) Build() error {
execName := filepath.Base(os.Args[0])
rootCmd := &cobra.Command{
Use: execName,
Short: "\nOperation with artefacts: files, images, service accounts and grants",
Short: "\nGet hello from service",
SilenceUsage: true,
}
rootCmd.CompletionOptions.DisableDefaultCmd = true
util.rootCmd = rootCmd
ServiceUtil := servcmd.NewServiceUtil()
rootCmd.AddCommand(ServiceUtil.MakeServiceCmds())
return err
}
+14 -1
View File
@@ -5,17 +5,30 @@ go 1.26.2
require (
github.com/google/uuid v1.6.0
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.11.1
go.yaml.in/yaml/v4 v4.0.0-rc.4
sigs.k8s.io/yaml v1.6.0
)
require (
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.28.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+40 -2
View File
@@ -2,25 +2,63 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U=
go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
+1 -14
View File
@@ -2,20 +2,7 @@
AUTOMAKE_OPTIONS = foreign no-dependencies no-installinfo
dist_man1_MANS = \
mproxyctl.1 \
mproxyctl-accounts.1 \
mproxyctl-accounts-create.1 \
mproxyctl-accounts-delete.1 \
mproxyctl-accounts-get.1 \
mproxyctl-accounts-list.1 \
mproxyctl-accounts-update.1 \
mproxyctl-grants.1 \
mproxyctl-grants-create.1 \
mproxyctl-grants-delete.1 \
mproxyctl-grants-get.1 \
mproxyctl-grants-list.1 \
mproxyctl-grants-update.1
mproxyctl.1
dist_man8_MANS = \
mproxyd.8
+1 -13
View File
@@ -267,19 +267,7 @@ top_builddir = @top_builddir@
top_srcdir = @top_srcdir@
AUTOMAKE_OPTIONS = foreign no-dependencies no-installinfo
dist_man1_MANS = \
mproxyctl.1 \
mproxyctl-accounts.1 \
mproxyctl-accounts-create.1 \
mproxyctl-accounts-delete.1 \
mproxyctl-accounts-get.1 \
mproxyctl-accounts-list.1 \
mproxyctl-accounts-update.1 \
mproxyctl-grants.1 \
mproxyctl-grants-create.1 \
mproxyctl-grants-delete.1 \
mproxyctl-grants-get.1 \
mproxyctl-grants-list.1 \
mproxyctl-grants-update.1
mproxyctl.1
dist_man8_MANS = \
mproxyd.8
+58
View File
@@ -0,0 +1,58 @@
package servcli
import (
"encoding/base64"
"net/http"
)
// BasicAuthMiddleware
func NewBasicAuthMiddleware(user, pass string) MiddlewareFunc {
return func(next http.RoundTripper) http.RoundTripper {
return newBasicAuthMW(next, user, pass)
}
}
type BasicAuthMW struct {
user, pass string
next http.RoundTripper
}
func newBasicAuthMW(next http.RoundTripper, user, pass string) *BasicAuthMW {
return &BasicAuthMW{
user: user,
pass: pass,
next: next,
}
}
func (tran BasicAuthMW) RoundTrip(req *http.Request) (*http.Response, error) {
if tran.user != "" && tran.pass != "" {
pair := base64.StdEncoding.EncodeToString([]byte(tran.user + ":" + tran.pass))
req.Header.Set("Authorization", "Basic "+pair)
}
return tran.next.RoundTrip(req)
}
// BearerAuthMiddleware
func NewBearerAuthMiddleware(token string) MiddlewareFunc {
return func(next http.RoundTripper) http.RoundTripper {
return newBearerAuthMW(next, token)
}
}
type BearerAuthMW struct {
token string
next http.RoundTripper
}
func newBearerAuthMW(next http.RoundTripper, token string) *BearerAuthMW {
return &BearerAuthMW{
token: token,
next: next,
}
}
func (tran BearerAuthMW) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("Authorization", "Bearer "+tran.token)
return tran.next.RoundTrip(req)
}
+3 -98
View File
@@ -1,11 +1,11 @@
package servcli
import (
"crypto/tls"
"encoding/base64"
"net/http"
)
type MiddlewareFunc func(next http.RoundTripper) http.RoundTripper
type Client struct {
httpClient *http.Client
userAgent string
@@ -21,10 +21,9 @@ func NewClient(transport http.RoundTripper, mwFuncs ...MiddlewareFunc) *Client {
httpClient := &http.Client{
Transport: transport,
}
return &Client{
httpClient: httpClient,
userAgent: "ociClient/1.0",
userAgent: "proxyClient/1.0",
}
}
@@ -32,100 +31,6 @@ func (cli *Client) SetTransport(transport http.RoundTripper) {
cli.httpClient.Transport = transport
}
type MiddlewareFunc func(next http.RoundTripper) http.RoundTripper
func (cli *Client) UseMiddleware(mwFunc MiddlewareFunc) {
cli.httpClient.Transport = mwFunc(cli.httpClient.Transport)
}
// BasicAuthMiddleware
func NewBasicAuthMiddleware(user, pass string) MiddlewareFunc {
return func(next http.RoundTripper) http.RoundTripper {
return newBasicAuthMW(next, user, pass)
}
}
type BasicAuthMW struct {
user, pass string
next http.RoundTripper
}
func newBasicAuthMW(next http.RoundTripper, user, pass string) *BasicAuthMW {
return &BasicAuthMW{
user: user,
pass: pass,
next: next,
}
}
func (tran BasicAuthMW) RoundTrip(req *http.Request) (*http.Response, error) {
if tran.user != "" && tran.pass != "" {
pair := base64.StdEncoding.EncodeToString([]byte(tran.user + ":" + tran.pass))
req.Header.Set("Authorization", "Basic "+pair)
}
return tran.next.RoundTrip(req)
}
// BearerAuthMiddleware
func NewBearerAuthMiddleware(token string) MiddlewareFunc {
return func(next http.RoundTripper) http.RoundTripper {
return newBearerAuthMW(next, token)
}
}
type BearerAuthMW struct {
token string
next http.RoundTripper
}
func newBearerAuthMW(next http.RoundTripper, token string) *BearerAuthMW {
return &BearerAuthMW{
token: token,
next: next,
}
}
func (tran BearerAuthMW) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("Authorization", "Bearer "+tran.token)
return tran.next.RoundTrip(req)
}
// DefaultTransport
type DefaultTransport struct {
transport http.RoundTripper
}
func NewDefaultTransport() *DefaultTransport {
return &DefaultTransport{
transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
}
func (wrap *DefaultTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return wrap.transport.RoundTrip(req)
}
// ExampleMiddleware
func NewExampleMiddleware() MiddlewareFunc {
return func(next http.RoundTripper) http.RoundTripper {
return newExampleTransport(next)
}
}
type ExampleTransport struct {
next http.RoundTripper
}
func newExampleTransport(next http.RoundTripper) *ExampleTransport {
return &ExampleTransport{
next: next,
}
}
func (tran ExampleTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return tran.next.RoundTrip(req)
}
+42
View File
@@ -0,0 +1,42 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*/
package servcli
import (
"context"
"errors"
"io"
)
func Copy(ctx context.Context, writer io.Writer, reader io.Reader) (int64, error) {
var err error
var size int64
var halt bool
buffer := make([]byte, 1024*4)
for {
select {
case <-ctx.Done():
err = errors.New("Break copy by context")
break
default:
}
rsize, err := reader.Read(buffer)
if err == io.EOF {
err = nil
halt = true
}
if err != nil {
return size, err
}
wsize, err := writer.Write(buffer[0:rsize])
size += int64(wsize)
if err != nil {
return size, err
}
if halt {
break
}
}
return size, err
}
+77
View File
@@ -0,0 +1,77 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*/
package servcli
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"strconv"
"mproxy/app/servoper"
)
func (cli *Client) GetHello(ctx context.Context, rawpath string) error {
var err error
req := servoper.GetHelloParams{}
reqdata, err := json.Marshal(req)
if err != nil {
return err
}
_, err = cli.DoCall(ctx, rawpath, reqdata)
if err != nil {
return err
}
return err
}
func (cli *Client) DoCall(ctx context.Context, rawpath string, reqdata []byte) ([]byte, error) {
var err error
res := make([]byte, 0)
ref, err := ParsePath(rawpath)
if err != nil {
return res, err
}
uri := ref.HelloEP()
reader := bytes.NewReader(reqdata)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, reader)
if err != nil {
return res, err
}
req.Header.Set("User-Agent", cli.userAgent)
req.Header.Set("Accept", "*/*")
resp, err := cli.httpClient.Do(req)
if err != nil {
return res, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
return res, err
}
if resp.StatusCode != http.StatusOK {
err := fmt.Errorf("Unexpected response code %s", resp.Status)
return res, err
}
contentLength := resp.Header.Get("Content-Length")
if contentLength == "" {
err := fmt.Errorf("Content-Length header is missing")
return res, err
}
blobSize, err := strconv.ParseInt(contentLength, 10, 64)
if err != nil {
return res, err
}
buffer := bytes.NewBuffer(nil)
recSize, err := Copy(ctx, buffer, resp.Body)
if blobSize != recSize {
err := fmt.Errorf("Mismatch declared and actual body size: %d and %d", blobSize, recSize)
return res, err
}
res = buffer.Bytes()
return res, err
}
-56
View File
@@ -1,56 +0,0 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*/
package servcli
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"strconv"
)
func (cli *Client) doHTTPCall(ctx context.Context, host, obj, oper string, req []byte) ([]byte, error) {
var err error
var res []byte
reader := bytes.NewReader(req)
ref, err := NewReferer(host, obj, oper)
if err != nil {
return res, err
}
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, ref.Point(), reader)
if err != nil {
return res, err
}
httpReq.Header.Set("User-Agent", cli.userAgent)
httpReq.Header.Set("Accept", "*/*")
httpResp, err := cli.httpClient.Do(httpReq)
if err != nil {
return res, err
}
defer httpResp.Body.Close()
if httpResp.StatusCode != http.StatusOK {
err := fmt.Errorf("Unexpected response code: %s", httpResp.Status)
return res, err
}
contentLength := httpResp.Header.Get("Content-Length")
if contentLength == "" {
err := fmt.Errorf("Content-Length header is missing")
return res, err
}
blobSize, err := strconv.ParseInt(contentLength, 10, 64)
if err != nil {
return res, err
}
buffer := bytes.NewBuffer(nil)
recSize, err := io.Copy(buffer, httpResp.Body)
if blobSize != recSize {
err := fmt.Errorf("Mismatch declared and actual body size, %d and %d", blobSize, recSize)
return res, err
}
res = buffer.Bytes()
return res, err
}
+43 -33
View File
@@ -1,26 +1,36 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*/
package servcli
import (
"net/url"
"path"
"strconv"
"strings"
)
const (
PathTypeIdentic = "identic"
PathTypePrefix = "prefix"
PathTypeRegexp = "regexp"
)
type Referer struct {
urlobj *url.URL
user, pass string
obj, oper string
resource string
values url.Values
}
func NewReferer(hostname, object, operation string) (*Referer, error) {
func ParsePath(rawpath string) (*Referer, error) {
ref := &Referer{
obj: object,
oper: operation,
values: url.Values{},
}
if !strings.Contains(hostname, "://") {
hostname = "https://" + hostname
if !strings.Contains(rawpath, "://") {
rawpath = "https://" + rawpath
}
urlobj, err := url.Parse(hostname)
urlobj, err := url.Parse(rawpath)
if err != nil {
return ref, err
}
@@ -29,40 +39,40 @@ func NewReferer(hostname, object, operation string) (*Referer, error) {
ref.pass, _ = urlobj.User.Password()
urlobj.User = nil
}
ref.resource = path.Join("/", urlobj.Path)
urlobj.Path = "/"
ref.urlobj = urlobj
ref.values = urlobj.Query()
return ref, err
}
func ParseHostinfo(hostname string) (*Referer, error) {
ref := &Referer{}
if !strings.Contains(hostname, "://") {
hostname = "https://" + hostname
}
urlobj, err := url.Parse(hostname)
if err != nil {
return ref, err
}
if urlobj.User != nil {
ref.user = urlobj.User.Username()
ref.pass, _ = urlobj.User.Password()
urlobj.User = nil
}
urlobj.Path = "/"
ref.urlobj = urlobj
return ref, err
}
func (ref *Referer) Host() string {
return ref.urlobj.Host
}
func (ref *Referer) Raw() string {
return path.Join(ref.urlobj.Host, "/v3/api/", ref.obj, ref.oper)
res := path.Join(ref.urlobj.Host, ref.resource)
query := ref.values.Encode()
if query != "" {
return res + "?" + query
}
return res
}
func (ref *Referer) Point() string {
curl := ref.urlobj.JoinPath("/v3/api/", ref.obj, ref.oper)
func (ref *Referer) SetResource(resource string) {
ref.resource = path.Join("/", resource)
}
func (ref *Referer) JoinResource(resource string) {
ref.resource = path.Join("/", ref.resource, resource)
}
func (ref *Referer) PathType(typ string) {
ref.values.Set("pathType", typ)
}
func (ref *Referer) DryRun(yesno bool) {
ref.values.Set("dryRun", strconv.FormatBool(yesno))
}
func (ref *Referer) HelloEP() string {
curl := ref.urlobj.JoinPath("/v3/api/service/hello", ref.resource)
return curl.String()
}
-36
View File
@@ -1,36 +0,0 @@
package servcli
import (
"context"
"encoding/json"
"errors"
"mproxy/app/handler"
"mproxy/app/servoper"
)
func (cli *Client) ServiceHello(ctx context.Context, host string) (bool, error) {
var res bool
var err error
params := servoper.SendHelloParams{}
reqdata, err := json.Marshal(params)
if err != nil {
return res, err
}
resdata, err := cli.doHTTPCall(ctx, host, "service", "hello", reqdata)
if err != nil {
return res, err
}
response := handler.Response[servoper.SendHelloResult]{}
err = json.Unmarshal(resdata, &response)
if err != nil {
return res, err
}
if response.Error {
err = errors.New(response.Message)
return res, err
}
res = response.Result.Alive
return res, err
}
+25
View File
@@ -0,0 +1,25 @@
package servcli
import (
"crypto/tls"
"net/http"
)
// DefaultTransport
type DefaultTransport struct {
transport http.RoundTripper
}
func NewDefaultTransport() *DefaultTransport {
return &DefaultTransport{
transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
}
func (wrap *DefaultTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return wrap.transport.RoundTrip(req)
}