initial commit
This commit is contained in:
108
client.go
Normal file
108
client.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// errHostUnreachable marks a transport-level failure (tunnel down, host server
|
||||
// not running) as distinct from an HTTP-level rejection, so the client can fall
|
||||
// back to the local opener only when forwarding genuinely failed.
|
||||
var errHostUnreachable = errors.New("host unreachable")
|
||||
|
||||
var httpClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: (&net.Dialer{Timeout: 3 * time.Second}).DialContext,
|
||||
},
|
||||
}
|
||||
|
||||
func runClient(args []string, openerName string) int {
|
||||
if len(args) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "open: missing operand")
|
||||
return 2
|
||||
}
|
||||
|
||||
c := &client{
|
||||
base: "http://" + envAddr(),
|
||||
token: envToken(),
|
||||
}
|
||||
|
||||
exit := 0
|
||||
for _, arg := range args {
|
||||
err := c.openOne(arg)
|
||||
if errors.Is(err, errHostUnreachable) {
|
||||
if ferr := localOpen(openerName, arg); ferr != nil {
|
||||
fmt.Fprintf(os.Stderr, "open: %s: host unreachable, local fallback failed: %v\n", arg, ferr)
|
||||
exit = 1
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "open: %s: %v\n", arg, err)
|
||||
exit = 1
|
||||
}
|
||||
}
|
||||
return exit
|
||||
}
|
||||
|
||||
type client struct {
|
||||
base string
|
||||
token string
|
||||
}
|
||||
|
||||
func (c *client) openOne(arg string) error {
|
||||
// Existing local file takes priority: copy it so self-contained files render
|
||||
// on the host. Directories and missing paths fall through to string/URL.
|
||||
if info, err := os.Stat(arg); err == nil && info.Mode().IsRegular() {
|
||||
return c.sendFile(arg)
|
||||
}
|
||||
return c.sendString(arg)
|
||||
}
|
||||
|
||||
func (c *client) sendFile(path string) error {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, c.base+"/file", f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set(fileNameHeader, filepath.Base(path))
|
||||
req.Header.Set("Content-Type", "application/octet-stream")
|
||||
return c.do(req)
|
||||
}
|
||||
|
||||
func (c *client) sendString(s string) error {
|
||||
req, err := http.NewRequest(http.MethodPost, c.base+"/open", strings.NewReader(s))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
return c.do(req)
|
||||
}
|
||||
|
||||
func (c *client) do(req *http.Request) error {
|
||||
if c.token != "" {
|
||||
req.Header.Set(tokenHeader, c.token)
|
||||
}
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s: %v", errHostUnreachable, c.base, err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<10))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("host returned %s: %s", resp.Status, strings.TrimSpace(string(body)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user