github.com/vmware/transport-go@v1.3.4/plank/services/ping-pong-service.go (about) 1 // Copyright 2019-2021 VMware, Inc. 2 // SPDX-License-Identifier: BSD-2-Clause 3 4 package services 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "github.com/google/uuid" 10 "github.com/gorilla/mux" 11 "github.com/vmware/transport-go/model" 12 "github.com/vmware/transport-go/service" 13 "io/ioutil" 14 "net/http" 15 "time" 16 ) 17 18 const ( 19 PingPongServiceChan = "ping-pong-service" 20 ) 21 22 // PingPongService is a very simple service to demonstrate how request-response cycles are handled in Transport & Plank. 23 // this service has two requests named "ping-post" and "ping-get", the first accepts the payload and expects it to be of 24 // a POJO type (e.g. {"anything": "here"}), whereas the second expects the payload to be a pure string. 25 // a request made through the Event Bus API like bus.RequestOnce() will be routed to HandleServiceRequest() 26 // which will match the request's Request to the list of available service request types and return the response. 27 type PingPongService struct{} 28 29 func NewPingPongService() *PingPongService { 30 return &PingPongService{} 31 } 32 33 // Init will fire when the service is being registered by the fabric, it passes a reference of the same core 34 // Passed through when implementing HandleServiceRequest 35 func (ps *PingPongService) Init(core service.FabricServiceCore) error { 36 37 // set default headers for this service. 38 core.SetHeaders(map[string]string{ 39 "Content-Type": "application/json", 40 }) 41 42 return nil 43 } 44 45 // HandleServiceRequest routes the incoming request and based on the Request property of request, it invokes the 46 // appropriate handler logic defined and separated by a switch statement like the one shown below. 47 func (ps *PingPongService) HandleServiceRequest(request *model.Request, core service.FabricServiceCore) { 48 switch request.Request { 49 // ping-post request type accepts the payload as a POJO 50 case "ping-post": 51 m := make(map[string]interface{}) 52 m["timestamp"] = time.Now().Unix() 53 err := json.Unmarshal(request.Payload.([]byte), &m) 54 if err != nil { 55 core.SendErrorResponse(request, 400, err.Error()) 56 } else { 57 core.SendResponse(request, m) 58 } 59 // ping-get request type accepts the payload as a string 60 case "ping-get": 61 rsp := make(map[string]interface{}) 62 val := request.Payload.(string) 63 rsp["payload"] = val + "-response" 64 rsp["timestamp"] = time.Now().Unix() 65 core.SendResponse(request, rsp) 66 default: 67 core.HandleUnknownRequest(request) 68 } 69 } 70 71 // OnServiceReady contains logic that handles the service initialization that needs to be carried out 72 // before it is ready to accept user requests. Plank monitors and waits for service initialization to 73 // complete by trying to receive a boolean payload from a channel of boolean type. as a service developer 74 // you need to perform any and every init logic here and return a channel that would receive a payload 75 // once your service truly becomes ready to accept requests. 76 func (ps *PingPongService) OnServiceReady() chan bool { 77 // for sample purposes this service initializes instantly 78 readyChan := make(chan bool, 1) 79 readyChan <- true 80 return readyChan 81 } 82 83 // OnServerShutdown is the opposite of OnServiceReady. it is called when the server enters graceful shutdown 84 // where all the running services need to complete before the server could shut down finally. this method does not need 85 // to return anything because the main server thread is going to shut down soon, but if there's any important teardown 86 // or cleanup that needs to be done, this is the right place to perform that. 87 func (ps *PingPongService) OnServerShutdown() { 88 // for sample purposes emulate a 1 second teardown process 89 time.Sleep(1 * time.Second) 90 } 91 92 // GetRESTBridgeConfig returns a list of REST bridge configurations that Plank will use to automatically register 93 // REST endpoints that map to the requests for this service. this means you can map any request types defined under 94 // HandleServiceRequest with any combination of URI, HTTP verb, path parameter, query parameter and request headers. 95 // as the service author you have full control over every aspect of the translation process which basically turns 96 // an incoming *http.Request into model.Request. See FabricRequestBuilder below to see it in action. 97 func (ps *PingPongService) GetRESTBridgeConfig() []*service.RESTBridgeConfig { 98 return []*service.RESTBridgeConfig{ 99 { 100 ServiceChannel: PingPongServiceChan, 101 Uri: "/rest/ping-pong", 102 Method: http.MethodPost, 103 AllowHead: true, 104 AllowOptions: true, 105 FabricRequestBuilder: func(w http.ResponseWriter, r *http.Request) model.Request { 106 body, _ := ioutil.ReadAll(r.Body) 107 return model.CreateServiceRequest("ping-post", body) 108 }, 109 }, 110 { 111 ServiceChannel: PingPongServiceChan, 112 Uri: "/rest/ping-pong2", 113 Method: http.MethodGet, 114 AllowHead: true, 115 AllowOptions: true, 116 FabricRequestBuilder: func(w http.ResponseWriter, r *http.Request) model.Request { 117 return model.Request{Id: &uuid.UUID{}, Request: "ping-get", Payload: r.URL.Query().Get("message")} 118 }, 119 }, 120 { 121 ServiceChannel: PingPongServiceChan, 122 Uri: "/rest/ping-pong/{from}/{to}/{message}", 123 Method: http.MethodGet, 124 FabricRequestBuilder: func(w http.ResponseWriter, r *http.Request) model.Request { 125 pathParams := mux.Vars(r) 126 return model.Request{ 127 Id: &uuid.UUID{}, 128 Request: "ping-get", 129 Payload: fmt.Sprintf( 130 "From %s to %s: %s", 131 pathParams["from"], 132 pathParams["to"], 133 pathParams["message"])} 134 }, 135 }, 136 } 137 }