add Context.ReadJSONProtobuf method

Former-commit-id: 9c7f268c0d7be7e405ea97a5ac12257beba3a4f3
This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-06-28 23:05:07 +03:00
parent cdf0fe24e9
commit 871b4ef7fc
11 changed files with 362 additions and 6 deletions

View File

@ -445,6 +445,7 @@ New Context Methods:
- `Context.Protobuf(proto.Message)` sends protobuf to the client - `Context.Protobuf(proto.Message)` sends protobuf to the client
- `Context.MsgPack(interface{})` sends msgpack format data to the client - `Context.MsgPack(interface{})` sends msgpack format data to the client
- `Context.ReadProtobuf(ptr)` binds request body to a proto message - `Context.ReadProtobuf(ptr)` binds request body to a proto message
- `Context.ReadJSONProtobuf(ptr, ...options)` binds JSON request body to a proto message
- `Context.ReadMsgPack(ptr)` binds request body of a msgpack format to a struct - `Context.ReadMsgPack(ptr)` binds request body of a msgpack format to a struct
- `Context.ReadBody(ptr)` binds the request body to the "ptr" depending on the request's Method and Content-Type - `Context.ReadBody(ptr)` binds the request body to the "ptr" depending on the request's Method and Content-Type
- `Context.Defer(Handler)` works like `Party.Done` but for the request life-cycle instead - `Context.Defer(Handler)` works like `Party.Done` but for the request life-cycle instead

View File

@ -131,6 +131,7 @@
* Response Writer * Response Writer
* [Content Negotiation](response-writer/content-negotiation) * [Content Negotiation](response-writer/content-negotiation)
* [Text, Markdown, YAML, HTML, JSON, JSONP, Msgpack, XML and Binary](response-writer/write-rest/main.go) * [Text, Markdown, YAML, HTML, JSON, JSONP, Msgpack, XML and Binary](response-writer/write-rest/main.go)
* [Protocol Buffers](response-writer/protobuf/main.go)
* [Write Gzip](response-writer/write-gzip/main.go) * [Write Gzip](response-writer/write-gzip/main.go)
* [HTTP/2 Server Push](response-writer/http2push/main.go) * [HTTP/2 Server Push](response-writer/http2push/main.go)
* [Stream Writer](response-writer/stream-writer/main.go) * [Stream Writer](response-writer/stream-writer/main.go)

View File

