add web api

This commit is contained in:
ginuerzh
2022-02-08 23:46:54 +08:00
parent 0fba6d2500
commit f1eaef8d69
22 changed files with 1209 additions and 33 deletions

86
pkg/api/api.go Normal file
View File

@ -0,0 +1,86 @@
package api
import (
"net"
"net/http"
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/go-gost/gost/pkg/logger"
)
var (
apiServer = &http.Server{}
)
func init() {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(
cors.New((cors.Config{
AllowAllOrigins: true,
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowHeaders: []string{"*"},
})),
loggerHandler,
gin.Recovery(),
)
r.StaticFile("/swagger.yaml", "swagger.yaml")
config := r.Group("/config")
{
config.GET("", getConfig)
config.POST("/services", createService)
config.PUT("/services/:service", updateService)
config.DELETE("/services/:service", deleteService)
config.POST("/chains", createChain)
config.PUT("/chains/:chain", updateChain)
config.DELETE("/chains/:chain", deleteChain)
config.POST("/bypasses", createBypass)
config.PUT("/bypasses/:bypass", updateBypass)
config.DELETE("/bypasses/:bypass", deleteBypass)
config.POST("/resolvers", createResolver)
config.PUT("/resolvers/:resolver", updateResolver)
config.DELETE("/resolvers/:resolver", deleteResolver)
config.POST("/hosts", createHosts)
config.PUT("/hosts/:hosts", updateHosts)
config.DELETE("/hosts/:hosts", deleteHosts)
}
apiServer.Handler = r
}
type Response struct {
Code int `json:"code,omitempty"`
Msg string `json:"msg,omitempty"`
}
func Run(ln net.Listener) error {
return apiServer.Serve(ln)
}
func loggerHandler(ctx *gin.Context) {
// start time
startTime := time.Now()
// Processing request
ctx.Next()
duration := time.Since(startTime)
logger.Default().WithFields(map[string]interface{}{
"kind": "api",
"method": ctx.Request.Method,
"uri": ctx.Request.RequestURI,
"code": ctx.Writer.Status(),
"client": ctx.ClientIP(),
"duration": duration,
}).Infof("| %3d | %13v | %15s | %-7s %s",
ctx.Writer.Status(), duration, ctx.ClientIP(), ctx.Request.Method, ctx.Request.RequestURI)
}

53
pkg/api/config.go Normal file
View File

@ -0,0 +1,53 @@
package api
import (
"bytes"
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/gost/pkg/config"
)
// swagger:parameters getConfigRequest
type getConfigRequest struct {
// output format, one of yaml|json, default is json.
// in: query
Format string `form:"format" json:"format"`
}
// successful operation.
// swagger:response getConfigResponse
type getConfigResponse struct {
Config *config.Config
}
func getConfig(ctx *gin.Context) {
// swagger:route GET /config ConfigManagement getConfigRequest
//
// Get current config.
//
// Responses:
// 200: getConfigResponse
var req getConfigRequest
ctx.ShouldBindQuery(&req)
var resp getConfigResponse
resp.Config = config.Global()
buf := &bytes.Buffer{}
switch req.Format {
case "yaml":
default:
req.Format = "json"
}
resp.Config.Write(buf, req.Format)
contentType := "application/json"
if req.Format == "yaml" {
contentType = "text/x-yaml"
}
ctx.Data(http.StatusOK, contentType, buf.Bytes())
}

158
pkg/api/config_bypass.go Normal file
View File

