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 }