From 24960b69eecde1feb69912c77870e4eb6caed9eb Mon Sep 17 00:00:00 2001 From: Christian Plankl Date: Tue, 8 Feb 2022 19:07:48 +0100 Subject: [PATCH] Add http proxy cababitlity for rest-trigger --- trigger/rest/README.md | 130 +++++++++-------------------------- trigger/rest/descriptor.json | 19 +++-- trigger/rest/metadata.go | 5 +- trigger/rest/server.go | 23 +++---- trigger/rest/trigger.go | 26 +++++-- trigger/rest/trigger_test.go | 62 ++++++++++++----- 6 files changed, 124 insertions(+), 141 deletions(-) diff --git a/trigger/rest/README.md b/trigger/rest/README.md index d7332300..f4d35383 100644 --- a/trigger/rest/README.md +++ b/trigger/rest/README.md @@ -1,115 +1,49 @@ - # REST Trigger This trigger provides your flogo application the ability to start an action via REST over HTTP - +If the option `isPassThroughUri` is set, a pathParam is automatically create with the rest of the path after the give settings path of the handler ## Installation ```bash flogo install github.com/project-flogo/contrib/trigger/rest ``` +## TIBCO sandbox - Manual loading +```bash +zip the 'rest' directory (including the directory 'rest' ) +Go to https://eu.integration.cloud.tibco.com/envtools/flogo_extensions and upload the zip +ensure to increase the version nr in the description.json before uploading to distingues between versions +``` + ## Configuration ### Settings: -| Name | Type | Description -|:--- | :--- | :--- -| port | int | The port to listen on - **REQUIRED** -| enableTLS | bool | Enable TLS on the server -| certFile | string | The path to PEM encoded server certificate -| keyFile | string | The path to PEM encoded server key - +| Name | Type | Description | +|:----------|:-------|:-------------------------------------------| +| port | int | The port to listen on - **REQUIRED** | +| enableTLS | bool | Enable TLS on the server | +| certFile | string | The path to PEM encoded server certificate | +| keyFile | string | The path to PEM encoded server key | ### Handler Settings: -| Name | Type | Description -|:--- | :--- | :--- -| method | string | The HTTP method (ie. GET,POST,PUT,PATCH or DELETE) - **REQUIRED** -| path | string | The resource path - **REQUIRED** +| Name | Type | Description | +|:-----------|:--------|:-------------------------------------------------------------------| +| method | string | The HTTP method (ie. GET,POST,PUT,PATCH or DELETE) - **REQUIRED** | +| path | string | The resource path - **REQUIRED** | + ### Output: -| Name | Type | Description -|:--- | :--- | :--- -| pathParams | params | The path parameters (e.g., 'id' in http://.../pet/:id/name ) -| queryParams | params | The query parameters (e.g., 'id' in http://.../pet?id=someValue ) -| headers | params | The HTTP header parameters -| method | string | The HTTP method used for the request -| content | any | The content of the request +| Name | Type | Description | +|-------------|--------|-------------------------------------------------------------------| +| pathParams | params | The path parameters (e.g., 'id' in http://.../pet/:id/name ) | +| queryParams | params | The query parameters (e.g., 'id' in http://.../pet?id=someValue ) | +| headers | params | The HTTP header parameters | +| method | string | The HTTP method used for the request | +| content | any | The content of the request | ### Reply: -| Name | Type | Description -|:--- | :--- | :--- -| code | int | The http code to reply with -| data | any | The data to reply with -| headers | params | The HTTP response headers -| cookies | params | The HTTP response cookies to set (uses 'Set-Cookie' headers) - -## Example Configurations - -Triggers are configured via the triggers.json of your application. The following are some example configuration of the REST Trigger. - -### POST -Configure the Trigger to handle a POST on /device - -```json -{ - "triggers": [ - { - "id": "flogo-rest", - "ref": "github.com/project-flogo/contrib/trigger/rest", - "settings": { - "port": 8080 - }, - "handlers": [ - { - "settings": { - "method": "POST", - "path": "/device" - }, - "action": { - "ref": "github.com/project-flogo/flow", - "settings": { - "flowURI": "res://flow:new_device_flow" - } - } - } - ] - } - ] -} -``` - -### GET -Configure the Trigger to handle a GET on /device/:id - -```json -{ - "triggers": [ - { - "id": "flogo-rest", - "ref": "github.com/project-flogo/contrib/trigger/rest", - "settings": { - "port": 8080 - }, - "handlers": [ - { - "settings": { - "method": "GET", - "path": "/device/:id" - }, - "action": { - "ref": "github.com/project-flogo/flow", - "settings": { - "flowURI": "res://flow:get_device_flow" - }, - "input":{ - "deviceId":"=$.pathParams.id" - } - } - } - ] - } - ] -} -``` +| Name | Type | Description | +|---------|--------|--------------------------------------------------------------| +| code | int | The http code to reply with | +| data | any | The data to reply with | +| headers | params | The HTTP response headers | +| cookies | params | The HTTP response cookies to set (uses 'Set-Cookie' headers) | diff --git a/trigger/rest/descriptor.json b/trigger/rest/descriptor.json index a46ebd15..5d7c6b02 100644 --- a/trigger/rest/descriptor.json +++ b/trigger/rest/descriptor.json @@ -1,10 +1,10 @@ { - "name": "flogo-rest", + "name": "flogo-lp-rest", "type": "flogo:trigger", - "version": "0.10.0", - "title": "Receive HTTP Message", - "description": "Simple REST Trigger", - "homepage": "https://github.com/project-flogo/contrib/tree/master/trigger/rest", + "version": "1.0.0", + "title": "HTTP uri-path pass through", + "description": "Rest Trigger with uri-path pass through capabilities ", + "homepage": "https://gitlab.core-services.leaseplan.systems/workloads/0026-wkl-ng-integration/integration/shared/flogoresttrigger", "settings": [ { "name": "port", @@ -58,7 +58,7 @@ "reply": [ { "name": "code", - "type": "int", + "type": "number", "description": "The http code to reply with" }, { @@ -79,6 +79,13 @@ ], "handler": { "settings": [ + { + "name": "isPassThroughUri", + "type": "string", + "required" : true, + "allowed" : ["YES", "NO"], + "description": "use as a pass through will automatically add a 'partial' part of the defined 'path' as a pathParam with name 'restOfThePath'" + }, { "name": "method", "type": "string", diff --git a/trigger/rest/metadata.go b/trigger/rest/metadata.go index e3686ce1..5b4e46fc 100644 --- a/trigger/rest/metadata.go +++ b/trigger/rest/metadata.go @@ -12,8 +12,9 @@ type Settings struct { } type HandlerSettings struct { - Method string `md:"method,required,allowed(GET,POST,PUT,PATCH,DELETE)"` // The HTTP method (ie. GET,POST,PUT,PATCH or DELETE) - Path string `md:"path,required"` // The resource path + IsPassThroughUri string `md:"isPassThroughUri,required,allowed(YES,NO)"` // use as a pass through will automatically add a 'partial' part of the defined 'path' as a pathParam with name 'restOfThePath' + Method string `md:"method,required,allowed(GET,POST,PUT,PATCH,DELETE)"` // The HTTP method (ie. GET,POST,PUT,PATCH or DELETE) + Path string `md:"path,required"` // The resource path } type Output struct { diff --git a/trigger/rest/server.go b/trigger/rest/server.go index 6d1c51df..f891ae68 100644 --- a/trigger/rest/server.go +++ b/trigger/rest/server.go @@ -12,20 +12,20 @@ import ( ) const ( - httpDefaultAddr = ":http" //todo should this be :8080 + httpDefaultAddr = ":http" //todo should this be :8080 httpDefaultTlsAddr = ":https" //todo should this be :8443 - httpDefaultReadTimeout = 15 * time.Second + httpDefaultReadTimeout = 15 * time.Second httpDefaultWriteTimeout = 15 * time.Second ) type Server struct { running bool - srv *http.Server + srv *http.Server tlsEnabled bool - certFile string - keyFile string + certFile string + keyFile string } func NewServer(addr string, handler http.Handler, opts ...func(*Server)) (*Server, error) { @@ -36,10 +36,10 @@ func NewServer(addr string, handler http.Handler, opts ...func(*Server)) (*Serve srv := &Server{} srv.srv = &http.Server{ - Addr: addr, - Handler: handler, - ReadTimeout:httpDefaultReadTimeout, - WriteTimeout:httpDefaultWriteTimeout, + Addr: addr, + Handler: handler, + ReadTimeout: httpDefaultReadTimeout, + WriteTimeout: httpDefaultWriteTimeout, } for _, opt := range opts { @@ -53,7 +53,6 @@ func NewServer(addr string, handler http.Handler, opts ...func(*Server)) (*Serve return srv, nil } - /////////////////////// // Options @@ -145,7 +144,7 @@ func (s *Server) Stop() error { /////////////////////// // Validation Helpers -func (s *Server) validateStart() error { +func (s *Server) validateStart() error { //check if port is available ln, err := net.Listen("tcp", s.srv.Addr) @@ -157,7 +156,7 @@ func (s *Server) validateStart() error { return nil } -func (s *Server) validateInit() error { +func (s *Server) validateInit() error { if s.tlsEnabled { // using tls, so validate cert & key diff --git a/trigger/rest/trigger.go b/trigger/rest/trigger.go index 3cb0d06a..f374aad9 100644 --- a/trigger/rest/trigger.go +++ b/trigger/rest/trigger.go @@ -49,11 +49,11 @@ func (*Factory) New(config *trigger.Config) (trigger.Trigger, error) { // Trigger REST trigger struct type Trigger struct { - server *Server - settings *Settings - id string - logger log.Logger -// serverInstanceID string + server *Server + settings *Settings + id string + logger log.Logger + // serverInstanceID string } func (t *Trigger) Initialize(ctx trigger.InitContext) error { @@ -79,7 +79,12 @@ func (t *Trigger) Initialize(ctx trigger.InitContext) error { method := s.Method path := s.Path - + if s.IsPassThroughUri == "YES" { + if len(path) == 0 || path[len(path)-1] != '/' { + path = path + "/" + } + path = path + "*restOfThePath" + } t.logger.Debugf("Registering handler [%s: %s]", method, path) if _, ok := pathMap[path]; !ok { @@ -138,6 +143,13 @@ type IDResponse struct { ID string `json:"id"` } +func removeFirstCharIfForwardSlash(input string) string { + if input[0] == '/' { + return input[1:] + } + return input +} + func newActionHandler(rt *Trigger, method string, handler trigger.Handler) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { @@ -157,7 +169,7 @@ func newActionHandler(rt *Trigger, method string, handler trigger.Handler) httpr out.PathParams = make(map[string]string) for _, param := range ps { - out.PathParams[param.Key] = param.Value + out.PathParams[param.Key] = removeFirstCharIfForwardSlash(param.Value) // router 'catch all' path part remove '/' to make pathParams look the same } queryValues := r.URL.Query() diff --git a/trigger/rest/trigger_test.go b/trigger/rest/trigger_test.go index 289c66ed..ad6d9516 100644 --- a/trigger/rest/trigger_test.go +++ b/trigger/rest/trigger_test.go @@ -2,10 +2,10 @@ package rest import ( "context" - "crypto/tls" - "crypto/x509" "encoding/json" "fmt" + "net/http" + "strings" "sync" "testing" "time" @@ -27,14 +27,15 @@ func TestTrigger_Register(t *testing.T) { } const testConfig string = `{ - "id": "trigger-rest", - "ref": "github.com/project-flogo/contrib/trigger/rest", + "id": "trigger-restOld", + "ref": "github.com/project-flogo/contrib/trigger/restOld", "settings": { "port": "8888" }, "handlers": [ { "settings": { + "isPassThroughUri": "NO", "method": "GET", "path": "/test" }, @@ -69,7 +70,7 @@ func TestRestTrigger_Initialize(t *testing.T) { func Test_App(t *testing.T) { var wg sync.WaitGroup - app := myApp() + app := myApp("NO") e, err := api.NewEngine(app) @@ -84,35 +85,64 @@ func Test_App(t *testing.T) { go func() { time.Sleep(5 * time.Second) - roots := x509.NewCertPool() - - conn, err := tls.Dial("tcp", "localhost:5050", &tls.Config{ - RootCAs: roots, - }) + bodyReader := strings.NewReader("{\"marker\":\"hello\"}") + _, err := http.Post("http://localhost:5050", "application/json", bodyReader) if err != nil { panic("failed to connect: " + err.Error()) } - conn.Close() + if err != nil { assert.NotNil(t, err) wg.Done() } + wg.Done() + }() + wg.Wait() + fmt.Println("The response is") + e.Stop() +} + +func Test_AppWith(t *testing.T) { + var wg sync.WaitGroup + app := myApp("YES") + + e, err := api.NewEngine(app) + + if err != nil { + fmt.Println("Error:", err) + return + } + //assert.Nil(t, err) + + wg.Add(1) + go engine.RunEngine(e) + + go func() { + time.Sleep(5 * time.Second) + bodyReader := strings.NewReader("{\"marker\":\"hello\"}") + _, err := http.Post("http://localhost:5050", "application/json", bodyReader) + if err != nil { + panic("failed to connect: " + err.Error()) + } - //todo fix this - // /assert.Equal(t, "text/plain; charset=UTF-8", resp.Header.Get("Content-type")) + if err != nil { + assert.NotNil(t, err) + wg.Done() + } wg.Done() }() wg.Wait() fmt.Println("The response is") + e.Stop() } -func myApp() *api.App { +func myApp(isProxyValue string) *api.App { app := api.NewApp() - trg := app.NewTrigger(&Trigger{}, &Settings{Port: 5050, EnableTLS: true, CertFile: "/cert.pem", KeyFile: "/key.pem"}) + trg := app.NewTrigger(&Trigger{}, &Settings{Port: 5050}) - h, _ := trg.NewHandler(&HandlerSettings{Method: "GET", Path: "/test"}) + h, _ := trg.NewHandler(&HandlerSettings{IsPassThroughUri: isProxyValue, Method: "GET", Path: "/test"}) h.NewAction(RunActivities)