@ -0,0 +1,158 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/gost/pkg/config"
"github.com/go-gost/gost/pkg/config/parsing"
"github.com/go-gost/gost/pkg/registry"
)
// swagger:parameters createBypassRequest
type createBypassRequest struct {
// in: body
Data config.BypassConfig `json:"data"`
}
// successful operation.
// swagger:response createBypassResponse
type createBypassResponse struct {
Data Response
}
func createBypass(ctx *gin.Context) {
// swagger:route POST /config/bypasses ConfigManagement createBypassRequest
//
// create a new bypass, the name of bypass must be unique in bypass list.
//
// Responses:
// 200: createBypassResponse
var req createBypassRequest
ctx.ShouldBindJSON(&req.Data)
if req.Data.Name == "" {
writeError(ctx, ErrInvalid)
return
}
v := parsing.ParseBypass(&req.Data)
if err := registry.Bypass().Register(req.Data.Name, v); err != nil {
writeError(ctx, ErrDup)
return
}
cfg := config.Global()
cfg.Bypasses = append(cfg.Bypasses, &req.Data)
config.SetGlobal(cfg)
ctx.JSON(http.StatusOK, Response{
Msg: "OK",
})
}
// swagger:parameters updateBypassRequest
type updateBypassRequest struct {
// in: path
// required: true
Bypass string `uri:"bypass" json:"bypass"`
// in: body
Data config.BypassConfig `json:"data"`
}
// successful operation.
// swagger:response updateBypassResponse
type updateBypassResponse struct {
Data Response
}
func updateBypass(ctx *gin.Context) {
// swagger:route PUT /config/bypasses/{bypass} ConfigManagement updateBypassRequest
//
// update bypass by name, the bypass must already exist.
//
// Responses:
// 200: updateBypassResponse
var req updateBypassRequest
ctx.ShouldBindUri(&req)
ctx.ShouldBindJSON(&req.Data)
if !registry.Bypass().IsRegistered(req.Bypass) {
writeError(ctx, ErrNotFound)
return
}
req.Data.Name = req.Bypass
v := parsing.ParseBypass(&req.Data)
registry.Bypass().Unregister(req.Bypass)
if err := registry.Bypass().Register(req.Bypass, v); err != nil {
writeError(ctx, ErrDup)
return
}
cfg := config.Global()
for i := range cfg.Bypasses {
if cfg.Bypasses[i].Name == req.Bypass {
cfg.Bypasses[i] = &req.Data
break
}
}
config.SetGlobal(cfg)
ctx.JSON(http.StatusOK, Response{
Msg: "OK",
})
}
// swagger:parameters deleteBypassRequest
type deleteBypassRequest struct {
// in: path
// required: true
Bypass string `uri:"bypass" json:"bypass"`
}
// successful operation.
// swagger:response deleteBypassResponse
type deleteBypassResponse struct {
Data Response
}
func deleteBypass(ctx *gin.Context) {
// swagger:route DELETE /config/bypasses/{bypass} ConfigManagement deleteBypassRequest
//
// delete bypass by name.
//
// Responses:
// 200: deleteBypassResponse
var req deleteBypassRequest
ctx.ShouldBindUri(&req)
svc := registry.Bypass().Get(req.Bypass)
if svc == nil {
writeError(ctx, ErrNotFound)
return
}
registry.Bypass().Unregister(req.Bypass)
cfg := config.Global()
bypasses := cfg.Bypasses
cfg.Bypasses = nil
for _, s := range bypasses {
if s.Name == req.Bypass {
continue
}
cfg.Bypasses = append(cfg.Bypasses, s)
}
config.SetGlobal(cfg)
ctx.JSON(http.StatusOK, Response{
Msg: "OK",
})
}

167
pkg/api/config_chain.go Normal file
View File

