github.com/micro/go-micro/examples@v0.0.0-20210105173217-bf4ab679e18b/booking/api/hotel/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"errors"
     7  	_ "expvar"
     8  
     9  	"net/http"
    10  	_ "net/http/pprof"
    11  	"strings"
    12  
    13  	"golang.org/x/net/trace"
    14  
    15  	"github.com/google/uuid"
    16  	"github.com/micro/go-micro/examples/booking/api/hotel/proto"
    17  	"github.com/micro/go-micro/examples/booking/srv/auth/proto"
    18  	"github.com/micro/go-micro/examples/booking/srv/geo/proto"
    19  	"github.com/micro/go-micro/examples/booking/srv/profile/proto"
    20  	"github.com/micro/go-micro/examples/booking/srv/rate/proto"
    21  	"github.com/micro/go-micro/v2"
    22  	"github.com/micro/go-micro/v2/client"
    23  	merr "github.com/micro/go-micro/v2/errors"
    24  	"github.com/micro/go-micro/v2/metadata"
    25  )
    26  
    27  const (
    28  	BASIC_SCHEMA  string = "Basic "
    29  	BEARER_SCHEMA string = "Bearer "
    30  )
    31  
    32  type profileResults struct {
    33  	hotels []*profile.Hotel
    34  	err    error
    35  }
    36  
    37  type rateResults struct {
    38  	ratePlans []*rate.RatePlan
    39  	err       error
    40  }
    41  
    42  type Hotel struct {
    43  	Client client.Client
    44  }
    45  
    46  func (s *Hotel) Rates(ctx context.Context, req *hotel.Request, rsp *hotel.Response) error {
    47  	// tracing
    48  	tr := trace.New("api.v1", "Hotel.Rates")
    49  	defer tr.Finish()
    50  
    51  	// context
    52  	ctx = trace.NewContext(ctx, tr)
    53  
    54  	md, ok := metadata.FromContext(ctx)
    55  	if !ok {
    56  		md = metadata.Metadata{}
    57  	}
    58  
    59  	// add a unique request id to context
    60  	traceID := uuid.New()
    61  	// make copy
    62  	tmd := metadata.Metadata{}
    63  	for k, v := range md {
    64  		tmd[k] = v
    65  	}
    66  
    67  	tmd["traceID"] = traceID.String()
    68  	tmd["fromName"] = "api.v1"
    69  	ctx = metadata.NewContext(ctx, tmd)
    70  
    71  	// token from request headers
    72  	token, err := getToken(md)
    73  	if err != nil {
    74  		return merr.Forbidden("api.hotel.rates", err.Error())
    75  	}
    76  
    77  	// verify token w/ auth service
    78  	authClient := auth.NewAuthService("go.micro.srv.auth", s.Client)
    79  	if _, err = authClient.VerifyToken(ctx, &auth.Request{AuthToken: token}); err != nil {
    80  		return merr.Unauthorized("api.hotel.rates", "Unauthorized")
    81  	}
    82  
    83  	// checkin and checkout date query params
    84  	inDate, outDate := req.InDate, req.OutDate
    85  	if inDate == "" || outDate == "" {
    86  		return merr.BadRequest("api.hotel.rates", "Please specify inDate/outDate params")
    87  	}
    88  
    89  	// finds nearby hotels
    90  	// TODO(hw): use lat/lon from request params
    91  	geoClient := geo.NewGeoService("go.micro.srv.geo", s.Client)
    92  	nearby, err := geoClient.Nearby(ctx, &geo.Request{
    93  		Lat: 51.502973,
    94  		Lon: -0.114723,
    95  	})
    96  	if err != nil {
    97  		return merr.InternalServerError("api.hotel.rates", err.Error())
    98  	}
    99  
   100  	// make requests for profiles and rates
   101  	profileCh := getHotelProfiles(s.Client, ctx, nearby.HotelIds)
   102  	rateCh := getRatePlans(s.Client, ctx, nearby.HotelIds, inDate, outDate)
   103  
   104  	// wait on profiles reply
   105  	profileReply := <-profileCh
   106  	if err := profileReply.err; err != nil {
   107  		return merr.InternalServerError("api.hotel.rates", err.Error())
   108  	}
   109  
   110  	// wait on rates reply
   111  	rateReply := <-rateCh
   112  	if err := rateReply.err; err != nil {
   113  		return merr.InternalServerError("api.hotel.rates", err.Error())
   114  	}
   115  
   116  	rsp.Hotels = profileReply.hotels
   117  	rsp.RatePlans = rateReply.ratePlans
   118  	return nil
   119  }
   120  
   121  func getToken(md metadata.Metadata) (string, error) {
   122  	// Grab the raw Authorization header
   123  	authHeader := md["Authorization"]
   124  	if authHeader == "" {
   125  		return "", errors.New("Authorization header required")
   126  	}
   127  
   128  	// Confirm the request is sending Basic Authentication credentials.
   129  	if !strings.HasPrefix(authHeader, BASIC_SCHEMA) && !strings.HasPrefix(authHeader, BEARER_SCHEMA) {
   130  		return "", errors.New("Authorization requires Basic/Bearer scheme")
   131  	}
   132  
   133  	// Get the token from the request header
   134  	// The first six characters are skipped - e.g. "Basic ".
   135  	if strings.HasPrefix(authHeader, BASIC_SCHEMA) {
   136  		str, err := base64.StdEncoding.DecodeString(authHeader[len(BASIC_SCHEMA):])
   137  		if err != nil {
   138  			return "", errors.New("Base64 encoding issue")
   139  		}
   140  		creds := strings.Split(string(str), ":")
   141  		return creds[0], nil
   142  	}
   143  
   144  	return authHeader[len(BEARER_SCHEMA):], nil
   145  }
   146  
   147  func getRatePlans(c client.Client, ctx context.Context, hotelIDs []string, inDate string, outDate string) chan rateResults {
   148  	rateClient := rate.NewRateService("go.micro.srv.rate", c)
   149  	ch := make(chan rateResults, 1)
   150  
   151  	go func() {
   152  		res, err := rateClient.GetRates(ctx, &rate.Request{
   153  			HotelIds: hotelIDs,
   154  			InDate:   inDate,
   155  			OutDate:  outDate,
   156  		})
   157  		ch <- rateResults{res.RatePlans, err}
   158  	}()
   159  
   160  	return ch
   161  }
   162  
   163  func getHotelProfiles(c client.Client, ctx context.Context, hotelIDs []string) chan profileResults {
   164  	profileClient := profile.NewProfileService("go.micro.srv.profile", c)
   165  	ch := make(chan profileResults, 1)
   166  
   167  	go func() {
   168  		res, err := profileClient.GetProfiles(ctx, &profile.Request{
   169  			HotelIds: hotelIDs,
   170  			Locale:   "en",
   171  		})
   172  		ch <- profileResults{res.Hotels, err}
   173  	}()
   174  
   175  	return ch
   176  }
   177  
   178  func main() {
   179  	// trace library patched for demo purposes.
   180  	// https://github.com/golang/net/blob/master/trace/trace.go#L94
   181  	trace.AuthRequest = func(req *http.Request) (any, sensitive bool) {
   182  		return true, true
   183  	}
   184  
   185  	service := micro.NewService(
   186  		micro.Name("go.micro.api.hotel"),
   187  	)
   188  
   189  	service.Init()
   190  	hotel.RegisterHotelHandler(service.Server(), &Hotel{service.Client()})
   191  	service.Run()
   192  }