first version
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"rpirelay/config"
|
||||
"rpirelay/internal/api/relay"
|
||||
rt "rpirelay/internal/api/relay/transport"
|
||||
cc "rpirelay/internal/context"
|
||||
"rpirelay/internal/middleware"
|
||||
"rpirelay/internal/server"
|
||||
|
||||
"github.com/stianeikeland/go-rpio/v4"
|
||||
)
|
||||
|
||||
func Start(ctx context.Context, cfg *config.Config) chan error {
|
||||
errChan := make(chan error, 0)
|
||||
|
||||
go func() {
|
||||
if err := startAPIServer(ctx, cfg); err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
close(errChan)
|
||||
}()
|
||||
|
||||
return errChan
|
||||
}
|
||||
|
||||
func startAPIServer(ctx context.Context, cfg *config.Config) error {
|
||||
var err error
|
||||
|
||||
// init gpio
|
||||
err = rpio.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rpio.Close()
|
||||
|
||||
// set gpio pin
|
||||
relayPin := rpio.Pin(cfg.Server.RelayPin)
|
||||
// set pin mode output
|
||||
relayPin.Output()
|
||||
defer func() {
|
||||
relayPin.Low()
|
||||
}()
|
||||
|
||||
// new middleware
|
||||
mw := middleware.Initialize(cfg.Server.Secret)
|
||||
|
||||
svc := server.New(cfg)
|
||||
|
||||
apiGroup := svc.Group("/api")
|
||||
apiGroup.Use(cc.PatchContext(mw.APIPanicCatch()))
|
||||
{
|
||||
rt.NewHTTP(relay.Initialize(relayPin), apiGroup, mw.Authorization())
|
||||
}
|
||||
|
||||
svc.Start(ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package relay
|
||||
|
||||
import cc "rpirelay/internal/context"
|
||||
|
||||
var _ Service = (*Relay)(nil)
|
||||
|
||||
func (r *Relay) SwitchOn(c *cc.C) (err error) {
|
||||
r.pin.High()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Relay) SwitchOff(c *cc.C) (err error) {
|
||||
r.pin.Low()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Relay) GetState(c *cc.C) (open bool, err error) {
|
||||
state := r.pin.Read()
|
||||
|
||||
return state == 1, nil
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
cc "rpirelay/internal/context"
|
||||
|
||||
"github.com/stianeikeland/go-rpio/v4"
|
||||
)
|
||||
|
||||
func Initialize(pin rpio.Pin) *Relay {
|
||||
return &Relay{pin: pin}
|
||||
}
|
||||
|
||||
type Relay struct {
|
||||
pin rpio.Pin
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
SwitchOn(c *cc.C) (err error)
|
||||
SwitchOff(c *cc.C) (err error)
|
||||
GetState(c *cc.C) (open bool, err error)
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"rpirelay/internal/api/relay"
|
||||
|
||||
cc "rpirelay/internal/context"
|
||||
apierr "rpirelay/internal/errors"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type HTTP struct {
|
||||
svc relay.Service
|
||||
}
|
||||
|
||||
func NewHTTP(svc relay.Service, e *gin.RouterGroup, authorizationMW cc.CustomHandler) {
|
||||
h := &HTTP{svc: svc}
|
||||
patch := cc.PatchContext
|
||||
|
||||
g := e.Group("/relay")
|
||||
g.Use(patch(authorizationMW))
|
||||
|
||||
// swagger:route POST /api/relay/on relay switchOn
|
||||
// set relay on
|
||||
// security:
|
||||
// api_key:
|
||||
// responses:
|
||||
// default: respDefault
|
||||
g.POST("/on", patch(h.switchOn()))
|
||||
|
||||
// swagger:route POST /api/relay/off relay switchOff
|
||||
// set relay off
|
||||
// security:
|
||||
// api_key:
|
||||
// responses:
|
||||
// default: respDefault
|
||||
g.POST("/off", patch(h.switchOff()))
|
||||
|
||||
// swagger:route GET /api/relay relay getState
|
||||
// get relay state
|
||||
// security:
|
||||
// api_key:
|
||||
// responses:
|
||||
// 200: relayStateResp
|
||||
// default: respDefault
|
||||
g.GET("/", patch(h.getState()))
|
||||
g.GET("", patch(h.getState()))
|
||||
}
|
||||
|
||||
func (h *HTTP) switchOn() cc.CustomHandler {
|
||||
return func(c *cc.C) {
|
||||
if err := h.svc.SwitchOn(c); err != nil {
|
||||
panic(apierr.ErrInternalError.SetErr(errors.WithStack(err)))
|
||||
}
|
||||
|
||||
c.Success()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HTTP) switchOff() cc.CustomHandler {
|
||||
return func(c *cc.C) {
|
||||
if err := h.svc.SwitchOff(c); err != nil {
|
||||
panic(apierr.ErrInternalError.SetErr(errors.WithStack(err)))
|
||||
}
|
||||
|
||||
c.Success()
|
||||
}
|
||||
}
|
||||
|
||||
type relayStateResp struct {
|
||||
Open bool `json:"open"`
|
||||
}
|
||||
|
||||
func (h *HTTP) getState() cc.CustomHandler {
|
||||
return func(c *cc.C) {
|
||||
open, err := h.svc.GetState(c)
|
||||
if err != nil {
|
||||
panic(apierr.ErrInternalError.SetErr(errors.WithStack(err)))
|
||||
}
|
||||
|
||||
resp := relayStateResp{Open: open}
|
||||
|
||||
c.Success(resp)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package transport
|
||||
|
||||
// swagger:response relayStateResp
|
||||
type swaggRelayStateResp struct {
|
||||
// in: body
|
||||
Body relayStateResp
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// RPI Relay API.
|
||||
//
|
||||
// Terms Of Service:
|
||||
//
|
||||
// there are no TOS at this moment, use at your own risk we take no responsibility
|
||||
//
|
||||
// Schemes: http, https
|
||||
// Host: localhost
|
||||
// BasePath: /
|
||||
// Version: 0.0.1
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// SecurityDefinitions:
|
||||
// api_key:
|
||||
// type: apiKey
|
||||
// name: X-Lawsnote-Key
|
||||
// in: header
|
||||
// description: secret
|
||||
//
|
||||
// swagger:meta
|
||||
package api
|
||||
@@ -0,0 +1,70 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"rpirelay/internal/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type C struct {
|
||||
*gin.Context
|
||||
}
|
||||
|
||||
type CustomHandler func(c *C)
|
||||
|
||||
func PatchContext(handler CustomHandler) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
c := &C{
|
||||
Context: ctx,
|
||||
}
|
||||
handler(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Success
|
||||
// send success body to client
|
||||
func (c *C) Success(i ...interface{}) {
|
||||
r := response.Get(response.RespSuccess)
|
||||
|
||||
var resp interface{} = r.Body
|
||||
if len(i) > 0 {
|
||||
resp = i[0]
|
||||
}
|
||||
|
||||
c.AbortWithStatusJSON(r.Status, resp)
|
||||
}
|
||||
|
||||
func (c *C) DataFormat(code ...response.MessageCode) {
|
||||
r := response.Get(response.RespDataFormat, code...)
|
||||
c.AbortWithStatusJSON(r.Status, r.Body)
|
||||
}
|
||||
|
||||
func (c *C) NotFound(code ...response.MessageCode) {
|
||||
r := response.Get(response.RespNotFound, code...)
|
||||
c.AbortWithStatusJSON(r.Status, r.Body)
|
||||
}
|
||||
|
||||
func (c *C) Forbidden(code ...response.MessageCode) {
|
||||
r := response.Get(response.RespNotFound, code...)
|
||||
c.AbortWithStatusJSON(r.Status, r.Body)
|
||||
}
|
||||
|
||||
func (c *C) ServerError(code ...response.MessageCode) {
|
||||
r := response.Get(response.RespInternalError, code...)
|
||||
c.AbortWithStatusJSON(r.Status, r.Body)
|
||||
}
|
||||
|
||||
func (c *C) CustomResp(rt response.RespType, code response.MessageCode, data ...interface{}) {
|
||||
r := response.Get(rt)
|
||||
|
||||
if code != r.Body.MessageCode {
|
||||
r.Body.MessageCode, r.Body.Message = response.GetCodeMessage(code)
|
||||
}
|
||||
|
||||
var resp interface{} = r.Body
|
||||
if len(data) > 0 {
|
||||
resp = data[0]
|
||||
}
|
||||
|
||||
c.AbortWithStatusJSON(r.Status, resp)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"rpirelay/internal/response"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDataFormat = New(response.RespDataFormat)
|
||||
ErrUnauthorized = New(response.RespUnauthorized)
|
||||
ErrNotFound = New(response.RespNotFound)
|
||||
ErrInternalError = New(response.RespInternalError)
|
||||
ErrForbidden = New(response.RespForbidden)
|
||||
)
|
||||
|
||||
type APIError struct {
|
||||
code *response.MessageCode
|
||||
status response.RespType
|
||||
message string
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *APIError) Error() string {
|
||||
s := fmt.Sprintf("Status: %s, Message: %s", e.status, e.message)
|
||||
if e.code != nil {
|
||||
c, m := response.GetCodeMessage(*e.code)
|
||||
s = fmt.Sprintf("%s, Code: %d, CodeMessage: %s", s, c, m)
|
||||
}
|
||||
if e.err != nil {
|
||||
s += fmt.Sprintf("\n%+v", e.err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (e *APIError) SetCode(code response.MessageCode) *APIError {
|
||||
e = &APIError{
|
||||
status: e.status,
|
||||
code: &code,
|
||||
message: e.message,
|
||||
err: e.err,
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *APIError) SetMessage(s string) *APIError {
|
||||
e = &APIError{
|
||||
status: e.status,
|
||||
code: e.code,
|
||||
message: s,
|
||||
err: e.err,
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *APIError) SetErr(err error) *APIError {
|
||||
e = &APIError{
|
||||
status: e.status,
|
||||
code: e.code,
|
||||
message: e.message,
|
||||
err: err,
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *APIError) Code() *response.MessageCode { return e.code }
|
||||
func (e *APIError) Status() response.RespType { return e.status }
|
||||
|
||||
func New(status response.RespType, code ...response.MessageCode) *APIError {
|
||||
e := &APIError{
|
||||
status: status,
|
||||
}
|
||||
|
||||
if len(code) > 0 {
|
||||
e.code = &code[0]
|
||||
}
|
||||
return e
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
cc "rpirelay/internal/context"
|
||||
apierr "rpirelay/internal/errors"
|
||||
"rpirelay/internal/response"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func Initialize(secret string) *Middleware {
|
||||
return &Middleware{secret: secret}
|
||||
}
|
||||
|
||||
type Middleware struct {
|
||||
secret string
|
||||
}
|
||||
|
||||
func (m *Middleware) APIPanicCatch() cc.CustomHandler {
|
||||
return func(c *cc.C) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
// build error
|
||||
// *****
|
||||
builder := new(strings.Builder)
|
||||
builder.WriteString("[API Error Catch]\n")
|
||||
|
||||
pc := make([]uintptr, 10)
|
||||
n := runtime.Callers(1, pc)
|
||||
frames := runtime.CallersFrames(pc[:n])
|
||||
|
||||
builder.WriteString("\n[Stack]\n")
|
||||
for {
|
||||
frame, more := frames.Next()
|
||||
builder.WriteString(fmt.Sprintf("%s\n\t%s:%d\n", frame.Func.Name(), frame.File, frame.Line))
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
builder.WriteString("\n[Error]\n")
|
||||
builder.WriteString(fmt.Sprintf("%v\n", err))
|
||||
|
||||
fmt.Printf("%s\n", builder.String())
|
||||
// *****
|
||||
var e *apierr.APIError
|
||||
if converted, ok := err.(*apierr.APIError); ok {
|
||||
e = converted
|
||||
} else {
|
||||
e = apierr.ErrInternalError
|
||||
}
|
||||
|
||||
code := make([]response.MessageCode, 0)
|
||||
if c := e.Code(); c != nil {
|
||||
code = append(code, *c)
|
||||
}
|
||||
resp := response.Get(e.Status(), code...)
|
||||
|
||||
c.AbortWithStatusJSON(resp.Status, resp.Body)
|
||||
}
|
||||
}()
|
||||
|
||||
// 是json body 才做複製
|
||||
if c.GetHeader("content-type") == "application/json" {
|
||||
data, err := c.GetRawData()
|
||||
if err != nil {
|
||||
panic(apierr.ErrInternalError.SetErr(err))
|
||||
}
|
||||
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(data))
|
||||
c.Set("_RawBody", data)
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Middleware) Authorization() cc.CustomHandler {
|
||||
return func(c *cc.C) {
|
||||
auth := c.GetHeader("x-lawsnote-key")
|
||||
if auth == "" {
|
||||
panic(apierr.ErrUnauthorized.SetErr(errors.New("no lawsnote header found")))
|
||||
}
|
||||
|
||||
if auth != m.secret {
|
||||
panic(apierr.ErrForbidden.SetErr(errors.New("secret not match")))
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package response
|
||||
|
||||
type MessageCode int
|
||||
|
||||
type CodeMessage interface {
|
||||
String() string
|
||||
Code() int
|
||||
}
|
||||
|
||||
const (
|
||||
// HTTP Default Message
|
||||
CodeSuccess MessageCode = 1000
|
||||
CodeCreated MessageCode = 1001
|
||||
CodeAccepted MessageCode = 1002
|
||||
CodeNoContent MessageCode = 1003
|
||||
CodeRedirect MessageCode = 1004
|
||||
CodeDataFormat MessageCode = 1005
|
||||
CodeUnauthorized MessageCode = 1006
|
||||
CodeForbidden MessageCode = 1007
|
||||
CodeNotFound MessageCode = 1008
|
||||
CodeInternalError MessageCode = 1009
|
||||
|
||||
// Custom Message
|
||||
)
|
||||
|
||||
func (mc MessageCode) String() string {
|
||||
switch mc {
|
||||
case CodeSuccess:
|
||||
return "Success"
|
||||
case CodeCreated:
|
||||
return "Created"
|
||||
case CodeAccepted:
|
||||
return "Accepted"
|
||||
case CodeNoContent:
|
||||
return "No Content"
|
||||
case CodeRedirect:
|
||||
return "Moved Permanently"
|
||||
case CodeDataFormat:
|
||||
return "Data Format Error"
|
||||
case CodeUnauthorized:
|
||||
return "Unauthorized"
|
||||
case CodeForbidden:
|
||||
return "Forbidden"
|
||||
case CodeNotFound:
|
||||
return "Not Found"
|
||||
case CodeInternalError:
|
||||
return "Internal Error"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (mc MessageCode) Code() int {
|
||||
return int(mc)
|
||||
}
|
||||
|
||||
func GetCodeMessage(c MessageCode) (MessageCode, string) {
|
||||
return c, c.String()
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package response
|
||||
|
||||
type RespType string
|
||||
|
||||
const (
|
||||
RespSuccess RespType = "success"
|
||||
RespCreated RespType = "created"
|
||||
RespAccepted RespType = "accepted"
|
||||
RespNoContent RespType = "noContent"
|
||||
RespRedirect RespType = "redirect"
|
||||
RespDataFormat RespType = "dataFormat"
|
||||
RespUnauthorized RespType = "unauthorized"
|
||||
RespForbidden RespType = "forbidden"
|
||||
RespNotFound RespType = "notFound"
|
||||
RespInternalError RespType = "internalError"
|
||||
)
|
||||
|
||||
type RespBody struct {
|
||||
MessageCode MessageCode `json:"messageCode"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// default response
|
||||
// swagger:response respDefault
|
||||
type Resp struct {
|
||||
// in: body
|
||||
Body RespBody
|
||||
Status int
|
||||
}
|
||||
|
||||
func Get(key RespType, c ...MessageCode) Resp {
|
||||
r := Resp{Body: RespBody{}}
|
||||
|
||||
switch key {
|
||||
case RespSuccess:
|
||||
r.Status = 200
|
||||
r.Body.MessageCode, r.Body.Message = GetCodeMessage(CodeSuccess)
|
||||
break
|
||||
case RespCreated:
|
||||
r.Status = 201
|
||||
r.Body.MessageCode, r.Body.Message = GetCodeMessage(CodeCreated)
|
||||
break
|
||||
case RespAccepted:
|
||||
r.Status = 202
|
||||
r.Body.MessageCode, r.Body.Message = GetCodeMessage(CodeAccepted)
|
||||
break
|
||||
case RespNoContent:
|
||||
r.Status = 204
|
||||
r.Body.MessageCode, r.Body.Message = GetCodeMessage(CodeNoContent)
|
||||
break
|
||||
case RespRedirect:
|
||||
r.Status = 301
|
||||
r.Body.MessageCode, r.Body.Message = GetCodeMessage(CodeRedirect)
|
||||
break
|
||||
case RespDataFormat:
|
||||
r.Status = 400
|
||||
r.Body.MessageCode, r.Body.Message = GetCodeMessage(CodeDataFormat)
|
||||
break
|
||||
case RespUnauthorized:
|
||||
r.Status = 401
|
||||
r.Body.MessageCode, r.Body.Message = GetCodeMessage(CodeUnauthorized)
|
||||
break
|
||||
case RespForbidden:
|
||||
r.Status = 403
|
||||
r.Body.MessageCode, r.Body.Message = GetCodeMessage(CodeForbidden)
|
||||
break
|
||||
case RespNotFound:
|
||||
r.Status = 404
|
||||
r.Body.MessageCode, r.Body.Message = GetCodeMessage(CodeNotFound)
|
||||
break
|
||||
default:
|
||||
r.Status = 500
|
||||
r.Body.MessageCode, r.Body.Message = GetCodeMessage(CodeInternalError)
|
||||
}
|
||||
|
||||
if len(c) > 0 {
|
||||
r.Body.MessageCode, r.Body.Message = GetCodeMessage(c[0])
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"rpirelay/config"
|
||||
"time"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
*gin.Engine
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func New(cfg *config.Config) *Server {
|
||||
e := gin.New()
|
||||
|
||||
e.Use(gin.Logger())
|
||||
e.Use(gin.Recovery())
|
||||
|
||||
corsCfg := cors.DefaultConfig()
|
||||
corsCfg.AllowOriginFunc = func(origin string) bool { return origin != "" }
|
||||
corsCfg.AllowCredentials = true
|
||||
corsCfg.AddAllowHeaders("Authorization")
|
||||
|
||||
e.Use(cors.New(corsCfg))
|
||||
|
||||
if os.Getenv("RELEASE") != "1" {
|
||||
e.Use(SwaggerServe("/api-docs", nil))
|
||||
e.GET("/api-docs.json", SwaggerSpecServe())
|
||||
}
|
||||
|
||||
// set healthcheck path
|
||||
e.GET("/", func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "ok")
|
||||
})
|
||||
|
||||
return &Server{
|
||||
Engine: e,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Server) Start(ctx context.Context) {
|
||||
s := &http.Server{
|
||||
Handler: w,
|
||||
Addr: fmt.Sprintf(":%d", w.cfg.Server.Port),
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
s.ErrorLog.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
c, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
if err := s.Shutdown(c); err != nil {
|
||||
s.ErrorLog.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"regexp"
|
||||
rpiRelay "rpirelay"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SwaggerOptions struct {
|
||||
Root string
|
||||
}
|
||||
|
||||
var regex = regexp.MustCompile(`(src|href)="./`)
|
||||
var specRegex = regexp.MustCompile(`url:.+`)
|
||||
|
||||
func SwaggerSpecServe() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
b := rpiRelay.GetSpec()
|
||||
|
||||
var j map[string]interface{}
|
||||
if err := json.Unmarshal(b, &j); err != nil {
|
||||
c.String(http.StatusInternalServerError, "internal error")
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := j["host"]; ok {
|
||||
j["host"] = c.Request.Host
|
||||
}
|
||||
if xfp := c.Request.Header.Get(textproto.CanonicalMIMEHeaderKey("X-Forwarded-Proto")); xfp != "" && xfp == "https" {
|
||||
j["schemes"] = []string{xfp}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, j)
|
||||
}
|
||||
}
|
||||
|
||||
func SwaggerServe(prefix string, opts *SwaggerOptions) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
fp := c.Request.URL.Path
|
||||
|
||||
if prefix != "" {
|
||||
if !strings.HasPrefix(fp, prefix) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if fp = strings.Replace(fp, prefix, "", 1); fp == "" {
|
||||
fp = "/"
|
||||
}
|
||||
}
|
||||
|
||||
// end of slash replace to index.html file
|
||||
if strings.HasSuffix(fp, "/") {
|
||||
fp += "index.html"
|
||||
}
|
||||
|
||||
if b, mime, err := rpiRelay.GetWebFile(fp); err == nil {
|
||||
// 如果是 index.html 要更新內部的連結 轉換成絕對路徑
|
||||
if fp == "/index.html" {
|
||||
replPrefix := prefix
|
||||
if !strings.HasSuffix(replPrefix, "/") {
|
||||
replPrefix += "/"
|
||||
}
|
||||
|
||||
b = regex.ReplaceAll(b, []byte(fmt.Sprintf(`$1="%s`, replPrefix)))
|
||||
b = specRegex.ReplaceAll(b, []byte(fmt.Sprintf(`url: "%s.json",`, prefix)))
|
||||
}
|
||||
|
||||
c.Writer.Header().Set(textproto.CanonicalMIMEHeaderKey("Content-Type"), mime)
|
||||
c.Status(http.StatusOK)
|
||||
c.Writer.Write(b)
|
||||
c.Abort()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
Version string
|
||||
BuildDate string
|
||||
)
|
||||
|
||||
func PrintCliVersion() string {
|
||||
return fmt.Sprintf(
|
||||
"version: %s, built on %s, %s",
|
||||
Version,
|
||||
BuildDate,
|
||||
runtime.Version(),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user