From 737d2fb7ab4ad358c5fa01b7a08ce346dcaebd79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 22 Jun 2018 09:51:06 +0800 Subject: [PATCH 01/55] add grpc example (#1401) use grpc helloworld example. --- Makefile | 3 +- coverage.sh | 2 +- examples/grpc/README.md | 19 ++++ examples/grpc/gin/main.go | 46 +++++++++ examples/grpc/grpc/server.go | 34 +++++++ examples/grpc/pb/helloworld.pb.go | 151 ++++++++++++++++++++++++++++++ examples/grpc/pb/helloworld.proto | 37 ++++++++ 7 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 examples/grpc/README.md create mode 100644 examples/grpc/gin/main.go create mode 100644 examples/grpc/grpc/server.go create mode 100644 examples/grpc/pb/helloworld.pb.go create mode 100644 examples/grpc/pb/helloworld.proto diff --git a/Makefile b/Makefile index 5468563a..51b9969f 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ GOFMT ?= gofmt "-s" PACKAGES ?= $(shell go list ./... | grep -v /vendor/) +VETPACKAGES ?= $(shell go list ./... | grep -v /vendor/ | grep -v /examples/) GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*") all: install @@ -26,7 +27,7 @@ fmt-check: fi; vet: - go vet $(PACKAGES) + go vet $(VETPACKAGES) deps: @hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ diff --git a/coverage.sh b/coverage.sh index 81437f91..4d1ee036 100644 --- a/coverage.sh +++ b/coverage.sh @@ -4,7 +4,7 @@ set -e echo "mode: count" > coverage.out -for d in $(go list ./... | grep -E 'gin$|binding$|render$'); do +for d in $(go list ./... | grep -E 'gin$|binding$|render$' | grep -v 'examples'); do go test -v -covermode=count -coverprofile=profile.out $d if [ -f profile.out ]; then cat profile.out | grep -v "mode:" >> coverage.out diff --git a/examples/grpc/README.md b/examples/grpc/README.md new file mode 100644 index 00000000..a96d3c1c --- /dev/null +++ b/examples/grpc/README.md @@ -0,0 +1,19 @@ +## How to run this example + +1. run grpc server + +```sh +$ go run grpc/server.go +``` + +2. run gin server + +```sh +$ go run gin/main.go +``` + +3. use curl command to test it + +```sh +$ curl -v 'http://localhost:8052/rest/n/thinkerou' +``` diff --git a/examples/grpc/gin/main.go b/examples/grpc/gin/main.go new file mode 100644 index 00000000..c21692ae --- /dev/null +++ b/examples/grpc/gin/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + "log" + "net/http" + + "github.com/gin-gonic/gin" + pb "github.com/gin-gonic/gin/examples/grpc/pb" + "google.golang.org/grpc" +) + +func main() { + // Set up a connection to the server. + conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) + if err != nil { + log.Fatalf("did not connect: %v", err) + } + defer conn.Close() + client := pb.NewGreeterClient(conn) + + // Set up a http setver. + r := gin.Default() + r.GET("/rest/n/:name", func(c *gin.Context) { + name := c.Param("name") + + // Contact the server and print out its response. + req := &pb.HelloRequest{Name: name} + res, err := client.SayHello(g, req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "result": fmt.Sprint(res.Message), + }) + }) + + // Run http server + if err := r.Run(":8052"); err != nil { + log.Fatalf("could not run server: %v", err) + } +} diff --git a/examples/grpc/grpc/server.go b/examples/grpc/grpc/server.go new file mode 100644 index 00000000..d9bf9fc5 --- /dev/null +++ b/examples/grpc/grpc/server.go @@ -0,0 +1,34 @@ +package main + +import ( + "log" + "net" + + pb "github.com/gin-gonic/gin/examples/grpc/pb" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +// server is used to implement helloworld.GreeterServer. +type server struct{} + +// SayHello implements helloworld.GreeterServer +func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { + return &pb.HelloReply{Message: "Hello " + in.Name}, nil +} + +func main() { + lis, err := net.Listen("tcp", ":50051") + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + s := grpc.NewServer() + pb.RegisterGreeterServer(s, &server{}) + + // Register reflection service on gRPC server. + reflection.Register(s) + if err := s.Serve(lis); err != nil { + log.Fatalf("failed to serve: %v", err) + } +} diff --git a/examples/grpc/pb/helloworld.pb.go b/examples/grpc/pb/helloworld.pb.go new file mode 100644 index 00000000..c8c8942a --- /dev/null +++ b/examples/grpc/pb/helloworld.pb.go @@ -0,0 +1,151 @@ +// Code generated by protoc-gen-go. +// source: helloworld.proto +// DO NOT EDIT! + +/* +Package helloworld is a generated protocol buffer package. + +It is generated from these files: + helloworld.proto + +It has these top-level messages: + HelloRequest + HelloReply +*/ +package helloworld + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// 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.ProtoPackageIsVersion2 // please upgrade the proto package + +// The request message containing the user's name. +type HelloRequest struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` +} + +func (m *HelloRequest) Reset() { *m = HelloRequest{} } +func (m *HelloRequest) String() string { return proto.CompactTextString(m) } +func (*HelloRequest) ProtoMessage() {} +func (*HelloRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +// The response message containing the greetings +type HelloReply struct { + Message string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"` +} + +func (m *HelloReply) Reset() { *m = HelloReply{} } +func (m *HelloReply) String() string { return proto.CompactTextString(m) } +func (*HelloReply) ProtoMessage() {} +func (*HelloReply) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func init() { + proto.RegisterType((*HelloRequest)(nil), "helloworld.HelloRequest") + proto.RegisterType((*HelloReply)(nil), "helloworld.HelloReply") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for Greeter service + +type GreeterClient interface { + // Sends a greeting + SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) +} + +type greeterClient struct { + cc *grpc.ClientConn +} + +func NewGreeterClient(cc *grpc.ClientConn) GreeterClient { + return &greeterClient{cc} +} + +func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { + out := new(HelloReply) + err := grpc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Greeter service + +type GreeterServer interface { + // Sends a greeting + SayHello(context.Context, *HelloRequest) (*HelloReply, error) +} + +func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) { + s.RegisterService(&_Greeter_serviceDesc, srv) +} + +func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HelloRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GreeterServer).SayHello(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/helloworld.Greeter/SayHello", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Greeter_serviceDesc = grpc.ServiceDesc{ + ServiceName: "helloworld.Greeter", + HandlerType: (*GreeterServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SayHello", + Handler: _Greeter_SayHello_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "helloworld.proto", +} + +func init() { proto.RegisterFile("helloworld.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 174 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 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, 0x00, 0xad, 0x50, 0x62, 0x70, 0x32, 0xe0, 0x92, 0xce, 0xcc, 0xd7, + 0x4b, 0x2f, 0x2a, 0x48, 0xd6, 0x4b, 0xad, 0x48, 0xcc, 0x2d, 0xc8, 0x49, 0x2d, 0x46, 0x52, 0xeb, + 0xc4, 0x0f, 0x56, 0x1c, 0x0e, 0x62, 0x07, 0x80, 0xbc, 0x14, 0xc0, 0x98, 0xc4, 0x06, 0xf6, 0x9b, + 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xb7, 0xcd, 0xf2, 0xef, 0x00, 0x00, 0x00, +} diff --git a/examples/grpc/pb/helloworld.proto b/examples/grpc/pb/helloworld.proto new file mode 100644 index 00000000..d79a6a0d --- /dev/null +++ b/examples/grpc/pb/helloworld.proto @@ -0,0 +1,37 @@ +// 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; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} From 605aa1c30f196733a90a4f773b691d2350a30e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 22 Jun 2018 23:44:45 +0800 Subject: [PATCH 02/55] example: fix typo for grpc (#1405) sorry...fixed #1403 --- examples/grpc/gin/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/grpc/gin/main.go b/examples/grpc/gin/main.go index c21692ae..edc1ca9b 100644 --- a/examples/grpc/gin/main.go +++ b/examples/grpc/gin/main.go @@ -26,7 +26,7 @@ func main() { // Contact the server and print out its response. req := &pb.HelloRequest{Name: name} - res, err := client.SayHello(g, req) + res, err := client.SayHello(c, req) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), From 8035359102b5384ad1f860675c85c0238b06b6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 23 Jun 2018 00:08:58 +0800 Subject: [PATCH 03/55] use strings.Split instead of strings.IndexByte (#1406) --- context.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) mode change 100755 => 100644 context.go diff --git a/context.go b/context.go old mode 100755 new mode 100644 index 462357e1..6a84de5e --- a/context.go +++ b/context.go @@ -539,14 +539,10 @@ func (c *Context) ShouldBindBodyWith( func (c *Context) ClientIP() string { if c.engine.ForwardedByClientIP { clientIP := c.requestHeader("X-Forwarded-For") - if index := strings.IndexByte(clientIP, ','); index >= 0 { - clientIP = clientIP[0:index] + clientIP = strings.TrimSpace(strings.Split(clientIP, ",")[0]) + if clientIP == "" { + clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip")) } - clientIP = strings.TrimSpace(clientIP) - if clientIP != "" { - return clientIP - } - clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip")) if clientIP != "" { return clientIP } From 760d0574db7432376d91b53dba9146c1f32d28c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 23 Jun 2018 00:45:43 +0800 Subject: [PATCH 04/55] vendor: remove vendor package from example folder (#1402) updated `vendor.json` is ok. but set `ignore test` in `vendor.json`, `x/net/context` package only use in `context_test.go`, I don't know why vendor still need it. please @appleboy review the pull request, thanks a lot. --- vendor/vendor.json | 61 ---------------------------------------------- 1 file changed, 61 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 5ace49df..c34c2de3 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -9,61 +9,30 @@ "revision": "346938d642f2ec3594ed81d874461961cd0faa76", "revisionTime": "2016-10-29T20:57:26Z" }, - { - "checksumSHA1": "7c3FuEadBInl/4ExSrB7iJMXpe4=", - "path": "github.com/dustin/go-broadcast", - "revision": "3bdf6d4a7164a50bc19d5f230e2981d87d2584f1", - "revisionTime": "2014-06-27T04:00:55Z" - }, { "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", "path": "github.com/gin-contrib/sse", "revision": "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae", "revisionTime": "2017-01-09T09:34:21Z" }, - { - "checksumSHA1": "+vZNyF2MykVjenLg1TpjjgjthV0=", - "path": "github.com/gin-gonic/autotls", - "revision": "8ca25fbde72bb72a00466215b94b489c71fcb815", - "revisionTime": "2017-09-16T16:54:15Z" - }, { "checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=", "path": "github.com/golang/protobuf/proto", "revision": "5a0f697c9ed9d68fef0116532c6e05cfeae00e55", "revisionTime": "2017-06-01T23:02:30Z" }, - { - "checksumSHA1": "Cq9h7eDNXXyR/qJPvO8/Rk4pmFg=", - "path": "github.com/jessevdk/go-assets", - "revision": "4f4301a06e153ff90e17793577ab6bf79f8dc5c5", - "revisionTime": "2016-09-21T14:41:39Z" - }, { "checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=", "path": "github.com/json-iterator/go", "revision": "36b14963da70d11297d313183d7e6388c8510e1e", "revisionTime": "2017-08-29T15:58:51Z" }, - { - "checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=", - "path": "github.com/manucorporat/stats", - "revision": "8f2d6ace262eba462e9beb552382c98be51d807b", - "revisionTime": "2015-05-31T20:46:25Z" - }, { "checksumSHA1": "U6lX43KDDlNOn+Z0Yyww+ZzHfFo=", "path": "github.com/mattn/go-isatty", "revision": "57fdcb988a5c543893cc61bce354a6e24ab70022", "revisionTime": "2017-03-07T16:30:44Z" }, - { - "checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=", - "comment": "v1.0.0", - "path": "github.com/pmezard/go-difflib/difflib", - "revision": "792786c7400a136282c1664665ae0a8db921c6c2", - "revisionTime": "2016-01-10T10:55:54Z" - }, { "checksumSHA1": "Q2V7Zs3diLmLfmfbiuLpSxETSuY=", "comment": "v1.1.4", @@ -71,30 +40,12 @@ "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506", "revisionTime": "2016-09-25T22:06:09Z" }, - { - "checksumSHA1": "IopMW+arBezL5bqOfrVU6UEfn28=", - "path": "github.com/thinkerou/favicon", - "revision": "94a442a49da6e2d44bdd5e0d2e2e185c43a19d93", - "revisionTime": "2017-07-10T14:05:20Z" - }, { "checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=", "path": "github.com/ugorji/go/codec", "revision": "c88ee250d0221a57af388746f5cf03768c21d6e2", "revisionTime": "2017-02-15T20:11:44Z" }, - { - "checksumSHA1": "W0j4I7QpxXlChjyhAojZmFjU6Bg=", - "path": "golang.org/x/crypto/acme", - "revision": "adbae1b6b6fb4b02448a0fc0dbbc9ba2b95b294d", - "revisionTime": "2017-06-19T06:03:41Z" - }, - { - "checksumSHA1": "TrKJW+flz7JulXU3sqnBJjGzgQc=", - "path": "golang.org/x/crypto/acme/autocert", - "revision": "adbae1b6b6fb4b02448a0fc0dbbc9ba2b95b294d", - "revisionTime": "2017-06-19T06:03:41Z" - }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "comment": "release-branch.go1.7", @@ -102,18 +53,6 @@ "revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a", "revisionTime": "2016-10-18T08:54:36Z" }, - { - "checksumSHA1": "S0DP7Pn7sZUmXc55IzZnNvERu6s=", - "path": "golang.org/x/sync/errgroup", - "revision": "8e0aa688b654ef28caa72506fa5ec8dba9fc7690", - "revisionTime": "2017-07-19T03:38:01Z" - }, - { - "checksumSHA1": "TVEkpH3gq84iQ39I4R+mlDwjuVI=", - "path": "golang.org/x/sys/unix", - "revision": "99f16d856c9836c42d24e7ab64ea72916925fa97", - "revisionTime": "2017-03-08T15:04:45Z" - }, { "checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=", "comment": "v8.18.1", From 1f59bad84b577735b27a1ee855f01ed2f50cd88b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 23 Jun 2018 11:06:27 +0800 Subject: [PATCH 05/55] add an edge case from httprouter (#1407) --- path.go | 2 +- path_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/path.go b/path.go index ed63ad1a..1c2b8498 100644 --- a/path.go +++ b/path.go @@ -41,7 +41,7 @@ func cleanPath(p string) string { buf[0] = '/' } - trailing := n > 2 && p[n-1] == '/' + trailing := n > 1 && p[n-1] == '/' // A bit more clunky without a 'lazybuf' like the path package, but the loop // gets completely inlined (bufApp). So in contrast to the path package this diff --git a/path_test.go b/path_test.go index 4a6d945b..c1e6ed4f 100644 --- a/path_test.go +++ b/path_test.go @@ -24,6 +24,7 @@ var cleanTests = []struct { // missing root {"", "/"}, + {"a/", "/a/"}, {"abc", "/abc"}, {"abc/def", "/abc/def"}, {"a/b/c", "/a/b/c"}, From 6c6d97ba2e7eeaf78e20fc4344b21c4e057e2a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 26 Jun 2018 17:21:32 +0800 Subject: [PATCH 06/55] remove hardcode instead of http status value (#1411) --- auth.go | 3 ++- context.go | 6 +++--- gin.go | 12 ++++++------ recovery.go | 3 ++- response_writer.go | 2 +- routergroup.go | 2 +- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/auth.go b/auth.go index c2143091..9ed81b5d 100644 --- a/auth.go +++ b/auth.go @@ -7,6 +7,7 @@ package gin import ( "crypto/subtle" "encoding/base64" + "net/http" "strconv" ) @@ -51,7 +52,7 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc { if !found { // Credentials doesn't match, we return 401 and abort handlers chain. c.Header("WWW-Authenticate", realm) - c.AbortWithStatus(401) + c.AbortWithStatus(http.StatusUnauthorized) return } diff --git a/context.go b/context.go index 6a84de5e..65fbb629 100644 --- a/context.go +++ b/context.go @@ -474,7 +474,7 @@ func (c *Context) BindQuery(obj interface{}) error { // See the binding package. func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { if err = c.ShouldBindWith(obj, b); err != nil { - c.AbortWithError(400, err).SetType(ErrorTypeBind) + c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) } return @@ -589,9 +589,9 @@ func bodyAllowedForStatus(status int) bool { switch { case status >= 100 && status <= 199: return false - case status == 204: + case status == http.StatusNoContent: return false - case status == 304: + case status == http.StatusNotModified: return false } return true diff --git a/gin.go b/gin.go index b91fc2fa..65d6d446 100644 --- a/gin.go +++ b/gin.go @@ -378,14 +378,14 @@ func (engine *Engine) handleHTTPRequest(c *Context) { if tree.method != httpMethod { if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil { c.handlers = engine.allNoMethod - serveError(c, 405, default405Body) + serveError(c, http.StatusMethodNotAllowed, default405Body) return } } } } c.handlers = engine.allNoRoute - serveError(c, 404, default404Body) + serveError(c, http.StatusNotFound, default404Body) } var mimePlain = []string{MIMEPlain} @@ -406,9 +406,9 @@ func serveError(c *Context, code int, defaultMessage []byte) { func redirectTrailingSlash(c *Context) { req := c.Request path := req.URL.Path - code := 301 // Permanent redirect, request with GET method + code := http.StatusMovedPermanently // Permanent redirect, request with GET method if req.Method != "GET" { - code = 307 + code = http.StatusTemporaryRedirect } if length := len(path); length > 1 && path[length-1] == '/' { @@ -430,9 +430,9 @@ func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { trailingSlash, ) if found { - code := 301 // Permanent redirect, request with GET method + code := http.StatusMovedPermanently // Permanent redirect, request with GET method if req.Method != "GET" { - code = 307 + code = http.StatusTemporaryRedirect } req.URL.Path = string(fixedPath) debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String()) diff --git a/recovery.go b/recovery.go index dcbeba74..61c5bd53 100644 --- a/recovery.go +++ b/recovery.go @@ -10,6 +10,7 @@ import ( "io" "io/ioutil" "log" + "net/http" "net/http/httputil" "runtime" "time" @@ -41,7 +42,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { httprequest, _ := httputil.DumpRequest(c.Request, false) logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), string(httprequest), err, stack, reset) } - c.AbortWithStatus(500) + c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() diff --git a/response_writer.go b/response_writer.go index 14b1cfc3..166ae8c3 100644 --- a/response_writer.go +++ b/response_writer.go @@ -13,7 +13,7 @@ import ( const ( noWritten = -1 - defaultStatus = 200 + defaultStatus = http.StatusOK ) type responseWriterBase interface { diff --git a/routergroup.go b/routergroup.go index 53045d10..876a61b8 100644 --- a/routergroup.go +++ b/routergroup.go @@ -184,7 +184,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS _, nolisting := fs.(*onlyfilesFS) return func(c *Context) { if nolisting { - c.Writer.WriteHeader(404) + c.Writer.WriteHeader(http.StatusNotFound) } fileServer.ServeHTTP(c.Writer, c.Request) } From c00f21ff239822d013b80e54bd403fbc08a0b5ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 26 Jun 2018 18:56:43 +0800 Subject: [PATCH 07/55] add go version prerequisite and debug warning (#1394) * add go version prerequisite and debug warning * merge duplicate content * remove duplicate content --- README.md | 113 +++++++++++++++++++++++++++----------------------- debug.go | 3 ++ debug_test.go | 2 +- 3 files changed, 64 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index c2917a5f..e09a51f2 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,11 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi ## Contents +- [Installation](#installation) +- [Prerequisite](#prerequisite) - [Quick start](#quick-start) - [Benchmarks](#benchmarks) - [Gin v1.stable](#gin-v1-stable) -- [Start using it](#start-using-it) - [Build with jsoniter](#build-with-jsoniter) - [API Examples](#api-examples) - [Using GET,POST,PUT,PATCH,DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) @@ -58,6 +59,64 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Testing](#testing) - [Users](#users--) +## Installation + +To install Gin package, you need to install Go and set your Go workspace first. + +1. Download and install it: + +```sh +$ go get -u github.com/gin-gonic/gin +``` + +2. Import it in your code: + +```go +import "github.com/gin-gonic/gin" +``` + +3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`. + +```go +import "net/http" +``` + +### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor) + +1. `go get` govendor + +```sh +$ go get github.com/kardianos/govendor +``` +2. Create your project folder and `cd` inside + +```sh +$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" +``` + +3. Vendor init your project and add gin + +```sh +$ govendor init +$ govendor fetch github.com/gin-gonic/gin@v1.2 +``` + +4. Copy a starting template inside your project + +```sh +$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go +``` + +5. Run your project + +```sh +$ go run main.go +``` + +## Prerequisite + +Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. + ## Quick start ```sh @@ -135,58 +194,6 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 - [x] Battle tested - [x] API frozen, new releases will not break your code. -## Start using it - -1. Download and install it: - -```sh -$ go get github.com/gin-gonic/gin -``` - -2. Import it in your code: - -```go -import "github.com/gin-gonic/gin" -``` - -3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`. - -```go -import "net/http" -``` - -### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor) - -1. `go get` govendor - -```sh -$ go get github.com/kardianos/govendor -``` -2. Create your project folder and `cd` inside - -```sh -$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" -``` - -3. Vendor init your project and add gin - -```sh -$ govendor init -$ govendor fetch github.com/gin-gonic/gin@v1.2 -``` - -4. Copy a starting template inside your project - -```sh -$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go -``` - -5. Run your project - -```sh -$ go run main.go -``` - ## Build with [jsoniter](https://github.com/json-iterator/go) Gin use `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. diff --git a/debug.go b/debug.go index 897c4943..f11156b3 100644 --- a/debug.go +++ b/debug.go @@ -47,6 +47,9 @@ func debugPrint(format string, values ...interface{}) { } func debugPrintWARNINGDefault() { + debugPrint(`[WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. + +`) debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. `) diff --git a/debug_test.go b/debug_test.go index dfd54c82..440173b7 100644 --- a/debug_test.go +++ b/debug_test.go @@ -92,7 +92,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { defer teardown() debugPrintWARNINGDefault() - assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", w.String()) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", w.String()) } func TestDebugPrintWARNINGNew(t *testing.T) { From eb9f313144e6768d91421855810ed5937ce348c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 28 Jun 2018 17:08:09 +0800 Subject: [PATCH 08/55] add comment for context (#1413) ref #1075 annotation from go context source. --- context.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/context.go b/context.go index 65fbb629..6fc5d25f 100644 --- a/context.go +++ b/context.go @@ -836,18 +836,33 @@ func (c *Context) SetAccepted(formats ...string) { /***** GOLANG.ORG/X/NET/CONTEXT *****/ /************************************/ +// Deadline returns the time when work done on behalf of this context +// should be canceled. Deadline returns ok==false when no deadline is +// set. Successive calls to Deadline return the same results. func (c *Context) Deadline() (deadline time.Time, ok bool) { return } +// Done returns a channel that's closed when work done on behalf of this +// context should be canceled. Done may return nil if this context can +// never be canceled. Successive calls to Done return the same value. func (c *Context) Done() <-chan struct{} { return nil } +// Err returns a non-nil error value after Done is closed, +// successive calls to Err return the same error. +// If Done is not yet closed, Err returns nil. +// If Done is closed, Err returns a non-nil error explaining why: +// Canceled if the context was canceled +// or DeadlineExceeded if the context's deadline passed. func (c *Context) Err() error { return nil } +// Value returns the value associated with this context for key, or nil +// if no value is associated with key. Successive calls to Value with +// the same key returns the same result. func (c *Context) Value(key interface{}) interface{} { if key == 0 { return c.Request From cdd02fa9d65c15de42f1a4522fcb9af28fe56cf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 1 Jul 2018 21:10:48 +0800 Subject: [PATCH 09/55] update error(err) to err (#1416) the pull request update `return error(err)` to `return err`, and remove `kindOfData`. --- binding/binding.go | 5 ++--- binding/default_validator.go | 19 ++++++++----------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index 1a984777..3a2aad9c 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -4,10 +4,9 @@ package binding -import ( - "net/http" -) +import "net/http" +// Content-Type MIME of the most common data formats. const ( MIMEJSON = "application/json" MIMEHTML = "text/html" diff --git a/binding/default_validator.go b/binding/default_validator.go index c67aa8a3..e7a302de 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -18,11 +18,17 @@ type defaultValidator struct { var _ StructValidator = &defaultValidator{} +// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type. func (v *defaultValidator) ValidateStruct(obj interface{}) error { - if kindOfData(obj) == reflect.Struct { + value := reflect.ValueOf(obj) + valueType := value.Kind() + if valueType == reflect.Ptr { + valueType = value.Elem().Kind() + } + if valueType == reflect.Struct { v.lazyinit() if err := v.validate.Struct(obj); err != nil { - return error(err) + return err } } return nil @@ -43,12 +49,3 @@ func (v *defaultValidator) lazyinit() { v.validate = validator.New(config) }) } - -func kindOfData(data interface{}) reflect.Kind { - value := reflect.ValueOf(data) - valueType := value.Kind() - if valueType == reflect.Ptr { - valueType = value.Elem().Kind() - } - return valueType -} From 1c4cbfae59e0f62a6812b7808839eed45b66515f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 2 Jul 2018 11:06:56 +0800 Subject: [PATCH 10/55] chore: remove duplicate code (#1418) --- gin.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/gin.go b/gin.go index 65d6d446..3ee8018d 100644 --- a/gin.go +++ b/gin.go @@ -171,14 +171,14 @@ func (engine *Engine) SecureJsonPrefix(prefix string) *Engine { func (engine *Engine) LoadHTMLGlob(pattern string) { left := engine.delims.Left right := engine.delims.Right + templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern)) if IsDebugging() { - debugPrintLoadTemplate(template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))) + debugPrintLoadTemplate(templ) engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims} return } - templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern)) engine.SetHTMLTemplate(templ) } @@ -425,11 +425,7 @@ func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { req := c.Request path := req.URL.Path - fixedPath, found := root.findCaseInsensitivePath( - cleanPath(path), - trailingSlash, - ) - if found { + if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(path), trailingSlash); ok { code := http.StatusMovedPermanently // Permanent redirect, request with GET method if req.Method != "GET" { code = http.StatusTemporaryRedirect From d17a12591fb637da47576b8669fabb8752fe656d Mon Sep 17 00:00:00 2001 From: vz Date: Tue, 3 Jul 2018 15:39:18 +0800 Subject: [PATCH 11/55] update assert param(expect, actual) position (#1421) - update assert param(expect, actual) position --- errors_test.go | 18 +++++++++--------- response_writer_test.go | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/errors_test.go b/errors_test.go index a666d7c1..0626611f 100644 --- a/errors_test.go +++ b/errors_test.go @@ -19,17 +19,17 @@ func TestError(t *testing.T) { Type: ErrorTypePrivate, } assert.Equal(t, err.Error(), baseError.Error()) - assert.Equal(t, err.JSON(), H{"error": baseError.Error()}) + assert.Equal(t, H{"error": baseError.Error()}, err.JSON()) assert.Equal(t, err.SetType(ErrorTypePublic), err) - assert.Equal(t, err.Type, ErrorTypePublic) + assert.Equal(t, ErrorTypePublic, err.Type) assert.Equal(t, err.SetMeta("some data"), err) - assert.Equal(t, err.Meta, "some data") - assert.Equal(t, err.JSON(), H{ + assert.Equal(t, "some data", err.Meta) + assert.Equal(t, H{ "error": baseError.Error(), "meta": "some data", - }) + }, err.JSON()) jsonBytes, _ := json.Marshal(err) assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes)) @@ -38,22 +38,22 @@ func TestError(t *testing.T) { "status": "200", "data": "some data", }) - assert.Equal(t, err.JSON(), H{ + assert.Equal(t, H{ "error": baseError.Error(), "status": "200", "data": "some data", - }) + }, err.JSON()) err.SetMeta(H{ "error": "custom error", "status": "200", "data": "some data", }) - assert.Equal(t, err.JSON(), H{ + assert.Equal(t, H{ "error": "custom error", "status": "200", "data": "some data", - }) + }, err.JSON()) type customError struct { status string diff --git a/response_writer_test.go b/response_writer_test.go index cec27338..4289a7c1 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -51,7 +51,7 @@ func TestResponseWriterWriteHeader(t *testing.T) { w.WriteHeader(300) assert.False(t, w.Written()) assert.Equal(t, 300, w.Status()) - assert.NotEqual(t, testWritter.Code, 300) + assert.NotEqual(t, 300, testWritter.Code) w.WriteHeader(-1) assert.Equal(t, 300, w.Status()) From 85221af84cf6ee99dc900bc41912abc09acfc1b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rex=20Lee=28=E6=9D=8E=E4=BF=8A=29?= Date: Tue, 3 Jul 2018 17:17:08 +0800 Subject: [PATCH 12/55] add json ASCII string render (#1358) add a json render that rendering json as ASCII string --- README.md | 23 +++++++++++++++++++++++ context.go | 6 ++++++ context_test.go | 11 +++++++++++ render/json.go | 32 ++++++++++++++++++++++++++++++++ render/render_test.go | 29 +++++++++++++++++++++++++++++ 5 files changed, 101 insertions(+) diff --git a/README.md b/README.md index e09a51f2..93896469 100644 --- a/README.md +++ b/README.md @@ -900,6 +900,29 @@ func main() { } ``` +#### AsciiJSON + +Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII chracters. + +```go +func main() { + r := gin.Default() + + r.GET("/someJSON", func(c *gin.Context) { + data := map[string]interface{}{ + "lang": "GO语言", + "tag": "
", + } + + // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} + c.AsciiJSON(http.StatusOK, data) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + ### Serving static files ```go diff --git a/context.go b/context.go index 6fc5d25f..d0b4e87f 100644 --- a/context.go +++ b/context.go @@ -704,6 +704,12 @@ func (c *Context) JSON(code int, obj interface{}) { c.Render(code, render.JSON{Data: obj}) } +// AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string. +// It also sets the Content-Type as "application/json". +func (c *Context) AsciiJSON(code int, obj interface{}) { + c.Render(code, render.AsciiJSON{Data: obj}) +} + // XML serializes the given struct as XML into the response body. // It also sets the Content-Type as "application/xml". func (c *Context) XML(code int, obj interface{}) { diff --git a/context_test.go b/context_test.go index 12e02fa0..6f37682e 100644 --- a/context_test.go +++ b/context_test.go @@ -686,6 +686,17 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } +func TestContextRenderNoContentAsciiJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.AsciiJSON(http.StatusNoContent, []string{"lang", "Go语言"}) + + assert.Equal(t, http.StatusNoContent, w.Code) + assert.Empty(t, w.Body.String()) + assert.Equal(t, "application/json", w.HeaderMap.Get("Content-Type")) +} + // Tests that the response executes the templates // and responds with Content-Type set to text/html func TestContextRenderHTML(t *testing.T) { diff --git a/render/json.go b/render/json.go index 3a2e8b2f..6e5089a0 100755 --- a/render/json.go +++ b/render/json.go @@ -6,6 +6,7 @@ package render import ( "bytes" + "fmt" "html/template" "net/http" @@ -30,10 +31,15 @@ type JsonpJSON struct { Data interface{} } +type AsciiJSON struct { + Data interface{} +} + type SecureJSONPrefix string var jsonContentType = []string{"application/json; charset=utf-8"} var jsonpContentType = []string{"application/javascript; charset=utf-8"} +var jsonAsciiContentType = []string{"application/json"} func (r JSON) Render(w http.ResponseWriter) (err error) { if err = WriteJSON(w, r.Data); err != nil { @@ -112,3 +118,29 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonpContentType) } + +func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { + r.WriteContentType(w) + ret, err := json.Marshal(r.Data) + if err != nil { + return err + } + + var buffer bytes.Buffer + for _, r := range string(ret) { + cvt := "" + if r < 128 { + cvt = string(r) + } else { + cvt = fmt.Sprintf("\\u%04x", int64(r)) + } + buffer.WriteString(cvt) + } + + w.Write(buffer.Bytes()) + return nil +} + +func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { + writeContentType(w, jsonAsciiContentType) +} diff --git a/render/render_test.go b/render/render_test.go index 40ec806e..2f728441 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -167,6 +167,35 @@ func TestRenderJsonpJSONFail(t *testing.T) { assert.Error(t, err) } +func TestRenderAsciiJSON(t *testing.T) { + w1 := httptest.NewRecorder() + data1 := map[string]interface{}{ + "lang": "GO语言", + "tag": "
", + } + + err := (AsciiJSON{data1}).Render(w1) + + assert.NoError(t, err) + assert.Equal(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String()) + assert.Equal(t, "application/json", w1.Header().Get("Content-Type")) + + w2 := httptest.NewRecorder() + data2 := float64(3.1415926) + + err = (AsciiJSON{data2}).Render(w2) + assert.NoError(t, err) + assert.Equal(t, "3.1415926", w2.Body.String()) +} + +func TestRenderAsciiJSONFail(t *testing.T) { + w := httptest.NewRecorder() + data := make(chan int) + + // json: unsupported type: chan int + assert.Error(t, (AsciiJSON{data}).Render(w)) +} + type xmlmap map[string]interface{} // Allows type H to be used with xml.Marshal From 220e8d34538f5dfdcc299dda79bf83cf52e633f6 Mon Sep 17 00:00:00 2001 From: solos Date: Sat, 21 Jul 2018 00:52:55 +0800 Subject: [PATCH 13/55] return json if jsonp has not callback (#1438) return json if jsonp has not callback --- context.go | 7 ++++++- context_test.go | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index d0b4e87f..720c4251 100644 --- a/context.go +++ b/context.go @@ -695,7 +695,12 @@ func (c *Context) SecureJSON(code int, obj interface{}) { // It add padding to response body to request data from a server residing in a different domain than the client. // It also sets the Content-Type as "application/javascript". func (c *Context) JSONP(code int, obj interface{}) { - c.Render(code, render.JsonpJSON{Callback: c.DefaultQuery("callback", ""), Data: obj}) + callback := c.DefaultQuery("callback", "") + if callback == "" { + c.Render(code, render.JSON{Data: obj}) + } else { + c.Render(code, render.JsonpJSON{Callback: callback, Data: obj}) + } } // JSON serializes the given struct as JSON into the response body. diff --git a/context_test.go b/context_test.go index 6f37682e..e29569fd 100644 --- a/context_test.go +++ b/context_test.go @@ -596,6 +596,20 @@ func TestContextRenderJSONP(t *testing.T) { assert.Equal(t, "application/javascript; charset=utf-8", w.HeaderMap.Get("Content-Type")) } +// Tests that the response is serialized as JSONP +// and Content-Type is set to application/json +func TestContextRenderJSONPWithoutCallback(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("GET", "http://example.com", nil) + + c.JSONP(201, H{"foo": "bar"}) + + assert.Equal(t, 201, w.Code) + assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} + // Tests that no JSON is rendered if code is 204 func TestContextRenderNoContentJSON(t *testing.T) { w := httptest.NewRecorder() From 631cfbd1ef2374f5a01f5d17ed8c8f08779ad6fe Mon Sep 17 00:00:00 2001 From: Dmitry Dorogin Date: Sun, 5 Aug 2018 08:29:26 +0300 Subject: [PATCH 14/55] Simplify context error (#1431) Hello! Looking through context package and found a little bit complicated switch block. And tried to make it easier. Thanks! --- context.go | 9 ++++----- ginS/gins.go | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/context.go b/context.go index 720c4251..779484b1 100644 --- a/context.go +++ b/context.go @@ -159,16 +159,15 @@ func (c *Context) Error(err error) *Error { if err == nil { panic("err is nil") } - var parsedError *Error - switch err.(type) { - case *Error: - parsedError = err.(*Error) - default: + + parsedError, ok := err.(*Error) + if !ok { parsedError = &Error{ Err: err, Type: ErrorTypePrivate, } } + c.Errors = append(c.Errors, parsedError) return parsedError } diff --git a/ginS/gins.go b/ginS/gins.go index ee00b381..a7686f23 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -128,7 +128,7 @@ func Run(addr ...string) (err error) { // RunTLS : The router is attached to a http.Server and starts listening and serving HTTPS requests. // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) // Note: this method will block the calling goroutine undefinitelly unless an error happens. -func RunTLS(addr string, certFile string, keyFile string) (err error) { +func RunTLS(addr, certFile, keyFile string) (err error) { return engine().RunTLS(addr, certFile, keyFile) } From 647535cd9b780b6ccbeccfedfba36896e1a1da37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 6 Aug 2018 12:07:11 +0800 Subject: [PATCH 15/55] Support map as query string or post form parameters (#1383) * support query map * add GetQueryMap and unittest * support post-form map * add readme for query map * attempt to fix bug for post-form map when go version is 1.6 * remove duplicate code * remove comment --- README.md | 29 +++++++++++++++++++++++++++++ context.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ context_test.go | 45 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 121 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 93896469..11c91c9a 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Querystring parameters](#querystring-parameters) - [Multipart/Urlencoded Form](#multiparturlencoded-form) - [Another example: query + post form](#another-example-query--post-form) + - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters) - [Upload files](#upload-files) - [Grouping routes](#grouping-routes) - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) @@ -323,6 +324,34 @@ func main() { id: 1234; page: 1; name: manu; message: this_is_great ``` +### Map as querystring or postform parameters + +``` +POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 +Content-Type: application/x-www-form-urlencoded + +names[first]=thinkerou&names[second]=tianou +``` + +```go +func main() { + router := gin.Default() + + router.POST("/post", func(c *gin.Context) { + + ids := c.QueryMap("ids") + names := c.PostFormMap("names") + + fmt.Printf("ids: %v; names: %v", ids, names) + }) + router.Run(":8080") +} +``` + +``` +ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] +``` + ### Upload files #### Single file diff --git a/context.go b/context.go index 779484b1..724ded79 100644 --- a/context.go +++ b/context.go @@ -360,6 +360,18 @@ func (c *Context) GetQueryArray(key string) ([]string, bool) { return []string{}, false } +// QueryMap returns a map for a given query key. +func (c *Context) QueryMap(key string) map[string]string { + dicts, _ := c.GetQueryMap(key) + return dicts +} + +// GetQueryMap returns a map for a given query key, plus a boolean value +// whether at least one value exists for the given key. +func (c *Context) GetQueryMap(key string) (map[string]string, bool) { + return c.get(c.Request.URL.Query(), key) +} + // PostForm returns the specified key from a POST urlencoded form or multipart form // when it exists, otherwise it returns an empty string `("")`. func (c *Context) PostForm(key string) string { @@ -415,6 +427,42 @@ func (c *Context) GetPostFormArray(key string) ([]string, bool) { return []string{}, false } +// PostFormMap returns a map for a given form key. +func (c *Context) PostFormMap(key string) map[string]string { + dicts, _ := c.GetPostFormMap(key) + return dicts +} + +// GetPostFormMap returns a map for a given form key, plus a boolean value +// whether at least one value exists for the given key. +func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { + req := c.Request + req.ParseForm() + req.ParseMultipartForm(c.engine.MaxMultipartMemory) + dicts, exist := c.get(req.PostForm, key) + + if !exist && req.MultipartForm != nil && req.MultipartForm.File != nil { + dicts, exist = c.get(req.MultipartForm.Value, key) + } + + return dicts, exist +} + +// get is an internal method and returns a map which satisfy conditions. +func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) { + dicts := make(map[string]string) + exist := false + for k, v := range m { + if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key { + if j := strings.IndexByte(k[i+1:], ']'); j >= 1 { + exist = true + dicts[k[i+1:][:j]] = v[0] + } + } + } + return dicts, exist +} + // FormFile returns the first file for the provided form key. func (c *Context) FormFile(name string) (*multipart.FileHeader, error) { _, fh, err := c.Request.FormFile(name) diff --git a/context_test.go b/context_test.go index e29569fd..589ab336 100644 --- a/context_test.go +++ b/context_test.go @@ -47,6 +47,8 @@ func createMultipartRequest() *http.Request { must(mw.WriteField("time_local", "31/12/2016 14:55")) must(mw.WriteField("time_utc", "31/12/2016 14:55")) must(mw.WriteField("time_location", "31/12/2016 14:55")) + must(mw.WriteField("names[a]", "thinkerou")) + must(mw.WriteField("names[b]", "tianou")) req, err := http.NewRequest("POST", "/", body) must(err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) @@ -371,7 +373,8 @@ func TestContextQuery(t *testing.T) { func TestContextQueryAndPostForm(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second") - c.Request, _ = http.NewRequest("POST", "/?both=GET&id=main&id=omit&array[]=first&array[]=second", body) + c.Request, _ = http.NewRequest("POST", + "/?both=GET&id=main&id=omit&array[]=first&array[]=second&ids[a]=hi&ids[b]=3.14", body) c.Request.Header.Add("Content-Type", MIMEPOSTForm) assert.Equal(t, "bar", c.DefaultPostForm("foo", "none")) @@ -439,6 +442,30 @@ func TestContextQueryAndPostForm(t *testing.T) { values = c.QueryArray("both") assert.Equal(t, 1, len(values)) assert.Equal(t, "GET", values[0]) + + dicts, ok := c.GetQueryMap("ids") + assert.True(t, ok) + assert.Equal(t, "hi", dicts["a"]) + assert.Equal(t, "3.14", dicts["b"]) + + dicts, ok = c.GetQueryMap("nokey") + assert.False(t, ok) + assert.Equal(t, 0, len(dicts)) + + dicts, ok = c.GetQueryMap("both") + assert.False(t, ok) + assert.Equal(t, 0, len(dicts)) + + dicts, ok = c.GetQueryMap("array") + assert.False(t, ok) + assert.Equal(t, 0, len(dicts)) + + dicts = c.QueryMap("ids") + assert.Equal(t, "hi", dicts["a"]) + assert.Equal(t, "3.14", dicts["b"]) + + dicts = c.QueryMap("nokey") + assert.Equal(t, 0, len(dicts)) } func TestContextPostFormMultipart(t *testing.T) { @@ -515,6 +542,22 @@ func TestContextPostFormMultipart(t *testing.T) { values = c.PostFormArray("foo") assert.Equal(t, 1, len(values)) assert.Equal(t, "bar", values[0]) + + dicts, ok := c.GetPostFormMap("names") + assert.True(t, ok) + assert.Equal(t, "thinkerou", dicts["a"]) + assert.Equal(t, "tianou", dicts["b"]) + + dicts, ok = c.GetPostFormMap("nokey") + assert.False(t, ok) + assert.Equal(t, 0, len(dicts)) + + dicts = c.PostFormMap("names") + assert.Equal(t, "thinkerou", dicts["a"]) + assert.Equal(t, "tianou", dicts["b"]) + + dicts = c.PostFormMap("nokey") + assert.Equal(t, 0, len(dicts)) } func TestContextSetCookie(t *testing.T) { From e2b4cf6e2dffb4e59f95f96b78e25e65f9037dec Mon Sep 17 00:00:00 2001 From: grapeVine Date: Mon, 6 Aug 2018 23:08:01 +0800 Subject: [PATCH 16/55] interface implement type check (#1459) interface implement type check --- render/render.go | 1 + 1 file changed, 1 insertion(+) diff --git a/render/render.go b/render/render.go index 7caf9bba..4ff1c7b6 100755 --- a/render/render.go +++ b/render/render.go @@ -26,6 +26,7 @@ var ( _ Render = YAML{} _ Render = MsgPack{} _ Render = Reader{} + _ Render = AsciiJSON{} ) func writeContentType(w http.ResponseWriter, value []string) { From 9b7e7bdce6e073d47635d57fbacfb3205cb3127e Mon Sep 17 00:00:00 2001 From: Dmitry Dorogin Date: Tue, 7 Aug 2018 01:44:32 +0300 Subject: [PATCH 17/55] Add tests for context.Stream (#1433) --- context_test.go | 36 ++++++++++++++++++++++++++++++++++++ test_helpers.go | 21 +++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/context_test.go b/context_test.go index 589ab336..40ae5bf8 100644 --- a/context_test.go +++ b/context_test.go @@ -21,6 +21,7 @@ import ( "github.com/gin-gonic/gin/binding" "github.com/stretchr/testify/assert" "golang.org/x/net/context" + "io" ) var _ context.Context = &Context{} @@ -1558,3 +1559,38 @@ func TestContextRenderDataFromReader(t *testing.T) { assert.Equal(t, fmt.Sprintf("%d", contentLength), w.HeaderMap.Get("Content-Length")) assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) } + +func TestContextStream(t *testing.T) { + w := CreateTestResponseRecorder() + c, _ := CreateTestContext(w) + + stopStream := true + c.Stream(func(w io.Writer) bool { + defer func() { + stopStream = false + }() + + w.Write([]byte("test")) + + return stopStream + }) + + assert.Equal(t, "testtest", w.Body.String()) +} + +func TestContextStreamWithClientGone(t *testing.T) { + w := CreateTestResponseRecorder() + c, _ := CreateTestContext(w) + + c.Stream(func(writer io.Writer) bool { + defer func() { + w.closeClient() + }() + + writer.Write([]byte("test")) + + return true + }) + + assert.Equal(t, "test", w.Body.String()) +} diff --git a/test_helpers.go b/test_helpers.go index 2aed37f2..554568d9 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -6,6 +6,7 @@ package gin import ( "net/http" + "net/http/httptest" ) // CreateTestContext returns a fresh engine and context for testing purposes @@ -16,3 +17,23 @@ func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { c.writermem.reset(w) return } + +type TestResponseRecorder struct { + *httptest.ResponseRecorder + closeChannel chan bool +} + +func (r *TestResponseRecorder) CloseNotify() <-chan bool { + return r.closeChannel +} + +func (r *TestResponseRecorder) closeClient() { + r.closeChannel <- true +} + +func CreateTestResponseRecorder() *TestResponseRecorder { + return &TestResponseRecorder{ + httptest.NewRecorder(), + make(chan bool, 1), + } +} From 0552c3bc3a6de6b802684b024ae49c3195cc70dd Mon Sep 17 00:00:00 2001 From: zhanweidu Date: Tue, 7 Aug 2018 12:41:28 +0800 Subject: [PATCH 18/55] flush operation will overwrite the origin status code (#1460) The status of responseWriter will be overwrite if flush was called. This is caused by the Flush of http.response.Flush(). --- response_writer.go | 1 + response_writer_test.go | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/response_writer.go b/response_writer.go index 166ae8c3..923b53f8 100644 --- a/response_writer.go +++ b/response_writer.go @@ -110,5 +110,6 @@ func (w *responseWriter) CloseNotify() <-chan bool { // Flush implements the http.Flush interface. func (w *responseWriter) Flush() { + w.WriteHeaderNow() w.ResponseWriter.(http.Flusher).Flush() } diff --git a/response_writer_test.go b/response_writer_test.go index 4289a7c1..fcc48b3b 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -113,3 +113,19 @@ func TestResponseWriterHijack(t *testing.T) { w.Flush() } + +func TestResponseWriterFlush(t *testing.T) { + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + writer := &responseWriter{} + writer.reset(w) + + writer.WriteHeader(http.StatusInternalServerError) + writer.Flush() + })) + defer testServer.Close() + + // should return 500 + resp, err := http.Get(testServer.URL) + assert.NoError(t, err) + assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) +} From 9666ba67383f6cc7bed3bb18691c2382717d51ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 7 Aug 2018 13:49:31 +0800 Subject: [PATCH 19/55] chore: update top bar header (#1461) --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 11c91c9a..c2cfe2d0 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,11 @@ [![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) - [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) - [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) - [![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) - [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) +[![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) +[![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) +[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. @@ -1811,7 +1812,7 @@ func TestPingRoute(t *testing.T) { } ``` -## Users [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) +## Users Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. From 1f1bc429ed7a4d591a7374f0e267b59747b177bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 9 Aug 2018 17:20:06 +0800 Subject: [PATCH 20/55] chore: add test case for source/function of recovery.go (#1467) --- recovery_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/recovery_test.go b/recovery_test.go index de3b62a5..fa065b13 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -41,3 +41,23 @@ func TestPanicWithAbort(t *testing.T) { // TEST assert.Equal(t, 400, w.Code) } + +func TestSource(t *testing.T) { + bs := source(nil, 0) + assert.Equal(t, []byte("???"), bs) + + in := [][]byte{ + []byte("Hello world."), + []byte("Hi, gin.."), + } + bs = source(in, 10) + assert.Equal(t, []byte("???"), bs) + + bs = source(in, 1) + assert.Equal(t, []byte("Hello world."), bs) +} + +func TestFunction(t *testing.T) { + bs := function(1) + assert.Equal(t, []byte("???"), bs) +} From 8fc8ce047243dbbbc5eb3d01b9fd026fc3b08c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 10 Aug 2018 20:50:23 +0800 Subject: [PATCH 21/55] small enhance for cleanPath (#1469) from httprouter patch: https://github.com/julienschmidt/httprouter/pull/243 --- path.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/path.go b/path.go index 1c2b8498..d1f59622 100644 --- a/path.go +++ b/path.go @@ -59,11 +59,11 @@ func cleanPath(p string) string { case p[r] == '.' && p[r+1] == '/': // . element - r++ + r += 2 case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'): // .. element: remove to last / - r += 2 + r += 3 if w > 1 { // can backtrack From 7e64d32269d817b645d5d4694100a8a8ad6960cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Aug 2018 10:12:33 +0800 Subject: [PATCH 22/55] Attempt to fix #1462 (#1463) #1462 --- context_test.go | 20 ++++++++++++++++++++ test_helpers.go | 25 +------------------------ 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/context_test.go b/context_test.go index 40ae5bf8..0185507f 100644 --- a/context_test.go +++ b/context_test.go @@ -1560,6 +1560,26 @@ func TestContextRenderDataFromReader(t *testing.T) { assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) } +type TestResponseRecorder struct { + *httptest.ResponseRecorder + closeChannel chan bool +} + +func (r *TestResponseRecorder) CloseNotify() <-chan bool { + return r.closeChannel +} + +func (r *TestResponseRecorder) closeClient() { + r.closeChannel <- true +} + +func CreateTestResponseRecorder() *TestResponseRecorder { + return &TestResponseRecorder{ + httptest.NewRecorder(), + make(chan bool, 1), + } +} + func TestContextStream(t *testing.T) { w := CreateTestResponseRecorder() c, _ := CreateTestContext(w) diff --git a/test_helpers.go b/test_helpers.go index 554568d9..3a7a5ddf 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -4,10 +4,7 @@ package gin -import ( - "net/http" - "net/http/httptest" -) +import "net/http" // CreateTestContext returns a fresh engine and context for testing purposes func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { @@ -17,23 +14,3 @@ func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { c.writermem.reset(w) return } - -type TestResponseRecorder struct { - *httptest.ResponseRecorder - closeChannel chan bool -} - -func (r *TestResponseRecorder) CloseNotify() <-chan bool { - return r.closeChannel -} - -func (r *TestResponseRecorder) closeClient() { - r.closeChannel <- true -} - -func CreateTestResponseRecorder() *TestResponseRecorder { - return &TestResponseRecorder{ - httptest.NewRecorder(), - make(chan bool, 1), - } -} From e5bb4f62a284426de199d68d71900fcbd1285691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Aug 2018 21:17:57 +0800 Subject: [PATCH 23/55] chore: add return or remove else for reduce indent (#1470) --- gin.go | 69 ++++++++++++++++++++++++++------------------------ routes_test.go | 10 ++++++++ 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/gin.go b/gin.go index 3ee8018d..fc4d6564 100644 --- a/gin.go +++ b/gin.go @@ -349,38 +349,40 @@ func (engine *Engine) handleHTTPRequest(c *Context) { // Find root of the tree for the given HTTP method t := engine.trees for i, tl := 0, len(t); i < tl; i++ { - if t[i].method == httpMethod { - root := t[i].root - // Find route in tree - handlers, params, tsr := root.getValue(path, c.Params, unescape) - if handlers != nil { - c.handlers = handlers - c.Params = params - c.Next() - c.writermem.WriteHeaderNow() + if t[i].method != httpMethod { + continue + } + root := t[i].root + // Find route in tree + handlers, params, tsr := root.getValue(path, c.Params, unescape) + if handlers != nil { + c.handlers = handlers + c.Params = params + c.Next() + c.writermem.WriteHeaderNow() + return + } + if httpMethod != "CONNECT" && path != "/" { + if tsr && engine.RedirectTrailingSlash { + redirectTrailingSlash(c) return } - if httpMethod != "CONNECT" && path != "/" { - if tsr && engine.RedirectTrailingSlash { - redirectTrailingSlash(c) - return - } - if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { - return - } + if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { + return } - break } + break } if engine.HandleMethodNotAllowed { for _, tree := range engine.trees { - if tree.method != httpMethod { - if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil { - c.handlers = engine.allNoMethod - serveError(c, http.StatusMethodNotAllowed, default405Body) - return - } + if tree.method == httpMethod { + continue + } + if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil { + c.handlers = engine.allNoMethod + serveError(c, http.StatusMethodNotAllowed, default405Body) + return } } } @@ -393,14 +395,16 @@ var mimePlain = []string{MIMEPlain} func serveError(c *Context, code int, defaultMessage []byte) { c.writermem.status = code c.Next() - if !c.writermem.Written() { - if c.writermem.Status() == code { - c.writermem.Header()["Content-Type"] = mimePlain - c.Writer.Write(defaultMessage) - } else { - c.writermem.WriteHeaderNow() - } + if c.writermem.Written() { + return } + if c.writermem.Status() == code { + c.writermem.Header()["Content-Type"] = mimePlain + c.Writer.Write(defaultMessage) + return + } + c.writermem.WriteHeaderNow() + return } func redirectTrailingSlash(c *Context) { @@ -411,10 +415,9 @@ func redirectTrailingSlash(c *Context) { code = http.StatusTemporaryRedirect } + req.URL.Path = path + "/" if length := len(path); length > 1 && path[length-1] == '/' { req.URL.Path = path[:length-1] - } else { - req.URL.Path = path + "/" } debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String()) http.Redirect(c.Writer, req, req.URL.String(), code) diff --git a/routes_test.go b/routes_test.go index 81293907..a2389f9b 100644 --- a/routes_test.go +++ b/routes_test.go @@ -333,6 +333,16 @@ func TestRouteNotAllowedEnabled(t *testing.T) { assert.Equal(t, http.StatusTeapot, w.Code) } +func TestRouteNotAllowedEnabled2(t *testing.T) { + router := New() + router.HandleMethodNotAllowed = true + // add one methodTree to trees + router.addRoute("POST", "/", HandlersChain{func(_ *Context) {}}) + router.GET("/path2", func(c *Context) {}) + w := performRequest(router, "POST", "/path2") + assert.Equal(t, http.StatusMethodNotAllowed, w.Code) +} + func TestRouteNotAllowedDisabled(t *testing.T) { router := New() router.HandleMethodNotAllowed = false From 202db4fb117768795c0fcc909cc9c3d75b9172b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Aug 2018 21:38:07 +0800 Subject: [PATCH 24/55] improve utils code coverage (#1473) --- utils_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/utils_test.go b/utils_test.go index 3d019e7e..95b5de5e 100644 --- a/utils_test.go +++ b/utils_test.go @@ -5,6 +5,8 @@ package gin import ( + "bytes" + "encoding/xml" "fmt" "net/http" "testing" @@ -124,3 +126,14 @@ func TestBindMiddleware(t *testing.T) { Bind(&bindTestStruct{}) }) } + +func TestMarshalXMLforH(t *testing.T) { + h := H{ + "": "test", + } + var b bytes.Buffer + enc := xml.NewEncoder(&b) + var x xml.StartElement + e := h.MarshalXML(enc, x) + assert.Error(t, e) +} From 1ae32f3a2cfa53d1ae3e5c10a72841673bedc449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Aug 2018 22:02:37 +0800 Subject: [PATCH 25/55] improve render code coverage (#1474) all code coverage > 99% --- render/render_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/render/render_test.go b/render/render_test.go index 2f728441..9e768850 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -158,6 +158,21 @@ func TestRenderJsonpJSON(t *testing.T) { assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) } +func TestRenderJsonpJSONError2(t *testing.T) { + w := httptest.NewRecorder() + data := map[string]interface{}{ + "foo": "bar", + } + (JsonpJSON{"", data}).WriteContentType(w) + assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) + + e := (JsonpJSON{"", data}).Render(w) + assert.NoError(t, e) + + assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) +} + func TestRenderJsonpJSONFail(t *testing.T) { w := httptest.NewRecorder() data := make(chan int) From 61592134625e0fca1b2de7c22107a01684e8b130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Aug 2018 23:38:31 +0800 Subject: [PATCH 26/55] unify test data (#1417) mkdir a test data dir. --- README.md | 2 +- binding/binding_body_test.go | 6 +++--- binding/binding_test.go | 14 +++++++------- debug_test.go | 2 +- examples/template/main.go | 2 +- gin_test.go | 8 ++++---- render/render_test.go | 4 ++-- .../testdata => testdata/certificate}/cert.pem | 0 .../testdata => testdata/certificate}/key.pem | 0 .../example => testdata/protoexample}/test.pb.go | 6 +++--- .../example => testdata/protoexample}/test.proto | 2 +- {fixtures/basic => testdata/template}/hello.tmpl | 0 {fixtures/basic => testdata/template}/raw.tmpl | 0 13 files changed, 23 insertions(+), 23 deletions(-) rename {fixtures/testdata => testdata/certificate}/cert.pem (100%) rename {fixtures/testdata => testdata/certificate}/key.pem (100%) rename {binding/example => testdata/protoexample}/test.pb.go (94%) rename {binding/example => testdata/protoexample}/test.proto (90%) rename {fixtures/basic => testdata/template}/hello.tmpl (100%) rename {fixtures/basic => testdata/template}/raw.tmpl (100%) diff --git a/README.md b/README.md index c2cfe2d0..bf1bbb49 100644 --- a/README.md +++ b/README.md @@ -1117,7 +1117,7 @@ func main() { router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) - router.LoadHTMLFiles("./fixtures/basic/raw.tmpl") + router.LoadHTMLFiles("./testdata/template/raw.tmpl") router.GET("/raw", func(c *gin.Context) { c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ diff --git a/binding/binding_body_test.go b/binding/binding_body_test.go index c41d9f86..dfd761e1 100644 --- a/binding/binding_body_test.go +++ b/binding/binding_body_test.go @@ -5,7 +5,7 @@ import ( "io/ioutil" "testing" - "github.com/gin-gonic/gin/binding/example" + "github.com/gin-gonic/gin/testdata/protoexample" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/ugorji/go/codec" @@ -55,12 +55,12 @@ func msgPackBody(t *testing.T) string { } func TestBindingBodyProto(t *testing.T) { - test := example.Test{ + test := protoexample.Test{ Label: proto.String("FOO"), } data, _ := proto.Marshal(&test) req := requestWithBody("POST", "/", string(data)) - form := example.Test{} + form := protoexample.Test{} body, _ := ioutil.ReadAll(req.Body) assert.NoError(t, ProtoBuf.BindBody(body, &form)) assert.Equal(t, test, form) diff --git a/binding/binding_test.go b/binding/binding_test.go index 936deac7..efe87669 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -14,7 +14,7 @@ import ( "testing" "time" - "github.com/gin-gonic/gin/binding/example" + "github.com/gin-gonic/gin/testdata/protoexample" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/ugorji/go/codec" @@ -562,7 +562,7 @@ func TestBindingFormMultipartFail(t *testing.T) { } func TestBindingProtoBuf(t *testing.T) { - test := &example.Test{ + test := &protoexample.Test{ Label: proto.String("yes"), } data, _ := proto.Marshal(test) @@ -574,7 +574,7 @@ func TestBindingProtoBuf(t *testing.T) { } func TestBindingProtoBufFail(t *testing.T) { - test := &example.Test{ + test := &protoexample.Test{ Label: proto.String("yes"), } data, _ := proto.Marshal(test) @@ -1156,14 +1156,14 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) - obj := example.Test{} + obj := protoexample.Test{} req := requestWithBody("POST", path, body) req.Header.Add("Content-Type", MIMEPROTOBUF) err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, "yes", *obj.Label) - obj = example.Test{} + obj = protoexample.Test{} req = requestWithBody("POST", badPath, badBody) req.Header.Add("Content-Type", MIMEPROTOBUF) err = ProtoBuf.Bind(req, &obj) @@ -1179,7 +1179,7 @@ func (h hook) Read([]byte) (int, error) { func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) - obj := example.Test{} + obj := protoexample.Test{} req := requestWithBody("POST", path, body) req.Body = ioutil.NopCloser(&hook{}) @@ -1187,7 +1187,7 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body err := b.Bind(req, &obj) assert.Error(t, err) - obj = example.Test{} + obj = protoexample.Test{} req = requestWithBody("POST", badPath, badBody) req.Header.Add("Content-Type", MIMEPROTOBUF) err = ProtoBuf.Bind(req, &obj) diff --git a/debug_test.go b/debug_test.go index 440173b7..ed5a6a54 100644 --- a/debug_test.go +++ b/debug_test.go @@ -72,7 +72,7 @@ func TestDebugPrintLoadTemplate(t *testing.T) { setup(&w) defer teardown() - templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./fixtures/basic/hello.tmpl")) + templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl")) debugPrintLoadTemplate(templ) assert.Regexp(t, `^\[GIN-debug\] Loaded HTML Templates \(2\): \n(\t- \n|\t- hello\.tmpl\n){2}\n`, w.String()) } diff --git a/examples/template/main.go b/examples/template/main.go index f9e611df..e20a3b98 100644 --- a/examples/template/main.go +++ b/examples/template/main.go @@ -20,7 +20,7 @@ func main() { router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) - router.LoadHTMLFiles("../../fixtures/basic/raw.tmpl") + router.LoadHTMLFiles("../../testdata/template/raw.tmpl") router.GET("/raw", func(c *gin.Context) { c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ diff --git a/gin_test.go b/gin_test.go index 3ac60577..b3cab715 100644 --- a/gin_test.go +++ b/gin_test.go @@ -30,7 +30,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool) func() { router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) - router.LoadHTMLFiles("./fixtures/basic/hello.tmpl", "./fixtures/basic/raw.tmpl") + router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") router.GET("/test", func(c *Context) { c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) }) @@ -41,7 +41,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool) func() { }) if tls { // these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1` - router.RunTLS(":9999", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem") + router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem") } else { router.Run(":8888") } @@ -59,7 +59,7 @@ func setupHTMLGlob(t *testing.T, mode string, tls bool) func() { router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) - router.LoadHTMLGlob("./fixtures/basic/*") + router.LoadHTMLGlob("./testdata/template/*") router.GET("/test", func(c *Context) { c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) }) @@ -70,7 +70,7 @@ func setupHTMLGlob(t *testing.T, mode string, tls bool) func() { }) if tls { // these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1` - router.RunTLS(":9999", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem") + router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem") } else { router.Run(":8888") } diff --git a/render/render_test.go b/render/render_test.go index 9e768850..8fad12b3 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -388,7 +388,7 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) { func TestRenderHTMLDebugFiles(t *testing.T) { w := httptest.NewRecorder() - htmlRender := HTMLDebug{Files: []string{"../fixtures/basic/hello.tmpl"}, + htmlRender := HTMLDebug{Files: []string{"../testdata/template/hello.tmpl"}, Glob: "", Delims: Delims{Left: "{[{", Right: "}]}"}, FuncMap: nil, @@ -407,7 +407,7 @@ func TestRenderHTMLDebugFiles(t *testing.T) { func TestRenderHTMLDebugGlob(t *testing.T) { w := httptest.NewRecorder() htmlRender := HTMLDebug{Files: nil, - Glob: "../fixtures/basic/hello*", + Glob: "../testdata/template/hello*", Delims: Delims{Left: "{[{", Right: "}]}"}, FuncMap: nil, } diff --git a/fixtures/testdata/cert.pem b/testdata/certificate/cert.pem similarity index 100% rename from fixtures/testdata/cert.pem rename to testdata/certificate/cert.pem diff --git a/fixtures/testdata/key.pem b/testdata/certificate/key.pem similarity index 100% rename from fixtures/testdata/key.pem rename to testdata/certificate/key.pem diff --git a/binding/example/test.pb.go b/testdata/protoexample/test.pb.go similarity index 94% rename from binding/example/test.pb.go rename to testdata/protoexample/test.pb.go index 3de8444f..21997ca1 100644 --- a/binding/example/test.pb.go +++ b/testdata/protoexample/test.pb.go @@ -3,7 +3,7 @@ // DO NOT EDIT! /* -Package example is a generated protocol buffer package. +Package protoexample is a generated protocol buffer package. It is generated from these files: test.proto @@ -11,7 +11,7 @@ It is generated from these files: It has these top-level messages: Test */ -package example +package protoexample import proto "github.com/golang/protobuf/proto" import math "math" @@ -109,5 +109,5 @@ func (m *Test_OptionalGroup) GetRequiredField() string { } func init() { - proto.RegisterEnum("example.FOO", FOO_name, FOO_value) + proto.RegisterEnum("protoexample.FOO", FOO_name, FOO_value) } diff --git a/binding/example/test.proto b/testdata/protoexample/test.proto similarity index 90% rename from binding/example/test.proto rename to testdata/protoexample/test.proto index 8ee9800a..3e734287 100644 --- a/binding/example/test.proto +++ b/testdata/protoexample/test.proto @@ -1,4 +1,4 @@ -package example; +package protoexample; enum FOO {X=17;}; diff --git a/fixtures/basic/hello.tmpl b/testdata/template/hello.tmpl similarity index 100% rename from fixtures/basic/hello.tmpl rename to testdata/template/hello.tmpl diff --git a/fixtures/basic/raw.tmpl b/testdata/template/raw.tmpl similarity index 100% rename from fixtures/basic/raw.tmpl rename to testdata/template/raw.tmpl From 8aef947f6eeeb47872c2e5910d20258cc6ccf375 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 12 Aug 2018 22:54:22 +0200 Subject: [PATCH 27/55] docs: remove double negative in README.md (#1480) "not match neither" means that it will match. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bf1bbb49..28598baf 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ func main() { func main() { router := gin.Default() - // This handler will match /user/john but will not match neither /user/ or /user + // This handler will match /user/john but will not match /user/ or /user router.GET("/user/:name", func(c *gin.Context) { name := c.Param("name") c.String(http.StatusOK, "Hello %s", name) From f45c928a156e60e521dceea99a183f9c9cf5b2de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 14 Aug 2018 09:51:56 +0800 Subject: [PATCH 28/55] chore: use http.Status* instead of hard code (#1482) --- auth_test.go | 12 +-- benchmarks_test.go | 6 +- context_test.go | 153 ++++++++++++++------------- examples/basic/main.go | 10 +- examples/basic/main_test.go | 2 +- examples/favicon/main.go | 4 +- examples/http2/main.go | 3 +- examples/realtime-advanced/routes.go | 11 +- examples/realtime-chat/main.go | 5 +- githubapi_test.go | 2 +- logger.go | 7 +- logger_test.go | 17 +-- middleware_test.go | 33 +++--- recovery_test.go | 7 +- render/redirect.go | 2 + render/render_test.go | 4 +- response_writer_test.go | 20 ++-- routergroup_test.go | 7 +- routes_test.go | 88 +++++++-------- utils_test.go | 8 +- 20 files changed, 209 insertions(+), 192 deletions(-) diff --git a/auth_test.go b/auth_test.go index dc8523b0..ab7e94be 100644 --- a/auth_test.go +++ b/auth_test.go @@ -93,7 +93,7 @@ func TestBasicAuthSucceed(t *testing.T) { router := New() router.Use(BasicAuth(accounts)) router.GET("/login", func(c *Context) { - c.String(200, c.MustGet(AuthUserKey).(string)) + c.String(http.StatusOK, c.MustGet(AuthUserKey).(string)) }) w := httptest.NewRecorder() @@ -101,7 +101,7 @@ func TestBasicAuthSucceed(t *testing.T) { req.Header.Set("Authorization", authorizationHeader("admin", "password")) router.ServeHTTP(w, req) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "admin", w.Body.String()) } @@ -112,7 +112,7 @@ func TestBasicAuth401(t *testing.T) { router.Use(BasicAuth(accounts)) router.GET("/login", func(c *Context) { called = true - c.String(200, c.MustGet(AuthUserKey).(string)) + c.String(http.StatusOK, c.MustGet(AuthUserKey).(string)) }) w := httptest.NewRecorder() @@ -121,7 +121,7 @@ func TestBasicAuth401(t *testing.T) { router.ServeHTTP(w, req) assert.False(t, called) - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "Basic realm=\"Authorization Required\"", w.HeaderMap.Get("WWW-Authenticate")) } @@ -132,7 +132,7 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) { router.Use(BasicAuthForRealm(accounts, "My Custom \"Realm\"")) router.GET("/login", func(c *Context) { called = true - c.String(200, c.MustGet(AuthUserKey).(string)) + c.String(http.StatusOK, c.MustGet(AuthUserKey).(string)) }) w := httptest.NewRecorder() @@ -141,6 +141,6 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) { router.ServeHTTP(w, req) assert.False(t, called) - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.HeaderMap.Get("WWW-Authenticate")) } diff --git a/benchmarks_test.go b/benchmarks_test.go index e7970034..0b3f82df 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -54,7 +54,7 @@ func BenchmarkOneRouteJSON(B *testing.B) { Status string `json:"status"` }{"ok"} router.GET("/json", func(c *Context) { - c.JSON(200, data) + c.JSON(http.StatusOK, data) }) runRequest(B, router, "GET", "/json") } @@ -66,7 +66,7 @@ func BenchmarkOneRouteHTML(B *testing.B) { router.SetHTMLTemplate(t) router.GET("/html", func(c *Context) { - c.HTML(200, "index", "hola") + c.HTML(http.StatusOK, "index", "hola") }) runRequest(B, router, "GET", "/html") } @@ -82,7 +82,7 @@ func BenchmarkOneRouteSet(B *testing.B) { func BenchmarkOneRouteString(B *testing.B) { router := New() router.GET("/text", func(c *Context) { - c.String(200, "this is a plain text") + c.String(http.StatusOK, "this is a plain text") }) runRequest(B, router, "GET", "/text") } diff --git a/context_test.go b/context_test.go index 0185507f..99d5267d 100644 --- a/context_test.go +++ b/context_test.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "html/template" + "io" "mime/multipart" "net/http" "net/http/httptest" @@ -21,7 +22,6 @@ import ( "github.com/gin-gonic/gin/binding" "github.com/stretchr/testify/assert" "golang.org/x/net/context" - "io" ) var _ context.Context = &Context{} @@ -585,10 +585,11 @@ func TestContextGetCookie(t *testing.T) { } func TestContextBodyAllowedForStatus(t *testing.T) { + // todo(thinkerou): go1.6 not support StatusProcessing assert.False(t, false, bodyAllowedForStatus(102)) - assert.False(t, false, bodyAllowedForStatus(204)) - assert.False(t, false, bodyAllowedForStatus(304)) - assert.True(t, true, bodyAllowedForStatus(500)) + assert.False(t, false, bodyAllowedForStatus(http.StatusNoContent)) + assert.False(t, false, bodyAllowedForStatus(http.StatusNotModified)) + assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError)) } type TestPanicRender struct { @@ -619,9 +620,9 @@ func TestContextRenderJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.JSON(201, H{"foo": "bar"}) + c.JSON(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -633,9 +634,9 @@ func TestContextRenderJSONP(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("GET", "http://example.com/?callback=x", nil) - c.JSONP(201, H{"foo": "bar"}) + c.JSONP(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "x({\"foo\":\"bar\"})", w.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -647,9 +648,9 @@ func TestContextRenderJSONPWithoutCallback(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("GET", "http://example.com", nil) - c.JSONP(201, H{"foo": "bar"}) + c.JSONP(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -659,9 +660,9 @@ func TestContextRenderNoContentJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.JSON(204, H{"foo": "bar"}) + c.JSON(http.StatusNoContent, H{"foo": "bar"}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -673,9 +674,9 @@ func TestContextRenderAPIJSON(t *testing.T) { c, _ := CreateTestContext(w) c.Header("Content-Type", "application/vnd.api+json") - c.JSON(201, H{"foo": "bar"}) + c.JSON(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/vnd.api+json", w.HeaderMap.Get("Content-Type")) } @@ -686,9 +687,9 @@ func TestContextRenderNoContentAPIJSON(t *testing.T) { c, _ := CreateTestContext(w) c.Header("Content-Type", "application/vnd.api+json") - c.JSON(204, H{"foo": "bar"}) + c.JSON(http.StatusNoContent, H{"foo": "bar"}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/vnd.api+json") } @@ -699,9 +700,9 @@ func TestContextRenderIndentedJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.IndentedJSON(201, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) + c.IndentedJSON(http.StatusCreated, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -711,9 +712,9 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.IndentedJSON(204, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) + c.IndentedJSON(http.StatusNoContent, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -725,9 +726,9 @@ func TestContextRenderSecureJSON(t *testing.T) { c, router := CreateTestContext(w) router.SecureJsonPrefix("&&&START&&&") - c.SecureJSON(201, []string{"foo", "bar"}) + c.SecureJSON(http.StatusCreated, []string{"foo", "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "&&&START&&&[\"foo\",\"bar\"]", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -737,9 +738,9 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.SecureJSON(204, []string{"foo", "bar"}) + c.SecureJSON(http.StatusNoContent, []string{"foo", "bar"}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -764,9 +765,9 @@ func TestContextRenderHTML(t *testing.T) { templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) router.SetHTMLTemplate(templ) - c.HTML(201, "t", H{"name": "alexandernyquist"}) + c.HTML(http.StatusCreated, "t", H{"name": "alexandernyquist"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -788,9 +789,9 @@ func TestContextRenderHTML2(t *testing.T) { assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", b.String()) - c.HTML(201, "t", H{"name": "alexandernyquist"}) + c.HTML(http.StatusCreated, "t", H{"name": "alexandernyquist"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -802,9 +803,9 @@ func TestContextRenderNoContentHTML(t *testing.T) { templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) router.SetHTMLTemplate(templ) - c.HTML(204, "t", H{"name": "alexandernyquist"}) + c.HTML(http.StatusNoContent, "t", H{"name": "alexandernyquist"}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -815,9 +816,9 @@ func TestContextRenderXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.XML(201, H{"foo": "bar"}) + c.XML(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "bar", w.Body.String()) assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -827,9 +828,9 @@ func TestContextRenderNoContentXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.XML(204, H{"foo": "bar"}) + c.XML(http.StatusNoContent, H{"foo": "bar"}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -840,9 +841,9 @@ func TestContextRenderString(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.String(201, "test %s %d", "string", 2) + c.String(http.StatusCreated, "test %s %d", "string", 2) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "test string 2", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -852,9 +853,9 @@ func TestContextRenderNoContentString(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.String(204, "test %s %d", "string", 2) + c.String(http.StatusNoContent, "test %s %d", "string", 2) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -866,9 +867,9 @@ func TestContextRenderHTMLString(t *testing.T) { c, _ := CreateTestContext(w) c.Header("Content-Type", "text/html; charset=utf-8") - c.String(201, "%s %d", "string", 3) + c.String(http.StatusCreated, "%s %d", "string", 3) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "string 3", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -879,9 +880,9 @@ func TestContextRenderNoContentHTMLString(t *testing.T) { c, _ := CreateTestContext(w) c.Header("Content-Type", "text/html; charset=utf-8") - c.String(204, "%s %d", "string", 3) + c.String(http.StatusNoContent, "%s %d", "string", 3) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -892,9 +893,9 @@ func TestContextRenderData(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Data(201, "text/csv", []byte(`foo,bar`)) + c.Data(http.StatusCreated, "text/csv", []byte(`foo,bar`)) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "foo,bar", w.Body.String()) assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type")) } @@ -904,9 +905,9 @@ func TestContextRenderNoContentData(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Data(204, "text/csv", []byte(`foo,bar`)) + c.Data(http.StatusNoContent, "text/csv", []byte(`foo,bar`)) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type")) } @@ -935,7 +936,7 @@ func TestContextRenderFile(t *testing.T) { c.Request, _ = http.NewRequest("GET", "/", nil) c.File("./gin.go") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "func New() *Engine {") assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -946,9 +947,9 @@ func TestContextRenderYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.YAML(201, H{"foo": "bar"}) + c.YAML(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "foo: bar\n", w.Body.String()) assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -978,9 +979,9 @@ func TestContextRenderRedirectWithRelativePath(t *testing.T) { assert.Panics(t, func() { c.Redirect(299, "/new_path") }) assert.Panics(t, func() { c.Redirect(309, "/new_path") }) - c.Redirect(301, "/path") + c.Redirect(http.StatusMovedPermanently, "/path") c.Writer.WriteHeaderNow() - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) assert.Equal(t, "/path", w.Header().Get("Location")) } @@ -989,10 +990,10 @@ func TestContextRenderRedirectWithAbsolutePath(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "http://example.com", nil) - c.Redirect(302, "http://google.com") + c.Redirect(http.StatusFound, "http://google.com") c.Writer.WriteHeaderNow() - assert.Equal(t, 302, w.Code) + assert.Equal(t, http.StatusFound, w.Code) assert.Equal(t, "http://google.com", w.Header().Get("Location")) } @@ -1001,21 +1002,23 @@ func TestContextRenderRedirectWith201(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "http://example.com", nil) - c.Redirect(201, "/resource") + c.Redirect(http.StatusCreated, "/resource") c.Writer.WriteHeaderNow() - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "/resource", w.Header().Get("Location")) } func TestContextRenderRedirectAll(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "http://example.com", nil) - assert.Panics(t, func() { c.Redirect(200, "/resource") }) - assert.Panics(t, func() { c.Redirect(202, "/resource") }) + assert.Panics(t, func() { c.Redirect(http.StatusOK, "/resource") }) + assert.Panics(t, func() { c.Redirect(http.StatusAccepted, "/resource") }) assert.Panics(t, func() { c.Redirect(299, "/resource") }) assert.Panics(t, func() { c.Redirect(309, "/resource") }) - assert.NotPanics(t, func() { c.Redirect(300, "/resource") }) + assert.NotPanics(t, func() { c.Redirect(http.StatusMultipleChoices, "/resource") }) + // todo(thinkerou): go1.6 not support StatusPermanentRedirect(308) + // when we upgrade go version we can use http.StatusPermanentRedirect assert.NotPanics(t, func() { c.Redirect(308, "/resource") }) } @@ -1024,12 +1027,12 @@ func TestContextNegotiationWithJSON(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "", nil) - c.Negotiate(200, Negotiate{ + c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEJSON, MIMEXML}, Data: H{"foo": "bar"}, }) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -1039,12 +1042,12 @@ func TestContextNegotiationWithXML(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "", nil) - c.Negotiate(200, Negotiate{ + c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEXML, MIMEJSON}, Data: H{"foo": "bar"}, }) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "bar", w.Body.String()) assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -1056,13 +1059,13 @@ func TestContextNegotiationWithHTML(t *testing.T) { templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) router.SetHTMLTemplate(templ) - c.Negotiate(200, Negotiate{ + c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEHTML}, Data: H{"name": "gin"}, HTMLName: "t", }) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "Hello gin", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -1072,11 +1075,11 @@ func TestContextNegotiationNotSupport(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "", nil) - c.Negotiate(200, Negotiate{ + c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEPOSTForm}, }) - assert.Equal(t, 406, w.Code) + assert.Equal(t, http.StatusNotAcceptable, w.Code) assert.Equal(t, c.index, abortIndex) assert.True(t, c.IsAborted()) } @@ -1134,11 +1137,11 @@ func TestContextAbortWithStatus(t *testing.T) { c, _ := CreateTestContext(w) c.index = 4 - c.AbortWithStatus(401) + c.AbortWithStatus(http.StatusUnauthorized) assert.Equal(t, abortIndex, c.index) - assert.Equal(t, 401, c.Writer.Status()) - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, c.Writer.Status()) + assert.Equal(t, http.StatusUnauthorized, w.Code) assert.True(t, c.IsAborted()) } @@ -1156,11 +1159,11 @@ func TestContextAbortWithStatusJSON(t *testing.T) { in.Bar = "barValue" in.Foo = "fooValue" - c.AbortWithStatusJSON(415, in) + c.AbortWithStatusJSON(http.StatusUnsupportedMediaType, in) assert.Equal(t, abortIndex, c.index) - assert.Equal(t, 415, c.Writer.Status()) - assert.Equal(t, 415, w.Code) + assert.Equal(t, http.StatusUnsupportedMediaType, c.Writer.Status()) + assert.Equal(t, http.StatusUnsupportedMediaType, w.Code) assert.True(t, c.IsAborted()) contentType := w.Header().Get("Content-Type") @@ -1223,9 +1226,9 @@ func TestContextAbortWithError(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.AbortWithError(401, errors.New("bad input")).SetMeta("some input") + c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, abortIndex, c.index) assert.True(t, c.IsAborted()) } @@ -1333,7 +1336,7 @@ func TestContextBadAutoBind(t *testing.T) { assert.Empty(t, obj.Bar) assert.Empty(t, obj.Foo) - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) assert.True(t, c.IsAborted()) } diff --git a/examples/basic/main.go b/examples/basic/main.go index 473c6a09..48fa7bba 100644 --- a/examples/basic/main.go +++ b/examples/basic/main.go @@ -1,6 +1,8 @@ package main import ( + "net/http" + "github.com/gin-gonic/gin" ) @@ -13,7 +15,7 @@ func setupRouter() *gin.Engine { // Ping test r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") + c.String(http.StatusOK, "pong") }) // Get user value @@ -21,9 +23,9 @@ func setupRouter() *gin.Engine { user := c.Params.ByName("name") value, ok := DB[user] if ok { - c.JSON(200, gin.H{"user": user, "value": value}) + c.JSON(http.StatusOK, gin.H{"user": user, "value": value}) } else { - c.JSON(200, gin.H{"user": user, "status": "no value"}) + c.JSON(http.StatusOK, gin.H{"user": user, "status": "no value"}) } }) @@ -49,7 +51,7 @@ func setupRouter() *gin.Engine { if c.Bind(&json) == nil { DB[user] = json.Value - c.JSON(200, gin.H{"status": "ok"}) + c.JSON(http.StatusOK, gin.H{"status": "ok"}) } }) diff --git a/examples/basic/main_test.go b/examples/basic/main_test.go index 61203d66..5eb85240 100644 --- a/examples/basic/main_test.go +++ b/examples/basic/main_test.go @@ -15,6 +15,6 @@ func TestPingRoute(t *testing.T) { req, _ := http.NewRequest("GET", "/ping", nil) router.ServeHTTP(w, req) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "pong", w.Body.String()) } diff --git a/examples/favicon/main.go b/examples/favicon/main.go index 5ad39331..d32ca098 100644 --- a/examples/favicon/main.go +++ b/examples/favicon/main.go @@ -1,6 +1,8 @@ package main import ( + "net/http" + "github.com/gin-gonic/gin" "github.com/thinkerou/favicon" ) @@ -9,7 +11,7 @@ func main() { app := gin.Default() app.Use(favicon.New("./favicon.ico")) app.GET("/ping", func(c *gin.Context) { - c.String(200, "Hello favicon.") + c.String(http.StatusOK, "Hello favicon.") }) app.Run(":8080") } diff --git a/examples/http2/main.go b/examples/http2/main.go index 07df01e2..6598a4c9 100644 --- a/examples/http2/main.go +++ b/examples/http2/main.go @@ -3,6 +3,7 @@ package main import ( "html/template" "log" + "net/http" "os" "github.com/gin-gonic/gin" @@ -27,7 +28,7 @@ func main() { r.SetHTMLTemplate(html) r.GET("/welcome", func(c *gin.Context) { - c.HTML(200, "https", gin.H{ + c.HTML(http.StatusOK, "https", gin.H{ "status": "success", }) }) diff --git a/examples/realtime-advanced/routes.go b/examples/realtime-advanced/routes.go index 86da9bea..03c69910 100644 --- a/examples/realtime-advanced/routes.go +++ b/examples/realtime-advanced/routes.go @@ -4,6 +4,7 @@ import ( "fmt" "html" "io" + "net/http" "strings" "time" @@ -21,12 +22,12 @@ func rateLimit(c *gin.Context) { fmt.Println("ip blocked") } c.Abort() - c.String(503, "you were automatically banned :)") + c.String(http.StatusServiceUnavailable, "you were automatically banned :)") } } func index(c *gin.Context) { - c.Redirect(301, "/room/hn") + c.Redirect(http.StatusMovedPermanently, "/room/hn") } func roomGET(c *gin.Context) { @@ -38,7 +39,7 @@ func roomGET(c *gin.Context) { if len(nick) > 13 { nick = nick[0:12] + "..." } - c.HTML(200, "room_login.templ.html", gin.H{ + c.HTML(http.StatusOK, "room_login.templ.html", gin.H{ "roomid": roomid, "nick": nick, "timestamp": time.Now().Unix(), @@ -55,7 +56,7 @@ func roomPOST(c *gin.Context) { validMessage := len(message) > 1 && len(message) < 200 validNick := len(nick) > 1 && len(nick) < 14 if !validMessage || !validNick { - c.JSON(400, gin.H{ + c.JSON(http.StatusBadRequest, gin.H{ "status": "failed", "error": "the message or nickname is too long", }) @@ -68,7 +69,7 @@ func roomPOST(c *gin.Context) { } messages.Add("inbound", 1) room(roomid).Submit(post) - c.JSON(200, post) + c.JSON(http.StatusOK, post) } func streamRoom(c *gin.Context) { diff --git a/examples/realtime-chat/main.go b/examples/realtime-chat/main.go index e4b55a0f..5741fcba 100644 --- a/examples/realtime-chat/main.go +++ b/examples/realtime-chat/main.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "math/rand" + "net/http" "github.com/gin-gonic/gin" ) @@ -34,7 +35,7 @@ func stream(c *gin.Context) { func roomGET(c *gin.Context) { roomid := c.Param("roomid") userid := fmt.Sprint(rand.Int31()) - c.HTML(200, "chat_room", gin.H{ + c.HTML(http.StatusOK, "chat_room", gin.H{ "roomid": roomid, "userid": userid, }) @@ -46,7 +47,7 @@ func roomPOST(c *gin.Context) { message := c.PostForm("message") room(roomid).Submit(userid + ": " + message) - c.JSON(200, gin.H{ + c.JSON(http.StatusOK, gin.H{ "status": "success", "message": message, }) diff --git a/githubapi_test.go b/githubapi_test.go index a08c264d..f631035d 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -293,7 +293,7 @@ func githubConfigRouter(router *Engine) { for _, param := range c.Params { output[param.Key] = param.Value } - c.JSON(200, output) + c.JSON(http.StatusOK, output) }) } } diff --git a/logger.go b/logger.go index c679c787..1a8df601 100644 --- a/logger.go +++ b/logger.go @@ -7,6 +7,7 @@ package gin import ( "fmt" "io" + "net/http" "os" "time" @@ -118,11 +119,11 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { func colorForStatus(code int) string { switch { - case code >= 200 && code < 300: + case code >= http.StatusOK && code < http.StatusMultipleChoices: return green - case code >= 300 && code < 400: + case code >= http.StatusMultipleChoices && code < http.StatusBadRequest: return white - case code >= 400 && code < 500: + case code >= http.StatusBadRequest && code < http.StatusInternalServerError: return yellow default: return red diff --git a/logger_test.go b/logger_test.go index 74a9659c..7dbbf7b1 100644 --- a/logger_test.go +++ b/logger_test.go @@ -7,6 +7,7 @@ package gin import ( "bytes" "errors" + "net/http" "testing" "github.com/stretchr/testify/assert" @@ -93,9 +94,9 @@ func TestColorForMethod(t *testing.T) { } func TestColorForStatus(t *testing.T) { - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(200), "2xx should be green") - assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(301), "3xx should be white") - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForStatus(404), "4xx should be yellow") + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(http.StatusOK), "2xx should be green") + assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(http.StatusMovedPermanently), "3xx should be white") + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForStatus(http.StatusNotFound), "4xx should be yellow") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForStatus(2), "other things should be red") } @@ -106,23 +107,23 @@ func TestErrorLogger(t *testing.T) { c.Error(errors.New("this is an error")) }) router.GET("/abort", func(c *Context) { - c.AbortWithError(401, errors.New("no authorized")) + c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) }) router.GET("/print", func(c *Context) { c.Error(errors.New("this is an error")) - c.String(500, "hola!") + c.String(http.StatusInternalServerError, "hola!") }) w := performRequest(router, "GET", "/error") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String()) w = performRequest(router, "GET", "/abort") - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String()) w = performRequest(router, "GET", "/print") - assert.Equal(t, 500, w.Code) + assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String()) } diff --git a/middleware_test.go b/middleware_test.go index aa6a37a8..983ad933 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -6,6 +6,7 @@ package gin import ( "errors" + "net/http" "strings" "testing" @@ -37,7 +38,7 @@ func TestMiddlewareGeneralCase(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "ACDB", signature) } @@ -73,7 +74,7 @@ func TestMiddlewareNoRoute(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, "ACEGHFDB", signature) } @@ -110,7 +111,7 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 405, w.Code) + assert.Equal(t, http.StatusMethodNotAllowed, w.Code) assert.Equal(t, "ACEGHFDB", signature) } @@ -147,7 +148,7 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, "AC X DB", signature) } @@ -159,7 +160,7 @@ func TestMiddlewareAbort(t *testing.T) { }) router.Use(func(c *Context) { signature += "C" - c.AbortWithStatus(401) + c.AbortWithStatus(http.StatusUnauthorized) c.Next() signature += "D" }) @@ -173,7 +174,7 @@ func TestMiddlewareAbort(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "ACD", signature) } @@ -183,7 +184,7 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) { router.Use(func(c *Context) { signature += "A" c.Next() - c.AbortWithStatus(410) + c.AbortWithStatus(http.StatusGone) signature += "B" }) @@ -195,7 +196,7 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 410, w.Code) + assert.Equal(t, http.StatusGone, w.Code) assert.Equal(t, "ACB", signature) } @@ -207,7 +208,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) { router := New() router.Use(func(context *Context) { signature += "A" - context.AbortWithError(500, errors.New("foo")) + context.AbortWithError(http.StatusInternalServerError, errors.New("foo")) }) router.Use(func(context *Context) { signature += "B" @@ -218,25 +219,25 @@ func TestMiddlewareFailHandlersChain(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 500, w.Code) + assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "A", signature) } func TestMiddlewareWrite(t *testing.T) { router := New() router.Use(func(c *Context) { - c.String(400, "hola\n") + c.String(http.StatusBadRequest, "hola\n") }) router.Use(func(c *Context) { - c.XML(400, H{"foo": "bar"}) + c.XML(http.StatusBadRequest, H{"foo": "bar"}) }) router.Use(func(c *Context) { - c.JSON(400, H{"foo": "bar"}) + c.JSON(http.StatusBadRequest, H{"foo": "bar"}) }) router.GET("/", func(c *Context) { - c.JSON(400, H{"foo": "bar"}) + c.JSON(http.StatusBadRequest, H{"foo": "bar"}) }, func(c *Context) { - c.Render(400, sse.Event{ + c.Render(http.StatusBadRequest, sse.Event{ Event: "test", Data: "message", }) @@ -244,6 +245,6 @@ func TestMiddlewareWrite(t *testing.T) { w := performRequest(router, "GET", "/") - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, strings.Replace("hola\nbar{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) } diff --git a/recovery_test.go b/recovery_test.go index fa065b13..53f4a071 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -6,6 +6,7 @@ package gin import ( "bytes" + "net/http" "testing" "github.com/stretchr/testify/assert" @@ -22,7 +23,7 @@ func TestPanicInHandler(t *testing.T) { // RUN w := performRequest(router, "GET", "/recovery") // TEST - assert.Equal(t, 500, w.Code) + assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") assert.Contains(t, buffer.String(), "TestPanicInHandler") @@ -33,13 +34,13 @@ func TestPanicWithAbort(t *testing.T) { router := New() router.Use(RecoveryWithWriter(nil)) router.GET("/recovery", func(c *Context) { - c.AbortWithStatus(400) + c.AbortWithStatus(http.StatusBadRequest) panic("Oupps, Houston, we have a problem") }) // RUN w := performRequest(router, "GET", "/recovery") // TEST - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) } func TestSource(t *testing.T) { diff --git a/render/redirect.go b/render/redirect.go index f874a351..a0634f5a 100644 --- a/render/redirect.go +++ b/render/redirect.go @@ -16,6 +16,8 @@ type Redirect struct { } func (r Redirect) Render(w http.ResponseWriter) error { + // todo(thinkerou): go1.6 not support StatusPermanentRedirect(308) + // when we upgrade go version we can use http.StatusPermanentRedirect if (r.Code < 300 || r.Code > 308) && r.Code != 201 { panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code)) } diff --git a/render/render_test.go b/render/render_test.go index 8fad12b3..47abf262 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -286,7 +286,7 @@ func TestRenderRedirect(t *testing.T) { assert.NoError(t, err) data1 := Redirect{ - Code: 301, + Code: http.StatusMovedPermanently, Request: req, Location: "/new/location", } @@ -296,7 +296,7 @@ func TestRenderRedirect(t *testing.T) { assert.NoError(t, err) data2 := Redirect{ - Code: 200, + Code: http.StatusOK, Request: req, Location: "/new/location", } diff --git a/response_writer_test.go b/response_writer_test.go index fcc48b3b..cc5a89dc 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -35,10 +35,10 @@ func TestResponseWriterReset(t *testing.T) { writer.reset(testWritter) assert.Equal(t, -1, writer.size) - assert.Equal(t, 200, writer.status) + assert.Equal(t, http.StatusOK, writer.status) assert.Equal(t, testWritter, writer.ResponseWriter) assert.Equal(t, -1, w.Size()) - assert.Equal(t, 200, w.Status()) + assert.Equal(t, http.StatusOK, w.Status()) assert.False(t, w.Written()) } @@ -48,13 +48,13 @@ func TestResponseWriterWriteHeader(t *testing.T) { writer.reset(testWritter) w := ResponseWriter(writer) - w.WriteHeader(300) + w.WriteHeader(http.StatusMultipleChoices) assert.False(t, w.Written()) - assert.Equal(t, 300, w.Status()) - assert.NotEqual(t, 300, testWritter.Code) + assert.Equal(t, http.StatusMultipleChoices, w.Status()) + assert.NotEqual(t, http.StatusMultipleChoices, testWritter.Code) w.WriteHeader(-1) - assert.Equal(t, 300, w.Status()) + assert.Equal(t, http.StatusMultipleChoices, w.Status()) } func TestResponseWriterWriteHeadersNow(t *testing.T) { @@ -63,12 +63,12 @@ func TestResponseWriterWriteHeadersNow(t *testing.T) { writer.reset(testWritter) w := ResponseWriter(writer) - w.WriteHeader(300) + w.WriteHeader(http.StatusMultipleChoices) w.WriteHeaderNow() assert.True(t, w.Written()) assert.Equal(t, 0, w.Size()) - assert.Equal(t, 300, testWritter.Code) + assert.Equal(t, http.StatusMultipleChoices, testWritter.Code) writer.size = 10 w.WriteHeaderNow() @@ -84,8 +84,8 @@ func TestResponseWriterWrite(t *testing.T) { n, err := w.Write([]byte("hola")) assert.Equal(t, 4, n) assert.Equal(t, 4, w.Size()) - assert.Equal(t, 200, w.Status()) - assert.Equal(t, 200, testWritter.Code) + assert.Equal(t, http.StatusOK, w.Status()) + assert.Equal(t, http.StatusOK, testWritter.Code) assert.Equal(t, "hola", testWritter.Body.String()) assert.NoError(t, err) diff --git a/routergroup_test.go b/routergroup_test.go index a362e23d..ce3d54a2 100644 --- a/routergroup_test.go +++ b/routergroup_test.go @@ -5,6 +5,7 @@ package gin import ( + "net/http" "testing" "github.com/stretchr/testify/assert" @@ -50,7 +51,7 @@ func performRequestInGroup(t *testing.T, method string) { assert.Equal(t, "/v1/login/", login.BasePath()) handler := func(c *Context) { - c.String(400, "the method was %s and index %d", c.Request.Method, c.index) + c.String(http.StatusBadRequest, "the method was %s and index %d", c.Request.Method, c.index) } switch method { @@ -80,11 +81,11 @@ func performRequestInGroup(t *testing.T, method string) { } w := performRequest(router, method, "/v1/login/test") - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "the method was "+method+" and index 3", w.Body.String()) w = performRequest(router, method, "/v1/test") - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "the method was "+method+" and index 1", w.Body.String()) } diff --git a/routes_test.go b/routes_test.go index a2389f9b..23e749e2 100644 --- a/routes_test.go +++ b/routes_test.go @@ -80,20 +80,20 @@ func testRouteNotOK2(method string, t *testing.T) { func TestRouterMethod(t *testing.T) { router := New() router.PUT("/hey2", func(c *Context) { - c.String(200, "sup2") + c.String(http.StatusOK, "sup2") }) router.PUT("/hey", func(c *Context) { - c.String(200, "called") + c.String(http.StatusOK, "called") }) router.PUT("/hey3", func(c *Context) { - c.String(200, "sup3") + c.String(http.StatusOK, "sup3") }) w := performRequest(router, "PUT", "/hey") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "called", w.Body.String()) } @@ -144,42 +144,42 @@ func TestRouteRedirectTrailingSlash(t *testing.T) { w := performRequest(router, "GET", "/path/") assert.Equal(t, "/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, "GET", "/path2") assert.Equal(t, "/path2/", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, "POST", "/path3/") assert.Equal(t, "/path3", w.Header().Get("Location")) - assert.Equal(t, 307, w.Code) + assert.Equal(t, http.StatusTemporaryRedirect, w.Code) w = performRequest(router, "PUT", "/path4") assert.Equal(t, "/path4/", w.Header().Get("Location")) - assert.Equal(t, 307, w.Code) + assert.Equal(t, http.StatusTemporaryRedirect, w.Code) w = performRequest(router, "GET", "/path") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) w = performRequest(router, "GET", "/path2/") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) w = performRequest(router, "POST", "/path3") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) w = performRequest(router, "PUT", "/path4/") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) router.RedirectTrailingSlash = false w = performRequest(router, "GET", "/path/") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) w = performRequest(router, "GET", "/path2") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) w = performRequest(router, "POST", "/path3/") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) w = performRequest(router, "PUT", "/path4") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) } func TestRouteRedirectFixedPath(t *testing.T) { @@ -194,19 +194,19 @@ func TestRouteRedirectFixedPath(t *testing.T) { w := performRequest(router, "GET", "/PATH") assert.Equal(t, "/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, "GET", "/path2") assert.Equal(t, "/Path2", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, "POST", "/path3") assert.Equal(t, "/PATH3", w.Header().Get("Location")) - assert.Equal(t, 307, w.Code) + assert.Equal(t, http.StatusTemporaryRedirect, w.Code) w = performRequest(router, "POST", "/path4") assert.Equal(t, "/Path4/", w.Header().Get("Location")) - assert.Equal(t, 307, w.Code) + assert.Equal(t, http.StatusTemporaryRedirect, w.Code) } // TestContextParamsGet tests that a parameter can be parsed from the URL. @@ -236,7 +236,7 @@ func TestRouteParamsByName(t *testing.T) { w := performRequest(router, "GET", "/test/john/smith/is/super/great") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "john", name) assert.Equal(t, "smith", lastName) assert.Equal(t, "/is/super/great", wild) @@ -265,7 +265,7 @@ func TestRouteStaticFile(t *testing.T) { w2 := performRequest(router, "GET", "/result") assert.Equal(t, w, w2) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "Gin Web Framework", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) @@ -273,7 +273,7 @@ func TestRouteStaticFile(t *testing.T) { w4 := performRequest(router, "HEAD", "/result") assert.Equal(t, w3, w4) - assert.Equal(t, 200, w3.Code) + assert.Equal(t, http.StatusOK, w3.Code) } // TestHandleStaticDir - ensure the root/sub dir handles properly @@ -283,7 +283,7 @@ func TestRouteStaticListingDir(t *testing.T) { w := performRequest(router, "GET", "/") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "gin.go") assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -295,7 +295,7 @@ func TestRouteStaticNoListing(t *testing.T) { w := performRequest(router, "GET", "/") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) assert.NotContains(t, w.Body.String(), "gin.go") } @@ -310,7 +310,7 @@ func TestRouterMiddlewareAndStatic(t *testing.T) { w := performRequest(router, "GET", "/gin.go") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "package gin") assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.NotEqual(t, w.HeaderMap.Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST") @@ -348,14 +348,14 @@ func TestRouteNotAllowedDisabled(t *testing.T) { router.HandleMethodNotAllowed = false router.POST("/path", func(c *Context) {}) w := performRequest(router, "GET", "/path") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) router.NoMethod(func(c *Context) { c.String(http.StatusTeapot, "responseText") }) w = performRequest(router, "GET", "/path") assert.Equal(t, "404 page not found", w.Body.String()) - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) } func TestRouterNotFound(t *testing.T) { @@ -370,20 +370,20 @@ func TestRouterNotFound(t *testing.T) { code int location string }{ - {"/path/", 301, "/path"}, // TSR -/ - {"/dir", 301, "/dir/"}, // TSR +/ - {"", 301, "/"}, // TSR +/ - {"/PATH", 301, "/path"}, // Fixed Case - {"/DIR/", 301, "/dir/"}, // Fixed Case - {"/PATH/", 301, "/path"}, // Fixed Case -/ - {"/DIR", 301, "/dir/"}, // Fixed Case +/ - {"/../path", 301, "/path"}, // CleanPath - {"/nope", 404, ""}, // NotFound + {"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/ + {"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/ + {"", http.StatusMovedPermanently, "/"}, // TSR +/ + {"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case + {"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case + {"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/ + {"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/ + {"/../path", http.StatusMovedPermanently, "/path"}, // CleanPath + {"/nope", http.StatusNotFound, ""}, // NotFound } for _, tr := range testRoutes { w := performRequest(router, "GET", tr.route) assert.Equal(t, tr.code, w.Code) - if w.Code != 404 { + if w.Code != http.StatusNotFound { assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location"))) } } @@ -391,24 +391,24 @@ func TestRouterNotFound(t *testing.T) { // Test custom not found handler var notFound bool router.NoRoute(func(c *Context) { - c.AbortWithStatus(404) + c.AbortWithStatus(http.StatusNotFound) notFound = true }) w := performRequest(router, "GET", "/nope") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) assert.True(t, notFound) // Test other method than GET (want 307 instead of 301) router.PATCH("/path", func(c *Context) {}) w = performRequest(router, "PATCH", "/path/") - assert.Equal(t, 307, w.Code) + assert.Equal(t, http.StatusTemporaryRedirect, w.Code) assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header())) // Test special case where no node for the prefix "/" exists router = New() router.GET("/a", func(c *Context) {}) w = performRequest(router, "GET", "/") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) } func TestRouteRawPath(t *testing.T) { @@ -427,7 +427,7 @@ func TestRouteRawPath(t *testing.T) { }) w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) } func TestRouteRawPathNoUnescape(t *testing.T) { @@ -447,7 +447,7 @@ func TestRouteRawPathNoUnescape(t *testing.T) { }) w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) } func TestRouteServeErrorWithWriteHeader(t *testing.T) { diff --git a/utils_test.go b/utils_test.go index 95b5de5e..9b57c57b 100644 --- a/utils_test.go +++ b/utils_test.go @@ -25,7 +25,7 @@ type testStruct struct { func (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) { assert.Equal(t.T, "POST", req.Method) assert.Equal(t.T, "/path", req.URL.Path) - w.WriteHeader(500) + w.WriteHeader(http.StatusInternalServerError) fmt.Fprint(w, "hello") } @@ -35,16 +35,16 @@ func TestWrap(t *testing.T) { router.GET("/path2", WrapF(func(w http.ResponseWriter, req *http.Request) { assert.Equal(t, "GET", req.Method) assert.Equal(t, "/path2", req.URL.Path) - w.WriteHeader(400) + w.WriteHeader(http.StatusBadRequest) fmt.Fprint(w, "hola!") })) w := performRequest(router, "POST", "/path") - assert.Equal(t, 500, w.Code) + assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "hello", w.Body.String()) w = performRequest(router, "GET", "/path2") - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "hola!", w.Body.String()) } From 6c8a9731345b6782c923054b0c973c2b4b9394a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 14 Aug 2018 11:35:13 +0800 Subject: [PATCH 29/55] add issue and pull request template explain (#1483) * add issue/pr template explain * add issue/pr template explain --- .github/ISSUE_TEMPLATE.md | 13 +++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 7 +++++++ 2 files changed, 20 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..de067ce0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,13 @@ +- With issues: + - Use the search tool before opening a new issue. + - Please provide source code and commit sha if you found a bug. + - Review existing issues and provide feedback or react to them. + +- gin version (or commit ref): +- git version: +- operating system: + +## Description + +## Screenshots + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..8630bc35 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +- With pull requests: + - Open your pull request against `master` + - Your pull request should have no more than two commits, if not you should squash them. + - It should pass all tests in the available continuous integrations systems such as TravisCI. + - You should add/modify tests to cover your proposed code changes. + - If your pull request contains a new feature, please document it on the README. + From b869fe1415e4b9eb52f247441830d502aece2d4d Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Tue, 14 Aug 2018 10:58:52 +0200 Subject: [PATCH 30/55] docs: add changelog for v1.3.0, update authors and version const (#1478) * docs: add changelog for v1.3.0, update authors and version const * add link for every referenced pull request (#1481) * docs: add changelog for v1.3.0, update authors and version const * add link for pr --- AUTHORS.md | 6 +++++- CHANGELOG.md | 24 +++++++++++++++++++++++- gin.go | 2 +- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 7ab7213d..dda19bcf 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,8 +1,12 @@ List of all the awesome people working to make Gin the best Web Framework in Go. +## gin 1.x series authors + +**Gin Core Team:** Bo-Yi Wu (@appleboy), 田欧 (@thinkerou), Javier Provecho (@javierprovecho) + ## gin 0.x series authors -**Maintainer:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho) +**Maintainers:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho) People and companies, who have contributed, in alphabetical order. diff --git a/CHANGELOG.md b/CHANGELOG.md index ee485ec3..e6a108ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,28 @@ # CHANGELOG -### Gin 1.2 +### Gin 1.3.0 + +- [NEW] Add [`func (*Context) QueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.QueryMap), [`func (*Context) GetQueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetQueryMap), [`func (*Context) PostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.PostFormMap) and [`func (*Context) GetPostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetPostFormMap) to support `type map[string]string` as query string or form parameters, see [#1383](https://github.com/gin-gonic/gin/pull/1383) +- [NEW] Add [`func (*Context) AsciiJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.AsciiJSON), see [#1358](https://github.com/gin-gonic/gin/pull/1358) +- [NEW] Add `Pusher()` in [`type ResponseWriter`](https://godoc.org/github.com/gin-gonic/gin#ResponseWriter) for supporting http2 push, see [#1273](https://github.com/gin-gonic/gin/pull/1273) +- [NEW] Add [`func (*Context) DataFromReader`](https://godoc.org/github.com/gin-gonic/gin#Context.DataFromReader) for serving dynamic data, see [#1304](https://github.com/gin-gonic/gin/pull/1304) +- [NEW] Add [`func (*Context) ShouldBindBodyWith`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindBodyWith) allowing to call binding multiple times, see [#1341](https://github.com/gin-gonic/gin/pull/1341) +- [NEW] Support pointers in form binding, see [#1336](https://github.com/gin-gonic/gin/pull/1336) +- [NEW] Add [`func (*Context) JSONP`](https://godoc.org/github.com/gin-gonic/gin#Context.JSONP), see [#1333](https://github.com/gin-gonic/gin/pull/1333) +- [NEW] Support default value in form binding, see [#1138](https://github.com/gin-gonic/gin/pull/1138) +- [NEW] Expose validator engine in [`type StructValidator`](https://godoc.org/github.com/gin-gonic/gin/binding#StructValidator), see [#1277](https://github.com/gin-gonic/gin/pull/1277) +- [NEW] Add [`func (*Context) ShouldBind`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBind), [`func (*Context) ShouldBindQuery`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindQuery) and [`func (*Context) ShouldBindJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindJSON), see [#1047](https://github.com/gin-gonic/gin/pull/1047) +- [NEW] Add support for `time.Time` location in form binding, see [#1117](https://github.com/gin-gonic/gin/pull/1117) +- [NEW] Add [`func (*Context) BindQuery`](https://godoc.org/github.com/gin-gonic/gin#Context.BindQuery), see [#1029](https://github.com/gin-gonic/gin/pull/1029) +- [NEW] Make [jsonite](https://github.com/json-iterator/go) optional with build tags, see [#1026](https://github.com/gin-gonic/gin/pull/1026) +- [NEW] Show query string in logger, see [#999](https://github.com/gin-gonic/gin/pull/999) +- [NEW] Add [`func (*Context) SecureJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.SecureJSON), see [#987](https://github.com/gin-gonic/gin/pull/987) and [#993](https://github.com/gin-gonic/gin/pull/993) +- [DEPRECATE] `func (*Context) GetCookie` for [`func (*Context) Cookie`](https://godoc.org/github.com/gin-gonic/gin#Context.Cookie) +- [FIX] Don't display color tags if [`func DisableConsoleColor`](https://godoc.org/github.com/gin-gonic/gin#DisableConsoleColor) called, see [#1072](https://github.com/gin-gonic/gin/pull/1072) +- [FIX] Gin Mode `""` when calling [`func Mode`](https://godoc.org/github.com/gin-gonic/gin#Mode) now returns `const DebugMode`, see [#1250](https://github.com/gin-gonic/gin/pull/1250) +- [FIX] `Flush()` now doesn't overwrite `responseWriter` status code, see [#1460](https://github.com/gin-gonic/gin/pull/1460) + +### Gin 1.2.0 - [NEW] Switch from godeps to govendor - [NEW] Add support for Let's Encrypt via gin-gonic/autotls diff --git a/gin.go b/gin.go index fc4d6564..aa62e014 100644 --- a/gin.go +++ b/gin.go @@ -16,7 +16,7 @@ import ( const ( // Version is Framework's version. - Version = "v1.2" + Version = "v1.3.0" defaultMultipartMemory = 32 << 20 // 32 MB ) From 64a45486421c37bd60af66ce96a3ef7b62a191aa Mon Sep 17 00:00:00 2001 From: Abner Chen Date: Wed, 15 Aug 2018 13:42:12 +0800 Subject: [PATCH 31/55] Fix typo in readme (#1490) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 28598baf..d8e3ffaa 100644 --- a/README.md +++ b/README.md @@ -679,7 +679,7 @@ $ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09" {"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"} ``` -[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registed this way. +[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way. See the [struct-lvl-validation example](examples/struct-lvl-validations) to learn more. ### Only Bind Query String From bef6c56c8928cfe1d96a9fdefe29fcfdeb2b2f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 16 Aug 2018 17:38:17 +0800 Subject: [PATCH 32/55] chore: upgrade dependency library version (#1491) upgrade lib version, and upgrade `github.com/json-iterator/go` to add two libs. --- vendor/vendor.json | 94 +++++++++++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 30 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index c34c2de3..e6d038a4 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1,13 +1,12 @@ { - "comment": "v1.2", + "comment": "v1.3.0", "ignore": "test", "package": [ { - "checksumSHA1": "dvabztWVQX8f6oMLRyv4dLH+TGY=", - "comment": "v1.1.0", + "checksumSHA1": "CSPbwbyzqA6sfORicn4HFtIhF/c=", "path": "github.com/davecgh/go-spew/spew", - "revision": "346938d642f2ec3594ed81d874461961cd0faa76", - "revisionTime": "2016-10-29T20:57:26Z" + "revision": "8991bc29aa16c548c550c7ff78260e27b9ab7c73", + "revisionTime": "2018-02-21T22:46:20Z" }, { "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", @@ -16,35 +15,62 @@ "revisionTime": "2017-01-09T09:34:21Z" }, { - "checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=", + "checksumSHA1": "Pyou8mceOASSFxc7GeXZuVdSMi0=", "path": "github.com/golang/protobuf/proto", - "revision": "5a0f697c9ed9d68fef0116532c6e05cfeae00e55", - "revisionTime": "2017-06-01T23:02:30Z" + "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265", + "revisionTime": "2018-04-30T18:52:41Z", + "version": "v1.1.0", + "versionExact": "v1.1.0" }, { - "checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=", + "checksumSHA1": "WqeEgS7pqqkwK8mlrAZmDgtWJMY=", "path": "github.com/json-iterator/go", - "revision": "36b14963da70d11297d313183d7e6388c8510e1e", - "revisionTime": "2017-08-29T15:58:51Z" + "revision": "1624edc4454b8682399def8740d46db5e4362ba4", + "revisionTime": "2018-08-06T06:07:27Z", + "version": "1.1.5", + "versionExact": "1.1.5" }, { - "checksumSHA1": "U6lX43KDDlNOn+Z0Yyww+ZzHfFo=", + "checksumSHA1": "y/A5iuvwjytQE2CqVuphQRXR2nI=", "path": "github.com/mattn/go-isatty", - "revision": "57fdcb988a5c543893cc61bce354a6e24ab70022", - "revisionTime": "2017-03-07T16:30:44Z" + "revision": "0360b2af4f38e8d38c7fce2a9f4e702702d73a39", + "revisionTime": "2017-09-25T05:34:41Z", + "version": "v0.0.3", + "versionExact": "v0.0.3" }, { - "checksumSHA1": "Q2V7Zs3diLmLfmfbiuLpSxETSuY=", - "comment": "v1.1.4", + "checksumSHA1": "ZTcgWKWHsrX0RXYVXn5Xeb8Q0go=", + "path": "github.com/modern-go/concurrent", + "revision": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94", + "revisionTime": "2018-03-06T01:26:44Z" + }, + { + "checksumSHA1": "qvH48wzTIV3QKSDqI0dLFtVjaDI=", + "path": "github.com/modern-go/reflect2", + "revision": "94122c33edd36123c84d5368cfb2b69df93a0ec8", + "revisionTime": "2018-07-18T01:23:57Z" + }, + { + "checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=", + "path": "github.com/pmezard/go-difflib/difflib", + "revision": "792786c7400a136282c1664665ae0a8db921c6c2", + "revisionTime": "2016-01-10T10:55:54Z" + }, + { + "checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=", "path": "github.com/stretchr/testify/assert", - "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506", - "revisionTime": "2016-09-25T22:06:09Z" + "revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686", + "revisionTime": "2018-05-06T18:05:49Z", + "version": "v1.2.2", + "versionExact": "v1.2.2" }, { - "checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=", + "checksumSHA1": "5Bd8RPhhaKcEXkagzPqymP4Gx5E=", "path": "github.com/ugorji/go/codec", - "revision": "c88ee250d0221a57af388746f5cf03768c21d6e2", - "revisionTime": "2017-02-15T20:11:44Z" + "revision": "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab", + "revisionTime": "2018-04-07T10:07:33Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", @@ -54,18 +80,26 @@ "revisionTime": "2016-10-18T08:54:36Z" }, { - "checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=", - "comment": "v8.18.1", - "path": "gopkg.in/go-playground/validator.v8", - "revision": "5f57d2222ad794d0dffb07e664ea05e2ee07d60c", - "revisionTime": "2016-07-18T13:41:25Z" + "checksumSHA1": "7Gocawl8bm27cpAILtuf21xvVD8=", + "path": "golang.org/x/sys/unix", + "revision": "1c9583448a9c3aa0f9a6a5241bf73c0bd8aafded", + "revisionTime": "2018-08-15T07:37:39Z" }, { - "checksumSHA1": "12GqsW8PiRPnezDDy0v4brZrndM=", - "comment": "v2", + "checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=", + "path": "gopkg.in/go-playground/validator.v8", + "revision": "5f1438d3fca68893a817e4a66806cea46a9e4ebf", + "revisionTime": "2017-07-30T05:02:35Z", + "version": "v8.18.2", + "versionExact": "v8.18.2" + }, + { + "checksumSHA1": "ZSWoOPUNRr5+3dhkLK3C4cZAQPk=", "path": "gopkg.in/yaml.v2", - "revision": "a5b47d31c556af34a302ce5d659e6fea44d90de0", - "revisionTime": "2016-09-28T15:37:09Z" + "revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183", + "revisionTime": "2018-03-28T19:50:20Z", + "version": "v2.2.1", + "versionExact": "v2.2.1" } ], "rootPath": "github.com/gin-gonic/gin" From 40ab9de4b57ac6ce467a96b1fb4c3379658b29c2 Mon Sep 17 00:00:00 2001 From: syssam Date: Fri, 17 Aug 2018 09:12:15 +0800 Subject: [PATCH 33/55] Add BindXML AND ShouldBindXML #1484 (#1485) Add BindXML AND ShouldBindXML #1484 --- README.md | 61 ++++++++++++++++++++++++++++++++++--------------- context.go | 10 ++++++++ context_test.go | 41 +++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index d8e3ffaa..5de22dc3 100644 --- a/README.md +++ b/README.md @@ -534,10 +534,10 @@ Note that you need to set the corresponding binding tag on all fields you want t Also, Gin provides two sets of methods for binding: - **Type** - Must bind - - **Methods** - `Bind`, `BindJSON`, `BindQuery` + - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery` - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. - **Type** - Should bind - - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindQuery` + - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery` - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. @@ -547,8 +547,8 @@ You can also specify that specific fields are required. If a field is decorated ```go // Binding from JSON type Login struct { - User string `form:"user" json:"user" binding:"required"` - Password string `form:"password" json:"password" binding:"required"` + User string `form:"user" json:"user" xml:"user" binding:"required"` + Password string `form:"password" json:"password" xml:"password" binding:"required"` } func main() { @@ -557,30 +557,55 @@ func main() { // Example for binding JSON ({"user": "manu", "password": "123"}) router.POST("/loginJSON", func(c *gin.Context) { var json Login - if err := c.ShouldBindJSON(&json); err == nil { - if json.User == "manu" && json.Password == "123" { - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - } else { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - } - } else { + if err := c.ShouldBindXML(&json); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return } + + if json.User != "manu" || json.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Example for binding XML ( + // + // + // user + // 123 + // ) + router.POST("/loginXML", func(c *gin.Context) { + var xml Login + if err := c.ShouldBindXML(&xml); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if xml.User != "manu" || xml.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) }) // Example for binding a HTML form (user=manu&password=123) router.POST("/loginForm", func(c *gin.Context) { var form Login // This will infer what binder to use depending on the content-type header. - if err := c.ShouldBind(&form); err == nil { - if form.User == "manu" && form.Password == "123" { - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - } else { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - } - } else { + if err := c.ShouldBind(&form); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return } + + if form.User != "manu" || form.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) }) // Listen and serve on 0.0.0.0:8080 diff --git a/context.go b/context.go index 724ded79..bbdb7e4f 100644 --- a/context.go +++ b/context.go @@ -511,6 +511,11 @@ func (c *Context) BindJSON(obj interface{}) error { return c.MustBindWith(obj, binding.JSON) } +// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML). +func (c *Context) BindXML(obj interface{}) error { + return c.MustBindWith(obj, binding.XML) +} + // BindQuery is a shortcut for c.MustBindWith(obj, binding.Query). func (c *Context) BindQuery(obj interface{}) error { return c.MustBindWith(obj, binding.Query) @@ -545,6 +550,11 @@ func (c *Context) ShouldBindJSON(obj interface{}) error { return c.ShouldBindWith(obj, binding.JSON) } +// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML). +func (c *Context) ShouldBindXML(obj interface{}) error { + return c.ShouldBindWith(obj, binding.XML) +} + // ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query). func (c *Context) ShouldBindQuery(obj interface{}) error { return c.ShouldBindWith(obj, binding.Query) diff --git a/context_test.go b/context_test.go index 99d5267d..13fb9099 100644 --- a/context_test.go +++ b/context_test.go @@ -1302,6 +1302,26 @@ func TestContextBindWithJSON(t *testing.T) { assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) } +func TestContextBindWithXML(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(` + + FOO + BAR + `)) + c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type + + var obj struct { + Foo string `xml:"foo"` + Bar string `xml:"bar"` + } + assert.NoError(t, c.BindXML(&obj)) + assert.Equal(t, "FOO", obj.Foo) + assert.Equal(t, "BAR", obj.Bar) + assert.Equal(t, 0, w.Body.Len()) +} func TestContextBindWithQuery(t *testing.T) { w := httptest.NewRecorder() @@ -1372,6 +1392,27 @@ func TestContextShouldBindWithJSON(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) } +func TestContextShouldBindWithXML(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(` + + FOO + BAR + `)) + c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type + + var obj struct { + Foo string `xml:"foo"` + Bar string `xml:"bar"` + } + assert.NoError(t, c.ShouldBindXML(&obj)) + assert.Equal(t, "FOO", obj.Foo) + assert.Equal(t, "BAR", obj.Bar) + assert.Equal(t, 0, w.Body.Len()) +} + func TestContextShouldBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) From 7eb0f74b89af91b609f910f549e312a5c5db2ada Mon Sep 17 00:00:00 2001 From: Alexander Lokhman Date: Fri, 17 Aug 2018 02:41:56 +0100 Subject: [PATCH 34/55] Set default time format in form binding (#1487) --- binding/form_mapping.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 3f6b9bfa..a714291f 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -178,7 +178,7 @@ func setFloatField(val string, bitSize int, field reflect.Value) error { func setTimeField(val string, structField reflect.StructField, value reflect.Value) error { timeFormat := structField.Tag.Get("time_format") if timeFormat == "" { - return errors.New("Blank time format") + timeFormat = time.RFC3339 } if val == "" { From a643d206054d630f66fce6513db80d864159030a Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Fri, 17 Aug 2018 11:21:14 +0800 Subject: [PATCH 35/55] readme: fix users link (#1493) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5de22dc3..37eec614 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) - [http2 server push](#http2-server-push) - [Testing](#testing) -- [Users](#users--) +- [Users](#users) ## Installation From f5451bd645be353ba40cd5d308a545f201e68283 Mon Sep 17 00:00:00 2001 From: David Zhang Date: Fri, 17 Aug 2018 11:33:23 +0800 Subject: [PATCH 36/55] Fix typo in README [ci skip] (#1492) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 37eec614..920b634b 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 ## Build with [jsoniter](https://github.com/json-iterator/go) -Gin use `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. +Gin uses `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. ```sh $ go build -tags=jsoniter . From f856aa85cd175ad10d2f55b0a41a51a08f4bdb02 Mon Sep 17 00:00:00 2001 From: chainhelen Date: Fri, 17 Aug 2018 14:59:55 +0800 Subject: [PATCH 37/55] Update readme about the version of gin (#1494) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 920b634b..161ea28b 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ $ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" ```sh $ govendor init -$ govendor fetch github.com/gin-gonic/gin@v1.2 +$ govendor fetch github.com/gin-gonic/gin@v1.3 ``` 4. Copy a starting template inside your project From efdd3c8b81a42e1769fe70483d4828d5bba7e87e Mon Sep 17 00:00:00 2001 From: aljun Date: Sun, 19 Aug 2018 10:45:56 +0800 Subject: [PATCH 38/55] Add support for Protobuf format response and unit test (#1479) `Gin` now have the `protobufBinding` function to check the request format, but didn't have a protobuf response function like `c.YAML()`. In our company [ByteDance](http://bytedance.com/), the largest internet company using golang in China, we use `gin` to transfer __Protobuf__ instead of __Json__, we have to write some internal library to make some wrappers to achieve that, and the code is not elegant. So we really want such a feature. --- README.md | 17 +++++++++++++++-- context.go | 6 ++++++ context_test.go | 27 +++++++++++++++++++++++++++ render/protobuf.go | 33 +++++++++++++++++++++++++++++++++ render/render.go | 1 + render/render_test.go | 31 +++++++++++++++++++++++++++++++ 6 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 render/protobuf.go diff --git a/README.md b/README.md index 161ea28b..ae183f92 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Bind Query String or Post Data](#bind-query-string-or-post-data) - [Bind HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - - [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering) + - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering) - [JSONP rendering](#jsonp) - [Serving static files](#serving-static-files) - [Serving data from reader](#serving-data-from-reader) @@ -871,7 +871,7 @@ Test it with: $ curl -v --form user=user --form password=password http://localhost:8080/login ``` -### XML, JSON and YAML rendering +### XML, JSON, YAML and ProtoBuf rendering ```go func main() { @@ -905,6 +905,19 @@ func main() { c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) }) + r.GET("/someProtoBuf", func(c *gin.Context) { + reps := []int64{int64(1), int64(2)} + label := "test" + // The specific definition of protobuf is written in the testdata/protoexample file. + data := &protoexample.Test{ + Label: &label, + Reps: reps, + } + // Note that data becomes binary data in the response + // Will output protoexample.Test protobuf serialized data + c.ProtoBuf(http.StatusOK, data) + }) + // Listen and serve on 0.0.0.0:8080 r.Run(":8080") } diff --git a/context.go b/context.go index bbdb7e4f..5cd52837 100644 --- a/context.go +++ b/context.go @@ -20,6 +20,7 @@ import ( "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/render" + "github.com/golang/protobuf/proto" ) // Content-Type MIME of the most common data formats. @@ -845,6 +846,11 @@ func (c *Context) Stream(step func(w io.Writer) bool) { } } +// ProtoBuf serializes the given struct as ProtoBuf into the response body. +func (c *Context) ProtoBuf(code int, obj proto.Message) { + c.Render(code, render.ProtoBuf{Data: obj}) +} + /************************************/ /******** CONTENT NEGOTIATION *******/ /************************************/ diff --git a/context_test.go b/context_test.go index 13fb9099..675c2a49 100644 --- a/context_test.go +++ b/context_test.go @@ -20,8 +20,11 @@ import ( "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" + "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "golang.org/x/net/context" + + testdata "github.com/gin-gonic/gin/testdata/protoexample" ) var _ context.Context = &Context{} @@ -954,6 +957,30 @@ func TestContextRenderYAML(t *testing.T) { assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } +// TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf +// and Content-Type is set to application/x-protobuf +// and we just use the example protobuf to check if the response is correct +func TestContextRenderProtoBuf(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + reps := []int64{int64(1), int64(2)} + label := "test" + data := &testdata.Test{ + Label: &label, + Reps: reps, + } + + c.ProtoBuf(http.StatusCreated, data) + + protoData, err := proto.Marshal(data) + assert.NoError(t, err) + + assert.Equal(t, http.StatusCreated, w.Code) + assert.Equal(t, string(protoData[:]), w.Body.String()) + assert.Equal(t, "application/x-protobuf", w.HeaderMap.Get("Content-Type")) +} + func TestContextHeaders(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Header("Content-Type", "text/plain") diff --git a/render/protobuf.go b/render/protobuf.go new file mode 100644 index 00000000..fe826884 --- /dev/null +++ b/render/protobuf.go @@ -0,0 +1,33 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package render + +import ( + "net/http" + + "github.com/golang/protobuf/proto" +) + +type ProtoBuf struct { + Data proto.Message +} + +var protobufContentType = []string{"application/x-protobuf"} + +func (r ProtoBuf) Render(w http.ResponseWriter) error { + r.WriteContentType(w) + + bytes, err := proto.Marshal(r.Data) + if err != nil { + return err + } + + w.Write(bytes) + return nil +} + +func (r ProtoBuf) WriteContentType(w http.ResponseWriter) { + writeContentType(w, protobufContentType) +} diff --git a/render/render.go b/render/render.go index 4ff1c7b6..df0d1d7c 100755 --- a/render/render.go +++ b/render/render.go @@ -27,6 +27,7 @@ var ( _ Render = MsgPack{} _ Render = Reader{} _ Render = AsciiJSON{} + _ Render = ProtoBuf{} ) func writeContentType(w http.ResponseWriter, value []string) { diff --git a/render/render_test.go b/render/render_test.go index 47abf262..905b76a1 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -15,6 +15,8 @@ import ( "strings" "testing" + testdata "github.com/gin-gonic/gin/testdata/protoexample" + "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/ugorji/go/codec" ) @@ -265,6 +267,35 @@ func TestRenderYAMLFail(t *testing.T) { assert.Error(t, err) } +// test Protobuf rendering +func TestRenderProtoBuf(t *testing.T) { + w := httptest.NewRecorder() + reps := []int64{int64(1), int64(2)} + label := "test" + data := &testdata.Test{ + Label: &label, + Reps: reps, + } + + (ProtoBuf{data}).WriteContentType(w) + protoData, err := proto.Marshal(data) + assert.NoError(t, err) + assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) + + err = (ProtoBuf{data}).Render(w) + + assert.NoError(t, err) + assert.Equal(t, string(protoData[:]), w.Body.String()) + assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) +} + +func TestRenderProtoBufFail(t *testing.T) { + w := httptest.NewRecorder() + data := &testdata.Test{} + err := (ProtoBuf{data}).Render(w) + assert.Error(t, err) +} + func TestRenderXML(t *testing.T) { w := httptest.NewRecorder() data := xmlmap{ From 6073a79ee08657ea9784c26b8521a8f8ef9e1644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 19 Aug 2018 17:39:58 +0800 Subject: [PATCH 39/55] not use protobuf on context but use it on render (#1496) --- context.go | 11 +++++------ render/json.go | 0 render/protobuf.go | 4 ++-- render/render.go | 0 render/render_test.go | 3 ++- 5 files changed, 9 insertions(+), 9 deletions(-) mode change 100755 => 100644 render/json.go mode change 100755 => 100644 render/render.go mode change 100755 => 100644 render/render_test.go diff --git a/context.go b/context.go index 5cd52837..6d80284e 100644 --- a/context.go +++ b/context.go @@ -20,7 +20,6 @@ import ( "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/render" - "github.com/golang/protobuf/proto" ) // Content-Type MIME of the most common data formats. @@ -784,6 +783,11 @@ func (c *Context) YAML(code int, obj interface{}) { c.Render(code, render.YAML{Data: obj}) } +// ProtoBuf serializes the given struct as ProtoBuf into the response body. +func (c *Context) ProtoBuf(code int, obj interface{}) { + c.Render(code, render.ProtoBuf{Data: obj}) +} + // String writes the given string into the response body. func (c *Context) String(code int, format string, values ...interface{}) { c.Render(code, render.String{Format: format, Data: values}) @@ -846,11 +850,6 @@ func (c *Context) Stream(step func(w io.Writer) bool) { } } -// ProtoBuf serializes the given struct as ProtoBuf into the response body. -func (c *Context) ProtoBuf(code int, obj proto.Message) { - c.Render(code, render.ProtoBuf{Data: obj}) -} - /************************************/ /******** CONTENT NEGOTIATION *******/ /************************************/ diff --git a/render/json.go b/render/json.go old mode 100755 new mode 100644 diff --git a/render/protobuf.go b/render/protobuf.go index fe826884..34f1e9b5 100644 --- a/render/protobuf.go +++ b/render/protobuf.go @@ -11,7 +11,7 @@ import ( ) type ProtoBuf struct { - Data proto.Message + Data interface{} } var protobufContentType = []string{"application/x-protobuf"} @@ -19,7 +19,7 @@ var protobufContentType = []string{"application/x-protobuf"} func (r ProtoBuf) Render(w http.ResponseWriter) error { r.WriteContentType(w) - bytes, err := proto.Marshal(r.Data) + bytes, err := proto.Marshal(r.Data.(proto.Message)) if err != nil { return err } diff --git a/render/render.go b/render/render.go old mode 100755 new mode 100644 diff --git a/render/render_test.go b/render/render_test.go old mode 100755 new mode 100644 index 905b76a1..cd20d01d --- a/render/render_test.go +++ b/render/render_test.go @@ -15,10 +15,11 @@ import ( "strings" "testing" - testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/ugorji/go/codec" + + testdata "github.com/gin-gonic/gin/testdata/protoexample" ) // TODO unit tests From 32b58e0fd2b101cc0bedc925ac71f1bbca620869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 19 Aug 2018 22:14:02 +0800 Subject: [PATCH 40/55] render: update msgpack usage (#1498) please see msgpack usage: https://github.com/ugorji/go/tree/master/codec#usage --- render/msgpack.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/render/msgpack.go b/render/msgpack.go index e6c13e58..b6b8aa0f 100644 --- a/render/msgpack.go +++ b/render/msgpack.go @@ -26,6 +26,6 @@ func (r MsgPack) Render(w http.ResponseWriter) error { func WriteMsgPack(w http.ResponseWriter, obj interface{}) error { writeContentType(w, msgpackContentType) - var h codec.Handle = new(codec.MsgpackHandle) - return codec.NewEncoder(w, h).Encode(obj) + var mh codec.MsgpackHandle + return codec.NewEncoder(w, &mh).Encode(obj) } From b7bb9baa642d2b9d5918a5bcd3829dae64c65257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 19 Aug 2018 22:52:43 +0800 Subject: [PATCH 41/55] chore: add missing copyright and update if/else (#1497) --- render/reader.go | 4 ++++ render/text.go | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/render/reader.go b/render/reader.go index be2132c8..7a06cce4 100644 --- a/render/reader.go +++ b/render/reader.go @@ -1,3 +1,7 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package render import ( diff --git a/render/text.go b/render/text.go index 74cd26be..7a6acc44 100644 --- a/render/text.go +++ b/render/text.go @@ -30,7 +30,7 @@ func WriteString(w http.ResponseWriter, format string, data []interface{}) { writeContentType(w, plainContentType) if len(data) > 0 { fmt.Fprintf(w, format, data...) - } else { - io.WriteString(w, format) + return } + io.WriteString(w, format) } From c6110f970ca5af365283bd4083b8a105e974e92c Mon Sep 17 00:00:00 2001 From: Filip Figiel Date: Mon, 20 Aug 2018 09:15:31 +0200 Subject: [PATCH 42/55] Add PureJSON renderer (#694) Closes #693 --- README.md | 28 ++++++++++++++++++++++++++++ context_17.go | 17 +++++++++++++++++ context_17_test.go | 27 +++++++++++++++++++++++++++ context_test.go | 5 +++-- json/json.go | 1 + render/json_17.go | 28 ++++++++++++++++++++++++++++ render/render_17_test.go | 26 ++++++++++++++++++++++++++ render/render_test.go | 5 +++-- 8 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 context_17.go create mode 100644 context_17_test.go create mode 100644 render/json_17.go create mode 100644 render/render_17_test.go diff --git a/README.md b/README.md index ae183f92..053750bb 100644 --- a/README.md +++ b/README.md @@ -991,6 +991,34 @@ func main() { } ``` +#### PureJSON + +Normally, JSON replaces special HTML characters with their unicode entities, e.g. `<` becomes `\u003c`. If you want to encode such characters literally, you can use PureJSON instead. +This feature is unavailable in Go 1.6 and lower. + +```go +func main() { + r := gin.Default() + + // Serves unicode entities + r.GET("/json", func(c *gin.Context) { + c.JSON(200, gin.H{ + "html": "Hello, world!", + }) + }) + + // Serves literal characters + r.GET("/purejson", func(c *gin.Context) { + c.PureJSON(200, gin.H{ + "html": "Hello, world!", + }) + }) + + // listen and serve on 0.0.0.0:8080 + r.Run(":8080) +} +``` + ### Serving static files ```go diff --git a/context_17.go b/context_17.go new file mode 100644 index 00000000..8e9f75ad --- /dev/null +++ b/context_17.go @@ -0,0 +1,17 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package gin + +import ( + "github.com/gin-gonic/gin/render" +) + +// PureJSON serializes the given struct as JSON into the response body. +// PureJSON, unlike JSON, does not replace special html characters with their unicode entities. +func (c *Context) PureJSON(code int, obj interface{}) { + c.Render(code, render.PureJSON{Data: obj}) +} diff --git a/context_17_test.go b/context_17_test.go new file mode 100644 index 00000000..d2251904 --- /dev/null +++ b/context_17_test.go @@ -0,0 +1,27 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package gin + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +// Tests that the response is serialized as JSON +// and Content-Type is set to application/json +// and special HTML characters are preserved +func TestContextRenderPureJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": ""}) + assert.Equal(t, http.StatusCreated, w.Code) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} diff --git a/context_test.go b/context_test.go index 675c2a49..782f7bed 100644 --- a/context_test.go +++ b/context_test.go @@ -619,14 +619,15 @@ func TestContextRenderPanicIfErr(t *testing.T) { // Tests that the response is serialized as JSON // and Content-Type is set to application/json +// and special HTML characters are escaped func TestContextRenderJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.JSON(http.StatusCreated, H{"foo": "bar"}) + c.JSON(http.StatusCreated, H{"foo": "bar", "html": ""}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } diff --git a/json/json.go b/json/json.go index aa76aa30..4f643c56 100644 --- a/json/json.go +++ b/json/json.go @@ -12,4 +12,5 @@ var ( Marshal = json.Marshal MarshalIndent = json.MarshalIndent NewDecoder = json.NewDecoder + NewEncoder = json.NewEncoder ) diff --git a/render/json_17.go b/render/json_17.go new file mode 100644 index 00000000..df0dc145 --- /dev/null +++ b/render/json_17.go @@ -0,0 +1,28 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package render + +import ( + "net/http" + + "github.com/gin-gonic/gin/json" +) + +type PureJSON struct { + Data interface{} +} + +func (r PureJSON) Render(w http.ResponseWriter) error { + r.WriteContentType(w) + encoder := json.NewEncoder(w) + encoder.SetEscapeHTML(false) + return encoder.Encode(r.Data) +} + +func (r PureJSON) WriteContentType(w http.ResponseWriter) { + writeContentType(w, jsonContentType) +} diff --git a/render/render_17_test.go b/render/render_17_test.go new file mode 100644 index 00000000..68330090 --- /dev/null +++ b/render/render_17_test.go @@ -0,0 +1,26 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package render + +import ( + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRenderPureJSON(t *testing.T) { + w := httptest.NewRecorder() + data := map[string]interface{}{ + "foo": "bar", + "html": "", + } + err := (PureJSON{data}).Render(w) + assert.NoError(t, err) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) +} diff --git a/render/render_test.go b/render/render_test.go index cd20d01d..fe9228e9 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -52,7 +52,8 @@ func TestRenderMsgPack(t *testing.T) { func TestRenderJSON(t *testing.T) { w := httptest.NewRecorder() data := map[string]interface{}{ - "foo": "bar", + "foo": "bar", + "html": "", } (JSON{data}).WriteContentType(w) @@ -61,7 +62,7 @@ func TestRenderJSON(t *testing.T) { err := (JSON{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } From 0ebd42d0a921e49ed6a36fae33f2a2dcae800d6f Mon Sep 17 00:00:00 2001 From: junfengye Date: Mon, 20 Aug 2018 18:25:45 +0800 Subject: [PATCH 43/55] Update jsoniter.go (#1500) add newencoder to fix compile error for -tags=jsoniter --- json/jsoniter.go | 1 + 1 file changed, 1 insertion(+) diff --git a/json/jsoniter.go b/json/jsoniter.go index ffe1424a..f1ed5bea 100644 --- a/json/jsoniter.go +++ b/json/jsoniter.go @@ -13,4 +13,5 @@ var ( Marshal = json.Marshal MarshalIndent = json.MarshalIndent NewDecoder = json.NewDecoder + NewEncoder = json.NewEncoder ) From 85f3e78abcd408e7e155f58f72094446c4411e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 20 Aug 2018 21:49:24 +0800 Subject: [PATCH 44/55] chore: remove else instead of return/continue (#1502) As[ Effective Go](https://golang.org/doc/effective_go.html?#if) about `if` said, remove else statement instead of return/continue statement. --- binding/form_mapping.go | 16 ++++++++-------- context.go | 8 ++++---- render/json.go | 6 ++---- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index a714291f..f46a0dc1 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -74,16 +74,16 @@ func mapForm(ptr interface{}, form map[string][]string) error { } } val.Field(i).Set(slice) - } else { - if _, isTime := structField.Interface().(time.Time); isTime { - if err := setTimeField(inputValue[0], typeField, structField); err != nil { - return err - } - continue - } - if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil { + continue + } + if _, isTime := structField.Interface().(time.Time); isTime { + if err := setTimeField(inputValue[0], typeField, structField); err != nil { return err } + continue + } + if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil { + return err } } return nil diff --git a/context.go b/context.go index 6d80284e..063c72f0 100644 --- a/context.go +++ b/context.go @@ -665,9 +665,9 @@ func (c *Context) Status(code int) { func (c *Context) Header(key, value string) { if value == "" { c.Writer.Header().Del(key) - } else { - c.Writer.Header().Set(key, value) + return } + c.Writer.Header().Set(key, value) } // GetHeader returns value from request headers. @@ -755,9 +755,9 @@ func (c *Context) JSONP(code int, obj interface{}) { callback := c.DefaultQuery("callback", "") if callback == "" { c.Render(code, render.JSON{Data: obj}) - } else { - c.Render(code, render.JsonpJSON{Callback: callback, Data: obj}) + return } + c.Render(code, render.JsonpJSON{Callback: callback, Data: obj}) } // JSON serializes the given struct as JSON into the response body. diff --git a/render/json.go b/render/json.go index 6e5089a0..4d1857f5 100644 --- a/render/json.go +++ b/render/json.go @@ -128,10 +128,8 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { var buffer bytes.Buffer for _, r := range string(ret) { - cvt := "" - if r < 128 { - cvt = string(r) - } else { + cvt := string(r) + if r >= 128 { cvt = fmt.Sprintf("\\u%04x", int64(r)) } buffer.WriteString(cvt) From 0da5b0c85ac2539e775a7e63aebf3e0b5808dab4 Mon Sep 17 00:00:00 2001 From: anoty Date: Tue, 21 Aug 2018 13:29:25 +0800 Subject: [PATCH 45/55] format readme code import (#1503) --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 053750bb..9f66dd76 100644 --- a/README.md +++ b/README.md @@ -750,9 +750,12 @@ See the [detail information](https://github.com/gin-gonic/gin/issues/742#issueco ```go package main -import "log" -import "github.com/gin-gonic/gin" -import "time" +import ( + "log" + "time" + + "github.com/gin-gonic/gin" +) type Person struct { Name string `form:"name"` From 09d342abbc35e473bfbe11abaecdfe237df0630d Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 30 Aug 2018 14:22:51 +0800 Subject: [PATCH 46/55] Add golang 1.11.x testing (#1514) * Add golang 1.11.x testing * remove the latest golang testing See the issue: https://github.com/gin-gonic/gin/pull/1510 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e9101568..a93458f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ go: - 1.8.x - 1.9.x - 1.10.x - - master + - 1.11.x git: depth: 10 From 708b76adf011840eb587d57804edf93b2ef4ba75 Mon Sep 17 00:00:00 2001 From: llgoer Date: Thu, 30 Aug 2018 14:29:26 +0800 Subject: [PATCH 47/55] Update README.md (#1509) change `ShouldBindXML` to `ShouldBindJSON` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f66dd76..7d13cf20 100644 --- a/README.md +++ b/README.md @@ -557,7 +557,7 @@ func main() { // Example for binding JSON ({"user": "manu", "password": "123"}) router.POST("/loginJSON", func(c *gin.Context) { var json Login - if err := c.ShouldBindXML(&json); err != nil { + if err := c.ShouldBindJSON(&json); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } From 72db8acd99feab85e81ac6e2ebd16c76e0f986be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 30 Aug 2018 19:04:03 +0800 Subject: [PATCH 48/55] add internal package which includes json package (#1504) --- binding/json.go | 2 +- errors.go | 2 +- errors_test.go | 2 +- {json => internal/json}/json.go | 0 {json => internal/json}/jsoniter.go | 0 render/json.go | 2 +- render/json_17.go | 2 +- 7 files changed, 5 insertions(+), 5 deletions(-) rename {json => internal/json}/json.go (100%) rename {json => internal/json}/jsoniter.go (100%) diff --git a/binding/json.go b/binding/json.go index fea17bb2..310922c1 100644 --- a/binding/json.go +++ b/binding/json.go @@ -9,7 +9,7 @@ import ( "io" "net/http" - "github.com/gin-gonic/gin/json" + "github.com/gin-gonic/gin/internal/json" ) // EnableDecoderUseNumber is used to call the UseNumber method on the JSON diff --git a/errors.go b/errors.go index dbfccd85..6f90377e 100644 --- a/errors.go +++ b/errors.go @@ -9,7 +9,7 @@ import ( "fmt" "reflect" - "github.com/gin-gonic/gin/json" + "github.com/gin-gonic/gin/internal/json" ) type ErrorType uint64 diff --git a/errors_test.go b/errors_test.go index 0626611f..9351b578 100644 --- a/errors_test.go +++ b/errors_test.go @@ -8,7 +8,7 @@ import ( "errors" "testing" - "github.com/gin-gonic/gin/json" + "github.com/gin-gonic/gin/internal/json" "github.com/stretchr/testify/assert" ) diff --git a/json/json.go b/internal/json/json.go similarity index 100% rename from json/json.go rename to internal/json/json.go diff --git a/json/jsoniter.go b/internal/json/jsoniter.go similarity index 100% rename from json/jsoniter.go rename to internal/json/jsoniter.go diff --git a/render/json.go b/render/json.go index 4d1857f5..f6b7878e 100644 --- a/render/json.go +++ b/render/json.go @@ -10,7 +10,7 @@ import ( "html/template" "net/http" - "github.com/gin-gonic/gin/json" + "github.com/gin-gonic/gin/internal/json" ) type JSON struct { diff --git a/render/json_17.go b/render/json_17.go index df0dc145..75f7f374 100644 --- a/render/json_17.go +++ b/render/json_17.go @@ -9,7 +9,7 @@ package render import ( "net/http" - "github.com/gin-gonic/gin/json" + "github.com/gin-gonic/gin/internal/json" ) type PureJSON struct { From 7451a402bbab9dd439b09aa377f18638c7d8fd55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 30 Aug 2018 23:36:53 +0800 Subject: [PATCH 49/55] chore: update vendor version (#1520) #1491 adds some lib when upgrade json-iterator but it is not needed, and use `v1.1.5` not `1.1.5` version for json-iterator. --- vendor/vendor.json | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index e6d038a4..86df11be 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -27,8 +27,8 @@ "path": "github.com/json-iterator/go", "revision": "1624edc4454b8682399def8740d46db5e4362ba4", "revisionTime": "2018-08-06T06:07:27Z", - "version": "1.1.5", - "versionExact": "1.1.5" + "version": "v1.1", + "versionExact": "v1.1.5" }, { "checksumSHA1": "y/A5iuvwjytQE2CqVuphQRXR2nI=", @@ -38,24 +38,6 @@ "version": "v0.0.3", "versionExact": "v0.0.3" }, - { - "checksumSHA1": "ZTcgWKWHsrX0RXYVXn5Xeb8Q0go=", - "path": "github.com/modern-go/concurrent", - "revision": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94", - "revisionTime": "2018-03-06T01:26:44Z" - }, - { - "checksumSHA1": "qvH48wzTIV3QKSDqI0dLFtVjaDI=", - "path": "github.com/modern-go/reflect2", - "revision": "94122c33edd36123c84d5368cfb2b69df93a0ec8", - "revisionTime": "2018-07-18T01:23:57Z" - }, - { - "checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=", - "path": "github.com/pmezard/go-difflib/difflib", - "revision": "792786c7400a136282c1664665ae0a8db921c6c2", - "revisionTime": "2016-01-10T10:55:54Z" - }, { "checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=", "path": "github.com/stretchr/testify/assert", @@ -79,12 +61,6 @@ "revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a", "revisionTime": "2016-10-18T08:54:36Z" }, - { - "checksumSHA1": "7Gocawl8bm27cpAILtuf21xvVD8=", - "path": "golang.org/x/sys/unix", - "revision": "1c9583448a9c3aa0f9a6a5241bf73c0bd8aafded", - "revisionTime": "2018-08-15T07:37:39Z" - }, { "checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=", "path": "gopkg.in/go-playground/validator.v8", From 705e1992981b974ce131303cdf415ff78114f728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 1 Sep 2018 00:40:33 +0800 Subject: [PATCH 50/55] chore: update issue_implate (#1524) --- .github/ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index de067ce0..9d49aa41 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -3,8 +3,8 @@ - Please provide source code and commit sha if you found a bug. - Review existing issues and provide feedback or react to them. +- go version: - gin version (or commit ref): -- git version: - operating system: ## Description From 500ebd9ea866c57427aeaddc31f374adedd4084b Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Fri, 31 Aug 2018 22:38:16 +0200 Subject: [PATCH 51/55] docs: add fnproject to gin's user list (#1505) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d13cf20..a4bc4e76 100644 --- a/README.md +++ b/README.md @@ -1885,5 +1885,6 @@ func TestPingRoute(t *testing.T) { Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. -* [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go +* [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go. * [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. +* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. From df1e17c2f0ca7179b5acf03ce1da5c4f07c4dd7d Mon Sep 17 00:00:00 2001 From: "Iskander (Alex) Sharipov" Date: Wed, 12 Sep 2018 02:13:16 +0100 Subject: [PATCH 52/55] remove debug print statements from test code (#1540) Found using https://go-critic.github.io/overview#commentedOutCode-ref --- tree_test.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tree_test.go b/tree_test.go index 5bc27171..152f6331 100644 --- a/tree_test.go +++ b/tree_test.go @@ -125,8 +125,6 @@ func TestTreeAddAndGet(t *testing.T) { tree.addRoute(route, fakeHandler(route)) } - //printChildren(tree, "") - checkRequests(t, tree, testRequests{ {"/a", false, "/a", nil}, {"/", true, "", nil}, @@ -168,8 +166,6 @@ func TestTreeWildcard(t *testing.T) { tree.addRoute(route, fakeHandler(route)) } - //printChildren(tree, "") - checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, {"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}}, @@ -208,7 +204,6 @@ func TestUnescapeParameters(t *testing.T) { tree.addRoute(route, fakeHandler(route)) } - //printChildren(tree, "") unescape := true checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, @@ -260,8 +255,6 @@ func testRoutes(t *testing.T, routes []testRoute) { t.Errorf("unexpected panic for route '%s': %v", route.path, recv) } } - - //printChildren(tree, "") } func TestTreeWildcardConflict(t *testing.T) { @@ -328,8 +321,6 @@ func TestTreeDupliatePath(t *testing.T) { } } - //printChildren(tree, "") - checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, {"/doc/", false, "/doc/", nil}, @@ -444,8 +435,6 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { } } - //printChildren(tree, "") - tsrRoutes := [...]string{ "/hi/", "/b", From 3f27866f806536be319b3e342001dd4e0fb81691 Mon Sep 17 00:00:00 2001 From: "Iskander (Alex) Sharipov" Date: Wed, 12 Sep 2018 14:21:26 +0100 Subject: [PATCH 53/55] simplify slice expressions: s[:] => s (#1541) Found using https://go-critic.github.io/overview#unslice-ref --- context_test.go | 2 +- gin_test.go | 20 ++++++++++---------- render/render_test.go | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/context_test.go b/context_test.go index 782f7bed..4c307080 100644 --- a/context_test.go +++ b/context_test.go @@ -978,7 +978,7 @@ func TestContextRenderProtoBuf(t *testing.T) { assert.NoError(t, err) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, string(protoData[:]), w.Body.String()) + assert.Equal(t, string(protoData), w.Body.String()) assert.Equal(t, "application/x-protobuf", w.HeaderMap.Get("Content-Type")) } diff --git a/gin_test.go b/gin_test.go index b3cab715..95b9cc16 100644 --- a/gin_test.go +++ b/gin_test.go @@ -88,7 +88,7 @@ func TestLoadHTMLGlob(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -101,7 +101,7 @@ func TestLoadHTMLGlob2(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -114,7 +114,7 @@ func TestLoadHTMLGlob3(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -134,7 +134,7 @@ func TestLoadHTMLGlobUsingTLS(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -148,7 +148,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "Date: 2017/07/01\n", string(resp[:])) + assert.Equal(t, "Date: 2017/07/01\n", string(resp)) td() } @@ -187,7 +187,7 @@ func TestLoadHTMLFiles(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -199,7 +199,7 @@ func TestLoadHTMLFiles2(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -211,7 +211,7 @@ func TestLoadHTMLFiles3(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -230,7 +230,7 @@ func TestLoadHTMLFilesUsingTLS(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -243,7 +243,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "Date: 2017/07/01\n", string(resp[:])) + assert.Equal(t, "Date: 2017/07/01\n", string(resp)) td() } diff --git a/render/render_test.go b/render/render_test.go index fe9228e9..09ccc658 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -287,7 +287,7 @@ func TestRenderProtoBuf(t *testing.T) { err = (ProtoBuf{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, string(protoData[:]), w.Body.String()) + assert.Equal(t, string(protoData), w.Body.String()) assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) } From d510595aa58c2417373d89a8d8ffa21cf58673cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 15 Sep 2018 10:23:32 +0800 Subject: [PATCH 54/55] chore: add some annotations (#1544) ref: #1075 because I am not a native English, maybe have a bit problem. --- README.md | 1 + examples/basic/main.go | 6 +++--- examples/custom-validation/server.go | 1 + examples/realtime-advanced/main.go | 3 +++ examples/realtime-advanced/stats.go | 1 + ginS/gins.go | 9 ++++++++- internal/json/json.go | 10 +++++++--- internal/json/jsoniter.go | 12 ++++++++---- render/data.go | 2 ++ render/html.go | 14 +++++++++++++- render/json.go | 17 +++++++++++++++++ render/json_17.go | 3 +++ render/msgpack.go | 4 ++++ render/protobuf.go | 3 +++ render/reader.go | 3 +++ render/redirect.go | 3 +++ render/render.go | 3 +++ render/text.go | 4 ++++ render/xml.go | 3 +++ render/yaml.go | 3 +++ utils.go | 8 ++++---- 21 files changed, 97 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index a4bc4e76..9a97ec54 100644 --- a/README.md +++ b/README.md @@ -657,6 +657,7 @@ import ( "gopkg.in/go-playground/validator.v8" ) +// Booking contains binded and validated data. type Booking struct { CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` diff --git a/examples/basic/main.go b/examples/basic/main.go index 48fa7bba..1c9e0ac4 100644 --- a/examples/basic/main.go +++ b/examples/basic/main.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" ) -var DB = make(map[string]string) +var db = make(map[string]string) func setupRouter() *gin.Engine { // Disable Console Color @@ -21,7 +21,7 @@ func setupRouter() *gin.Engine { // Get user value r.GET("/user/:name", func(c *gin.Context) { user := c.Params.ByName("name") - value, ok := DB[user] + value, ok := db[user] if ok { c.JSON(http.StatusOK, gin.H{"user": user, "value": value}) } else { @@ -50,7 +50,7 @@ func setupRouter() *gin.Engine { } if c.Bind(&json) == nil { - DB[user] = json.Value + db[user] = json.Value c.JSON(http.StatusOK, gin.H{"status": "ok"}) } }) diff --git a/examples/custom-validation/server.go b/examples/custom-validation/server.go index dea0c302..9238200b 100644 --- a/examples/custom-validation/server.go +++ b/examples/custom-validation/server.go @@ -10,6 +10,7 @@ import ( "gopkg.in/go-playground/validator.v8" ) +// Booking contains binded and validated data. type Booking struct { CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` diff --git a/examples/realtime-advanced/main.go b/examples/realtime-advanced/main.go index 1f3c8585..a16c58b0 100644 --- a/examples/realtime-advanced/main.go +++ b/examples/realtime-advanced/main.go @@ -13,16 +13,19 @@ func main() { StartGin() } +// ConfigRuntime sets the number of operating system threads. func ConfigRuntime() { nuCPU := runtime.NumCPU() runtime.GOMAXPROCS(nuCPU) fmt.Printf("Running with %d CPUs\n", nuCPU) } +// StartWrokers start starsWorker by goroutine. func StartWorkers() { go statsWorker() } +// StartGin starts gin web server with setting router. func StartGin() { gin.SetMode(gin.ReleaseMode) diff --git a/examples/realtime-advanced/stats.go b/examples/realtime-advanced/stats.go index 4afedcb5..a6488035 100644 --- a/examples/realtime-advanced/stats.go +++ b/examples/realtime-advanced/stats.go @@ -50,6 +50,7 @@ func connectedUsers() uint64 { return uint64(connected) } +// Stats returns savedStats data. func Stats() map[string]uint64 { mutexStats.RLock() defer mutexStats.RUnlock() diff --git a/ginS/gins.go b/ginS/gins.go index a7686f23..04cf131c 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -22,14 +22,17 @@ func engine() *gin.Engine { return internalEngine } +// LoadHTMLGlob is a wrapper for Engine.LoadHTMLGlob. func LoadHTMLGlob(pattern string) { engine().LoadHTMLGlob(pattern) } +// LoadHTMLFiles is a wrapper for Engine.LoadHTMLFiles. func LoadHTMLFiles(files ...string) { engine().LoadHTMLFiles(files...) } +// SetHTMLTemplate is a wrapper for Engine.SetHTMLTemplate. func SetHTMLTemplate(templ *template.Template) { engine().SetHTMLTemplate(templ) } @@ -39,7 +42,7 @@ func NoRoute(handlers ...gin.HandlerFunc) { engine().NoRoute(handlers...) } -// NoMethod sets the handlers called when... TODO +// NoMethod is a wrapper for Engine.NoMethod. func NoMethod(handlers ...gin.HandlerFunc) { engine().NoMethod(handlers...) } @@ -50,6 +53,7 @@ func Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup { return engine().Group(relativePath, handlers...) } +// Handle is a wrapper for Engine.Handle. func Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().Handle(httpMethod, relativePath, handlers...) } @@ -89,10 +93,12 @@ func HEAD(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().HEAD(relativePath, handlers...) } +// Any is a wrapper for Engine.Any. func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().Any(relativePath, handlers...) } +// StaticFile is a wrapper for Engine.StaticFile. func StaticFile(relativePath, filepath string) gin.IRoutes { return engine().StaticFile(relativePath, filepath) } @@ -107,6 +113,7 @@ func Static(relativePath, root string) gin.IRoutes { return engine().Static(relativePath, root) } +// StaticFS is a wrapper for Engine.StaticFS. func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes { return engine().StaticFS(relativePath, fs) } diff --git a/internal/json/json.go b/internal/json/json.go index 4f643c56..419d35f2 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -9,8 +9,12 @@ package json import "encoding/json" var ( - Marshal = json.Marshal + // Marshal is exported by gin/json package. + Marshal = json.Marshal + // MarshalIndent is exported by gin/json package. MarshalIndent = json.MarshalIndent - NewDecoder = json.NewDecoder - NewEncoder = json.NewEncoder + // NewDecoder is exported by gin/json package. + NewDecoder = json.NewDecoder + // NewEncoder is exported by gin/json package. + NewEncoder = json.NewEncoder ) diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go index f1ed5bea..2021c53c 100644 --- a/internal/json/jsoniter.go +++ b/internal/json/jsoniter.go @@ -9,9 +9,13 @@ package json import "github.com/json-iterator/go" var ( - json = jsoniter.ConfigCompatibleWithStandardLibrary - Marshal = json.Marshal + json = jsoniter.ConfigCompatibleWithStandardLibrary + // Marshal is exported by gin/json package. + Marshal = json.Marshal + // MarshalIndent is exported by gin/json package. MarshalIndent = json.MarshalIndent - NewDecoder = json.NewDecoder - NewEncoder = json.NewEncoder + // NewDecoder is exported by gin/json package. + NewDecoder = json.NewDecoder + // NewEncoder is exported by gin/json package. + NewEncoder = json.NewEncoder ) diff --git a/render/data.go b/render/data.go index 33194913..6ba657ba 100644 --- a/render/data.go +++ b/render/data.go @@ -6,6 +6,7 @@ package render import "net/http" +// Data contains ContentType and bytes data. type Data struct { ContentType string Data []byte @@ -18,6 +19,7 @@ func (r Data) Render(w http.ResponseWriter) (err error) { return } +// WriteContentType (Data) writes custom ContentType. func (r Data) WriteContentType(w http.ResponseWriter) { writeContentType(w, []string{r.ContentType}) } diff --git a/render/html.go b/render/html.go index 1e3be65b..4d3d5812 100644 --- a/render/html.go +++ b/render/html.go @@ -9,20 +9,27 @@ import ( "net/http" ) +// Delims represents a set of Left and Right delimiters for HTML template rendering. type Delims struct { - Left string + // Left delimiter, defaults to {{. + Left string + // Right delimiter, defaults to }}. Right string } +// HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug. type HTMLRender interface { + // Instance returns an HTML instance. Instance(string, interface{}) Render } +// HTMLProduction contains template reference and its delims. type HTMLProduction struct { Template *template.Template Delims Delims } +// HTMLDebug contains template delims and pattern and function with file list. type HTMLDebug struct { Files []string Glob string @@ -30,6 +37,7 @@ type HTMLDebug struct { FuncMap template.FuncMap } +// HTML contains template reference and its name with given interface object. type HTML struct { Template *template.Template Name string @@ -38,6 +46,7 @@ type HTML struct { var htmlContentType = []string{"text/html; charset=utf-8"} +// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface.. func (r HTMLProduction) Instance(name string, data interface{}) Render { return HTML{ Template: r.Template, @@ -46,6 +55,7 @@ func (r HTMLProduction) Instance(name string, data interface{}) Render { } } +// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface.. func (r HTMLDebug) Instance(name string, data interface{}) Render { return HTML{ Template: r.loadTemplate(), @@ -66,6 +76,7 @@ func (r HTMLDebug) loadTemplate() *template.Template { panic("the HTML debug render was created without files or glob pattern") } +// Render (HTML) executes template and writes its result with custom ContentType for response. func (r HTML) Render(w http.ResponseWriter) error { r.WriteContentType(w) @@ -75,6 +86,7 @@ func (r HTML) Render(w http.ResponseWriter) error { return r.Template.ExecuteTemplate(w, r.Name, r.Data) } +// WriteContentType (HTML) writes HTML ContentType. func (r HTML) WriteContentType(w http.ResponseWriter) { writeContentType(w, htmlContentType) } diff --git a/render/json.go b/render/json.go index f6b7878e..32d0fc42 100644 --- a/render/json.go +++ b/render/json.go @@ -13,34 +13,41 @@ import ( "github.com/gin-gonic/gin/internal/json" ) +// JSON contains the given interface object. type JSON struct { Data interface{} } +// IndentedJSON contains the given interface object. type IndentedJSON struct { Data interface{} } +// SecureJSON contains the given interface object and its prefix. type SecureJSON struct { Prefix string Data interface{} } +// JsonpJSON contains the given interface object its callback. type JsonpJSON struct { Callback string Data interface{} } +// AsciiJSON contains the given interface object. type AsciiJSON struct { Data interface{} } +// SecureJSONPrefix is a string which represents SecureJSON prefix. type SecureJSONPrefix string var jsonContentType = []string{"application/json; charset=utf-8"} var jsonpContentType = []string{"application/javascript; charset=utf-8"} var jsonAsciiContentType = []string{"application/json"} +// Render (JSON) writes data with custom ContentType. func (r JSON) Render(w http.ResponseWriter) (err error) { if err = WriteJSON(w, r.Data); err != nil { panic(err) @@ -48,10 +55,12 @@ func (r JSON) Render(w http.ResponseWriter) (err error) { return } +// WriteContentType (JSON) writes JSON ContentType. func (r JSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } +// WriteJSON marshals the given interface object and writes it with custom ContentType. func WriteJSON(w http.ResponseWriter, obj interface{}) error { writeContentType(w, jsonContentType) jsonBytes, err := json.Marshal(obj) @@ -62,6 +71,7 @@ func WriteJSON(w http.ResponseWriter, obj interface{}) error { return nil } +// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType. func (r IndentedJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) jsonBytes, err := json.MarshalIndent(r.Data, "", " ") @@ -72,10 +82,12 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error { return nil } +// WriteContentType (IndentedJSON) writes JSON ContentType. func (r IndentedJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } +// Render (SecureJSON) marshals the given interface object and writes it with custom ContentType. func (r SecureJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) jsonBytes, err := json.Marshal(r.Data) @@ -90,10 +102,12 @@ func (r SecureJSON) Render(w http.ResponseWriter) error { return nil } +// WriteContentType (SecureJSON) writes JSON ContentType. func (r SecureJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } +// Render (JsonpJSON) marshals the given interface object and writes it and its callback with custom ContentType. func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) ret, err := json.Marshal(r.Data) @@ -115,10 +129,12 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { return nil } +// WriteContentType (JsonpJSON) writes Javascript ContentType. func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonpContentType) } +// Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType. func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) ret, err := json.Marshal(r.Data) @@ -139,6 +155,7 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { return nil } +// WriteContentType (AsciiJSON) writes JSON ContentType. func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonAsciiContentType) } diff --git a/render/json_17.go b/render/json_17.go index 75f7f374..208193c7 100644 --- a/render/json_17.go +++ b/render/json_17.go @@ -12,10 +12,12 @@ import ( "github.com/gin-gonic/gin/internal/json" ) +// PureJSON contains the given interface object. type PureJSON struct { Data interface{} } +// Render (PureJSON) writes custom ContentType and encodes the given interface object. func (r PureJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) encoder := json.NewEncoder(w) @@ -23,6 +25,7 @@ func (r PureJSON) Render(w http.ResponseWriter) error { return encoder.Encode(r.Data) } +// WriteContentType (PureJSON) writes custom ContentType. func (r PureJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } diff --git a/render/msgpack.go b/render/msgpack.go index b6b8aa0f..dc681fcf 100644 --- a/render/msgpack.go +++ b/render/msgpack.go @@ -10,20 +10,24 @@ import ( "github.com/ugorji/go/codec" ) +// MsgPack contains the given interface object. type MsgPack struct { Data interface{} } var msgpackContentType = []string{"application/msgpack; charset=utf-8"} +// WriteContentType (MsgPack) writes MsgPack ContentType. func (r MsgPack) WriteContentType(w http.ResponseWriter) { writeContentType(w, msgpackContentType) } +// Render (MsgPack) encodes the given interface object and writes data with custom ContentType. func (r MsgPack) Render(w http.ResponseWriter) error { return WriteMsgPack(w, r.Data) } +// WriteMsgPack writes MsgPack ContentType and encodes the given interface object. func WriteMsgPack(w http.ResponseWriter, obj interface{}) error { writeContentType(w, msgpackContentType) var mh codec.MsgpackHandle diff --git a/render/protobuf.go b/render/protobuf.go index 34f1e9b5..47895072 100644 --- a/render/protobuf.go +++ b/render/protobuf.go @@ -10,12 +10,14 @@ import ( "github.com/golang/protobuf/proto" ) +// ProtoBuf contains the given interface object. type ProtoBuf struct { Data interface{} } var protobufContentType = []string{"application/x-protobuf"} +// Render (ProtoBuf) marshals the given interface object and writes data with custom ContentType. func (r ProtoBuf) Render(w http.ResponseWriter) error { r.WriteContentType(w) @@ -28,6 +30,7 @@ func (r ProtoBuf) Render(w http.ResponseWriter) error { return nil } +// WriteContentType (ProtoBuf) writes ProtoBuf ContentType. func (r ProtoBuf) WriteContentType(w http.ResponseWriter) { writeContentType(w, protobufContentType) } diff --git a/render/reader.go b/render/reader.go index 7a06cce4..ab60e53a 100644 --- a/render/reader.go +++ b/render/reader.go @@ -10,6 +10,7 @@ import ( "strconv" ) +// Reader contains the IO reader and its length, and custom ContentType and other headers. type Reader struct { ContentType string ContentLength int64 @@ -26,10 +27,12 @@ func (r Reader) Render(w http.ResponseWriter) (err error) { return } +// WriteContentType (Reader) writes custom ContentType. func (r Reader) WriteContentType(w http.ResponseWriter) { writeContentType(w, []string{r.ContentType}) } +// writeHeaders writes custom Header. func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) { header := w.Header() for k, v := range headers { diff --git a/render/redirect.go b/render/redirect.go index a0634f5a..9c145fe2 100644 --- a/render/redirect.go +++ b/render/redirect.go @@ -9,12 +9,14 @@ import ( "net/http" ) +// Redirect contains the http request reference and redirects status code and location. type Redirect struct { Code int Request *http.Request Location string } +// Render (Redirect) redirects the http request to new location and writes redirect response. func (r Redirect) Render(w http.ResponseWriter) error { // todo(thinkerou): go1.6 not support StatusPermanentRedirect(308) // when we upgrade go version we can use http.StatusPermanentRedirect @@ -25,4 +27,5 @@ func (r Redirect) Render(w http.ResponseWriter) error { return nil } +// WriteContentType (Redirect) don't write any ContentType. func (r Redirect) WriteContentType(http.ResponseWriter) {} diff --git a/render/render.go b/render/render.go index df0d1d7c..abfc79fc 100644 --- a/render/render.go +++ b/render/render.go @@ -6,8 +6,11 @@ package render import "net/http" +// Render interface is to be implemented by JSON, XML, HTML, YAML and so on. type Render interface { + // Render writes data with custom ContentType. Render(http.ResponseWriter) error + // WriteContentType writes custom ContentType. WriteContentType(w http.ResponseWriter) } diff --git a/render/text.go b/render/text.go index 7a6acc44..2ea7343c 100644 --- a/render/text.go +++ b/render/text.go @@ -10,6 +10,7 @@ import ( "net/http" ) +// String contains the given interface object slice and its format. type String struct { Format string Data []interface{} @@ -17,15 +18,18 @@ type String struct { var plainContentType = []string{"text/plain; charset=utf-8"} +// Render (String) writes data with custom ContentType. func (r String) Render(w http.ResponseWriter) error { WriteString(w, r.Format, r.Data) return nil } +// WriteContentType (String) writes Plain ContentType. func (r String) WriteContentType(w http.ResponseWriter) { writeContentType(w, plainContentType) } +// WriteString writes data according to its format and write custom ContentType. func WriteString(w http.ResponseWriter, format string, data []interface{}) { writeContentType(w, plainContentType) if len(data) > 0 { diff --git a/render/xml.go b/render/xml.go index cff1ac3e..cc5390a2 100644 --- a/render/xml.go +++ b/render/xml.go @@ -9,17 +9,20 @@ import ( "net/http" ) +// XML contains the given interface object. type XML struct { Data interface{} } var xmlContentType = []string{"application/xml; charset=utf-8"} +// Render (XML) encodes the given interface object and writes data with custom ContentType. func (r XML) Render(w http.ResponseWriter) error { r.WriteContentType(w) return xml.NewEncoder(w).Encode(r.Data) } +// WriteContentType (XML) writes XML ContentType for response. func (r XML) WriteContentType(w http.ResponseWriter) { writeContentType(w, xmlContentType) } diff --git a/render/yaml.go b/render/yaml.go index 25d0ebd4..33bc3254 100644 --- a/render/yaml.go +++ b/render/yaml.go @@ -10,12 +10,14 @@ import ( "gopkg.in/yaml.v2" ) +// YAML contains the given interface object. type YAML struct { Data interface{} } var yamlContentType = []string{"application/x-yaml; charset=utf-8"} +// Render (YAML) marshals the given interface object and writes data with custom ContentType. func (r YAML) Render(w http.ResponseWriter) error { r.WriteContentType(w) @@ -28,6 +30,7 @@ func (r YAML) Render(w http.ResponseWriter) error { return nil } +// WriteContentType (YAML) writes YAML ContentType for response. func (r YAML) WriteContentType(w http.ResponseWriter) { writeContentType(w, yamlContentType) } diff --git a/utils.go b/utils.go index bf32c775..f4532d56 100644 --- a/utils.go +++ b/utils.go @@ -14,8 +14,10 @@ import ( "strings" ) +// BindKey indicates a default bind key. const BindKey = "_gin-gonic/gin/bindkey" +// Bind is a helper function for given interface object and returns a Gin middleware. func Bind(val interface{}) HandlerFunc { value := reflect.ValueOf(val) if value.Kind() == reflect.Ptr { @@ -33,16 +35,14 @@ func Bind(val interface{}) HandlerFunc { } } -// WrapF is a helper function for wrapping http.HandlerFunc -// Returns a Gin middleware +// WrapF is a helper function for wrapping http.HandlerFunc and returns a Gin middleware. func WrapF(f http.HandlerFunc) HandlerFunc { return func(c *Context) { f(c.Writer, c.Request) } } -// WrapH is a helper function for wrapping http.Handler -// Returns a Gin middleware +// WrapH is a helper function for wrapping http.Handler and returns a Gin middleware. func WrapH(h http.Handler) HandlerFunc { return func(c *Context) { h.ServeHTTP(c.Writer, c.Request) From 6db092f778277ef13da427acfa3501890e952436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 15 Sep 2018 15:21:54 +0800 Subject: [PATCH 55/55] chore: add some annotations (#1550) ref #1075 should all annotations and can close #1075 . --- context.go | 6 ++++++ errors.go | 20 +++++++++++++++----- examples/realtime-advanced/main.go | 2 +- gin.go | 6 ++++++ mode.go | 13 +++++++++++-- render/html.go | 4 ++-- routergroup.go | 8 ++++++-- 7 files changed, 47 insertions(+), 12 deletions(-) diff --git a/context.go b/context.go index 063c72f0..809902a3 100644 --- a/context.go +++ b/context.go @@ -711,6 +711,7 @@ func (c *Context) Cookie(name string) (string, error) { return val, nil } +// Render writes the response headers and calls render.Render to render data. func (c *Context) Render(code int, r render.Render) { c.Status(code) @@ -833,6 +834,7 @@ func (c *Context) SSEvent(name string, message interface{}) { }) } +// Stream sends a streaming response. func (c *Context) Stream(step func(w io.Writer) bool) { w := c.Writer clientGone := w.CloseNotify() @@ -854,6 +856,7 @@ func (c *Context) Stream(step func(w io.Writer) bool) { /******** CONTENT NEGOTIATION *******/ /************************************/ +// Negotiate contains all negotiations data. type Negotiate struct { Offered []string HTMLName string @@ -863,6 +866,7 @@ type Negotiate struct { Data interface{} } +// Negotiate calls different Render according acceptable Accept format. func (c *Context) Negotiate(code int, config Negotiate) { switch c.NegotiateFormat(config.Offered...) { case binding.MIMEJSON: @@ -882,6 +886,7 @@ func (c *Context) Negotiate(code int, config Negotiate) { } } +// NegotiateFormat returns an acceptable Accept format. func (c *Context) NegotiateFormat(offered ...string) string { assert1(len(offered) > 0, "you must provide at least one offer") @@ -901,6 +906,7 @@ func (c *Context) NegotiateFormat(offered ...string) string { return "" } +// SetAccepted sets Accept header data. func (c *Context) SetAccepted(formats ...string) { c.Accepted = formats } diff --git a/errors.go b/errors.go index 6f90377e..477b9d58 100644 --- a/errors.go +++ b/errors.go @@ -12,18 +12,24 @@ import ( "github.com/gin-gonic/gin/internal/json" ) +// ErrorType is an unsigned 64-bit error code as defined in the gin spec. type ErrorType uint64 const ( - ErrorTypeBind ErrorType = 1 << 63 // used when c.Bind() fails - ErrorTypeRender ErrorType = 1 << 62 // used when c.Render() fails + // ErrorTypeBind is used when Context.Bind() fails. + ErrorTypeBind ErrorType = 1 << 63 + // ErrorTypeRender is used when Context.Render() fails. + ErrorTypeRender ErrorType = 1 << 62 + // ErrorTypePrivate indicates a private error. ErrorTypePrivate ErrorType = 1 << 0 - ErrorTypePublic ErrorType = 1 << 1 - + // ErrorTypePublic indicates a public error. + ErrorTypePublic ErrorType = 1 << 1 + // ErrorTypeAny indicates other any error. ErrorTypeAny ErrorType = 1<<64 - 1 ErrorTypeNu = 2 ) +// Error represents a error's specification. type Error struct { Err error Type ErrorType @@ -34,11 +40,13 @@ type errorMsgs []*Error var _ error = &Error{} +// SetType sets the error's type. func (msg *Error) SetType(flags ErrorType) *Error { msg.Type = flags return msg } +// SetMeta sets the error's meta data. func (msg *Error) SetMeta(data interface{}) *Error { msg.Meta = data return msg @@ -70,11 +78,12 @@ func (msg *Error) MarshalJSON() ([]byte, error) { return json.Marshal(msg.JSON()) } -// Error implements the error interface +// Error implements the error interface. func (msg Error) Error() string { return msg.Err.Error() } +// IsType judges one error. func (msg *Error) IsType(flags ErrorType) bool { return (msg.Type & flags) > 0 } @@ -138,6 +147,7 @@ func (a errorMsgs) JSON() interface{} { } } +// MarshalJSON implements the json.Marshaller interface. func (a errorMsgs) MarshalJSON() ([]byte, error) { return json.Marshal(a.JSON()) } diff --git a/examples/realtime-advanced/main.go b/examples/realtime-advanced/main.go index a16c58b0..f3ead476 100644 --- a/examples/realtime-advanced/main.go +++ b/examples/realtime-advanced/main.go @@ -20,7 +20,7 @@ func ConfigRuntime() { fmt.Printf("Running with %d CPUs\n", nuCPU) } -// StartWrokers start starsWorker by goroutine. +// StartWorkers start starsWorker by goroutine. func StartWorkers() { go statsWorker() } diff --git a/gin.go b/gin.go index aa62e014..6bff3bd5 100644 --- a/gin.go +++ b/gin.go @@ -26,7 +26,10 @@ var ( defaultAppEngine bool ) +// HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) + +// HandlersChain defines a HandlerFunc array. type HandlersChain []HandlerFunc // Last returns the last handler in the chain. ie. the last handler is the main own. @@ -37,12 +40,14 @@ func (c HandlersChain) Last() HandlerFunc { return nil } +// RouteInfo represents a request route's specification which contains method and path and its handler. type RouteInfo struct { Method string Path string Handler string } +// RoutesInfo defines a RouteInfo array. type RoutesInfo []RouteInfo // Engine is the framework's instance, it contains the muxer, middleware and configuration settings. @@ -155,6 +160,7 @@ func (engine *Engine) allocateContext() *Context { return &Context{engine: engine} } +// Delims sets template left and right delims and returns a Engine instance. func (engine *Engine) Delims(left, right string) *Engine { engine.delims = render.Delims{Left: left, Right: right} return engine diff --git a/mode.go b/mode.go index 9df4e45f..7cb0143a 100644 --- a/mode.go +++ b/mode.go @@ -11,12 +11,16 @@ import ( "github.com/gin-gonic/gin/binding" ) +// ENV_GIN_MODE indicates environment name for gin mode. const ENV_GIN_MODE = "GIN_MODE" const ( - DebugMode = "debug" + // DebugMode indicates gin mode is debug. + DebugMode = "debug" + // ReleaseMode indicates gin mode is relase. ReleaseMode = "release" - TestMode = "test" + // TestMode indicates gin mode is test. + TestMode = "test" ) const ( debugCode = iota @@ -42,6 +46,7 @@ func init() { SetMode(mode) } +// SetMode sets gin mode according to input string. func SetMode(value string) { switch value { case DebugMode, "": @@ -59,14 +64,18 @@ func SetMode(value string) { modeName = value } +// DisableBindValidation closes the default validator. func DisableBindValidation() { binding.Validator = nil } +// EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumberto to +// call the UseNumber method on the JSON Decoder instance. func EnableJsonDecoderUseNumber() { binding.EnableDecoderUseNumber = true } +// Mode returns currently gin mode. func Mode() string { return modeName } diff --git a/render/html.go b/render/html.go index 4d3d5812..6696ece9 100644 --- a/render/html.go +++ b/render/html.go @@ -46,7 +46,7 @@ type HTML struct { var htmlContentType = []string{"text/html; charset=utf-8"} -// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface.. +// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface. func (r HTMLProduction) Instance(name string, data interface{}) Render { return HTML{ Template: r.Template, @@ -55,7 +55,7 @@ func (r HTMLProduction) Instance(name string, data interface{}) Render { } } -// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface.. +// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface. func (r HTMLDebug) Instance(name string, data interface{}) Render { return HTML{ Template: r.loadTemplate(), diff --git a/routergroup.go b/routergroup.go index 876a61b8..be561dee 100644 --- a/routergroup.go +++ b/routergroup.go @@ -11,11 +11,13 @@ import ( "strings" ) +// IRouter defines all router handle interface includes single and group router. type IRouter interface { IRoutes Group(string, ...HandlerFunc) *RouterGroup } +// Iroutes defins all router handle interface. type IRoutes interface { Use(...HandlerFunc) IRoutes @@ -34,8 +36,8 @@ type IRoutes interface { StaticFS(string, http.FileSystem) IRoutes } -// RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix -// and an array of handlers (middleware). +// RouterGroup is used internally to configure router, a RouterGroup is associated with +// a prefix and an array of handlers (middleware). type RouterGroup struct { Handlers HandlersChain basePath string @@ -61,6 +63,8 @@ func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *R } } +// BasePath returns the base path of router group. +// For example, if v := router.Group("/rest/n/v1/api"), v.BasePath() is "/rest/n/v1/api". func (group *RouterGroup) BasePath() string { return group.basePath }