doc.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. // Copyright 2017 Microsoft Corporation. All rights reserved.
  2. // Use of this source code is governed by an MIT
  3. // license that can be found in the LICENSE file.
  4. /*
  5. Package pipeline implements an HTTP request/response middleware pipeline whose
  6. policy objects mutate an HTTP request's URL, query parameters, and/or headers before
  7. the request is sent over the wire.
  8. Not all policy objects mutate an HTTP request; some policy objects simply impact the
  9. flow of requests/responses by performing operations such as logging, retry policies,
  10. timeouts, failure injection, and deserialization of response payloads.
  11. Implementing the Policy Interface
  12. To implement a policy, define a struct that implements the pipeline.Policy interface's Do method. Your Do
  13. method is called when an HTTP request wants to be sent over the network. Your Do method can perform any
  14. operation(s) it desires. For example, it can log the outgoing request, mutate the URL, headers, and/or query
  15. parameters, inject a failure, etc. Your Do method must then forward the HTTP request to next Policy object
  16. in a linked-list ensuring that the remaining Policy objects perform their work. Ultimately, the last Policy
  17. object sends the HTTP request over the network (by calling the HTTPSender's Do method).
  18. When an HTTP response comes back, each Policy object in the linked-list gets a chance to process the response
  19. (in reverse order). The Policy object can log the response, retry the operation if due to a transient failure
  20. or timeout, deserialize the response body, etc. Ultimately, the last Policy object returns the HTTP response
  21. to the code that initiated the original HTTP request.
  22. Here is a template for how to define a pipeline.Policy object:
  23. type myPolicy struct {
  24. node PolicyNode
  25. // TODO: Add configuration/setting fields here (if desired)...
  26. }
  27. func (p *myPolicy) Do(ctx context.Context, request pipeline.Request) (pipeline.Response, error) {
  28. // TODO: Mutate/process the HTTP request here...
  29. response, err := p.node.Do(ctx, request) // Forward HTTP request to next Policy & get HTTP response
  30. // TODO: Mutate/process the HTTP response here...
  31. return response, err // Return response/error to previous Policy
  32. }
  33. Implementing the Factory Interface
  34. Each Policy struct definition requires a factory struct definition that implements the pipeline.Factory interface's New
  35. method. The New method is called when application code wants to initiate a new HTTP request. Factory's New method is
  36. passed a pipeline.PolicyNode object which contains a reference to the owning pipeline.Pipeline object (discussed later) and
  37. a reference to the next Policy object in the linked list. The New method should create its corresponding Policy object
  38. passing it the PolicyNode and any other configuration/settings fields appropriate for the specific Policy object.
  39. Here is a template for how to define a pipeline.Policy object:
  40. // NOTE: Once created & initialized, Factory objects should be goroutine-safe (ex: immutable);
  41. // this allows reuse (efficient use of memory) and makes these objects usable by multiple goroutines concurrently.
  42. type myPolicyFactory struct {
  43. // TODO: Add any configuration/setting fields if desired...
  44. }
  45. func (f *myPolicyFactory) New(node pipeline.PolicyNode) Policy {
  46. return &myPolicy{node: node} // TODO: Also initialize any configuration/setting fields here (if desired)...
  47. }
  48. Using your Factory and Policy objects via a Pipeline
  49. To use the Factory and Policy objects, an application constructs a slice of Factory objects and passes
  50. this slice to the pipeline.NewPipeline function.
  51. func NewPipeline(factories []pipeline.Factory, sender pipeline.HTTPSender) Pipeline
  52. This function also requires an object implementing the HTTPSender interface. For simple scenarios,
  53. passing nil for HTTPSender causes a standard Go http.Client object to be created and used to actually
  54. send the HTTP response over the network. For more advanced scenarios, you can pass your own HTTPSender
  55. object in. This allows sharing of http.Client objects or the use of custom-configured http.Client objects
  56. or other objects that can simulate the network requests for testing purposes.
  57. Now that you have a pipeline.Pipeline object, you can create a pipeline.Request object (which is a simple
  58. wrapper around Go's standard http.Request object) and pass it to Pipeline's Do method along with passing a
  59. context.Context for cancelling the HTTP request (if desired).
  60. type Pipeline interface {
  61. Do(ctx context.Context, methodFactory pipeline.Factory, request pipeline.Request) (pipeline.Response, error)
  62. }
  63. Do iterates over the slice of Factory objects and tells each one to create its corresponding
  64. Policy object. After the linked-list of Policy objects have been created, Do calls the first
  65. Policy object passing it the Context & HTTP request parameters. These parameters now flow through
  66. all the Policy objects giving each object a chance to look at and/or mutate the HTTP request.
  67. The last Policy object sends the message over the network.
  68. When the network operation completes, the HTTP response and error return values pass
  69. back through the same Policy objects in reverse order. Most Policy objects ignore the
  70. response/error but some log the result, retry the operation (depending on the exact
  71. reason the operation failed), or deserialize the response's body. Your own Policy
  72. objects can do whatever they like when processing outgoing requests or incoming responses.
  73. Note that after an I/O request runs to completion, the Policy objects for that request
  74. are garbage collected. However, Pipeline object (like Factory objects) are goroutine-safe allowing
  75. them to be created once and reused over many I/O operations. This allows for efficient use of
  76. memory and also makes them safely usable by multiple goroutines concurrently.
  77. Inserting a Method-Specific Factory into the Linked-List of Policy Objects
  78. While Pipeline and Factory objects can be reused over many different operations, it is
  79. common to have special behavior for a specific operation/method. For example, a method
  80. may need to deserialize the response's body to an instance of a specific data type.
  81. To accommodate this, the Pipeline's Do method takes an additional method-specific
  82. Factory object. The Do method tells this Factory to create a Policy object and
  83. injects this method-specific Policy object into the linked-list of Policy objects.
  84. When creating a Pipeline object, the slice of Factory objects passed must have 1
  85. (and only 1) entry marking where the method-specific Factory should be injected.
  86. The Factory marker is obtained by calling the pipeline.MethodFactoryMarker() function:
  87. func MethodFactoryMarker() pipeline.Factory
  88. Creating an HTTP Request Object
  89. The HTTP request object passed to Pipeline's Do method is not Go's http.Request struct.
  90. Instead, it is a pipeline.Request struct which is a simple wrapper around Go's standard
  91. http.Request. You create a pipeline.Request object by calling the pipeline.NewRequest function:
  92. func NewRequest(method string, url url.URL, options pipeline.RequestOptions) (request pipeline.Request, err error)
  93. To this function, you must pass a pipeline.RequestOptions that looks like this:
  94. type RequestOptions struct {
  95. // The readable and seekable stream to be sent to the server as the request's body.
  96. Body io.ReadSeeker
  97. // The callback method (if not nil) to be invoked to report progress as the stream is uploaded in the HTTP request.
  98. Progress ProgressReceiver
  99. }
  100. The method and struct ensure that the request's body stream is a read/seekable stream.
  101. A seekable stream is required so that upon retry, the final Policy object can seek
  102. the stream back to the beginning before retrying the network request and re-uploading the
  103. body. In addition, you can associate a ProgressReceiver callback function which will be
  104. invoked periodically to report progress while bytes are being read from the body stream
  105. and sent over the network.
  106. Processing the HTTP Response
  107. When an HTTP response comes in from the network, a reference to Go's http.Response struct is
  108. embedded in a struct that implements the pipeline.Response interface:
  109. type Response interface {
  110. Response() *http.Response
  111. }
  112. This interface is returned through all the Policy objects. Each Policy object can call the Response
  113. interface's Response method to examine (or mutate) the embedded http.Response object.
  114. A Policy object can internally define another struct (implementing the pipeline.Response interface)
  115. that embeds an http.Response and adds additional fields and return this structure to other Policy
  116. objects. This allows a Policy object to deserialize the body to some other struct and return the
  117. original http.Response and the additional struct back through the Policy chain. Other Policy objects
  118. can see the Response but cannot see the additional struct with the deserialized body. After all the
  119. Policy objects have returned, the pipeline.Response interface is returned by Pipeline's Do method.
  120. The caller of this method can perform a type assertion attempting to get back to the struct type
  121. really returned by the Policy object. If the type assertion is successful, the caller now has
  122. access to both the http.Response and the deserialized struct object.*/
  123. package pipeline