mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
New: gRPC MVC features, new WithLowercaseRouting option and add some new context methods
read HISTORY.md Former-commit-id: 30a16cceb11f754aa32923058abeda1e736350e7
This commit is contained in:
parent
0cf5d5a4a3
commit
5d3c96947c
16
HISTORY.md
16
HISTORY.md
|
@ -176,6 +176,14 @@ Here is a preview of what the new Hero handlers look like:
|
|||
|
||||
Other Improvements:
|
||||
|
||||
- [gRPC](https://grpc.io/) features:
|
||||
- New Router [Wrapper](middleware/grpc).
|
||||
- New MVC `.Handle(ctrl, mvc.GRPC{...})` option which allows to register gRPC services per-party (without the requirement of a full wrapper) and optionally strict access to gRPC clients only, see the [example here](_examples/mvc/grpc-compatible).
|
||||
|
||||
- Improved logging (with `app.Logger().SetLevel("debug")`) for MVC-registered routes.
|
||||
|
||||
- New `iris.WithLowercaseRouting` option which forces all routes' paths to be lowercase and converts request paths to their lowercase for matching.
|
||||
|
||||
- New `app.Validator { Struct(interface{}) error }` field and `app.Validate` method were added. The `app.Validator = ` can be used to integrate a 3rd-party package such as [go-playground/validator](https://github.com/go-playground/validator). If set-ed then Iris `Context`'s `ReadJSON`, `ReadXML`, `ReadMsgPack`, `ReadYAML`, `ReadForm`, `ReadQuery`, `ReadBody` methods will return the validation error on data validation failures. The [read-json-struct-validation](_examples/http_request/read-json-struct-validation) example was updated.
|
||||
|
||||
- A result of <T> can implement the new `hero.PreflightResult` interface which contains a single method of `Preflight(iris.Context) error`. If this method exists on a custom struct value which is returned from a handler then it will fire that `Preflight` first and if not errored then it will cotninue by sending the struct value as JSON(by-default) response body.
|
||||
|
@ -184,10 +192,16 @@ Other Improvements:
|
|||
|
||||
- Hero Handlers (and `app.ConfigureContainer().Handle`) do not have to require `iris.Context` just to call `ctx.Next()` anymore, this is done automatically now.
|
||||
|
||||
- Improve Remote Address parsing as requested at: https://github.com/kataras/iris/issues/1453. Add `Configuration.RemoteAddrPrivateSubnets` to exclude those addresses when fetched by `Configuration.RemoteAddrHeaders` through `context.RemoteAddr() string`.
|
||||
- Improve Remote Address parsing as requested at: [#1453](https://github.com/kataras/iris/issues/1453). Add `Configuration.RemoteAddrPrivateSubnets` to exclude those addresses when fetched by `Configuration.RemoteAddrHeaders` through `context.RemoteAddr() string`.
|
||||
|
||||
- Fix [#1487](https://github.com/kataras/iris/issues/1487).
|
||||
|
||||
- Fix [#1473](https://github.com/kataras/iris/issues/1473).
|
||||
|
||||
New Context Methods:
|
||||
|
||||
- `context.IsHTTP2() bool` reports whether the protocol version for incoming request was HTTP/2
|
||||
- `context.IsGRPC() bool` reports whether the request came from a gRPC client
|
||||
- `context.UpsertCookie(*http.Cookie, cookieOptions ...context.CookieOption)` upserts a cookie, fixes [#1485](https://github.com/kataras/iris/issues/1485) too
|
||||
- `context.StopWithStatus(int)` stops the handlers chain and writes the status code
|
||||
- `context.StopWithJSON(int, interface{})` stops the handlers chain, writes the status code and sends a JSON response
|
||||
|
|
20
_examples/mvc/grpc-compatible/README.md
Normal file
20
_examples/mvc/grpc-compatible/README.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
# gRPC Iris Example
|
||||
|
||||
## Generate TLS Keys
|
||||
|
||||
```sh
|
||||
$ openssl genrsa -out server.key 2048
|
||||
$ openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
|
||||
```
|
||||
|
||||
## Install the protoc Go plugin
|
||||
|
||||
```sh
|
||||
$ go get -u github.com/golang/protobuf/protoc-gen-go
|
||||
```
|
||||
|
||||
## Generate proto
|
||||
|
||||
```sh
|
||||
$ protoc -I helloworld/ helloworld/helloworld.proto --go_out=plugins=grpc:helloworld
|
||||
```
|
|
@ -39,7 +39,7 @@ func main() {
|
|||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
r, err := c.PostHello(ctx, &pb.HelloRequest{Name: name})
|
||||
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
|
||||
if err != nil {
|
||||
log.Fatalf("could not greet: %v", err)
|
||||
}
|
||||
|
|
|
@ -1,130 +1,240 @@
|
|||
// Copyright 2015 gRPC authors.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.21.0
|
||||
// protoc v3.11.1
|
||||
// source: helloworld.proto
|
||||
|
||||
package helloworld
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
math "math"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
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 to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||
// of the legacy proto package is being used.
|
||||
const _ = proto.ProtoPackageIsVersion4
|
||||
|
||||
// The request message containing the user's name.
|
||||
type HelloRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *HelloRequest) Reset() { *m = HelloRequest{} }
|
||||
func (m *HelloRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (x *HelloRequest) Reset() {
|
||||
*x = HelloRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_helloworld_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_helloworld_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 fileDescriptor_17b8c58d586b62f2, []int{0}
|
||||
return file_helloworld_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (m *HelloRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_HelloRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_HelloRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *HelloRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_HelloRequest.Merge(m, src)
|
||||
}
|
||||
func (m *HelloRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_HelloRequest.Size(m)
|
||||
}
|
||||
func (m *HelloRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_HelloRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_HelloRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *HelloRequest) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
func (x *HelloRequest) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// The response message containing the greetings
|
||||
type HelloReply struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *HelloReply) Reset() { *m = HelloReply{} }
|
||||
func (m *HelloReply) String() string { return proto.CompactTextString(m) }
|
||||
func (x *HelloReply) Reset() {
|
||||
*x = HelloReply{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_helloworld_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_helloworld_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 fileDescriptor_17b8c58d586b62f2, []int{1}
|
||||
return file_helloworld_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (m *HelloReply) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_HelloReply.Unmarshal(m, b)
|
||||
}
|
||||
func (m *HelloReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_HelloReply.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *HelloReply) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_HelloReply.Merge(m, src)
|
||||
}
|
||||
func (m *HelloReply) XXX_Size() int {
|
||||
return xxx_messageInfo_HelloReply.Size(m)
|
||||
}
|
||||
func (m *HelloReply) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_HelloReply.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_HelloReply proto.InternalMessageInfo
|
||||
|
||||
func (m *HelloReply) GetMessage() string {
|
||||
if m != nil {
|
||||
return m.Message
|
||||
func (x *HelloReply) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*HelloRequest)(nil), "helloworld.HelloRequest")
|
||||
proto.RegisterType((*HelloReply)(nil), "helloworld.HelloReply")
|
||||
var File_helloworld_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_helloworld_proto_rawDesc = []byte{
|
||||
0x0a, 0x10, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x12, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 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, 0x32, 0x49, 0x0a, 0x07, 0x47, 0x72,
|
||||
0x65, 0x65, 0x74, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c,
|
||||
0x6f, 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48,
|
||||
0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65,
|
||||
0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65,
|
||||
0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x30, 0x0a, 0x1b, 0x69, 0x6f, 0x2e, 0x67, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77,
|
||||
0x6f, 0x72, 0x6c, 0x64, 0x42, 0x0f, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x64,
|
||||
0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_17b8c58d586b62f2) }
|
||||
var (
|
||||
file_helloworld_proto_rawDescOnce sync.Once
|
||||
file_helloworld_proto_rawDescData = file_helloworld_proto_rawDesc
|
||||
)
|
||||
|
||||
var fileDescriptor_17b8c58d586b62f2 = []byte{
|
||||
// 175 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9,
|
||||
0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x42, 0x88,
|
||||
0x28, 0x29, 0x71, 0xf1, 0x78, 0x80, 0x78, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x42,
|
||||
0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x92,
|
||||
0x1a, 0x17, 0x17, 0x54, 0x4d, 0x41, 0x4e, 0xa5, 0x90, 0x04, 0x17, 0x7b, 0x6e, 0x6a, 0x71, 0x71,
|
||||
0x62, 0x3a, 0x4c, 0x11, 0x8c, 0x6b, 0xe4, 0xc9, 0xc5, 0xee, 0x5e, 0x94, 0x9a, 0x5a, 0x92, 0x5a,
|
||||
0x24, 0x64, 0xc7, 0xc5, 0x11, 0x9c, 0x58, 0x09, 0xd6, 0x25, 0x24, 0xa1, 0x87, 0xe4, 0x02, 0x64,
|
||||
0xcb, 0xa4, 0xc4, 0xb0, 0xc8, 0x14, 0xe4, 0x54, 0x2a, 0x31, 0x38, 0x19, 0x70, 0x49, 0x67, 0xe6,
|
||||
0xeb, 0xa5, 0x17, 0x15, 0x24, 0xeb, 0xa5, 0x56, 0x24, 0xe6, 0x16, 0xe4, 0xa4, 0x16, 0x23, 0xa9,
|
||||
0x75, 0xe2, 0x07, 0x2b, 0x0e, 0x07, 0xb1, 0x03, 0x40, 0x5e, 0x0a, 0x60, 0x4c, 0x62, 0x03, 0xfb,
|
||||
0xcd, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xb7, 0xcd, 0xf2, 0xef, 0x00, 0x00, 0x00,
|
||||
func file_helloworld_proto_rawDescGZIP() []byte {
|
||||
file_helloworld_proto_rawDescOnce.Do(func() {
|
||||
file_helloworld_proto_rawDescData = protoimpl.X.CompressGZIP(file_helloworld_proto_rawDescData)
|
||||
})
|
||||
return file_helloworld_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_helloworld_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_helloworld_proto_goTypes = []interface{}{
|
||||
(*HelloRequest)(nil), // 0: helloworld.HelloRequest
|
||||
(*HelloReply)(nil), // 1: helloworld.HelloReply
|
||||
}
|
||||
var file_helloworld_proto_depIdxs = []int32{
|
||||
0, // 0: helloworld.Greeter.SayHello:input_type -> helloworld.HelloRequest
|
||||
1, // 1: helloworld.Greeter.SayHello:output_type -> helloworld.HelloReply
|
||||
1, // [1:2] is the sub-list for method output_type
|
||||
0, // [0:1] 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_helloworld_proto_init() }
|
||||
func file_helloworld_proto_init() {
|
||||
if File_helloworld_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_helloworld_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_helloworld_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_helloworld_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_helloworld_proto_goTypes,
|
||||
DependencyIndexes: file_helloworld_proto_depIdxs,
|
||||
MessageInfos: file_helloworld_proto_msgTypes,
|
||||
}.Build()
|
||||
File_helloworld_proto = out.File
|
||||
file_helloworld_proto_rawDesc = nil
|
||||
file_helloworld_proto_goTypes = nil
|
||||
file_helloworld_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
|
@ -140,7 +250,7 @@ const _ = grpc.SupportPackageIsVersion6
|
|||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||
type GreeterClient interface {
|
||||
// Sends a greeting
|
||||
PostHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
|
||||
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
|
||||
}
|
||||
|
||||
type greeterClient struct {
|
||||
|
@ -151,9 +261,9 @@ func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {
|
|||
return &greeterClient{cc}
|
||||
}
|
||||
|
||||
func (c *greeterClient) PostHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
|
||||
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
|
||||
out := new(HelloReply)
|
||||
err := c.cc.Invoke(ctx, "/helloworld.Greeter/PostHello", in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -163,15 +273,15 @@ func (c *greeterClient) PostHello(ctx context.Context, in *HelloRequest, opts ..
|
|||
// GreeterServer is the server API for Greeter service.
|
||||
type GreeterServer interface {
|
||||
// Sends a greeting
|
||||
PostHello(context.Context, *HelloRequest) (*HelloReply, error)
|
||||
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
|
||||
}
|
||||
|
||||
// UnimplementedGreeterServer can be embedded to have forward compatible implementations.
|
||||
type UnimplementedGreeterServer struct {
|
||||
}
|
||||
|
||||
func (*UnimplementedGreeterServer) PostHello(ctx context.Context, req *HelloRequest) (*HelloReply, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method PostHello not implemented")
|
||||
func (*UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
|
||||
}
|
||||
|
||||
func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
|
||||
|
@ -184,14 +294,14 @@ func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(in
|
|||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(GreeterServer).PostHello(ctx, in)
|
||||
return srv.(GreeterServer).SayHello(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/helloworld.Greeter/PostHello",
|
||||
FullMethod: "/helloworld.Greeter/SayHello",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(GreeterServer).PostHello(ctx, req.(*HelloRequest))
|
||||
return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
@ -201,7 +311,7 @@ var _Greeter_serviceDesc = grpc.ServiceDesc{
|
|||
HandlerType: (*GreeterServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "PostHello",
|
||||
MethodName: "SayHello",
|
||||
Handler: _Greeter_SayHello_Handler,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,3 +1,17 @@
|
|||
// Copyright 2015 gRPC authors.
|
||||
//
|
||||
// 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.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
option java_multiple_files = true;
|
||||
|
@ -9,7 +23,7 @@ package helloworld;
|
|||
// The greeting service definition.
|
||||
service Greeter {
|
||||
// Sends a greeting
|
||||
rpc PostHello (HelloRequest) returns (HelloReply) {}
|
||||
rpc SayHello (HelloRequest) returns (HelloReply) {}
|
||||
}
|
||||
|
||||
// The request message containing the user's name.
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
pb "github.com/kataras/iris/v12/_examples/mvc/grpc-compatible/helloworld"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
grpcWrapper "github.com/kataras/iris/v12/middleware/grpc"
|
||||
"github.com/kataras/iris/v12/mvc"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
@ -23,7 +22,8 @@ func main() {
|
|||
app := newApp()
|
||||
app.Logger().SetLevel("debug")
|
||||
|
||||
// POST: https://localhost/hello
|
||||
// The Iris server should ran under TLS (it's a gRPC requirement).
|
||||
// POST: https://localhost:443/helloworld.greeter/sayhello
|
||||
// with request data: {"name": "John"}
|
||||
// and expected output: {"message": "Hello John"}
|
||||
app.Run(iris.TLS(":443", "server.crt", "server.key"))
|
||||
|
@ -31,24 +31,28 @@ func main() {
|
|||
|
||||
func newApp() *iris.Application {
|
||||
app := iris.New()
|
||||
app.Logger().SetLevel("debug")
|
||||
|
||||
ctrl := &myController{}
|
||||
// Register gRPC server.
|
||||
grpcServer := grpc.NewServer()
|
||||
pb.RegisterGreeterServer(grpcServer, ctrl)
|
||||
|
||||
// Register MVC application controller.
|
||||
mvc.New(app).Handle(ctrl)
|
||||
// serviceName := pb.File_helloworld_proto.Services().Get(0).FullName()
|
||||
|
||||
// Register MVC application controller.
|
||||
mvc.New(app).Handle(ctrl, mvc.GRPC{
|
||||
Server: grpcServer, // Required.
|
||||
ServiceName: "helloworld.Greeter", // Required.
|
||||
Strict: false,
|
||||
})
|
||||
|
||||
// Serve the gRPC server under the Iris HTTP webserver one,
|
||||
// the Iris server should ran under TLS (it's a gRPC requirement).
|
||||
app.WrapRouter(grpcWrapper.New(grpcServer))
|
||||
return app
|
||||
}
|
||||
|
||||
type myController struct{}
|
||||
|
||||
// PostHello implements helloworld.GreeterServer
|
||||
func (c *myController) PostHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
|
||||
// SayHello implements helloworld.GreeterServer.
|
||||
func (c *myController) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
|
||||
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ func TestGRPCCompatible(t *testing.T) {
|
|||
app := newApp()
|
||||
|
||||
e := httptest.New(t, app)
|
||||
e.POST("/hello").WithJSON(map[string]string{"name": "makis"}).Expect().
|
||||
e.POST("/helloworld.Greeter/SayHello").WithJSON(map[string]string{"name": "makis"}).Expect().
|
||||
Status(httptest.StatusOK).
|
||||
JSON().Equal(map[string]string{"message": "Hello makis"})
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
```sh
|
||||
$ openssl genrsa -out server.key 2048
|
||||
$ openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
|
||||
```
|
|
@ -258,13 +258,21 @@ var WithoutAutoFireStatusCode = func(app *Application) {
|
|||
app.config.DisableAutoFireStatusCode = true
|
||||
}
|
||||
|
||||
// WithPathEscape enables the PathEscape setting.
|
||||
// WithPathEscape sets the EnablePathEscape setting to true.
|
||||
//
|
||||
// See `Configuration`.
|
||||
var WithPathEscape = func(app *Application) {
|
||||
app.config.EnablePathEscape = true
|
||||
}
|
||||
|
||||
// WithLowercaseRouting enables for lowercase routing by
|
||||
// setting the `ForceLowercaseRoutes` to true.
|
||||
//
|
||||
// See `Configuration`.
|
||||
var WithLowercaseRouting = func(app *Application) {
|
||||
app.config.ForceLowercaseRouting = true
|
||||
}
|
||||
|
||||
// WithOptimizations can force the application to optimize for the best performance where is possible.
|
||||
//
|
||||
// See `Configuration`.
|
||||
|
@ -744,7 +752,8 @@ type Configuration struct {
|
|||
// Defaults to false.
|
||||
DisableInterruptHandler bool `json:"disableInterruptHandler,omitempty" yaml:"DisableInterruptHandler" toml:"DisableInterruptHandler"`
|
||||
|
||||
// DisablePathCorrection corrects and redirects or executes directly the handler of
|
||||
// DisablePathCorrection disables the correcting
|
||||
// and redirecting or executing directly the handler of
|
||||
// the requested path to the registered path
|
||||
// for example, if /home/ path is requested but no handler for this Route found,
|
||||
// then the Router checks if /home handler exists, if yes,
|
||||
|
@ -762,9 +771,7 @@ type Configuration struct {
|
|||
// Defaults to false.
|
||||
DisablePathCorrectionRedirection bool `json:"disablePathCorrectionRedirection,omitempty" yaml:"DisablePathCorrectionRedirection" toml:"DisablePathCorrectionRedirection"`
|
||||
|
||||
// EnablePathEscape when is true then its escapes the path, the named parameters (if any).
|
||||
// Change to false it if you want something like this https://github.com/kataras/iris/issues/135 to work
|
||||
//
|
||||
// EnablePathEscape when is true then its escapes the path and the named parameters (if any).
|
||||
// When do you need to Disable(false) it:
|
||||
// accepts parameters with slash '/'
|
||||
// Request: http://localhost:8080/details/Project%2FDelta
|
||||
|
@ -775,6 +782,12 @@ type Configuration struct {
|
|||
// Defaults to false.
|
||||
EnablePathEscape bool `json:"enablePathEscape,omitempty" yaml:"EnablePathEscape" toml:"EnablePathEscape"`
|
||||
|
||||
// ForceLowercaseRouting if enabled, converts all registered routes paths to lowercase
|
||||
// and it does lowercase the request path too for matching.
|
||||
//
|
||||
// Defaults to false.
|
||||
ForceLowercaseRouting bool `json:"forceLowercaseRouting,omitempty" yaml:"ForceLowercaseRouting" toml:"ForceLowercaseRouting"`
|
||||
|
||||
// EnableOptimization when this field is true
|
||||
// then the application tries to optimize for the best performance where is possible.
|
||||
//
|
||||
|
@ -900,8 +913,10 @@ func (c Configuration) GetVHost() string {
|
|||
return c.vhost
|
||||
}
|
||||
|
||||
// GetDisablePathCorrection returns the Configuration#DisablePathCorrection,
|
||||
// DisablePathCorrection corrects and redirects the requested path to the registered path
|
||||
// GetDisablePathCorrection returns the Configuration#DisablePathCorrection.
|
||||
// DisablePathCorrection disables the correcting
|
||||
// and redirecting or executing directly the handler of
|
||||
// the requested path to the registered path
|
||||
// for example, if /home/ path is requested but no handler for this Route found,
|
||||
// then the Router checks if /home handler exists, if yes,
|
||||
// (permanent)redirects the client to the correct path /home.
|
||||
|
@ -922,6 +937,11 @@ func (c Configuration) GetEnablePathEscape() bool {
|
|||
return c.EnablePathEscape
|
||||
}
|
||||
|
||||
// GetForceLowercaseRouting returns the value of the `ForceLowercaseRouting` setting.
|
||||
func (c Configuration) GetForceLowercaseRouting() bool {
|
||||
return c.ForceLowercaseRouting
|
||||
}
|
||||
|
||||
// GetEnableOptimizations returns whether
|
||||
// the application has performance optimizations enabled.
|
||||
func (c Configuration) GetEnableOptimizations() bool {
|
||||
|
@ -1079,6 +1099,10 @@ func WithConfiguration(c Configuration) Configurator {
|
|||
main.EnablePathEscape = v
|
||||
}
|
||||
|
||||
if v := c.ForceLowercaseRouting; v {
|
||||
main.ForceLowercaseRouting = v
|
||||
}
|
||||
|
||||
if v := c.EnableOptimizations; v {
|
||||
main.EnableOptimizations = v
|
||||
}
|
||||
|
@ -1150,6 +1174,7 @@ func DefaultConfiguration() Configuration {
|
|||
DisableInterruptHandler: false,
|
||||
DisablePathCorrection: false,
|
||||
EnablePathEscape: false,
|
||||
ForceLowercaseRouting: false,
|
||||
FireMethodNotAllowed: false,
|
||||
DisableBodyConsumptionOnUnmarshal: false,
|
||||
DisableAutoFireStatusCode: false,
|
||||
|
|
|
@ -25,7 +25,8 @@ type ConfigurationReadOnly interface {
|
|||
GetDisablePathCorrection() bool
|
||||
|
||||
// GetDisablePathCorrectionRedirection returns the Configuration#DisablePathCorrectionRedirection field.
|
||||
// If DisablePathCorrectionRedirection set to true then it will fire the handler of the matching route without
|
||||
// If DisablePathCorrectionRedirection set to true then it will handle paths as they are.
|
||||
// it will fire the handler of the matching route without
|
||||
// the last slash ("/") instead of send a redirection status.
|
||||
GetDisablePathCorrectionRedirection() bool
|
||||
|
||||
|
@ -33,6 +34,9 @@ type ConfigurationReadOnly interface {
|
|||
// returns true when its escapes the path, the named parameters (if any).
|
||||
GetEnablePathEscape() bool
|
||||
|
||||
// GetForceLowercaseRouting returns the value of the `ForceLowercaseRouting` setting.
|
||||
GetForceLowercaseRouting() bool
|
||||
|
||||
// GetEnableOptimizations returns whether
|
||||
// the application has performance optimizations enabled.
|
||||
GetEnableOptimizations() bool
|
||||
|
|
|
@ -165,7 +165,6 @@ type Context interface {
|
|||
// Router is calling this function to add the route's handler.
|
||||
// If AddHandler called then the handlers will be inserted
|
||||
// to the end of the already-defined route's handler.
|
||||
//
|
||||
AddHandler(...Handler)
|
||||
// SetHandlers replaces all handlers with the new.
|
||||
SetHandlers(Handlers)
|
||||
|
@ -387,6 +386,11 @@ type Context interface {
|
|||
IsMobile() bool
|
||||
// IsScript reports whether a client is a script.
|
||||
IsScript() bool
|
||||
// IsHTTP2 reports whether the protocol version for incoming request was HTTP/2.
|
||||
// The client code always uses either HTTP/1.1 or HTTP/2.
|
||||
IsHTTP2() bool
|
||||
// IsGRPC reports whether the request came from a gRPC client.
|
||||
IsGRPC() bool
|
||||
// GetReferrer extracts and returns the information from the "Referer" header as specified
|
||||
// in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
|
||||
// or by the URL query parameter "referer".
|
||||
|
@ -1668,6 +1672,7 @@ func (ctx *context) RequestPath(escape bool) string {
|
|||
if escape {
|
||||
return ctx.request.URL.EscapedPath() // DecodeQuery(ctx.request.URL.EscapedPath())
|
||||
}
|
||||
|
||||
return ctx.request.URL.Path // RawPath returns empty, requesturi can be used instead also.
|
||||
}
|
||||
|
||||
|
@ -1826,6 +1831,17 @@ func (ctx *context) IsScript() bool {
|
|||
return isScriptRegex.MatchString(s)
|
||||
}
|
||||
|
||||
// IsHTTP2 reports whether the protocol version for incoming request was HTTP/2.
|
||||
// The client code always uses either HTTP/1.1 or HTTP/2.
|
||||
func (ctx *context) IsHTTP2() bool {
|
||||
return ctx.Request().ProtoMajor == 2
|
||||
}
|
||||
|
||||
// IsGRPC reports whether the request came from a gRPC client.
|
||||
func (ctx *context) IsGRPC() bool {
|
||||
return ctx.IsHTTP2() && ctx.GetContentTypeRequested() == ContentGRPCHeaderValue
|
||||
}
|
||||
|
||||
type (
|
||||
// Referrer contains the extracted information from the `GetReferrer`
|
||||
//
|
||||
|
@ -3316,6 +3332,8 @@ const (
|
|||
ContentFormHeaderValue = "application/x-www-form-urlencoded"
|
||||
// ContentFormMultipartHeaderValue header value for post multipart form data.
|
||||
ContentFormMultipartHeaderValue = "multipart/form-data"
|
||||
// ContentGRPCHeaderValue Content-Type header value for gRPC.
|
||||
ContentGRPCHeaderValue = "application/grpc"
|
||||
)
|
||||
|
||||
// Binary writes out the raw bytes as binary data.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
@ -27,20 +28,38 @@ type Handler func(Context)
|
|||
// See `Handler` for more.
|
||||
type Handlers []Handler
|
||||
|
||||
func valueOf(v interface{}) reflect.Value {
|
||||
if val, ok := v.(reflect.Value); ok {
|
||||
return val
|
||||
}
|
||||
|
||||
return reflect.ValueOf(v)
|
||||
}
|
||||
|
||||
// HandlerName returns the handler's function name.
|
||||
// See `context.HandlerName` to get function name of the current running handler in the chain.
|
||||
func HandlerName(h Handler) string {
|
||||
pc := reflect.ValueOf(h).Pointer()
|
||||
func HandlerName(h interface{}) string {
|
||||
pc := valueOf(h).Pointer()
|
||||
return runtime.FuncForPC(pc).Name()
|
||||
}
|
||||
|
||||
// HandlerFileLine returns the handler's file and line information.
|
||||
// See `context.HandlerFileLine` to get the file, line of the current running handler in the chain.
|
||||
func HandlerFileLine(h Handler) (file string, line int) {
|
||||
pc := reflect.ValueOf(h).Pointer()
|
||||
func HandlerFileLine(h interface{}) (file string, line int) {
|
||||
pc := valueOf(h).Pointer()
|
||||
return runtime.FuncForPC(pc).FileLine(pc)
|
||||
}
|
||||
|
||||
// HandlerFileLineRel same as `HandlerFileLine` but it returns the path as relative to the "workingDir".
|
||||
func HandlerFileLineRel(h interface{}, workingDir string) (string, int) {
|
||||
file, line := HandlerFileLine(h)
|
||||
if relFile, err := filepath.Rel(workingDir, file); err == nil {
|
||||
file = "./" + relFile
|
||||
}
|
||||
|
||||
return file, line
|
||||
}
|
||||
|
||||
// MainHandlerName tries to find the main handler than end-developer
|
||||
// registered on the provided chain of handlers and returns its function name.
|
||||
func MainHandlerName(handlers Handlers) (name string) {
|
||||
|
|
|
@ -163,20 +163,14 @@ var _ RoutesProvider = (*APIBuilder)(nil) // passed to the default request handl
|
|||
// NewAPIBuilder creates & returns a new builder
|
||||
// which is responsible to build the API and the router handler.
|
||||
func NewAPIBuilder() *APIBuilder {
|
||||
api := &APIBuilder{
|
||||
return &APIBuilder{
|
||||
macros: macro.Defaults,
|
||||
errorCodeHandlers: defaultErrorCodeHandlers(),
|
||||
errors: errgroup.New("API Builder"),
|
||||
relativePath: "/",
|
||||
routes: new(repository),
|
||||
apiBuilderDI: &APIContainer{Container: hero.New()},
|
||||
}
|
||||
|
||||
api.apiBuilderDI = &APIContainer{
|
||||
Self: api,
|
||||
Container: hero.New(),
|
||||
}
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
// ConfigureContainer accepts one or more functions that can be used
|
||||
|
@ -187,13 +181,15 @@ func NewAPIBuilder() *APIBuilder {
|
|||
//
|
||||
// It returns the same `APIBuilder` featured with Dependency Injection.
|
||||
func (api *APIBuilder) ConfigureContainer(builder ...func(*APIContainer)) *APIContainer {
|
||||
for _, b := range builder {
|
||||
if b == nil {
|
||||
continue
|
||||
if api.apiBuilderDI.Self == nil {
|
||||
api.apiBuilderDI.Self = api
|
||||
}
|
||||
|
||||
for _, b := range builder {
|
||||
if b != nil {
|
||||
b(api.apiBuilderDI)
|
||||
}
|
||||
}
|
||||
|
||||
return api.apiBuilderDI
|
||||
}
|
||||
|
@ -463,7 +459,7 @@ func (api *APIBuilder) CreateRoutes(methods []string, relativePath string, handl
|
|||
subdomain, path := splitSubdomainAndPath(fullpath)
|
||||
|
||||
// if allowMethods are empty, then simply register with the passed, main, method.
|
||||
methods = removeDuplString(append(api.allowMethods, methods...))
|
||||
methods = removeDuplicates(append(api.allowMethods, methods...))
|
||||
|
||||
routes := make([]*Route, len(methods))
|
||||
|
||||
|
@ -487,7 +483,7 @@ func (api *APIBuilder) CreateRoutes(methods []string, relativePath string, handl
|
|||
return routes
|
||||
}
|
||||
|
||||
func removeDuplString(elements []string) (result []string) {
|
||||
func removeDuplicates(elements []string) (result []string) {
|
||||
seen := make(map[string]struct{})
|
||||
|
||||
for v := range elements {
|
||||
|
@ -551,15 +547,11 @@ func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) P
|
|||
allowMethods: allowMethods,
|
||||
handlerExecutionRules: api.handlerExecutionRules,
|
||||
routeRegisterRule: api.routeRegisterRule,
|
||||
}
|
||||
|
||||
apiBuilderDI: &APIContainer{
|
||||
// attach a new Container with correct dynamic path parameter start index for input arguments
|
||||
// based on the fullpath.
|
||||
childContainer := api.apiBuilderDI.Container.Clone()
|
||||
|
||||
childAPI.apiBuilderDI = &APIContainer{
|
||||
Self: childAPI,
|
||||
Container: childContainer,
|
||||
Container: api.apiBuilderDI.Container.Clone(),
|
||||
},
|
||||
}
|
||||
|
||||
return childAPI
|
||||
|
|
|
@ -84,7 +84,7 @@ func BenchmarkAPIBuilder(b *testing.B) {
|
|||
paths := genPaths(routesLength, 15, 42)
|
||||
|
||||
api := NewAPIBuilder()
|
||||
requestHandler := NewDefaultHandler()
|
||||
requestHandler := NewDefaultHandler(nil)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
// By-default is the router algorithm.
|
||||
type RequestHandler interface {
|
||||
// HandleRequest should handle the request based on the Context.
|
||||
HandleRequest(context.Context)
|
||||
HandleRequest(ctx context.Context)
|
||||
// Build should builds the handler, it's being called on router's BuildRouter.
|
||||
Build(provider RoutesProvider) error
|
||||
// RouteExists reports whether a particular route exists.
|
||||
|
@ -27,6 +27,7 @@ type RequestHandler interface {
|
|||
type routerHandler struct {
|
||||
trees []*trie
|
||||
hosts bool // true if at least one route contains a Subdomain.
|
||||
config context.ConfigurationReadOnly
|
||||
}
|
||||
|
||||
var _ RequestHandler = &routerHandler{}
|
||||
|
@ -67,9 +68,10 @@ func (h *routerHandler) AddRoute(r *Route) error {
|
|||
|
||||
// NewDefaultHandler returns the handler which is responsible
|
||||
// to map the request with a route (aka mux implementation).
|
||||
func NewDefaultHandler() RequestHandler {
|
||||
h := &routerHandler{}
|
||||
return h
|
||||
func NewDefaultHandler(config context.ConfigurationReadOnly) RequestHandler {
|
||||
return &routerHandler{
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// RoutesProvider should be implemented by
|
||||
|
@ -128,6 +130,11 @@ func (h *routerHandler) Build(provider RoutesProvider) error {
|
|||
})
|
||||
|
||||
for _, r := range registeredRoutes {
|
||||
if h.config != nil && h.config.GetForceLowercaseRouting() {
|
||||
// only in that state, keep everyting else as end-developer registered.
|
||||
r.Path = strings.ToLower(r.Path)
|
||||
}
|
||||
|
||||
if r.Subdomain != "" {
|
||||
h.hosts = true
|
||||
}
|
||||
|
@ -188,20 +195,21 @@ func bindMultiParamTypesHandler(top *Route, r *Route) {
|
|||
func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||
method := ctx.Method()
|
||||
path := ctx.Path()
|
||||
if !ctx.Application().ConfigurationReadOnly().GetDisablePathCorrection() {
|
||||
config := h.config // ctx.Application().GetConfigurationReadOnly()
|
||||
|
||||
if !config.GetDisablePathCorrection() {
|
||||
if len(path) > 1 && strings.HasSuffix(path, "/") {
|
||||
// Remove trailing slash and client-permanent rule for redirection,
|
||||
// if confgiuration allows that and path has an extra slash.
|
||||
|
||||
// update the new path and redirect.
|
||||
r := ctx.Request()
|
||||
u := ctx.Request().URL
|
||||
// use Trim to ensure there is no open redirect due to two leading slashes
|
||||
path = "/" + strings.Trim(path, "/")
|
||||
|
||||
r.URL.Path = path
|
||||
if !ctx.Application().ConfigurationReadOnly().GetDisablePathCorrectionRedirection() {
|
||||
u.Path = path
|
||||
if !config.GetDisablePathCorrectionRedirection() {
|
||||
// do redirect, else continue with the modified path without the last "/".
|
||||
url := r.URL.String()
|
||||
url := u.String()
|
||||
|
||||
// Fixes https://github.com/kataras/iris/issues/921
|
||||
// This is caused for security reasons, imagine a payment shop,
|
||||
|
@ -238,7 +246,7 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
|||
// localhost -> invalid
|
||||
// sub.mydomain.com -> valid
|
||||
// sub.localhost -> valid
|
||||
serverHost := ctx.Application().ConfigurationReadOnly().GetVHost()
|
||||
serverHost := config.GetVHost()
|
||||
if serverHost == requestHost {
|
||||
continue // it's not a subdomain, it's a full domain (with .com...)
|
||||
}
|
||||
|
@ -266,7 +274,7 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
|||
break
|
||||
}
|
||||
|
||||
if ctx.Application().ConfigurationReadOnly().GetFireMethodNotAllowed() {
|
||||
if config.GetFireMethodNotAllowed() {
|
||||
for i := range h.trees {
|
||||
t := h.trees[i]
|
||||
// if `Configuration#FireMethodNotAllowed` is kept as defaulted(false) then this function will not
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
// caller should Refresh the router.
|
||||
type Route struct {
|
||||
Name string `json:"name"` // "userRoute"
|
||||
Description string `json:"description"` // "lists a user"
|
||||
Method string `json:"method"` // "GET"
|
||||
methodBckp string // if Method changed to something else (which is possible at runtime as well, via RefreshRouter) then this field will be filled with the old one.
|
||||
Subdomain string `json:"subdomain"` // "admin."
|
||||
|
@ -324,6 +325,10 @@ func (r *Route) Trace() string {
|
|||
}
|
||||
printfmt += fmt.Sprintf(" %s", r.Tmpl().Src)
|
||||
|
||||
if r.Description != "" {
|
||||
printfmt += fmt.Sprintf(" (%s)", r.Description)
|
||||
}
|
||||
|
||||
mainHandlerName := r.MainHandlerName
|
||||
if !strings.HasSuffix(mainHandlerName, ")") {
|
||||
mainHandlerName += "()"
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package router_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
|
@ -39,3 +41,34 @@ func TestRouteExists(t *testing.T) {
|
|||
// run the tests
|
||||
httptest.New(t, app, httptest.Debug(false)).Request("GET", "/route-test").Expect().Status(iris.StatusOK)
|
||||
}
|
||||
|
||||
func TestLowercaseRouting(t *testing.T) {
|
||||
app := iris.New()
|
||||
app.WrapRouter(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
// test bottom to begin wrapper, the last ones should execute first.
|
||||
// The ones that are registered at `Build` state, after this `WrapRouter` call.
|
||||
// So path should be already lowecased.
|
||||
if expected, got := strings.ToLower(r.URL.Path), r.URL.Path; expected != got {
|
||||
t.Fatalf("expected path: %s but got: %s", expected, got)
|
||||
}
|
||||
next(w, r)
|
||||
})
|
||||
|
||||
h := func(ctx iris.Context) { ctx.WriteString(ctx.Path()) }
|
||||
|
||||
// Register routes.
|
||||
tests := []string{"/", "/lowercase", "/UPPERCASE", "/Title", "/m1xEd2"}
|
||||
for _, tt := range tests {
|
||||
app.Get(tt, h)
|
||||
}
|
||||
|
||||
app.Configure(iris.WithLowercaseRouting)
|
||||
// Test routes.
|
||||
e := httptest.New(t, app)
|
||||
for _, tt := range tests {
|
||||
s := strings.ToLower(tt)
|
||||
e.GET(tt).Expect().Status(httptest.StatusOK).Body().Equal(s)
|
||||
e.GET(s).Expect().Status(httptest.StatusOK).Body().Equal(s)
|
||||
e.GET(strings.ToUpper(tt)).Expect().Status(httptest.StatusOK).Body().Equal(s)
|
||||
}
|
||||
}
|
||||
|
|
12
iris.go
12
iris.go
|
@ -765,18 +765,24 @@ func (app *Application) Build() error {
|
|||
if app.I18n.Loaded() {
|
||||
// {{ tr "lang" "key" arg1 arg2 }}
|
||||
app.view.AddFunc("tr", app.I18n.Tr)
|
||||
app.WrapRouter(app.I18n.Wrapper())
|
||||
app.Router.WrapRouter(app.I18n.Wrapper())
|
||||
}
|
||||
|
||||
if !app.Router.Downgraded() {
|
||||
// router
|
||||
|
||||
if err := app.tryInjectLiveReload(); err != nil {
|
||||
rp.Errf("LiveReload: init: failed: %v", err)
|
||||
}
|
||||
|
||||
if app.config.ForceLowercaseRouting {
|
||||
app.Router.WrapRouter(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
r.URL.Path = strings.ToLower(r.URL.Path)
|
||||
next(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// create the request handler, the default routing handler
|
||||
routerHandler := router.NewDefaultHandler()
|
||||
routerHandler := router.NewDefaultHandler(app.config)
|
||||
err := app.Router.BuildRouter(app.ContextPool, routerHandler, app.APIBuilder, false)
|
||||
if err != nil {
|
||||
rp.Err(err)
|
||||
|
|
|
@ -2,6 +2,7 @@ package mvc
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
|
@ -81,6 +82,9 @@ type ControllerActivator struct {
|
|||
|
||||
// true if this controller listens and serves to websocket events.
|
||||
servesWebsocket bool
|
||||
|
||||
// true to skip the internal "activate".
|
||||
activated bool
|
||||
}
|
||||
|
||||
// NameOf returns the package name + the struct type's name,
|
||||
|
@ -96,6 +100,14 @@ func NameOf(v interface{}) string {
|
|||
}
|
||||
|
||||
func newControllerActivator(app *Application, controller interface{}) *ControllerActivator {
|
||||
if controller == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c, ok := controller.(*ControllerActivator); ok {
|
||||
return c
|
||||
}
|
||||
|
||||
typ := reflect.TypeOf(controller)
|
||||
|
||||
c := &ControllerActivator{
|
||||
|
@ -225,6 +237,11 @@ func (c *ControllerActivator) isReservedMethod(name string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (c *ControllerActivator) markAsWebsocket() {
|
||||
c.servesWebsocket = true
|
||||
c.attachInjector()
|
||||
}
|
||||
|
||||
func (c *ControllerActivator) attachInjector() {
|
||||
if c.injector == nil {
|
||||
partyCountParams := macro.CountParams(c.app.Router.GetRelPath(), *c.app.Router.Macros())
|
||||
|
@ -232,12 +249,18 @@ func (c *ControllerActivator) attachInjector() {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *ControllerActivator) markAsWebsocket() {
|
||||
c.servesWebsocket = true
|
||||
c.attachInjector()
|
||||
// Activated can be called to skip the internal method parsing.
|
||||
func (c *ControllerActivator) Activated() bool {
|
||||
b := c.activated
|
||||
c.activated = true
|
||||
return b
|
||||
}
|
||||
|
||||
func (c *ControllerActivator) activate() {
|
||||
if c.Activated() {
|
||||
return
|
||||
}
|
||||
|
||||
c.parseMethods()
|
||||
}
|
||||
|
||||
|
@ -314,10 +337,16 @@ func (c *ControllerActivator) handleMany(method, path, funcName string, override
|
|||
return nil
|
||||
}
|
||||
|
||||
wd, _ := os.Getwd()
|
||||
|
||||
for _, r := range routes {
|
||||
// change the main handler's name in order to respect the controller's and give
|
||||
// a proper debug message.
|
||||
// change the main handler's name and file:line
|
||||
// in order to respect the controller's and give
|
||||
// a proper debug/log message.
|
||||
r.MainHandlerName = fmt.Sprintf("%s.%s", c.fullName, funcName)
|
||||
if m, ok := c.Type.MethodByName(funcName); ok {
|
||||
r.SourceFileName, r.SourceLineNumber = context.HandlerFileLineRel(m.Func, wd)
|
||||
}
|
||||
}
|
||||
|
||||
// add this as a reserved method name in order to
|
||||
|
|
66
mvc/grpc.go
Normal file
66
mvc/grpc.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package mvc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/kataras/iris/v12/context"
|
||||
)
|
||||
|
||||
// GRPC registers a controller which serves gRPC clients.
|
||||
// It accepts the controller ptr to a struct value,
|
||||
// the gRPCServer itself, and a strict option which is explained below.
|
||||
//
|
||||
// The differences by a common controller are:
|
||||
// HTTP verb: only POST (Party.AllowMethods can be used for more),
|
||||
// method parsing is disabled: path is the function name as it is,
|
||||
// if 'strictMode' option is true then this controller will only serve gRPC-based clients
|
||||
// and fires 404 on common HTTP clients,
|
||||
// otherwise HTTP clients can send and receive JSON (protos contain json struct fields by-default).
|
||||
type GRPC struct {
|
||||
// Server is required and should be gRPC Server derives from google's grpc package.
|
||||
Server http.Handler
|
||||
// ServiceName is required and should be the name of the service (used to build the gRPC route path),
|
||||
// e.g. "helloworld.Greeter".
|
||||
// For a controller's method of "SayHello" and ServiceName "helloworld.Greeter",
|
||||
// both gRPC and common HTTP request path is: "/helloworld.Greeter/SayHello".
|
||||
//
|
||||
// Tip: the ServiceName can be fetched through proto's file descriptor, e.g.
|
||||
// serviceName := pb.File_helloworld_proto.Services().Get(0).FullName().
|
||||
ServiceName string
|
||||
|
||||
// When Strict option is true then this controller will only serve gRPC-based clients
|
||||
// and fires 404 on common HTTP clients.
|
||||
Strict bool
|
||||
}
|
||||
|
||||
// Apply parses the controller's methods and registers gRPC handlers to the application.
|
||||
func (g GRPC) Apply(c *ControllerActivator) {
|
||||
defer c.Activated()
|
||||
|
||||
pre := func(ctx context.Context) {
|
||||
if ctx.IsGRPC() { // gRPC, consumes and produces protobuf.
|
||||
g.Server.ServeHTTP(ctx.ResponseWriter(), ctx.Request())
|
||||
ctx.StopExecution()
|
||||
return
|
||||
}
|
||||
|
||||
if g.Strict {
|
||||
ctx.NotFound()
|
||||
} else {
|
||||
// Allow common HTTP clients, consumes and produces JSON.
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < c.Type.NumMethod(); i++ {
|
||||
m := c.Type.Method(i)
|
||||
path := path.Join(g.ServiceName, m.Name)
|
||||
if route := c.Handle(http.MethodPost, path, m.Name, pre); route != nil {
|
||||
route.Description = "gRPC"
|
||||
if g.Strict {
|
||||
route.Description = "-only"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
24
mvc/mvc.go
24
mvc/mvc.go
|
@ -111,6 +111,15 @@ func (app *Application) Register(dependencies ...interface{}) *Application {
|
|||
return app
|
||||
}
|
||||
|
||||
// Option is an interface which does contain a single `Apply` method that accepts
|
||||
// a `ControllerActivator`. It can be passed on `Application.Handle` method to
|
||||
// mdoify the behavior right after the `BeforeActivation` state.
|
||||
//
|
||||
// See `GRPC` package-level structure too.
|
||||
type Option interface {
|
||||
Apply(*ControllerActivator)
|
||||
}
|
||||
|
||||
// Handle serves a controller for the current mvc application's Router.
|
||||
// It accept any custom struct which its functions will be transformed
|
||||
// to routes.
|
||||
|
@ -154,9 +163,12 @@ func (app *Application) Register(dependencies ...interface{}) *Application {
|
|||
// Result or (Result, error)
|
||||
// where Get is an HTTP Method func.
|
||||
//
|
||||
// Default behavior can be changed through second, variadic, variable "options",
|
||||
// e.g. Handle(controller, GRPC {Server: grpcServer, Strict: true})
|
||||
//
|
||||
// Examples at: https://github.com/kataras/iris/tree/master/_examples/mvc
|
||||
func (app *Application) Handle(controller interface{}) *Application {
|
||||
app.handle(controller)
|
||||
func (app *Application) Handle(controller interface{}, options ...Option) *Application {
|
||||
app.handle(controller, options...)
|
||||
return app
|
||||
}
|
||||
|
||||
|
@ -195,7 +207,7 @@ func (app *Application) GetNamespaces() websocket.Namespaces {
|
|||
return websocket.JoinConnHandlers(app.websocketControllers...).GetNamespaces()
|
||||
}
|
||||
|
||||
func (app *Application) handle(controller interface{}) *ControllerActivator {
|
||||
func (app *Application) handle(controller interface{}, options ...Option) *ControllerActivator {
|
||||
// initialize the controller's activator, nothing too magical so far.
|
||||
c := newControllerActivator(app, controller)
|
||||
|
||||
|
@ -208,6 +220,12 @@ func (app *Application) handle(controller interface{}) *ControllerActivator {
|
|||
before.BeforeActivation(c)
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
if opt != nil {
|
||||
opt.Apply(c)
|
||||
}
|
||||
}
|
||||
|
||||
c.activate()
|
||||
|
||||
if after, okAfter := controller.(interface {
|
||||
|
|
Loading…
Reference in New Issue
Block a user