Require Go 1.21+
.
$ go get -u github.com/xgfone/go-loadbalancer
package main
import (
"encoding/json"
"flag"
"net/http"
"github.com/xgfone/go-loadbalancer"
"github.com/xgfone/go-loadbalancer/balancer"
"github.com/xgfone/go-loadbalancer/forwarder"
"github.com/xgfone/go-loadbalancer/httpx"
)
var listenAddr = flag.String("listenaddr", ":80", "The address that api gateway listens on.")
func main() {
flag.Parse()
http.HandleFunc("/admin/route", registerRouteHandler)
_ = http.ListenAndServe(*listenAddr, nil)
}
func registerRouteHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
var req struct {
// Route Matcher
Path string `json:"path" validate:"required"`
Method string `json:"method" validate:"required"`
// Upstream Endpoints
Upstream struct {
ForwardPolicy string `json:"forwardPolicy" default:"weight_random"`
Servers []struct {
Host string `json:"host" validate:"host"`
Port uint16 `json:"port" validate:"ranger(1,65535)"`
Weight int `json:"weight" default:"1" validate:"min(1)"`
} `json:"servers"`
} `json:"upstream"`
}
// Notice: here we don't validate whether the values are valid.
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid request route paramenter: "+err.Error(), 400)
return
}
// Build the upstream backend servers.
static := loadbalancer.NewStaticWithCap(len(req.Upstream.Servers))
for _, server := range req.Upstream.Servers {
c := httpx.Config{Host: server.Host, Port: server.Port, Weight: server.Weight}
static.Append(c.NewEndpoint())
}
// Build the loadbalancer forwarder.
balancer := balancer.Get(req.Upstream.ForwardPolicy) // not check it is nil
forwarder := forwarder.New(req.Method+"@"+req.Path, balancer, static)
// Register the route and forward the request to forwarder.
http.HandleFunc(req.Path, func(w http.ResponseWriter, r *http.Request) {
if r.Method != req.Method {
w.WriteHeader(http.StatusMethodNotAllowed)
} else {
// You can use forwarder.ForwardHTTP to control the request and response.
forwarder.ServeHTTP(w, r)
}
})
}
# Run the mini API-Gateway on the host 192.168.1.10
$ nohup go run main.go &
# Add the route
# Notice: remove the characters from // to the line end.
$ curl -XPOST http://127.0.0.1/admin/route -H 'Content-Type: application/json' -d '
{
"path": "/path",
"method": "GET",
"upstream": {
"forwardPolicy": "weight_round_robin",
"servers": [
{"host": "192.168.1.11", "port": 80, "weight": 10}, // 33.3% requests
{"host": "192.168.1.12", "port": 80, "weight": 20} // 66.7% requests
]
}
}'
# Access the backend servers by the mini API-Gateway:
# 2/6(33.3%) requests -> 192.168.1.11
# 4/6(66.7%) requests -> 192.168.1.12
$ curl http://192.168.1.10/path
192.168.1.11/backend/path
$ curl http://192.168.1.10/path
192.168.1.12/backend/path
$ curl http://192.168.1.10/path
192.168.1.12/backend/path
$ curl http://192.168.1.10/path
192.168.1.11/backend/path
$ curl http://192.168.1.10/path
192.168.1.12/backend/path
$ curl http://192.168.1.10/path
192.168.1.12/backend/path