-
Notifications
You must be signed in to change notification settings - Fork 8.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[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 <jyellick@us.ibm.com>
- Loading branch information
Jason Yellick
committed
Jun 2, 2017
1 parent
a5d6216
commit 5fb91b5
Showing
5 changed files
with
325 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* | ||
Copyright IBM Corp. 2017 All Rights Reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package main | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"net/http" | ||
|
||
"github.com/hyperledger/fabric/common/tools/configtxlator/rest" | ||
|
||
"github.com/op/go-logging" | ||
) | ||
|
||
var logger = logging.MustGetLogger("configtxlator") | ||
|
||
func main() { | ||
var serverPort int | ||
|
||
flag.IntVar(&serverPort, "serverPort", 7059, "Specify the port for the REST server to listen on.") | ||
flag.Parse() | ||
|
||
logger.Infof("Serving HTTP requests on port: %d", serverPort) | ||
err := http.ListenAndServe(fmt.Sprintf(":%d", serverPort), rest.NewRouter()) | ||
|
||
logger.Fatal("Error runing http server:", err) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
/* | ||
Copyright IBM Corp. 2017 All Rights Reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package rest | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"reflect" | ||
|
||
"github.com/hyperledger/fabric/common/tools/protolator" | ||
|
||
// Import these to register the proto types | ||
_ "github.com/hyperledger/fabric/protos/common" | ||
_ "github.com/hyperledger/fabric/protos/msp" | ||
_ "github.com/hyperledger/fabric/protos/orderer" | ||
_ "github.com/hyperledger/fabric/protos/peer" | ||
|
||
"github.com/golang/protobuf/proto" | ||
"github.com/gorilla/mux" | ||
) | ||
|
||
func getMsgType(r *http.Request) (proto.Message, error) { | ||
vars := mux.Vars(r) | ||
msgName := vars["msgName"] // Will not arrive is unset | ||
|
||
msgType := proto.MessageType(msgName) | ||
if msgType == nil { | ||
return nil, fmt.Errorf("message name not found") | ||
} | ||
return reflect.New(msgType.Elem()).Interface().(proto.Message), nil | ||
} | ||
|
||
func Decode(w http.ResponseWriter, r *http.Request) { | ||
msg, err := getMsgType(r) | ||
if err != nil { | ||
w.WriteHeader(http.StatusNotFound) | ||
fmt.Fprintln(w, err) | ||
return | ||
} | ||
|
||
buf, err := ioutil.ReadAll(r.Body) | ||
if err != nil { | ||
w.WriteHeader(http.StatusBadRequest) | ||
fmt.Fprintln(w, err) | ||
return | ||
} | ||
|
||
err = proto.Unmarshal(buf, msg) | ||
if err != nil { | ||
w.WriteHeader(http.StatusBadRequest) | ||
fmt.Fprintln(w, err) | ||
return | ||
} | ||
|
||
var buffer bytes.Buffer | ||
err = protolator.DeepMarshalJSON(&buffer, msg) | ||
if err != nil { | ||
w.WriteHeader(http.StatusBadRequest) | ||
fmt.Fprintln(w, err) | ||
return | ||
} | ||
|
||
w.WriteHeader(http.StatusOK) | ||
w.Header().Set("Content-Type", "application/json") | ||
buffer.WriteTo(w) | ||
} | ||
|
||
func Encode(w http.ResponseWriter, r *http.Request) { | ||
msg, err := getMsgType(r) | ||
if err != nil { | ||
w.WriteHeader(http.StatusNotFound) | ||
fmt.Fprintln(w, err) | ||
return | ||
} | ||
|
||
err = protolator.DeepUnmarshalJSON(r.Body, msg) | ||
if err != nil { | ||
w.WriteHeader(http.StatusBadRequest) | ||
fmt.Fprintln(w, err) | ||
return | ||
} | ||
|
||
data, err := proto.Marshal(msg) | ||
if err != nil { | ||
w.WriteHeader(http.StatusBadRequest) | ||
fmt.Fprintln(w, err) | ||
return | ||
} | ||
|
||
w.WriteHeader(http.StatusOK) | ||
w.Header().Set("Content-Type", "application/octet-stream") | ||
w.Write(data) | ||
} |
128 changes: 128 additions & 0 deletions
128
common/tools/configtxlator/rest/protolator_handlers_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/* | ||
Copyright IBM Corp. 2017 All Rights Reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package rest | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"net/http" | ||
"net/http/httptest" | ||
"strings" | ||
"testing" | ||
|
||
cb "github.com/hyperledger/fabric/protos/common" | ||
"github.com/hyperledger/fabric/protos/utils" | ||
|
||
"github.com/golang/protobuf/proto" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
var ( | ||
testProto = &cb.Block{ | ||
Header: &cb.BlockHeader{ | ||
PreviousHash: []byte("foo"), | ||
}, | ||
Data: &cb.BlockData{ | ||
Data: [][]byte{ | ||
utils.MarshalOrPanic(&cb.Envelope{ | ||
Signature: []byte("bar"), | ||
}), | ||
}, | ||
}, | ||
} | ||
|
||
testOutput = `{"data":{"data":[{"signature":"YmFy"}]},"header":{"previous_hash":"Zm9v"}}` | ||
) | ||
|
||
func TestProtolatorDecode(t *testing.T) { | ||
data, err := proto.Marshal(testProto) | ||
assert.NoError(t, err) | ||
|
||
url := fmt.Sprintf("/protolator/decode/%s", proto.MessageName(testProto)) | ||
|
||
req, _ := http.NewRequest("POST", url, bytes.NewReader(data)) | ||
rec := httptest.NewRecorder() | ||
r := NewRouter() | ||
r.ServeHTTP(rec, req) | ||
|
||
assert.Equal(t, http.StatusOK, rec.Code) | ||
|
||
// Remove all the whitespace | ||
compactJSON := strings.Replace(strings.Replace(strings.Replace(rec.Body.String(), "\n", "", -1), "\t", "", -1), " ", "", -1) | ||
|
||
assert.Equal(t, testOutput, compactJSON) | ||
} | ||
|
||
func TestProtolatorEncode(t *testing.T) { | ||
|
||
url := fmt.Sprintf("/protolator/encode/%s", proto.MessageName(testProto)) | ||
|
||
req, _ := http.NewRequest("POST", url, bytes.NewReader([]byte(testOutput))) | ||
rec := httptest.NewRecorder() | ||
r := NewRouter() | ||
r.ServeHTTP(rec, req) | ||
|
||
assert.Equal(t, http.StatusOK, rec.Code) | ||
|
||
outputMsg := &cb.Block{} | ||
|
||
err := proto.Unmarshal(rec.Body.Bytes(), outputMsg) | ||
assert.NoError(t, err) | ||
assert.Equal(t, testProto, outputMsg) | ||
} | ||
|
||
func TestProtolatorDecodeNonExistantProto(t *testing.T) { | ||
req, _ := http.NewRequest("POST", "/protolator/decode/NonExistantMsg", bytes.NewReader([]byte{})) | ||
rec := httptest.NewRecorder() | ||
r := NewRouter() | ||
r.ServeHTTP(rec, req) | ||
|
||
assert.Equal(t, http.StatusNotFound, rec.Code) | ||
} | ||
|
||
func TestProtolatorEncodeNonExistantProto(t *testing.T) { | ||
req, _ := http.NewRequest("POST", "/protolator/encode/NonExistantMsg", bytes.NewReader([]byte{})) | ||
rec := httptest.NewRecorder() | ||
r := NewRouter() | ||
r.ServeHTTP(rec, req) | ||
|
||
assert.Equal(t, http.StatusNotFound, rec.Code) | ||
} | ||
|
||
func TestProtolatorDecodeBadData(t *testing.T) { | ||
url := fmt.Sprintf("/protolator/decode/%s", proto.MessageName(testProto)) | ||
|
||
req, _ := http.NewRequest("POST", url, bytes.NewReader([]byte("Garbage"))) | ||
|
||
rec := httptest.NewRecorder() | ||
r := NewRouter() | ||
r.ServeHTTP(rec, req) | ||
|
||
assert.Equal(t, http.StatusBadRequest, rec.Code) | ||
} | ||
|
||
func TestProtolatorEncodeBadData(t *testing.T) { | ||
url := fmt.Sprintf("/protolator/encode/%s", proto.MessageName(testProto)) | ||
|
||
req, _ := http.NewRequest("POST", url, bytes.NewReader([]byte("Garbage"))) | ||
|
||
rec := httptest.NewRecorder() | ||
r := NewRouter() | ||
r.ServeHTTP(rec, req) | ||
|
||
assert.Equal(t, http.StatusBadRequest, rec.Code) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* | ||
Copyright IBM Corp. 2017 All Rights Reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package rest | ||
|
||
import ( | ||
"github.com/gorilla/mux" | ||
"github.com/op/go-logging" | ||
) | ||
|
||
var logger = logging.MustGetLogger("configtxlator/rest") | ||
|
||
func NewRouter() *mux.Router { | ||
router := mux.NewRouter().StrictSlash(true) | ||
router. | ||
HandleFunc("/protolator/encode/{msgName}", Encode). | ||
Methods("POST") | ||
|
||
router. | ||
HandleFunc("/protolator/decode/{msgName}", Decode). | ||
Methods("POST") | ||
|
||
return router | ||
} |