175 lines
4.1 KiB
Go
175 lines
4.1 KiB
Go
package diagnostic
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
|
|
"github.com/cloudflare/cloudflared/logger"
|
|
)
|
|
|
|
type httpClient struct {
|
|
http.Client
|
|
baseURL *url.URL
|
|
}
|
|
|
|
func NewHTTPClient() *httpClient {
|
|
httpTransport := http.Transport{
|
|
TLSHandshakeTimeout: defaultTimeout,
|
|
ResponseHeaderTimeout: defaultTimeout,
|
|
}
|
|
|
|
return &httpClient{
|
|
http.Client{
|
|
Transport: &httpTransport,
|
|
Timeout: defaultTimeout,
|
|
},
|
|
nil,
|
|
}
|
|
}
|
|
|
|
func (client *httpClient) SetBaseURL(baseURL *url.URL) {
|
|
client.baseURL = baseURL
|
|
}
|
|
|
|
func (client *httpClient) GET(ctx context.Context, endpoint string) (*http.Response, error) {
|
|
if client.baseURL == nil {
|
|
return nil, ErrNoBaseUrl
|
|
}
|
|
url := client.baseURL.JoinPath(endpoint)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating GET request: %w", err)
|
|
}
|
|
|
|
req.Header.Add("Accept", "application/json;version=1")
|
|
|
|
response, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error GET request: %w", err)
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
type LogConfiguration struct {
|
|
logFile string
|
|
logDirectory string
|
|
uid int // the uid of the user that started cloudflared
|
|
}
|
|
|
|
func (client *httpClient) GetLogConfiguration(ctx context.Context) (*LogConfiguration, error) {
|
|
response, err := client.GET(ctx, configurationEndpoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer response.Body.Close()
|
|
|
|
var data map[string]string
|
|
if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
|
|
return nil, fmt.Errorf("failed to decode body: %w", err)
|
|
}
|
|
|
|
uidStr, exists := data[configurationKeyUID]
|
|
if !exists {
|
|
return nil, ErrKeyNotFound
|
|
}
|
|
|
|
uid, err := strconv.Atoi(uidStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error convertin pid to int: %w", err)
|
|
}
|
|
|
|
logFile, exists := data[logger.LogFileFlag]
|
|
if exists {
|
|
return &LogConfiguration{logFile, "", uid}, nil
|
|
}
|
|
|
|
logDirectory, exists := data[logger.LogDirectoryFlag]
|
|
if exists {
|
|
return &LogConfiguration{"", logDirectory, uid}, nil
|
|
}
|
|
|
|
// No log configured may happen when cloudflared is executed as a managed service or
|
|
// when containerized
|
|
return &LogConfiguration{"", "", uid}, nil
|
|
}
|
|
|
|
func (client *httpClient) GetMemoryDump(ctx context.Context, writer io.Writer) error {
|
|
response, err := client.GET(ctx, memoryDumpEndpoint)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return copyToWriter(response, writer)
|
|
}
|
|
|
|
func (client *httpClient) GetGoroutineDump(ctx context.Context, writer io.Writer) error {
|
|
response, err := client.GET(ctx, goroutineDumpEndpoint)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return copyToWriter(response, writer)
|
|
}
|
|
|
|
func (client *httpClient) GetTunnelState(ctx context.Context) (*TunnelState, error) {
|
|
response, err := client.GET(ctx, tunnelStateEndpoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer response.Body.Close()
|
|
|
|
var state TunnelState
|
|
if err := json.NewDecoder(response.Body).Decode(&state); err != nil {
|
|
return nil, fmt.Errorf("failed to decode body: %w", err)
|
|
}
|
|
|
|
return &state, nil
|
|
}
|
|
|
|
func (client *httpClient) GetSystemInformation(ctx context.Context, writer io.Writer) error {
|
|
response, err := client.GET(ctx, systemInformationEndpoint)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return copyToWriter(response, writer)
|
|
}
|
|
|
|
func (client *httpClient) GetMetrics(ctx context.Context, writer io.Writer) error {
|
|
response, err := client.GET(ctx, metricsEndpoint)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return copyToWriter(response, writer)
|
|
}
|
|
|
|
func copyToWriter(response *http.Response, writer io.Writer) error {
|
|
defer response.Body.Close()
|
|
|
|
_, err := io.Copy(writer, response.Body)
|
|
if err != nil {
|
|
return fmt.Errorf("error writing metrics: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type HTTPClient interface {
|
|
GetLogConfiguration(ctx context.Context) (*LogConfiguration, error)
|
|
GetMemoryDump(ctx context.Context, writer io.Writer) error
|
|
GetGoroutineDump(ctx context.Context, writer io.Writer) error
|
|
GetTunnelState(ctx context.Context) (*TunnelState, error)
|
|
GetSystemInformation(ctx context.Context, writer io.Writer) error
|
|
GetMetrics(ctx context.Context, writer io.Writer) error
|
|
}
|