🗑️ Clean up command related stuff

🚚 Move http package to web
This commit is contained in:
2025-03-01 13:00:50 +08:00
parent fbb670b96b
commit 481404c4a2
22 changed files with 16 additions and 791 deletions

View File

@ -1,22 +0,0 @@
package directory
const (
CommandMethodGet = "get"
CommandMethodPut = "put"
CommandMethodPatch = "patch"
CommandMethodPost = "post"
CommandMethodDelete = "delete"
)
type Command struct {
// The unique identifier of the command, different method command can hold the same command id
ID string `json:"id"`
// The method of the command, such as get, post, others; inspired by RESTful design
Method string `json:"method"`
// The tags of the command will be used to invoke the pre-command middlewares and post-command middlewares
Tags []string `json:"tags"`
// The implementation of the command, the handler is the service that will be invoked
Handler []*ServiceInstance `json:"handler"`
RobinIndex uint `json:"robin_index"`
}

View File

@ -1,79 +0,0 @@
package directory
import (
"context"
"git.solsynth.dev/hypernet/nexus/pkg/internal/kv"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"github.com/goccy/go-json"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
)
const CommandInfoKvPrefix = "nexus.command/"
func AddCommand(id, method string, tags []string, handler *ServiceInstance) error {
if tags == nil {
tags = make([]string, 0)
}
ky := CommandInfoKvPrefix + nex.GetCommandKey(id, method)
command := &Command{
ID: id,
Method: method,
Tags: tags,
Handler: []*ServiceInstance{handler},
}
command.Handler = lo.UniqBy(command.Handler, func(item *ServiceInstance) string {
return item.ID
})
commandJSON, err := json.Marshal(command)
if err != nil {
log.Printf("Error marshaling command: %v", err)
return nil
}
_, err = kv.Kv.Put(context.Background(), ky, string(commandJSON))
return err
}
func GetCommandHandler(id, method string) *ServiceInstance {
ky := CommandInfoKvPrefix + nex.GetCommandKey(id, method)
resp, err := kv.Kv.Get(context.Background(), ky)
if err != nil {
return nil
}
if len(resp.Kvs) == 0 {
return nil
}
var command Command
if err := json.Unmarshal(resp.Kvs[0].Value, &command); err != nil {
return nil
}
if len(command.Handler) == 0 {
return nil
}
idx := command.RobinIndex % uint(len(command.Handler))
command.RobinIndex = idx + 1
raw, err := json.Marshal(&command)
if err == nil {
_, _ = kv.Kv.Put(context.Background(), ky, string(raw))
}
return command.Handler[idx]
}
func RemoveCommand(id, method string) error {
ky := CommandInfoKvPrefix + nex.GetCommandKey(id, method)
_, err := kv.Kv.Delete(context.Background(), ky)
return err
}

View File

@ -1,115 +0,0 @@
package directory
import (
"context"
"git.solsynth.dev/hypernet/nexus/pkg/proto"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"io"
"net/http"
"time"
)
type CommandRpcServer struct {
proto.UnimplementedCommandProviderServer
}
func (c *CommandRpcServer) AddCommand(ctx context.Context, info *proto.CommandInfo) (*proto.AddCommandResponse, error) {
clientId, err := GetClientId(ctx)
if err != nil {
return nil, err
}
service := GetServiceInstance(clientId)
if service == nil {
return nil, status.Errorf(codes.NotFound, "service not found")
}
AddCommand(info.GetId(), info.GetMethod(), info.GetTags(), service)
return &proto.AddCommandResponse{
IsSuccess: true,
}, nil
}
func (c *CommandRpcServer) RemoveCommand(ctx context.Context, request *proto.CommandLookupRequest) (*proto.RemoveCommandResponse, error) {
RemoveCommand(request.GetId(), request.GetMethod())
return &proto.RemoveCommandResponse{
IsSuccess: true,
}, nil
}
func (c *CommandRpcServer) SendCommand(ctx context.Context, argument *proto.CommandArgument) (*proto.CommandReturn, error) {
id := argument.GetCommand()
method := argument.GetMethod()
handler := GetCommandHandler(id, method)
if handler == nil {
return &proto.CommandReturn{
IsDelivered: false,
Status: http.StatusNotFound,
ContentType: "text/plain+error",
Payload: []byte("command not found"),
}, nil
}
conn, err := handler.GetGrpcConn()
if err != nil {
return &proto.CommandReturn{
IsDelivered: false,
Status: http.StatusServiceUnavailable,
ContentType: "text/plain+error",
Payload: []byte("service unavailable"),
}, nil
}
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel()
out, err := proto.NewCommandProviderClient(conn).SendCommand(ctx, argument)
if err != nil {
return &proto.CommandReturn{
IsDelivered: true,
Status: http.StatusInternalServerError,
ContentType: "text/plain+error",
Payload: []byte(err.Error()),
}, nil
}
out.IsDelivered = true
return out, nil
}
func (c *CommandRpcServer) SendStreamCommand(g proto.CommandProvider_SendStreamCommandServer) error {
for {
pck, err := g.Recv()
if err == io.EOF {
return nil
} else if err != nil {
return err
}
id := pck.GetCommand()
method := pck.GetMethod()
handler := GetCommandHandler(id, method)
if handler == nil {
return status.Errorf(codes.NotFound, "command not found")
}
conn, err := handler.GetGrpcConn()
ctx, cancel := context.WithTimeout(g.Context(), time.Second*10)
out, err := proto.NewCommandProviderClient(conn).SendCommand(ctx, pck)
cancel()
if err != nil {
_ = g.Send(&proto.CommandReturn{
IsDelivered: false,
Status: http.StatusInternalServerError,
ContentType: "text/plain+error",
Payload: []byte(err.Error()),
})
} else {
_ = g.Send(out)
}
}
}

