mirror of
https://github.com/aljazceru/signal-cli-rest-api.git
synced 2026-02-05 15:14:37 +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:
@@ -136,13 +136,17 @@ COPY src/scripts /tmp/signal-cli-rest-api-src/scripts
|
||||
COPY src/main.go /tmp/signal-cli-rest-api-src/
|
||||
COPY src/go.mod /tmp/signal-cli-rest-api-src/
|
||||
COPY src/go.sum /tmp/signal-cli-rest-api-src/
|
||||
COPY src/plugin_loader.go /tmp/signal-cli-rest-api-src/
|
||||
|
||||
# build signal-cli-rest-api
|
||||
RUN cd /tmp/signal-cli-rest-api-src && swag init && go test ./client -v && go build
|
||||
RUN cd /tmp/signal-cli-rest-api-src && swag init && go test ./client -v && go build -o signal-cli-rest-api main.go
|
||||
|
||||
# build supervisorctl_config_creator
|
||||
RUN cd /tmp/signal-cli-rest-api-src/scripts && go build -o jsonrpc2-helper
|
||||
|
||||
# build plugin_loader
|
||||
RUN cd /tmp/signal-cli-rest-api-src && go build -buildmode=plugin -o signal-cli-rest-api_plugin_loader.so plugin_loader.go
|
||||
|
||||
# Start a fresh container for release container
|
||||
|
||||
# eclipse-temurin doesn't provide a OpenJDK 21 image for armv7 (see https://github.com/adoptium/containers/issues/502). Until this
|
||||
@@ -159,6 +163,7 @@ ARG SIGNAL_CLI_VERSION
|
||||
ARG BUILD_VERSION_ARG
|
||||
|
||||
ENV BUILD_VERSION=$BUILD_VERSION_ARG
|
||||
ENV SIGNAL_CLI_REST_API_PLUGIN_SHARED_OBJ_DIR=/usr/bin/
|
||||
|
||||
RUN dpkg-reconfigure debconf --frontend=noninteractive \
|
||||
&& apt-get -qq update \
|
||||
@@ -169,6 +174,7 @@ COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/signal-cli-rest-api /usr
|
||||
COPY --from=buildcontainer /opt/signal-cli-${SIGNAL_CLI_VERSION} /opt/signal-cli-${SIGNAL_CLI_VERSION}
|
||||
COPY --from=buildcontainer /tmp/signal-cli-${SIGNAL_CLI_VERSION}-source/build/native/nativeCompile/signal-cli /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli-native
|
||||
COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/scripts/jsonrpc2-helper /usr/bin/jsonrpc2-helper
|
||||
COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/signal-cli-rest-api_plugin_loader.so /usr/bin/signal-cli-rest-api_plugin_loader.so
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
|
||||
|
||||
@@ -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