implemented support for custom timeout parameter for ping/reachability analyzer
This commit is contained in:
47
README.md
47
README.md
@@ -36,7 +36,16 @@ yosoy responds to all requests with a JSON containing the information about:
|
||||
|
||||
Check [Sample JSON response](#sample-json-response) to see how you can use yosoy for stubbing/prototyping/troubleshooting distributed applications.
|
||||
|
||||
Check [ping/reachability analyzer](#pingreachability-analyzer) to see how you can use yosoy for troubleshooting network connectivity.
|
||||
## ping/reachability analyzer
|
||||
|
||||
yosoy includes a simple ping/reachability analyzer. You can use this functionality when prototyping distributed systems to validate whether a given component can reach a specific endpoint. yosoy exposes a dedicated `/_/yosoy/ping` endpoint which accepts the following 4 query parameters:
|
||||
|
||||
* `h` - required - hostname of the endpoint
|
||||
* `p` - required - port of the endpoint
|
||||
* `n` - optional - network, all valid Go networks are supported (including the most popular ones like `tcp`, `udp`, IPv4, IPV6, etc.). If `n` parameter is not provided, it defaults to `tcp`. If `n` parameter is set to unknown network, an error will be returned.
|
||||
* `t` - optional - timeout in seconds. If `t` parameter is not provided, it defaults to `10`. If `t` contains invalid integer literal, an error will be returned.
|
||||
|
||||
Check [Sample ping/reachability analyzer responses](#sample-pingreachability-analyzer-responses) to see how you can use yosoy for troubleshooting network connectivity.
|
||||
|
||||
## Docker image
|
||||
|
||||
@@ -132,24 +141,40 @@ A sample yosoy JSON response to a request made from a single page application (S
|
||||
}
|
||||
```
|
||||
|
||||
## ping/reachability analyzer
|
||||
## Sample ping/reachability analyzer responses
|
||||
|
||||
yosoy includes a simple ping/reachability analyzer. You can use this functionality when prototyping distributed systems to validate whether a given component can reach a specific endpoint. yosoy exposes a dedicated `/_/yosoy/ping` endpoint which accepts the following 3 query parameters:
|
||||
|
||||
* `h` - required - hostname of the endpoint
|
||||
* `p` - required - port of the endpoint
|
||||
* `n` - optional - network, all valid Go networks are supported (including the most popular ones like `tcp`, `udp`, IPv4, IPV6, etc.). If `n` parameter is not provided, it defaults to `tcp`. Go will throw an error if `n` parameter will be set to unknown network.
|
||||
|
||||
For example, to test if yosoy can connect to `google.com` on port `443` using default `tcp` network use the following command:
|
||||
To test if yosoy can connect to `google.com` on port `443` using default `tcp` network use the following command:
|
||||
|
||||
```bash
|
||||
curl "$URL/_/yosoy/ping?h=google.com&p=443"
|
||||
curl -v "http://localhost/_/yosoy/ping?h=google.com&p=443"
|
||||
> GET /_/yosoy/ping?h=google.com&p=443 HTTP/1.1
|
||||
> Host: localhost
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 200 OK
|
||||
< Date: Fri, 17 Nov 2023 05:54:36 GMT
|
||||
< Content-Length: 29
|
||||
< Content-Type: text/plain; charset=utf-8
|
||||
<
|
||||
{"message":"ping succeeded"}
|
||||
```
|
||||
|
||||
To see an unsuccessful response you may use localhost with some random port number:
|
||||
|
||||
```bash
|
||||
curl "$URL/_/yosoy/ping?h=127.0.0.1&p=12345"
|
||||
curl -v "http://localhost/_/yosoy/ping?h=127.0.0.1&p=12345"
|
||||
> GET /_/yosoy/ping?h=127.0.0.1&p=12345 HTTP/1.1
|
||||
> Host: localhost
|
||||
> User-Agent: curl/7.86.0
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 500 Internal Server Error
|
||||
< Date: Fri, 17 Nov 2023 05:53:48 GMT
|
||||
< Content-Length: 66
|
||||
< Content-Type: text/plain; charset=utf-8
|
||||
<
|
||||
{"error":"dial tcp 127.0.0.1:12345: connect: connection refused"}
|
||||
```
|
||||
|
||||
## Building and testing locally
|
||||
|
||||
35
server.go
35
server.go
@@ -6,12 +6,17 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
const PING_DEFAULT_TIMEOUT = 10
|
||||
const PING_DEFAULT_NETWORK = "tcp"
|
||||
|
||||
type response struct {
|
||||
Host string `json:"host"`
|
||||
Proto string `json:"proto"`
|
||||
@@ -95,10 +100,12 @@ func handler(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
func ping(w http.ResponseWriter, req *http.Request) {
|
||||
// get h, p, t parameters from query string
|
||||
// get h, p, n, t parameters from query string
|
||||
hostname := req.URL.Query().Get("h")
|
||||
port := req.URL.Query().Get("p")
|
||||
network := req.URL.Query().Get("n")
|
||||
timeoutString := req.URL.Query().Get("t")
|
||||
var timeout int64 = PING_DEFAULT_TIMEOUT
|
||||
|
||||
// return HTTP BadRequest when hostname is empty
|
||||
if hostname == "" {
|
||||
@@ -114,12 +121,25 @@ func ping(w http.ResponseWriter, req *http.Request) {
|
||||
json.NewEncoder(w).Encode(&errorResponse{"port is empty"})
|
||||
return
|
||||
}
|
||||
// check if timeoutString is a valid int64, return HTTP BadRequest when invalid, otherwise set timeout value
|
||||
if timeoutString != "" {
|
||||
timeoutInt, err := strconv.ParseInt(timeoutString, 10, 64)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Header().Add("Content-Type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(&errorResponse{"timeout is invalid"})
|
||||
return
|
||||
}
|
||||
timeout = timeoutInt
|
||||
}
|
||||
|
||||
// if network is empty set default to tcp
|
||||
if network == "" {
|
||||
network = "tcp"
|
||||
network = PING_DEFAULT_NETWORK
|
||||
}
|
||||
// ping the hostname and port by opening a socket
|
||||
err := pingHost(hostname, port, network)
|
||||
|
||||
// ping the hostname and port
|
||||
err := pingHost(hostname, port, network, timeout)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Header().Add("Content-Type", "application/json; charset=utf-8")
|
||||
@@ -132,9 +152,12 @@ func ping(w http.ResponseWriter, req *http.Request) {
|
||||
json.NewEncoder(w).Encode(&successResponse{"ping succeeded"})
|
||||
}
|
||||
|
||||
func pingHost(hostname, port, network string) error {
|
||||
func pingHost(hostname, port, network string, timeout int64) error {
|
||||
// create timeoutDuration variable of Duration type using timeout as the value in seconds
|
||||
timeoutDuration := time.Duration(timeout) * time.Second
|
||||
|
||||
// open a socket to hostname and port
|
||||
conn, err := net.Dial(network, hostname+":"+port)
|
||||
conn, err := net.DialTimeout(network, hostname+":"+port, timeoutDuration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -235,3 +235,71 @@ func TestHandlerPreflight(t *testing.T) {
|
||||
assert.Equal(t, "600", result.Header.Get("Access-Control-Max-Age"))
|
||||
assert.Equal(t, "*", result.Header.Get("Access-Control-Expose-Headers"))
|
||||
}
|
||||
|
||||
// write test for request /_/yosoy/ping?h=127.0.0.1&p=8123&n=tcp&t=5, the request should return 200 ok and return JSON with message "ping succeeded"
|
||||
func TestHandlerPingWithHostnameAndPortAndNetworkAndTimeout(t *testing.T) {
|
||||
|
||||
// create tcp process to listen on port 8123
|
||||
listener, err := net.Listen("tcp", "localhost:8123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
req, err := http.NewRequest("GET", "https://example.org/_/yosoy/ping?h=127.0.0.1&p=8123&n=tcp&t=5", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Header.Set("Accept", "*/*")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
handler := http.HandlerFunc(ping)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
if status := rr.Code; status != http.StatusOK {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
|
||||
}
|
||||
|
||||
result := rr.Result()
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(result.Body)
|
||||
var response successResponse
|
||||
json.Unmarshal(buf.Bytes(), &response)
|
||||
// test response
|
||||
assert.Equal(t, "ping succeeded", response.Message)
|
||||
}
|
||||
|
||||
// write test for request /_/yosoy/ping?h=127.0.0.1&p=8123&n=tcp&t=invalid, the request should return 400 bad request and return JSON with error "timeout is invalid"
|
||||
func TestHandlerPingWithHostnameAndPortAndNetworkAndTimeoutInvalid(t *testing.T) {
|
||||
|
||||
// create tcp process to listen on port 8123
|
||||
listener, err := net.Listen("tcp", "localhost:8123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
req, err := http.NewRequest("GET", "https://example.org/_/yosoy/ping?h=127.0.0.1&p=8123&n=tcp&t=invalid", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Header.Set("Accept", "*/*")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
handler := http.HandlerFunc(ping)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
if status := rr.Code; status != http.StatusBadRequest {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
result := rr.Result()
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(result.Body)
|
||||
var response errorResponse
|
||||
json.Unmarshal(buf.Bytes(), &response)
|
||||
// test response
|
||||
assert.Equal(t, "timeout is invalid", response.Error)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user