162 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			162 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Go
		
	
	
	
// Copyright 2017 Microsoft Corporation. All rights reserved.
 | 
						|
// Use of this source code is governed by an MIT
 | 
						|
// license that can be found in the LICENSE file.
 | 
						|
 | 
						|
/*
 | 
						|
Package pipeline implements an HTTP request/response middleware pipeline whose
 | 
						|
policy objects mutate an HTTP request's URL, query parameters, and/or headers before
 | 
						|
the request is sent over the wire.
 | 
						|
 | 
						|
Not all policy objects mutate an HTTP request; some policy objects simply impact the
 | 
						|
flow of requests/responses by performing operations such as logging, retry policies,
 | 
						|
timeouts, failure injection, and deserialization of response payloads.
 | 
						|
 | 
						|
Implementing the Policy Interface
 | 
						|
 | 
						|
To implement a policy, define a struct that implements the pipeline.Policy interface's Do method. Your Do
 | 
						|
method is called when an HTTP request wants to be sent over the network. Your Do method can perform any
 | 
						|
operation(s) it desires. For example, it can log the outgoing request, mutate the URL, headers, and/or query
 | 
						|
parameters, inject a failure, etc. Your Do method must then forward the HTTP request to next Policy object
 | 
						|
in a linked-list ensuring that the remaining Policy objects perform their work. Ultimately, the last Policy
 | 
						|
object sends the HTTP request over the network (by calling the HTTPSender's Do method).
 | 
						|
 | 
						|
When an HTTP response comes back, each Policy object in the linked-list gets a chance to process the response
 | 
						|
(in reverse order). The Policy object can log the response, retry the operation if due to a transient failure
 | 
						|
or timeout, deserialize the response body, etc. Ultimately, the last Policy object returns the HTTP response
 | 
						|
to the code that initiated the original HTTP request.
 | 
						|
 | 
						|
Here is a template for how to define a pipeline.Policy object:
 | 
						|
 | 
						|
   type myPolicy struct {
 | 
						|
      node   PolicyNode
 | 
						|
      // TODO: Add configuration/setting fields here (if desired)...
 | 
						|
   }
 | 
						|
 | 
						|
   func (p *myPolicy) Do(ctx context.Context, request pipeline.Request) (pipeline.Response, error) {
 | 
						|
      // TODO: Mutate/process the HTTP request here...
 | 
						|
      response, err := p.node.Do(ctx, request)	// Forward HTTP request to next Policy & get HTTP response
 | 
						|
      // TODO: Mutate/process the HTTP response here...
 | 
						|
      return response, err	// Return response/error to previous Policy
 | 
						|
   }
 | 
						|
 | 
						|
Implementing the Factory Interface
 | 
						|
 | 
						|
Each Policy struct definition requires a factory struct definition that implements the pipeline.Factory interface's New
 | 
						|
method. The New method is called when application code wants to initiate a new HTTP request. Factory's New method is
 | 
						|
passed a pipeline.PolicyNode object which contains a reference to the owning pipeline.Pipeline object (discussed later) and
 | 
						|
a reference to the next Policy object in the linked list. The New method should create its corresponding Policy object
 | 
						|
passing it the PolicyNode and any other configuration/settings fields appropriate for the specific Policy object.
 | 
						|
 | 
						|
Here is a template for how to define a pipeline.Policy object:
 | 
						|
 | 
						|
   // NOTE: Once created & initialized, Factory objects should be goroutine-safe (ex: immutable);
 | 
						|
   // this allows reuse (efficient use of memory) and makes these objects usable by multiple goroutines concurrently.
 | 
						|
   type myPolicyFactory struct {
 | 
						|
      // TODO: Add any configuration/setting fields if desired...
 | 
						|
   }
 | 
						|
 | 
						|
   func (f *myPolicyFactory) New(node pipeline.PolicyNode) Policy {
 | 
						|
      return &myPolicy{node: node} // TODO: Also initialize any configuration/setting fields here (if desired)...
 | 
						|
   }
 | 
						|
 | 
						|
Using your Factory and Policy objects via a Pipeline
 | 
						|
 | 
						|
To use the Factory and Policy objects, an application constructs a slice of Factory objects and passes
 | 
						|
this slice to the pipeline.NewPipeline function.
 | 
						|
 | 
						|
   func NewPipeline(factories []pipeline.Factory, sender pipeline.HTTPSender) Pipeline
 | 
						|
 | 
						|
This function also requires an object implementing the HTTPSender interface. For simple scenarios,
 | 
						|
passing nil for HTTPSender causes a standard Go http.Client object to be created and used to actually
 | 
						|
send the HTTP response over the network. For more advanced scenarios, you can pass your own HTTPSender
 | 
						|
object in. This allows sharing of http.Client objects or the use of custom-configured http.Client objects
 | 
						|
or other objects that can simulate the network requests for testing purposes.
 | 
						|
 | 
						|
Now that you have a pipeline.Pipeline object, you can create a pipeline.Request object (which is a simple
 | 
						|
wrapper around Go's standard http.Request object) and pass it to Pipeline's Do method along with passing a
 | 
						|
context.Context for cancelling the HTTP request (if desired).
 | 
						|
 | 
						|
   type Pipeline interface {
 | 
						|
      Do(ctx context.Context, methodFactory pipeline.Factory, request pipeline.Request) (pipeline.Response, error)
 | 
						|
   }
 | 
						|
 | 
						|
Do iterates over the slice of Factory objects and tells each one to create its corresponding
 | 
						|
Policy object. After the linked-list of Policy objects have been created, Do calls the first
 | 
						|
Policy object passing it the Context & HTTP request parameters. These parameters now flow through
 | 
						|
all the Policy objects giving each object a chance to look at and/or mutate the HTTP request.
 | 
						|
The last Policy object sends the message over the network.
 | 
						|
 | 
						|
When the network operation completes, the HTTP response and error return values pass
 | 
						|
back through the same Policy objects in reverse order. Most Policy objects ignore the
 | 
						|
response/error but some log the result, retry the operation (depending on the exact
 | 
						|
reason the operation failed), or deserialize the response's body. Your own Policy
 | 
						|
objects can do whatever they like when processing outgoing requests or incoming responses.
 | 
						|
 | 
						|
Note that after an I/O request runs to completion, the Policy objects for that request
 | 
						|
are garbage collected. However, Pipeline object (like Factory objects) are goroutine-safe allowing
 | 
						|
them to be created once and reused over many I/O operations. This allows for efficient use of
 | 
						|
memory and also makes them safely usable by multiple goroutines concurrently.
 | 
						|
 | 
						|
Inserting a Method-Specific Factory into the Linked-List of Policy Objects
 | 
						|
 | 
						|
While Pipeline and Factory objects can be reused over many different operations, it is
 | 
						|
common to have special behavior for a specific operation/method. For example, a method
 | 
						|
may need to deserialize the response's body to an instance of a specific data type.
 | 
						|
To accommodate this, the Pipeline's Do method takes an additional method-specific
 | 
						|
Factory object. The Do method tells this Factory to create a Policy object and
 | 
						|
injects this method-specific Policy object into the linked-list of Policy objects.
 | 
						|
 | 
						|
When creating a Pipeline object, the slice of Factory objects passed must have 1
 | 
						|
(and only 1) entry marking where the method-specific Factory should be injected.
 | 
						|
The Factory marker is obtained by calling the pipeline.MethodFactoryMarker() function:
 | 
						|
 | 
						|
   func MethodFactoryMarker() pipeline.Factory
 | 
						|
 | 
						|
Creating an HTTP Request Object
 | 
						|
 | 
						|
The HTTP request object passed to Pipeline's Do method is not Go's http.Request struct.
 | 
						|
Instead, it is a pipeline.Request struct which is a simple wrapper around Go's standard
 | 
						|
http.Request. You create a pipeline.Request object by calling the pipeline.NewRequest function:
 | 
						|
 | 
						|
   func NewRequest(method string, url url.URL, options pipeline.RequestOptions) (request pipeline.Request, err error)
 | 
						|
 | 
						|
To this function, you must pass a pipeline.RequestOptions that looks like this:
 | 
						|
 | 
						|
   type RequestOptions struct {
 | 
						|
      // The readable and seekable stream to be sent to the server as the request's body.
 | 
						|
      Body io.ReadSeeker
 | 
						|
 | 
						|
      // The callback method (if not nil) to be invoked to report progress as the stream is uploaded in the HTTP request.
 | 
						|
      Progress ProgressReceiver
 | 
						|
   }
 | 
						|
 | 
						|
The method and struct ensure that the request's body stream is a read/seekable stream.
 | 
						|
A seekable stream is required so that upon retry, the final Policy object can seek
 | 
						|
the stream back to the beginning before retrying the network request and re-uploading the
 | 
						|
body. In addition, you can associate a ProgressReceiver callback function which will be
 | 
						|
invoked periodically to report progress while bytes are being read from the body stream
 | 
						|
and sent over the network.
 | 
						|
 | 
						|
Processing the HTTP Response
 | 
						|
 | 
						|
When an HTTP response comes in from the network, a reference to Go's http.Response struct is
 | 
						|
embedded in a struct that implements the pipeline.Response interface:
 | 
						|
 | 
						|
   type Response interface {
 | 
						|
      Response() *http.Response
 | 
						|
   }
 | 
						|
 | 
						|
This interface is returned through all the Policy objects. Each Policy object can call the Response
 | 
						|
interface's Response method to examine (or mutate) the embedded http.Response object.
 | 
						|
 | 
						|
A Policy object can internally define another struct (implementing the pipeline.Response interface)
 | 
						|
that embeds an http.Response and adds additional fields and return this structure to other Policy
 | 
						|
objects. This allows a Policy object to deserialize the body to some other struct and return the
 | 
						|
original http.Response and the additional struct back through the Policy chain. Other Policy objects
 | 
						|
can see the Response but cannot see the additional struct with the deserialized body. After all the
 | 
						|
Policy objects have returned, the pipeline.Response interface is returned by Pipeline's Do method.
 | 
						|
The caller of this method can perform a type assertion attempting to get back to the struct type
 | 
						|
really returned by the Policy object. If the type assertion is successful, the caller now has
 | 
						|
access to both the http.Response and the deserialized struct object.*/
 | 
						|
package pipeline
 |