这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<h1 align="center">up</h1>
# up

<div align="center">
<img alt="Logo" src="https://media.giphy.com/media/pYyFAHLW0zJL2/giphy.gif" width="40%">
</div>
Expand Down Expand Up @@ -39,7 +40,7 @@ Check [the examples](examples) to see how to use this project in your own code.

## License

This project is under the MIT License. See the [LICENSE](LICENSE) file for the full license text.
This project is under the MIT License. See the [LICENSE](LICENSE) file for the full text.

[doc-img]: https://pkg.go.dev/badge/github.com/jesusprubio/up
[doc]: https://pkg.go.dev/github.com/jesusprubio/up
Expand Down
2 changes: 1 addition & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ tasks:

build:
summary: "Build application"
cmd: go build -o dist/up cmd/main.go
cmd: go build -o dist/up .

clean:
summary: "Clean the project"
Expand Down
2 changes: 0 additions & 2 deletions examples/doc.go

This file was deleted.

3 changes: 0 additions & 3 deletions examples/probe.go

This file was deleted.

151 changes: 99 additions & 52 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package main implements a simple CLI to use the library.
package main

import (
Expand All @@ -15,6 +16,8 @@ import (
"github.com/jesusprubio/up/pkg"
)

// TODO(#39): STDIN piped input.

const (
appName = "up"
appDesc = `
Expand All @@ -29,72 +32,41 @@ const (
This utility exits with one of the following values:
0 At least one response was heard.
2 The transmission was successful but no responses were received.
1 Any other value An error occurred.
1 Any other error occurred.
`
)

// CLI initialization.
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Input options.
flagProto := flag.String(
"p",
"",
fmt.Sprintf("Use only one protocol: %v", pkg.Protocols),
)
flagCount := flag.Uint("c", 0, "Number of iterations. (0 = infinite)")
flagTimeout := flag.Duration("t",
5*time.Second,
"Time to wait for a response",
)
flagDelay := flag.Duration(
"d",
500*time.Millisecond,
"Delay between requests",
)
flagStop := flag.Bool("s", false, "Stop after the first successful request")
// Output options.
flagJSONOutput := flag.Bool("j", false, "Output in JSON format")
flagNoColor := flag.Bool("nc", false, "Disable color output")
flagVerbose := flag.Bool("v", false, "Verbose output")
flagHelp := flag.Bool("h", false, "Show app documentation")
flag.Parse()
// Only used for debugging.
lvl := new(slog.LevelVar)
lvl.Set(slog.LevelError)
// Only used for debugging.
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: lvl,
}))
if *flagVerbose {
var opts options
opts.parse()
if opts.verbose {
// TODO(#37): Debug and verbose should not be the same thing.
lvl.Set(slog.LevelDebug)
}
logger.Debug("Running",
"protocol", *flagProto,
"count", *flagCount,
"timeout", *flagTimeout,
"delay", *flagDelay,
"stop", *flagStop,
"json", *flagJSONOutput,
"no-color", *flagNoColor,
"verbose", *flagVerbose,
"help", *flagHelp,
)
logger.Debug("Starting", "options", opts)
protocols := pkg.Protocols
if *flagProto != "" {
protocol := pkg.ProtocolByID(*flagProto)
if opts.protocol != "" {
protocol := pkg.ProtocolByID(opts.protocol)
if protocol == nil {
fatal(fmt.Errorf("unknown protocol: %s", *flagProto))
fatal(fmt.Errorf("unknown protocol: %s", opts.protocol))
}
protocols = []*pkg.Protocol{protocol}
}
logger.Info("Running", "protocols", protocols, "count", *flagCount)
if *flagHelp {
logger.Info("Running", "protocols", protocols, "count", opts.count)
if opts.help {
fmt.Fprintf(os.Stderr, "%s\n", appDesc)
flag.Usage()
os.Exit(1)
}
if *flagNoColor {
if opts.noColor {
color.NoColor = true
}
// To wait for termination signals.
Expand All @@ -111,9 +83,9 @@ func main() {
}()
probe := pkg.Probe{
Protocols: protocols,
Count: *flagCount,
Delay: *flagDelay,
Timeout: *flagTimeout,
Count: opts.count,
Timeout: opts.timeout,
Delay: opts.delay,
Logger: logger,
ReportCh: make(chan *pkg.Report),
}
Expand All @@ -122,19 +94,19 @@ func main() {
for report := range probe.ReportCh {
logger.Debug("New report", "report", *report)
var line string
if *flagJSONOutput {
if opts.jsonOutput {
reportJSON, err := json.Marshal(report)
if err != nil {
fatal(fmt.Errorf("marshaling report: %w", err))
}
line = string(reportJSON)
} else {
line = report.String()
line = reportToLine(report)
}
fmt.Println(line)
if report.Error == nil {
if *flagStop {
logger.Debug("Stop requested")
if opts.stop {
logger.Debug("Stopping after first successful request")
cancel()
}
}
Expand All @@ -143,12 +115,87 @@ func main() {
logger.Debug("Running", "setup", probe)
err := probe.Run(ctx)
if err != nil {
fatal(err)
fatal(fmt.Errorf("running probe: %w", err))
}
logger.Debug("Bye!")
}

// Flags passed by the user.
type options struct {
// Input flags.
// Protocol to use.
protocol string
// Number of iterations. Zero means infinite.
count uint
// Time to wait for a response.
timeout time.Duration
// Delay between requests.
delay time.Duration
// Stop after the first successful request.
stop bool
// Output flags.
// Output in JSON format.
jsonOutput bool
// Disable color output.
noColor bool
// Verbose output.
verbose bool
// Show app documentation.
help bool
}

// Parses the command line flags provided by the user.
func (opts *options) parse() {
flag.StringVar(&opts.protocol, "p", "", "Test only one protocol")
flag.UintVar(&opts.count, "c", 0, "Number of iterations")
flag.DurationVar(
&opts.timeout,
"t",
5*time.Second,
"Time to wait for a response",
)
flag.DurationVar(
&opts.delay,
"d",
500*time.Millisecond,
"Delay between requests",
)
flag.BoolVar(
&opts.stop,
"s",
false,
"Stop after the first successful request",
)
flag.BoolVar(&opts.jsonOutput, "j", false, "Output in JSON format")
flag.BoolVar(&opts.noColor, "nc", false, "Disable color output")
flag.BoolVar(&opts.verbose, "v", false, "Verbose output")
flag.BoolVar(&opts.help, "h", false, "Show app documentation")
flag.Parse()
}

// Logs the error to the standard output and exits with status 1.
func fatal(err error) {
fmt.Fprintf(os.Stderr, "%s: %s\n", appName, err)
os.Exit(1)
}

// String returns a human-readable representation of the report.
func reportToLine(r *pkg.Report) string {
// TODO(#40): Use Go string padding.
line := fmt.Sprintf("%s\t%s\t%s", bold(r.ProtocolID), r.Time, r.RHost)
suffix := r.Extra
prefix := green("✔")
if r.Error != nil {
prefix = red("✘")
suffix = r.Error.Error()
}
suffix = fmt.Sprintf("(%s)", suffix)
return fmt.Sprintf("%s %s %s", prefix, line, faint(suffix))
}

var (
green = color.New(color.FgGreen).SprintFunc()
red = color.New(color.FgRed).SprintFunc()
bold = color.New(color.Bold).SprintFunc()
faint = color.New(color.Faint).SprintFunc()
)
13 changes: 7 additions & 6 deletions pkg/probe.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ type Probe struct {
Protocols []*Protocol
// Number of iterations. Zero means infinite.
Count uint
// Delay between requests.
Delay time.Duration
// Time to wait for a response.
Timeout time.Duration
// Delay between requests.
Delay time.Duration
// For debugging purposes.
Logger *slog.Logger
// Optional channel to send back partial results.
// Channel to send back partial results.
ReportCh chan *Report
}

Expand All @@ -34,6 +34,7 @@ func (p Probe) validate() error {
if p.Timeout == 0 {
return fmt.Errorf(tmplRequiredProp, "Timeout")
}
// 'Delay' could be zero.
if p.Logger == nil {
return fmt.Errorf(tmplRequiredProp, "Logger")
}
Expand All @@ -43,17 +44,17 @@ func (p Probe) validate() error {
return nil
}

// Run the connection attempts to the public servers.
// Run the connection requests against the public servers.
//
// The context can be cancelled between different protocol attempts or count
// iterations.
// Returns an error if the setup is invalid.
func (p Probe) Run(ctx context.Context) error {
p.Logger.Debug("Starting", "setup", p)
err := p.validate()
if err != nil {
return fmt.Errorf("invalid setup: %w", err)
}
p.Logger.Debug("Starting", "setup", p)
count := uint(0)
for {
select {
Expand Down Expand Up @@ -99,11 +100,11 @@ func (p Probe) Run(ctx context.Context) error {
time.Sleep(p.Delay)
}
}
count++
p.Logger.Debug(
"End of iteration",
"count", count, "p.Count", p.Count,
)
count++
if count == p.Count {
p.Logger.Debug("Count limit reached", "count", count)
return nil
Expand Down
2 changes: 1 addition & 1 deletion pkg/protocol.go → pkg/protocols.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (

// Protocols included in the library.
var Protocols = []*Protocol{
{ID: "http", Request: requestHTTP, RHost: RandomCaptivePortal},
{ID: "tcp", Request: requestTCP, RHost: RandomTCPServer},
{ID: "dns", Request: requestDNS, RHost: RandomDomain},
{ID: "http", Request: requestHTTP, RHost: RandomCaptivePortal},
}

// ProtocolByID returns a protocol from the list.
Expand Down
26 changes: 1 addition & 25 deletions pkg/report.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
package pkg

import (
"fmt"
"time"

"github.com/fatih/color"
)

var (
green = color.New(color.FgGreen).SprintFunc()
red = color.New(color.FgRed).SprintFunc()
bold = color.New(color.Bold).SprintFunc()
faint = color.New(color.Faint).SprintFunc()
)

// Report is the result of a connection attempt.
//
// Depending on the result, only one of the properties 'Response' or 'Error'
// is set.
// Only one of the properties 'Response' or 'Error' is set.
type Report struct {
// Protocol used to connect to.
ProtocolID string `json:"protocol"`
Expand All @@ -33,16 +22,3 @@ type Report struct {
// Network error.
Error error `json:"error,omitempty"`
}

// String returns a human-readable representation of the report.
func (r *Report) String() string {
line := fmt.Sprintf("%s\t%s\t%s", bold(r.ProtocolID), r.Time, r.RHost)
suffix := r.Extra
prefix := green("✔")
if r.Error != nil {
prefix = red("✘")
suffix = r.Error.Error()
}
suffix = fmt.Sprintf("(%s)", suffix)
return fmt.Sprintf("%s %s %s", prefix, line, faint(suffix))
}