Skip to content

Commit 5fb91b5

Browse files
author
Jason Yellick
committed
[FAB-4107] Expose proto translator via REST
As part of FAB-4100, there is a requirement to expose configuration utilities through a REST interface. This CR adds a proto-translator REST component. Further documentation is pending, but the high level usage is: POST binary-proto to /protolator/decode/fq.MessageType replies with a deeply marshaled JSON version of the proto. POST json-doc to /protolator/encode/fq.MessageType replies with the proto version of the deeply marshaled JSON doc. Change-Id: I595b92b1c292d8d4d360b0bd4223b138615143ac Signed-off-by: Jason Yellick <[email protected]>
1 parent a5d6216 commit 5fb91b5

File tree

5 files changed

+325
-2
lines changed

5 files changed

+325
-2
lines changed

Makefile

+10-2
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,11 @@ PROTOS = $(shell git ls-files *.proto | grep -v vendor)
8484
PROJECT_FILES = $(shell git ls-files)
8585
IMAGES = peer orderer ccenv javaenv buildenv testenv zookeeper kafka couchdb tools
8686
RELEASE_PLATFORMS = windows-amd64 darwin-amd64 linux-amd64 linux-ppc64le linux-s390x
87-
RELEASE_PKGS = configtxgen cryptogen
87+
RELEASE_PKGS = configtxgen cryptogen configtxlator
8888

8989
pkgmap.cryptogen := $(PKGNAME)/common/tools/cryptogen
9090
pkgmap.configtxgen := $(PKGNAME)/common/configtx/tool/configtxgen
91+
pkgmap.configtxlator := $(PKGNAME)/common/tools/configtxlator
9192
pkgmap.peer := $(PKGNAME)/peer
9293
pkgmap.orderer := $(PKGNAME)/orderer
9394
pkgmap.block-listener := $(PKGNAME)/examples/events/block-listener
@@ -129,6 +130,8 @@ orderer-docker: build/image/orderer/$(DUMMY)
129130
configtxgen: GO_TAGS+= nopkcs11
130131
configtxgen: build/bin/configtxgen
131132

133+
configtxlator: build/bin/configtxlator
134+
132135
cryptogen: build/bin/cryptogen
133136

134137
tools-docker: build/image/tools/$(DUMMY)
@@ -152,7 +155,7 @@ test-cmd:
152155
@echo "go test -ldflags \"$(GO_LDFLAGS)\""
153156

154157
docker: $(patsubst %,build/image/%/$(DUMMY), $(IMAGES))
155-
native: peer orderer configtxgen cryptogen
158+
native: peer orderer configtxgen cryptogen configtxlator
156159

157160
behave-deps: docker peer build/bin/block-listener configtxgen cryptogen
158161
behave: behave-deps
@@ -312,6 +315,11 @@ release/linux-s390x: DOCKER_ARCH=s390x
312315
release/linux-s390x: GO_TAGS+= nopkcs11
313316
release/linux-s390x: $(patsubst %,release/linux-s390x/bin/%, $(RELEASE_PKGS)) release/linux-s390x/install
314317

318+
release/%/bin/configtxlator: $(PROJECT_FILES)
319+
@echo "Building $@ for $(GOOS)-$(GOARCH)"
320+
mkdir -p $(@D)
321+
$(CGO_FLAGS) GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $(abspath $@) -tags "$(GO_TAGS)" -ldflags "$(GO_LDFLAGS)" $(pkgmap.$(@F))
322+
315323
release/%/bin/configtxgen: $(PROJECT_FILES)
316324
@echo "Building $@ for $(GOOS)-$(GOARCH)"
317325
mkdir -p $(@D)

