Merge branch 'master' into master

This commit is contained in:
Jun 2018-10-23 23:02:30 +08:00 committed by GitHub
commit 9a495ae025
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 662 additions and 242 deletions

View File

@ -7,12 +7,24 @@ go:
- 1.9.x
- 1.10.x
- 1.11.x
- master
matrix:
fast_finish: true
include:
- go: 1.11.x
env: GO111MODULE=on
git:
depth: 10
before_install:
- if [[ "${GO111MODULE}" = "on" ]]; then mkdir "${HOME}/go"; export GOPATH="${HOME}/go"; fi
install:
- make install
- if [[ "${GO111MODULE}" = "on" ]]; then go mod download; else make install; fi
- if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi
- if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi
go_import_path: github.com/gin-gonic/gin

View File

@ -1,7 +1,9 @@
GO ?= go
GOFMT ?= gofmt "-s"
PACKAGES ?= $(shell go list ./... | grep -v /vendor/)
VETPACKAGES ?= $(shell go list ./... | grep -v /vendor/ | grep -v /examples/)
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/*")
TESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|binding$$|render$$' | grep -v examples)
all: install
@ -10,7 +12,14 @@ install: deps
.PHONY: test
test:
sh coverage.sh
echo "mode: count" > coverage.out
for d in $(TESTFOLDER); do \
$(GO) test -v -covermode=count -coverprofile=profile.out $$d; \
if [ -f profile.out ]; then \
cat profile.out | grep -v "mode:" >> coverage.out; \
rm profile.out; \
fi; \
done
.PHONY: fmt
fmt:
@ -18,7 +27,6 @@ fmt:
.PHONY: fmt-check
fmt-check:
# get all go files and run go fmt on them
@diff=$$($(GOFMT) -d $(GOFILES)); \
if [ -n "$$diff" ]; then \
echo "Please run 'make fmt' and commit the result:"; \
@ -27,14 +35,14 @@ fmt-check:
fi;
vet:
go vet $(VETPACKAGES)
$(GO) vet $(VETPACKAGES)
deps:
@hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/kardianos/govendor; \
$(GO) get -u github.com/kardianos/govendor; \
fi
@hash embedmd > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/campoy/embedmd; \
$(GO) get -u github.com/campoy/embedmd; \
fi
embedmd:
@ -43,20 +51,26 @@ embedmd:
.PHONY: lint
lint:
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/golang/lint/golint; \
$(GO) get -u golang.org/x/lint/golint; \
fi
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
.PHONY: misspell-check
misspell-check:
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/client9/misspell/cmd/misspell; \
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
fi
misspell -error $(GOFILES)
.PHONY: misspell
misspell:
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/client9/misspell/cmd/misspell; \
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
fi
misspell -w $(GOFILES)
.PHONY: tools
tools:
go install golang.org/x/lint/golint; \
go install github.com/client9/misspell/cmd/misspell; \
go install github.com/campoy/embedmd;

View File

@ -9,6 +9,7 @@
[![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)
[![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases)
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.
@ -59,6 +60,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)
- [Define format for the log of routes](#define-format-for-the-log-of-routes)
- [Set and get a cookie](#set-and-get-a-cookie)
- [Testing](#testing)
- [Users](#users)
@ -1720,11 +1722,11 @@ type StructX struct {
}
type StructY struct {
Y StructX `form:"name_y"` // HERE hava form
Y StructX `form:"name_y"` // HERE have form
}
type StructZ struct {
Z *StructZ `form:"name_z"` // HERE hava form
Z *StructZ `form:"name_z"` // HERE have form
}
```
@ -1879,6 +1881,35 @@ func main() {
}
```
### Set and get a cookie
```go
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/cookie", func(c *gin.Context) {
cookie, err := c.Cookie("gin_cookie")
if err != nil {
cookie = "NotSet"
c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
}
fmt.Printf("Cookie value: %s \n", cookie)
})
router.Run()
}
```
## Testing
@ -1933,3 +1964,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor
* [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.
* [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow.

View File

@ -122,7 +122,7 @@ func TestBasicAuth401(t *testing.T) {
assert.False(t, called)
assert.Equal(t, http.StatusUnauthorized, w.Code)
assert.Equal(t, "Basic realm=\"Authorization Required\"", w.HeaderMap.Get("WWW-Authenticate"))
assert.Equal(t, "Basic realm=\"Authorization Required\"", w.Header().Get("WWW-Authenticate"))
}
func TestBasicAuth401WithCustomRealm(t *testing.T) {
@ -142,5 +142,5 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) {
assert.False(t, called)
assert.Equal(t, http.StatusUnauthorized, w.Code)
assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.HeaderMap.Get("WWW-Authenticate"))
assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.Header().Get("WWW-Authenticate"))
}

View File

@ -416,7 +416,6 @@ func (c *Context) PostFormArray(key string) []string {
// a boolean value whether at least one value exists for the given key.
func (c *Context) GetPostFormArray(key string) ([]string, bool) {
req := c.Request
req.ParseForm()
req.ParseMultipartForm(c.engine.MaxMultipartMemory)
if values := req.PostForm[key]; len(values) > 0 {
return values, true
@ -439,7 +438,6 @@ func (c *Context) PostFormMap(key string) map[string]string {
// 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)
@ -467,6 +465,11 @@ func (c *Context) get(m map[string][]string, key string) (map[string]string, boo
// FormFile returns the first file for the provided form key.
func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
if c.Request.MultipartForm == nil {
if err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
return nil, err
}
}
_, fh, err := c.Request.FormFile(name)
return fh, err
}

View File

@ -23,5 +23,5 @@ func TestContextRenderPureJSON(t *testing.T) {
c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
}

View File

@ -84,6 +84,19 @@ func TestContextFormFile(t *testing.T) {
assert.NoError(t, c.SaveUploadedFile(f, "test"))
}
func TestContextFormFileFailed(t *testing.T) {
buf := new(bytes.Buffer)
mw := multipart.NewWriter(buf)
mw.Close()
c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "/", nil)
c.Request.Header.Set("Content-Type", mw.FormDataContentType())
c.engine.MaxMultipartMemory = 8 << 20
f, err := c.FormFile("file")
assert.Error(t, err)
assert.Nil(t, f)
}
func TestContextMultipartForm(t *testing.T) {
buf := new(bytes.Buffer)
mw := multipart.NewWriter(buf)
@ -628,7 +641,7 @@ func TestContextRenderJSON(t *testing.T) {
assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
}
// Tests that the response is serialized as JSONP
@ -642,7 +655,7 @@ func TestContextRenderJSONP(t *testing.T) {
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"))
assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type"))
}
// Tests that the response is serialized as JSONP
@ -656,7 +669,7 @@ func TestContextRenderJSONPWithoutCallback(t *testing.T) {
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"))
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
}
// Tests that no JSON is rendered if code is 204
@ -668,7 +681,7 @@ func TestContextRenderNoContentJSON(t *testing.T) {
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"))
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
}
// Tests that the response is serialized as JSON
@ -682,7 +695,7 @@ func TestContextRenderAPIJSON(t *testing.T) {
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"))
assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type"))
}
// Tests that no Custom JSON is rendered if code is 204
@ -695,7 +708,7 @@ func TestContextRenderNoContentAPIJSON(t *testing.T) {
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")
assert.Equal(t, w.Header().Get("Content-Type"), "application/vnd.api+json")
}
// Tests that the response is serialized as JSON
@ -708,7 +721,7 @@ func TestContextRenderIndentedJSON(t *testing.T) {
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"))
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
}
// Tests that no Custom JSON is rendered if code is 204
@ -720,7 +733,7 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) {
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"))
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
}
// Tests that the response is serialized as Secure JSON
@ -734,7 +747,7 @@ func TestContextRenderSecureJSON(t *testing.T) {
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"))
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
}
// Tests that no Custom JSON is rendered if code is 204
@ -746,7 +759,7 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) {
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"))
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
}
func TestContextRenderNoContentAsciiJSON(t *testing.T) {
@ -757,7 +770,7 @@ func TestContextRenderNoContentAsciiJSON(t *testing.T) {
assert.Equal(t, http.StatusNoContent, w.Code)
assert.Empty(t, w.Body.String())
assert.Equal(t, "application/json", w.HeaderMap.Get("Content-Type"))
assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
}
// Tests that the response executes the templates
@ -773,7 +786,7 @@ func TestContextRenderHTML(t *testing.T) {
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"))
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
}
func TestContextRenderHTML2(t *testing.T) {
@ -797,7 +810,7 @@ func TestContextRenderHTML2(t *testing.T) {
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"))
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
}
// Tests that no HTML is rendered if code is 204
@ -811,7 +824,7 @@ func TestContextRenderNoContentHTML(t *testing.T) {
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"))
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
}
// TestContextXML tests that the response is serialized as XML
@ -824,7 +837,7 @@ func TestContextRenderXML(t *testing.T) {
assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "<map><foo>bar</foo></map>", w.Body.String())
assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type"))
}
// Tests that no XML is rendered if code is 204
@ -836,7 +849,7 @@ func TestContextRenderNoContentXML(t *testing.T) {
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"))
assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type"))
}
// TestContextString tests that the response is returned
@ -849,7 +862,7 @@ func TestContextRenderString(t *testing.T) {
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"))
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
}
// Tests that no String is rendered if code is 204
@ -861,7 +874,7 @@ func TestContextRenderNoContentString(t *testing.T) {
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"))
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
}
// TestContextString tests that the response is returned
@ -875,7 +888,7 @@ func TestContextRenderHTMLString(t *testing.T) {
assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "<html>string 3</html>", w.Body.String())
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
}
// Tests that no HTML String is rendered if code is 204
@ -888,7 +901,7 @@ func TestContextRenderNoContentHTMLString(t *testing.T) {
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"))
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
}
// TestContextData tests that the response can be written from `bytesting`
@ -901,7 +914,7 @@ func TestContextRenderData(t *testing.T) {
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"))
assert.Equal(t, "text/csv", w.Header().Get("Content-Type"))
}
// Tests that no Custom Data is rendered if code is 204
@ -913,7 +926,7 @@ func TestContextRenderNoContentData(t *testing.T) {
assert.Equal(t, http.StatusNoContent, w.Code)
assert.Empty(t, w.Body.String())
assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type"))
assert.Equal(t, "text/csv", w.Header().Get("Content-Type"))
}
func TestContextRenderSSE(t *testing.T) {
@ -942,7 +955,7 @@ func TestContextRenderFile(t *testing.T) {
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"))
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
}
// TestContextRenderYAML tests that the response is serialized as YAML
@ -955,7 +968,7 @@ func TestContextRenderYAML(t *testing.T) {
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"))
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
}
// TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf
@ -979,7 +992,7 @@ func TestContextRenderProtoBuf(t *testing.T) {
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"))
assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type"))
}
func TestContextHeaders(t *testing.T) {
@ -1062,7 +1075,7 @@ func TestContextNegotiationWithJSON(t *testing.T) {
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"))
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
}
func TestContextNegotiationWithXML(t *testing.T) {
@ -1077,7 +1090,7 @@ func TestContextNegotiationWithXML(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "<map><foo>bar</foo></map>", w.Body.String())
assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type"))
}
func TestContextNegotiationWithHTML(t *testing.T) {
@ -1095,7 +1108,7 @@ func TestContextNegotiationWithHTML(t *testing.T) {
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"))
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
}
func TestContextNegotiationNotSupport(t *testing.T) {
@ -1131,7 +1144,7 @@ func TestContextNegotiationFormatWithAccept(t *testing.T) {
assert.Empty(t, c.NegotiateFormat(MIMEJSON))
}
func TestContextNegotiationFormatCustum(t *testing.T) {
func TestContextNegotiationFormatCustom(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "/", nil)
c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
@ -1627,9 +1640,9 @@ func TestContextRenderDataFromReader(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, body, w.Body.String())
assert.Equal(t, contentType, w.HeaderMap.Get("Content-Type"))
assert.Equal(t, fmt.Sprintf("%d", contentLength), w.HeaderMap.Get("Content-Length"))
assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition"))
assert.Equal(t, contentType, w.Header().Get("Content-Type"))
assert.Equal(t, fmt.Sprintf("%d", contentLength), w.Header().Get("Content-Length"))
assert.Equal(t, extraHeaders["Content-Disposition"], w.Header().Get("Content-Disposition"))
}
type TestResponseRecorder struct {

View File

@ -1,13 +0,0 @@
#!/usr/bin/env bash
set -e
echo "mode: count" > coverage.out
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
rm profile.out
fi
done

View File

@ -8,14 +8,21 @@ import (
"bytes"
"fmt"
"html/template"
"os"
"runtime"
"strconv"
"strings"
)
const ginSupportMinGoVer = 6
// IsDebugging returns true if the framework is running in debug mode.
// Use SetMode(gin.ReleaseMode) to disable debug mode.
func IsDebugging() bool {
return ginMode == debugCode
}
// DebugPrintRouteFunc indicates debug log output format.
var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int)
func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) {
@ -44,14 +51,25 @@ func debugPrintLoadTemplate(tmpl *template.Template) {
func debugPrint(format string, values ...interface{}) {
if IsDebugging() {
fmt.Printf("[GIN-debug] "+format, values...)
fmt.Fprintf(os.Stderr, "[GIN-debug] "+format, values...)
}
}
func getMinVer(v string) (uint64, error) {
first := strings.IndexByte(v, '.')
last := strings.LastIndexByte(v, '.')
if first == last {
return strconv.ParseUint(v[first+1:], 10, 64)
}
return strconv.ParseUint(v[first+1:last], 10, 64)
}
func debugPrintWARNINGDefault() {
debugPrint(`[WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.
if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
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.
`)

View File

@ -11,6 +11,7 @@ import (
"io"
"log"
"os"
"runtime"
"sync"
"testing"
@ -88,7 +89,12 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
debugPrintWARNINGDefault()
SetMode(TestMode)
})
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", re)
m, e := getMinVer(runtime.Version())
if e == nil && m <= ginSupportMinGoVer {
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", re)
} else {
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
}
}
func TestDebugPrintWARNINGNew(t *testing.T) {
@ -129,3 +135,18 @@ func captureOutput(f func()) string {
writer.Close()
return <-out
}
func TestGetMinVer(t *testing.T) {
var m uint64
var e error
_, e = getMinVer("go1")
assert.NotNil(t, e)
m, e = getMinVer("go1.1")
assert.Equal(t, uint64(1), m)
assert.Nil(t, e)
m, e = getMinVer("go1.1.1")
assert.Nil(t, e)
assert.Equal(t, uint64(1), m)
_, e = getMinVer("go1.1.1.1")
assert.NotNil(t, e)
}

View File

@ -0,0 +1,137 @@
# How to build one effective middleware?
## Consitituent part
The middleware has two parts:
- part one is what is executed once, when you initalize your middleware. That's where you set up all the global objects, logicals etc. Everything that happens one per application lifetime.
- part two is what executes on every request. For example, a database middleware you simply inject your "global" database object into the context. Once it's inside the context, you can retrieve it from within other middlewares and your handler furnction.
```go
func funcName(params string) gin.HandlerFunc {
// <---
// This is part one
// --->
// The follow code is an example
if err := check(params); err != nil {
panic(err)
}
return func(c *gin.Context) {
// <---
// This is part two
// --->
// The follow code is an example
c.Set("TestVar", params)
c.Next()
}
}
```
## Execution process
Firstly, we have the follow example code:
```go
func main() {
router := gin.Default()
router.Use(globalMiddleware())
router.GET("/rest/n/api/*some", mid1(), mid2(), handler)
router.Run()
}
func globalMiddleware() gin.HandlerFunc {
fmt.Println("globalMiddleware...1")
return func(c *gin.Context) {
fmt.Println("globalMiddleware...2")
c.Next()
fmt.Println("globalMiddleware...3")
}
}
func handler(c *gin.Context) {
fmt.Println("exec handler.")
}
func mid1() gin.HandlerFunc {
fmt.Println("mid1...1")
return func(c *gin.Context) {
fmt.Println("mid1...2")
c.Next()
fmt.Println("mid1...3")
}
}
func mid2() gin.HandlerFunc {
fmt.Println("mid2...1")
return func(c *gin.Context) {
fmt.Println("mid2...2")
c.Next()
fmt.Println("mid2...3")
}
}
```
According to [Consitituent part](#consitituent-part) said, when we run the gin process, **part one** will execute firstly and will print the follow information:
```go
globalMiddleware...1
mid1...1
mid2...1
```
And init order are:
```go
globalMiddleware...1
|
v
mid1...1
|
v
mid2...1
```
When we curl one request `curl -v localhost:8080/rest/n/api/some`, **part two** will execute their middleware and output the following information:
```go
globalMiddleware...2
mid1...2
mid2...2
exec handler.
mid2...3
mid1...3
globalMiddleware...3
```
In other words, run order are:
```go
globalMiddleware...2
|
v
mid1...2
|
v
mid2...2
|
v
exec handler.
|
v
mid2...3
|
v
mid1...3
|
v
globalMiddleware...3
```

16
gin.go
View File

@ -38,9 +38,10 @@ func (c HandlersChain) Last() HandlerFunc {
// RouteInfo represents a request route's specification which contains method and path and its handler.
type RouteInfo struct {
Method string
Path string
Handler string
Method string
Path string
Handler string
HandlerFunc HandlerFunc
}
// RoutesInfo defines a RouteInfo array.
@ -266,10 +267,12 @@ func (engine *Engine) Routes() (routes RoutesInfo) {
func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
path += root.path
if len(root.handlers) > 0 {
handlerFunc := root.handlers.Last()
routes = append(routes, RouteInfo{
Method: method,
Path: path,
Handler: nameOfFunction(root.handlers.Last()),
Method: method,
Path: path,
Handler: nameOfFunction(handlerFunc),
HandlerFunc: handlerFunc,
})
}
for _, child := range root.children {
@ -336,7 +339,6 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
func (engine *Engine) HandleContext(c *Context) {
c.reset()
engine.handleHTTPRequest(c)
engine.pool.Put(c)
}
func (engine *Engine) handleHTTPRequest(c *Context) {

View File

@ -6,12 +6,14 @@ package gin
import (
"bufio"
"crypto/tls"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"os"
"sync"
"testing"
"time"
@ -19,7 +21,14 @@ import (
)
func testRequest(t *testing.T, url string) {
resp, err := http.Get(url)
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
client := &http.Client{Transport: tr}
resp, err := client.Get(url)
assert.NoError(t, err)
defer resp.Body.Close()
@ -44,6 +53,22 @@ func TestRunEmpty(t *testing.T) {
testRequest(t, "http://localhost:8080/example")
}
func TestRunTLS(t *testing.T) {
router := New()
go func() {
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
assert.NoError(t, router.RunTLS(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
}()
// have to wait for the goroutine to start and run the server
// otherwise the main thread will complete
time.Sleep(5 * time.Millisecond)
assert.Error(t, router.RunTLS(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
testRequest(t, "https://localhost:8443/example")
}
func TestRunEmptyWithEnv(t *testing.T) {
os.Setenv("PORT", "3123")
router := New()
@ -119,6 +144,29 @@ func TestWithHttptestWithAutoSelectedPort(t *testing.T) {
testRequest(t, ts.URL+"/example")
}
func TestConcurrentHandleContext(t *testing.T) {
router := New()
router.GET("/", func(c *Context) {
c.Request.URL.Path = "/example"
router.HandleContext(c)
})
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
ts := httptest.NewServer(router)
defer ts.Close()
var wg sync.WaitGroup
iterations := 200
wg.Add(iterations)
for i := 0; i < iterations; i++ {
go func() {
testRequest(t, ts.URL+"/")
wg.Done()
}()
}
wg.Wait()
}
// func TestWithHttptestWithSpecifiedPort(t *testing.T) {
// router := New()
// router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })

View File

@ -10,6 +10,7 @@ import (
"html/template"
"io/ioutil"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"time"
@ -22,105 +23,105 @@ func formatAsDate(t time.Time) string {
return fmt.Sprintf("%d/%02d/%02d", year, month, day)
}
func setupHTMLFiles(t *testing.T, mode string, tls bool) func() {
go func() {
SetMode(mode)
router := New()
router.Delims("{[{", "}]}")
router.SetFuncMap(template.FuncMap{
"formatAsDate": formatAsDate,
func setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine)) *httptest.Server {
SetMode(mode)
router := New()
router.Delims("{[{", "}]}")
router.SetFuncMap(template.FuncMap{
"formatAsDate": formatAsDate,
})
loadMethod(router)
router.GET("/test", func(c *Context) {
c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
})
router.GET("/raw", func(c *Context) {
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
})
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"})
})
router.GET("/raw", func(c *Context) {
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
})
})
if tls {
// these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1`
router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")
} else {
router.Run(":8888")
}
}()
t.Log("waiting 1 second for server startup")
time.Sleep(1 * time.Second)
return func() {}
})
var ts *httptest.Server
if tls {
ts = httptest.NewTLSServer(router)
} else {
ts = httptest.NewServer(router)
}
return ts
}
func setupHTMLGlob(t *testing.T, mode string, tls bool) func() {
go func() {
SetMode(mode)
router := New()
router.Delims("{[{", "}]}")
router.SetFuncMap(template.FuncMap{
"formatAsDate": formatAsDate,
})
router.LoadHTMLGlob("./testdata/template/*")
router.GET("/test", func(c *Context) {
c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
})
router.GET("/raw", func(c *Context) {
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
})
})
if tls {
// these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1`
router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")
} else {
router.Run(":8888")
}
}()
t.Log("waiting 1 second for server startup")
time.Sleep(1 * time.Second)
return func() {}
}
func TestLoadHTMLGlobDebugMode(t *testing.T) {
ts := setupHTMLFiles(
t,
DebugMode,
false,
func(router *Engine) {
router.LoadHTMLGlob("./testdata/template/*")
},
)
defer ts.Close()
func TestLoadHTMLGlob(t *testing.T) {
td := setupHTMLGlob(t, DebugMode, false)
res, err := http.Get("http://127.0.0.1:8888/test")
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
if err != nil {
fmt.Println(err)
}
resp, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
td()
}
func TestLoadHTMLGlob2(t *testing.T) {
td := setupHTMLGlob(t, TestMode, false)
res, err := http.Get("http://127.0.0.1:8888/test")
func TestLoadHTMLGlobTestMode(t *testing.T) {
ts := setupHTMLFiles(
t,
TestMode,
false,
func(router *Engine) {
router.LoadHTMLGlob("./testdata/template/*")
},
)
defer ts.Close()
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
if err != nil {
fmt.Println(err)
}
resp, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
td()
}
func TestLoadHTMLGlob3(t *testing.T) {
td := setupHTMLGlob(t, ReleaseMode, false)
res, err := http.Get("http://127.0.0.1:8888/test")
func TestLoadHTMLGlobReleaseMode(t *testing.T) {
ts := setupHTMLFiles(
t,
ReleaseMode,
false,
func(router *Engine) {
router.LoadHTMLGlob("./testdata/template/*")
},
)
defer ts.Close()
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
if err != nil {
fmt.Println(err)
}
resp, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
td()
}
func TestLoadHTMLGlobUsingTLS(t *testing.T) {
td := setupHTMLGlob(t, DebugMode, true)
ts := setupHTMLFiles(
t,
DebugMode,
true,
func(router *Engine) {
router.LoadHTMLGlob("./testdata/template/*")
},
)
defer ts.Close()
// Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error
tr := &http.Transport{
TLSClientConfig: &tls.Config{
@ -128,29 +129,33 @@ func TestLoadHTMLGlobUsingTLS(t *testing.T) {
},
}
client := &http.Client{Transport: tr}
res, err := client.Get("https://127.0.0.1:9999/test")
res, err := client.Get(fmt.Sprintf("%s/test", ts.URL))
if err != nil {
fmt.Println(err)
}
resp, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
td()
}
func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
time.Now()
td := setupHTMLGlob(t, DebugMode, false)
res, err := http.Get("http://127.0.0.1:8888/raw")
ts := setupHTMLFiles(
t,
DebugMode,
false,
func(router *Engine) {
router.LoadHTMLGlob("./testdata/template/*")
},
)
defer ts.Close()
res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL))
if err != nil {
fmt.Println(err)
}
resp, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, "Date: 2017/07/01\n", string(resp))
td()
}
func init() {
@ -164,59 +169,77 @@ func TestCreateEngine(t *testing.T) {
assert.Empty(t, router.Handlers)
}
// func TestLoadHTMLDebugMode(t *testing.T) {
// router := New()
// SetMode(DebugMode)
// router.LoadHTMLGlob("*.testtmpl")
// r := router.HTMLRender.(render.HTMLDebug)
// assert.Empty(t, r.Files)
// assert.Equal(t, "*.testtmpl", r.Glob)
//
// router.LoadHTMLFiles("index.html.testtmpl", "login.html.testtmpl")
// r = router.HTMLRender.(render.HTMLDebug)
// assert.Empty(t, r.Glob)
// assert.Equal(t, []string{"index.html", "login.html"}, r.Files)
// SetMode(TestMode)
// }
func TestLoadHTMLFilesTestMode(t *testing.T) {
ts := setupHTMLFiles(
t,
TestMode,
false,
func(router *Engine) {
router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl")
},
)
defer ts.Close()
func TestLoadHTMLFiles(t *testing.T) {
td := setupHTMLFiles(t, TestMode, false)
res, err := http.Get("http://127.0.0.1:8888/test")
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
if err != nil {
fmt.Println(err)
}
resp, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
td()
}
func TestLoadHTMLFiles2(t *testing.T) {
td := setupHTMLFiles(t, DebugMode, false)
res, err := http.Get("http://127.0.0.1:8888/test")
func TestLoadHTMLFilesDebugMode(t *testing.T) {
ts := setupHTMLFiles(
t,
DebugMode,
false,
func(router *Engine) {
router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl")
},
)
defer ts.Close()
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
if err != nil {
fmt.Println(err)
}
resp, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
td()
}
func TestLoadHTMLFiles3(t *testing.T) {
td := setupHTMLFiles(t, ReleaseMode, false)
res, err := http.Get("http://127.0.0.1:8888/test")
func TestLoadHTMLFilesReleaseMode(t *testing.T) {
ts := setupHTMLFiles(
t,
ReleaseMode,
false,
func(router *Engine) {
router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl")
},
)
defer ts.Close()
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
if err != nil {
fmt.Println(err)
}
resp, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
td()
}
func TestLoadHTMLFilesUsingTLS(t *testing.T) {
td := setupHTMLFiles(t, TestMode, true)
ts := setupHTMLFiles(
t,
TestMode,
true,
func(router *Engine) {
router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl")
},
)
defer ts.Close()
// Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error
tr := &http.Transport{
TLSClientConfig: &tls.Config{
@ -224,28 +247,33 @@ func TestLoadHTMLFilesUsingTLS(t *testing.T) {
},
}
client := &http.Client{Transport: tr}
res, err := client.Get("https://127.0.0.1:9999/test")
res, err := client.Get(fmt.Sprintf("%s/test", ts.URL))
if err != nil {
fmt.Println(err)
}
resp, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
td()
}
func TestLoadHTMLFilesFuncMap(t *testing.T) {
time.Now()
td := setupHTMLFiles(t, TestMode, false)
res, err := http.Get("http://127.0.0.1:8888/raw")
ts := setupHTMLFiles(
t,
TestMode,
false,
func(router *Engine) {
router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl")
},
)
defer ts.Close()
res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL))
if err != nil {
fmt.Println(err)
}
resp, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, "Date: 2017/07/01\n", string(resp))
td()
}
func TestAddRoute(t *testing.T) {

17
go.mod
View File

@ -1,25 +1,28 @@
module github.com/gin-gonic/gin
require (
github.com/davecgh/go-spew v0.0.0-20180221232628-8991bc29aa16 // indirect
github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296
github.com/client9/misspell v0.3.4
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7
github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b
github.com/golang/protobuf v1.2.0
github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15
github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b
github.com/json-iterator/go v1.1.5
github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227
github.com/mattn/go-isatty v0.0.3
github.com/mattn/go-isatty v0.0.4
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.2.2
github.com/thinkerou/favicon v0.0.0-20170710140520-94a442a49da6
github.com/thinkerou/favicon v0.1.0
github.com/ugorji/go v1.1.1
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d
golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e
golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e // indirect
golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba // indirect
google.golang.org/grpc v1.15.0
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/go-playground/validator.v8 v8.18.2

33
go.sum
View File

@ -1,7 +1,10 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296 h1:tRsilif6pbtt+PX6uRoyGd+qR+4ZPucFZLHlc3Ak6z8=
github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296/go.mod h1:/dBk8ICkslPCmyRdn4azP+QvBxL6Eg3EYxUGI9xMMFw=
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v0.0.0-20180221232628-8991bc29aa16 h1:HqkufMBR7waVfFFRABWqHa1WgTvjtVDJTLJe3CR576A=
github.com/davecgh/go-spew v0.0.0-20180221232628-8991bc29aa16/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66 h1:QnnoVdChKs+GeTvN4rPYTW6b5U6M3HMEvQ/+x4IGtfY=
github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66/go.mod h1:kTEh6M2J/mh7nsskr28alwLCXm/DSG5OSA/o31yy2XU=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY=
@ -16,13 +19,13 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 h1:cW/amwGEJK5MSKntPXRjX4dxs/nGxGT8gXKIsKFmHGc=
github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15/go.mod h1:Fdm/oWRW+CH8PRbLntksCNtmcCBximKPkVQYvmMl80k=
github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b h1:X61dhFTE1Au92SvyF8HyAwdjWqiSdfBgFR7wTxC0+uU=
github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 h1:KIaAZ/V+/0/6BOULrmBQ9T1ed8BkKqGIjIKW923nJuo=
github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227/go.mod h1:ruMr5t05gVho4tuDv0PbI0Bb8nOxc/5Y6JzRHe/yfA0=
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
@ -31,23 +34,27 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/thinkerou/favicon v0.0.0-20170710140520-94a442a49da6 h1:d/LEgOfWe+AlOCz/kzmkvlO+gq9LRGhjSHqt2nx8Muc=
github.com/thinkerou/favicon v0.0.0-20170710140520-94a442a49da6/go.mod h1:HL7Pap5kOluZv1ku34pZo/AJ44GaxMEPFZ3pmuexV2s=
github.com/thinkerou/favicon v0.1.0 h1:eWMISKTpHq2G8HOuKn7ydD55j5DDehx94b0C2y8ABMs=
github.com/thinkerou/favicon v0.1.0/go.mod h1:HL7Pap5kOluZv1ku34pZo/AJ44GaxMEPFZ3pmuexV2s=
github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w=
github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b h1:2b9XGzhjiYsYPnKXoEfL7klWZQIt8IfyRCz62gCqqlQ=
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e h1:IzypfodbhbnViNUO/MEh0FzCUooG97cIGfdggUrUSyU=
golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I=
golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd h1:cgsAvzdqkDKdI02tIvDjO225vDPHMDCgfKqx5KEVI7U=
golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba h1:nZJIJPGow0Kf9bU9QTc1U6OXbs/7Hu4e+cNv+hxH+Zc=
golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52 h1:JG/0uqcGdTNgq7FdU+61l5Pdmb8putNZlXb65bJBROs=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=

View File

@ -17,7 +17,7 @@ import (
var (
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109})
yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109})
yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109})
red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109})
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109})

View File

@ -85,7 +85,7 @@ func TestLogger(t *testing.T) {
func TestColorForMethod(t *testing.T) {
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), colorForMethod("GET"), "get should be blue")
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), colorForMethod("POST"), "post should be cyan")
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForMethod("PUT"), "put should be yellow")
assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 51, 109}), colorForMethod("PUT"), "put should be yellow")
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForMethod("DELETE"), "delete should be red")
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForMethod("PATCH"), "patch should be green")
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 53, 109}), colorForMethod("HEAD"), "head should be magenta")
@ -96,7 +96,7 @@ func TestColorForMethod(t *testing.T) {
func TestColorForStatus(t *testing.T) {
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, 48, 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")
}

View File

@ -39,8 +39,12 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
if err := recover(); err != nil {
if logger != nil {
stack := stack(3)
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)
if IsDebugging() {
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)
} else {
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s", timeFormat(time.Now()), err, stack, reset)
}
}
c.AbortWithStatus(http.StatusInternalServerError)
}

View File

@ -24,9 +24,19 @@ func TestPanicInHandler(t *testing.T) {
w := performRequest(router, "GET", "/recovery")
// TEST
assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Contains(t, buffer.String(), "GET /recovery")
assert.Contains(t, buffer.String(), "panic recovered")
assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem")
assert.Contains(t, buffer.String(), "TestPanicInHandler")
assert.NotContains(t, buffer.String(), "GET /recovery")
// Debug mode prints the request
SetMode(DebugMode)
// RUN
w = performRequest(router, "GET", "/recovery")
// TEST
assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Contains(t, buffer.String(), "GET /recovery")
}
// TestPanicWithAbort assert that panic has been recovered even if context.Abort was used.

View File

@ -480,7 +480,7 @@ func TestRenderReader(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, body, w.Body.String())
assert.Equal(t, "image/png", w.HeaderMap.Get("Content-Type"))
assert.Equal(t, strconv.Itoa(len(body)), w.HeaderMap.Get("Content-Length"))
assert.Equal(t, headers["Content-Disposition"], w.HeaderMap.Get("Content-Disposition"))
assert.Equal(t, "image/png", w.Header().Get("Content-Type"))
assert.Equal(t, strconv.Itoa(len(body)), w.Header().Get("Content-Length"))
assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition"))
}

View File

@ -17,7 +17,7 @@ type IRouter interface {
Group(string, ...HandlerFunc) *RouterGroup
}
// Iroutes defins all router handle interface.
// IRoutes defines all router handle interface.
type IRoutes interface {
Use(...HandlerFunc) IRoutes

View File

@ -267,7 +267,7 @@ func TestRouteStaticFile(t *testing.T) {
assert.Equal(t, w, w2)
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"))
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
w3 := performRequest(router, "HEAD", "/using_static/"+filename)
w4 := performRequest(router, "HEAD", "/result")
@ -285,7 +285,7 @@ func TestRouteStaticListingDir(t *testing.T) {
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"))
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
}
// TestHandleHeadToDir - ensure the root/sub dir handles properly
@ -312,10 +312,10 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
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")
assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.HeaderMap.Get("Expires"))
assert.Equal(t, "Gin Framework", w.HeaderMap.Get("x-GIN"))
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
assert.NotEqual(t, w.Header().Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST")
assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires"))
assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN"))
}
func TestRouteNotAllowedEnabled(t *testing.T) {

25
tools.go Normal file
View File

@ -0,0 +1,25 @@
// 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 tools
// This file exists to cause `go mod` and `go get` to believe these tools
// are dependencies, even though they are not runtime dependencies of any
// gin package. This means they will appear in `go.mod` file, but will not
// be a part of the build.
package gin
import (
_ "github.com/campoy/embedmd"
_ "github.com/client9/misspell/cmd/misspell"
_ "github.com/dustin/go-broadcast"
_ "github.com/gin-gonic/autotls"
_ "github.com/jessevdk/go-assets"
_ "github.com/manucorporat/stats"
_ "github.com/thinkerou/favicon"
_ "golang.org/x/crypto/acme/autocert"
_ "golang.org/x/lint/golint"
_ "google.golang.org/grpc"
)

11
tree.go
View File

@ -193,9 +193,16 @@ func (n *node) addRoute(path string, handlers HandlersChain) {
}
}
panic("path segment '" + path +
pathSeg := path
if n.nType != catchAll {
pathSeg = strings.SplitN(path, "/", 2)[0]
}
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
panic("'" + pathSeg +
"' in new path '" + fullPath +
"' conflicts with existing wildcard '" + n.path +
"' in path '" + fullPath + "'")
"' in existing prefix '" + prefix +
"'")
}
c := path[0]

View File

@ -5,7 +5,9 @@
package gin
import (
"fmt"
"reflect"
"regexp"
"strings"
"testing"
)
@ -656,3 +658,43 @@ func TestTreeInvalidNodeType(t *testing.T) {
t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
}
}
func TestTreeWildcardConflictEx(t *testing.T) {
conflicts := [...]struct {
route string
segPath string
existPath string
existSegPath string
}{
{"/who/are/foo", "/foo", `/who/are/\*you`, `/\*you`},
{"/who/are/foo/", "/foo/", `/who/are/\*you`, `/\*you`},
{"/who/are/foo/bar", "/foo/bar", `/who/are/\*you`, `/\*you`},
{"/conxxx", "xxx", `/con:tact`, `:tact`},
{"/conooo/xxx", "ooo", `/con:tact`, `:tact`},
}
for _, conflict := range conflicts {
// I have to re-create a 'tree', because the 'tree' will be
// in an inconsistent state when the loop recovers from the
// panic which threw by 'addRoute' function.
tree := &node{}
routes := [...]string{
"/con:tact",
"/who/are/*you",
"/who/foo/hello",
}
for _, route := range routes {
tree.addRoute(route, fakeHandler(route))
}
recv := catchPanic(func() {
tree.addRoute(conflict.route, fakeHandler(conflict.route))
})
if !regexp.MustCompile(fmt.Sprintf("'%s' in new path .* conflicts with existing wildcard '%s' in existing prefix '%s'",
conflict.segPath, conflict.existSegPath, conflict.existPath)).MatchString(fmt.Sprint(recv)) {
t.Fatalf("invalid wildcard conflict error (%v)", recv)
}
}
}

43
vendor/vendor.json vendored
View File

@ -6,7 +6,9 @@
"checksumSHA1": "CSPbwbyzqA6sfORicn4HFtIhF/c=",
"path": "github.com/davecgh/go-spew/spew",
"revision": "8991bc29aa16c548c550c7ff78260e27b9ab7c73",
"revisionTime": "2018-02-21T22:46:20Z"
"revisionTime": "2018-02-21T22:46:20Z",
"version": "v1.1",
"versionExact": "v1.1.1"
},
{
"checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=",
@ -15,12 +17,12 @@
"revisionTime": "2017-01-09T09:34:21Z"
},
{
"checksumSHA1": "Pyou8mceOASSFxc7GeXZuVdSMi0=",
"checksumSHA1": "mE9XW26JSpe4meBObM6J/Oeq0eg=",
"path": "github.com/golang/protobuf/proto",
"revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265",
"revisionTime": "2018-04-30T18:52:41Z",
"version": "v1.1.0",
"versionExact": "v1.1.0"
"revision": "aa810b61a9c79d51363740d207bb46cf8e620ed5",
"revisionTime": "2018-08-14T21:14:27Z",
"version": "v1.2",
"versionExact": "v1.2.0"
},
{
"checksumSHA1": "WqeEgS7pqqkwK8mlrAZmDgtWJMY=",
@ -31,19 +33,19 @@
"versionExact": "v1.1.5"
},
{
"checksumSHA1": "y/A5iuvwjytQE2CqVuphQRXR2nI=",
"checksumSHA1": "w5RcOnfv5YDr3j2bd1YydkPiZx4=",
"path": "github.com/mattn/go-isatty",
"revision": "0360b2af4f38e8d38c7fce2a9f4e702702d73a39",
"revisionTime": "2017-09-25T05:34:41Z",
"version": "v0.0.3",
"versionExact": "v0.0.3"
"revision": "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c",
"revisionTime": "2017-11-07T05:05:31Z",
"version": "v0.0",
"versionExact": "v0.0.4"
},
{
"checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=",
"path": "github.com/stretchr/testify/assert",
"revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686",
"revisionTime": "2018-05-06T18:05:49Z",
"version": "v1.2.2",
"version": "v1.2",
"versionExact": "v1.2.2"
},
{
@ -51,15 +53,20 @@
"path": "github.com/ugorji/go/codec",
"revision": "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab",
"revisionTime": "2018-04-07T10:07:33Z",
"version": "v1.1.1",
"version": "v1.1",
"versionExact": "v1.1.1"
},
{
"checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=",
"comment": "release-branch.go1.7",
"checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=",
"path": "golang.org/x/net/context",
"revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a",
"revisionTime": "2016-10-18T08:54:36Z"
"revision": "49bb7cea24b1df9410e1712aa6433dae904ff66a",
"revisionTime": "2018-10-11T05:27:23Z"
},
{
"checksumSHA1": "SiJNkx+YGtq3Gtr6Ldu6OW83O+U=",
"path": "golang.org/x/sys/unix",
"revision": "fa43e7bc11baaae89f3f902b2b4d832b68234844",
"revisionTime": "2018-10-11T14:35:51Z"
},
{
"checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=",
@ -74,7 +81,7 @@
"path": "gopkg.in/yaml.v2",
"revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183",
"revisionTime": "2018-03-28T19:50:20Z",
"version": "v2.2.1",
"version": "v2.2",
"versionExact": "v2.2.1"
}
],