github.com/hyperledger/aries-framework-go@v0.3.2/pkg/didcomm/transport/http/outbound.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package http
     8  
     9  import (
    10  	"bytes"
    11  	"crypto/tls"
    12  	"errors"
    13  	"fmt"
    14  	"net/http"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
    19  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/transport"
    20  )
    21  
    22  //go:generate testdata/scripts/openssl_env.sh testdata/scripts/generate_test_keys.sh
    23  
    24  const (
    25  	commContentType       = "application/didcomm-envelope-enc"
    26  	commContentTypeLegacy = "application/ssi-agent-wire"
    27  	httpScheme            = "http"
    28  )
    29  
    30  // outboundCommHTTPOpts holds options for the HTTP transport implementation of CommTransport
    31  // it has an http.Client instance.
    32  type outboundCommHTTPOpts struct {
    33  	client *http.Client
    34  }
    35  
    36  // OutboundHTTPOpt is an outbound HTTP transport option.
    37  type OutboundHTTPOpt func(opts *outboundCommHTTPOpts)
    38  
    39  // WithOutboundHTTPClient option is for creating an Outbound HTTP transport using an http.Client instance.
    40  func WithOutboundHTTPClient(client *http.Client) OutboundHTTPOpt {
    41  	return func(opts *outboundCommHTTPOpts) {
    42  		opts.client = client
    43  	}
    44  }
    45  
    46  // WithOutboundTimeout option is for creating an Outbound HTTP transport using a client timeout value.
    47  func WithOutboundTimeout(timeout time.Duration) OutboundHTTPOpt {
    48  	return func(opts *outboundCommHTTPOpts) {
    49  		opts.client.Timeout = timeout
    50  	}
    51  }
    52  
    53  // WithOutboundTLSConfig option is for creating an Outbound HTTP transport using a tls.Config instance.
    54  func WithOutboundTLSConfig(tlsConfig *tls.Config) OutboundHTTPOpt {
    55  	return func(opts *outboundCommHTTPOpts) {
    56  		opts.client = &http.Client{
    57  			Transport: &http.Transport{
    58  				TLSClientConfig: tlsConfig,
    59  			},
    60  		}
    61  	}
    62  }
    63  
    64  // OutboundHTTPClient represents the Outbound HTTP transport instance.
    65  type OutboundHTTPClient struct {
    66  	client *http.Client
    67  }
    68  
    69  // NewOutbound creates a new instance of Outbound HTTP transport to Post requests to other Agents.
    70  // An http.Client or tls.Config options is mandatory to create a transport instance.
    71  func NewOutbound(opts ...OutboundHTTPOpt) (*OutboundHTTPClient, error) {
    72  	clOpts := &outboundCommHTTPOpts{}
    73  	// Apply options
    74  	for _, opt := range opts {
    75  		opt(clOpts)
    76  	}
    77  
    78  	if clOpts.client == nil {
    79  		return nil, errors.New("creation of outbound transport requires an HTTP client")
    80  	}
    81  
    82  	cs := &OutboundHTTPClient{
    83  		client: clOpts.client,
    84  	}
    85  
    86  	return cs, nil
    87  }
    88  
    89  // Start starts outbound transport.
    90  func (cs *OutboundHTTPClient) Start(prov transport.Provider) error {
    91  	return nil
    92  }
    93  
    94  // Send sends a2a exchange data via HTTP (client side).
    95  func (cs *OutboundHTTPClient) Send(data []byte, destination *service.Destination) (string, error) {
    96  	uri, err := destination.ServiceEndpoint.URI()
    97  	if err != nil {
    98  		return "", fmt.Errorf("error getting ServiceEndpoint URI: %w", err)
    99  	}
   100  
   101  	resp, err := cs.client.Post(uri, commContentType, bytes.NewBuffer(data))
   102  	if err != nil {
   103  		logger.Errorf("posting DID envelope to agent failed [%s, %v]", destination.ServiceEndpoint, err)
   104  		return "", err
   105  	}
   106  
   107  	var respData string
   108  
   109  	if resp != nil {
   110  		// handle response
   111  		defer func() {
   112  			e := resp.Body.Close()
   113  			if e != nil {
   114  				logger.Errorf("closing response body failed: %v", e)
   115  			}
   116  		}()
   117  
   118  		buf := new(bytes.Buffer)
   119  
   120  		_, e := buf.ReadFrom(resp.Body)
   121  		if e != nil {
   122  			return "", e
   123  		}
   124  
   125  		respData = buf.String()
   126  
   127  		isStatusSuccess := resp.StatusCode == http.StatusAccepted || resp.StatusCode == http.StatusOK
   128  		if !isStatusSuccess {
   129  			logger.Errorf("didcomm failed : transport=http serviceEndpoint=%s status=%v errMsg=%s",
   130  				destination.ServiceEndpoint, resp.Status, respData)
   131  
   132  			return "", fmt.Errorf("received unsuccessful POST HTTP status from agent "+
   133  				"[%s, %v %s]", destination.ServiceEndpoint, resp.Status, respData)
   134  		}
   135  	}
   136  
   137  	return respData, nil
   138  }
   139  
   140  // AcceptRecipient checks if there is a connection for the list of recipient keys.
   141  func (cs *OutboundHTTPClient) AcceptRecipient([]string) bool {
   142  	return false
   143  }
   144  
   145  // Accept url.
   146  func (cs *OutboundHTTPClient) Accept(url string) bool {
   147  	return strings.HasPrefix(url, httpScheme)
   148  }