common/tools/configtxlator/main.go

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"flag"
21+
"fmt"
22+
"net/http"
23+
24+
"github.com/hyperledger/fabric/common/tools/configtxlator/rest"
25+
26+
"github.com/op/go-logging"
27+
)
28+
29+
var logger = logging.MustGetLogger("configtxlator")
30+
31+
func main() {
32+
var serverPort int
33+
34+
flag.IntVar(&serverPort, "serverPort", 7059, "Specify the port for the REST server to listen on.")
35+
flag.Parse()
36+
37+
logger.Infof("Serving HTTP requests on port: %d", serverPort)
38+
err := http.ListenAndServe(fmt.Sprintf(":%d", serverPort), rest.NewRouter())
39+
40+
logger.Fatal("Error runing http server:", err)
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package rest
18+
19+
import (
20+
"bytes"
21+
"fmt"
22+
"io/ioutil"
23+
"net/http"
24+
"reflect"
25+
26+
"github.com/hyperledger/fabric/common/tools/protolator"
27+
28+
// Import these to register the proto types
29+
_ "github.com/hyperledger/fabric/protos/common"
30+
_ "github.com/hyperledger/fabric/protos/msp"
31+
_ "github.com/hyperledger/fabric/protos/orderer"
32+
_ "github.com/hyperledger/fabric/protos/peer"
33+
34+
"github.com/golang/protobuf/proto"
35+
"github.com/gorilla/mux"
36+
)
37+
38+
func getMsgType(r *http.Request) (proto.Message, error) {
39+
vars := mux.Vars(r)
40+
msgName := vars["msgName"] // Will not arrive is unset
41+
42+
msgType := proto.MessageType(msgName)
43+
if msgType == nil {
44+
return nil, fmt.Errorf("message name not found")
45+
}
46+
return reflect.New(msgType.Elem()).Interface().(proto.Message), nil
47+
}
48+
49+
func Decode(w http.ResponseWriter, r *http.Request) {
50+
msg, err := getMsgType(r)
51+
if err != nil {
52+
w.WriteHeader(http.StatusNotFound)
53+
fmt.Fprintln(w, err)
54+
return
55+
}
56+
57+
buf, err := ioutil.ReadAll(r.Body)
58+
if err != nil {
59+
w.WriteHeader(http.StatusBadRequest)
60+
fmt.Fprintln(w, err)
61+
return
62+
}
63+
64+
err = proto.Unmarshal(buf, msg)
65+
if err != nil {
66+
w.WriteHeader(http.StatusBadRequest)
67+
fmt.Fprintln(w, err)
68+
return
69+
}
70+
71+
var buffer bytes.Buffer
72+
err = protolator.DeepMarshalJSON(&buffer, msg)
73+
if err != nil {
74+
w.WriteHeader(http.StatusBadRequest)
75+
fmt.Fprintln(w, err)
76+
return
77+
}
78+
79+
w.WriteHeader(http.StatusOK)
80+
w.Header().Set("Content-Type", "application/json")
81+
buffer.WriteTo(w)
82+
}
83+
84+
func Encode(w http.ResponseWriter, r *http.Request) {
85+
msg, err := getMsgType(r)
86+
if err != nil {
87+
w.WriteHeader(http.StatusNotFound)
88+
fmt.Fprintln(w, err)
89+
return
90+
}
91+
92+
err = protolator.DeepUnmarshalJSON(r.Body, msg)
93+
if err != nil {
94+
w.WriteHeader(http.StatusBadRequest)
95+
fmt.Fprintln(w, err)
96+
return
97+
}
98+
99+
data, err := proto.Marshal(msg)
100+
if err != nil {
101+
w.WriteHeader(http.StatusBadRequest)
102+
fmt.Fprintln(w, err)
103+
return
104+
}
105+
106+
w.WriteHeader(http.StatusOK)
107+
w.Header().Set("Content-Type", "application/octet-stream")
108+
w.Write(data)
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package rest
18+
19+
import (
20+
"bytes"
21+
"fmt"
22+
"net/http"
23+
"net/http/httptest"
24+
"strings"
25+
"testing"
26+
27+
cb "github.com/hyperledger/fabric/protos/common"
28+
"github.com/hyperledger/fabric/protos/utils"
29+
30+
"github.com/golang/protobuf/proto"
31+
"github.com/stretchr/testify/assert"
32+
)
33+
34+
var (
35+
testProto = &cb.Block{
36+
Header: &cb.BlockHeader{
37+
PreviousHash: []byte("foo"),
38+
},
39+
Data: &cb.BlockData{
40+
Data: [][]byte{
41+
utils.MarshalOrPanic(&cb.Envelope{
42+
Signature: []byte("bar"),
43+
}),
44+
},
45+
},
46+
}
47+
48+
testOutput = `{"data":{"data":[{"signature":"YmFy"}]},"header":{"previous_hash":"Zm9v"}}`
49+
)
50+
51+
func TestProtolatorDecode(t *testing.T) {
52+
data, err := proto.Marshal(testProto)
53+
assert.NoError(t, err)
54+
55+
url := fmt.Sprintf("/protolator/decode/%s", proto.MessageName(testProto))
56+
57+
req, _ := http.NewRequest("POST", url, bytes.NewReader(data))
58+
rec := httptest.NewRecorder()
59+
r := NewRouter()
60+
r.ServeHTTP(rec, req)
61+
62+
assert.Equal(t, http.StatusOK, rec.Code)
63+
64+
// Remove all the whitespace
65+
compactJSON := strings.Replace(strings.Replace(strings.Replace(rec.Body.String(), "\n", "", -1), "\t", "", -1), " ", "", -1)
66+
67+
assert.Equal(t, testOutput, compactJSON)
68+
}
69+
70+
func TestProtolatorEncode(t *testing.T) {
71+
72+
url := fmt.Sprintf("/protolator/encode/%s", proto.MessageName(testProto))
73+
74+
req, _ := http.NewRequest("POST", url, bytes.NewReader([]byte(testOutput)))
75+
rec := httptest.NewRecorder()
76+
r := NewRouter()
77+
r.ServeHTTP(rec, req)
78+
79+
assert.Equal(t, http.StatusOK, rec.Code)
80+
81+
outputMsg := &cb.Block{}
82+
83+
err := proto.Unmarshal(rec.Body.Bytes(), outputMsg)
84+
assert.NoError(t, err)
85+
assert.Equal(t, testProto, outputMsg)
86+
}
87+
88+
func TestProtolatorDecodeNonExistantProto(t *testing.T) {
89+
req, _ := http.NewRequest("POST", "/protolator/decode/NonExistantMsg", bytes.NewReader([]byte{}))
90+
rec := httptest.NewRecorder()
91+
r := NewRouter()
92+
r.ServeHTTP(rec, req)
93+
94+
assert.Equal(t, http.StatusNotFound, rec.Code)
95+
}
96+
97+
func TestProtolatorEncodeNonExistantProto(t *testing.T) {
98+
req, _ := http.NewRequest("POST", "/protolator/encode/NonExistantMsg", bytes.NewReader([]byte{}))
99+
rec := httptest.NewRecorder()
100+
r := NewRouter()
101+
r.ServeHTTP(rec, req)
102+
103+
assert.Equal(t, http.StatusNotFound, rec.Code)
104+
}
105+
106+
func TestProtolatorDecodeBadData(t *testing.T) {
107+
url := fmt.Sprintf("/protolator/decode/%s", proto.MessageName(testProto))
108+
109+
req, _ := http.NewRequest("POST", url, bytes.NewReader([]byte("Garbage")))
110+
111+
rec := httptest.NewRecorder()
112+
r := NewRouter()
113+
r.ServeHTTP(rec, req)
114+
115+
assert.Equal(t, http.StatusBadRequest, rec.Code)
116+
}
117+
118+
func TestProtolatorEncodeBadData(t *testing.T) {
119+
url := fmt.Sprintf("/protolator/encode/%s", proto.MessageName(testProto))
120+
121+
req, _ := http.NewRequest("POST", url, bytes.NewReader([]byte("Garbage")))
122+
123+
rec := httptest.NewRecorder()
124+
r := NewRouter()
125+
r.ServeHTTP(rec, req)
126+
127+
assert.Equal(t, http.StatusBadRequest, rec.Code)
128+
}
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package rest
18+
19+
import (
20+
"github.com/gorilla/mux"
21+
"github.com/op/go-logging"
22+
)
23+
24+
var logger = logging.MustGetLogger("configtxlator/rest")
25+
26+
func NewRouter() *mux.Router {
27+
router := mux.NewRouter().StrictSlash(true)
28+
router.
29+
HandleFunc("/protolator/encode/{msgName}", Encode).
30+
Methods("POST")
31+
32+
router.
33+
HandleFunc("/protolator/decode/{msgName}", Decode).
34+
Methods("POST")
35+
36+
return router
37+
}

0 commit comments

Comments
 (0)