diff --git a/client.go b/client.go index de5120e..7666ffa 100644 --- a/client.go +++ b/client.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "encoding/xml" + "errors" "fmt" "io" "mime" @@ -47,6 +48,10 @@ type Client struct { type ClientOpt func(*Client) error +// Callback for json stream events, return an error if you want to stop streaming +// with an error and io.EOF if you want to stop streaming and return success +type JsonStreamCallback func(v any) error + /////////////////////////////////////////////////////////////////////////////// // GLOBALS @@ -302,8 +307,20 @@ func do(client *http.Client, req *http.Request, accept string, strict bool, out // Decode the body switch mimetype { case ContentTypeJson: - if err := json.NewDecoder(response.Body).Decode(out); err != nil { - return err + // JSON decode is streamable + dec := json.NewDecoder(response.Body) + for { + if err := dec.Decode(out); err == io.EOF { + break + } else if err != nil { + return err + } else if reqopts.jsonStreamCallback != nil { + if err := reqopts.jsonStreamCallback(out); errors.Is(err, io.EOF) { + break + } else if err != nil { + return err + } + } } case ContentTypeTextStream: if err := NewTextStream().Decode(response.Body, reqopts.textStreamCallback); err != nil { diff --git a/cmd/api/auth.go b/cmd/api/auth.go new file mode 100644 index 0000000..6f3e41d --- /dev/null +++ b/cmd/api/auth.go @@ -0,0 +1,90 @@ +package main + +import ( + "context" + "time" + + // Packages + tablewriter "github.com/djthorpe/go-tablewriter" + client "github.com/mutablelogic/go-client" + auth "github.com/mutablelogic/go-server/pkg/handler/auth/client" +) + +var ( + authClient *auth.Client + authName = "tokenauth" + authDuration time.Duration +) + +func authRegister(flags *Flags) { + // Register flags + flags.String(authName, "tokenauth-endpoint", "${TOKENAUTH_ENDPOINT}", "tokenauth endpoint (ie, http://host/api/auth/)") + flags.String(authName, "tokenauth-token", "${TOKENAUTH_TOKEN}", "tokenauth token") + flags.Duration(authName, "expiry", 0, "token expiry duration") + + // Register commands + flags.Register(Cmd{ + Name: authName, + Description: "Manage token authentication", + Parse: authParse, + Fn: []Fn{ + // Default caller + {Call: authList, Description: "List authentication tokens"}, + {Name: "list", Call: authList, Description: "List authentication tokens"}, + {Name: "create", Call: authCreate, Description: "Create a token", MinArgs: 1}, + {Name: "delete", Call: authDelete, Description: "Delete a token", MinArgs: 1, MaxArgs: 1}, + }, + }) +} + +func authParse(flags *Flags, opts ...client.ClientOpt) error { + endpoint := flags.GetString("tokenauth-endpoint") + if token := flags.GetString("tokenauth-token"); token != "" { + opts = append(opts, client.OptReqToken(client.Token{ + Scheme: "Bearer", + Value: token, + })) + } + + if duration := flags.GetString("expiry"); duration != "" { + if d, err := time.ParseDuration(duration); err != nil { + return err + } else { + authDuration = d + } + } + + if client, err := auth.New(endpoint, opts...); err != nil { + return err + } else { + authClient = client + } + return nil +} + +func authList(_ context.Context, w *tablewriter.Writer, _ []string) error { + tokens, err := authClient.List() + if err != nil { + return err + } + return w.Write(tokens) +} + +func authCreate(_ context.Context, w *tablewriter.Writer, params []string) error { + name := params[0] + scopes := params[1:] + token, err := authClient.Create(name, authDuration, scopes...) + if err != nil { + return err + } + return w.Write(token) +} + +func authDelete(ctx context.Context, w *tablewriter.Writer, params []string) error { + name := params[0] + err := authClient.Delete(name) + if err != nil { + return err + } + return authList(ctx, w, nil) +} diff --git a/cmd/api/main.go b/cmd/api/main.go index cb0f264..1c85e61 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -20,6 +20,7 @@ func main() { // Register commands anthropicRegister(flags) + authRegister(flags) bwRegister(flags) elRegister(flags) haRegister(flags) diff --git a/cmd/api/nginx.go_old b/cmd/api/nginx.go_old new file mode 100644 index 0000000..70d0525 --- /dev/null +++ b/cmd/api/nginx.go_old @@ -0,0 +1,48 @@ +package main + +import ( + "context" + + // Packages + tablewriter "github.com/djthorpe/go-tablewriter" + client "github.com/mutablelogic/go-client" + nginx "github.com/mutablelogic/go-server/pkg/handler/nginx/client" +) + +var ( + nginxClient *nginx.Client + nginxName = "nginx" + nginxEndpoint string +) + +func nginxRegister(flags *Flags) { + flags.Register(Cmd{ + Name: nginxName, + Description: "Manage nginx instances", + Parse: nginxParse, + Fn: []Fn{ + // Default caller + {Call: nginxGetVersion, Description: "Get the nginx version that is running"}, + }, + }) +} + +func nginxParse(flags *Flags, opts ...client.ClientOpt) error { + // Register flags + flags.String(nginxName, "nginx-endpoint", "${NGINX_ENDPOINT}", "nginx endpoint") + + if client, err := nginx.New(nginxEndpoint, opts...); err != nil { + return err + } else { + nginxClient = client + } + return nil +} + +func nginxGetVersion(_ context.Context, w *tablewriter.Writer, _ []string) error { + version, _, err := nginxClient.Health() + if err != nil { + return err + } + return w.Write(version) +} diff --git a/go.mod b/go.mod index 255a157..0c6ddc2 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,10 @@ toolchain go1.22.3 require ( github.com/andreburgaud/crypt2go v1.5.0 github.com/djthorpe/go-errors v1.0.3 - github.com/djthorpe/go-tablewriter v0.0.6 + github.com/djthorpe/go-tablewriter v0.0.7 github.com/go-audio/audio v1.0.0 github.com/go-audio/wav v1.1.0 + github.com/mutablelogic/go-server v1.4.7 github.com/stretchr/testify v1.9.0 github.com/xdg-go/pbkdf2 v1.0.0 golang.org/x/crypto v0.23.0 diff --git a/go.sum b/go.sum index f4273f0..73a10fd 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ 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/djthorpe/go-errors v1.0.3 h1:GZeMPkC1mx2vteXLI/gvxZS0Ee9zxzwD1mcYyKU5jD0= github.com/djthorpe/go-errors v1.0.3/go.mod h1:HtfrZnMd6HsX75Mtbv9Qcnn0BqOrrFArvCaj3RMnZhY= -github.com/djthorpe/go-tablewriter v0.0.6 h1:iGi1eln0KEknJUH9AtPeTeOUcioilDRs7QruyRcmtWM= -github.com/djthorpe/go-tablewriter v0.0.6/go.mod h1:LL+Dxaepm8Q0qUVD9EB6+d0xr7I7OgQYEfrugI8fBUA= +github.com/djthorpe/go-tablewriter v0.0.7 h1:jnNsJDjjLLCt0OAqB5DzGZN7V3beT1IpNMQ8GcOwZDU= +github.com/djthorpe/go-tablewriter v0.0.7/go.mod h1:NVBvytpL+6fHfCKn0+3lSi15/G3A1HWf2cLNeHg6YBg= github.com/go-audio/audio v1.0.0 h1:zS9vebldgbQqktK4H0lUqWrG8P0NxCJVqcj7ZpNnwd4= github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= github.com/go-audio/riff v1.0.0 h1:d8iCGbDvox9BfLagY94fBynxSPHO80LmZCaOsmKxokA= @@ -14,6 +14,8 @@ github.com/go-audio/wav v1.1.0 h1:jQgLtbqBzY7G+BM8fXF7AHUk1uHUviWS4X39d5rsL2g= github.com/go-audio/wav v1.1.0/go.mod h1:mpe9qfwbScEbkd8uybLuIpTgHyrISw/OTuvjUW2iGtE= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mutablelogic/go-server v1.4.7 h1:NpzG30f/D50Xbwr96dA6uiapyr4QHBziSanc/q/LR7k= +github.com/mutablelogic/go-server v1.4.7/go.mod h1:wrrDg863hlv5/DUpSG/Pb4k9LiSYO7VxRgLPiMhrE6M= 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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= diff --git a/pkg/multipart/multipart.go b/pkg/multipart/multipart.go index a6d91dc..55d064d 100644 --- a/pkg/multipart/multipart.go +++ b/pkg/multipart/multipart.go @@ -55,7 +55,7 @@ func NewMultipartEncoder(w io.Writer) *Encoder { } } -// NewFormEncoder creates a new encoder object, whichwrites +// NewFormEncoder creates a new encoder object, which writes // application/x-www-form-urlencoded to the io.Writer func NewFormEncoder(w io.Writer) *Encoder { return &Encoder{ diff --git a/requestopts.go b/requestopts.go index b026301..46729b2 100644 --- a/requestopts.go +++ b/requestopts.go @@ -17,6 +17,7 @@ type requestOpts struct { *http.Request noTimeout bool // OptNoTimeout textStreamCallback TextStreamCallback // OptTextStreamCallback + jsonStreamCallback JsonStreamCallback // OptJsonStreamCallback } type RequestOpt func(*requestOpts) error @@ -103,3 +104,11 @@ func OptTextStreamCallback(fn TextStreamCallback) RequestOpt { return nil } } + +// OptJsonStreamCallback is called for each decoded JSON event +func OptJsonStreamCallback(fn JsonStreamCallback) RequestOpt { + return func(r *requestOpts) error { + r.jsonStreamCallback = fn + return nil + } +}