diff --git a/action.go b/action.go index 3c36f2e..35772a4 100644 --- a/action.go +++ b/action.go @@ -5,7 +5,11 @@ import ( "encoding/json" "errors" "fmt" + "io/ioutil" + "net/http" + "net/url" "os" + "path/filepath" "strings" _ "github.com/project-flogo/contrib/function" @@ -43,6 +47,7 @@ func init() { } var actionMetadata = action.ToMetadata(&Settings{}, &Input{}, &Output{}) +var resourceMap = make(map[string]*api.Microgateway) // LoadResource loads the microgateway definition func (m *Manager) LoadResource(config *resource.Config) (*resource.Resource, error) { @@ -58,7 +63,6 @@ func (m *Manager) LoadResource(config *resource.Config) (*resource.Resource, err if err != nil { return nil, fmt.Errorf("error marshalling microgateway definition resource with id '%s', %s", config.ID, err.Error()) } - return resource.New("microgateway", definition), nil } @@ -123,8 +127,50 @@ func (f *Factory) New(config *action.Config) (action.Action, error) { var actionData *api.Microgateway if uri := act.settings.URI; uri != "" { + url, err := url.Parse(uri) + if err != nil { + return nil, err + } if resData := api.GetResource(uri); resData != nil { actionData = resData + } else if resData := resourceMap[uri]; resData != nil { + actionData = resData + } else if url.Scheme == "http" { + //get resource from http + res, err := http.Get(uri) + if err != nil { + return nil, fmt.Errorf("Error in accessing specified HTTP url") + } + resData, err := ioutil.ReadAll(res.Body) + res.Body.Close() + if err != nil { + return nil, fmt.Errorf("Error receving HTTP resource data") + } + var definition *api.Microgateway + err = json.Unmarshal(resData, &definition) + if err != nil { + return nil, fmt.Errorf("error marshalling microgateway definition resource") + } + resourceMap[uri] = definition + actionData = definition + } else if url.Scheme == "file" { + //get resource from local file system + resData, err := ioutil.ReadFile(filepath.FromSlash(uri[7:])) + if err != nil { + return nil, fmt.Errorf("File reading error") + } + + err = schema.Validate(resData) + if err != nil { + return nil, fmt.Errorf("error validating schema: %s", err.Error()) + } + var definition *api.Microgateway + err = json.Unmarshal(resData, &definition) + if err != nil { + return nil, fmt.Errorf("error marshalling microgateway definition resource") + } + resourceMap[uri] = definition + actionData = definition } else { // Load action data from resources resData := f.Manager.GetResource(uri) diff --git a/api/api.go b/api/api.go index 1b92e33..427aabd 100644 --- a/api/api.go +++ b/api/api.go @@ -21,6 +21,7 @@ func GetResource(name string) *Microgateway { resourcesMutex.RLock() defer resourcesMutex.RUnlock() return resources[name] + } // ClearResources clears the resources for testing diff --git a/examples/api/resource-handler/fileResource/README.md b/examples/api/resource-handler/fileResource/README.md new file mode 100644 index 0000000..a756c50 --- /dev/null +++ b/examples/api/resource-handler/fileResource/README.md @@ -0,0 +1,30 @@ +# Gateway using File Resource +This recipe is a gateway using the file resource. + +## Installation +* Install [Go](https://golang.org/) + +## Setup +```bash +git clone https://github.com/project-flogo/microgateway +cd microgateway/examples/api/resource-handler/fileResource +``` + +## Testing + +Start the gateway: +```bash +go run main.go +``` +and test below scenario. + +### Request is successful +Run the following command: +```bash +curl --request GET http://localhost:9096/endpoint +``` + +You should see: +```json +{"category":{"id":0,"name":"string"},"id":4,"name":"hc0x3yiw302","photoUrls":["string"],"status":"available","tags":[{"id":0,"name":"string"}]} +``` \ No newline at end of file diff --git a/examples/api/resource-handler/fileResource/main.go b/examples/api/resource-handler/fileResource/main.go new file mode 100644 index 0000000..cc56c22 --- /dev/null +++ b/examples/api/resource-handler/fileResource/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "github.com/project-flogo/core/engine" + "github.com/project-flogo/microgateway/examples" +) + +func main() { + e, err := examples.FileResourceHandlerExample("file://../../../json/resource-handler/fileResource/resource.json") + if err != nil { + panic(err) + } + engine.RunEngine(e) +} diff --git a/examples/api/resource-handler/httpResource/README.md b/examples/api/resource-handler/httpResource/README.md new file mode 100644 index 0000000..5601e86 --- /dev/null +++ b/examples/api/resource-handler/httpResource/README.md @@ -0,0 +1,35 @@ +# Gateway using HTTP Resource +This recipe is a gateway using the HTTP resource. The resource is downloaded from the requested HTTP server + +## Installation +* Install [Go](https://golang.org/) + +## Setup +```bash +git clone https://github.com/project-flogo/microgateway +cd microgateway/examples/api/resource-handler/httpResource +``` + +## Testing + +In terminal start the server first: +```bash +go run main.go -server +``` + +Start the gateway: +```bash +go run main.go +``` +and test below scenario. + +### Request is successful +Run the following command: +```bash +curl --request GET http://localhost:9096/endpoint +``` + +You should see: +```json +{"category":{"id":0,"name":"string"},"id":4,"name":"hc0x3yiw302","photoUrls":["string"],"status":"available","tags":[{"id":0,"name":"string"}]} +``` \ No newline at end of file diff --git a/examples/api/resource-handler/httpResource/main.go b/examples/api/resource-handler/httpResource/main.go new file mode 100644 index 0000000..8d6cd9d --- /dev/null +++ b/examples/api/resource-handler/httpResource/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "flag" + "fmt" + "html" + "io/ioutil" + "net/http" + + "github.com/project-flogo/core/engine" + "github.com/project-flogo/microgateway/examples" +) + +var ( + server = flag.Bool("server", false, "run the test server") +) + +const resource = `{ + "name": "Pets", + "steps": [{ + "service": "PetStorePets", + "input": { + "pathParams": "=$.payload.pathParams" + } + }], + "responses": [{ + "error": false, + "output": { + "code": 200, + "data": "=$.PetStorePets.outputs.data" + } + }], + "services": [{ + "name": "PetStorePets", + "description": "Get pets by ID from the petstore", + "ref": "github.com/project-flogo/contrib/activity/rest", + "settings": { + "uri": "http://petstore.swagger.io/v2/pet/:petId", + "method": "GET", + "headers": { + "Accept": "application/json" + } + } + }] +}` + +func main() { + + flag.Parse() + + if *server { + http.HandleFunc("/pets", func(w http.ResponseWriter, r *http.Request) { + fmt.Printf("url: %q\n", html.EscapeString(r.URL.Path)) + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + if err != nil { + panic(err) + } + fmt.Println(string(body)) + w.Header().Set("Content-Type", "application/json") + _, err = w.Write([]byte(resource)) + if err != nil { + panic(err) + } + }) + + err := http.ListenAndServe(":1234", nil) + if err != nil { + panic(err) + } + + return + } + + e, err := examples.HTTPResourceHandlerExample() + if err != nil { + panic(err) + } + engine.RunEngine(e) +} diff --git a/examples/examples.go b/examples/examples.go index a775f46..3be1be0 100644 --- a/examples/examples.go +++ b/examples/examples.go @@ -287,3 +287,75 @@ func AsyncGatewayExample() (engine.Engine, error) { return api.NewEngine(app) } + +// ResourceHandlerGateway :- read resource from file system +func FileResourceHandlerExample(uri string) (engine.Engine, error) { + app := api.NewApp() + + gateway := microapi.New("Pets") + service := gateway.NewService("PetStorePets", &rest.Activity{}) + service.SetDescription("Get pets by ID from the petstore") + service.AddSetting("uri", "http://petstore.swagger.io/v2/pet/:petId") + service.AddSetting("method", "GET") + service.AddSetting("headers", map[string]string{ + "Accept": "application/json", + }) + step := gateway.NewStep(service) + step.AddInput("pathParams", "=$.payload.pathParams") + response := gateway.NewResponse(false) + response.SetCode(200) + response.SetData("=$.PetStorePets.outputs.data") + + trg := app.NewTrigger(&trigger.Trigger{}, &trigger.Settings{Port: 9096}) + handler, err := trg.NewHandler(&trigger.HandlerSettings{ + Method: "GET", + Path: "/pets/:petId", + }) + if err != nil { + return nil, err + } + + _, err = handler.NewAction(µgateway.Action{}, map[string]interface{}{ + "uri": uri}) + if err != nil { + return nil, err + } + + return api.NewEngine(app) +} + +// ResourceHandlerGateway :- getting Http resource +func HTTPResourceHandlerExample() (engine.Engine, error) { + app := api.NewApp() + + gateway := microapi.New("Pets") + service := gateway.NewService("PetStorePets", &rest.Activity{}) + service.SetDescription("Get pets by ID from the petstore") + service.AddSetting("uri", "http://petstore.swagger.io/v2/pet/:petId") + service.AddSetting("method", "GET") + service.AddSetting("headers", map[string]string{ + "Accept": "application/json", + }) + step := gateway.NewStep(service) + step.AddInput("pathParams", "=$.payload.pathParams") + response := gateway.NewResponse(false) + response.SetCode(200) + response.SetData("=$.PetStorePets.outputs.data") + + trg := app.NewTrigger(&trigger.Trigger{}, &trigger.Settings{Port: 9096}) + handler, err := trg.NewHandler(&trigger.HandlerSettings{ + Method: "GET", + Path: "/pets/:petId", + }) + if err != nil { + return nil, err + } + + _, err = handler.NewAction(µgateway.Action{}, map[string]interface{}{ + "uri": "http://localhost:1234/pets"}) + if err != nil { + return nil, err + } + + return api.NewEngine(app) +} diff --git a/examples/examples_test.go b/examples/examples_test.go index cc39856..c018378 100644 --- a/examples/examples_test.go +++ b/examples/examples_test.go @@ -30,6 +30,10 @@ type handler struct { Slow bool } +type resourceHandler struct { + Slow bool +} + func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() _, err := ioutil.ReadAll(r.Body) @@ -48,6 +52,24 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } +func (h *resourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + _, err := ioutil.ReadAll(r.Body) + if err != nil { + panic(err) + } + + if h.Slow { + time.Sleep(10 * time.Second) + } + + w.Header().Set("Content-Type", "application/json") + _, err = w.Write([]byte(resource)) + if err != nil { + panic(err) + } +} + const reply = `{ "id": 1, "category": { @@ -60,6 +82,35 @@ const reply = `{ "status":"available" }` +const resource = `{ + "name": "Pets", + "steps": [{ + "service": "PetStorePets", + "input": { + "pathParams": "=$.payload.pathParams" + } + }], + "responses": [{ + "error": false, + "output": { + "code": 200, + "data": "=$.PetStorePets.outputs.data" + } + }], + "services": [{ + "name": "PetStorePets", + "description": "Get pets by ID from the petstore", + "ref": "github.com/project-flogo/contrib/activity/rest", + "settings": { + "uri": "http://petstore.swagger.io/v2/pet/:petId", + "method": "GET", + "headers": { + "Accept": "application/json" + } + } + }] +}` + func testBasicGatewayApplication(t *testing.T, e engine.Engine) { defer api.ClearResources() test.Drain("9096") @@ -346,7 +397,6 @@ func testAsyncGatewayExample(t *testing.T, e engine.Engine) { } body := request() - fmt.Println("body is:", body) assert.NotEqual(t, 0, len(body)) } @@ -372,3 +422,176 @@ func TestAsyncGatewayExampleJSON(t *testing.T) { assert.Nil(t, err) testAsyncGatewayExample(t, e) } + +func testResourceHandlerExampleFile(t *testing.T, e engine.Engine) { + defer api.ClearResources() + + test.Drain("9096") + err := e.Start() + assert.Nil(t, err) + defer func() { + err := e.Stop() + assert.Nil(t, err) + }() + test.Pour("9096") + + transport := &http.Transport{ + MaxIdleConns: 1, + } + defer transport.CloseIdleConnections() + client := &http.Client{ + Transport: transport, + } + request := func() string { + req, err := http.NewRequest(http.MethodGet, "http://localhost:9096/pets/4", nil) + assert.Nil(t, err) + response, err := client.Do(req) + assert.Nil(t, err) + body, err := ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + return string(body) + } + + body := request() + assert.NotEqual(t, 0, len(body)) +} + +func TestResourceHandlerExampleAPI_File(t *testing.T) { + if testing.Short() { + t.Skip("skipping Basic Gateway API integration test in short mode") + } + + e, err := FileResourceHandlerExample("file://./json/resource-handler/fileResource/resource.json") + assert.Nil(t, err) + testResourceHandlerExampleFile(t, e) +} + +func TestResourceHandlerExampleJSON_File(t *testing.T) { + if testing.Short() { + t.Skip("skipping Basic Gateway JSON integration test in short mode") + } + data, err := ioutil.ReadFile(filepath.FromSlash("./json/resource-handler/fileResource/flogo.json")) + assert.Nil(t, err) + flogoApp := FlogoJSON{} + err = json.Unmarshal(data, &flogoApp) + assert.Nil(t, err) + flogoApp.Actions[0].Settings["uri"] = "file://./json/resource-handler/fileResource/resource.json" + data, err = json.Marshal(&flogoApp) + assert.Nil(t, err) + cfg, err := engine.LoadAppConfig(string(data), false) + assert.Nil(t, err) + e, err := engine.New(cfg) + assert.Nil(t, err) + testResourceHandlerExampleFile(t, e) +} + +func testResourceHandlerExampleHTTP(t *testing.T, e engine.Engine) { + defer api.ClearResources() + test.Drain("9096") + err := e.Start() + assert.Nil(t, err) + defer func() { + err := e.Stop() + assert.Nil(t, err) + }() + test.Pour("9096") + + transport := &http.Transport{ + MaxIdleConns: 1, + } + defer transport.CloseIdleConnections() + client := &http.Client{ + Transport: transport, + } + request := func() string { + req, err := http.NewRequest(http.MethodGet, "http://localhost:9096/pets/4", nil) + assert.Nil(t, err) + response, err := client.Do(req) + assert.Nil(t, err) + body, err := ioutil.ReadAll(response.Body) + assert.Nil(t, err) + response.Body.Close() + return string(body) + } + + body := request() + assert.NotEqual(t, 0, len(body)) +} + +func TestResourceHandlerExampleAPI_HTTP(t *testing.T) { + if testing.Short() { + t.Skip("skipping Basic Gateway API integration test in short mode") + } + test.Drain("1234") + testHandler := resourceHandler{} + s := &http.Server{ + Addr: ":1234", + Handler: &testHandler, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } + go func() { + s.ListenAndServe() + }() + test.Pour("1234") + defer s.Shutdown(context.Background()) + e, err := HTTPResourceHandlerExample() + assert.Nil(t, err) + testResourceHandlerExampleHTTP(t, e) +} + +func TestResourceHandlerExampleJSON_HTTP(t *testing.T) { + if testing.Short() { + t.Skip("skipping Basic Gateway JSON integration test in short mode") + } + data, err := ioutil.ReadFile(filepath.FromSlash("./json/resource-handler/httpResource/flogo.json")) + assert.Nil(t, err) + cfg, err := engine.LoadAppConfig(string(data), false) + assert.Nil(t, err) + test.Drain("1234") + testHandler := resourceHandler{} + s := &http.Server{ + Addr: ":1234", + Handler: &testHandler, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } + go func() { + s.ListenAndServe() + }() + test.Pour("1234") + defer s.Shutdown(context.Background()) + + e, err := engine.New(cfg) + assert.Nil(t, err) + testResourceHandlerExampleHTTP(t, e) +} + +// FlogoJSON is the flogo JSON +type FlogoJSON struct { + Name string `json:"name"` + Type string `json:"type"` + Version string `json:"version"` + Desc string `json:"description"` + Prop interface{} `json:"properties"` + Channels interface{} `json:"channels"` + Trig interface{} `json:"triggers"` + Resources []struct { + ID string `json:"id"` + Compress bool `json:"compressed"` + Data struct { + Name string `json:"name"` + Steps []interface{} `json:"steps"` + Responses []interface{} `json:"responses"` + Services []map[string]interface{} `json:"services"` + } `json:"data"` + } `json:"resources"` + Actions []struct { + Ref string `json:"ref"` + Settings map[string]interface{} `json:"settings"` + ID string `json:"id"` + } `json:"actions"` +} diff --git a/examples/json/resource-handler/fileResource/README.md b/examples/json/resource-handler/fileResource/README.md new file mode 100644 index 0000000..f4ddcea --- /dev/null +++ b/examples/json/resource-handler/fileResource/README.md @@ -0,0 +1,37 @@ +# Gateway using File Resource +This recipe is a gateway using the file resource. + +## Installation +* Install [Go](https://golang.org/) +* Install the flogo [cli](https://github.com/project-flogo/cli) + +## Setup +```bash +git clone https://github.com/project-flogo/microgateway +cd microgateway/examples/json/resource-handler/fileResource +``` + +## Testing +Create the gateway: +``` +flogo create -f flogo.json +cd MyProxy +flogo install github.com/project-flogo/contrib/activity/rest +flogo build +``` + +Start the gateway: +``` +bin/MyProxy +``` +and test below scenario. + + +### Request is successful +```bash +curl http://localhost:9096/pets/1 +``` + +You should then see something like: +```json +{"category":{"id":0,"name":"string"},"id":1,"name":"aspen","photoUrls":["string"],"status":"done","tags":[{"id":0,"name":"string"}]} \ No newline at end of file diff --git a/examples/json/resource-handler/fileResource/flogo.json b/examples/json/resource-handler/fileResource/flogo.json new file mode 100644 index 0000000..b7b729a --- /dev/null +++ b/examples/json/resource-handler/fileResource/flogo.json @@ -0,0 +1,41 @@ +{ + "name": "MyProxy", + "type": "flogo:app", + "version": "1.0.0", + "description": "This is a simple proxy.", + "properties": null, + "channels": null, + "triggers": [ + { + "name": "flogo-rest", + "id": "MyProxy", + "ref": "github.com/project-flogo/contrib/trigger/rest", + "settings": { + "port": "9096" + }, + "handlers": [ + { + "settings": { + "method": "GET", + "path": "/pets/:petId" + }, + "actions": [ + { + "id": "microgateway:Pets" + } + ] + } + ] + } + ], + "actions": [ + { + "ref": "github.com/project-flogo/microgateway", + "settings": { + "uri": "file://../resource.json" + }, + "id": "microgateway:Pets", + "metadata": null + } + ] +} diff --git a/examples/json/resource-handler/fileResource/resource.json b/examples/json/resource-handler/fileResource/resource.json new file mode 100644 index 0000000..f343a9e --- /dev/null +++ b/examples/json/resource-handler/fileResource/resource.json @@ -0,0 +1,28 @@ +{ + "name": "Pets", + "steps": [{ + "service": "PetStorePets", + "input": { + "pathParams": "=$.payload.pathParams" + } + }], + "responses": [{ + "error": false, + "output": { + "code": 200, + "data": "=$.PetStorePets.outputs.data" + } + }], + "services": [{ + "name": "PetStorePets", + "description": "Get pets by ID from the petstore", + "ref": "github.com/project-flogo/contrib/activity/rest", + "settings": { + "uri": "http://petstore.swagger.io/v2/pet/:petId", + "method": "GET", + "headers": { + "Accept": "application/json" + } + } + }] +} \ No newline at end of file diff --git a/examples/json/resource-handler/httpResource/README.md b/examples/json/resource-handler/httpResource/README.md new file mode 100644 index 0000000..c25a714 --- /dev/null +++ b/examples/json/resource-handler/httpResource/README.md @@ -0,0 +1,42 @@ +# Gateway using HTTP Resource +This recipe is a gateway using the HTTP resource. The resource is downloaded from the requested HTTP server + +## Installation +* Install [Go](https://golang.org/) +* Install the flogo [cli](https://github.com/project-flogo/cli) + +## Setup +```bash +git clone https://github.com/project-flogo/microgateway +cd microgateway/examples/json/resource-handler/httpResource +``` + +## Testing +Create the gateway: +``` +flogo create -f flogo.json +cd MyProxy +flogo install github.com/project-flogo/contrib/activity/rest +flogo build +``` + +In another terminal start the server first: +```bash +go run main.go -server +``` + +Start the gateway: +``` +bin/MyProxy +``` +and test below scenario. + +### Request is successful +```bash +curl http://localhost:9096/pets/1 +``` + +You should then see something like: +```json +{"category":{"id":0,"name":"string"},"id":4,"name":"hc0x3yiw302","photoUrls":["string"],"status":"available","tags":[{"id":0,"name":"string"}]} +``` \ No newline at end of file diff --git a/examples/json/resource-handler/httpResource/flogo.json b/examples/json/resource-handler/httpResource/flogo.json new file mode 100644 index 0000000..15aa27e --- /dev/null +++ b/examples/json/resource-handler/httpResource/flogo.json @@ -0,0 +1,41 @@ +{ + "name": "MyProxy", + "type": "flogo:app", + "version": "1.0.0", + "description": "This is a simple proxy.", + "properties": null, + "channels": null, + "triggers": [ + { + "name": "flogo-rest", + "id": "MyProxy", + "ref": "github.com/project-flogo/contrib/trigger/rest", + "settings": { + "port": "9096" + }, + "handlers": [ + { + "settings": { + "method": "GET", + "path": "/pets/:petId" + }, + "actions": [ + { + "id": "microgateway:Pets" + } + ] + } + ] + } + ], + "actions": [ + { + "ref": "github.com/project-flogo/microgateway", + "settings": { + "uri": "http://localhost:1234/pets" + }, + "id": "microgateway:Pets", + "metadata": null + } + ] +} diff --git a/examples/json/resource-handler/httpResource/main.go b/examples/json/resource-handler/httpResource/main.go new file mode 100644 index 0000000..3b2aace --- /dev/null +++ b/examples/json/resource-handler/httpResource/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "flag" + "fmt" + "html" + "io/ioutil" + "net/http" +) + +var ( + server = flag.Bool("server", false, "run the test server") +) + +const resource = `{ + "name": "Pets", + "steps": [{ + "service": "PetStorePets", + "input": { + "pathParams": "=$.payload.pathParams" + } + }], + "responses": [{ + "error": false, + "output": { + "code": 200, + "data": "=$.PetStorePets.outputs.data" + } + }], + "services": [{ + "name": "PetStorePets", + "description": "Get pets by ID from the petstore", + "ref": "github.com/project-flogo/contrib/activity/rest", + "settings": { + "uri": "http://petstore.swagger.io/v2/pet/:petId", + "method": "GET", + "headers": { + "Accept": "application/json" + } + } + }] +}` + +func main() { + flag.Parse() + + if *server { + http.HandleFunc("/pets", func(w http.ResponseWriter, r *http.Request) { + fmt.Printf("url: %q\n", html.EscapeString(r.URL.Path)) + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + if err != nil { + panic(err) + } + fmt.Println(string(body)) + w.Header().Set("Content-Type", "application/json") + _, err = w.Write([]byte(resource)) + if err != nil { + panic(err) + } + }) + err := http.ListenAndServe(":1234", nil) + if err != nil { + panic(err) + } + + return + } +}