mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-20 04:42:33 +02:00
replacing label parser map with improved deserialization implementation, API host check now disabled when in debug mode
This commit is contained in:
parent
ef52ccb929
commit
8329a8ea9c
20 changed files with 201 additions and 371 deletions
|
@ -116,10 +116,6 @@ func main() {
|
||||||
printJSON(trace)
|
printJSON(trace)
|
||||||
}
|
}
|
||||||
|
|
||||||
if common.IsDebug {
|
|
||||||
printJSON(docker.GetRegisteredNamespaces())
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.StartProxyProviders()
|
cfg.StartProxyProviders()
|
||||||
cfg.WatchChanges()
|
cfg.WatchChanges()
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
- [Docker compose guide](#docker-compose-guide)
|
- [Docker compose guide](#docker-compose-guide)
|
||||||
- [Table of content](#table-of-content)
|
- [Table of content](#table-of-content)
|
||||||
|
- [Suggestions](#suggestions)
|
||||||
- [Additional setup](#additional-setup)
|
- [Additional setup](#additional-setup)
|
||||||
- [Labels](#labels)
|
- [Labels](#labels)
|
||||||
- [Syntax](#syntax)
|
- [Syntax](#syntax)
|
||||||
|
@ -16,6 +17,30 @@
|
||||||
- [Docker compose examples](#docker-compose-examples)
|
- [Docker compose examples](#docker-compose-examples)
|
||||||
- [Services URLs for above examples](#services-urls-for-above-examples)
|
- [Services URLs for above examples](#services-urls-for-above-examples)
|
||||||
|
|
||||||
|
## Suggestions
|
||||||
|
|
||||||
|
In order for labels to work correctly in `compose.yml`:
|
||||||
|
|
||||||
|
1. `key: value` mapping is suggested for label, instead of `- key=value`
|
||||||
|
2. you need to add `|` in the end for multiline strings.
|
||||||
|
|
||||||
|
Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
...
|
||||||
|
container_name: app
|
||||||
|
labels:
|
||||||
|
proxy.app.middlewares.modify_request.set_headers: |
|
||||||
|
X-Custom-Header1: value1, value2
|
||||||
|
X-Custom-Header2: value3
|
||||||
|
proxy.app.middlewares.modify_request.hide_headers: |
|
||||||
|
X-Custom-Header4
|
||||||
|
X-Custom-Header5
|
||||||
|
X-Custom-Header6
|
||||||
|
```
|
||||||
|
|
||||||
## Additional setup
|
## Additional setup
|
||||||
|
|
||||||
1. Enable HTTPs _(optional)_
|
1. Enable HTTPs _(optional)_
|
||||||
|
@ -89,7 +114,7 @@
|
||||||
| `port` | proxy port **(http/s)** | first port returned from docker | number in range of `1 - 65535` |
|
| `port` | proxy port **(http/s)** | first port returned from docker | number in range of `1 - 65535` |
|
||||||
| `port` | proxy port **(tcp/udp)** | `0:first_port` | `x:y` <br><ul><li>**x**: port for `go-proxy` to listen on.<br>**x** can be 0, which means listen on a random port</li><li>**y**: port or [_service name_](../src/common/constants.go#L55) of target container</li></ul> |
|
| `port` | proxy port **(tcp/udp)** | `0:first_port` | `x:y` <br><ul><li>**x**: port for `go-proxy` to listen on.<br>**x** can be 0, which means listen on a random port</li><li>**y**: port or [_service name_](../src/common/constants.go#L55) of target container</li></ul> |
|
||||||
| `no_tls_verify` | whether skip tls verify **(https only)** | `false` | boolean |
|
| `no_tls_verify` | whether skip tls verify **(https only)** | `false` | boolean |
|
||||||
| `path_patterns` | proxy path patterns **(http/s only)**<br> only requests that matched a pattern will be proxied | `/` **(proxy all requests)** | yaml style list[<sup>1</sup>](#list-example) of ([path patterns](https://pkg.go.dev/net/http#hdr-Patterns-ServeMux)) |
|
| `path_patterns` | proxy path patterns **(http/s only)**<br> only requests that matched a pattern will be proxied | `/` **(proxy all requests)** | list[<sup>1</sup>](#list-example) of ([path patterns](https://pkg.go.dev/net/http#hdr-Patterns-ServeMux)) |
|
||||||
|
|
||||||
|
|
||||||
[🔼Back to top](#table-of-content)
|
[🔼Back to top](#table-of-content)
|
||||||
|
@ -132,11 +157,11 @@ services:
|
||||||
...
|
...
|
||||||
labels:
|
labels:
|
||||||
proxy.nginx.path_patterns: | # remember to add the '|'
|
proxy.nginx.path_patterns: | # remember to add the '|'
|
||||||
- GET /
|
GET /
|
||||||
- POST /auth
|
POST /auth
|
||||||
proxy.nginx.middlewares.modify_request.hide_headers: | # remember to add the '|'
|
proxy.nginx.middlewares.modify_request.hide_headers: | # remember to add the '|'
|
||||||
- X-Custom-Header1
|
X-Custom-Header1
|
||||||
- X-Custom-Header2
|
X-Custom-Header2
|
||||||
```
|
```
|
||||||
|
|
||||||
Include file
|
Include file
|
||||||
|
@ -145,8 +170,8 @@ Include file
|
||||||
service_a:
|
service_a:
|
||||||
host: service_a.internal
|
host: service_a.internal
|
||||||
path_patterns:
|
path_patterns:
|
||||||
- GET /
|
GET /
|
||||||
- POST /auth
|
POST /auth
|
||||||
middlewares:
|
middlewares:
|
||||||
modify_request:
|
modify_request:
|
||||||
hide_headers:
|
hide_headers:
|
||||||
|
|
|
@ -119,9 +119,9 @@ Check https://nginx.org/en/docs/http/ngx_http_realip_module.html for explainatio
|
||||||
# docker labels
|
# docker labels
|
||||||
proxy.app1.middlewares.real_ip.header: X-Real-IP
|
proxy.app1.middlewares.real_ip.header: X-Real-IP
|
||||||
proxy.app1.middlewares.real_ip.from: |
|
proxy.app1.middlewares.real_ip.from: |
|
||||||
- 127.0.0.1
|
127.0.0.1
|
||||||
- 192.168.0.0/16
|
192.168.0.0/16
|
||||||
- 10.0.0.0/8
|
10.0.0.0/8
|
||||||
proxy.app1.middlewares.real_ip.recursive: true
|
proxy.app1.middlewares.real_ip.recursive: true
|
||||||
|
|
||||||
# include file
|
# include file
|
||||||
|
@ -177,8 +177,8 @@ app1:
|
||||||
```yaml
|
```yaml
|
||||||
# docker labels
|
# docker labels
|
||||||
proxy.app1.middlewares.cidr_whitelist.allow: |
|
proxy.app1.middlewares.cidr_whitelist.allow: |
|
||||||
- 10.0.0.0/8
|
10.0.0.0/8
|
||||||
- 192.168.0.0/16
|
192.168.0.0/16
|
||||||
# optional (default: 403)
|
# optional (default: 403)
|
||||||
proxy.app1.middlewares.cidr_whitelist.status_code: 403
|
proxy.app1.middlewares.cidr_whitelist.status_code: 403
|
||||||
# optional (default: "IP not allowed")
|
# optional (default: "IP not allowed")
|
||||||
|
@ -270,8 +270,8 @@ location / {
|
||||||
```yaml
|
```yaml
|
||||||
# docker labels
|
# docker labels
|
||||||
proxy.app1.middlewares.modify_request.hide_headers: |
|
proxy.app1.middlewares.modify_request.hide_headers: |
|
||||||
- X-Custom-Header1
|
X-Custom-Header1
|
||||||
- X-Custom-Header2
|
X-Custom-Header2
|
||||||
|
|
||||||
# include file
|
# include file
|
||||||
app1:
|
app1:
|
||||||
|
@ -339,11 +339,11 @@ Fields:
|
||||||
proxy.app1.middlewares.forward_auth.address: https://auth.example.com
|
proxy.app1.middlewares.forward_auth.address: https://auth.example.com
|
||||||
proxy.app1.middlewares.forward_auth.trust_forward_header: true
|
proxy.app1.middlewares.forward_auth.trust_forward_header: true
|
||||||
proxy.app1.middlewares.forward_auth.auth_response_headers: |
|
proxy.app1.middlewares.forward_auth.auth_response_headers: |
|
||||||
- X-Auth-Token
|
X-Auth-Token
|
||||||
- X-Auth-User
|
X-Auth-User
|
||||||
proxy.app1.middlewares.forward_auth.add_auth_cookies_to_response: |
|
proxy.app1.middlewares.forward_auth.add_auth_cookies_to_response: |
|
||||||
- uid
|
uid
|
||||||
- session_id
|
session_id
|
||||||
|
|
||||||
# include file
|
# include file
|
||||||
app1:
|
app1:
|
||||||
|
@ -421,17 +421,17 @@ services:
|
||||||
proxy.#1.middlewares.forward_auth.address: https://your_authentik_forward_address
|
proxy.#1.middlewares.forward_auth.address: https://your_authentik_forward_address
|
||||||
proxy.#1.middlewares.forward_auth.trustForwardHeader: true
|
proxy.#1.middlewares.forward_auth.trustForwardHeader: true
|
||||||
proxy.#1.middlewares.forward_auth.authResponseHeaders: |
|
proxy.#1.middlewares.forward_auth.authResponseHeaders: |
|
||||||
- X-authentik-username
|
X-authentik-username
|
||||||
- X-authentik-groups
|
X-authentik-groups
|
||||||
- X-authentik-email
|
X-authentik-email
|
||||||
- X-authentik-name
|
X-authentik-name
|
||||||
- X-authentik-uid
|
X-authentik-uid
|
||||||
- X-authentik-jwt
|
X-authentik-jwt
|
||||||
- X-authentik-meta-jwks
|
X-authentik-meta-jwks
|
||||||
- X-authentik-meta-outpost
|
X-authentik-meta-outpost
|
||||||
- X-authentik-meta-provider
|
X-authentik-meta-provider
|
||||||
- X-authentik-meta-app
|
X-authentik-meta-app
|
||||||
- X-authentik-meta-version
|
X-authentik-meta-version
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -40,10 +40,13 @@ func NewHandler(cfg *config.Config) http.Handler {
|
||||||
|
|
||||||
// allow only requests to API server with host matching common.APIHTTPAddr
|
// allow only requests to API server with host matching common.APIHTTPAddr
|
||||||
func checkHost(f http.HandlerFunc) http.HandlerFunc {
|
func checkHost(f http.HandlerFunc) http.HandlerFunc {
|
||||||
|
if common.IsDebug {
|
||||||
|
return f
|
||||||
|
}
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Host != common.APIHTTPAddr {
|
if r.Host != common.APIHTTPAddr {
|
||||||
Logger.Warnf("invalid request to API server with host: %s, expect %s", r.Host, common.APIHTTPAddr)
|
Logger.Warnf("invalid request to API server with host: %s, expect %s", r.Host, common.APIHTTPAddr)
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusForbidden)
|
||||||
w.Write([]byte("invalid request"))
|
w.Write([]byte("invalid request"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,10 @@ package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
@ -35,14 +37,11 @@ func GetEnvBool(key string, defaultValue bool) bool {
|
||||||
if !ok || value == "" {
|
if !ok || value == "" {
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
switch strings.ToLower(value) {
|
b, err := strconv.ParseBool(value)
|
||||||
case "true", "yes", "1":
|
if err != nil {
|
||||||
return true
|
log.Fatalf("Invalid boolean value: %s", value)
|
||||||
case "false", "no", "0":
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
return defaultValue
|
|
||||||
}
|
}
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetEnv(key, defaultValue string) string {
|
func GetEnv(key, defaultValue string) string {
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
U "github.com/yusing/go-proxy/internal/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
F "github.com/yusing/go-proxy/internal/utils/functional"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -23,8 +22,6 @@ type (
|
||||||
Value any
|
Value any
|
||||||
}
|
}
|
||||||
NestedLabelMap map[string]U.SerializedObject
|
NestedLabelMap map[string]U.SerializedObject
|
||||||
ValueParser func(string) (any, E.NestedError)
|
|
||||||
ValueParserMap map[string]ValueParser
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l *Label) String() string {
|
func (l *Label) String() string {
|
||||||
|
@ -107,45 +104,5 @@ func ParseLabel(label string, value string) (*Label, E.NestedError) {
|
||||||
l.Value = nestedLabel
|
l.Value = nestedLabel
|
||||||
}
|
}
|
||||||
|
|
||||||
// find if namespace has value parser
|
|
||||||
pm, ok := valueParserMap.Load(U.ToLowerNoSnake(l.Namespace))
|
|
||||||
if !ok {
|
|
||||||
return l, nil
|
|
||||||
}
|
|
||||||
// find if attribute has value parser
|
|
||||||
p, ok := pm[U.ToLowerNoSnake(l.Attribute)]
|
|
||||||
if !ok {
|
|
||||||
return l, nil
|
|
||||||
}
|
|
||||||
// try to parse value
|
|
||||||
v, err := p(value)
|
|
||||||
if err.HasError() {
|
|
||||||
return nil, err.Subject(label)
|
|
||||||
}
|
|
||||||
l.Value = v
|
|
||||||
return l, nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterNamespace(namespace string, pm ValueParserMap) {
|
|
||||||
pmCleaned := make(ValueParserMap, len(pm))
|
|
||||||
for k, v := range pm {
|
|
||||||
pmCleaned[U.ToLowerNoSnake(k)] = v
|
|
||||||
}
|
|
||||||
valueParserMap.Store(U.ToLowerNoSnake(namespace), pmCleaned)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetRegisteredNamespaces() map[string][]string {
|
|
||||||
r := make(map[string][]string)
|
|
||||||
|
|
||||||
valueParserMap.RangeAll(func(ns string, vpm ValueParserMap) {
|
|
||||||
r[ns] = make([]string, 0, len(vpm))
|
|
||||||
for attr := range vpm {
|
|
||||||
r[ns] = append(r[ns], attr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// namespace:target.attribute -> func(string) (any, error)
|
|
||||||
var valueParserMap = F.NewMapOf[string, ValueParserMap]()
|
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
package docker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
NSProxy = "proxy"
|
|
||||||
ProxyAttributePathPatterns = "path_patterns"
|
|
||||||
ProxyAttributeNoTLSVerify = "no_tls_verify"
|
|
||||||
ProxyAttributeMiddlewares = "middlewares"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = func() int {
|
|
||||||
RegisterNamespace(NSProxy, ValueParserMap{
|
|
||||||
ProxyAttributePathPatterns: YamlStringListParser,
|
|
||||||
ProxyAttributeNoTLSVerify: BoolParser,
|
|
||||||
})
|
|
||||||
return 0
|
|
||||||
}()
|
|
||||||
|
|
||||||
func YamlStringListParser(value string) (any, E.NestedError) {
|
|
||||||
/*
|
|
||||||
- foo
|
|
||||||
- bar
|
|
||||||
- baz
|
|
||||||
*/
|
|
||||||
value = strings.TrimSpace(value)
|
|
||||||
if value == "" {
|
|
||||||
return []string{}, nil
|
|
||||||
}
|
|
||||||
var data []string
|
|
||||||
err := E.From(yaml.Unmarshal([]byte(value), &data))
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func YamlLikeMappingParser(allowDuplicate bool) func(string) (any, E.NestedError) {
|
|
||||||
return func(value string) (any, E.NestedError) {
|
|
||||||
/*
|
|
||||||
foo: bar
|
|
||||||
boo: baz
|
|
||||||
*/
|
|
||||||
value = strings.TrimSpace(value)
|
|
||||||
lines := strings.Split(value, "\n")
|
|
||||||
h := make(map[string]string)
|
|
||||||
for _, line := range lines {
|
|
||||||
parts := strings.SplitN(line, ":", 2)
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return nil, E.Invalid("syntax", line).With("too many colons")
|
|
||||||
}
|
|
||||||
key := strings.TrimSpace(parts[0])
|
|
||||||
val := strings.TrimSpace(parts[1])
|
|
||||||
if existing, ok := h[key]; ok {
|
|
||||||
if !allowDuplicate {
|
|
||||||
return nil, E.Duplicated("key", key)
|
|
||||||
}
|
|
||||||
h[key] = existing + ", " + val
|
|
||||||
} else {
|
|
||||||
h[key] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return h, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BoolParser(value string) (any, E.NestedError) {
|
|
||||||
switch strings.ToLower(value) {
|
|
||||||
case "true", "yes", "1":
|
|
||||||
return true, nil
|
|
||||||
case "false", "no", "0":
|
|
||||||
return false, nil
|
|
||||||
default:
|
|
||||||
return nil, E.Invalid("boolean value", value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func IntParser(value string) (any, E.NestedError) {
|
|
||||||
i, err := strconv.Atoi(value)
|
|
||||||
if err != nil {
|
|
||||||
return 0, E.Invalid("integer value", value)
|
|
||||||
}
|
|
||||||
return i, nil
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
package docker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
|
||||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeLabel(namespace string, alias string, field string) string {
|
|
||||||
return fmt.Sprintf("%s.%s.%s", namespace, alias, field)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseLabel(t *testing.T) {
|
|
||||||
alias := "foo"
|
|
||||||
field := "ip"
|
|
||||||
v := "bar"
|
|
||||||
pl, err := ParseLabel(makeLabel(NSHomePage, alias, field), v)
|
|
||||||
ExpectNoError(t, err.Error())
|
|
||||||
ExpectEqual(t, pl.Namespace, NSHomePage)
|
|
||||||
ExpectEqual(t, pl.Target, alias)
|
|
||||||
ExpectEqual(t, pl.Attribute, field)
|
|
||||||
ExpectEqual(t, pl.Value.(string), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStringProxyLabel(t *testing.T) {
|
|
||||||
v := "bar"
|
|
||||||
pl, err := ParseLabel(makeLabel(NSProxy, "foo", "ip"), v)
|
|
||||||
ExpectNoError(t, err.Error())
|
|
||||||
ExpectEqual(t, pl.Value.(string), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoolProxyLabelValid(t *testing.T) {
|
|
||||||
tests := map[string]bool{
|
|
||||||
"true": true,
|
|
||||||
"TRUE": true,
|
|
||||||
"yes": true,
|
|
||||||
"1": true,
|
|
||||||
"false": false,
|
|
||||||
"FALSE": false,
|
|
||||||
"no": false,
|
|
||||||
"0": false,
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range tests {
|
|
||||||
pl, err := ParseLabel(makeLabel(NSProxy, "foo", ProxyAttributeNoTLSVerify), k)
|
|
||||||
ExpectNoError(t, err.Error())
|
|
||||||
ExpectEqual(t, pl.Value.(bool), v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoolProxyLabelInvalid(t *testing.T) {
|
|
||||||
_, err := ParseLabel(makeLabel(NSProxy, "foo", ProxyAttributeNoTLSVerify), "invalid")
|
|
||||||
if !err.Is(E.ErrInvalid) {
|
|
||||||
t.Errorf("Expected err InvalidProxyLabel, got %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// func TestSetHeaderProxyLabelValid(t *testing.T) {
|
|
||||||
// v := `
|
|
||||||
// X-Custom-Header1: foo, bar
|
|
||||||
// X-Custom-Header1: baz
|
|
||||||
// X-Custom-Header2: boo`
|
|
||||||
// v = strings.TrimPrefix(v, "\n")
|
|
||||||
// h := map[string]string{
|
|
||||||
// "X-Custom-Header1": "foo, bar, baz",
|
|
||||||
// "X-Custom-Header2": "boo",
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pl, err := ParseLabel(makeLabel(NSProxy, "foo", ProxyAttributeSetHeaders), v)
|
|
||||||
// ExpectNoError(t, err.Error())
|
|
||||||
// hGot := ExpectType[map[string]string](t, pl.Value)
|
|
||||||
// ExpectFalse(t, hGot == nil)
|
|
||||||
// ExpectDeepEqual(t, h, hGot)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestSetHeaderProxyLabelInvalid(t *testing.T) {
|
|
||||||
// tests := []string{
|
|
||||||
// "X-Custom-Header1 = bar",
|
|
||||||
// "X-Custom-Header1",
|
|
||||||
// "- X-Custom-Header1",
|
|
||||||
// }
|
|
||||||
|
|
||||||
// for _, v := range tests {
|
|
||||||
// _, err := ParseLabel(makeLabel(NSProxy, "foo", ProxyAttributeSetHeaders), v)
|
|
||||||
// if !err.Is(E.ErrInvalid) {
|
|
||||||
// t.Errorf("Expected invalid err for %q, got %s", v, err.Error())
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestHideHeadersProxyLabel(t *testing.T) {
|
|
||||||
// v := `
|
|
||||||
// - X-Custom-Header1
|
|
||||||
// - X-Custom-Header2
|
|
||||||
// - X-Custom-Header3
|
|
||||||
// `
|
|
||||||
// v = strings.TrimPrefix(v, "\n")
|
|
||||||
// pl, err := ParseLabel(makeLabel(NSProxy, "foo", ProxyAttributeHideHeaders), v)
|
|
||||||
// ExpectNoError(t, err.Error())
|
|
||||||
// sGot := ExpectType[[]string](t, pl.Value)
|
|
||||||
// sWant := []string{"X-Custom-Header1", "X-Custom-Header2", "X-Custom-Header3"}
|
|
||||||
// ExpectFalse(t, sGot == nil)
|
|
||||||
// ExpectDeepEqual(t, sGot, sWant)
|
|
||||||
// }
|
|
|
@ -8,11 +8,15 @@ import (
|
||||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func makeLabel(ns, name, attr string) string {
|
||||||
|
return fmt.Sprintf("%s.%s.%s", ns, name, attr)
|
||||||
|
}
|
||||||
|
|
||||||
func TestNestedLabel(t *testing.T) {
|
func TestNestedLabel(t *testing.T) {
|
||||||
mName := "middleware1"
|
mName := "middleware1"
|
||||||
mAttr := "prop1"
|
mAttr := "prop1"
|
||||||
v := "value1"
|
v := "value1"
|
||||||
pl, err := ParseLabel(makeLabel(NSProxy, "foo", fmt.Sprintf("%s.%s.%s", ProxyAttributeMiddlewares, mName, mAttr)), v)
|
pl, err := ParseLabel(makeLabel(NSProxy, "foo", makeLabel("middlewares", mName, mAttr)), v)
|
||||||
ExpectNoError(t, err.Error())
|
ExpectNoError(t, err.Error())
|
||||||
sGot := ExpectType[*Label](t, pl.Value)
|
sGot := ExpectType[*Label](t, pl.Value)
|
||||||
ExpectFalse(t, sGot == nil)
|
ExpectFalse(t, sGot == nil)
|
||||||
|
@ -27,7 +31,7 @@ func TestApplyNestedLabel(t *testing.T) {
|
||||||
mName := "middleware1"
|
mName := "middleware1"
|
||||||
mAttr := "prop1"
|
mAttr := "prop1"
|
||||||
v := "value1"
|
v := "value1"
|
||||||
pl, err := ParseLabel(makeLabel(NSProxy, "foo", fmt.Sprintf("%s.%s.%s", ProxyAttributeMiddlewares, mName, mAttr)), v)
|
pl, err := ParseLabel(makeLabel(NSProxy, "foo", makeLabel("middlewares", mName, mAttr)), v)
|
||||||
ExpectNoError(t, err.Error())
|
ExpectNoError(t, err.Error())
|
||||||
err = ApplyLabel(entry, pl)
|
err = ApplyLabel(entry, pl)
|
||||||
ExpectNoError(t, err.Error())
|
ExpectNoError(t, err.Error())
|
||||||
|
@ -51,7 +55,7 @@ func TestApplyNestedLabelExisting(t *testing.T) {
|
||||||
entry.Middlewares[mName] = make(U.SerializedObject)
|
entry.Middlewares[mName] = make(U.SerializedObject)
|
||||||
entry.Middlewares[mName][checkAttr] = checkV
|
entry.Middlewares[mName][checkAttr] = checkV
|
||||||
|
|
||||||
pl, err := ParseLabel(makeLabel(NSProxy, "foo", fmt.Sprintf("%s.%s.%s", ProxyAttributeMiddlewares, mName, mAttr)), v)
|
pl, err := ParseLabel(makeLabel(NSProxy, "foo", makeLabel("middlewares", mName, mAttr)), v)
|
||||||
ExpectNoError(t, err.Error())
|
ExpectNoError(t, err.Error())
|
||||||
err = ApplyLabel(entry, pl)
|
err = ApplyLabel(entry, pl)
|
||||||
ExpectNoError(t, err.Error())
|
ExpectNoError(t, err.Error())
|
||||||
|
@ -76,7 +80,7 @@ func TestApplyNestedLabelNoAttr(t *testing.T) {
|
||||||
entry.Middlewares = make(NestedLabelMap)
|
entry.Middlewares = make(NestedLabelMap)
|
||||||
entry.Middlewares[mName] = make(U.SerializedObject)
|
entry.Middlewares[mName] = make(U.SerializedObject)
|
||||||
|
|
||||||
pl, err := ParseLabel(makeLabel(NSProxy, "foo", fmt.Sprintf("%s.%s", ProxyAttributeMiddlewares, mName)), v)
|
pl, err := ParseLabel(makeLabel(NSProxy, "foo", fmt.Sprintf("%s.%s", "middlewares", mName)), v)
|
||||||
ExpectNoError(t, err.Error())
|
ExpectNoError(t, err.Error())
|
||||||
err = ApplyLabel(entry, pl)
|
err = ApplyLabel(entry, pl)
|
||||||
ExpectNoError(t, err.Error())
|
ExpectNoError(t, err.Error())
|
||||||
|
|
|
@ -3,6 +3,8 @@ package docker
|
||||||
const (
|
const (
|
||||||
WildcardAlias = "*"
|
WildcardAlias = "*"
|
||||||
|
|
||||||
|
NSProxy = "proxy"
|
||||||
|
|
||||||
LabelAliases = NSProxy + ".aliases"
|
LabelAliases = NSProxy + ".aliases"
|
||||||
LabelExclude = NSProxy + ".exclude"
|
LabelExclude = NSProxy + ".exclude"
|
||||||
LabelIdleTimeout = NSProxy + ".idle_timeout"
|
LabelIdleTimeout = NSProxy + ".idle_timeout"
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
D "github.com/yusing/go-proxy/internal/docker"
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
"github.com/yusing/go-proxy/internal/types"
|
"github.com/yusing/go-proxy/internal/types"
|
||||||
F "github.com/yusing/go-proxy/internal/utils/functional"
|
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||||
|
@ -24,13 +23,7 @@ type cidrWhitelistOpts struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var CIDRWhiteList = &cidrWhitelist{
|
var CIDRWhiteList = &cidrWhitelist{
|
||||||
m: &Middleware{
|
m: &Middleware{withOptions: NewCIDRWhitelist},
|
||||||
labelParserMap: D.ValueParserMap{
|
|
||||||
"allow": D.YamlStringListParser,
|
|
||||||
"statusCode": D.IntParser,
|
|
||||||
},
|
|
||||||
withOptions: NewCIDRWhitelist,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var cidrWhitelistDefaults = func() *cidrWhitelistOpts {
|
var cidrWhitelistDefaults = func() *cidrWhitelistOpts {
|
||||||
|
|
|
@ -30,9 +30,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
var CloudflareRealIP = &realIP{
|
var CloudflareRealIP = &realIP{
|
||||||
m: &Middleware{
|
m: &Middleware{withOptions: NewCloudflareRealIP},
|
||||||
withOptions: NewCloudflareRealIP,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCloudflareRealIP(_ OptionsRaw) (*Middleware, E.NestedError) {
|
func NewCloudflareRealIP(_ OptionsRaw) (*Middleware, E.NestedError) {
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
D "github.com/yusing/go-proxy/internal/docker"
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
gpHTTP "github.com/yusing/go-proxy/internal/net/http"
|
gpHTTP "github.com/yusing/go-proxy/internal/net/http"
|
||||||
)
|
)
|
||||||
|
@ -33,17 +32,9 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var ForwardAuth = func() *forwardAuth {
|
var ForwardAuth = &forwardAuth{
|
||||||
fa := new(forwardAuth)
|
m: &Middleware{withOptions: NewForwardAuthfunc},
|
||||||
fa.m = new(Middleware)
|
}
|
||||||
fa.m.labelParserMap = D.ValueParserMap{
|
|
||||||
"trust_forward_header": D.BoolParser,
|
|
||||||
"auth_response_headers": D.YamlStringListParser,
|
|
||||||
"add_auth_cookies_to_response": D.YamlStringListParser,
|
|
||||||
}
|
|
||||||
fa.m.withOptions = NewForwardAuthfunc
|
|
||||||
return fa
|
|
||||||
}()
|
|
||||||
|
|
||||||
func NewForwardAuthfunc(optsRaw OptionsRaw) (*Middleware, E.NestedError) {
|
func NewForwardAuthfunc(optsRaw OptionsRaw) (*Middleware, E.NestedError) {
|
||||||
faWithOpts := new(forwardAuth)
|
faWithOpts := new(forwardAuth)
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
D "github.com/yusing/go-proxy/internal/docker"
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
gpHTTP "github.com/yusing/go-proxy/internal/net/http"
|
gpHTTP "github.com/yusing/go-proxy/internal/net/http"
|
||||||
U "github.com/yusing/go-proxy/internal/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
|
@ -37,7 +36,6 @@ type (
|
||||||
modifyResponse ModifyResponseFunc // runs after ReverseProxy.ModifyResponse
|
modifyResponse ModifyResponseFunc // runs after ReverseProxy.ModifyResponse
|
||||||
|
|
||||||
withOptions CloneWithOptFunc
|
withOptions CloneWithOptFunc
|
||||||
labelParserMap D.ValueParserMap
|
|
||||||
impl any
|
impl any
|
||||||
|
|
||||||
parent *Middleware
|
parent *Middleware
|
||||||
|
@ -92,7 +90,7 @@ func (m *Middleware) WithOptionsClone(optsRaw OptionsRaw) (*Middleware, E.Nested
|
||||||
m.name,
|
m.name,
|
||||||
m.before,
|
m.before,
|
||||||
m.modifyResponse,
|
m.modifyResponse,
|
||||||
nil, nil,
|
nil,
|
||||||
m.impl,
|
m.impl,
|
||||||
m.parent,
|
m.parent,
|
||||||
m.children,
|
m.children,
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
D "github.com/yusing/go-proxy/internal/docker"
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
U "github.com/yusing/go-proxy/internal/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
)
|
)
|
||||||
|
@ -42,11 +41,6 @@ func init() {
|
||||||
names := make(map[*Middleware][]string)
|
names := make(map[*Middleware][]string)
|
||||||
for name, m := range middlewares {
|
for name, m := range middlewares {
|
||||||
names[m] = append(names[m], http.CanonicalHeaderKey(name))
|
names[m] = append(names[m], http.CanonicalHeaderKey(name))
|
||||||
// register middleware name to docker label parsr
|
|
||||||
// in order to parse middleware_name.option=value into correct type
|
|
||||||
if m.labelParserMap != nil {
|
|
||||||
D.RegisterNamespace(name, m.labelParserMap)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for m, names := range names {
|
for m, names := range names {
|
||||||
if len(names) > 1 {
|
if len(names) > 1 {
|
||||||
|
|
|
@ -2,7 +2,6 @@ package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
D "github.com/yusing/go-proxy/internal/docker"
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,17 +18,9 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var ModifyRequest = func() *modifyRequest {
|
var ModifyRequest = &modifyRequest{
|
||||||
mr := new(modifyRequest)
|
m: &Middleware{withOptions: NewModifyRequest},
|
||||||
mr.m = new(Middleware)
|
}
|
||||||
mr.m.labelParserMap = D.ValueParserMap{
|
|
||||||
"set_headers": D.YamlLikeMappingParser(true),
|
|
||||||
"add_headers": D.YamlLikeMappingParser(true),
|
|
||||||
"hide_headers": D.YamlStringListParser,
|
|
||||||
}
|
|
||||||
mr.m.withOptions = NewModifyRequest
|
|
||||||
return mr
|
|
||||||
}()
|
|
||||||
|
|
||||||
func NewModifyRequest(optsRaw OptionsRaw) (*Middleware, E.NestedError) {
|
func NewModifyRequest(optsRaw OptionsRaw) (*Middleware, E.NestedError) {
|
||||||
mr := new(modifyRequest)
|
mr := new(modifyRequest)
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
D "github.com/yusing/go-proxy/internal/docker"
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,17 +20,9 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var ModifyResponse = func() (mr *modifyResponse) {
|
var ModifyResponse = &modifyResponse{
|
||||||
mr = new(modifyResponse)
|
m: &Middleware{withOptions: NewModifyResponse},
|
||||||
mr.m = new(Middleware)
|
}
|
||||||
mr.m.labelParserMap = D.ValueParserMap{
|
|
||||||
"set_headers": D.YamlLikeMappingParser(true),
|
|
||||||
"add_headers": D.YamlLikeMappingParser(true),
|
|
||||||
"hide_headers": D.YamlStringListParser,
|
|
||||||
}
|
|
||||||
mr.m.withOptions = NewModifyResponse
|
|
||||||
return
|
|
||||||
}()
|
|
||||||
|
|
||||||
func NewModifyResponse(optsRaw OptionsRaw) (*Middleware, E.NestedError) {
|
func NewModifyResponse(optsRaw OptionsRaw) (*Middleware, E.NestedError) {
|
||||||
mr := new(modifyResponse)
|
mr := new(modifyResponse)
|
||||||
|
|
|
@ -3,7 +3,6 @@ package middleware
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
D "github.com/yusing/go-proxy/internal/docker"
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
"github.com/yusing/go-proxy/internal/types"
|
"github.com/yusing/go-proxy/internal/types"
|
||||||
)
|
)
|
||||||
|
@ -32,13 +31,7 @@ type realIPOpts struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var RealIP = &realIP{
|
var RealIP = &realIP{
|
||||||
m: &Middleware{
|
m: &Middleware{withOptions: NewRealIP},
|
||||||
labelParserMap: D.ValueParserMap{
|
|
||||||
"from": D.YamlStringListParser,
|
|
||||||
"recursive": D.BoolParser,
|
|
||||||
},
|
|
||||||
withOptions: NewRealIP,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var realIPOptsDefault = func() *realIPOpts {
|
var realIPOptsDefault = func() *realIPOpts {
|
||||||
|
|
|
@ -5,7 +5,9 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/santhosh-tekuri/jsonschema"
|
"github.com/santhosh-tekuri/jsonschema"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
|
@ -210,17 +212,16 @@ func Convert(src reflect.Value, dst reflect.Value) E.NestedError {
|
||||||
switch {
|
switch {
|
||||||
case srcT.AssignableTo(dstT):
|
case srcT.AssignableTo(dstT):
|
||||||
dst.Set(src)
|
dst.Set(src)
|
||||||
|
return nil
|
||||||
case srcT.ConvertibleTo(dstT):
|
case srcT.ConvertibleTo(dstT):
|
||||||
dst.Set(src.Convert(dstT))
|
dst.Set(src.Convert(dstT))
|
||||||
|
return nil
|
||||||
case srcT.Kind() == reflect.Map:
|
case srcT.Kind() == reflect.Map:
|
||||||
obj, ok := src.Interface().(SerializedObject)
|
obj, ok := src.Interface().(SerializedObject)
|
||||||
if !ok {
|
if !ok {
|
||||||
return E.TypeMismatch[SerializedObject](src.Interface())
|
return E.TypeMismatch[SerializedObject](src.Interface())
|
||||||
}
|
}
|
||||||
err := Deserialize(obj, dst.Addr().Interface())
|
return Deserialize(obj, dst.Addr().Interface())
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case srcT.Kind() == reflect.Slice:
|
case srcT.Kind() == reflect.Slice:
|
||||||
if dstT.Kind() != reflect.Slice {
|
if dstT.Kind() != reflect.Slice {
|
||||||
return E.TypeError("slice", srcT, dstT)
|
return E.TypeError("slice", srcT, dstT)
|
||||||
|
@ -237,7 +238,13 @@ func Convert(src reflect.Value, dst reflect.Value) E.NestedError {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
dst.Set(newSlice)
|
dst.Set(newSlice)
|
||||||
default:
|
return nil
|
||||||
|
case src.Kind() == reflect.String:
|
||||||
|
if convertible, err := ConvertString(src.String(), dst); convertible {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var converter Converter
|
var converter Converter
|
||||||
var ok bool
|
var ok bool
|
||||||
// check if (*T).Convertor is implemented
|
// check if (*T).Convertor is implemented
|
||||||
|
@ -259,9 +266,84 @@ func Convert(src reflect.Value, dst reflect.Value) E.NestedError {
|
||||||
}
|
}
|
||||||
dst.Set(c)
|
dst.Set(c)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
func ConvertString(src string, dst reflect.Value) (convertible bool, convErr E.NestedError) {
|
||||||
|
convertible = true
|
||||||
|
// primitive types / simple types
|
||||||
|
switch dst.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
b, err := strconv.ParseBool(src)
|
||||||
|
if err != nil {
|
||||||
|
convErr = E.Invalid("boolean", src)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dst.Set(reflect.ValueOf(b))
|
||||||
|
return
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
i, err := strconv.ParseInt(src, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
convErr = E.Invalid("int", src)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dst.Set(reflect.ValueOf(i))
|
||||||
|
return
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
i, err := strconv.ParseUint(src, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
convErr = E.Invalid("uint", src)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dst.Set(reflect.ValueOf(i))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// yaml like
|
||||||
|
lines := strings.Split(strings.TrimSpace(src), "\n")
|
||||||
|
for i := range lines {
|
||||||
|
lines[i] = strings.TrimSpace(lines[i])
|
||||||
|
}
|
||||||
|
var tmp any
|
||||||
|
switch dst.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
// one liner is comma seperated list
|
||||||
|
if len(lines) == 0 {
|
||||||
|
dst.Set(reflect.ValueOf(CommaSeperatedList(src)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sl := make([]string, 0, len(lines))
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimLeftFunc(line, func(r rune) bool {
|
||||||
|
return r == '-' || unicode.IsSpace(r)
|
||||||
|
})
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sl = append(sl, line)
|
||||||
|
}
|
||||||
|
tmp = sl
|
||||||
|
case reflect.Map:
|
||||||
|
m := make(map[string]string, len(lines))
|
||||||
|
for i, line := range lines {
|
||||||
|
parts := strings.Split(line, ":")
|
||||||
|
if len(parts) < 2 {
|
||||||
|
convErr = E.Invalid("map", "missing colon").Subjectf("line#%d", i+1).With(line)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(parts) > 2 {
|
||||||
|
convErr = E.Invalid("map", "too many colons").Subjectf("line#%d", i+1).With(line)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
k := strings.TrimSpace(parts[0])
|
||||||
|
v := strings.TrimSpace(parts[1])
|
||||||
|
m[k] = v
|
||||||
|
}
|
||||||
|
tmp = m
|
||||||
|
}
|
||||||
|
if tmp == nil {
|
||||||
|
convertible = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return true, Convert(reflect.ValueOf(tmp), dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeserializeJson(j map[string]string, target any) E.NestedError {
|
func DeserializeJson(j map[string]string, target any) E.NestedError {
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CommaSeperatedList(s string) []string {
|
func CommaSeperatedList(s string) []string {
|
||||||
|
@ -14,6 +16,10 @@ func CommaSeperatedList(s string) []string {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IntParser(value string) (int, E.NestedError) {
|
||||||
|
return E.Check(strconv.Atoi(value))
|
||||||
|
}
|
||||||
|
|
||||||
func ExtractPort(fullURL string) (int, error) {
|
func ExtractPort(fullURL string) (int, error) {
|
||||||
url, err := url.Parse(fullURL)
|
url, err := url.Parse(fullURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Add table
Reference in a new issue