Add exit codes documentation. Add documentation to reduce binary file size. Implement /shutdown API endpoint for a graceful shutdown.

This commit is contained in:
Kleissner
2021-11-15 03:28:17 +01:00
parent d97c45b198
commit 6e27e7c003
5 changed files with 100 additions and 15 deletions

74
API.go
View File

@@ -8,11 +8,15 @@ package main
import (
"bytes"
"encoding/json"
"flag"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/PeernetOfficial/core"
"github.com/PeernetOfficial/core/webapi"
"github.com/gorilla/websocket"
)
@@ -28,12 +32,25 @@ func startAPI() {
} else if len(config.APIListen) != 0 {
// API settings via config file.
webapi.Start(config.APIListen, config.APIUseSSL, config.APICertificateFile, config.APICertificateKey, parseDuration(config.APITimeoutRead), parseDuration(config.APITimeoutWrite))
return
} else {
return
}
webapi.Router.HandleFunc("/console", apiConsole).Methods("GET")
webapi.Router.HandleFunc("/shutdown", apiShutdown).Methods("GET")
}
// parseCmdParamWebapi parses a "-webapi=" command line parameter
func parseCmdParamWebapi() (apiListen []string) {
var param string
flag.StringVar(&param, "webapi", "", "Specify the list of IP:Ports for the webapi to listen. Example: -webapi=127.0.0.1:1234")
flag.Parse()
if len(param) == 0 {
return nil
}
return strings.Split(param, ",")
}
// parseDuration is the same as time.ParseDuration without returning an error. Valid units are ms, s, m, h. For example "10s".
@@ -44,6 +61,7 @@ func parseDuration(input string) (result time.Duration) {
/*
apiConsole provides a websocket to send/receive internal commands
Request: GET /console
Result: 200 with JSON structure apiResponsePeerSelf
*/
@@ -100,15 +118,53 @@ func apiConsole(w http.ResponseWriter, r *http.Request) {
}
}
// parseCmdParamWebapi parses a "-webapi=" command line parameter
func parseCmdParamWebapi() (apiListen []string) {
var param string
flag.StringVar(&param, "webapi", "", "Specify the list of IP:Ports for the webapi to listen. Example: -webapi=127.0.0.1:1234")
flag.Parse()
/*
apiShutdown gracefully shuts down the application. Actions: 0 = Shutdown.
if len(param) == 0 {
return nil
Request: GET /shutdown?action=[action]
Result: 200 with JSON structure apiShutdownStatus
*/
func apiShutdown(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
action, err := strconv.Atoi(r.Form.Get("action"))
if err != nil || action != 0 {
http.Error(w, "", http.StatusBadRequest)
return
}
return strings.Split(param, ",")
if action == 0 {
// Later: Initiate shutdown signal to core library and wait for all requests to complete.
core.Filters.LogError("apiShutdown", "Graceful shutdown via API requested from '%s'\n", r.RemoteAddr)
EncodeJSONFlush(w, r, &apiShutdownStatus{Status: 0})
os.Exit(core.ExitGraceful)
}
}
type apiShutdownStatus struct {
Status int `json:"status"` // Status of the API call. 0 = Success.
}
// EncodeJSONFlush encodes the data as JSON and flushes the writer. It sets the Content-Length header so no subsequent writes should be made.
func EncodeJSONFlush(w http.ResponseWriter, r *http.Request, data interface{}) (err error) {
response, err := json.Marshal(data)
if err != nil {
core.Filters.LogError("EncodeJSONFlush", "Error marshalling data for route '%s': %v\n", r.URL.Path, err)
return err
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
w.WriteHeader(http.StatusOK)
_, err = w.Write(response)
// Flushing the buffer immediately is needed in case the application exits immediately after this call.
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
return
}

View File

@@ -31,22 +31,27 @@ var config struct {
func init() {
if status, err := core.LoadConfigOut(configFile, &config); err != nil {
var exitCode int
switch status {
case 0:
exitCode = core.ExitErrorConfigAccess
fmt.Printf("Unknown error accessing config file '%s': %s\n", configFile, err.Error())
case 1:
exitCode = core.ExitErrorConfigRead
fmt.Printf("Error reading config file '%s': %s\n", configFile, err.Error())
case 2:
exitCode = core.ExitErrorConfigParse
fmt.Printf("Error parsing config file '%s' (make sure it is valid YAML format): %s\n", configFile, err.Error())
default:
exitCode = core.ExitErrorConfigAccess
fmt.Printf("Unknown error loading config file '%s': %s\n", configFile, err.Error())
}
os.Exit(1)
os.Exit(exitCode)
}
if err := core.InitLog(); err != nil {
fmt.Printf("Error opening log file: %s\n", err.Error())
os.Exit(1)
os.Exit(core.ExitErrorLogInit)
}
monitorKeys = make(map[string]struct{})

View File

@@ -6,12 +6,18 @@ This client can be used as root peer to help speed up discovery of peers and dat
## Compile
To build:
Download the [latest version of Go](https://golang.org/dl/). To build:
```
go build
```
To reduce the binary size provide the linker switch -s which will "Omit the symbol table and debug information". This reduced the Windows binary by 26% in a test.
```
go build -ldflags "-s"
```
## Use
The config filename is hard-coded to `Config.yaml` and is created on the first run. Please see the core library for individual settings to change.
@@ -70,3 +76,21 @@ APICertificateKey: "certificate.key" # Private Key
APITimeoutRead: "10m" # The maximum duration for reading the entire request, including the body. In this example 10 minutes.
APITimeoutWrite: "10m" # The maximum duration before timing out writes of the response. This includes processing time and is therefore the max time any HTTP function may take.
```
## Error Handling
The application exits in case of the errors listed below and uses the specified exit code. Applications that launch this application can monitor for those exit codes. End users should look into the log file for additional information in case any of these errors occur, although some of them are pre log file initialization.
| Exit Code | Constant | Info |
| ---------- | ---------------------- | --------------------------------------------------- |
| 0 | ExitSuccess | This is actually never used. |
| 1 | ExitErrorConfigAccess | Error accessing the config file. |
| 2 | ExitErrorConfigRead | Error reading the config file. |
| 3 | ExitErrorConfigParse | Error parsing the config file. |
| 4 | ExitErrorLogInit | Error initializing log file. |
| 5 | ExitParamWebapiInvalid | Parameter for webapi is invalid. |
| 6 | ExitPrivateKeyCorrupt | Private key is corrupt. |
| 7 | ExitPrivateKeyCreate | Cannot create a new private key. |
| 8 | ExitBlockchainCorrupt | Blockchain is corrupt. |
| 9 | ExitGraceful | Graceful shutdown. |
| 0xC000013A | STATUS_CONTROL_C_EXIT | The application terminated as a result of a CTRL+C. |

2
go.mod
View File

@@ -3,6 +3,6 @@ module github.com/PeernetOfficial/Cmd
go 1.16
require (
github.com/PeernetOfficial/core v0.0.0-20211114213707-9626fa80882d
github.com/PeernetOfficial/core v0.0.0-20211115020024-d06c2e0b1a65
github.com/gorilla/websocket v1.4.3-0.20210424162022-e8629af678b7
)

4
go.sum
View File

@@ -1,5 +1,5 @@
github.com/PeernetOfficial/core v0.0.0-20211114213707-9626fa80882d h1:PzpGyK69XH6j+lRVVB173jz3i7sU+Q+lx59vw43UGxM=
github.com/PeernetOfficial/core v0.0.0-20211114213707-9626fa80882d/go.mod h1:0D/jIDYdV0WXg5WACS+fR5keafLhYB89WUs6ZEE8t4Y=
github.com/PeernetOfficial/core v0.0.0-20211115020024-d06c2e0b1a65 h1:WD3/HFSv4+z/hxD5fAXrShgwbtcC8tigre3tQN9Ck8c=
github.com/PeernetOfficial/core v0.0.0-20211115020024-d06c2e0b1a65/go.mod h1:0D/jIDYdV0WXg5WACS+fR5keafLhYB89WUs6ZEE8t4Y=
github.com/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w=
github.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=