github.com/hyperledger/aries-framework-go@v0.3.2/pkg/didcomm/transport/http/inbound.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  	"context"
    11  	"errors"
    12  	"fmt"
    13  	"io/ioutil"
    14  	"net/http"
    15  
    16  	"github.com/rs/cors"
    17  
    18  	"github.com/hyperledger/aries-framework-go/pkg/common/log"
    19  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/transport"
    20  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/transport/internal"
    21  )
    22  
    23  var logger = log.New("aries-framework/http")
    24  
    25  // TODO https://github.com/hyperledger/aries-framework-go/issues/891 Support for Transport Return Route (Duplex)
    26  
    27  // NewInboundHandler will create a new handler to enforce Did-Comm HTTP transport specs
    28  // then routes processing to the mandatory 'msgHandler' argument.
    29  //
    30  // Arguments:
    31  // * 'msgHandler' is the handler function that will be executed with the inbound request payload.
    32  //    Users of this library must manage the handling of all inbound payloads in this function.
    33  func NewInboundHandler(prov transport.Provider) (http.Handler, error) {
    34  	if prov == nil || prov.InboundMessageHandler() == nil {
    35  		logger.Errorf("Error creating a new inbound handler: message handler function is nil")
    36  		return nil, errors.New("creation of inbound handler failed")
    37  	}
    38  
    39  	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    40  		processPOSTRequest(w, r, prov)
    41  	})
    42  
    43  	return cors.Default().Handler(handler), nil
    44  }
    45  
    46  func processPOSTRequest(w http.ResponseWriter, r *http.Request, prov transport.Provider) {
    47  	if valid := validateHTTPMethod(w, r); !valid {
    48  		return
    49  	}
    50  
    51  	if valid := validatePayload(r, w); !valid {
    52  		return
    53  	}
    54  
    55  	body, err := ioutil.ReadAll(r.Body)
    56  	if err != nil {
    57  		logger.Errorf("Error reading request body: %s - returning Code: %d", err, http.StatusInternalServerError)
    58  		http.Error(w, "Failed to read payload", http.StatusInternalServerError)
    59  
    60  		return
    61  	}
    62  
    63  	unpackMsg, err := internal.UnpackMessage(body, prov.Packager(), "http")
    64  	if err != nil {
    65  		logger.Errorf("%w - returning Code: %d", err, http.StatusInternalServerError)
    66  		http.Error(w, "failed to unpack msg", http.StatusInternalServerError)
    67  
    68  		return
    69  	}
    70  
    71  	messageHandler := prov.InboundMessageHandler()
    72  
    73  	err = messageHandler(unpackMsg)
    74  	if err != nil {
    75  		// TODO https://github.com/hyperledger/aries-framework-go/issues/271 HTTP Response Codes based on errors
    76  		//  from service
    77  		logger.Errorf("incoming msg processing failed: %s", err)
    78  		w.WriteHeader(http.StatusInternalServerError)
    79  	} else {
    80  		w.WriteHeader(http.StatusAccepted)
    81  	}
    82  }
    83  
    84  // validatePayload validate and get the payload from the request.
    85  func validatePayload(r *http.Request, w http.ResponseWriter) bool {
    86  	if r.ContentLength == 0 { // empty payload should not be accepted
    87  		http.Error(w, "Empty payload", http.StatusBadRequest)
    88  		return false
    89  	}
    90  
    91  	return true
    92  }
    93  
    94  // validateHTTPMethod validate HTTP method and content-type.
    95  func validateHTTPMethod(w http.ResponseWriter, r *http.Request) bool {
    96  	if r.Method != "POST" {
    97  		http.Error(w, "HTTP Method not allowed", http.StatusMethodNotAllowed)
    98  		return false
    99  	}
   100  
   101  	ct := r.Header.Get("Content-type")
   102  
   103  	if ct != commContentType && ct != commContentTypeLegacy {
   104  		http.Error(w, fmt.Sprintf("Unsupported Content-type \"%s\"", ct), http.StatusUnsupportedMediaType)
   105  		return false
   106  	}
   107  
   108  	return true
   109  }
   110  
   111  // Inbound http type.
   112  type Inbound struct {
   113  	externalAddr      string
   114  	server            *http.Server
   115  	certFile, keyFile string
   116  }
   117  
   118  // NewInbound creates a new HTTP inbound transport instance.
   119  func NewInbound(internalAddr, externalAddr, certFile, keyFile string) (*Inbound, error) {
   120  	if internalAddr == "" {
   121  		return nil, errors.New("http address is mandatory")
   122  	}
   123  
   124  	if externalAddr == "" {
   125  		externalAddr = internalAddr
   126  	}
   127  
   128  	return &Inbound{
   129  		certFile:     certFile,
   130  		keyFile:      keyFile,
   131  		externalAddr: externalAddr,
   132  		server:       &http.Server{Addr: internalAddr},
   133  	}, nil
   134  }
   135  
   136  // Start the http server.
   137  func (i *Inbound) Start(prov transport.Provider) error {
   138  	handler, err := NewInboundHandler(prov)
   139  	if err != nil {
   140  		return fmt.Errorf("HTTP server start failed: %w", err)
   141  	}
   142  
   143  	i.server.Handler = handler
   144  
   145  	go func() {
   146  		if err := i.listenAndServe(); !errors.Is(err, http.ErrServerClosed) {
   147  			logger.Fatalf("HTTP server start with address [%s] failed, cause:  %s", i.server.Addr, err)
   148  		}
   149  	}()
   150  
   151  	return nil
   152  }
   153  
   154  func (i *Inbound) listenAndServe() error {
   155  	if i.certFile != "" && i.keyFile != "" {
   156  		return i.server.ListenAndServeTLS(i.certFile, i.keyFile)
   157  	}
   158  
   159  	return i.server.ListenAndServe()
   160  }
   161  
   162  // Stop the http server.
   163  func (i *Inbound) Stop() error {
   164  	if err := i.server.Shutdown(context.Background()); err != nil {
   165  		return fmt.Errorf("HTTP server shutdown failed: %w", err)
   166  	}
   167  
   168  	return nil
   169  }
   170  
   171  // Endpoint provides the http connection details.
   172  func (i *Inbound) Endpoint() string {
   173  	// return http prefix as framework only supports http
   174  	return i.externalAddr
   175  }