@ -0,0 +1,18 @@
# Protocol Buffers
The `Context.Protobuf(proto.Message)` is the method which sends protos to the client. It accepts a [proto.Message](https://godoc.org/google.golang.org/protobuf/proto#Message) value.
> Note: Iris is using the newest version of the Go protocol buffers implementation. Read more about it at [The Go Blog: A new Go API for Protocol Buffers](https://blog.golang.org/protobuf-apiv2).
1. Install the protoc-gen-go tool.
```sh
$ go get -u google.golang.org/protobuf/cmd/protoc-gen-go@latest
```
2. Generate proto
```sh
$ protoc -I protos/ protos/hello.proto --go_out=.
```

View File

@ -0,0 +1,60 @@
package main
import (
"app/protos"
"github.com/kataras/iris/v12"
)
func main() {
app := iris.New()
app.Get("/", send)
app.Get("/json", sendAsJSON)
app.Post("/read", read)
app.Post("/read_json", readFromJSON)
app.Listen(":8080")
}
func send(ctx iris.Context) {
response := &protos.HelloReply{Message: "Hello, World!"}
ctx.Protobuf(response)
}
func sendAsJSON(ctx iris.Context) {
response := &protos.HelloReply{Message: "Hello, World!"}
options := iris.JSON{
Proto: iris.ProtoMarshalOptions{
AllowPartial: true,
Multiline: true,
Indent: " ",
},
}
ctx.JSON(response, options)
}
func read(ctx iris.Context) {
var request protos.HelloRequest
err := ctx.ReadProtobuf(&request)
if err != nil {
ctx.StopWithError(iris.StatusBadRequest, err)
return
}
ctx.Writef("HelloRequest.Name = %s", request.Name)
}
func readFromJSON(ctx iris.Context) {
var request protos.HelloRequest
err := ctx.ReadJSONProtobuf(&request)
if err != nil {
ctx.StopWithError(iris.StatusBadRequest, err)
return
}
ctx.Writef("HelloRequest.Name = %s", request.Name)
}

View File

@ -0,0 +1,209 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.11.1
// source: hello.proto
package protos
import (
proto "github.com/golang/protobuf/proto"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This is a compile-time assertion that a sufficiently up-to-date version
// of the legacy proto package is being used.
const _ = proto.ProtoPackageIsVersion4
type HelloRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
func (x *HelloRequest) Reset() {
*x = HelloRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_hello_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HelloRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloRequest) ProtoMessage() {}
func (x *HelloRequest) ProtoReflect() protoreflect.Message {
mi := &file_hello_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead.
func (*HelloRequest) Descriptor() ([]byte, []int) {
return file_hello_proto_rawDescGZIP(), []int{0}
}
func (x *HelloRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
type HelloReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
}
func (x *HelloReply) Reset() {
*x = HelloReply{}
if protoimpl.UnsafeEnabled {
mi := &file_hello_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HelloReply) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloReply) ProtoMessage() {}
func (x *HelloReply) ProtoReflect() protoreflect.Message {
mi := &file_hello_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelloReply.ProtoReflect.Descriptor instead.
func (*HelloReply) Descriptor() ([]byte, []int) {
return file_hello_proto_rawDescGZIP(), []int{1}
}
func (x *HelloReply) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
var File_hello_proto protoreflect.FileDescriptor
var file_hello_proto_rawDesc = []byte{
0x0a, 0x0b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0x22, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x48, 0x65, 0x6c,
0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_hello_proto_rawDescOnce sync.Once
file_hello_proto_rawDescData = file_hello_proto_rawDesc
)
func file_hello_proto_rawDescGZIP() []byte {
file_hello_proto_rawDescOnce.Do(func() {
file_hello_proto_rawDescData = protoimpl.X.CompressGZIP(file_hello_proto_rawDescData)
})
return file_hello_proto_rawDescData
}
var file_hello_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_hello_proto_goTypes = []interface{}{
(*HelloRequest)(nil), // 0: protos.HelloRequest
(*HelloReply)(nil), // 1: protos.HelloReply
}
var file_hello_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_hello_proto_init() }
func file_hello_proto_init() {
if File_hello_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_hello_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HelloRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_hello_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HelloReply); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_hello_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_hello_proto_goTypes,
DependencyIndexes: file_hello_proto_depIdxs,
MessageInfos: file_hello_proto_msgTypes,
}.Build()
File_hello_proto = out.File
file_hello_proto_rawDesc = nil
file_hello_proto_goTypes = nil
file_hello_proto_depIdxs = nil
}

View File

@ -0,0 +1,13 @@
syntax = "proto3";
package protos;
option go_package = "./protos";
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}

View File

@ -73,7 +73,9 @@ func main() {
}) })
app.Get("/xml", func(ctx iris.Context) { app.Get("/xml", func(ctx iris.Context) {
ctx.XML(ExampleXML{One: "hello", Two: "xml"}) // or iris.Map{"One":"hello"...} ctx.XML(ExampleXML{One: "hello", Two: "xml"})
// OR:
// ctx.XML(iris.XMLMap("keys", iris.Map{"key": "value"}))
}) })
app.Get("/markdown", func(ctx iris.Context) { app.Get("/markdown", func(ctx iris.Context) {
@ -82,6 +84,8 @@ func main() {
app.Get("/yaml", func(ctx iris.Context) { app.Get("/yaml", func(ctx iris.Context) {
ctx.YAML(ExampleYAML{Name: "Iris", ServerAddr: "localhost:8080"}) ctx.YAML(ExampleYAML{Name: "Iris", ServerAddr: "localhost:8080"})
// OR:
// ctx.YAML(iris.Map{"name": "Iris", "serverAddr": "localhost:8080"})
}) })
// app.Get("/protobuf", func(ctx iris.Context) { // app.Get("/protobuf", func(ctx iris.Context) {

View File

@ -66,6 +66,10 @@ type (
// //
// It is an alias of the `context#JSON` type. // It is an alias of the `context#JSON` type.
JSON = context.JSON JSON = context.JSON
// ProtoMarshalOptions is a type alias for protojson.MarshalOptions.
ProtoMarshalOptions = context.ProtoMarshalOptions
// ProtoUnmarshalOptions is a type alias for protojson.UnmarshalOptions.
ProtoUnmarshalOptions = context.ProtoUnmarshalOptions
// XML the optional settings for XML renderer. // XML the optional settings for XML renderer.
// //
// It is an alias of the `context#XML` type. // It is an alias of the `context#XML` type.

View File

@ -38,6 +38,7 @@ import (
"github.com/vmihailenco/msgpack/v5" "github.com/vmihailenco/msgpack/v5"
"golang.org/x/net/publicsuffix" "golang.org/x/net/publicsuffix"
"golang.org/x/time/rate" "golang.org/x/time/rate"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -681,7 +682,11 @@ type Context interface {
// Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-query/main.go // Example: https://github.com/kataras/iris/blob/master/_examples/request-body/read-query/main.go
ReadQuery(ptr interface{}) error ReadQuery(ptr interface{}) error
// ReadProtobuf binds the body to the "ptr" of a proto Message and returns any error. // ReadProtobuf binds the body to the "ptr" of a proto Message and returns any error.
// Look `ReadJSONProtobuf` too.
ReadProtobuf(ptr proto.Message) error ReadProtobuf(ptr proto.Message) error
// ReadJSONProtobuf reads a JSON body request into the given "ptr" proto.Message.
// Look `ReadProtobuf` too.
ReadJSONProtobuf(ptr proto.Message, opts ...protojson.UnmarshalOptions) error
// ReadMsgPack binds the request body of msgpack format to the "ptr" and returns any error. // ReadMsgPack binds the request body of msgpack format to the "ptr" and returns any error.
ReadMsgPack(ptr interface{}) error ReadMsgPack(ptr interface{}) error
// ReadBody binds the request body to the "ptr" depending on the HTTP Method and the Request's Content-Type. // ReadBody binds the request body to the "ptr" depending on the HTTP Method and the Request's Content-Type.
@ -883,6 +888,8 @@ type Context interface {
// HTML writes out a string as text/html. // HTML writes out a string as text/html.
HTML(format string, args ...interface{}) (int, error) HTML(format string, args ...interface{}) (int, error)
// JSON marshals the given interface object and writes the JSON response. // JSON marshals the given interface object and writes the JSON response.
// If the value is a compatible `proto.Message` one
// then it only uses the options.Proto settings to marshal.
JSON(v interface{}, options ...JSON) (int, error) JSON(v interface{}, options ...JSON) (int, error)
// JSONP marshals the given interface object and writes the JSON response. // JSONP marshals the given interface object and writes the JSON response.
JSONP(v interface{}, options ...JSONP) (int, error) JSONP(v interface{}, options ...JSONP) (int, error)
@ -3000,6 +3007,7 @@ func (ctx *context) ReadQuery(ptr interface{}) error {
} }
// ReadProtobuf binds the body to the "ptr" of a proto Message and returns any error. // ReadProtobuf binds the body to the "ptr" of a proto Message and returns any error.
// Look `ReadJSONProtobuf` too.
func (ctx *context) ReadProtobuf(ptr proto.Message) error { func (ctx *context) ReadProtobuf(ptr proto.Message) error {
rawData, err := ctx.GetBody() rawData, err := ctx.GetBody()
if err != nil { if err != nil {
@ -3009,6 +3017,27 @@ func (ctx *context) ReadProtobuf(ptr proto.Message) error {
return proto.Unmarshal(rawData, ptr) return proto.Unmarshal(rawData, ptr)
} }
// ProtoUnmarshalOptions is a type alias for protojson.UnmarshalOptions.
type ProtoUnmarshalOptions = protojson.UnmarshalOptions
var defaultProtobufUnmarshalOptions ProtoUnmarshalOptions
// ReadJSONProtobuf reads a JSON body request into the given "ptr" proto.Message.
// Look `ReadProtobuf` too.
func (ctx *context) ReadJSONProtobuf(ptr proto.Message, opts ...ProtoUnmarshalOptions) error {
rawData, err := ctx.GetBody()
if err != nil {
return err
}
opt := defaultProtobufUnmarshalOptions
if len(opts) > 0 {
opt = opts[1]
}
return opt.Unmarshal(rawData, ptr)
}
// ReadMsgPack binds the request body of msgpack format to the "ptr" and returns any error. // ReadMsgPack binds the request body of msgpack format to the "ptr" and returns any error.
func (ctx *context) ReadMsgPack(ptr interface{}) error { func (ctx *context) ReadMsgPack(ptr interface{}) error {
rawData, err := ctx.GetBody() rawData, err := ctx.GetBody()
@ -3615,6 +3644,9 @@ func (ctx *context) HTML(format string, args ...interface{}) (int, error) {
return ctx.Writef(format, args...) return ctx.Writef(format, args...)
} }
// ProtoMarshalOptions is a type alias for protojson.MarshalOptions.
type ProtoMarshalOptions = protojson.MarshalOptions
// JSON contains the options for the JSON (Context's) Renderer. // JSON contains the options for the JSON (Context's) Renderer.
type JSON struct { type JSON struct {
// http-specific // http-specific
@ -3625,6 +3657,8 @@ type JSON struct {
Prefix string Prefix string
ASCII bool // if true writes with unicode to ASCII content. ASCII bool // if true writes with unicode to ASCII content.
Secure bool // if true then it prepends a "while(1);" when Go slice (to JSON Array) value. Secure bool // if true then it prepends a "while(1);" when Go slice (to JSON Array) value.
// proto.Message specific marshal options.
Proto ProtoMarshalOptions
} }
// JSONP contains the options for the JSONP (Context's) Renderer. // JSONP contains the options for the JSONP (Context's) Renderer.
@ -3673,6 +3707,15 @@ func WriteJSON(writer io.Writer, v interface{}, options JSON, optimize bool) (in
err error err error
) )
if m, ok := v.(proto.Message); ok {
result, err = options.Proto.Marshal(m)
if err != nil {
return 0, err
}
return writer.Write(result)
}
if !optimize && options.Indent == "" { if !optimize && options.Indent == "" {
options.Indent = " " options.Indent = " "
} }
@ -3746,6 +3789,8 @@ func stringToBytes(s string) []byte {
var DefaultJSONOptions = JSON{} var DefaultJSONOptions = JSON{}
// JSON marshals the given interface object and writes the JSON response to the client. // JSON marshals the given interface object and writes the JSON response to the client.
// If the value is a compatible `proto.Message` one
// then it only uses the options.Proto settings to marshal.
func (ctx *context) JSON(v interface{}, opts ...JSON) (n int, err error) { func (ctx *context) JSON(v interface{}, opts ...JSON) (n int, err error) {
options := DefaultJSONOptions options := DefaultJSONOptions

10
go.mod
View File

@ -23,18 +23,18 @@ require (
github.com/kataras/neffos v0.0.16 github.com/kataras/neffos v0.0.16
github.com/kataras/pio v0.0.8 github.com/kataras/pio v0.0.8
github.com/kataras/sitemap v0.0.5 github.com/kataras/sitemap v0.0.5
github.com/klauspost/compress v1.10.9 github.com/klauspost/compress v1.10.10
github.com/mediocregopher/radix/v3 v3.5.1 github.com/mediocregopher/radix/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.3 github.com/microcosm-cc/bluemonday v1.0.3
github.com/ryanuber/columnize v2.1.0+incompatible github.com/ryanuber/columnize v2.1.0+incompatible
github.com/schollz/closestmatch v2.1.0+incompatible github.com/schollz/closestmatch v2.1.0+incompatible
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/square/go-jose/v3 v3.0.0-20200603004136-8ccb8a19e809 github.com/square/go-jose/v3 v3.0.0-20200622023058-052237293361
github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1 github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1
go.etcd.io/bbolt v1.3.5 go.etcd.io/bbolt v1.3.5
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 golang.org/x/net v0.0.0-20200625001655-4c5254603344
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae
golang.org/x/text v0.3.3 golang.org/x/text v0.3.3
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1
google.golang.org/protobuf v1.24.0 google.golang.org/protobuf v1.24.0

View File

@ -31,6 +31,7 @@ type Config struct {
Addr string Addr string
// Clusters a list of network addresses for clusters. // Clusters a list of network addresses for clusters.
// If not empty "Addr" is ignored. // If not empty "Addr" is ignored.
// Currently only Radix() Driver supports it.
Clusters []string Clusters []string
// Password string .If no password then no 'AUTH'. Defaults to "". // Password string .If no password then no 'AUTH'. Defaults to "".
Password string Password string