client.go

 1package client
 2
 3import (
 4	"context"
 5	"encoding/json"
 6	"fmt"
 7	"net"
 8	"net/http"
 9	"path/filepath"
10	"time"
11
12	"github.com/charmbracelet/crush/internal/config"
13	"github.com/charmbracelet/crush/internal/server"
14)
15
16// Client represents an RPC client connected to a Crush server.
17type Client struct {
18	h    *http.Client
19	id   string
20	path string
21}
22
23// DefaultClient creates a new [Client] connected to the default server address.
24func DefaultClient(path string) (*Client, error) {
25	return NewClient(path, "unix", server.DefaultAddr())
26}
27
28// NewClient creates a new [Client] connected to the server at the given
29// network and address.
30func NewClient(path, network, address string) (*Client, error) {
31	var p http.Protocols
32	p.SetHTTP1(true)
33	p.SetUnencryptedHTTP2(true)
34	tr := http.DefaultTransport.(*http.Transport).Clone()
35	tr.Protocols = &p
36	tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
37		d := net.Dialer{
38			Timeout:   30 * time.Second,
39			KeepAlive: 30 * time.Second,
40		}
41		return d.DialContext(ctx, network, address)
42	}
43	h := &http.Client{
44		Transport: tr,
45		Timeout:   0, // we need this to be 0 for long-lived connections and SSE streams
46	}
47	return &Client{
48		h:    h,
49		path: filepath.Clean(path),
50	}, nil
51}
52
53// ID returns the client's instance unique identifier.
54func (c *Client) ID() string {
55	return c.id
56}
57
58// SetID sets the client's instance unique identifier.
59func (c *Client) SetID(id string) {
60	c.id = id
61}
62
63// Path returns the client's instance filesystem path.
64func (c *Client) Path() string {
65	return c.path
66}
67
68// GetGlobalConfig retrieves the server's configuration.
69func (c *Client) GetGlobalConfig() (*config.Config, error) {
70	var cfg config.Config
71	rsp, err := c.h.Get("http://localhost/v1/config")
72	if err != nil {
73		return nil, err
74	}
75	defer rsp.Body.Close()
76	if err := json.NewDecoder(rsp.Body).Decode(&cfg); err != nil {
77		return nil, err
78	}
79	return &cfg, nil
80}
81
82// Health checks the server's health status.
83func (c *Client) Health() error {
84	rsp, err := c.h.Get("http://localhost/v1/health")
85	if err != nil {
86		return err
87	}
88	defer rsp.Body.Close()
89	if rsp.StatusCode != http.StatusOK {
90		return fmt.Errorf("server health check failed: %s", rsp.Status)
91	}
92	return nil
93}