mirror of
https://github.com/aljazceru/signal-cli-rest-api.git
synced 2026-01-06 08:24:49 +01:00
implemented plugin endpoints as shared objects
* the plugin mechanism is an optional extension to the REST API. As the plugin mechanism depends on gopher-lua (and a bunch of gopher-lua plugins), it adds quite some dependencies to the project. Since most of the REST API users won't need the plugin mechanism, it makes sense to move that functionality (including all the dependencies) to a dedicated shared object, which gets loaded when needed.
This commit is contained in:
@@ -10,7 +10,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"io"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -20,11 +19,6 @@ import (
|
||||
"github.com/bbernhard/signal-cli-rest-api/client"
|
||||
ds "github.com/bbernhard/signal-cli-rest-api/datastructs"
|
||||
utils "github.com/bbernhard/signal-cli-rest-api/utils"
|
||||
|
||||
"github.com/yuin/gopher-lua"
|
||||
"github.com/cjoudrey/gluahttp"
|
||||
"layeh.com/gopher-luar"
|
||||
luajson "layeh.com/gopher-json"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -2172,69 +2166,3 @@ func (a *Api) ListContacts(c *gin.Context) {
|
||||
|
||||
c.JSON(200, contacts)
|
||||
}
|
||||
|
||||
type PluginInputData struct {
|
||||
Params map[string]string
|
||||
Payload string
|
||||
}
|
||||
|
||||
type PluginOutputData struct {
|
||||
payload string
|
||||
httpStatusCode int
|
||||
}
|
||||
|
||||
func (p *PluginOutputData) SetPayload(payload string) {
|
||||
p.payload = payload
|
||||
}
|
||||
|
||||
func (p *PluginOutputData) Payload() string {
|
||||
return p.payload
|
||||
}
|
||||
|
||||
func (p *PluginOutputData) SetHttpStatusCode(httpStatusCode int) {
|
||||
p.httpStatusCode = httpStatusCode
|
||||
}
|
||||
|
||||
func (p *PluginOutputData) HttpStatusCode() int {
|
||||
return p.httpStatusCode
|
||||
}
|
||||
|
||||
func (a *Api) ExecutePlugin(c *gin.Context, pluginConfig utils.PluginConfig) {
|
||||
jsonData, err := io.ReadAll(c.Request.Body)
|
||||
if err != nil {
|
||||
c.JSON(400, Error{Msg: "Couldn't process request - invalid input data"})
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
pluginInputData := &PluginInputData{
|
||||
Params: make(map[string]string),
|
||||
Payload: string(jsonData),
|
||||
}
|
||||
|
||||
pluginOutputData := &PluginOutputData{
|
||||
payload: "",
|
||||
httpStatusCode: 200,
|
||||
}
|
||||
|
||||
parts := strings.Split(pluginConfig.Endpoint, "/")
|
||||
for _, part := range parts {
|
||||
if strings.HasPrefix(part, ":") {
|
||||
paramName := strings.TrimPrefix(part, ":")
|
||||
pluginInputData.Params[paramName] = c.Param(paramName)
|
||||
}
|
||||
}
|
||||
|
||||
l := lua.NewState()
|
||||
l.SetGlobal("pluginInputData", luar.New(l, pluginInputData))
|
||||
l.SetGlobal("pluginOutputData", luar.New(l, pluginOutputData))
|
||||
l.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader)
|
||||
luajson.Preload(l)
|
||||
defer l.Close()
|
||||
if err := l.DoFile(pluginConfig.ScriptPath); err != nil {
|
||||
c.JSON(400, Error{Msg: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(pluginOutputData.HttpStatusCode(), pluginOutputData.Payload())
|
||||
}
|
||||
|
||||
34
src/main.go
34
src/main.go
@@ -16,6 +16,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"plugin"
|
||||
)
|
||||
|
||||
// @title Signal Cli REST API
|
||||
@@ -62,15 +63,6 @@ import (
|
||||
// @schemes http
|
||||
// @BasePath /
|
||||
|
||||
func PluginHandler(api *api.Api, pluginConfig utils.PluginConfig) gin.HandlerFunc {
|
||||
fn := func(c *gin.Context) {
|
||||
api.ExecutePlugin(c, pluginConfig)
|
||||
}
|
||||
|
||||
return gin.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
signalCliConfig := flag.String("signal-cli-config", "/home/.local/share/signal-cli/", "Config directory where signal-cli config is stored")
|
||||
attachmentTmpDir := flag.String("attachment-tmp-dir", "/tmp/", "Attachment tmp directory")
|
||||
@@ -293,6 +285,22 @@ func main() {
|
||||
}
|
||||
|
||||
if utils.GetEnv("ENABLE_PLUGINS", "false") == "true" {
|
||||
signalCliRestApiPluginSharedObjDir := utils.GetEnv("SIGNAL_CLI_REST_API_PLUGIN_SHARED_OBJ_DIR", "")
|
||||
sharedObj, err := plugin.Open(signalCliRestApiPluginSharedObjDir + "signal-cli-rest-api_plugin_loader.so")
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't load shared object: ", err)
|
||||
}
|
||||
|
||||
pluginHandlerSymbol, err := sharedObj.Lookup("PluginHandler")
|
||||
if err != nil {
|
||||
log.Fatal("Couldn't get PluginHandler: ", err)
|
||||
}
|
||||
|
||||
pluginHandler, ok := pluginHandlerSymbol.(utils.PluginHandler)
|
||||
if !ok {
|
||||
log.Fatal("Couldn't cast PluginHandler")
|
||||
}
|
||||
|
||||
plugins := v1.Group("/plugins")
|
||||
{
|
||||
pluginConfigs := utils.NewPluginConfigs()
|
||||
@@ -303,13 +311,13 @@ func main() {
|
||||
|
||||
for _, pluginConfig := range pluginConfigs.Configs {
|
||||
if pluginConfig.Method == "GET" {
|
||||
plugins.GET(pluginConfig.Endpoint, PluginHandler(api, pluginConfig))
|
||||
plugins.GET(pluginConfig.Endpoint, pluginHandler.ExecutePlugin(pluginConfig))
|
||||
} else if pluginConfig.Method == "POST" {
|
||||
plugins.POST(pluginConfig.Endpoint, PluginHandler(api, pluginConfig))
|
||||
plugins.POST(pluginConfig.Endpoint, pluginHandler.ExecutePlugin(pluginConfig))
|
||||
} else if pluginConfig.Method == "DELETE" {
|
||||
plugins.DELETE(pluginConfig.Endpoint, PluginHandler(api, pluginConfig))
|
||||
plugins.DELETE(pluginConfig.Endpoint, pluginHandler.ExecutePlugin(pluginConfig))
|
||||
} else if pluginConfig.Method == "PUT" {
|
||||
plugins.PUT(pluginConfig.Endpoint, PluginHandler(api, pluginConfig))
|
||||
plugins.PUT(pluginConfig.Endpoint, pluginHandler.ExecutePlugin(pluginConfig))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
95
src/plugin_loader.go
Normal file
95
src/plugin_loader.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/yuin/gopher-lua"
|
||||
"github.com/cjoudrey/gluahttp"
|
||||
"layeh.com/gopher-luar"
|
||||
luajson "layeh.com/gopher-json"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/bbernhard/signal-cli-rest-api/utils"
|
||||
"github.com/bbernhard/signal-cli-rest-api/api"
|
||||
"strings"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type PluginInputData struct {
|
||||
Params map[string]string
|
||||
Payload string
|
||||
}
|
||||
|
||||
type PluginOutputData struct {
|
||||
payload string
|
||||
httpStatusCode int
|
||||
}
|
||||
|
||||
func (p *PluginOutputData) SetPayload(payload string) {
|
||||
p.payload = payload
|
||||
}
|
||||
|
||||
func (p *PluginOutputData) Payload() string {
|
||||
return p.payload
|
||||
}
|
||||
|
||||
func (p *PluginOutputData) SetHttpStatusCode(httpStatusCode int) {
|
||||
p.httpStatusCode = httpStatusCode
|
||||
}
|
||||
|
||||
func (p *PluginOutputData) HttpStatusCode() int {
|
||||
return p.httpStatusCode
|
||||
}
|
||||
|
||||
func execPlugin(c *gin.Context, pluginConfig utils.PluginConfig) {
|
||||
jsonData, err := io.ReadAll(c.Request.Body)
|
||||
if err != nil {
|
||||
c.JSON(400, api.Error{Msg: "Couldn't process request - invalid input data"})
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
pluginInputData := &PluginInputData{
|
||||
Params: make(map[string]string),
|
||||
Payload: string(jsonData),
|
||||
}
|
||||
|
||||
pluginOutputData := &PluginOutputData{
|
||||
payload: "",
|
||||
httpStatusCode: 200,
|
||||
}
|
||||
|
||||
parts := strings.Split(pluginConfig.Endpoint, "/")
|
||||
for _, part := range parts {
|
||||
if strings.HasPrefix(part, ":") {
|
||||
paramName := strings.TrimPrefix(part, ":")
|
||||
pluginInputData.Params[paramName] = c.Param(paramName)
|
||||
}
|
||||
}
|
||||
|
||||
l := lua.NewState()
|
||||
l.SetGlobal("pluginInputData", luar.New(l, pluginInputData))
|
||||
l.SetGlobal("pluginOutputData", luar.New(l, pluginOutputData))
|
||||
l.PreloadModule("http", gluahttp.NewHttpModule(&http.Client{}).Loader)
|
||||
luajson.Preload(l)
|
||||
defer l.Close()
|
||||
if err := l.DoFile(pluginConfig.ScriptPath); err != nil {
|
||||
c.JSON(400, api.Error{Msg: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(pluginOutputData.HttpStatusCode(), pluginOutputData.Payload())
|
||||
}
|
||||
|
||||
type plugHandler struct {
|
||||
}
|
||||
|
||||
func (p plugHandler) ExecutePlugin(pluginConfig utils.PluginConfig) gin.HandlerFunc {
|
||||
fn := func(c *gin.Context) {
|
||||
execPlugin(c, pluginConfig)
|
||||
}
|
||||
|
||||
return gin.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
//exported
|
||||
var PluginHandler plugHandler
|
||||
9
src/utils/plugin_handler.go
Normal file
9
src/utils/plugin_handler.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type PluginHandler interface {
|
||||
ExecutePlugin(pluginConfig PluginConfig) gin.HandlerFunc
|
||||
}
|
||||
Reference in New Issue
Block a user