diff --git a/aws/configAws/models.go b/aws/configAws/models.go index c8d1c19..02bf664 100644 --- a/aws/configAws/models.go +++ b/aws/configAws/models.go @@ -139,8 +139,8 @@ func (c awsModel) Check(ctx context.Context) errors.Error { } d := net.Dialer{ - Timeout: httpcli.TIMEOUT_5_SEC, - KeepAlive: httpcli.TIMEOUT_5_SEC, + Timeout: httpcli.ClientTimeout5Sec, + KeepAlive: httpcli.ClientTimeout5Sec, } con, err = d.DialContext(ctx, "tcp", adr.Host) diff --git a/aws/configCustom/models.go b/aws/configCustom/models.go index ea680e7..fd7651d 100644 --- a/aws/configCustom/models.go +++ b/aws/configCustom/models.go @@ -263,8 +263,8 @@ func (c awsModel) Check(ctx context.Context) errors.Error { } d := net.Dialer{ - Timeout: httpcli.TIMEOUT_5_SEC, - KeepAlive: httpcli.TIMEOUT_5_SEC, + Timeout: httpcli.ClientTimeout5Sec, + KeepAlive: httpcli.ClientTimeout5Sec, } if c.endpoint.Port() == "" && c.endpoint.Scheme == "http" { diff --git a/go.mod b/go.mod index ae36ae4..701372c 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,11 @@ module github.com/nabbar/golib go 1.17 require ( - github.com/aws/aws-sdk-go-v2 v1.16.1 - github.com/aws/aws-sdk-go-v2/config v1.15.2 - github.com/aws/aws-sdk-go-v2/credentials v1.11.1 - github.com/aws/aws-sdk-go-v2/service/iam v1.18.2 - github.com/aws/aws-sdk-go-v2/service/s3 v1.26.2 + github.com/aws/aws-sdk-go-v2 v1.16.2 + github.com/aws/aws-sdk-go-v2/config v1.15.3 + github.com/aws/aws-sdk-go-v2/credentials v1.11.2 + github.com/aws/aws-sdk-go-v2/service/iam v1.18.3 + github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3 github.com/bits-and-blooms/bitset v1.2.2 github.com/c-bata/go-prompt v0.2.6 github.com/fatih/color v1.13.0 @@ -47,7 +47,7 @@ require ( golang.org/x/net v0.0.0-20220325170049-de3da57026de golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 + golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 gopkg.in/yaml.v2 v2.4.0 gorm.io/driver/clickhouse v0.3.1 @@ -72,16 +72,16 @@ require ( github.com/aokoli/goutils v1.1.1 // indirect github.com/armon/go-metrics v0.3.10 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.2 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.8 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.2 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.9 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.2 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.11.2 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.16.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.11.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.16.3 // indirect github.com/aws/smithy-go v1.11.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bwmarrin/snowflake v0.3.0 // indirect diff --git a/httpcli/cli.go b/httpcli/cli.go index aa11403..189583b 100644 --- a/httpcli/cli.go +++ b/httpcli/cli.go @@ -27,20 +27,25 @@ package httpcli import ( + "context" "net" "net/http" "time" - "github.com/nabbar/golib/certificates" - "github.com/nabbar/golib/errors" "golang.org/x/net/http2" + + libtls "github.com/nabbar/golib/certificates" + liberr "github.com/nabbar/golib/errors" ) const ( - TIMEOUT_30_SEC = 30 * time.Second - TIMEOUT_10_SEC = 10 * time.Second - TIMEOUT_5_SEC = 5 * time.Second - TIMEOUT_1_SEC = 1 * time.Second + ClientTimeout30Sec = 30 * time.Second + ClientTimeout10Sec = 10 * time.Second + ClientTimeout5Sec = 5 * time.Second + ClientTimeout1Sec = 1 * time.Second + + ClientNetworkTCP = "tcp" + ClientNetworkUDP = "udp" ) func GetClient(serverName string) *http.Client { @@ -53,11 +58,11 @@ func GetClient(serverName string) *http.Client { return c } -func GetClientError(serverName string) (*http.Client, errors.Error) { +func GetClientError(serverName string) (*http.Client, liberr.Error) { return GetClientTimeout(serverName, true, 0) } -func GetClientTimeout(serverName string, http2Tr bool, GlobalTimeout time.Duration) (*http.Client, errors.Error) { +func GetClientTimeout(serverName string, http2Tr bool, GlobalTimeout time.Duration) (*http.Client, liberr.Error) { dl := &net.Dialer{} tr := &http.Transport{ @@ -65,20 +70,51 @@ func GetClientTimeout(serverName string, http2Tr bool, GlobalTimeout time.Durati DialContext: dl.DialContext, DisableCompression: true, //nolint #staticcheck - TLSClientConfig: certificates.GetTLSConfig(serverName), + TLSClientConfig: libtls.GetTLSConfig(serverName), } return getclient(tr, http2Tr, GlobalTimeout) } -func GetClientCustom(tr *http.Transport, http2Tr bool, GlobalTimeout time.Duration) (*http.Client, errors.Error) { +func GetClientCustom(tr *http.Transport, http2Tr bool, GlobalTimeout time.Duration) (*http.Client, liberr.Error) { return getclient(tr, http2Tr, GlobalTimeout) } -func getclient(tr *http.Transport, http2Tr bool, GlobalTimeout time.Duration) (*http.Client, errors.Error) { +func GetClientTls(serverName string, tls libtls.TLSConfig, http2Tr bool, GlobalTimeout time.Duration) (*http.Client, liberr.Error) { + dl := &net.Dialer{} + + tr := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: dl.DialContext, + DisableCompression: true, + //nolint #staticcheck + TLSClientConfig: tls.TlsConfig(serverName), + } + + return getclient(tr, http2Tr, GlobalTimeout) +} + +func GetClientTlsForceIp(netw Network, ip string, serverName string, tls libtls.TLSConfig, http2Tr bool, GlobalTimeout time.Duration) (*http.Client, liberr.Error) { + fctDial := func(ctx context.Context, network, address string) (net.Conn, error) { + dl := &net.Dialer{} + return dl.DialContext(ctx, netw.Code(), ip) + } + + tr := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: fctDial, + DisableCompression: true, + //nolint #staticcheck + TLSClientConfig: tls.TlsConfig(serverName), + } + + return getclient(tr, http2Tr, GlobalTimeout) +} + +func getclient(tr *http.Transport, http2Tr bool, GlobalTimeout time.Duration) (*http.Client, liberr.Error) { if http2Tr { if e := http2.ConfigureTransport(tr); e != nil { - return nil, HTTP2_CONFIGURE.ErrorParent(e) + return nil, ErrorClientTransportHttp2.ErrorParent(e) } } diff --git a/httpcli/error.go b/httpcli/error.go index 0604ebd..a2ed00a 100644 --- a/httpcli/error.go +++ b/httpcli/error.go @@ -26,51 +26,27 @@ package httpcli -import "github.com/nabbar/golib/errors" +import "bytes" -const ( - EMPTY_PARAMS errors.CodeError = iota + errors.MinPkgHttpCli - URL_PARSE - HTTP_CLIENT - HTTP_REQUEST - HTTP_DO - IO_READ - BUFFER_WRITE - HTTP2_CONFIGURE -) - -var isCodeError = false - -func IsCodeError() bool { - return isCodeError +type requestError struct { + c int + s string + b *bytes.Buffer + e error } -func init() { - isCodeError = errors.ExistInMapMessage(EMPTY_PARAMS) - errors.RegisterIdFctMessage(EMPTY_PARAMS, getMessage) +func (r *requestError) StatusCode() int { + return r.c } -func getMessage(code errors.CodeError) (message string) { - switch code { - case errors.UNK_ERROR: - return "" - case EMPTY_PARAMS: - return "given parameters is empty" - case URL_PARSE: - return "uri/url parse error" - case HTTP_CLIENT: - return "error on creating a new http/http2 client" - case HTTP_REQUEST: - return "error on creating a new http/http2 request" - case HTTP_DO: - return "error on sending a http/http2 request" - case IO_READ: - return "error on reading i/o stream" - case BUFFER_WRITE: - return "error on writing bytes on buffer" - case HTTP2_CONFIGURE: - return "error while configure http2 transport for client" - } - - return "" +func (r *requestError) Status() string { + return r.s +} + +func (r *requestError) Body() *bytes.Buffer { + return r.b +} + +func (r *requestError) Error() error { + return r.e } diff --git a/httpcli/errors.go b/httpcli/errors.go new file mode 100644 index 0000000..1d54baa --- /dev/null +++ b/httpcli/errors.go @@ -0,0 +1,79 @@ +/* + * MIT License + * + * Copyright (c) 2020 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package httpcli + +import ( + liberr "github.com/nabbar/golib/errors" +) + +const ( + ErrorParamsEmpty liberr.CodeError = iota + liberr.MinPkgHttpCli + ErrorParamsInvalid + ErrorCreateRequest + ErrorSendRequest + ErrorResponseInvalid + ErrorResponseLoadBody + ErrorResponseStatus + ErrorResponseUnmarshall + ErrorClientTransportHttp2 +) + +func init() { + isCodeError = liberr.ExistInMapMessage(ErrorParamsEmpty) + liberr.RegisterIdFctMessage(ErrorParamsEmpty, getMessage) +} + +var isCodeError = false + +func IsCodeError() bool { + return isCodeError +} + +func getMessage(code liberr.CodeError) (message string) { + switch code { + case ErrorParamsEmpty: + return "at least one given parameters is empty" + case ErrorParamsInvalid: + return "at least one given parameters is invalid" + case ErrorCreateRequest: + return "cannot create http request for given params" + case ErrorSendRequest: + return "cannot send the http request" + case ErrorResponseInvalid: + return "the response for the sending request seems to be invalid" + case ErrorResponseLoadBody: + return "an error occurs while trying to load the response contents" + case ErrorResponseStatus: + return "the response status is not in the valid status list" + case ErrorResponseUnmarshall: + return "the response body cannot be unmarshal with given model" + case ErrorClientTransportHttp2: + return "error while configure http2 transport for client" + } + + return liberr.NUL_MESSAGE +} diff --git a/httpcli/http.go b/httpcli/http.go deleted file mode 100644 index a018287..0000000 --- a/httpcli/http.go +++ /dev/null @@ -1,170 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2019 Nicolas JUHEL - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package httpcli - -import ( - "bytes" - "context" - "io/ioutil" - "net/http" - "net/url" - "strings" - - liberr "github.com/nabbar/golib/errors" - liblog "github.com/nabbar/golib/logger" -) - -type httpClient struct { - url *url.URL - cli *http.Client - ctx context.Context -} - -type HTTP interface { - SetContext(ctx context.Context) - Check() liberr.Error - Call(file *bytes.Buffer) (bool, *bytes.Buffer, liberr.Error) -} - -func NewClient(uri string) (HTTP, liberr.Error) { - var ( - pUri *url.URL - err error - host string - ) - - if uri != "" { - pUri, err = url.Parse(uri) - - if err != nil { - return nil, URL_PARSE.ErrorParent(err) - } - - host = pUri.Host - } else { - pUri = nil - host = "" - } - - c, e := GetClientError(host) - - if e != nil { - return nil, HTTP_CLIENT.Error(e) - } - - return &httpClient{ - url: pUri, - cli: c, - ctx: context.Background(), - }, nil -} - -func (obj *httpClient) SetContext(ctx context.Context) { - if ctx != nil { - obj.ctx = ctx - } -} - -func (obj *httpClient) Check() liberr.Error { - req, e := obj.newRequest(http.MethodHead, nil) - - if e != nil { - return e - } - - var r *http.Response - - r, e = obj.doRequest(req) - - if r != nil && r.Body != nil { - _ = r.Body.Close() - } - - return e -} - -func (obj *httpClient) Call(body *bytes.Buffer) (bool, *bytes.Buffer, liberr.Error) { - req, e := obj.newRequest(http.MethodPost, body) - - if e != nil { - return false, nil, e - } - - res, e := obj.doRequest(req) - - if e != nil { - return false, nil, e - } - - return obj.checkResponse(res) -} - -func (obj *httpClient) newRequest(method string, body *bytes.Buffer) (*http.Request, liberr.Error) { - var reader *bytes.Reader - - if body != nil && body.Len() > 0 { - reader = bytes.NewReader(body.Bytes()) - } - - req, e := http.NewRequestWithContext(obj.ctx, method, obj.url.String(), reader) - if e != nil { - return req, HTTP_REQUEST.ErrorParent(e) - } - - return req, nil -} - -func (obj *httpClient) doRequest(req *http.Request) (*http.Response, liberr.Error) { - res, e := obj.cli.Do(req) - - if e != nil { - return res, HTTP_DO.ErrorParent(e) - } - - return res, nil -} - -func (obj *httpClient) checkResponse(res *http.Response) (bool, *bytes.Buffer, liberr.Error) { - var buf *bytes.Buffer - - if res.Body != nil { - bdy, err := ioutil.ReadAll(res.Body) - - if err != nil { - return false, nil, IO_READ.ErrorParent(err) - } - - _, err = buf.Write(bdy) - - if err != nil { - return false, nil, BUFFER_WRITE.ErrorParent(err) - } - - liblog.GetDefault().Entry(liblog.DebugLevel, "").ErrorAdd(true, err).FieldAdd("remote.uri", res.Request.URL.String()).FieldAdd("remote.method", res.Request.Method).Log() - } - - return strings.HasPrefix(res.Status, "2"), buf, nil -} diff --git a/httpcli/interface.go b/httpcli/interface.go new file mode 100644 index 0000000..0aba7fb --- /dev/null +++ b/httpcli/interface.go @@ -0,0 +1,92 @@ +/* + * MIT License + * + * Copyright (c) 2020 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package httpcli + +import ( + "bytes" + "context" + "io" + "net/http" + "net/url" + "sync" + "time" + + libtls "github.com/nabbar/golib/certificates" + + liberr "github.com/nabbar/golib/errors" +) + +type FctHttpClient func() *http.Client + +type RequestError interface { + StatusCode() int + Status() string + Body() *bytes.Buffer + Error() error +} + +type Request interface { + Clone() Request + New() Request + + SetClient(fct FctHttpClient) + UseClientPackage(ip string, tls libtls.TLSConfig, http2Tr bool, GlobalTimeout time.Duration) + + Endpoint(uri string) error + SetUrl(u *url.URL) + GetUrl() *url.URL + AddPath(path string) + AddParams(key, val string) + + AuthBearer(token string) + AuthBasic(user, pass string) + ContentType(content string) + + Header(key, value string) + Method(mtd string) + + RequestJson(body interface{}) error + RequestReader(body io.Reader) + + Error() RequestError + + Do(ctx context.Context) (*http.Response, liberr.Error) + DoParse(ctx context.Context, model interface{}, validStatus ...int) liberr.Error +} + +func New(fct FctHttpClient) Request { + return &request{ + s: sync.Mutex{}, + f: fct, + u: nil, + h: make(url.Values), + p: make(url.Values), + b: bytes.NewBuffer(make([]byte, 0)), + m: http.MethodGet, + e: nil, + } +} diff --git a/httpcli/model.go b/httpcli/model.go new file mode 100644 index 0000000..04ae3f7 --- /dev/null +++ b/httpcli/model.go @@ -0,0 +1,397 @@ +/* + * MIT License + * + * Copyright (c) 2020 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package httpcli + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "io" + "net/http" + "net/url" + "path/filepath" + "strings" + "sync" + "time" + + libtls "github.com/nabbar/golib/certificates" + + liberr "github.com/nabbar/golib/errors" +) + +type request struct { + s sync.Mutex + + f FctHttpClient + u *url.URL + h url.Values + p url.Values + b io.Reader + m string + e *requestError +} + +func (r *request) Clone() Request { + n := &request{ + s: sync.Mutex{}, + f: r.f, + u: &url.URL{ + Scheme: r.u.Scheme, + Opaque: r.u.Opaque, + User: r.u.User, + Host: r.u.Host, + Path: r.u.Path, + RawPath: r.u.RawPath, + ForceQuery: r.u.ForceQuery, + RawQuery: r.u.RawQuery, + Fragment: r.u.Fragment, + RawFragment: r.u.RawFragment, + }, + h: make(url.Values), + p: make(url.Values), + b: r.b, + m: http.MethodGet, + e: nil, + } + + for k, v := range r.h { + n.h[k] = v + } + + for k, v := range r.p { + n.p[k] = v + } + + return n +} + +func (r *request) New() Request { + return &request{ + s: sync.Mutex{}, + f: r.f, + u: nil, + h: make(url.Values), + p: make(url.Values), + b: bytes.NewBuffer(make([]byte, 0)), + m: http.MethodGet, + e: nil, + } +} + +func (r *request) _GetClient() *http.Client { + if r.f != nil { + if c := r.f(); c != nil { + return c + } + } + + return &http.Client{} +} + +func (r *request) SetClient(fct FctHttpClient) { + r.s.Lock() + defer r.s.Unlock() + r.f = fct +} + +func (r *request) UseClientPackage(ip string, tls libtls.TLSConfig, http2Tr bool, GlobalTimeout time.Duration) { + r.s.Lock() + defer r.s.Unlock() + + if tls == nil { + tls = libtls.Default + } + + if len(ip) > 0 { + r.f = func() *http.Client { + if h, e := GetClientTlsForceIp(NetworkTCP, ip, r.u.Hostname(), tls, http2Tr, GlobalTimeout); e == nil { + return h + } else if h, e = GetClientTimeout(r.u.Hostname(), false, GlobalTimeout); e == nil { + return h + } + + return &http.Client{} + } + } else { + r.f = func() *http.Client { + if h, e := GetClientTls(r.u.Hostname(), tls, http2Tr, GlobalTimeout); e == nil { + return h + } else if h, e = GetClientTls(r.u.Hostname(), tls, false, GlobalTimeout); e == nil { + return h + } + + return &http.Client{} + } + } +} + +func (r *request) Endpoint(uri string) error { + if u, e := url.Parse(uri); e != nil { + return e + } else { + r.s.Lock() + defer r.s.Unlock() + r.u = u + } + + return nil +} + +func (r *request) SetUrl(u *url.URL) { + r.s.Lock() + defer r.s.Unlock() + r.u = u +} + +func (r *request) GetUrl() *url.URL { + r.s.Lock() + defer r.s.Unlock() + return r.u +} + +func (r *request) AddPath(path string) { + r.s.Lock() + defer r.s.Unlock() + + if r.u == nil { + return + } + + if strings.HasPrefix(path, "/") { + path = strings.TrimPrefix(path, "/") + } + + if strings.HasSuffix(path, "/") { + path = strings.TrimSuffix(path, "/") + } + + r.u.Path = filepath.Join(r.u.Path, path) +} + +func (r *request) AddParams(key, val string) { + r.s.Lock() + defer r.s.Unlock() + + if len(r.p) < 1 { + r.p = make(url.Values) + } + + r.p.Set(key, val) +} + +func (r *request) AuthBearer(token string) { + r.Header("Authorization", "Bearer "+token) +} + +func (r *request) AuthBasic(user, pass string) { + r.Header("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(user+":"+pass))) +} + +func (r *request) ContentType(content string) { + r.Header("Content-Type", content) +} + +func (r *request) Header(key, value string) { + r.s.Lock() + defer r.s.Unlock() + + if len(r.h) < 1 { + r.h = make(url.Values) + } + + r.h.Set(key, value) +} + +func (r *request) Method(mtd string) { + r.s.Lock() + defer r.s.Unlock() + + r.m = mtd +} + +func (r *request) RequestJson(body interface{}) error { + if p, e := json.Marshal(body); e != nil { + return e + } else { + r.s.Lock() + defer r.s.Unlock() + + r.b = bytes.NewBuffer(p) + } + + r.ContentType("application/json") + return nil +} + +func (r *request) RequestReader(body io.Reader) { + r.s.Lock() + defer r.s.Unlock() + + r.b = body +} + +func (r *request) Error() RequestError { + r.s.Lock() + defer r.s.Unlock() + + return r.e +} + +func (r *request) Do(ctx context.Context) (*http.Response, liberr.Error) { + r.s.Lock() + defer r.s.Unlock() + + if r.m == "" || r.u == nil || r.u.String() == "" { + return nil, ErrorParamsInvalid.Error(nil) + } + + var ( + e error + req *http.Request + rsp *http.Response + err liberr.Error + ) + + r.e = nil + + req, err = r._MakeRequest(ctx) + if err != nil { + return nil, err + } + + rsp, e = r._GetClient().Do(req) + + if e != nil { + r.e = &requestError{ + c: 0, + s: "", + b: nil, + e: e, + } + return nil, ErrorSendRequest.ErrorParent(e) + } + + return rsp, nil +} + +func (r *request) _MakeRequest(ctx context.Context) (*http.Request, liberr.Error) { + var ( + req *http.Request + err error + ) + + req, err = http.NewRequestWithContext(ctx, r.m, r.u.String(), r.b) + + if err != nil { + return nil, ErrorCreateRequest.ErrorParent(err) + } + + if len(r.h) > 0 { + for k := range r.h { + req.Header.Set(k, r.h.Get(k)) + } + } + + q := req.URL.Query() + for k := range r.p { + q.Add(k, r.p.Get(k)) + } + req.URL.RawQuery = q.Encode() + + return req, nil +} + +func (r *request) DoParse(ctx context.Context, model interface{}, validStatus ...int) liberr.Error { + var ( + e error + b = bytes.NewBuffer(make([]byte, 0)) + + err liberr.Error + rsp *http.Response + ) + + if rsp, err = r.Do(ctx); err != nil { + return err + } else if rsp == nil { + return ErrorResponseInvalid.Error(nil) + } + + defer func() { + if !rsp.Close && rsp.Body != nil { + _ = rsp.Body.Close() + } + }() + + if rsp.Body != nil { + if _, e = io.Copy(b, rsp.Body); e != nil { + r.e = &requestError{ + c: rsp.StatusCode, + s: rsp.Status, + b: b, + e: e, + } + return ErrorResponseLoadBody.ErrorParent(e) + } + } + + if !r._IsValidCode(validStatus, rsp.StatusCode) { + r.e = &requestError{ + c: rsp.StatusCode, + s: rsp.Status, + b: b, + e: nil, + } + return ErrorResponseStatus.Error(nil) + } + + if e = json.Unmarshal(b.Bytes(), model); e != nil { + r.e = &requestError{ + c: rsp.StatusCode, + s: rsp.Status, + b: b, + e: e, + } + return ErrorResponseUnmarshall.ErrorParent(e) + } + + return nil +} + +func (r *request) _IsValidCode(listValid []int, statusCode int) bool { + if len(listValid) < 1 { + return true + } + + for _, c := range listValid { + if c == statusCode { + return true + } + } + + return false +} diff --git a/httpcli/network.go b/httpcli/network.go new file mode 100644 index 0000000..9f4796b --- /dev/null +++ b/httpcli/network.go @@ -0,0 +1,58 @@ +/* + * MIT License + * + * Copyright (c) 2020 Nicolas JUHEL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + */ + +package httpcli + +import "strings" + +type Network uint8 + +const ( + NetworkTCP Network = iota + NetworkUDP +) + +func GetNetworkFromString(str string) Network { + switch strings.ToLower(str) { + case NetworkUDP.Code(): + return NetworkUDP + default: + return NetworkTCP + } +} + +func (n Network) String() string { + switch n { + case NetworkUDP: + return "UDP" + default: + return "TCP" + } +} + +func (n Network) Code() string { + return strings.ToLower(n.String()) +}