mirror of
https://github.com/mpolden/echoip.git
synced 2025-07-15 05:23:30 +02:00
api: add /test for testing ip reachability
This commit is contained in:
66
api/api.go
66
api/api.go
@ -2,6 +2,7 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@ -10,7 +11,9 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"html/template"
|
"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 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 {
|
type API struct {
|
||||||
CORS bool
|
CORS bool
|
||||||
ReverseLookup bool
|
ReverseLookup bool
|
||||||
Template string
|
Template string
|
||||||
|
TestIP bool
|
||||||
lookupAddr func(string) ([]string, error)
|
lookupAddr func(string) ([]string, error)
|
||||||
lookupCountry func(net.IP) (string, error)
|
lookupCountry func(net.IP) (string, error)
|
||||||
ipFromRequest func(*http.Request) (net.IP, error)
|
ipFromRequest func(*http.Request) (net.IP, error)
|
||||||
|
client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *API {
|
func New() *API {
|
||||||
@ -42,6 +62,18 @@ func New() *API {
|
|||||||
lookupAddr: net.LookupAddr,
|
lookupAddr: net.LookupAddr,
|
||||||
lookupCountry: func(ip net.IP) (string, error) { return "", nil },
|
lookupCountry: func(ip net.IP) (string, error) { return "", nil },
|
||||||
ipFromRequest: ipFromRequest,
|
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
|
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 {
|
func cliMatcher(r *http.Request, rm *mux.RouteMatch) bool {
|
||||||
return cliUserAgentExp.MatchString(r.UserAgent())
|
return cliUserAgentExp.MatchString(r.UserAgent())
|
||||||
}
|
}
|
||||||
@ -269,6 +332,9 @@ func (a *API) Handlers() http.Handler {
|
|||||||
|
|
||||||
// CLI
|
// CLI
|
||||||
r.Handle("/", appHandler(a.CLIHandler)).Methods("GET").MatcherFunc(cliMatcher)
|
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)
|
r.Handle("/{header}", appHandler(a.CLIHandler)).Methods("GET").MatcherFunc(cliMatcher)
|
||||||
|
|
||||||
// Default
|
// Default
|
||||||
|
17
api/error.go
17
api/error.go
@ -1,6 +1,14 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errBadRequest = newAppError(http.StatusBadRequest)
|
||||||
|
errGone = newAppError(http.StatusGone)
|
||||||
|
)
|
||||||
|
|
||||||
type appError struct {
|
type appError struct {
|
||||||
Error error
|
Error error
|
||||||
@ -9,6 +17,13 @@ type appError struct {
|
|||||||
ContentType string
|
ContentType string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newAppError(code int) *appError {
|
||||||
|
return &appError{
|
||||||
|
Error: errors.New(http.StatusText(code)),
|
||||||
|
Code: code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func internalServerError(err error) *appError {
|
func internalServerError(err error) *appError {
|
||||||
return &appError{Error: err, Response: "Internal server error", Code: http.StatusInternalServerError}
|
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"`
|
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"`
|
CORS bool `short:"x" long:"cors" description:"Allow requests from other domains"`
|
||||||
ReverseLookup bool `short:"r" long:"reverselookup" description:"Perform reverse hostname lookups"`
|
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"`
|
Template string `short:"t" long:"template" description:"Path to template" default:"index.html"`
|
||||||
}
|
}
|
||||||
_, err := flags.ParseArgs(&opts, os.Args)
|
_, err := flags.ParseArgs(&opts, os.Args)
|
||||||
@ -35,6 +36,7 @@ func main() {
|
|||||||
a.CORS = opts.CORS
|
a.CORS = opts.CORS
|
||||||
a.ReverseLookup = opts.ReverseLookup
|
a.ReverseLookup = opts.ReverseLookup
|
||||||
a.Template = opts.Template
|
a.Template = opts.Template
|
||||||
|
a.TestIP = opts.TestIP
|
||||||
|
|
||||||
log.Printf("Listening on %s", opts.Listen)
|
log.Printf("Listening on %s", opts.Listen)
|
||||||
if err := a.ListenAndServe(opts.Listen); err != nil {
|
if err := a.ListenAndServe(opts.Listen); err != nil {
|
||||||
|
Reference in New Issue
Block a user