Skip to content

Commit

Permalink
fix: Adjust client side timeouts #277
Browse files Browse the repository at this point in the history
  • Loading branch information
simulot committed Jun 2, 2024
1 parent 80e102c commit 34100e1
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 31 deletions.
34 changes: 19 additions & 15 deletions cmd/shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"os"
"runtime"
"strings"
"time"

"github.com/simulot/immich-go/helpers/configuration"
"github.com/simulot/immich-go/helpers/fileevent"
Expand All @@ -22,20 +23,21 @@ import (

// SharedFlags collect all parameters that are common to all commands
type SharedFlags struct {
ConfigurationFile string // Path to the configuration file to use
Server string // Immich server address (http://<your-ip>:2283/api or https://<your-domain>/api)
API string // Immich api endpoint (http://container_ip:3301)
Key string // API Key
DeviceUUID string // Set a device UUID
APITrace bool // Enable API call traces
NoLogColors bool // Disable log colors
LogLevel string // Indicate the log level (string)
Level slog.Level
Debug bool // Enable the debug mode
TimeZone string // Override default TZ
SkipSSL bool // Skip SSL Verification
NoUI bool // Disable user interface
JSONLog bool // Enable JSON structured log
ConfigurationFile string // Path to the configuration file to use
Server string // Immich server address (http://<your-ip>:2283/api or https://<your-domain>/api)
API string // Immich api endpoint (http://container_ip:3301)
Key string // API Key
DeviceUUID string // Set a device UUID
APITrace bool // Enable API call traces
NoLogColors bool // Disable log colors
LogLevel string // Indicate the log level (string)
Level slog.Level // Set the log level
Debug bool // Enable the debug mode
TimeZone string // Override default TZ
SkipSSL bool // Skip SSL Verification
ClientTimeout time.Duration // Set the client request timeout
NoUI bool // Disable user interface
JSONLog bool // Enable JSON structured log

Immich immich.ImmichInterface // Immich client
Log *slog.Logger // Logger
Expand All @@ -55,6 +57,7 @@ func (app *SharedFlags) InitSharedFlags() {
app.LogLevel = "INFO"
app.NoUI = false
app.JSONLog = false
app.ClientTimeout = 5 * time.Minute
}

// SetFlag add common flags to a flagset
Expand All @@ -73,6 +76,7 @@ func (app *SharedFlags) SetFlags(fs *flag.FlagSet) {
fs.StringVar(&app.TimeZone, "time-zone", app.TimeZone, "Override the system time zone")
fs.BoolFunc("skip-verify-ssl", "Skip SSL verification", myflag.BoolFlagFn(&app.SkipSSL, app.SkipSSL))
fs.BoolFunc("no-ui", "Disable the user interface", myflag.BoolFlagFn(&app.NoUI, app.NoUI))
fs.Func("client-timeout", "Set server calls timeout, default 1m", myflag.DurationFlagFn(&app.ClientTimeout, app.ClientTimeout))
}

func (app *SharedFlags) Start(ctx context.Context) error {
Expand Down Expand Up @@ -150,7 +154,7 @@ func (app *SharedFlags) Start(ctx context.Context) error {
}
app.Log.Info("Connection to the server " + app.Server)

app.Immich, err = immich.NewImmichClient(app.Server, app.Key, app.SkipSSL)
app.Immich, err = immich.NewImmichClient(app.Server, app.Key, immich.OptionVerifySSL(app.SkipSSL), immich.OptionConnectionTimeout(app.ClientTimeout))
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/upload/e2e_upload_folder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func runCase(t *testing.T, tc testCase) {
}

ctx := context.Background()
ic, err := immich.NewImmichClient(host, key, false)
ic, err := immich.NewImmichClient(host, key)
if err != nil {
t.Error(err)
return
Expand Down
20 changes: 20 additions & 0 deletions helpers/myflag/duration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package myflag

import (
"fmt"
"strings"
"time"
)

func DurationFlagFn(flag *time.Duration, defaultValue time.Duration) func(string) error {
*flag = defaultValue
return func(v string) error {
v = strings.ToLower(v)
d, err := time.ParseDuration(v)
if err != nil {
return fmt.Errorf("can't parse the duration parameter: %w", err)
}
*flag = d
return nil
}
}
2 changes: 1 addition & 1 deletion immich/call_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func TestCall(t *testing.T) {
server := httptest.NewServer(&tst.server)
defer server.Close()
ctx := context.Background()
ic, err := NewImmichClient(server.URL, "1234", false)
ic, err := NewImmichClient(server.URL, "1234")
if err != nil {
t.Fail()
return
Expand Down
54 changes: 40 additions & 14 deletions immich/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Immich API documentation https://documentation.immich.app/docs/api/introduction

type ImmichClient struct {
client *http.Client
roundTripper *http.Transport
endPoint string // Server API url
key string // User KEY
DeviceUUID string // Device
Expand All @@ -43,8 +44,24 @@ func (ic *ImmichClient) SupportedMedia() SupportedMedia {
return ic.supportedMediaTypes
}

type clientOption func(ic *ImmichClient) error

func OptionVerifySSL(verify bool) clientOption {
return func(ic *ImmichClient) error {
ic.roundTripper.TLSClientConfig.InsecureSkipVerify = verify
return nil
}
}

func OptionConnectionTimeout(d time.Duration) clientOption {
return func(ic *ImmichClient) error {
ic.client.Timeout = d
return nil
}
}

// Create a new ImmichClient
func NewImmichClient(endPoint string, key string, sslVerify bool) (*ImmichClient, error) {
func NewImmichClient(endPoint string, key string, options ...clientOption) (*ImmichClient, error) {
var err error
deviceUUID, err := os.Hostname()
if err != nil {
Expand All @@ -54,27 +71,36 @@ func NewImmichClient(endPoint string, key string, sslVerify bool) (*ImmichClient
// Create a custom HTTP client with SSL verification disabled
// Add timeouts for #219
// Info at https://www.loginradius.com/blog/engineering/tune-the-go-http-client-for-high-performance/
transportOptions := &http.Transport{
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: sslVerify},
MaxIdleConnsPerHost: 100,
MaxConnsPerHost: 100,
}
tlsClient := &http.Client{
Timeout: time.Second * 10,
Transport: transportOptions,
}
// https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/
// ![image](https://blog.cloudflare.com/content/images/2016/06/Timeouts-002.png)

ic := ImmichClient{
endPoint: endPoint + "/api",
endPoint: endPoint + "/api",
roundTripper: &http.Transport{
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
MaxIdleConnsPerHost: 100,
MaxConnsPerHost: 100,
},
key: key,
client: tlsClient,
DeviceUUID: deviceUUID,
Retries: 1,
RetriesDelay: time.Second * 1,
}

ic.client = &http.Client{
Timeout: time.Second * 60,
Transport: ic.roundTripper,
}

for _, fn := range options {
err := fn(&ic)
if err != nil {
return nil, err
}
}

return &ic, nil
}

Expand Down
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ immich-go -server URL -key KEY -general_options COMMAND -command_options... {fil
| `-server=URL` | URL of the Immich service, example http://<your-ip>:2283 or https://your-domain | |
| `-api=URL` | URL of the Immich api endpoint (http://container_ip:3301) | |
| `-device-uuid=VALUE` | Force the device identification | `$HOSTNAME` |
| `-client-timeout=duration` | Set the timeout for server calls. Ex 60s, 1m30s, 500ms,.... | `5m` |
| `-skip-verify-ssl` | Skip SSL verification for use with self-signed certificates | `false` |
| `-key=KEY` | A key generated by the user. Uploaded photos will belong to the key's owner. | |
| `-no-colors-log` | Remove color codes from logs. | `TRUE` on Windows, `FALSE` otherwise |
Expand Down

0 comments on commit 34100e1

Please sign in to comment.