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 }