Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
FAB-17839 Ch.Part.API: List channels REST handler (#1232)
* FAB-17839 Ch.Part.API: List REST handler

Develop the following handler functionality.

List all channels:
`GET /participation/v1/channels`.

List a single handler:
`GET /participation/v1/channels/my-channel`.

Define the interface the Registrar will implement,
place skeleton methods in it.

Signed-off-by: Yoav Tock <tock@il.ibm.com>
Change-Id: I4ce65b406ccbbbe8e1dbd4a95680c3561f5fc7c9

* fix gofmt

Signed-off-by: Yoav Tock <tock@il.ibm.com>
Change-Id: I4836ff6dc3101c1ceaeb98db8234ee732f511717

* Review comments: split types to leaf package

Split the types used by packages multichannel & channelparticipation
to a leaf package, in order to minimize dependencies.

Signed-off-by: Yoav Tock <tock@il.ibm.com>
Change-Id: I0b9ea17fd85f1f5bf1aba3ea8be80fc7b19e190f

* Review comments: improve ChannelManagement interface

Streamline the ChannelManagement interface return types and type names.

Signed-off-by: Yoav Tock <tock@il.ibm.com>
Change-Id: I67e105f9e7e315c1ce6f5b16898de4aa94e609eb

* Review comments: use gorilla/mux to route requests

Instead of parsing and matching the path, use
gorilla/mux to route requests and identify the channel-id.

Signed-off-by: Yoav Tock <tock@il.ibm.com>
Change-Id: I6739fe027fab523a0a26a25838a36e42cc7f1469

* Review comments: use gorilla/mux to match headers

Signed-off-by: Yoav Tock <tock@il.ibm.com>
Change-Id: I179680a0556fef7110e69c0612687757844466e0

* Review comments: streamline types and return values

Change return values to non-pointer type.

Streamline ChannelList:
- Drop Size,
- Change Channels slice to carry app-channels only,
- Change SystemChannel to ChannelInfoShort.

Drop BlockHash from ChannelInfo.

Signed-off-by: Yoav Tock <tock@il.ibm.com>
Change-Id: I24c6ecccb58d95c307f03332ef0a4d2c06a063aa

* Review comments: rename methods

Signed-off-by: Yoav Tock <tock@il.ibm.com>
Change-Id: Ib5b88ab5bc63e4c3ed3ef1cd44c6a17e755b8b91
  • Loading branch information
tock-ibm committed May 19, 2020
1 parent 0defb0e commit 663a9bf
Show file tree
Hide file tree
Showing 6 changed files with 664 additions and 23 deletions.
143 changes: 143 additions & 0 deletions orderer/common/channelparticipation/mocks/channel_management.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

170 changes: 154 additions & 16 deletions orderer/common/channelparticipation/restapi.go
Expand Up @@ -9,21 +9,36 @@ package channelparticipation
import (
"encoding/json"
"net/http"
"path"
"strings"

"github.com/gorilla/mux"
"github.com/hyperledger/fabric/common/configtx"
"github.com/hyperledger/fabric/common/flogging"
"github.com/hyperledger/fabric/orderer/common/localconfig"
"github.com/hyperledger/fabric/orderer/common/types"
"github.com/pkg/errors"
)

const URLBaseV1 = "/participation/v1/"

type ErrorResponse struct {
Error string `json:"error"`
}
const (
URLBaseV1 = "/participation/v1/"
URLBaseV1Channels = URLBaseV1 + "channels"
channelIDKey = "channelID"
urlWithChannelIDKey = URLBaseV1Channels + "/{" + channelIDKey + "}"
)

//go:generate counterfeiter -o mocks/channel_management.go -fake-name ChannelManagement . ChannelManagement

type ChannelManagement interface {
// ChannelList returns a slice of ChannelInfoShort containing all application channels (excluding the system
// channel), and ChannelInfoShort of the system channel (nil if does not exist).
// The URL fields are empty, and are to be completed by the caller.
ChannelList() types.ChannelList

// ChannelInfo provides extended status information about a channel.
// The URL field is empty, and is to be completed by the caller.
ChannelInfo(channelID string) (types.ChannelInfo, error)

// TODO skeleton
}

Expand All @@ -32,15 +47,31 @@ type HTTPHandler struct {
logger *flogging.FabricLogger
config localconfig.ChannelParticipation
registrar ChannelManagement
router *mux.Router
// TODO skeleton
}

func NewHTTPHandler(config localconfig.ChannelParticipation, registrar ChannelManagement) *HTTPHandler {
return &HTTPHandler{
handler := &HTTPHandler{
logger: flogging.MustGetLogger("orderer.commmon.channelparticipation"),
config: config,
registrar: registrar,
router: mux.NewRouter(),
}

handler.router.HandleFunc(urlWithChannelIDKey, handler.serveListOne).Methods(http.MethodGet)
handler.router.HandleFunc(urlWithChannelIDKey, handler.serveJoin).Methods(http.MethodPost).Headers(
"Content-Type", "application/json") // TODO support application/octet-stream & multipart/form-data
handler.router.HandleFunc(urlWithChannelIDKey, handler.serveBadContentType).Methods(http.MethodPost)
handler.router.HandleFunc(urlWithChannelIDKey, handler.serveRemove).Methods(http.MethodDelete)
handler.router.HandleFunc(urlWithChannelIDKey, handler.serveNotAllowed)

handler.router.HandleFunc(URLBaseV1Channels, handler.serveListAll).Methods("GET")
handler.router.HandleFunc(URLBaseV1Channels, handler.serveNotAllowed)

handler.router.Handle(URLBaseV1, nil) //TODO redirect to URLBaseV1Channels

return handler
}

func (h *HTTPHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
Expand All @@ -50,23 +81,130 @@ func (h *HTTPHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
return
}

switch req.Method {
case http.MethodGet, http.MethodPost, http.MethodDelete:
//TODO skeleton
err := errors.Errorf("not implemented yet: %s %s", req.Method, req.URL.String())
h.sendResponseJsonError(resp, http.StatusNotImplemented, err)
h.router.ServeHTTP(resp, req)
}

// List all channels
func (h *HTTPHandler) serveListAll(resp http.ResponseWriter, req *http.Request) {
_, err := negotiateContentType(req) // Only application/json for now
if err != nil {
h.sendResponseJsonError(resp, http.StatusNotAcceptable, err)
return
}
channelList := h.registrar.ChannelList()
if channelList.SystemChannel != nil && channelList.SystemChannel.Name != "" {
channelList.SystemChannel.URL = path.Join(URLBaseV1Channels, channelList.SystemChannel.Name)
}
for i, info := range channelList.Channels {
channelList.Channels[i].URL = path.Join(URLBaseV1Channels, info.Name)
}
h.sendResponseOK(resp, channelList)
}

// List a single channel
func (h *HTTPHandler) serveListOne(resp http.ResponseWriter, req *http.Request) {
_, err := negotiateContentType(req) // Only application/json for now
if err != nil {
h.sendResponseJsonError(resp, http.StatusNotAcceptable, err)
return
}

channelID, ok := mux.Vars(req)[channelIDKey]
if !ok {
h.sendResponseJsonError(resp, http.StatusInternalServerError, errors.Wrap(err, "missing channel ID"))
return
}

if err = configtx.ValidateChannelID(channelID); err != nil {
h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrap(err, "invalid channel ID"))
return
}
infoFull, err := h.registrar.ChannelInfo(channelID)
if err != nil {
h.sendResponseJsonError(resp, http.StatusNotFound, err)
return
}
h.sendResponseOK(resp, infoFull)
}

// Join a channel
func (h *HTTPHandler) serveJoin(resp http.ResponseWriter, req *http.Request) {
_, err := negotiateContentType(req) // Only application/json for now
if err != nil {
h.sendResponseJsonError(resp, http.StatusNotAcceptable, err)
return
}

default:
err := errors.Errorf("invalid request method: %s", req.Method)
h.sendResponseJsonError(resp, http.StatusBadRequest, err)
//TODO
err = errors.Errorf("not implemented yet: %s %s", req.Method, req.URL.String())
h.sendResponseJsonError(resp, http.StatusNotImplemented, err)
}

// Remove a channel
func (h *HTTPHandler) serveRemove(resp http.ResponseWriter, req *http.Request) {
_, err := negotiateContentType(req) // Only application/json for now
if err != nil {
h.sendResponseJsonError(resp, http.StatusNotAcceptable, err)
return
}

//TODO
err = errors.Errorf("not implemented yet: %s %s", req.Method, req.URL.String())
h.sendResponseJsonError(resp, http.StatusNotImplemented, err)
}

func (h *HTTPHandler) serveBadContentType(resp http.ResponseWriter, req *http.Request) {
err := errors.Errorf("unsupported Content-Type: %s", req.Header.Values("Content-Type"))
h.sendResponseJsonError(resp, http.StatusBadRequest, err)
}

func (h *HTTPHandler) serveNotAllowed(resp http.ResponseWriter, req *http.Request) {
err := errors.Errorf("invalid request method: %s", req.Method)
encoder := json.NewEncoder(resp)
resp.WriteHeader(http.StatusMethodNotAllowed)
if _, ok := mux.Vars(req)[channelIDKey]; ok {
resp.Header().Set("Allow", "GET, POST, DELETE")
} else {
resp.Header().Set("Allow", "GET")
}
resp.Header().Set("Content-Type", "application/json")
if err := encoder.Encode(&types.ErrorResponse{Error: err.Error()}); err != nil {
h.logger.Errorf("failed to encode error, err: %s", err)
}
}

func negotiateContentType(req *http.Request) (string, error) {
acceptReq := req.Header.Get("Accept")
if len(acceptReq) == 0 {
return "application/json", nil
}

options := strings.Split(acceptReq, ",")
for _, opt := range options {
if strings.Contains(opt, "application/json") ||
strings.Contains(opt, "application/*") ||
strings.Contains(opt, "*/*") {
return "application/json", nil
}
}

return "", errors.New("response Content-Type is application/json only")
}

func (h *HTTPHandler) sendResponseJsonError(resp http.ResponseWriter, code int, err error) {
encoder := json.NewEncoder(resp)
resp.WriteHeader(code)
resp.Header().Set("Content-Type", "application/json")
if err := encoder.Encode(&ErrorResponse{Error: err.Error()}); err != nil {
h.logger.Errorw("failed to encode payload", "error", err)
if err := encoder.Encode(&types.ErrorResponse{Error: err.Error()}); err != nil {
h.logger.Errorf("failed to encode error, err: %s", err)
}
}

func (h *HTTPHandler) sendResponseOK(resp http.ResponseWriter, content interface{}) {
encoder := json.NewEncoder(resp)
resp.WriteHeader(http.StatusOK)
resp.Header().Set("Content-Type", "application/json")
if err := encoder.Encode(content); err != nil {
h.logger.Errorf("failed to encode content, err: %s", err)
}
}

0 comments on commit 663a9bf

Please sign in to comment.