View File

@ -1,9 +1,10 @@
package grpc
import (
"git.solsynth.dev/hypernet/nexus/pkg/internal/directory"
"net"
"git.solsynth.dev/hypernet/nexus/pkg/internal/directory"
"git.solsynth.dev/hypernet/nexus/pkg/proto"
"google.golang.org/grpc/reflection"
@ -29,7 +30,6 @@ func NewServer() *Server {
}
proto.RegisterDirectoryServiceServer(server.srv, &directory.ServiceRpcServer{})
proto.RegisterCommandProviderServer(server.srv, &directory.CommandRpcServer{})
proto.RegisterDatabaseServiceServer(server.srv, server)
proto.RegisterStreamServiceServer(server.srv, server)
proto.RegisterAllocatorServiceServer(server.srv, server)

View File

@ -4,7 +4,7 @@ import (
"context"
"fmt"
"git.solsynth.dev/hypernet/nexus/pkg/internal/http/ws"
"git.solsynth.dev/hypernet/nexus/pkg/internal/web/ws"
"github.com/rs/zerolog/log"
"git.solsynth.dev/hypernet/nexus/pkg/proto"

View File

@ -1,67 +0,0 @@
package api
import (
"context"
"fmt"
"git.solsynth.dev/hypernet/nexus/pkg/internal/directory"
"git.solsynth.dev/hypernet/nexus/pkg/proto"
"github.com/gofiber/fiber/v2"
"github.com/rs/zerolog/log"
"google.golang.org/grpc/metadata"
"strings"
"time"
)
func invokeCommand(c *fiber.Ctx) error {
command := c.Params("command")
method := strings.ToLower(c.Method())
handler := directory.GetCommandHandler(command, method)
if handler == nil {
return fiber.NewError(fiber.StatusNotFound, "command not found")
}
conn, err := handler.GetGrpcConn()
if err != nil {
return fiber.NewError(fiber.StatusServiceUnavailable, "service unavailable")
}
log.Debug().Str("id", command).Str("method", method).Msg("Invoking command from HTTP Gateway...")
var meta []string
meta = append(meta, "client_id", "http-gateway")
meta = append(meta, "net.ip", c.IP())
meta = append(meta, "http.user_agent", c.Get(fiber.HeaderUserAgent))
for k, v := range c.GetReqHeaders() {
meta = append(
meta,
strings.ToLower(fmt.Sprintf("header.%s", strings.ReplaceAll(k, "-", "_"))),
strings.Join(v, "\n"),
)
}
for k, v := range c.Queries() {
meta = append(
meta,
strings.ToLower(fmt.Sprintf("query.%s", strings.ReplaceAll(k, "-", "_"))),
v,
)
}
ctx := metadata.AppendToOutgoingContext(c.Context(), meta...)
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel()
out, err := proto.NewCommandProviderClient(conn).SendCommand(ctx, &proto.CommandArgument{
Command: command,
Method: method,
Payload: c.Body(),
})
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else {
c.Set(fiber.HeaderContentType, out.ContentType)
return c.Status(int(out.Status)).Send(out.Payload)
}
}

View File

@ -4,7 +4,7 @@ import (
pkg "git.solsynth.dev/hypernet/nexus/pkg/internal"
"git.solsynth.dev/hypernet/nexus/pkg/internal/auth"
"git.solsynth.dev/hypernet/nexus/pkg/internal/directory"
"git.solsynth.dev/hypernet/nexus/pkg/internal/http/ws"
"git.solsynth.dev/hypernet/nexus/pkg/internal/web/ws"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"github.com/gofiber/contrib/websocket"
"github.com/gofiber/fiber/v2"
@ -12,6 +12,8 @@ import (
)
func MapAPIs(app *fiber.App) {
app.Get("/check-ip", getClientIP)
// Some built-in public-accessible APIs
wellKnown := app.Group("/.well-known").Name("Well Known")
{
@ -22,7 +24,6 @@ func MapAPIs(app *fiber.App) {
"status": true,
})
})
wellKnown.Get("/check-ip", getClientIP)
wellKnown.Get("/directory/services", listExistsService)
wellKnown.Get("/openid-configuration", func(c *fiber.Ctx) error {
@ -50,6 +51,5 @@ func MapAPIs(app *fiber.App) {
// Common websocket gateway
app.Get("/ws", auth.ValidatorMiddleware, websocket.New(ws.Listen))
app.All("/inv/:command", invokeCommand)
app.All("/cgi/:service/*", forwardService)
}

View File

@ -1,10 +1,10 @@
package server
package web
import (
"time"
"git.solsynth.dev/hypernet/nexus/pkg/internal/auth"
"git.solsynth.dev/hypernet/nexus/pkg/internal/http/api"
"git.solsynth.dev/hypernet/nexus/pkg/internal/web/api"
"github.com/goccy/go-json"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
@ -15,11 +15,11 @@ import (
"github.com/spf13/viper"
)
type HTTPApp struct {
type WebApp struct {
app *fiber.App
}
func NewServer() *HTTPApp {
func NewServer() *WebApp {
app := fiber.New(fiber.Config{
DisableStartupMessage: true,
EnableIPValidation: true,
@ -55,10 +55,10 @@ func NewServer() *HTTPApp {
api.MapAPIs(app)
return &HTTPApp{app}
return &WebApp{app}
}
func (v *HTTPApp) Listen() {
func (v *WebApp) Listen() {
if err := v.app.Listen(viper.GetString("bind")); err != nil {
log.Fatal().Err(err).Msg("An error occurred when starting server...")
}