|
|
@ -16,6 +16,7 @@ package caddycmd |
|
|
|
|
|
|
|
import ( |
|
|
|
"bytes" |
|
|
|
"context" |
|
|
|
"crypto/rand" |
|
|
|
"encoding/json" |
|
|
|
"fmt" |
|
|
@ -276,24 +277,9 @@ func cmdRun(fl Flags) (int, error) { |
|
|
|
func cmdStop(fl Flags) (int, error) { |
|
|
|
stopCmdAddrFlag := fl.String("address") |
|
|
|
|
|
|
|
adminAddr := caddy.DefaultAdminListen |
|
|
|
if stopCmdAddrFlag != "" { |
|
|
|
adminAddr = stopCmdAddrFlag |
|
|
|
} |
|
|
|
stopEndpoint := fmt.Sprintf("http://%s/stop", adminAddr) |
|
|
|
|
|
|
|
req, err := http.NewRequest(http.MethodPost, stopEndpoint, nil) |
|
|
|
if err != nil { |
|
|
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("making request: %v", err) |
|
|
|
} |
|
|
|
req.Header.Set("Origin", adminAddr) |
|
|
|
|
|
|
|
err = apiRequest(req) |
|
|
|
err := apiRequest(stopCmdAddrFlag, http.MethodPost, "/stop", nil) |
|
|
|
if err != nil { |
|
|
|
caddy.Log().Warn("failed using API to stop instance", |
|
|
|
zap.String("endpoint", stopEndpoint), |
|
|
|
zap.Error(err), |
|
|
|
) |
|
|
|
caddy.Log().Warn("failed using API to stop instance", zap.Error(err)) |
|
|
|
return caddy.ExitCodeFailedStartup, err |
|
|
|
} |
|
|
|
|
|
|
@ -314,7 +300,7 @@ func cmdReload(fl Flags) (int, error) { |
|
|
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("no config file to load") |
|
|
|
} |
|
|
|
|
|
|
|
// get the address of the admin listener and craft endpoint URL
|
|
|
|
// get the address of the admin listener; use flag if specified
|
|
|
|
adminAddr := reloadCmdAddrFlag |
|
|
|
if adminAddr == "" && len(config) > 0 { |
|
|
|
var tmpStruct struct { |
|
|
@ -327,20 +313,8 @@ func cmdReload(fl Flags) (int, error) { |
|
|
|
} |
|
|
|
adminAddr = tmpStruct.Admin.Listen |
|
|
|
} |
|
|
|
if adminAddr == "" { |
|
|
|
adminAddr = caddy.DefaultAdminListen |
|
|
|
} |
|
|
|
loadEndpoint := fmt.Sprintf("http://%s/load", adminAddr) |
|
|
|
|
|
|
|
// prepare the request to update the configuration
|
|
|
|
req, err := http.NewRequest(http.MethodPost, loadEndpoint, bytes.NewReader(config)) |
|
|
|
if err != nil { |
|
|
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("making request: %v", err) |
|
|
|
} |
|
|
|
req.Header.Set("Content-Type", "application/json") |
|
|
|
req.Header.Set("Origin", adminAddr) |
|
|
|
|
|
|
|
err = apiRequest(req) |
|
|
|
err = apiRequest(adminAddr, http.MethodPost, "/load", bytes.NewReader(config)) |
|
|
|
if err != nil { |
|
|
|
return caddy.ExitCodeFailedStartup, fmt.Errorf("sending configuration to instance: %v", err) |
|
|
|
} |
|
|
@ -645,8 +619,62 @@ commands: |
|
|
|
return caddy.ExitCodeSuccess, nil |
|
|
|
} |
|
|
|
|
|
|
|
func apiRequest(req *http.Request) error { |
|
|
|
resp, err := http.DefaultClient.Do(req) |
|
|
|
// apiRequest makes an API request to the endpoint adminAddr with the
|
|
|
|
// given HTTP method and request URI. If body is non-nil, it will be
|
|
|
|
// assumed to be Content-Type application/json.
|
|
|
|
func apiRequest(adminAddr, method, uri string, body io.Reader) error { |
|
|
|
// parse the admin address
|
|
|
|
if adminAddr == "" { |
|
|
|
adminAddr = caddy.DefaultAdminListen |
|
|
|
} |
|
|
|
parsedAddr, err := caddy.ParseNetworkAddress(adminAddr) |
|
|
|
if err != nil || parsedAddr.PortRangeSize() > 1 { |
|
|
|
return fmt.Errorf("invalid admin address %s: %v", adminAddr, err) |
|
|
|
} |
|
|
|
origin := parsedAddr.JoinHostPort(0) |
|
|
|
if parsedAddr.IsUnixNetwork() { |
|
|
|
origin = "unixsocket" // hack so that http.NewRequest() is happy
|
|
|
|
} |
|
|
|
|
|
|
|
// form the request
|
|
|
|
req, err := http.NewRequest(method, "http://"+origin+uri, body) |
|
|
|
if err != nil { |
|
|
|
return fmt.Errorf("making request: %v", err) |
|
|
|
} |
|
|
|
if parsedAddr.IsUnixNetwork() { |
|
|
|
// When listening on a unix socket, the admin endpoint doesn't
|
|
|
|
// accept any Host header because there is no host:port for
|
|
|
|
// a unix socket's address. The server's host check is fairly
|
|
|
|
// strict for security reasons, so we don't allow just any
|
|
|
|
// Host header. For unix sockets, the Host header must be
|
|
|
|
// empty. Unfortunately, Go makes it impossible to make HTTP
|
|
|
|
// requests with an empty Host header... except with this one
|
|
|
|
// weird trick. (Hopefully they don't fix it. It's already
|
|
|
|
// hard enough to use HTTP over unix sockets.)
|
|
|
|
//
|
|
|
|
// An equivalent curl command would be something like:
|
|
|
|
// $ curl --unix-socket caddy.sock http:/:$REQUEST_URI
|
|
|
|
req.URL.Host = " " |
|
|
|
req.Host = "" |
|
|
|
} else { |
|
|
|
req.Header.Set("Origin", origin) |
|
|
|
} |
|
|
|
if body != nil { |
|
|
|
req.Header.Set("Content-Type", "application/json") |
|
|
|
} |
|
|
|
|
|
|
|
// make an HTTP client that dials our network type, since admin
|
|
|
|
// endpoints aren't always TCP, which is what the default transport
|
|
|
|
// expects; reuse is not of particular concern here
|
|
|
|
client := http.Client{ |
|
|
|
Transport: &http.Transport{ |
|
|
|
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { |
|
|
|
return net.Dial(parsedAddr.Network, parsedAddr.JoinHostPort(0)) |
|
|
|
}, |
|
|
|
}, |
|
|
|
} |
|
|
|
|
|
|
|
resp, err := client.Do(req) |
|
|
|
if err != nil { |
|
|
|
return fmt.Errorf("performing request: %v", err) |
|
|
|
} |
|
|
|