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  }