@ -0,0 +1,167 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/gost/pkg/config"
"github.com/go-gost/gost/pkg/config/parsing"
"github.com/go-gost/gost/pkg/registry"
)
// swagger:parameters createChainRequest
type createChainRequest struct {
// in: body
Data config.ChainConfig `json:"data"`
}
// successful operation.
// swagger:response createChainResponse
type createChainResponse struct {
Data Response
}
func createChain(ctx *gin.Context) {
// swagger:route POST /config/chains ConfigManagement createChainRequest
//
// create a new chain, the name of chain must be unique in chain list.
//
// Responses:
// 200: createChainResponse
var req createChainRequest
ctx.ShouldBindJSON(&req.Data)
if req.Data.Name == "" {
writeError(ctx, ErrInvalid)
return
}
v, err := parsing.ParseChain(&req.Data)
if err != nil {
writeError(ctx, ErrCreate)
return
}
if err := registry.Chain().Register(req.Data.Name, v); err != nil {
writeError(ctx, ErrDup)
return
}
cfg := config.Global()
cfg.Chains = append(cfg.Chains, &req.Data)
config.SetGlobal(cfg)
ctx.JSON(http.StatusOK, Response{
Msg: "OK",
})
}
// swagger:parameters updateChainRequest
type updateChainRequest struct {
// in: path
// required: true
// chain name
Chain string `uri:"chain" json:"chain"`
// in: body
Data config.ChainConfig `json:"data"`
}
// successful operation.
// swagger:response updateChainResponse
type updateChainResponse struct {
Data Response
}
func updateChain(ctx *gin.Context) {
// swagger:route PUT /config/chains/{chain} ConfigManagement updateChainRequest
//
// update chain by name, the chain must already exist.
//
// Responses:
// 200: updateChainResponse
var req updateChainRequest
ctx.ShouldBindUri(&req)
ctx.ShouldBindJSON(&req.Data)
if !registry.Chain().IsRegistered(req.Chain) {
writeError(ctx, ErrNotFound)
return
}
req.Data.Name = req.Chain
v, err := parsing.ParseChain(&req.Data)
if err != nil {
writeError(ctx, ErrCreate)
return
}
registry.Chain().Unregister(req.Chain)
if err := registry.Chain().Register(req.Chain, v); err != nil {
writeError(ctx, ErrDup)
return
}
cfg := config.Global()
for i := range cfg.Chains {
if cfg.Chains[i].Name == req.Chain {
cfg.Chains[i] = &req.Data
break
}
}
config.SetGlobal(cfg)
ctx.JSON(http.StatusOK, Response{
Msg: "OK",
})
}
// swagger:parameters deleteChainRequest
type deleteChainRequest struct {
// in: path
// required: true
Chain string `uri:"chain" json:"chain"`
}
// successful operation.
// swagger:response deleteChainResponse
type deleteChainResponse struct {
Data Response
}
func deleteChain(ctx *gin.Context) {
// swagger:route DELETE /config/chains/{chain} ConfigManagement deleteChainRequest
//
// delete chain by name.
//
// Responses:
// 200: deleteChainResponse
var req deleteChainRequest
ctx.ShouldBindUri(&req)
svc := registry.Chain().Get(req.Chain)
if svc == nil {
writeError(ctx, ErrNotFound)
return
}
registry.Chain().Unregister(req.Chain)
cfg := config.Global()
chains := cfg.Chains
cfg.Chains = nil
for _, s := range chains {
if s.Name == req.Chain {
continue
}
cfg.Chains = append(cfg.Chains, s)
}
config.SetGlobal(cfg)
ctx.JSON(http.StatusOK, Response{
Msg: "OK",
})
}

158
pkg/api/config_hosts.go Normal file
View File

