github.com/Ingenico-ePayments/connect-sdk-go@v0.0.0-20240318153750-1f8cd329b9c9/communicator/Communicator.go (about) 1 package communicator 2 3 import ( 4 "errors" 5 "io" 6 "io/ioutil" 7 "net/http" 8 "net/url" 9 "strconv" 10 "strings" 11 "time" 12 13 "github.com/Ingenico-ePayments/connect-sdk-go/communicator/communication" 14 sdkErrors "github.com/Ingenico-ePayments/connect-sdk-go/errors" 15 "github.com/Ingenico-ePayments/connect-sdk-go/logging" 16 "github.com/Ingenico-ePayments/connect-sdk-go/logging/obfuscation" 17 ) 18 19 var ( 20 // ErrNilSession occurs when the given session is nil 21 ErrNilSession = errors.New("session is nil") 22 // ErrNilMarshaller occurs when the given marshaller is nil 23 ErrNilMarshaller = errors.New("marshaller is nil") 24 ) 25 26 // A Communicator is used to communicate with the Ingenico ePayments platform web services. 27 // It contains all the logic to transform a request object to a HTTP request and a HTTP response to a response object. 28 // It is also thread safe. 29 type Communicator struct { 30 session *Session 31 marshaller Marshaller 32 } 33 34 // Session returns the session of this Communicator 35 func (c *Communicator) Session() *Session { 36 return c.session 37 } 38 39 // Marshaller returns the marshaller of this Communicator 40 func (c *Communicator) Marshaller() Marshaller { 41 return c.marshaller 42 } 43 44 // Close closes the connection of the Communicator 45 func (c *Communicator) Close() error { 46 return c.session.Connection().Close() 47 } 48 49 // SetBodyObfuscator sets the body obfuscator to use. 50 func (c *Communicator) SetBodyObfuscator(bodyObfuscator obfuscation.BodyObfuscator) { 51 if connection, ok := c.session.connection.(obfuscation.Capable); ok { 52 connection.SetBodyObfuscator(bodyObfuscator) 53 } 54 } 55 56 // SetHeaderObfuscator sets the header obfuscator to use. 57 func (c *Communicator) SetHeaderObfuscator(headerObfuscator obfuscation.HeaderObfuscator) { 58 if connection, ok := c.session.connection.(obfuscation.Capable); ok { 59 connection.SetHeaderObfuscator(headerObfuscator) 60 } 61 } 62 63 // EnableLogging turns on logging using the given communicator logger. 64 func (c *Communicator) EnableLogging(logger logging.CommunicatorLogger) { 65 c.session.Connection().EnableLogging(logger) 66 } 67 68 // DisableLogging turns off logging. 69 func (c *Communicator) DisableLogging() { 70 c.session.Connection().DisableLogging() 71 } 72 73 // Get corresponds to the HTTP Get method 74 // 75 // relativePath is the Path to call, relative to the base URI 76 // requestHeaders is an optimal list of request headers 77 // requestParameters is an optional set of request parameters. If not used set to nil 78 // context is an optional Call context which can be used 79 // expectedObject is a reference to the expected response object 80 // 81 // Possibly returns an error 82 func (c *Communicator) Get(relativePath string, requestHeaders []communication.Header, requestParameters ParamRequest, context communication.CallContext, expectedObject interface{}) error { 83 connection := c.session.Connection() 84 var requestParameterList []RequestParam 85 var err error 86 if requestParameters != nil { 87 requestParameterList = requestParameters.ToRequestParameters() 88 } 89 uri, err := c.toAbsoluteURI(relativePath, requestParameterList) 90 if err != nil { 91 return err 92 } 93 if requestHeaders == nil { 94 requestHeaders = []communication.Header{} 95 } 96 requestHeaders, err = c.addGenericHeaders(http.MethodGet, uri, requestHeaders, context) 97 if err != nil { 98 return err 99 } 100 101 _, err = connection.Get(uri, requestHeaders, communication.ResponseHandlerFunc(func(statusCode int, headers []communication.Header, reader io.Reader) (interface{}, error) { 102 return nil, c.processResponse(statusCode, reader, headers, relativePath, context, expectedObject) 103 })) 104 105 return err 106 } 107 108 // GetWithHandler corresponds to the HTTP Get method 109 // 110 // relativePath is the Path to call, relative to the base URI 111 // requestHeaders is an optimal list of request headers 112 // requestParameters is an optional set of request parameters. If not used set to nil 113 // context is an optional Call context which can be used 114 // expectedObject is a reference to the expected response object 115 // bodyHandler is a BodyHandler that handles the body stream 116 // 117 // Possibly returns an error 118 func (c *Communicator) GetWithHandler(relativePath string, requestHeaders []communication.Header, requestParameters ParamRequest, context communication.CallContext, bodyHandler BodyHandler) error { 119 connection := c.session.Connection() 120 var requestParameterList []RequestParam 121 var err error 122 if requestParameters != nil { 123 requestParameterList = requestParameters.ToRequestParameters() 124 } 125 uri, err := c.toAbsoluteURI(relativePath, requestParameterList) 126 if err != nil { 127 return err 128 } 129 if requestHeaders == nil { 130 requestHeaders = []communication.Header{} 131 } 132 requestHeaders, err = c.addGenericHeaders(http.MethodGet, uri, requestHeaders, context) 133 if err != nil { 134 return err 135 } 136 137 _, err = connection.Get(uri, requestHeaders, communication.ResponseHandlerFunc(func(statusCode int, headers []communication.Header, reader io.Reader) (interface{}, error) { 138 return nil, c.processResponseWithHandler(statusCode, reader, headers, relativePath, context, bodyHandler) 139 })) 140 141 return err 142 } 143 144 // Delete corresponds to the HTTP Delete method 145 // 146 // relativePath is the Path to call, relative to the base URI 147 // requestHeaders is an optimal list of request headers 148 // requestParameters is an optional set of request parameters. If not used set to nil 149 // context is an optional Call context which can be used 150 // expectedObject is a reference to the expected response object 151 // 152 // Possibly returns an error 153 func (c *Communicator) Delete(relativePath string, requestHeaders []communication.Header, requestParameters ParamRequest, context communication.CallContext, expectedObject interface{}) error { 154 connection := c.session.Connection() 155 var requestParameterList []RequestParam 156 var err error 157 if requestParameters != nil { 158 requestParameterList = requestParameters.ToRequestParameters() 159 } 160 uri, err := c.toAbsoluteURI(relativePath, requestParameterList) 161 if err != nil { 162 return err 163 } 164 if requestHeaders == nil { 165 requestHeaders = []communication.Header{} 166 } 167 requestHeaders, err = c.addGenericHeaders(http.MethodDelete, uri, requestHeaders, context) 168 if err != nil { 169 return err 170 } 171 172 _, err = connection.Delete(uri, requestHeaders, communication.ResponseHandlerFunc(func(statusCode int, headers []communication.Header, reader io.Reader) (interface{}, error) { 173 return nil, c.processResponse(statusCode, reader, headers, relativePath, context, expectedObject) 174 })) 175 176 return err 177 } 178 179 // DeleteWithHandler corresponds to the HTTP Delete method 180 // 181 // relativePath is the Path to call, relative to the base URI 182 // requestHeaders is an optimal list of request headers 183 // requestParameters is an optional set of request parameters. If not used set to nil 184 // context is an optional Call context which can be used 185 // expectedObject is a reference to the expected response object 186 // bodyHandler is a BodyHandler that handles the body stream 187 // 188 // Possibly returns an error 189 func (c *Communicator) DeleteWithHandler(relativePath string, requestHeaders []communication.Header, requestParameters ParamRequest, context communication.CallContext, bodyHandler BodyHandler) error { 190 connection := c.session.Connection() 191 var requestParameterList []RequestParam 192 var err error 193 if requestParameters != nil { 194 requestParameterList = requestParameters.ToRequestParameters() 195 } 196 uri, err := c.toAbsoluteURI(relativePath, requestParameterList) 197 if err != nil { 198 return err 199 } 200 if requestHeaders == nil { 201 requestHeaders = []communication.Header{} 202 } 203 requestHeaders, err = c.addGenericHeaders(http.MethodDelete, uri, requestHeaders, context) 204 if err != nil { 205 return err 206 } 207 208 _, err = connection.Delete(uri, requestHeaders, communication.ResponseHandlerFunc(func(statusCode int, headers []communication.Header, reader io.Reader) (interface{}, error) { 209 return nil, c.processResponseWithHandler(statusCode, reader, headers, relativePath, context, bodyHandler) 210 })) 211 212 return err 213 } 214 215 // Post corresponds to the HTTP Post method 216 // 217 // relativePath is the Path to call, relative to the base URI 218 // requestHeaders is an optimal list of request headers 219 // requestParameters is an optional set of request parameters. If not used set it to nil 220 // requestBody is the body of the request. If not used set to nil 221 // context is an optional Call context which can be used. If not used set it to nil 222 // expectedObject is a reference to the expected response object 223 // 224 // Possibly returns an error 225 func (c *Communicator) Post(relativePath string, requestHeaders []communication.Header, requestParameters ParamRequest, requestBody interface{}, context communication.CallContext, expectedResponse interface{}) error { 226 if multipartObject, ok := requestBody.(communication.MultipartFormDataObject); ok { 227 return c.postMultipart(relativePath, requestHeaders, requestParameters, &multipartObject, context, expectedResponse) 228 } 229 if multipartObject, ok := requestBody.(*communication.MultipartFormDataObject); ok { 230 return c.postMultipart(relativePath, requestHeaders, requestParameters, multipartObject, context, expectedResponse) 231 } 232 if multipartRequest, ok := requestBody.(MultipartFormDataRequest); ok { 233 return c.postMultipart(relativePath, requestHeaders, requestParameters, multipartRequest.ToMultipartFormDataObject(), context, expectedResponse) 234 } 235 236 connection := c.session.Connection() 237 var requestParameterList []RequestParam 238 if requestParameters != nil { 239 requestParameterList = requestParameters.ToRequestParameters() 240 } 241 uri, err := c.toAbsoluteURI(relativePath, requestParameterList) 242 if err != nil { 243 return err 244 } 245 if requestHeaders == nil { 246 requestHeaders = []communication.Header{} 247 } 248 249 var requestJSON string 250 if requestBody != nil { 251 jsonHeader, _ := communication.NewHeader("Content-Type", "application/json") 252 requestHeaders = append(requestHeaders, *jsonHeader) 253 254 requestJSON, err = c.marshaller.Marshal(requestBody) 255 256 if err != nil { 257 return err 258 } 259 } 260 261 requestHeaders, err = c.addGenericHeaders(http.MethodPost, uri, requestHeaders, context) 262 if err != nil { 263 return err 264 } 265 _, err = connection.Post(uri, requestHeaders, requestJSON, communication.ResponseHandlerFunc(func(statusCode int, headers []communication.Header, reader io.Reader) (interface{}, error) { 266 return nil, c.processResponse(statusCode, reader, headers, relativePath, context, expectedResponse) 267 })) 268 269 return err 270 } 271 272 func (c *Communicator) postMultipart(relativePath string, requestHeaders []communication.Header, requestParameters ParamRequest, requestBody *communication.MultipartFormDataObject, context communication.CallContext, expectedResponse interface{}) error { 273 connection := c.session.Connection() 274 var requestParameterList []RequestParam 275 if requestParameters != nil { 276 requestParameterList = requestParameters.ToRequestParameters() 277 } 278 uri, err := c.toAbsoluteURI(relativePath, requestParameterList) 279 if err != nil { 280 return err 281 } 282 if requestHeaders == nil { 283 requestHeaders = []communication.Header{} 284 } 285 286 multipartHeader, _ := communication.NewHeader("Content-Type", requestBody.GetContentType()) 287 requestHeaders = append(requestHeaders, *multipartHeader) 288 289 requestHeaders, err = c.addGenericHeaders(http.MethodPost, uri, requestHeaders, context) 290 if err != nil { 291 return err 292 } 293 _, err = connection.PostMultipart(uri, requestHeaders, requestBody, communication.ResponseHandlerFunc(func(statusCode int, headers []communication.Header, reader io.Reader) (interface{}, error) { 294 return nil, c.processResponse(statusCode, reader, headers, relativePath, context, expectedResponse) 295 })) 296 297 return err 298 } 299 300 // PostWithHandler corresponds to the HTTP Post method 301 // 302 // relativePath is the Path to call, relative to the base URI 303 // requestHeaders is an optimal list of request headers 304 // requestParameters is an optional set of request parameters. If not used set it to nil 305 // requestBody is the body of the request. If not used set to nil 306 // context is an optional Call context which can be used. If not used set it to nil 307 // expectedObject is a reference to the expected response object 308 // bodyHandler is a BodybodyHandler that handles the body stream 309 // 310 // Possibly returns an error 311 func (c *Communicator) PostWithHandler(relativePath string, requestHeaders []communication.Header, requestParameters ParamRequest, requestBody interface{}, context communication.CallContext, bodyHandler BodyHandler) error { 312 if multipartObject, ok := requestBody.(communication.MultipartFormDataObject); ok { 313 return c.postMultipartWithHandler(relativePath, requestHeaders, requestParameters, &multipartObject, context, bodyHandler) 314 } 315 if multipartObject, ok := requestBody.(*communication.MultipartFormDataObject); ok { 316 return c.postMultipartWithHandler(relativePath, requestHeaders, requestParameters, multipartObject, context, bodyHandler) 317 } 318 if multipartRequest, ok := requestBody.(MultipartFormDataRequest); ok { 319 return c.postMultipartWithHandler(relativePath, requestHeaders, requestParameters, multipartRequest.ToMultipartFormDataObject(), context, bodyHandler) 320 } 321 322 connection := c.session.Connection() 323 var requestParameterList []RequestParam 324 if requestParameters != nil { 325 requestParameterList = requestParameters.ToRequestParameters() 326 } 327 uri, err := c.toAbsoluteURI(relativePath, requestParameterList) 328 if err != nil { 329 return err 330 } 331 if requestHeaders == nil { 332 requestHeaders = []communication.Header{} 333 } 334 335 var requestJSON string 336 if requestBody != nil { 337 jsonHeader, _ := communication.NewHeader("Content-Type", "application/json") 338 requestHeaders = append(requestHeaders, *jsonHeader) 339 340 requestJSON, err = c.marshaller.Marshal(requestBody) 341 342 if err != nil { 343 return err 344 } 345 } 346 347 requestHeaders, err = c.addGenericHeaders(http.MethodPost, uri, requestHeaders, context) 348 if err != nil { 349 return err 350 } 351 352 _, err = connection.Post(uri, requestHeaders, requestJSON, communication.ResponseHandlerFunc(func(statusCode int, headers []communication.Header, reader io.Reader) (interface{}, error) { 353 return nil, c.processResponseWithHandler(statusCode, reader, headers, relativePath, context, bodyHandler) 354 })) 355 356 return err 357 } 358 359 func (c *Communicator) postMultipartWithHandler(relativePath string, requestHeaders []communication.Header, requestParameters ParamRequest, requestBody *communication.MultipartFormDataObject, context communication.CallContext, bodyHandler BodyHandler) error { 360 connection := c.session.Connection() 361 var requestParameterList []RequestParam 362 if requestParameters != nil { 363 requestParameterList = requestParameters.ToRequestParameters() 364 } 365 uri, err := c.toAbsoluteURI(relativePath, requestParameterList) 366 if err != nil { 367 return err 368 } 369 if requestHeaders == nil { 370 requestHeaders = []communication.Header{} 371 } 372 373 multipartHeader, _ := communication.NewHeader("Content-Type", requestBody.GetContentType()) 374 requestHeaders = append(requestHeaders, *multipartHeader) 375 376 requestHeaders, err = c.addGenericHeaders(http.MethodPost, uri, requestHeaders, context) 377 if err != nil { 378 return err 379 } 380 381 _, err = connection.PostMultipart(uri, requestHeaders, requestBody, communication.ResponseHandlerFunc(func(statusCode int, headers []communication.Header, reader io.Reader) (interface{}, error) { 382 return nil, c.processResponseWithHandler(statusCode, reader, headers, relativePath, context, bodyHandler) 383 })) 384 385 return err 386 } 387 388 // Put corresponds to the HTTP Put method 389 // 390 // relativePath is the Path to call, relative to the base URI 391 // requestHeaders is an optimal list of request headers 392 // requestParameters is an optional set of request parameters. If not used set it to nil 393 // requestBody is the body of the request. If not used set to nil 394 // context is an optional Call context which can be used. If not used set it to nil 395 // expectedObject is a reference to the expected response object 396 // 397 // Possibly returns an error 398 func (c *Communicator) Put(relativePath string, requestHeaders []communication.Header, requestParameters ParamRequest, requestBody interface{}, context communication.CallContext, expectedObject interface{}) error { 399 if multipartObject, ok := requestBody.(communication.MultipartFormDataObject); ok { 400 return c.putMultipart(relativePath, requestHeaders, requestParameters, &multipartObject, context, expectedObject) 401 } 402 if multipartObject, ok := requestBody.(*communication.MultipartFormDataObject); ok { 403 return c.putMultipart(relativePath, requestHeaders, requestParameters, multipartObject, context, expectedObject) 404 } 405 if multipartRequest, ok := requestBody.(MultipartFormDataRequest); ok { 406 return c.putMultipart(relativePath, requestHeaders, requestParameters, multipartRequest.ToMultipartFormDataObject(), context, expectedObject) 407 } 408 409 connection := c.session.Connection() 410 var requestParameterList []RequestParam 411 var err error 412 if requestParameters != nil { 413 requestParameterList = requestParameters.ToRequestParameters() 414 } 415 uri, err := c.toAbsoluteURI(relativePath, requestParameterList) 416 if err != nil { 417 return err 418 } 419 if requestHeaders == nil { 420 requestHeaders = []communication.Header{} 421 } 422 423 var requestJSON string 424 if requestBody != nil { 425 jsonHeader, _ := communication.NewHeader("Content-Type", "application/json") 426 requestHeaders = append(requestHeaders, *jsonHeader) 427 requestJSON, err = c.marshaller.Marshal(requestBody) 428 429 if err != nil { 430 return err 431 } 432 } 433 434 requestHeaders, err = c.addGenericHeaders(http.MethodPut, uri, requestHeaders, context) 435 if err != nil { 436 return err 437 } 438 _, err = connection.Put(uri, requestHeaders, requestJSON, communication.ResponseHandlerFunc(func(statusCode int, headers []communication.Header, reader io.Reader) (interface{}, error) { 439 return nil, c.processResponse(statusCode, reader, headers, relativePath, context, expectedObject) 440 })) 441 442 return err 443 } 444 445 func (c *Communicator) putMultipart(relativePath string, requestHeaders []communication.Header, requestParameters ParamRequest, requestBody *communication.MultipartFormDataObject, context communication.CallContext, expectedResponse interface{}) error { 446 connection := c.session.Connection() 447 var requestParameterList []RequestParam 448 if requestParameters != nil { 449 requestParameterList = requestParameters.ToRequestParameters() 450 } 451 uri, err := c.toAbsoluteURI(relativePath, requestParameterList) 452 if err != nil { 453 return err 454 } 455 if requestHeaders == nil { 456 requestHeaders = []communication.Header{} 457 } 458 459 jsonHeader, _ := communication.NewHeader("Content-Type", requestBody.GetContentType()) 460 requestHeaders = append(requestHeaders, *jsonHeader) 461 462 requestHeaders, err = c.addGenericHeaders(http.MethodPost, uri, requestHeaders, context) 463 if err != nil { 464 return err 465 } 466 _, err = connection.PutMultipart(uri, requestHeaders, requestBody, communication.ResponseHandlerFunc(func(statusCode int, headers []communication.Header, reader io.Reader) (interface{}, error) { 467 return nil, c.processResponse(statusCode, reader, headers, relativePath, context, expectedResponse) 468 })) 469 470 return err 471 } 472 473 // PutWithHandler corresponds to the HTTP Put method 474 // 475 // relativePath is the Path to call, relative to the base URI 476 // requestHeaders is an optimal list of request headers 477 // requestParameters is an optional set of request parameters. If not used set it to nil 478 // requestBody is the body of the request. If not used set to nil 479 // context is an optional Call context which can be used. If not used set it to nil 480 // expectedObject is a reference to the expected response object 481 // bodyHandler is a BodyHandler that handles the body stream 482 // 483 // Possibly returns an error 484 func (c *Communicator) PutWithHandler(relativePath string, requestHeaders []communication.Header, requestParameters ParamRequest, requestBody interface{}, context communication.CallContext, bodyHandler BodyHandler) error { 485 if multipartObject, ok := requestBody.(communication.MultipartFormDataObject); ok { 486 return c.putMultipartWithHandler(relativePath, requestHeaders, requestParameters, &multipartObject, context, bodyHandler) 487 } 488 if multipartObject, ok := requestBody.(*communication.MultipartFormDataObject); ok { 489 return c.putMultipartWithHandler(relativePath, requestHeaders, requestParameters, multipartObject, context, bodyHandler) 490 } 491 if multipartRequest, ok := requestBody.(MultipartFormDataRequest); ok { 492 return c.putMultipartWithHandler(relativePath, requestHeaders, requestParameters, multipartRequest.ToMultipartFormDataObject(), context, bodyHandler) 493 } 494 495 connection := c.session.Connection() 496 var requestParameterList []RequestParam 497 var err error 498 if requestParameters != nil { 499 requestParameterList = requestParameters.ToRequestParameters() 500 } 501 uri, err := c.toAbsoluteURI(relativePath, requestParameterList) 502 if err != nil { 503 return err 504 } 505 if requestHeaders == nil { 506 requestHeaders = []communication.Header{} 507 } 508 509 var requestJSON string 510 if requestBody != nil { 511 jsonHeader, _ := communication.NewHeader("Content-Type", "application/json") 512 requestHeaders = append(requestHeaders, *jsonHeader) 513 requestJSON, err = c.marshaller.Marshal(requestBody) 514 515 if err != nil { 516 return err 517 } 518 } 519 520 requestHeaders, err = c.addGenericHeaders(http.MethodPut, uri, requestHeaders, context) 521 if err != nil { 522 return err 523 } 524 525 _, err = connection.Put(uri, requestHeaders, requestJSON, communication.ResponseHandlerFunc(func(statusCode int, headers []communication.Header, reader io.Reader) (interface{}, error) { 526 return nil, c.processResponseWithHandler(statusCode, reader, headers, relativePath, context, bodyHandler) 527 })) 528 529 return err 530 } 531 532 func (c *Communicator) putMultipartWithHandler(relativePath string, requestHeaders []communication.Header, requestParameters ParamRequest, requestBody *communication.MultipartFormDataObject, context communication.CallContext, bodyHandler BodyHandler) error { 533 connection := c.session.Connection() 534 var requestParameterList []RequestParam 535 if requestParameters != nil { 536 requestParameterList = requestParameters.ToRequestParameters() 537 } 538 uri, err := c.toAbsoluteURI(relativePath, requestParameterList) 539 if err != nil { 540 return err 541 } 542 if requestHeaders == nil { 543 requestHeaders = []communication.Header{} 544 } 545 546 multipartHeader, _ := communication.NewHeader("Content-Type", requestBody.GetContentType()) 547 requestHeaders = append(requestHeaders, *multipartHeader) 548 549 requestHeaders, err = c.addGenericHeaders(http.MethodPost, uri, requestHeaders, context) 550 if err != nil { 551 return err 552 } 553 554 _, err = connection.PutMultipart(uri, requestHeaders, requestBody, communication.ResponseHandlerFunc(func(statusCode int, headers []communication.Header, reader io.Reader) (interface{}, error) { 555 return nil, c.processResponseWithHandler(statusCode, reader, headers, relativePath, context, bodyHandler) 556 })) 557 558 return err 559 } 560 561 // CloseExpiredConnections is a utility method that delegates the call to this communicator's session's connection. 562 // Also see Connection.CloseExpiredConnections 563 func (c *Communicator) CloseExpiredConnections() { 564 c.session.Connection().CloseExpiredConnections() 565 } 566 567 // CloseIdleConnections is a utility method that delegates the call to this communicator's session's connection. 568 // The duration argument is a specification of how long the connection has to be Idle. 569 // Also see Connection.CloseIdleConnections 570 func (c *Communicator) CloseIdleConnections(duration time.Duration) { 571 c.session.Connection().CloseIdleConnections(duration) 572 } 573 574 func (c *Communicator) toAbsoluteURI(relativePath string, requestParameters []RequestParam) (url.URL, error) { 575 apiEndpoint := c.session.APIEndpoint() 576 577 if apiEndpoint.Path != "" { 578 return url.URL{}, ErrPathEndpoint 579 } 580 if apiEndpoint.User != nil || apiEndpoint.Query().Encode() != "" || apiEndpoint.Fragment != "" { 581 return url.URL{}, ErrUserInfo 582 } 583 584 var absolutePath string 585 if strings.Index(relativePath, "/") == 0 { 586 absolutePath = relativePath 587 } else { 588 absolutePath = "/" + relativePath 589 } 590 591 var rawQuery = "" 592 for index, element := range requestParameters { 593 if index != 0 { 594 rawQuery += "&" 595 } 596 id := url.QueryEscape(element.Name()) 597 value := url.QueryEscape(element.Value()) 598 rawQuery += id + "=" + value 599 } 600 601 var url = url.URL{Scheme: apiEndpoint.Scheme, 602 Host: apiEndpoint.Host, 603 Path: absolutePath, 604 RawQuery: rawQuery} 605 606 return url, nil 607 } 608 609 // Adds the necessary headers to the given list of headers. This includes the authorization header, 610 // which uses other headers, so when you need to override this method, 611 // make sure to call AddGenericHeaders at the end of your overridden method. 612 func (c *Communicator) addGenericHeaders(httpMethod string, url url.URL, requestHeaders []communication.Header, context communication.CallContext) ([]communication.Header, error) { 613 //add server meta info headers 614 requestHeaders = append(requestHeaders, c.session.MetaDataProvider().MetaDataHeaders()...) 615 616 //add date header 617 header, err := communication.NewHeader("Date", getHeaderDateString()) 618 if err != nil { 619 return nil, err 620 } 621 requestHeaders = append(requestHeaders, *header) 622 623 //add content specific headers 624 if context != nil && context.GetIdempotenceKey() != "" { 625 header, err = communication.NewHeader("X-GCS-Idempotence-Key", context.GetIdempotenceKey()) 626 if err != nil { 627 return nil, err 628 } 629 requestHeaders = append(requestHeaders, *header) 630 } 631 632 //add signature 633 authenticator := c.session.Authenticator() 634 authenticationSignature, err := authenticator.CreateSimpleAuthenticationSignature(httpMethod, url, requestHeaders) 635 if err != nil { 636 return nil, err 637 } 638 639 header, err = communication.NewHeader("Authorization", authenticationSignature) 640 if err != nil { 641 return nil, err 642 } 643 requestHeaders = append(requestHeaders, *header) 644 return requestHeaders, nil 645 } 646 647 // Gets the date in the preferred format for the HTTP date header (RFC1123). 648 func getHeaderDateString() string { 649 return time.Now().UTC().Format(http.TimeFormat) 650 } 651 652 // Checks the Response for errors and creates an error if necessary. 653 func (c *Communicator) createErrorIfNecessary(statusCode int, reader io.Reader, headers []communication.Header, requestPath string) error { 654 if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices { 655 bodyBuff, err := ioutil.ReadAll(reader) 656 if err != nil { 657 return err 658 } 659 body := string(bodyBuff) 660 if body != "" && !isJSON(headers) { 661 var cause = sdkErrors.NewResponseError(statusCode, body, headers) 662 if statusCode == http.StatusNotFound { 663 err, _ := sdkErrors.NewNotFoundErrorVerbose("The requested resource was not found; invalid path: "+requestPath, cause) 664 return err 665 } 666 err, _ := sdkErrors.NewCommunicationError(cause) 667 return err 668 } 669 return sdkErrors.NewResponseError(statusCode, body, headers) 670 } 671 return nil 672 } 673 674 func (c *Communicator) processResponse(statusCode int, reader io.Reader, headers []communication.Header, requestPath string, context communication.CallContext, expectedObject interface{}) error { 675 if context != nil { 676 timestamp, err := getIdempotenceTimestamp(headers) 677 if err != nil { 678 return err 679 } 680 context.SetIdempotenceRequestTimestamp(timestamp) 681 } 682 683 err := c.createErrorIfNecessary(statusCode, reader, headers, requestPath) 684 if err != nil { 685 return err 686 } 687 688 return c.marshaller.UnmarshalFromReader(reader, expectedObject) 689 } 690 691 func (c *Communicator) processResponseWithHandler(statusCode int, reader io.Reader, headers []communication.Header, requestPath string, context communication.CallContext, bodyHandler BodyHandler) error { 692 if context != nil { 693 timestamp, err := getIdempotenceTimestamp(headers) 694 if err != nil { 695 return err 696 } 697 context.SetIdempotenceRequestTimestamp(timestamp) 698 } 699 700 err := c.createErrorIfNecessary(statusCode, reader, headers, requestPath) 701 if err != nil { 702 return err 703 } 704 705 return bodyHandler.Handle(headers, reader) 706 } 707 708 func getIdempotenceTimestamp(headers []communication.Header) (*int64, error) { 709 header := communication.Headers(headers).GetHeader("X-GCS-Idempotence-Request-Timestamp") 710 if header == nil { 711 return nil, nil 712 } 713 714 retValue, err := strconv.ParseInt(header.Value(), 10, 64) 715 return &retValue, err 716 } 717 718 func isJSON(headers []communication.Header) bool { 719 header := communication.Headers(headers).GetHeader("Content-Type") 720 if header == nil { 721 return true 722 } 723 724 contentType := strings.ToLower(header.Value()) 725 726 if contentType == "application/json" { 727 return true 728 } 729 730 return strings.HasPrefix(contentType, "application/json") 731 } 732 733 // NewCommunicator creates a communicator with the given session and marshaller 734 func NewCommunicator(session *Session, marshaller Marshaller) (*Communicator, error) { 735 if session == nil { 736 return nil, ErrNilSession 737 } 738 if marshaller == nil { 739 return nil, ErrNilMarshaller 740 } 741 742 return &Communicator{session, marshaller}, nil 743 }