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/README.md b/README.md
index 0bac65b2..c2917a5f 100644
--- a/README.md
+++ b/README.md
@@ -54,6 +54,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
- [Build a single binary with templates](#build-a-single-binary-with-templates)
- [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct)
- [Try to bind body into different structs](#try-to-bind-body-into-different-structs)
+ - [http2 server push](#http2-server-push)
- [Testing](#testing)
- [Users](#users--)
@@ -1656,6 +1657,55 @@ enough to call binding at once.
can be called by `c.ShouldBind()` multiple times without any damage to
performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)).
+### http2 server push
+
+http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information.
+
+[embedmd]:# (examples/http-pusher/main.go go)
+```go
+package main
+
+import (
+ "html/template"
+ "log"
+
+ "github.com/gin-gonic/gin"
+)
+
+var html = template.Must(template.New("https").Parse(`
+
+
+ Https Test
+
+
+
+ Welcome, Ginner!
+
+
+`))
+
+func main() {
+ r := gin.Default()
+ r.Static("/assets", "./assets")
+ r.SetHTMLTemplate(html)
+
+ r.GET("/", func(c *gin.Context) {
+ if pusher := c.Writer.Pusher(); pusher != nil {
+ // use pusher.Push() to do server push
+ if err := pusher.Push("/assets/app.js", nil); err != nil {
+ log.Printf("Failed to push: %v", err)
+ }
+ }
+ c.HTML(200, "https", gin.H{
+ "status": "success",
+ })
+ })
+
+ // Listen and Server in https://127.0.0.1:8080
+ r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key")
+}
+```
+
## Testing
The `net/http/httptest` package is preferable way for HTTP testing.
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
}
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..edc1ca9b
--- /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(c, 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;
+}
diff --git a/examples/http-pusher/assets/app.js b/examples/http-pusher/assets/app.js
new file mode 100644
index 00000000..05271b67
--- /dev/null
+++ b/examples/http-pusher/assets/app.js
@@ -0,0 +1 @@
+console.log("http2 pusher");
diff --git a/examples/http-pusher/main.go b/examples/http-pusher/main.go
new file mode 100644
index 00000000..d4f33aa0
--- /dev/null
+++ b/examples/http-pusher/main.go
@@ -0,0 +1,41 @@
+package main
+
+import (
+ "html/template"
+ "log"
+
+ "github.com/gin-gonic/gin"
+)
+
+var html = template.Must(template.New("https").Parse(`
+
+
+ Https Test
+
+
+
+ Welcome, Ginner!
+
+
+`))
+
+func main() {
+ r := gin.Default()
+ r.Static("/assets", "./assets")
+ r.SetHTMLTemplate(html)
+
+ r.GET("/", func(c *gin.Context) {
+ if pusher := c.Writer.Pusher(); pusher != nil {
+ // use pusher.Push() to do server push
+ if err := pusher.Push("/assets/app.js", nil); err != nil {
+ log.Printf("Failed to push: %v", err)
+ }
+ }
+ c.HTML(200, "https", gin.H{
+ "status": "success",
+ })
+ })
+
+ // Listen and Server in https://127.0.0.1:8080
+ r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key")
+}
diff --git a/examples/http-pusher/testdata/ca.pem b/examples/http-pusher/testdata/ca.pem
new file mode 100644
index 00000000..6c8511a7
--- /dev/null
+++ b/examples/http-pusher/testdata/ca.pem
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla
+Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0
+YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT
+BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7
++L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu
+g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd
+Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV
+HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau
+sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m
+oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG
+Dfcog5wrJytaQ6UA0wE=
+-----END CERTIFICATE-----
diff --git a/examples/http-pusher/testdata/server.key b/examples/http-pusher/testdata/server.key
new file mode 100644
index 00000000..143a5b87
--- /dev/null
+++ b/examples/http-pusher/testdata/server.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD
+M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf
+3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY
+AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm
+V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY
+tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p
+dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q
+K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR
+81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff
+DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd
+aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2
+ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3
+XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe
+F98XJ7tIFfJq
+-----END PRIVATE KEY-----
diff --git a/examples/http-pusher/testdata/server.pem b/examples/http-pusher/testdata/server.pem
new file mode 100644
index 00000000..f3d43fcc
--- /dev/null
+++ b/examples/http-pusher/testdata/server.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET
+MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ
+dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx
+MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV
+BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50
+ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco
+LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg
+zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd
+9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw
+CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy
+em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G
+CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6
+hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh
+y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8
+-----END CERTIFICATE-----
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"},
diff --git a/response_writer.go b/response_writer.go
index 232f00aa..14b1cfc3 100644
--- a/response_writer.go
+++ b/response_writer.go
@@ -16,7 +16,7 @@ const (
defaultStatus = 200
)
-type ResponseWriter interface {
+type responseWriterBase interface {
http.ResponseWriter
http.Hijacker
http.Flusher
diff --git a/response_writer_1.7.go b/response_writer_1.7.go
new file mode 100644
index 00000000..801d196b
--- /dev/null
+++ b/response_writer_1.7.go
@@ -0,0 +1,12 @@
+// +build !go1.8
+
+// 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 gin
+
+// ResponseWriter ...
+type ResponseWriter interface {
+ responseWriterBase
+}
diff --git a/response_writer_1.8.go b/response_writer_1.8.go
new file mode 100644
index 00000000..527c0038
--- /dev/null
+++ b/response_writer_1.8.go
@@ -0,0 +1,25 @@
+// +build go1.8
+
+// 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 gin
+
+import (
+ "net/http"
+)
+
+// ResponseWriter ...
+type ResponseWriter interface {
+ responseWriterBase
+ // get the http.Pusher for server push
+ Pusher() http.Pusher
+}
+
+func (w *responseWriter) Pusher() (pusher http.Pusher) {
+ if pusher, ok := w.ResponseWriter.(http.Pusher); ok {
+ return pusher
+ }
+ return nil
+}
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",