@ -0,0 +1,158 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/gost/pkg/config"
"github.com/go-gost/gost/pkg/config/parsing"
"github.com/go-gost/gost/pkg/registry"
)
// swagger:parameters createHostsRequest
type createHostsRequest struct {
// in: body
Data config.HostsConfig `json:"data"`
}
// successful operation.
// swagger:response createHostsResponse
type createHostsesponse struct {
Data Response
}
func createHosts(ctx *gin.Context) {
// swagger:route POST /config/hosts ConfigManagement createHostsRequest
//
// create a new hosts, the name of the hosts must be unique in hosts list.
//
// Responses:
// 200: createHostsResponse
var req createHostsRequest
ctx.ShouldBindJSON(&req.Data)
if req.Data.Name == "" {
writeError(ctx, ErrInvalid)
return
}
v := parsing.ParseHosts(&req.Data)
if err := registry.Hosts().Register(req.Data.Name, v); err != nil {
writeError(ctx, ErrDup)
return
}
cfg := config.Global()
cfg.Hosts = append(cfg.Hosts, &req.Data)
config.SetGlobal(cfg)
ctx.JSON(http.StatusOK, Response{
Msg: "OK",
})
}
// swagger:parameters updateHostsRequest
type updateHostsRequest struct {
// in: path
// required: true
Hosts string `uri:"hosts" json:"hosts"`
// in: body
Data config.HostsConfig `json:"data"`
}
// successful operation.
// swagger:response updateHostsResponse
type updateHostsResponse struct {
Data Response
}
func updateHosts(ctx *gin.Context) {
// swagger:route PUT /config/hosts/{hosts} ConfigManagement updateHostsRequest
//
// update hosts by name, the hosts must already exist.
//
// Responses:
// 200: updateHostsResponse
var req updateHostsRequest
ctx.ShouldBindUri(&req)
ctx.ShouldBindJSON(&req.Data)
if !registry.Hosts().IsRegistered(req.Hosts) {
writeError(ctx, ErrNotFound)
return
}
req.Data.Name = req.Hosts
v := parsing.ParseHosts(&req.Data)
registry.Hosts().Unregister(req.Hosts)
if err := registry.Hosts().Register(req.Hosts, v); err != nil {
writeError(ctx, ErrDup)
return
}
cfg := config.Global()
for i := range cfg.Hosts {
if cfg.Hosts[i].Name == req.Hosts {
cfg.Hosts[i] = &req.Data
break
}
}
config.SetGlobal(cfg)
ctx.JSON(http.StatusOK, Response{
Msg: "OK",
})
}
// swagger:parameters deleteHostsRequest
type deleteHostsRequest struct {
// in: path
// required: true
Hosts string `uri:"hosts" json:"hosts"`
}
// successful operation.
// swagger:response deleteHostsResponse
type deleteHostsResponse struct {
Data Response
}
func deleteHosts(ctx *gin.Context) {
// swagger:route DELETE /config/hosts/{hosts} ConfigManagement deleteHostsRequest
//
// delete hosts by name.
//
// Responses:
// 200: deleteHostsResponse
var req deleteHostsRequest
ctx.ShouldBindUri(&req)
svc := registry.Hosts().Get(req.Hosts)
if svc == nil {
writeError(ctx, ErrNotFound)
return
}
registry.Hosts().Unregister(req.Hosts)
cfg := config.Global()
hosts := cfg.Hosts
cfg.Hosts = nil
for _, s := range hosts {
if s.Name == req.Hosts {
continue
}
cfg.Hosts = append(cfg.Hosts, s)
}
config.SetGlobal(cfg)
ctx.JSON(http.StatusOK, Response{
Msg: "OK",
})
}

166
pkg/api/config_resolver.go Normal file
View File

