mirror of
https://github.com/itzg/mc-router.git
synced 2024-11-12 10:04:15 +01:00
Initial commit
This commit is contained in:
commit
17a4bd6515
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/vendor/
|
||||||
|
|
||||||
|
/.idea/
|
||||||
|
/*.iml
|
63
Gopkg.lock
generated
Normal file
63
Gopkg.lock
generated
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/alecthomas/kingpin"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "947dcec5ba9c011838740e680966fd7087a71d0d"
|
||||||
|
version = "v2.2.6"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/alecthomas/template"
|
||||||
|
packages = [
|
||||||
|
".",
|
||||||
|
"parse"
|
||||||
|
]
|
||||||
|
revision = "a0175ee3bccc567396460bf5acd36800cb10c49c"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/alecthomas/units"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/gorilla/context"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
|
||||||
|
version = "v1.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/gorilla/mux"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "53c1911da2b537f792e7cafcb446b05ffe33b996"
|
||||||
|
version = "v1.6.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/sirupsen/logrus"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
|
||||||
|
version = "v1.0.5"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/crypto"
|
||||||
|
packages = ["ssh/terminal"]
|
||||||
|
revision = "4ec37c66abab2c7e02ae775328b2ff001c3f025a"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/sys"
|
||||||
|
packages = [
|
||||||
|
"unix",
|
||||||
|
"windows"
|
||||||
|
]
|
||||||
|
revision = "6f686a352de66814cdd080d970febae7767857a3"
|
||||||
|
|
||||||
|
[solve-meta]
|
||||||
|
analyzer-name = "dep"
|
||||||
|
analyzer-version = 1
|
||||||
|
inputs-digest = "8d17b95931fea7bbf18743367ccc152c392add293fe7945ca9ce941ca1482b4f"
|
||||||
|
solver-name = "gps-cdcl"
|
||||||
|
solver-version = 1
|
38
Gopkg.toml
Normal file
38
Gopkg.toml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Gopkg.toml example
|
||||||
|
#
|
||||||
|
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||||
|
# for detailed Gopkg.toml documentation.
|
||||||
|
#
|
||||||
|
# required = ["github.com/user/thing/cmd/thing"]
|
||||||
|
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project"
|
||||||
|
# version = "1.0.0"
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project2"
|
||||||
|
# branch = "dev"
|
||||||
|
# source = "github.com/myfork/project2"
|
||||||
|
#
|
||||||
|
# [[override]]
|
||||||
|
# name = "github.com/x/y"
|
||||||
|
# version = "2.4.0"
|
||||||
|
#
|
||||||
|
# [prune]
|
||||||
|
# non-go = false
|
||||||
|
# go-tests = true
|
||||||
|
# unused-packages = true
|
||||||
|
|
||||||
|
|
||||||
|
[prune]
|
||||||
|
go-tests = true
|
||||||
|
unused-packages = true
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/alecthomas/kingpin"
|
||||||
|
version = "2.2.6"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/sirupsen/logrus"
|
||||||
|
version = "1.0.5"
|
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Geoff Bourne
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
30
README.md
Normal file
30
README.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
Routes Minecraft client connections to backend servers based upon the requested server address.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```text
|
||||||
|
Flags:
|
||||||
|
--help Show context-sensitive help (also try --help-long
|
||||||
|
and --help-man).
|
||||||
|
--port=25565 The port bound to listen for Minecraft client
|
||||||
|
connections
|
||||||
|
--api-binding=API-BINDING The host:port bound for servicing API requests
|
||||||
|
--mapping=MAPPING ... Mapping of external hostname to internal server
|
||||||
|
host:port
|
||||||
|
```
|
||||||
|
|
||||||
|
## REST API
|
||||||
|
|
||||||
|
* `GET /routes`
|
||||||
|
Retrieves the currently configured routes
|
||||||
|
* `POST /routes`
|
||||||
|
Registers a route given a JSON body structured like:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"serverAddress": "CLIENT REQUESTED SERVER ADDRESS",
|
||||||
|
"backend": "HOST:PORT"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* `DELETE /routes/{serverAddress}`
|
||||||
|
Deletes an existing route for the given `serverAddress`
|
42
cmd/mc-router/main.go
Normal file
42
cmd/mc-router/main.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"github.com/itzg/mc-router/server"
|
||||||
|
"github.com/alecthomas/kingpin"
|
||||||
|
"strconv"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
port = kingpin.Flag("port", "The port bound to listen for Minecraft client connections").
|
||||||
|
Default("25565").Int()
|
||||||
|
apiBinding = kingpin.Flag("api-binding", "The host:port bound for servicing API requests").
|
||||||
|
String()
|
||||||
|
mappings = kingpin.Flag("mapping", "Mapping of external hostname to internal server host:port").
|
||||||
|
StringMap()
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
kingpin.Parse()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c)
|
||||||
|
|
||||||
|
server.Routes.RegisterAll(*mappings)
|
||||||
|
|
||||||
|
server.Connector.StartAcceptingConnections(ctx, net.JoinHostPort("", strconv.Itoa(*port)))
|
||||||
|
|
||||||
|
if *apiBinding != "" {
|
||||||
|
server.StartApiServer(*apiBinding)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-c
|
||||||
|
logrus.Info("Stopping")
|
||||||
|
cancel()
|
||||||
|
}
|
168
mcproto/types.go
Normal file
168
mcproto/types.go
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
package mcproto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Frame struct {
|
||||||
|
Length int
|
||||||
|
Payload []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type Packet struct {
|
||||||
|
Length int
|
||||||
|
PacketID int
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
const PacketIdHandshake = 0x00
|
||||||
|
type Handshake struct {
|
||||||
|
ProtocolVersion int
|
||||||
|
ServerAddress string
|
||||||
|
ServerPort uint16
|
||||||
|
NextState int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ByteReader interface {
|
||||||
|
ReadByte() (byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadVarInt(reader io.Reader) (int, error) {
|
||||||
|
b := make([]byte, 1)
|
||||||
|
var numRead uint = 0
|
||||||
|
result := 0
|
||||||
|
for numRead <= 5 {
|
||||||
|
n, err := reader.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
value := b[0] & 0x7F
|
||||||
|
result |= int(value) << (7*numRead)
|
||||||
|
|
||||||
|
numRead++
|
||||||
|
|
||||||
|
if b[0] & 0x80 == 0 {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, errors.New("VarInt is too big")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadString(reader io.Reader) (string, error) {
|
||||||
|
length, err := ReadVarInt(reader)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 1)
|
||||||
|
var strBuilder strings.Builder
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
n, err := reader.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
strBuilder.WriteByte(b[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return strBuilder.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadUnsignedShort(reader io.Reader) (uint16, error) {
|
||||||
|
upper := make([]byte, 1)
|
||||||
|
_, err := reader.Read(upper)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
lower := make([]byte, 1)
|
||||||
|
_, err = reader.Read(lower)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint16(upper[0])<<8) | uint16(lower[0]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadFrame(reader io.Reader) (*Frame, error) {
|
||||||
|
var err error
|
||||||
|
frame := &Frame{}
|
||||||
|
|
||||||
|
frame.Length, err = ReadVarInt(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.Payload = make([]byte, frame.Length)
|
||||||
|
total := 0
|
||||||
|
for total < frame.Length {
|
||||||
|
readIntoThis := frame.Payload[total:]
|
||||||
|
n, err := reader.Read(readIntoThis)
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total += n
|
||||||
|
}
|
||||||
|
|
||||||
|
return frame, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadPacket(reader io.Reader) (*Packet, error) {
|
||||||
|
|
||||||
|
frame, err := ReadFrame(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
packet := &Packet{Length:frame.Length}
|
||||||
|
|
||||||
|
remainder := bytes.NewBuffer(frame.Payload)
|
||||||
|
|
||||||
|
packet.PacketID, err = ReadVarInt(remainder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
packet.Data = remainder.Bytes()
|
||||||
|
|
||||||
|
return packet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadHandshake(data []byte) (*Handshake, error) {
|
||||||
|
|
||||||
|
handshake := &Handshake{}
|
||||||
|
buffer := bytes.NewBuffer(data)
|
||||||
|
var err error
|
||||||
|
|
||||||
|
handshake.ProtocolVersion, err = ReadVarInt(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
handshake.ServerAddress, err = ReadString(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
handshake.ServerPort, err = ReadUnsignedShort(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nextState, err := ReadVarInt(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
handshake.NextState = nextState
|
||||||
|
return handshake, nil
|
||||||
|
}
|
17
server/api_server.go
Normal file
17
server/api_server.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
var apiRoutes = mux.NewRouter()
|
||||||
|
|
||||||
|
func StartApiServer(apiBinding string) {
|
||||||
|
logrus.WithField("binding", apiBinding).Info("Serving API requests")
|
||||||
|
go func() {
|
||||||
|
logrus.WithError(
|
||||||
|
http.ListenAndServe(apiBinding, apiRoutes)).Error("API server failed")
|
||||||
|
}()
|
||||||
|
}
|
144
server/connector.go
Normal file
144
server/connector.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/itzg/mc-router/mcproto"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IConnector interface {
|
||||||
|
StartAcceptingConnections(ctx context.Context, listenAddress string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var Connector IConnector = &connectorImpl{}
|
||||||
|
|
||||||
|
type connectorImpl struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connectorImpl) StartAcceptingConnections(ctx context.Context, listenAddress string) error {
|
||||||
|
|
||||||
|
ln, err := net.Listen("tcp", listenAddress)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Fatal("Unable to start listening")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logrus.WithField("listenAddress", listenAddress).Info("Listening for Minecraft client connections")
|
||||||
|
|
||||||
|
go c.acceptConnections(ctx, ln)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connectorImpl) acceptConnections(ctx context.Context, ln net.Listener) {
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to accept connection")
|
||||||
|
} else {
|
||||||
|
go c.HandleConnection(ctx, conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connectorImpl) HandleConnection(ctx context.Context, frontendConn net.Conn) {
|
||||||
|
defer frontendConn.Close()
|
||||||
|
|
||||||
|
clientAddr := frontendConn.RemoteAddr()
|
||||||
|
logrus.WithFields(logrus.Fields{"clientAddr": clientAddr}).Info("Got connection")
|
||||||
|
|
||||||
|
inspectionBuffer := new(bytes.Buffer)
|
||||||
|
|
||||||
|
inspectionReader := io.TeeReader(frontendConn, inspectionBuffer)
|
||||||
|
|
||||||
|
packet, err := mcproto.ReadPacket(inspectionReader)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).WithField("clientAddr", clientAddr).Error("Failed to read packet")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{"length": packet.Length, "packetID": packet.PacketID}).Info("Got packet")
|
||||||
|
|
||||||
|
if packet.PacketID == mcproto.PacketIdHandshake {
|
||||||
|
handshake, err := mcproto.ReadHandshake(packet.Data)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).WithField("clientAddr", clientAddr).Error("Failed to read handshake")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"protocolVersion": handshake.ProtocolVersion,
|
||||||
|
"server": handshake.ServerAddress,
|
||||||
|
"serverPort": handshake.ServerPort,
|
||||||
|
"nextState": handshake.NextState,
|
||||||
|
}).Info("Got handshake")
|
||||||
|
|
||||||
|
backendHostPort := Routes.FindBackendForServerAddress(handshake.ServerAddress)
|
||||||
|
if backendHostPort == "" {
|
||||||
|
logrus.WithField("serverAddress", handshake.ServerAddress).Warn("Unable to find registered backend")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithField("backendHostPort", backendHostPort).Info("Connecting to backend")
|
||||||
|
backendConn, err := net.Dial("tcp", backendHostPort)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).WithFields(logrus.Fields{
|
||||||
|
"serverAddress": handshake.ServerAddress,
|
||||||
|
"backend": backendHostPort,
|
||||||
|
}).Warn("Unable to connect to backend")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
amount, err := io.Copy(backendConn, inspectionBuffer)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to write handshake to backend connection")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logrus.WithField("amount", amount).Debug("Relayed handshake to backend")
|
||||||
|
|
||||||
|
pumpConnections(ctx, frontendConn, backendConn)
|
||||||
|
} else {
|
||||||
|
logrus.WithField("packetID", packet.PacketID).Error("Unexpected packetID, expected handshake")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pumpConnections(ctx context.Context, frontendConn, backendConn net.Conn) {
|
||||||
|
defer backendConn.Close()
|
||||||
|
|
||||||
|
errors := make(chan error, 2)
|
||||||
|
|
||||||
|
go pumpFrames(backendConn, frontendConn, errors, "backend", "frontend")
|
||||||
|
go pumpFrames(frontendConn, backendConn, errors, "frontend", "backend")
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case err := <-errors:
|
||||||
|
if err != io.EOF {
|
||||||
|
logrus.WithError(err).Error("Error observed on connection relay")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pumpFrames(incoming io.Reader, outgoing io.Writer, errors chan<- error, from, to string) {
|
||||||
|
amount, err := io.Copy(outgoing, incoming)
|
||||||
|
if err != nil {
|
||||||
|
errors <- err
|
||||||
|
}
|
||||||
|
logrus.WithField("amount", amount).Infof("Finished relay %s->%s", from, to)
|
||||||
|
}
|
131
server/routes.go
Normal file
131
server/routes.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"net/http"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
apiRoutes.Path("/routes").Methods("GET").
|
||||||
|
Headers("Accept", "application/json").
|
||||||
|
HandlerFunc(routesListHandler)
|
||||||
|
apiRoutes.Path("/routes").Methods("POST").
|
||||||
|
Headers("Content-Type", "application/json").
|
||||||
|
HandlerFunc(routesCreateHandler)
|
||||||
|
apiRoutes.Path("/routes/{serverAddress}").Methods("DELETE").HandlerFunc(routesDeleteHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func routesListHandler(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
mappings := Routes.GetMappings()
|
||||||
|
bytes, err := json.Marshal(mappings)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to marshal mappings")
|
||||||
|
writer.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writer.Write(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func routesDeleteHandler(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
serverAddress := mux.Vars(request)["serverAddress"]
|
||||||
|
if serverAddress != "" {
|
||||||
|
if Routes.DeleteMapping(serverAddress) {
|
||||||
|
writer.WriteHeader(http.StatusOK)
|
||||||
|
} else {
|
||||||
|
writer.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func routesCreateHandler(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
var definition = struct {
|
||||||
|
ServerAddress string
|
||||||
|
Backend string
|
||||||
|
}{}
|
||||||
|
|
||||||
|
defer request.Body.Close()
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(request.Body)
|
||||||
|
err := decoder.Decode(&definition)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Unable to get request body")
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Routes.CreateMapping(definition.ServerAddress, definition.Backend)
|
||||||
|
writer.WriteHeader(http.StatusCreated)
|
||||||
|
}
|
||||||
|
|
||||||
|
type IRoutes interface {
|
||||||
|
RegisterAll(mappings map[string]string)
|
||||||
|
// FindBackendForServerAddress returns the host:port for the external server address, if registered.
|
||||||
|
// Otherwise, an empty string is returned
|
||||||
|
FindBackendForServerAddress(serverAddress string) string
|
||||||
|
GetMappings() map[string]string
|
||||||
|
DeleteMapping(serverAddress string) bool
|
||||||
|
CreateMapping(serverAddress string, backend string)
|
||||||
|
}
|
||||||
|
|
||||||
|
var Routes IRoutes = &routesImpl{}
|
||||||
|
|
||||||
|
func (r *routesImpl) RegisterAll(mappings map[string]string) {
|
||||||
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
|
||||||
|
r.mappings = mappings
|
||||||
|
}
|
||||||
|
|
||||||
|
type routesImpl struct {
|
||||||
|
sync.RWMutex
|
||||||
|
mappings map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routesImpl) FindBackendForServerAddress(serverAddress string) string {
|
||||||
|
r.RLock()
|
||||||
|
defer r.RUnlock()
|
||||||
|
|
||||||
|
if r.mappings == nil {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return r.mappings[serverAddress]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routesImpl) GetMappings() map[string]string {
|
||||||
|
r.RLock()
|
||||||
|
defer r.RUnlock()
|
||||||
|
|
||||||
|
result := make(map[string]string, len(r.mappings))
|
||||||
|
for k, v := range r.mappings {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routesImpl) DeleteMapping(serverAddress string) bool {
|
||||||
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
logrus.WithField("serverAddress", serverAddress).Info("Deleting route")
|
||||||
|
|
||||||
|
if _, ok := r.mappings[serverAddress]; ok {
|
||||||
|
delete(r.mappings, serverAddress)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routesImpl) CreateMapping(serverAddress string, backend string) {
|
||||||
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"serverAddress": serverAddress,
|
||||||
|
"backend": backend,
|
||||||
|
}).Info("Creating route")
|
||||||
|
r.mappings[serverAddress] = backend
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user