mirror of
https://github.com/PeernetOfficial/Cmd.git
synced 2026-07-17 02:47:52 +01:00
Finish /console API endpoint. The code handling input/output from the user/command line needed to be adjusted.
This commit is contained in:
54
API.go
54
API.go
@@ -7,10 +7,10 @@ Author: Peter Kleissner
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -142,25 +142,49 @@ func apiConsole(w http.ResponseWriter, r *http.Request) {
|
||||
// May happen if request is simple HTTP request.
|
||||
return
|
||||
}
|
||||
|
||||
// start reader/writer to forward
|
||||
|
||||
defer c.Close()
|
||||
|
||||
bufferR := bytes.NewBuffer(make([]byte, 0, 4096))
|
||||
bufferW := bytes.NewBuffer(make([]byte, 0, 4096))
|
||||
|
||||
terminateSignal := make(chan struct{})
|
||||
defer close(terminateSignal)
|
||||
|
||||
// start userCommands which handles the actual commands
|
||||
go userCommands(bufferR, bufferW, terminateSignal)
|
||||
|
||||
// go routine to receive output from userCommands and forward to websocket
|
||||
go func() {
|
||||
bufferW2 := make([]byte, 4096)
|
||||
for {
|
||||
select {
|
||||
case <-terminateSignal:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
countRead, err := bufferW.Read(bufferW2)
|
||||
if err != nil || countRead == 0 {
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
c.WriteMessage(websocket.TextMessage, bufferW2[:countRead])
|
||||
}
|
||||
}()
|
||||
|
||||
// read from websocket loop and forward to the userCommands routine
|
||||
for {
|
||||
_, message, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
//fmt.Println("read:", err)
|
||||
if err != nil { // when channel is closed, an error is returned here
|
||||
break
|
||||
}
|
||||
//c.NextWriter(websocket.BinaryMessage)
|
||||
|
||||
//c.NextReader()
|
||||
//userCommands()
|
||||
fmt.Printf("recv: %s", message)
|
||||
//err = c.WriteMessage(mt, message)
|
||||
//if err != nil {
|
||||
// //fmt.Println("write:", err)
|
||||
// break
|
||||
//}
|
||||
// make sure the message has the \n delimiter which is used to detect a line
|
||||
if !bytes.HasSuffix(message, []byte{'\n'}) {
|
||||
message = append(message, '\n')
|
||||
}
|
||||
|
||||
bufferR.Write(message)
|
||||
}
|
||||
}
|
||||
|
||||
17
API.md
17
API.md
@@ -16,7 +16,11 @@ The API is still in development and endpoints are subject to change. The API sho
|
||||
|
||||
## Configuartion
|
||||
|
||||
The configuration file (default `Config.yaml`) contains settings for the API.
|
||||
The configuration file (default `Config.yaml`) contains settings for the API. To listen on `http://127.0.0.1:112/` add this line:
|
||||
|
||||
```yaml
|
||||
APIListen: ["127.0.0.1:112"]
|
||||
```
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -25,9 +29,6 @@ These are the functions provided by the API:
|
||||
```
|
||||
/status Provides current connectivity status to the network
|
||||
/peer/self Provides information about the self peer details
|
||||
/blockchain/self/header Header of the self peers blockchain
|
||||
/blockchain/self/read Read the self peers blockchain
|
||||
/blockchain/self/append Add a record to the blockchain
|
||||
/share/list List all files and directories that are shared
|
||||
/share/file Share a file via the peers blockchain
|
||||
|
||||
@@ -37,9 +38,13 @@ These are the functions provided by the API:
|
||||
|
||||
/status/ws Starts a websocket to receive updates on operations immediately (push instead of pull)
|
||||
/console Console provides a websocket to send/receive internal commands
|
||||
|
||||
/blockchain/self/header Header of the self peers blockchain
|
||||
/blockchain/self/read Read the self peers blockchain
|
||||
/blockchain/self/append Add a record to the blockchain
|
||||
```
|
||||
|
||||
The `/share` functions are providing high-level functionality to work with files; the `/blockchain` functions provide low-level functionality.
|
||||
The `/share` functions are providing high-level functionality to work with files. The `/blockchain` functions provide low-level functionality which is typically not needed.
|
||||
|
||||
## Status
|
||||
|
||||
@@ -84,7 +89,7 @@ Example response data: Todo
|
||||
|
||||
## Console
|
||||
|
||||
The `/console` web-socket allows to execute internal commands. This should be only used for debugging purposes by the end-user. The same input and output as raw text as via the command-line is provided through this endpoint.
|
||||
The `/console` websocket allows to execute internal commands. This should be only used for debugging purposes by the end-user. The same input and output as raw text as via the command-line is provided through this endpoint.
|
||||
|
||||
```
|
||||
Request: ws://127.0.0.1:112/console
|
||||
|
||||
206
Command Line.go
206
Command Line.go
@@ -23,65 +23,6 @@ import (
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
)
|
||||
|
||||
func getUserOptionString(reader *bufio.Reader) (response string, valid bool) {
|
||||
responseA, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
responseA = strings.TrimSpace(responseA)
|
||||
|
||||
return responseA, true
|
||||
}
|
||||
|
||||
func getUserOptionBool(reader *bufio.Reader) (response bool, valid bool) {
|
||||
responseA, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return false, false
|
||||
}
|
||||
|
||||
responseA = strings.TrimSpace(responseA) // also removes the delimiter
|
||||
|
||||
responseI, err := strconv.Atoi(responseA)
|
||||
if err != nil || (responseI != 0 && responseI != 1) {
|
||||
return false, false
|
||||
}
|
||||
|
||||
return responseI == 1, true
|
||||
}
|
||||
|
||||
func getUserOptionInt(reader *bufio.Reader) (response int, valid bool) {
|
||||
responseA, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
responseA = strings.TrimSpace(responseA) // also removes the delimiter
|
||||
|
||||
responseI, err := strconv.Atoi(responseA)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return responseI, true
|
||||
}
|
||||
|
||||
func getUserOptionHash(reader *bufio.Reader) (hash []byte, valid bool) {
|
||||
responseA, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
responseA = strings.TrimSpace(responseA)
|
||||
|
||||
hash, err = hex.DecodeString(responseA)
|
||||
if err != nil || len(hash) != 256/8 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return hash, true
|
||||
}
|
||||
|
||||
func showHelp(output io.Writer) {
|
||||
fmt.Fprint(output, "Please enter a command:\n"+
|
||||
"help Show this help\n"+
|
||||
@@ -104,18 +45,18 @@ func showHelp(output io.Writer) {
|
||||
"\n")
|
||||
}
|
||||
|
||||
func userCommands(input io.Reader, output io.Writer) {
|
||||
func userCommands(input io.Reader, output io.Writer, terminateSignal chan struct{}) {
|
||||
reader := bufio.NewReader(input)
|
||||
|
||||
fmt.Fprint(output, appName+" "+core.Version+"\n------------------------------\n")
|
||||
showHelp(output)
|
||||
|
||||
for {
|
||||
command, valid := getUserOptionString(reader)
|
||||
if !valid {
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
command, _, terminate := getUserOptionString(reader, terminateSignal)
|
||||
if terminate {
|
||||
return
|
||||
}
|
||||
|
||||
command = strings.ToLower(command)
|
||||
|
||||
switch command {
|
||||
@@ -155,8 +96,10 @@ func userCommands(input io.Reader, output io.Writer) {
|
||||
}
|
||||
|
||||
case "chat all", "chat":
|
||||
if text, valid := getUserOptionString(reader); valid {
|
||||
if text, valid, terminate := getUserOptionString(reader, terminateSignal); valid {
|
||||
core.SendChatAll(text)
|
||||
} else if terminate {
|
||||
return
|
||||
}
|
||||
|
||||
case "status":
|
||||
@@ -242,13 +185,15 @@ func userCommands(input io.Reader, output io.Writer) {
|
||||
fmt.Fprintf(output, "\n")
|
||||
|
||||
case "hash":
|
||||
if text, valid := getUserOptionString(reader); valid {
|
||||
if text, valid, terminate := getUserOptionString(reader, terminateSignal); valid {
|
||||
hash := core.Data2Hash([]byte(text))
|
||||
fmt.Fprintf(output, "blake3 hash: %s\n", hex.EncodeToString(hash))
|
||||
} else if terminate {
|
||||
return
|
||||
}
|
||||
|
||||
case "warehouse get":
|
||||
if hash, valid := getUserOptionHash(reader); valid {
|
||||
if hash, valid, terminate := getUserOptionHash(reader, terminateSignal); valid {
|
||||
data, found := core.GetDataLocal(hash)
|
||||
if !found {
|
||||
fmt.Fprintf(output, "Not found.\n")
|
||||
@@ -256,30 +201,36 @@ func userCommands(input io.Reader, output io.Writer) {
|
||||
fmt.Fprintf(output, "Data hex: %s\n", hex.EncodeToString(data))
|
||||
fmt.Fprintf(output, "Data string: %s\n", string(data))
|
||||
}
|
||||
} else if terminate {
|
||||
return
|
||||
} else {
|
||||
fmt.Fprintf(output, "Invalid hash. Hex-encoded blake3 hash as input is required.\n")
|
||||
}
|
||||
|
||||
case "warehouse store":
|
||||
if text, valid := getUserOptionString(reader); valid {
|
||||
if text, valid, terminate := getUserOptionString(reader, terminateSignal); valid {
|
||||
if err := core.StoreDataLocal([]byte(text)); err != nil {
|
||||
fmt.Fprintf(output, "Error storing data: %s\n", err.Error())
|
||||
break
|
||||
}
|
||||
fmt.Fprintf(output, "Stored via hash: %s\n", hex.EncodeToString(core.Data2Hash([]byte(text))))
|
||||
} else if terminate {
|
||||
return
|
||||
}
|
||||
|
||||
case "dht store":
|
||||
if text, valid := getUserOptionString(reader); valid {
|
||||
if text, valid, terminate := getUserOptionString(reader, terminateSignal); valid {
|
||||
if err := core.StoreDataDHT([]byte(text), 5); err != nil {
|
||||
fmt.Fprintf(output, "Error storing data: %s\n", err.Error())
|
||||
break
|
||||
}
|
||||
fmt.Fprintf(output, "Stored via hash: %s\n", hex.EncodeToString(core.Data2Hash([]byte(text))))
|
||||
} else if terminate {
|
||||
return
|
||||
}
|
||||
|
||||
case "dht get":
|
||||
if hash, valid := getUserOptionHash(reader); valid {
|
||||
if hash, valid, terminate := getUserOptionHash(reader, terminateSignal); valid {
|
||||
data, sender, found := core.GetDataDHT(hash)
|
||||
if !found {
|
||||
fmt.Fprintf(output, "Not found.\n")
|
||||
@@ -288,22 +239,28 @@ func userCommands(input io.Reader, output io.Writer) {
|
||||
fmt.Fprintf(output, "Data hex: %s\n", hex.EncodeToString(data))
|
||||
fmt.Fprintf(output, "Data string: %s\n", string(data))
|
||||
}
|
||||
} else if terminate {
|
||||
return
|
||||
} else {
|
||||
fmt.Fprintf(output, "Invalid hash. Hex-encoded blake3 hash as input is required.\n")
|
||||
}
|
||||
|
||||
case "log error":
|
||||
fmt.Fprintf(output, "Please choose the target output of error messages:\n0 = Log file (default)\n1 = Command line\n2 = Log file + command line\n3 = None\n")
|
||||
if number, valid := getUserOptionInt(reader); !valid || number < 0 || number > 3 {
|
||||
fmt.Fprintf(output, "Invalid option.\n")
|
||||
} else {
|
||||
if number, valid, terminate := getUserOptionInt(reader, terminateSignal); valid && number >= 0 && number <= 3 {
|
||||
config.ErrorOutput = number
|
||||
} else if terminate {
|
||||
return
|
||||
} else {
|
||||
fmt.Fprintf(output, "Invalid option.\n")
|
||||
}
|
||||
|
||||
case "debug connect":
|
||||
fmt.Fprintf(output, "Please specify the target peer to connect to via DHT lookup, either by peer ID or node ID:\n")
|
||||
text, valid := getUserOptionString(reader)
|
||||
if !valid || (len(text) != 66 && len(text) != 64) {
|
||||
text, valid, terminate := getUserOptionString(reader, terminateSignal)
|
||||
if terminate {
|
||||
return
|
||||
} else if !valid || (len(text) != 66 && len(text) != 64) {
|
||||
fmt.Fprintf(output, "Invalid peer ID or node ID. It must be hex-encoded and 66 (peer ID) or 64 characters (node ID) long.\n")
|
||||
break
|
||||
}
|
||||
@@ -345,31 +302,40 @@ func userCommands(input io.Reader, output io.Writer) {
|
||||
|
||||
case "debug watch searches":
|
||||
fmt.Fprintf(output, "Enable (1) or disable (0) watching of all outgoing DHT searches? (current setting: %t)\n", enableMonitorAll)
|
||||
if number, valid := getUserOptionInt(reader); !valid || number < 0 || number > 1 {
|
||||
fmt.Fprintf(output, "Invalid option.\n")
|
||||
} else {
|
||||
if number, valid, terminate := getUserOptionInt(reader, terminateSignal); valid && number >= 0 && number <= 1 {
|
||||
enableMonitorAll = number == 1
|
||||
} else if terminate {
|
||||
return
|
||||
} else {
|
||||
fmt.Fprintf(output, "Invalid option.\n")
|
||||
}
|
||||
|
||||
case "debug watch incoming":
|
||||
fmt.Fprintf(output, "Enable (1) or disable (0) watching of all incoming information requests? (current setting: %t)\n", enableWatchIncomingAll)
|
||||
if number, valid := getUserOptionInt(reader); !valid || number < 0 || number > 1 {
|
||||
fmt.Fprintf(output, "Invalid option.\n")
|
||||
} else {
|
||||
if number, valid, terminate := getUserOptionInt(reader, terminateSignal); valid && number >= 0 && number <= 1 {
|
||||
enableWatchIncomingAll = number == 1
|
||||
} else if terminate {
|
||||
return
|
||||
} else {
|
||||
fmt.Fprintf(output, "Invalid option.\n")
|
||||
}
|
||||
|
||||
case "debug bucket refresh":
|
||||
fmt.Fprintf(output, "Disable (1) or enable (0) bucket refresh. This can be useful to disable bucket refresh when debugging outgoing DHT searches. (current setting: %t)\n", dht.DisableBucketRefresh)
|
||||
if number, valid := getUserOptionInt(reader); !valid || number < 0 || number > 1 {
|
||||
fmt.Fprintf(output, "Invalid option.\n")
|
||||
} else {
|
||||
if number, valid, terminate := getUserOptionInt(reader, terminateSignal); valid && number >= 0 && number <= 1 {
|
||||
dht.DisableBucketRefresh = number == 1
|
||||
} else if terminate {
|
||||
return
|
||||
} else {
|
||||
fmt.Fprintf(output, "Invalid option.\n")
|
||||
}
|
||||
|
||||
case "debug watch":
|
||||
fmt.Fprintf(output, "Enter hash of data or node ID to watch. This monitors info requests and packets. Enter same hash again to remove from list.\n")
|
||||
text, _ := getUserOptionString(reader)
|
||||
text, _, terminate := getUserOptionString(reader, terminateSignal)
|
||||
if terminate {
|
||||
return
|
||||
}
|
||||
var hash []byte
|
||||
var err error
|
||||
if hash, err = hex.DecodeString(text); err != nil || len(hash) != 256/8 {
|
||||
@@ -564,3 +530,73 @@ func logError(function, format string, v ...interface{}) {
|
||||
fmt.Printf("["+function+"] "+format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- command-line helper functions ----
|
||||
|
||||
// timeRetryUserInput defines how long the code waits for user input from reader before trying again
|
||||
// The termination signal takes effect once the reader is drained and returns io.EOF.
|
||||
const timeRetryUserInput = 500 * time.Millisecond
|
||||
|
||||
// readUserText reads user text from the buffer. Blocking, unless termination signal is raised!
|
||||
func readUserText(reader *bufio.Reader, terminateSignal <-chan struct{}) (text string, valid, terminate bool) {
|
||||
for {
|
||||
if text, err := reader.ReadString('\n'); err == nil {
|
||||
return strings.TrimSpace(text), true, false
|
||||
}
|
||||
|
||||
// check for termination signal
|
||||
select {
|
||||
case <-terminateSignal:
|
||||
return "", false, true
|
||||
default:
|
||||
}
|
||||
|
||||
time.Sleep(timeRetryUserInput)
|
||||
}
|
||||
}
|
||||
|
||||
func getUserOptionString(reader *bufio.Reader, terminateSignal <-chan struct{}) (response string, valid, terminate bool) {
|
||||
return readUserText(reader, terminateSignal)
|
||||
}
|
||||
|
||||
func getUserOptionBool(reader *bufio.Reader, terminateSignal <-chan struct{}) (response bool, valid, terminate bool) {
|
||||
responseA, valid, terminate := readUserText(reader, terminateSignal)
|
||||
if !valid || terminate {
|
||||
return false, valid, terminate
|
||||
}
|
||||
|
||||
responseI, err := strconv.Atoi(responseA)
|
||||
if err != nil || (responseI != 0 && responseI != 1) {
|
||||
return false, false, false
|
||||
}
|
||||
|
||||
return responseI == 1, true, false
|
||||
}
|
||||
|
||||
func getUserOptionInt(reader *bufio.Reader, terminateSignal <-chan struct{}) (response int, valid, terminate bool) {
|
||||
responseA, valid, terminate := readUserText(reader, terminateSignal)
|
||||
if !valid || terminate {
|
||||
return 0, valid, terminate
|
||||
}
|
||||
|
||||
responseI, err := strconv.Atoi(responseA)
|
||||
if err != nil {
|
||||
return 0, false, false
|
||||
}
|
||||
|
||||
return responseI, true, false
|
||||
}
|
||||
|
||||
func getUserOptionHash(reader *bufio.Reader, terminateSignal <-chan struct{}) (hash []byte, valid, terminate bool) {
|
||||
responseA, valid, terminate := readUserText(reader, terminateSignal)
|
||||
if !valid || terminate {
|
||||
return nil, valid, terminate
|
||||
}
|
||||
|
||||
hash, err := hex.DecodeString(responseA)
|
||||
if err != nil || len(hash) != 256/8 {
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
return hash, true, false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user