@ -0,0 +1,166 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/gost/pkg/config"
"github.com/go-gost/gost/pkg/config/parsing"
"github.com/go-gost/gost/pkg/registry"
)
// swagger:parameters createResolverRequest
type createResolverRequest struct {
// in: body
Data config.ResolverConfig `json:"data"`
}
// successful operation.
// swagger:response createResolverResponse
type createResolverResponse struct {
Data Response
}
func createResolver(ctx *gin.Context) {
// swagger:route POST /config/resolvers ConfigManagement createResolverRequest
//
// create a new resolver, the name of the resolver must be unique in resolver list.
//
// Responses:
// 200: createResolverResponse
var req createResolverRequest
ctx.ShouldBindJSON(&req.Data)
if req.Data.Name == "" {
writeError(ctx, ErrInvalid)
return
}
v, err := parsing.ParseResolver(&req.Data)
if err != nil {
writeError(ctx, ErrCreate)
return
}
if err := registry.Resolver().Register(req.Data.Name, v); err != nil {
writeError(ctx, ErrDup)
return
}
cfg := config.Global()
cfg.Resolvers = append(cfg.Resolvers, &req.Data)
config.SetGlobal(cfg)
ctx.JSON(http.StatusOK, Response{
Msg: "OK",
})
}
// swagger:parameters updateResolverRequest
type updateResolverRequest struct {
// in: path
// required: true
Resolver string `uri:"resolver" json:"resolver"`
// in: body
Data config.ResolverConfig `json:"data"`
}
// successful operation.
// swagger:response updateResolverResponse
type updateResolverResponse struct {
Data Response
}
func updateResolver(ctx *gin.Context) {
// swagger:route PUT /config/resolvers/{resolver} ConfigManagement updateResolverRequest
//
// update resolver by name, the resolver must already exist.
//
// Responses:
// 200: updateResolverResponse
var req updateResolverRequest
ctx.ShouldBindUri(&req)
ctx.ShouldBindJSON(&req.Data)
if !registry.Resolver().IsRegistered(req.Resolver) {
writeError(ctx, ErrNotFound)
return
}
req.Data.Name = req.Resolver
v, err := parsing.ParseResolver(&req.Data)
if err != nil {
writeError(ctx, ErrCreate)
return
}
registry.Resolver().Unregister(req.Resolver)
if err := registry.Resolver().Register(req.Resolver, v); err != nil {
writeError(ctx, ErrDup)
return
}
cfg := config.Global()
for i := range cfg.Resolvers {
if cfg.Resolvers[i].Name == req.Resolver {
cfg.Resolvers[i] = &req.Data
break
}
}
config.SetGlobal(cfg)
ctx.JSON(http.StatusOK, Response{
Msg: "OK",
})
}
// swagger:parameters deleteResolverRequest
type deleteResolverRequest struct {
// in: path
// required: true
Resolver string `uri:"resolver" json:"resolver"`
}
// successful operation.
// swagger:response deleteResolverResponse
type deleteResolverResponse struct {
Data Response
}
func deleteResolver(ctx *gin.Context) {
// swagger:route DELETE /config/resolvers/{resolver} ConfigManagement deleteResolverRequest
//
// delete resolver by name.
//
// Responses:
// 200: deleteResolverResponse
var req deleteResolverRequest
ctx.ShouldBindUri(&req)
svc := registry.Resolver().Get(req.Resolver)
if svc == nil {
writeError(ctx, ErrNotFound)
return
}
registry.Resolver().Unregister(req.Resolver)
cfg := config.Global()
resolvers := cfg.Resolvers
cfg.Resolvers = nil
for _, s := range resolvers {
if s.Name == req.Resolver {
continue
}
cfg.Resolvers = append(cfg.Resolvers, s)
}
config.SetGlobal(cfg)
ctx.JSON(http.StatusOK, Response{
Msg: "OK",
})
}

181
pkg/api/config_service.go Normal file
View File

