mirror of
https://github.com/mpolden/echoip.git
synced 2025-03-15 08:40:08 +01:00
api: add /test for testing ip reachability
This commit is contained in:
parent
f70690f4b4
commit
5e5e9c07cd
66
api/api.go
66
api/api.go
@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
@ -10,7 +11,9 @@ import (
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"html/template"
|
||||
|
||||
@ -28,13 +31,30 @@ const (
|
||||
|
||||
var cliUserAgentExp = regexp.MustCompile(`^(?i)((curl|wget|fetch\slibfetch|Go-http-client)\/.*|Go\s1\.1\spackage\shttp)$`)
|
||||
|
||||
var errNoRedirect = errors.New("no redirect")
|
||||
|
||||
func dontRedirect(*http.Request, []*http.Request) error {
|
||||
return errNoRedirect
|
||||
}
|
||||
|
||||
func isError(err error) error {
|
||||
if e, ok := err.(*url.Error); ok {
|
||||
if e.Err == errNoRedirect {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type API struct {
|
||||
CORS bool
|
||||
ReverseLookup bool
|
||||
Template string
|
||||
TestIP bool
|
||||
lookupAddr func(string) ([]string, error)
|
||||
lookupCountry func(net.IP) (string, error)
|
||||
ipFromRequest func(*http.Request) (net.IP, error)
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func New() *API {
|
||||
@ -42,6 +62,18 @@ func New() *API {
|
||||
lookupAddr: net.LookupAddr,
|
||||
lookupCountry: func(ip net.IP) (string, error) { return "", nil },
|
||||
ipFromRequest: ipFromRequest,
|
||||
client: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
ResponseHeaderTimeout: 15 * time.Second,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 10 * time.Second,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
},
|
||||
Timeout: 15 * time.Second,
|
||||
CheckRedirect: dontRedirect,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,6 +225,37 @@ func (a *API) CLIHandler(w http.ResponseWriter, r *http.Request) *appError {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *API) TestHandler(w http.ResponseWriter, r *http.Request) *appError {
|
||||
s := r.URL.Path[len("/test/"):]
|
||||
port, err := strconv.ParseUint(s, 10, 16)
|
||||
if err != nil {
|
||||
return errBadRequest
|
||||
}
|
||||
|
||||
ip, err := ipFromRequest(r)
|
||||
if err != nil {
|
||||
return errBadRequest
|
||||
}
|
||||
|
||||
u := url.URL{
|
||||
Scheme: "http",
|
||||
Host: net.JoinHostPort(ip.String(), strconv.FormatUint(port, 10)),
|
||||
Path: "/",
|
||||
}
|
||||
|
||||
resp, err := a.client.Get(u.String())
|
||||
if err = isError(err); err != nil {
|
||||
return errGone
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if n := resp.StatusCode / 100; n != 2 && n != 3 {
|
||||
return errGone
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func cliMatcher(r *http.Request, rm *mux.RouteMatch) bool {
|
||||
return cliUserAgentExp.MatchString(r.UserAgent())
|
||||
}
|
||||
@ -269,6 +332,9 @@ func (a *API) Handlers() http.Handler {
|
||||
|
||||
// CLI
|
||||
r.Handle("/", appHandler(a.CLIHandler)).Methods("GET").MatcherFunc(cliMatcher)
|
||||
if a.TestIP {
|
||||
r.Handle("/test/{port}", appHandler(a.TestHandler)).Methods("GET").MatcherFunc(cliMatcher)
|
||||
}
|
||||
r.Handle("/{header}", appHandler(a.CLIHandler)).Methods("GET").MatcherFunc(cliMatcher)
|
||||
|
||||
// Default
|
||||
|
17
api/error.go
17
api/error.go
@ -1,6 +1,14 @@
|
||||
package api
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
errBadRequest = newAppError(http.StatusBadRequest)
|
||||
errGone = newAppError(http.StatusGone)
|
||||
)
|
||||
|
||||
type appError struct {
|
||||
Error error
|
||||
@ -9,6 +17,13 @@ type appError struct {
|
||||
ContentType string
|
||||
}
|
||||
|
||||
func newAppError(code int) *appError {
|
||||
return &appError{
|
||||
Error: errors.New(http.StatusText(code)),
|
||||
Code: code,
|
||||
}
|
||||
}
|
||||
|
||||
func internalServerError(err error) *appError {
|
||||
return &appError{Error: err, Response: "Internal server error", Code: http.StatusInternalServerError}
|
||||
}
|
||||
|
2
main.go
2
main.go
@ -15,6 +15,7 @@ func main() {
|
||||
Listen string `short:"l" long:"listen" description:"Listening address" value-name:"ADDR" default:":8080"`
|
||||
CORS bool `short:"x" long:"cors" description:"Allow requests from other domains"`
|
||||
ReverseLookup bool `short:"r" long:"reverselookup" description:"Perform reverse hostname lookups"`
|
||||
TestIP bool `short:"s" long:"testip" description:"Enable IP reachability testing"`
|
||||
Template string `short:"t" long:"template" description:"Path to template" default:"index.html"`
|
||||
}
|
||||
_, err := flags.ParseArgs(&opts, os.Args)
|
||||
@ -35,6 +36,7 @@ func main() {
|
||||
a.CORS = opts.CORS
|
||||
a.ReverseLookup = opts.ReverseLookup
|
||||
a.Template = opts.Template
|
||||
a.TestIP = opts.TestIP
|
||||
|
||||
log.Printf("Listening on %s", opts.Listen)
|
||||
if err := a.ListenAndServe(opts.Listen); err != nil {
|
||||
|
Loading…
x
Reference in New Issue
Block a user