github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/orderer/common/channelparticipation/restapi.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package channelparticipation
     8  
     9  import (
    10  	"encoding/json"
    11  	"io/ioutil"
    12  	"mime"
    13  	"mime/multipart"
    14  	"net/http"
    15  	"path"
    16  	"strings"
    17  
    18  	"github.com/golang/protobuf/proto"
    19  	"github.com/gorilla/mux"
    20  	"github.com/hechain20/hechain/common/configtx"
    21  	"github.com/hechain20/hechain/common/flogging"
    22  	"github.com/hechain20/hechain/orderer/common/localconfig"
    23  	"github.com/hechain20/hechain/orderer/common/types"
    24  	cb "github.com/hyperledger/fabric-protos-go/common"
    25  	"github.com/pkg/errors"
    26  )
    27  
    28  const (
    29  	URLBaseV1              = "/participation/v1/"
    30  	URLBaseV1Channels      = URLBaseV1 + "channels"
    31  	FormDataConfigBlockKey = "config-block"
    32  
    33  	channelIDKey        = "channelID"
    34  	urlWithChannelIDKey = URLBaseV1Channels + "/{" + channelIDKey + "}"
    35  )
    36  
    37  //go:generate counterfeiter -o mocks/channel_management.go -fake-name ChannelManagement . ChannelManagement
    38  
    39  type ChannelManagement interface {
    40  	// ChannelList returns a slice of ChannelInfoShort containing all application channels (excluding the system
    41  	// channel), and ChannelInfoShort of the system channel (nil if does not exist).
    42  	// The URL fields are empty, and are to be completed by the caller.
    43  	ChannelList() types.ChannelList
    44  
    45  	// ChannelInfo provides extended status information about a channel.
    46  	// The URL field is empty, and is to be completed by the caller.
    47  	ChannelInfo(channelID string) (types.ChannelInfo, error)
    48  
    49  	// JoinChannel instructs the orderer to create a channel and join it with the provided config block.
    50  	// The URL field is empty, and is to be completed by the caller.
    51  	JoinChannel(channelID string, configBlock *cb.Block, isAppChannel bool) (types.ChannelInfo, error)
    52  
    53  	// RemoveChannel instructs the orderer to remove a channel.
    54  	RemoveChannel(channelID string) error
    55  }
    56  
    57  // HTTPHandler handles all the HTTP requests to the channel participation API.
    58  type HTTPHandler struct {
    59  	logger    *flogging.FabricLogger
    60  	config    localconfig.ChannelParticipation
    61  	registrar ChannelManagement
    62  	router    *mux.Router
    63  }
    64  
    65  func NewHTTPHandler(config localconfig.ChannelParticipation, registrar ChannelManagement) *HTTPHandler {
    66  	handler := &HTTPHandler{
    67  		logger:    flogging.MustGetLogger("orderer.commmon.channelparticipation"),
    68  		config:    config,
    69  		registrar: registrar,
    70  		router:    mux.NewRouter(),
    71  	}
    72  
    73  	// swagger:operation GET /v1/participation/channels/{channelID} channels listChannel
    74  	// ---
    75  	// summary: Returns detailed channel information for a specific channel Ordering Service Node (OSN) has joined.
    76  	// parameters:
    77  	// - name: channelID
    78  	//   in: path
    79  	//   description: Channel ID
    80  	//   required: true
    81  	//   type: string
    82  	// responses:
    83  	//    '200':
    84  	//       description: Successfully retrieved channel.
    85  	//       schema:
    86  	//         "$ref": "#/definitions/channelInfo"
    87  	//       headers:
    88  	//        Content-Type:
    89  	//          description: The media type of the resource
    90  	//          type: string
    91  	//        Cache-Control:
    92  	//         description: The directives for caching responses
    93  	//         type: string
    94  
    95  	handler.router.HandleFunc(urlWithChannelIDKey, handler.serveListOne).Methods(http.MethodGet)
    96  
    97  	// swagger:operation DELETE /v1/participation/channels/{channelID} channels removeChannel
    98  	// ---
    99  	// summary: Removes an Ordering Service Node (OSN) from a channel.
   100  	// parameters:
   101  	// - name: channelID
   102  	//   in: path
   103  	//   description: Channel ID
   104  	//   required: true
   105  	//   type: string
   106  	// responses:
   107  	//    '204':
   108  	//      description: Successfully removed channel.
   109  	//    '400':
   110  	//      description: Bad request.
   111  	//    '404':
   112  	//      description: The channel does not exist.
   113  	//    '405':
   114  	//      description: The system channel exists, removal is not allowed.
   115  	//    '409':
   116  	//      description: The channel is pending removal.
   117  
   118  	handler.router.HandleFunc(urlWithChannelIDKey, handler.serveRemove).Methods(http.MethodDelete)
   119  	handler.router.HandleFunc(urlWithChannelIDKey, handler.serveNotAllowed)
   120  
   121  	// swagger:operation GET /v1/participation/channels channels listChannels
   122  	// ---
   123  	// summary: Returns the complete list of channels an Ordering Service Node (OSN) has joined.
   124  	// responses:
   125  	//    '200':
   126  	//       description: Successfully retrieved channels.
   127  	//       schema:
   128  	//         "$ref": "#/definitions/channelList"
   129  	//       headers:
   130  	//        Content-Type:
   131  	//          description: The media type of the resource
   132  	//          type: string
   133  	//        Cache-Control:
   134  	//         description: The directives for caching responses
   135  	//         type: string
   136  
   137  	handler.router.HandleFunc(URLBaseV1Channels, handler.serveListAll).Methods(http.MethodGet)
   138  
   139  	// swagger:operation POST /v1/participation/channels channels joinChannel
   140  	// ---
   141  	// summary: Joins an Ordering Service Node (OSN) to a channel.
   142  	// description: If a channel does not yet exist, it will be created.
   143  	// parameters:
   144  	// - name: configBlock
   145  	//   in: formData
   146  	//   type: string
   147  	//   required: true
   148  	// responses:
   149  	//    '201':
   150  	//      description: Successfully joined channel.
   151  	//      schema:
   152  	//        "$ref": "#/definitions/channelInfo"
   153  	//      headers:
   154  	//       Content-Type:
   155  	//         description: The media type of the resource
   156  	//         type: string
   157  	//       Location:
   158  	//        description: The URL to redirect a page to
   159  	//        type: string
   160  	//    '400':
   161  	//      description: Cannot join channel.
   162  	//    '403':
   163  	//      description: The client is trying to join the system-channel that does not exist, but application channels exist.
   164  	//    '405':
   165  	//      description: |
   166  	//                   The client is trying to join an app-channel, but the system channel exists.
   167  	//                   The client is trying to join an app-channel that exists, but the system channel does not.
   168  	//                   The client is trying to join the system-channel, and it exists.
   169  	//    '409':
   170  	//      description: The client is trying to join a channel that is currently being removed.
   171  	//    '500':
   172  	//      description: Removal of channel failed.
   173  	// consumes:
   174  	//   - multipart/form-data
   175  
   176  	handler.router.HandleFunc(URLBaseV1Channels, handler.serveJoin).Methods(http.MethodPost).HeadersRegexp(
   177  		"Content-Type", "multipart/form-data*")
   178  	handler.router.HandleFunc(URLBaseV1Channels, handler.serveBadContentType).Methods(http.MethodPost)
   179  
   180  	handler.router.HandleFunc(URLBaseV1Channels, handler.serveNotAllowed)
   181  
   182  	handler.router.HandleFunc(URLBaseV1, handler.redirectBaseV1).Methods(http.MethodGet)
   183  
   184  	return handler
   185  }
   186  
   187  func (h *HTTPHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
   188  	if !h.config.Enabled {
   189  		err := errors.New("channel participation API is disabled")
   190  		h.sendResponseJsonError(resp, http.StatusServiceUnavailable, err)
   191  		return
   192  	}
   193  
   194  	h.router.ServeHTTP(resp, req)
   195  }
   196  
   197  // List all channels
   198  func (h *HTTPHandler) serveListAll(resp http.ResponseWriter, req *http.Request) {
   199  	_, err := negotiateContentType(req) // Only application/json responses for now
   200  	if err != nil {
   201  		h.sendResponseJsonError(resp, http.StatusNotAcceptable, err)
   202  		return
   203  	}
   204  	channelList := h.registrar.ChannelList()
   205  	if channelList.SystemChannel != nil && channelList.SystemChannel.Name != "" {
   206  		channelList.SystemChannel.URL = path.Join(URLBaseV1Channels, channelList.SystemChannel.Name)
   207  	}
   208  	for i, info := range channelList.Channels {
   209  		channelList.Channels[i].URL = path.Join(URLBaseV1Channels, info.Name)
   210  	}
   211  	resp.Header().Set("Cache-Control", "no-store")
   212  	h.sendResponseOK(resp, channelList)
   213  }
   214  
   215  // List a single channel
   216  func (h *HTTPHandler) serveListOne(resp http.ResponseWriter, req *http.Request) {
   217  	_, err := negotiateContentType(req) // Only application/json responses for now
   218  	if err != nil {
   219  		h.sendResponseJsonError(resp, http.StatusNotAcceptable, err)
   220  		return
   221  	}
   222  
   223  	channelID, err := h.extractChannelID(req, resp)
   224  	if err != nil {
   225  		return
   226  	}
   227  
   228  	infoFull, err := h.registrar.ChannelInfo(channelID)
   229  	if err != nil {
   230  		h.sendResponseJsonError(resp, http.StatusNotFound, err)
   231  		return
   232  	}
   233  	infoFull.URL = path.Join(URLBaseV1Channels, infoFull.Name)
   234  
   235  	resp.Header().Set("Cache-Control", "no-store")
   236  	h.sendResponseOK(resp, infoFull)
   237  }
   238  
   239  func (h *HTTPHandler) redirectBaseV1(resp http.ResponseWriter, req *http.Request) {
   240  	http.Redirect(resp, req, URLBaseV1Channels, http.StatusFound)
   241  }
   242  
   243  // Join a channel.
   244  // Expect multipart/form-data.
   245  func (h *HTTPHandler) serveJoin(resp http.ResponseWriter, req *http.Request) {
   246  	_, err := negotiateContentType(req) // Only application/json responses for now
   247  	if err != nil {
   248  		h.sendResponseJsonError(resp, http.StatusNotAcceptable, err)
   249  		return
   250  	}
   251  
   252  	_, params, err := mime.ParseMediaType(req.Header.Get("Content-Type"))
   253  	if err != nil {
   254  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrap(err, "cannot parse Mime media type"))
   255  		return
   256  	}
   257  
   258  	block := h.multipartFormDataBodyToBlock(params, req, resp)
   259  	if block == nil {
   260  		return
   261  	}
   262  
   263  	channelID, isAppChannel, err := ValidateJoinBlock(block)
   264  	if err != nil {
   265  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.WithMessage(err, "invalid join block"))
   266  		return
   267  	}
   268  
   269  	info, err := h.registrar.JoinChannel(channelID, block, isAppChannel)
   270  	if err != nil {
   271  		h.sendJoinError(err, resp)
   272  		return
   273  	}
   274  	info.URL = path.Join(URLBaseV1Channels, info.Name)
   275  
   276  	h.logger.Debugf("Successfully joined channel: %s", info.URL)
   277  	h.sendResponseCreated(resp, info.URL, info)
   278  }
   279  
   280  // Expect a multipart/form-data with a single part, of type file, with key FormDataConfigBlockKey.
   281  func (h *HTTPHandler) multipartFormDataBodyToBlock(params map[string]string, req *http.Request, resp http.ResponseWriter) *cb.Block {
   282  	boundary := params["boundary"]
   283  	reader := multipart.NewReader(
   284  		http.MaxBytesReader(resp, req.Body, int64(h.config.MaxRequestBodySize)),
   285  		boundary,
   286  	)
   287  	form, err := reader.ReadForm(2 * int64(h.config.MaxRequestBodySize))
   288  	if err != nil {
   289  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrap(err, "cannot read form from request body"))
   290  		return nil
   291  	}
   292  
   293  	if _, exist := form.File[FormDataConfigBlockKey]; !exist {
   294  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Errorf("form does not contains part key: %s", FormDataConfigBlockKey))
   295  		return nil
   296  	}
   297  
   298  	if len(form.File) != 1 || len(form.Value) != 0 {
   299  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.New("form contains too many parts"))
   300  		return nil
   301  	}
   302  
   303  	fileHeader := form.File[FormDataConfigBlockKey][0]
   304  	file, err := fileHeader.Open()
   305  	if err != nil {
   306  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrapf(err, "cannot open file part %s from request body", FormDataConfigBlockKey))
   307  		return nil
   308  	}
   309  
   310  	blockBytes, err := ioutil.ReadAll(file)
   311  	if err != nil {
   312  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrapf(err, "cannot read file part %s from request body", FormDataConfigBlockKey))
   313  		return nil
   314  	}
   315  
   316  	block := &cb.Block{}
   317  	err = proto.Unmarshal(blockBytes, block)
   318  	if err != nil {
   319  		h.logger.Debugf("Failed to unmarshal blockBytes: %s", err)
   320  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrapf(err, "cannot unmarshal file part %s into a block", FormDataConfigBlockKey))
   321  		return nil
   322  	}
   323  
   324  	return block
   325  }
   326  
   327  func (h *HTTPHandler) extractChannelID(req *http.Request, resp http.ResponseWriter) (string, error) {
   328  	channelID, ok := mux.Vars(req)[channelIDKey]
   329  	if !ok {
   330  		err := errors.New("missing channel ID")
   331  		h.sendResponseJsonError(resp, http.StatusInternalServerError, err)
   332  		return "", err
   333  	}
   334  
   335  	if err := configtx.ValidateChannelID(channelID); err != nil {
   336  		err = errors.WithMessage(err, "invalid channel ID")
   337  		h.sendResponseJsonError(resp, http.StatusBadRequest, err)
   338  		return "", err
   339  	}
   340  	return channelID, nil
   341  }
   342  
   343  func (h *HTTPHandler) sendJoinError(err error, resp http.ResponseWriter) {
   344  	h.logger.Debugf("Failed to JoinChannel: %s", err)
   345  	switch err {
   346  	case types.ErrSystemChannelExists:
   347  		// The client is trying to join an app-channel, but the system channel exists: only GET is allowed on app channels.
   348  		h.sendResponseNotAllowed(resp, errors.WithMessage(err, "cannot join"), http.MethodGet)
   349  	case types.ErrChannelAlreadyExists:
   350  		// The client is trying to join an app-channel that exists, but the system channel does not;
   351  		// The client is trying to join the system-channel, and it exists. GET & DELETE are allowed on the channel.
   352  		h.sendResponseNotAllowed(resp, errors.WithMessage(err, "cannot join"), http.MethodGet, http.MethodDelete)
   353  	case types.ErrAppChannelsAlreadyExists:
   354  		// The client is trying to join the system-channel that does not exist, but app channels exist.
   355  		h.sendResponseJsonError(resp, http.StatusForbidden, errors.WithMessage(err, "cannot join"))
   356  	case types.ErrChannelPendingRemoval:
   357  		// The client is trying to join a channel that is currently being removed.
   358  		h.sendResponseJsonError(resp, http.StatusConflict, errors.WithMessage(err, "cannot join"))
   359  	case types.ErrChannelRemovalFailure:
   360  		h.sendResponseJsonError(resp, http.StatusInternalServerError, errors.WithMessage(err, "cannot join"))
   361  	default:
   362  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.WithMessage(err, "cannot join"))
   363  	}
   364  }
   365  
   366  // Remove a channel
   367  func (h *HTTPHandler) serveRemove(resp http.ResponseWriter, req *http.Request) {
   368  	_, err := negotiateContentType(req) // Only application/json responses for now
   369  	if err != nil {
   370  		h.sendResponseJsonError(resp, http.StatusNotAcceptable, err)
   371  		return
   372  	}
   373  
   374  	channelID, err := h.extractChannelID(req, resp)
   375  	if err != nil {
   376  		return
   377  	}
   378  
   379  	err = h.registrar.RemoveChannel(channelID)
   380  	if err == nil {
   381  		h.logger.Debugf("Successfully removed channel: %s", channelID)
   382  		resp.WriteHeader(http.StatusNoContent)
   383  		return
   384  	}
   385  
   386  	h.logger.Debugf("Failed to remove channel: %s, err: %s", channelID, err)
   387  
   388  	switch err {
   389  	case types.ErrSystemChannelExists:
   390  		h.sendResponseNotAllowed(resp, errors.WithMessage(err, "cannot remove"), http.MethodGet)
   391  	case types.ErrChannelNotExist:
   392  		h.sendResponseJsonError(resp, http.StatusNotFound, errors.WithMessage(err, "cannot remove"))
   393  	case types.ErrChannelPendingRemoval:
   394  		h.sendResponseJsonError(resp, http.StatusConflict, errors.WithMessage(err, "cannot remove"))
   395  	default:
   396  		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.WithMessage(err, "cannot remove"))
   397  	}
   398  }
   399  
   400  func (h *HTTPHandler) serveBadContentType(resp http.ResponseWriter, req *http.Request) {
   401  	err := errors.Errorf("unsupported Content-Type: %s", req.Header.Values("Content-Type"))
   402  	h.sendResponseJsonError(resp, http.StatusBadRequest, err)
   403  }
   404  
   405  func (h *HTTPHandler) serveNotAllowed(resp http.ResponseWriter, req *http.Request) {
   406  	err := errors.Errorf("invalid request method: %s", req.Method)
   407  
   408  	if _, ok := mux.Vars(req)[channelIDKey]; ok {
   409  		h.sendResponseNotAllowed(resp, err, http.MethodGet, http.MethodDelete)
   410  		return
   411  	}
   412  
   413  	h.sendResponseNotAllowed(resp, err, http.MethodGet, http.MethodPost)
   414  }
   415  
   416  func negotiateContentType(req *http.Request) (string, error) {
   417  	acceptReq := req.Header.Get("Accept")
   418  	if len(acceptReq) == 0 {
   419  		return "application/json", nil
   420  	}
   421  
   422  	options := strings.Split(acceptReq, ",")
   423  	for _, opt := range options {
   424  		if strings.Contains(opt, "application/json") ||
   425  			strings.Contains(opt, "application/*") ||
   426  			strings.Contains(opt, "*/*") {
   427  			return "application/json", nil
   428  		}
   429  	}
   430  
   431  	return "", errors.New("response Content-Type is application/json only")
   432  }
   433  
   434  func (h *HTTPHandler) sendResponseJsonError(resp http.ResponseWriter, code int, err error) {
   435  	encoder := json.NewEncoder(resp)
   436  	resp.Header().Set("Content-Type", "application/json")
   437  	resp.WriteHeader(code)
   438  	if err := encoder.Encode(&types.ErrorResponse{Error: err.Error()}); err != nil {
   439  		h.logger.Errorf("failed to encode error, err: %s", err)
   440  	}
   441  }
   442  
   443  func (h *HTTPHandler) sendResponseOK(resp http.ResponseWriter, content interface{}) {
   444  	encoder := json.NewEncoder(resp)
   445  	resp.Header().Set("Content-Type", "application/json")
   446  	resp.WriteHeader(http.StatusOK)
   447  	if err := encoder.Encode(content); err != nil {
   448  		h.logger.Errorf("failed to encode content, err: %s", err)
   449  	}
   450  }
   451  
   452  func (h *HTTPHandler) sendResponseCreated(resp http.ResponseWriter, location string, content interface{}) {
   453  	encoder := json.NewEncoder(resp)
   454  	resp.Header().Set("Location", location)
   455  	resp.Header().Set("Content-Type", "application/json")
   456  	resp.WriteHeader(http.StatusCreated)
   457  	if err := encoder.Encode(content); err != nil {
   458  		h.logger.Errorf("failed to encode content, err: %s", err)
   459  	}
   460  }
   461  
   462  func (h *HTTPHandler) sendResponseNotAllowed(resp http.ResponseWriter, err error, allow ...string) {
   463  	encoder := json.NewEncoder(resp)
   464  	allowVal := strings.Join(allow, ", ")
   465  	resp.Header().Set("Allow", allowVal)
   466  	resp.Header().Set("Content-Type", "application/json")
   467  	resp.WriteHeader(http.StatusMethodNotAllowed)
   468  	if err := encoder.Encode(&types.ErrorResponse{Error: err.Error()}); err != nil {
   469  		h.logger.Errorf("failed to encode error, err: %s", err)
   470  	}
   471  }