@ -0,0 +1,181 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/gost/pkg/config"
"github.com/go-gost/gost/pkg/config/parsing"
"github.com/go-gost/gost/pkg/registry"
)
// swagger:parameters createServiceRequest
type createServiceRequest struct {
// in: body
Data config.ServiceConfig `json:"data"`
}
// successful operation.
// swagger:response createServiceResponse
type createServiceResponse struct {
Data Response
}
func createService(ctx *gin.Context) {
// swagger:route POST /config/services ConfigManagement createServiceRequest
//
// create a new service, the name of the service must be unique in service list.
//
// Responses:
// 200: createServiceResponse
var req createServiceRequest
ctx.ShouldBindJSON(&req.Data)
if req.Data.Name == "" {
writeError(ctx, ErrInvalid)
return
}
if registry.Service().IsRegistered(req.Data.Name) {
writeError(ctx, ErrDup)
return
}
svc, err := parsing.ParseService(&req.Data)
if err != nil {
writeError(ctx, ErrCreate)
return
}
if err := registry.Service().Register(req.Data.Name, svc); err != nil {
svc.Close()
writeError(ctx, ErrDup)
return
}
go svc.Run()
cfg := config.Global()
cfg.Services = append(cfg.Services, &req.Data)
config.SetGlobal(cfg)
ctx.JSON(http.StatusOK, Response{
Msg: "OK",
})
}
// swagger:parameters updateServiceRequest
type updateServiceRequest struct {
// in: path
// required: true
Service string `uri:"service" json:"service"`
// in: body
Data config.ServiceConfig `json:"data"`
}
// successful operation.
// swagger:response updateServiceResponse
type updateServiceResponse struct {
Data Response
}
func updateService(ctx *gin.Context) {
// swagger:route PUT /config/services/{service} ConfigManagement updateServiceRequest
//
// update service by name, the service must already exist.
//
// Responses:
// 200: updateServiceResponse
var req updateServiceRequest
ctx.ShouldBindUri(&req)
ctx.ShouldBindJSON(&req.Data)
old := registry.Service().Get(req.Service)
if old == nil {
writeError(ctx, ErrNotFound)
return
}
old.Close()
req.Data.Name = req.Service
svc, err := parsing.ParseService(&req.Data)
if err != nil {
writeError(ctx, ErrCreate)
return
}
registry.Service().Unregister(req.Service)
if err := registry.Service().Register(req.Service, svc); err != nil {
svc.Close()
writeError(ctx, ErrDup)
return
}
go svc.Run()
cfg := config.Global()
for i := range cfg.Services {
if cfg.Services[i].Name == req.Service {
cfg.Services[i] = &req.Data
break
}
}
config.SetGlobal(cfg)
ctx.JSON(http.StatusOK, Response{
Msg: "OK",
})
}
// swagger:parameters deleteServiceRequest
type deleteServiceRequest struct {
// in: path
// required: true
Service string `uri:"service" json:"service"`
}
// successful operation.
// swagger:response deleteServiceResponse
type deleteServiceResponse struct {
Data Response
}
func deleteService(ctx *gin.Context) {
// swagger:route DELETE /config/services/{service} ConfigManagement deleteServiceRequest
//
// delete service by name.
//
// Responses:
// 200: deleteServiceResponse
var req deleteServiceRequest
ctx.ShouldBindUri(&req)
svc := registry.Service().Get(req.Service)
if svc == nil {
writeError(ctx, ErrNotFound)
return
}
registry.Service().Unregister(req.Service)
svc.Close()
cfg := config.Global()
services := cfg.Services
cfg.Services = nil
for _, s := range services {
if s.Name == req.Service {
continue
}
cfg.Services = append(cfg.Services, s)
}
config.SetGlobal(cfg)
ctx.JSON(http.StatusOK, Response{
Msg: "OK",
})
}

17
pkg/api/doc.go Normal file
View File

@ -0,0 +1,17 @@
// Documentation of GOST Web API.
//
// Schemes: http
// BasePath: /
// Version: 1.0.0
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// SecurityDefinitions:
// api_key:
//
// swagger:meta
package api

44
pkg/api/error.go Normal file
View File

@ -0,0 +1,44 @@
package api
import (
"encoding/json"
"net/http"
"github.com/gin-gonic/gin"
)
var (
ErrInvalid = &Error{statusCode: http.StatusBadRequest, Code: 40001, Msg: "instance invalid"}
ErrDup = &Error{statusCode: http.StatusBadRequest, Code: 40002, Msg: "instance duplicated"}
ErrCreate = &Error{statusCode: http.StatusConflict, Code: 40003, Msg: "instance creation failed"}
ErrNotFound = &Error{statusCode: http.StatusBadRequest, Code: 40004, Msg: "instance not found"}
)
// Error is an api error.
type Error struct {
statusCode int
Code int `json:"code"`
Msg string `json:"msg"`
}
func (e *Error) Error() string {
b, _ := json.Marshal(e)
return string(b)
}
func writeError(c *gin.Context, err error) {
// c.Set(HTTPResponseTag, err)
c.JSON(getStatusCode(err), err)
}
func getStatusCode(err error) int {
if err == nil {
return http.StatusOK
}
if e, ok := err.(*Error); ok {
if e.statusCode >= http.StatusOK && e.statusCode < 600 {
return e.statusCode
}
}
return http.StatusInternalServerError
}