mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-23 01:57:55 +08:00
Merge branch 'master' into staticfs-noroute
This commit is contained in:
commit
b1a6b51e89
13
.github/ISSUE_TEMPLATE.md
vendored
Normal file
13
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -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.
|
||||||
|
|
||||||
|
- go version:
|
||||||
|
- gin version (or commit ref):
|
||||||
|
- operating system:
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -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.
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ vendor/*
|
|||||||
!vendor/vendor.json
|
!vendor/vendor.json
|
||||||
coverage.out
|
coverage.out
|
||||||
count.out
|
count.out
|
||||||
|
test
|
||||||
|
17
.travis.yml
17
.travis.yml
@ -5,13 +5,26 @@ go:
|
|||||||
- 1.7.x
|
- 1.7.x
|
||||||
- 1.8.x
|
- 1.8.x
|
||||||
- 1.9.x
|
- 1.9.x
|
||||||
|
- 1.10.x
|
||||||
|
- 1.11.x
|
||||||
- master
|
- master
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
|
include:
|
||||||
|
- go: 1.11.x
|
||||||
|
env: GO111MODULE=on
|
||||||
|
|
||||||
git:
|
git:
|
||||||
depth: 3
|
depth: 10
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- if [[ "${GO111MODULE}" = "on" ]]; then mkdir "${HOME}/go"; export GOPATH="${HOME}/go"; fi
|
||||||
|
|
||||||
install:
|
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
|
go_import_path: github.com/gin-gonic/gin
|
||||||
|
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
List of all the awesome people working to make Gin the best Web Framework in Go.
|
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
|
## 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.
|
People and companies, who have contributed, in alphabetical order.
|
||||||
|
|
||||||
|
24
CHANGELOG.md
24
CHANGELOG.md
@ -1,6 +1,28 @@
|
|||||||
# CHANGELOG
|
# 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] Switch from godeps to govendor
|
||||||
- [NEW] Add support for Let's Encrypt via gin-gonic/autotls
|
- [NEW] Add support for Let's Encrypt via gin-gonic/autotls
|
||||||
|
35
Makefile
35
Makefile
@ -1,15 +1,25 @@
|
|||||||
|
GO ?= go
|
||||||
GOFMT ?= gofmt "-s"
|
GOFMT ?= gofmt "-s"
|
||||||
PACKAGES ?= $(shell go list ./... | grep -v /vendor/)
|
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/*")
|
GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*")
|
||||||
|
TESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|binding$$|render$$' | grep -v examples)
|
||||||
|
|
||||||
all: build
|
all: install
|
||||||
|
|
||||||
install: deps
|
install: deps
|
||||||
govendor sync
|
govendor sync
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
go test -v -covermode=count -coverprofile=coverage.out
|
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
|
.PHONY: fmt
|
||||||
fmt:
|
fmt:
|
||||||
@ -17,7 +27,6 @@ fmt:
|
|||||||
|
|
||||||
.PHONY: fmt-check
|
.PHONY: fmt-check
|
||||||
fmt-check:
|
fmt-check:
|
||||||
# get all go files and run go fmt on them
|
|
||||||
@diff=$$($(GOFMT) -d $(GOFILES)); \
|
@diff=$$($(GOFMT) -d $(GOFILES)); \
|
||||||
if [ -n "$$diff" ]; then \
|
if [ -n "$$diff" ]; then \
|
||||||
echo "Please run 'make fmt' and commit the result:"; \
|
echo "Please run 'make fmt' and commit the result:"; \
|
||||||
@ -26,14 +35,14 @@ fmt-check:
|
|||||||
fi;
|
fi;
|
||||||
|
|
||||||
vet:
|
vet:
|
||||||
go vet $(PACKAGES)
|
$(GO) vet $(VETPACKAGES)
|
||||||
|
|
||||||
deps:
|
deps:
|
||||||
@hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@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
|
fi
|
||||||
@hash embedmd > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@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
|
fi
|
||||||
|
|
||||||
embedmd:
|
embedmd:
|
||||||
@ -42,20 +51,26 @@ embedmd:
|
|||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint:
|
lint:
|
||||||
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@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
|
fi
|
||||||
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
|
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
|
||||||
|
|
||||||
.PHONY: misspell-check
|
.PHONY: misspell-check
|
||||||
misspell-check:
|
misspell-check:
|
||||||
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@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
|
fi
|
||||||
misspell -error $(GOFILES)
|
misspell -error $(GOFILES)
|
||||||
|
|
||||||
.PHONY: misspell
|
.PHONY: misspell
|
||||||
misspell:
|
misspell:
|
||||||
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@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
|
fi
|
||||||
misspell -w $(GOFILES)
|
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;
|
||||||
|
745
README.md
745
README.md
@ -7,11 +7,123 @@
|
|||||||
[](https://goreportcard.com/report/github.com/gin-gonic/gin)
|
[](https://goreportcard.com/report/github.com/gin-gonic/gin)
|
||||||
[](https://godoc.org/github.com/gin-gonic/gin)
|
[](https://godoc.org/github.com/gin-gonic/gin)
|
||||||
[](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
[](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
|
||||||
|
[](https://www.codetriage.com/gin-gonic/gin)
|
||||||
|
[](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.
|
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Prerequisite](#prerequisite)
|
||||||
|
- [Quick start](#quick-start)
|
||||||
|
- [Benchmarks](#benchmarks)
|
||||||
|
- [Gin v1.stable](#gin-v1-stable)
|
||||||
|
- [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)
|
||||||
|
- [Parameters in path](#parameters-in-path)
|
||||||
|
- [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)
|
||||||
|
- [Using middleware](#using-middleware)
|
||||||
|
- [How to write log file](#how-to-write-log-file)
|
||||||
|
- [Model binding and validation](#model-binding-and-validation)
|
||||||
|
- [Custom Validators](#custom-validators)
|
||||||
|
- [Only Bind Query String](#only-bind-query-string)
|
||||||
|
- [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, 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)
|
||||||
|
- [HTML rendering](#html-rendering)
|
||||||
|
- [Multitemplate](#multitemplate)
|
||||||
|
- [Redirects](#redirects)
|
||||||
|
- [Custom Middleware](#custom-middleware)
|
||||||
|
- [Using BasicAuth() middleware](#using-basicauth-middleware)
|
||||||
|
- [Goroutines inside a middleware](#goroutines-inside-a-middleware)
|
||||||
|
- [Custom HTTP configuration](#custom-http-configuration)
|
||||||
|
- [Support Let's Encrypt](#support-lets-encrypt)
|
||||||
|
- [Run multiple service using Gin](#run-multiple-service-using-gin)
|
||||||
|
- [Graceful restart or stop](#graceful-restart-or-stop)
|
||||||
|
- [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)
|
||||||
|
- [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)
|
||||||
|
|
||||||
|
## 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.3
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
```sh
|
||||||
# assume the following codes in example.go file
|
# assume the following codes in example.go file
|
||||||
$ cat example.go
|
$ cat example.go
|
||||||
@ -87,61 +199,9 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894
|
|||||||
- [x] Battle tested
|
- [x] Battle tested
|
||||||
- [x] API frozen, new releases will not break your code.
|
- [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)
|
## 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
|
```sh
|
||||||
$ go build -tags=jsoniter .
|
$ go build -tags=jsoniter .
|
||||||
@ -181,7 +241,7 @@ func main() {
|
|||||||
func main() {
|
func main() {
|
||||||
router := gin.Default()
|
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) {
|
router.GET("/user/:name", func(c *gin.Context) {
|
||||||
name := c.Param("name")
|
name := c.Param("name")
|
||||||
c.String(http.StatusOK, "Hello %s", name)
|
c.String(http.StatusOK, "Hello %s", name)
|
||||||
@ -268,6 +328,34 @@ func main() {
|
|||||||
id: 1234; page: 1; name: manu; message: this_is_great
|
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
|
### Upload files
|
||||||
|
|
||||||
#### Single file
|
#### Single file
|
||||||
@ -435,7 +523,7 @@ func main() {
|
|||||||
c.String(200, "pong")
|
c.String(200, "pong")
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Run(":8080")
|
router.Run(":8080")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -449,10 +537,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:
|
Also, Gin provides two sets of methods for binding:
|
||||||
- **Type** - Must bind
|
- **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.
|
- **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
|
- **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.
|
- **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`.
|
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`.
|
||||||
@ -462,8 +550,8 @@ You can also specify that specific fields are required. If a field is decorated
|
|||||||
```go
|
```go
|
||||||
// Binding from JSON
|
// Binding from JSON
|
||||||
type Login struct {
|
type Login struct {
|
||||||
User string `form:"user" json:"user" binding:"required"`
|
User string `form:"user" json:"user" xml:"user" binding:"required"`
|
||||||
Password string `form:"password" json:"password" binding:"required"`
|
Password string `form:"password" json:"password" xml:"password" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -472,30 +560,55 @@ func main() {
|
|||||||
// Example for binding JSON ({"user": "manu", "password": "123"})
|
// Example for binding JSON ({"user": "manu", "password": "123"})
|
||||||
router.POST("/loginJSON", func(c *gin.Context) {
|
router.POST("/loginJSON", func(c *gin.Context) {
|
||||||
var json Login
|
var json Login
|
||||||
if err := c.ShouldBindJSON(&json); err == nil {
|
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 {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
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 (
|
||||||
|
// <?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
// <root>
|
||||||
|
// <user>user</user>
|
||||||
|
// <password>123</user>
|
||||||
|
// </root>)
|
||||||
|
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)
|
// Example for binding a HTML form (user=manu&password=123)
|
||||||
router.POST("/loginForm", func(c *gin.Context) {
|
router.POST("/loginForm", func(c *gin.Context) {
|
||||||
var form Login
|
var form Login
|
||||||
// This will infer what binder to use depending on the content-type header.
|
// This will infer what binder to use depending on the content-type header.
|
||||||
if err := c.ShouldBind(&form); err == nil {
|
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 {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
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
|
// Listen and serve on 0.0.0.0:8080
|
||||||
@ -525,6 +638,10 @@ $ curl -v -X POST \
|
|||||||
{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
|
{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Skip validate**
|
||||||
|
|
||||||
|
When running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again.
|
||||||
|
|
||||||
### Custom Validators
|
### Custom Validators
|
||||||
|
|
||||||
It is also possible to register custom validators. See the [example code](examples/custom-validation/server.go).
|
It is also possible to register custom validators. See the [example code](examples/custom-validation/server.go).
|
||||||
@ -543,6 +660,7 @@ import (
|
|||||||
"gopkg.in/go-playground/validator.v8"
|
"gopkg.in/go-playground/validator.v8"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Booking contains binded and validated data.
|
||||||
type Booking struct {
|
type Booking struct {
|
||||||
CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
|
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"`
|
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
|
||||||
@ -563,7 +681,11 @@ func bookableDate(
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
route := gin.Default()
|
route := gin.Default()
|
||||||
binding.Validator.RegisterValidation("bookabledate", bookableDate)
|
|
||||||
|
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
|
||||||
|
v.RegisterValidation("bookabledate", bookableDate)
|
||||||
|
}
|
||||||
|
|
||||||
route.GET("/bookable", getBookable)
|
route.GET("/bookable", getBookable)
|
||||||
route.Run(":8085")
|
route.Run(":8085")
|
||||||
}
|
}
|
||||||
@ -579,13 +701,16 @@ func getBookable(c *gin.Context) {
|
|||||||
```
|
```
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ curl "localhost:8085/bookable?check_in=2017-08-16&check_out=2017-08-17"
|
$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17"
|
||||||
{"message":"Booking dates are valid!"}
|
{"message":"Booking dates are valid!"}
|
||||||
|
|
||||||
$ curl "localhost:8085/bookable?check_in=2017-08-15&check_out=2017-08-16"
|
$ 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"}
|
{"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 registered this way.
|
||||||
|
See the [struct-lvl-validation example](examples/struct-lvl-validations) to learn more.
|
||||||
|
|
||||||
### Only Bind Query String
|
### Only Bind Query String
|
||||||
|
|
||||||
`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017).
|
`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017).
|
||||||
@ -629,9 +754,12 @@ See the [detail information](https://github.com/gin-gonic/gin/issues/742#issueco
|
|||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "log"
|
import (
|
||||||
import "github.com/gin-gonic/gin"
|
"log"
|
||||||
import "time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
type Person struct {
|
type Person struct {
|
||||||
Name string `form:"name"`
|
Name string `form:"name"`
|
||||||
@ -750,7 +878,7 @@ Test it with:
|
|||||||
$ curl -v --form user=user --form password=password http://localhost:8080/login
|
$ curl -v --form user=user --form password=password http://localhost:8080/login
|
||||||
```
|
```
|
||||||
|
|
||||||
### XML, JSON and YAML rendering
|
### XML, JSON, YAML and ProtoBuf rendering
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
@ -784,6 +912,19 @@ func main() {
|
|||||||
c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
|
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
|
// Listen and serve on 0.0.0.0:8080
|
||||||
r.Run(":8080")
|
r.Run(":8080")
|
||||||
}
|
}
|
||||||
@ -811,6 +952,79 @@ func main() {
|
|||||||
r.Run(":8080")
|
r.Run(":8080")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
#### JSONP
|
||||||
|
|
||||||
|
Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
r.GET("/JSONP?callback=x", func(c *gin.Context) {
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
|
||||||
|
//callback is x
|
||||||
|
// Will output : x({\"foo\":\"bar\"})
|
||||||
|
c.JSONP(http.StatusOK, data)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Listen and serve on 0.0.0.0:8080
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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": "<br>",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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": "<b>Hello, world!</b>",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Serves literal characters
|
||||||
|
r.GET("/purejson", func(c *gin.Context) {
|
||||||
|
c.PureJSON(200, gin.H{
|
||||||
|
"html": "<b>Hello, world!</b>",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// listen and serve on 0.0.0.0:8080
|
||||||
|
r.Run(":8080)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Serving static files
|
### Serving static files
|
||||||
|
|
||||||
@ -826,6 +1040,32 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Serving data from reader
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
router.GET("/someDataFromReader", func(c *gin.Context) {
|
||||||
|
response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
|
||||||
|
if err != nil || response.StatusCode != http.StatusOK {
|
||||||
|
c.Status(http.StatusServiceUnavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := response.Body
|
||||||
|
contentLength := response.ContentLength
|
||||||
|
contentType := response.Header.Get("Content-Type")
|
||||||
|
|
||||||
|
extraHeaders := map[string]string{
|
||||||
|
"Content-Disposition": `attachment; filename="gopher.png"`,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
|
||||||
|
})
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### HTML rendering
|
### HTML rendering
|
||||||
|
|
||||||
Using LoadHTMLGlob() or LoadHTMLFiles()
|
Using LoadHTMLGlob() or LoadHTMLFiles()
|
||||||
@ -950,7 +1190,7 @@ func main() {
|
|||||||
router.SetFuncMap(template.FuncMap{
|
router.SetFuncMap(template.FuncMap{
|
||||||
"formatAsDate": formatAsDate,
|
"formatAsDate": formatAsDate,
|
||||||
})
|
})
|
||||||
router.LoadHTMLFiles("./fixtures/basic/raw.tmpl")
|
router.LoadHTMLFiles("./testdata/template/raw.tmpl")
|
||||||
|
|
||||||
router.GET("/raw", func(c *gin.Context) {
|
router.GET("/raw", func(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
|
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
|
||||||
@ -980,14 +1220,26 @@ Gin allow by default use only one html.Template. Check [a multitemplate render](
|
|||||||
|
|
||||||
### Redirects
|
### Redirects
|
||||||
|
|
||||||
Issuing a HTTP redirect is easy:
|
Issuing a HTTP redirect is easy. Both internal and external locations are supported.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
r.GET("/test", func(c *gin.Context) {
|
r.GET("/test", func(c *gin.Context) {
|
||||||
c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
|
c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
Both internal and external locations are supported.
|
|
||||||
|
|
||||||
|
Issuing a Router redirect, use `HandleContext` like below.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
r.GET("/test", func(c *gin.Context) {
|
||||||
|
c.Request.URL.Path = "/test2"
|
||||||
|
r.HandleContext(c)
|
||||||
|
})
|
||||||
|
r.GET("/test2", func(c *gin.Context) {
|
||||||
|
c.JSON(200, gin.H{"hello": "world"})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Custom Middleware
|
### Custom Middleware
|
||||||
@ -1190,7 +1442,7 @@ func main() {
|
|||||||
|
|
||||||
### Run multiple service using Gin
|
### Run multiple service using Gin
|
||||||
|
|
||||||
See the [question](https://github.com/gin-gonic/gin/issues/346) and try the folling example:
|
See the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example:
|
||||||
|
|
||||||
[embedmd]:# (examples/multiple-service/main.go go)
|
[embedmd]:# (examples/multiple-service/main.go go)
|
||||||
```go
|
```go
|
||||||
@ -1323,8 +1575,8 @@ func main() {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
// service connections
|
// service connections
|
||||||
if err := srv.ListenAndServe(); err != nil {
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
log.Printf("listen: %s\n", err)
|
log.Fatalf("listen: %s\n", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -1344,6 +1596,321 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Build a single binary with templates
|
||||||
|
|
||||||
|
You can build a server into a single binary containing templates by using [go-assets][].
|
||||||
|
|
||||||
|
[go-assets]: https://github.com/jessevdk/go-assets
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
r := gin.New()
|
||||||
|
|
||||||
|
t, err := loadTemplate()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
r.SetHTMLTemplate(t)
|
||||||
|
|
||||||
|
r.GET("/", func(c *gin.Context) {
|
||||||
|
c.HTML(http.StatusOK, "/html/index.tmpl",nil)
|
||||||
|
})
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadTemplate loads templates embedded by go-assets-builder
|
||||||
|
func loadTemplate() (*template.Template, error) {
|
||||||
|
t := template.New("")
|
||||||
|
for name, file := range Assets.Files {
|
||||||
|
if file.IsDir() || !strings.HasSuffix(name, ".tmpl") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h, err := ioutil.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t, err = t.New(name).Parse(string(h))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See a complete example in the `examples/assets-in-binary` directory.
|
||||||
|
|
||||||
|
### Bind form-data request with custom struct
|
||||||
|
|
||||||
|
The follow example using custom struct:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type StructA struct {
|
||||||
|
FieldA string `form:"field_a"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StructB struct {
|
||||||
|
NestedStruct StructA
|
||||||
|
FieldB string `form:"field_b"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StructC struct {
|
||||||
|
NestedStructPointer *StructA
|
||||||
|
FieldC string `form:"field_c"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StructD struct {
|
||||||
|
NestedAnonyStruct struct {
|
||||||
|
FieldX string `form:"field_x"`
|
||||||
|
}
|
||||||
|
FieldD string `form:"field_d"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDataB(c *gin.Context) {
|
||||||
|
var b StructB
|
||||||
|
c.Bind(&b)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"a": b.NestedStruct,
|
||||||
|
"b": b.FieldB,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDataC(c *gin.Context) {
|
||||||
|
var b StructC
|
||||||
|
c.Bind(&b)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"a": b.NestedStructPointer,
|
||||||
|
"c": b.FieldC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDataD(c *gin.Context) {
|
||||||
|
var b StructD
|
||||||
|
c.Bind(&b)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"x": b.NestedAnonyStruct,
|
||||||
|
"d": b.FieldD,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
r.GET("/getb", GetDataB)
|
||||||
|
r.GET("/getc", GetDataC)
|
||||||
|
r.GET("/getd", GetDataD)
|
||||||
|
|
||||||
|
r.Run()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Using the command `curl` command result:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl "http://localhost:8080/getb?field_a=hello&field_b=world"
|
||||||
|
{"a":{"FieldA":"hello"},"b":"world"}
|
||||||
|
$ curl "http://localhost:8080/getc?field_a=hello&field_c=world"
|
||||||
|
{"a":{"FieldA":"hello"},"c":"world"}
|
||||||
|
$ curl "http://localhost:8080/getd?field_x=hello&field_d=world"
|
||||||
|
{"d":"world","x":{"FieldX":"hello"}}
|
||||||
|
```
|
||||||
|
|
||||||
|
**NOTE**: NOT support the follow style struct:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type StructX struct {
|
||||||
|
X struct {} `form:"name_x"` // HERE have form
|
||||||
|
}
|
||||||
|
|
||||||
|
type StructY struct {
|
||||||
|
Y StructX `form:"name_y"` // HERE have form
|
||||||
|
}
|
||||||
|
|
||||||
|
type StructZ struct {
|
||||||
|
Z *StructZ `form:"name_z"` // HERE have form
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In a word, only support nested custom struct which have no `form` now.
|
||||||
|
|
||||||
|
### Try to bind body into different structs
|
||||||
|
|
||||||
|
The normal methods for binding request body consumes `c.Request.Body` and they
|
||||||
|
cannot be called multiple times.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type formA struct {
|
||||||
|
Foo string `json:"foo" xml:"foo" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type formB struct {
|
||||||
|
Bar string `json:"bar" xml:"bar" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func SomeHandler(c *gin.Context) {
|
||||||
|
objA := formA{}
|
||||||
|
objB := formB{}
|
||||||
|
// This c.ShouldBind consumes c.Request.Body and it cannot be reused.
|
||||||
|
if errA := c.ShouldBind(&objA); errA == nil {
|
||||||
|
c.String(http.StatusOK, `the body should be formA`)
|
||||||
|
// Always an error is occurred by this because c.Request.Body is EOF now.
|
||||||
|
} else if errB := c.ShouldBind(&objB); errB == nil {
|
||||||
|
c.String(http.StatusOK, `the body should be formB`)
|
||||||
|
} else {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For this, you can use `c.ShouldBindBodyWith`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func SomeHandler(c *gin.Context) {
|
||||||
|
objA := formA{}
|
||||||
|
objB := formB{}
|
||||||
|
// This reads c.Request.Body and stores the result into the context.
|
||||||
|
if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
|
||||||
|
c.String(http.StatusOK, `the body should be formA`)
|
||||||
|
// At this time, it reuses body stored in the context.
|
||||||
|
} else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
|
||||||
|
c.String(http.StatusOK, `the body should be formB JSON`)
|
||||||
|
// And it can accepts other formats
|
||||||
|
} else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
|
||||||
|
c.String(http.StatusOK, `the body should be formB XML`)
|
||||||
|
} else {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* `c.ShouldBindBodyWith` stores body into the context before binding. This has
|
||||||
|
a slight impact to performance, so you should not use this method if you are
|
||||||
|
enough to call binding at once.
|
||||||
|
* This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`,
|
||||||
|
`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`,
|
||||||
|
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(`
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Https Test</title>
|
||||||
|
<script src="/assets/app.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 style="color:red;">Welcome, Ginner!</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`))
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Define format for the log of routes
|
||||||
|
|
||||||
|
The default log of routes is:
|
||||||
|
```
|
||||||
|
[GIN-debug] POST /foo --> main.main.func1 (3 handlers)
|
||||||
|
[GIN-debug] GET /bar --> main.main.func2 (3 handlers)
|
||||||
|
[GIN-debug] GET /status --> main.main.func3 (3 handlers)
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`.
|
||||||
|
In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs.
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
|
||||||
|
log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.POST("/foo", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, "foo")
|
||||||
|
})
|
||||||
|
|
||||||
|
r.GET("/bar", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, "bar")
|
||||||
|
})
|
||||||
|
|
||||||
|
r.GET("/status", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, "ok")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Listen and Server in http://0.0.0.0:8080
|
||||||
|
r.Run()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
## Testing
|
||||||
|
|
||||||
The `net/http/httptest` package is preferable way for HTTP testing.
|
The `net/http/httptest` package is preferable way for HTTP testing.
|
||||||
@ -1390,9 +1957,11 @@ func TestPingRoute(t *testing.T) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Users [](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
|
## Users
|
||||||
|
|
||||||
Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework.
|
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.
|
* [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.
|
||||||
|
3
auth.go
3
auth.go
@ -7,6 +7,7 @@ package gin
|
|||||||
import (
|
import (
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
|
|||||||
if !found {
|
if !found {
|
||||||
// Credentials doesn't match, we return 401 and abort handlers chain.
|
// Credentials doesn't match, we return 401 and abort handlers chain.
|
||||||
c.Header("WWW-Authenticate", realm)
|
c.Header("WWW-Authenticate", realm)
|
||||||
c.AbortWithStatus(401)
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
auth_test.go
16
auth_test.go
@ -93,7 +93,7 @@ func TestBasicAuthSucceed(t *testing.T) {
|
|||||||
router := New()
|
router := New()
|
||||||
router.Use(BasicAuth(accounts))
|
router.Use(BasicAuth(accounts))
|
||||||
router.GET("/login", func(c *Context) {
|
router.GET("/login", func(c *Context) {
|
||||||
c.String(200, c.MustGet(AuthUserKey).(string))
|
c.String(http.StatusOK, c.MustGet(AuthUserKey).(string))
|
||||||
})
|
})
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
@ -101,7 +101,7 @@ func TestBasicAuthSucceed(t *testing.T) {
|
|||||||
req.Header.Set("Authorization", authorizationHeader("admin", "password"))
|
req.Header.Set("Authorization", authorizationHeader("admin", "password"))
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Equal(t, "admin", w.Body.String())
|
assert.Equal(t, "admin", w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ func TestBasicAuth401(t *testing.T) {
|
|||||||
router.Use(BasicAuth(accounts))
|
router.Use(BasicAuth(accounts))
|
||||||
router.GET("/login", func(c *Context) {
|
router.GET("/login", func(c *Context) {
|
||||||
called = true
|
called = true
|
||||||
c.String(200, c.MustGet(AuthUserKey).(string))
|
c.String(http.StatusOK, c.MustGet(AuthUserKey).(string))
|
||||||
})
|
})
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
@ -121,8 +121,8 @@ func TestBasicAuth401(t *testing.T) {
|
|||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.False(t, called)
|
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"))
|
assert.Equal(t, "Basic realm=\"Authorization Required\"", w.Header().Get("WWW-Authenticate"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBasicAuth401WithCustomRealm(t *testing.T) {
|
func TestBasicAuth401WithCustomRealm(t *testing.T) {
|
||||||
@ -132,7 +132,7 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) {
|
|||||||
router.Use(BasicAuthForRealm(accounts, "My Custom \"Realm\""))
|
router.Use(BasicAuthForRealm(accounts, "My Custom \"Realm\""))
|
||||||
router.GET("/login", func(c *Context) {
|
router.GET("/login", func(c *Context) {
|
||||||
called = true
|
called = true
|
||||||
c.String(200, c.MustGet(AuthUserKey).(string))
|
c.String(http.StatusOK, c.MustGet(AuthUserKey).(string))
|
||||||
})
|
})
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
@ -141,6 +141,6 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) {
|
|||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.False(t, called)
|
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"))
|
assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.Header().Get("WWW-Authenticate"))
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ func BenchmarkOneRouteJSON(B *testing.B) {
|
|||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
}{"ok"}
|
}{"ok"}
|
||||||
router.GET("/json", func(c *Context) {
|
router.GET("/json", func(c *Context) {
|
||||||
c.JSON(200, data)
|
c.JSON(http.StatusOK, data)
|
||||||
})
|
})
|
||||||
runRequest(B, router, "GET", "/json")
|
runRequest(B, router, "GET", "/json")
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ func BenchmarkOneRouteHTML(B *testing.B) {
|
|||||||
router.SetHTMLTemplate(t)
|
router.SetHTMLTemplate(t)
|
||||||
|
|
||||||
router.GET("/html", func(c *Context) {
|
router.GET("/html", func(c *Context) {
|
||||||
c.HTML(200, "index", "hola")
|
c.HTML(http.StatusOK, "index", "hola")
|
||||||
})
|
})
|
||||||
runRequest(B, router, "GET", "/html")
|
runRequest(B, router, "GET", "/html")
|
||||||
}
|
}
|
||||||
@ -82,7 +82,7 @@ func BenchmarkOneRouteSet(B *testing.B) {
|
|||||||
func BenchmarkOneRouteString(B *testing.B) {
|
func BenchmarkOneRouteString(B *testing.B) {
|
||||||
router := New()
|
router := New()
|
||||||
router.GET("/text", func(c *Context) {
|
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")
|
runRequest(B, router, "GET", "/text")
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,9 @@
|
|||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import "net/http"
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"gopkg.in/go-playground/validator.v8"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
// Content-Type MIME of the most common data formats.
|
||||||
const (
|
const (
|
||||||
MIMEJSON = "application/json"
|
MIMEJSON = "application/json"
|
||||||
MIMEHTML = "text/html"
|
MIMEHTML = "text/html"
|
||||||
@ -23,11 +20,25 @@ const (
|
|||||||
MIMEMSGPACK2 = "application/msgpack"
|
MIMEMSGPACK2 = "application/msgpack"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Binding describes the interface which needs to be implemented for binding the
|
||||||
|
// data present in the request such as JSON request body, query parameters or
|
||||||
|
// the form POST.
|
||||||
type Binding interface {
|
type Binding interface {
|
||||||
Name() string
|
Name() string
|
||||||
Bind(*http.Request, interface{}) error
|
Bind(*http.Request, interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
|
||||||
|
// but it reads the body from supplied bytes instead of req.Body.
|
||||||
|
type BindingBody interface {
|
||||||
|
Binding
|
||||||
|
BindBody([]byte, interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructValidator is the minimal interface which needs to be implemented in
|
||||||
|
// order for it to be used as the validator engine for ensuring the correctness
|
||||||
|
// of the reqest. Gin provides a default implementation for this using
|
||||||
|
// https://github.com/go-playground/validator/tree/v8.18.2.
|
||||||
type StructValidator interface {
|
type StructValidator interface {
|
||||||
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
||||||
// If the received type is not a struct, any validation should be skipped and nil must be returned.
|
// If the received type is not a struct, any validation should be skipped and nil must be returned.
|
||||||
@ -36,14 +47,18 @@ type StructValidator interface {
|
|||||||
// Otherwise nil must be returned.
|
// Otherwise nil must be returned.
|
||||||
ValidateStruct(interface{}) error
|
ValidateStruct(interface{}) error
|
||||||
|
|
||||||
// RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key
|
// Engine returns the underlying validator engine which powers the
|
||||||
// NOTE: if the key already exists, the previous validation function will be replaced.
|
// StructValidator implementation.
|
||||||
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
|
Engine() interface{}
|
||||||
RegisterValidation(string, validator.Func) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validator is the default validator which implements the StructValidator
|
||||||
|
// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2
|
||||||
|
// under the hood.
|
||||||
var Validator StructValidator = &defaultValidator{}
|
var Validator StructValidator = &defaultValidator{}
|
||||||
|
|
||||||
|
// These implement the Binding interface and can be used to bind the data
|
||||||
|
// present in the request to struct instances.
|
||||||
var (
|
var (
|
||||||
JSON = jsonBinding{}
|
JSON = jsonBinding{}
|
||||||
XML = xmlBinding{}
|
XML = xmlBinding{}
|
||||||
@ -55,6 +70,8 @@ var (
|
|||||||
MsgPack = msgpackBinding{}
|
MsgPack = msgpackBinding{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Default returns the appropriate Binding instance based on the HTTP method
|
||||||
|
// and the content type.
|
||||||
func Default(method, contentType string) Binding {
|
func Default(method, contentType string) Binding {
|
||||||
if method == "GET" {
|
if method == "GET" {
|
||||||
return Form
|
return Form
|
||||||
|
67
binding/binding_body_test.go
Normal file
67
binding/binding_body_test.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/testdata/protoexample"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/ugorji/go/codec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBindingBody(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
binding BindingBody
|
||||||
|
body string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "JSON bidning",
|
||||||
|
binding: JSON,
|
||||||
|
body: `{"foo":"FOO"}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "XML bidning",
|
||||||
|
binding: XML,
|
||||||
|
body: `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root>
|
||||||
|
<foo>FOO</foo>
|
||||||
|
</root>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MsgPack binding",
|
||||||
|
binding: MsgPack,
|
||||||
|
body: msgPackBody(t),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Logf("testing: %s", tt.name)
|
||||||
|
req := requestWithBody("POST", "/", tt.body)
|
||||||
|
form := FooStruct{}
|
||||||
|
body, _ := ioutil.ReadAll(req.Body)
|
||||||
|
assert.NoError(t, tt.binding.BindBody(body, &form))
|
||||||
|
assert.Equal(t, FooStruct{"FOO"}, form)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func msgPackBody(t *testing.T) string {
|
||||||
|
test := FooStruct{"FOO"}
|
||||||
|
h := new(codec.MsgpackHandle)
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
assert.NoError(t, codec.NewEncoder(buf, h).Encode(test))
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindingBodyProto(t *testing.T) {
|
||||||
|
test := protoexample.Test{
|
||||||
|
Label: proto.String("FOO"),
|
||||||
|
}
|
||||||
|
data, _ := proto.Marshal(&test)
|
||||||
|
req := requestWithBody("POST", "/", string(data))
|
||||||
|
form := protoexample.Test{}
|
||||||
|
body, _ := ioutil.ReadAll(req.Body)
|
||||||
|
assert.NoError(t, ProtoBuf.BindBody(body, &form))
|
||||||
|
assert.Equal(t, test, form)
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -18,19 +18,29 @@ type defaultValidator struct {
|
|||||||
|
|
||||||
var _ StructValidator = &defaultValidator{}
|
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 {
|
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()
|
v.lazyinit()
|
||||||
if err := v.validate.Struct(obj); err != nil {
|
if err := v.validate.Struct(obj); err != nil {
|
||||||
return error(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *defaultValidator) RegisterValidation(key string, fn validator.Func) error {
|
// Engine returns the underlying validator engine which powers the default
|
||||||
|
// Validator instance. This is useful if you want to register custom validations
|
||||||
|
// or struct level validations. See validator GoDoc for more info -
|
||||||
|
// https://godoc.org/gopkg.in/go-playground/validator.v8
|
||||||
|
func (v *defaultValidator) Engine() interface{} {
|
||||||
v.lazyinit()
|
v.lazyinit()
|
||||||
return v.validate.RegisterValidation(key, fn)
|
return v.validate
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *defaultValidator) lazyinit() {
|
func (v *defaultValidator) lazyinit() {
|
||||||
@ -39,12 +49,3 @@ func (v *defaultValidator) lazyinit() {
|
|||||||
v.validate = validator.New(config)
|
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
|
|
||||||
}
|
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,12 +24,28 @@ func mapForm(ptr interface{}, form map[string][]string) error {
|
|||||||
|
|
||||||
structFieldKind := structField.Kind()
|
structFieldKind := structField.Kind()
|
||||||
inputFieldName := typeField.Tag.Get("form")
|
inputFieldName := typeField.Tag.Get("form")
|
||||||
|
inputFieldNameList := strings.Split(inputFieldName, ",")
|
||||||
|
inputFieldName = inputFieldNameList[0]
|
||||||
|
var defaultValue string
|
||||||
|
if len(inputFieldNameList) > 1 {
|
||||||
|
defaultList := strings.SplitN(inputFieldNameList[1], "=", 2)
|
||||||
|
if defaultList[0] == "default" {
|
||||||
|
defaultValue = defaultList[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
if inputFieldName == "" {
|
if inputFieldName == "" {
|
||||||
inputFieldName = typeField.Name
|
inputFieldName = typeField.Name
|
||||||
|
|
||||||
// if "form" tag is nil, we inspect if the field is a struct.
|
// if "form" tag is nil, we inspect if the field is a struct or struct pointer.
|
||||||
// this would not make sense for JSON parsing but it does for a form
|
// this would not make sense for JSON parsing but it does for a form
|
||||||
// since data is flatten
|
// since data is flatten
|
||||||
|
if structFieldKind == reflect.Ptr {
|
||||||
|
if !structField.Elem().IsValid() {
|
||||||
|
structField.Set(reflect.New(structField.Type().Elem()))
|
||||||
|
}
|
||||||
|
structField = structField.Elem()
|
||||||
|
structFieldKind = structField.Kind()
|
||||||
|
}
|
||||||
if structFieldKind == reflect.Struct {
|
if structFieldKind == reflect.Struct {
|
||||||
err := mapForm(structField.Addr().Interface(), form)
|
err := mapForm(structField.Addr().Interface(), form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -38,9 +55,14 @@ func mapForm(ptr interface{}, form map[string][]string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
inputValue, exists := form[inputFieldName]
|
inputValue, exists := form[inputFieldName]
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
|
if defaultValue == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
inputValue = make([]string, 1)
|
||||||
|
inputValue[0] = defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
numElems := len(inputValue)
|
numElems := len(inputValue)
|
||||||
if structFieldKind == reflect.Slice && numElems > 0 {
|
if structFieldKind == reflect.Slice && numElems > 0 {
|
||||||
@ -52,7 +74,8 @@ func mapForm(ptr interface{}, form map[string][]string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val.Field(i).Set(slice)
|
val.Field(i).Set(slice)
|
||||||
} else {
|
continue
|
||||||
|
}
|
||||||
if _, isTime := structField.Interface().(time.Time); isTime {
|
if _, isTime := structField.Interface().(time.Time); isTime {
|
||||||
if err := setTimeField(inputValue[0], typeField, structField); err != nil {
|
if err := setTimeField(inputValue[0], typeField, structField); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -63,7 +86,6 @@ func mapForm(ptr interface{}, form map[string][]string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,6 +119,12 @@ func setWithProperType(valueKind reflect.Kind, val string, structField reflect.V
|
|||||||
return setFloatField(val, 64, structField)
|
return setFloatField(val, 64, structField)
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
structField.SetString(val)
|
structField.SetString(val)
|
||||||
|
case reflect.Ptr:
|
||||||
|
if !structField.Elem().IsValid() {
|
||||||
|
structField.Set(reflect.New(structField.Type().Elem()))
|
||||||
|
}
|
||||||
|
structFieldElem := structField.Elem()
|
||||||
|
return setWithProperType(structFieldElem.Kind(), val, structFieldElem)
|
||||||
default:
|
default:
|
||||||
return errors.New("Unknown type")
|
return errors.New("Unknown type")
|
||||||
}
|
}
|
||||||
@ -133,7 +161,7 @@ func setBoolField(val string, field reflect.Value) error {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
field.SetBool(boolVal)
|
field.SetBool(boolVal)
|
||||||
}
|
}
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func setFloatField(val string, bitSize int, field reflect.Value) error {
|
func setFloatField(val string, bitSize int, field reflect.Value) error {
|
||||||
@ -150,7 +178,7 @@ func setFloatField(val string, bitSize int, field reflect.Value) error {
|
|||||||
func setTimeField(val string, structField reflect.StructField, value reflect.Value) error {
|
func setTimeField(val string, structField reflect.StructField, value reflect.Value) error {
|
||||||
timeFormat := structField.Tag.Get("time_format")
|
timeFormat := structField.Tag.Get("time_format")
|
||||||
if timeFormat == "" {
|
if timeFormat == "" {
|
||||||
return errors.New("Blank time format")
|
timeFormat = time.RFC3339
|
||||||
}
|
}
|
||||||
|
|
||||||
if val == "" {
|
if val == "" {
|
||||||
|
@ -5,11 +5,16 @@
|
|||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
"net/http"
|
"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
|
||||||
|
// Decoder instance. UseNumber causes the Decoder to unmarshal a number into an
|
||||||
|
// interface{} as a Number instead of as a float64.
|
||||||
var EnableDecoderUseNumber = false
|
var EnableDecoderUseNumber = false
|
||||||
|
|
||||||
type jsonBinding struct{}
|
type jsonBinding struct{}
|
||||||
@ -19,7 +24,15 @@ func (jsonBinding) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (jsonBinding) Bind(req *http.Request, obj interface{}) error {
|
func (jsonBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
decoder := json.NewDecoder(req.Body)
|
return decodeJSON(req.Body, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jsonBinding) BindBody(body []byte, obj interface{}) error {
|
||||||
|
return decodeJSON(bytes.NewReader(body), obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeJSON(r io.Reader, obj interface{}) error {
|
||||||
|
decoder := json.NewDecoder(r)
|
||||||
if EnableDecoderUseNumber {
|
if EnableDecoderUseNumber {
|
||||||
decoder.UseNumber()
|
decoder.UseNumber()
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/ugorji/go/codec"
|
"github.com/ugorji/go/codec"
|
||||||
@ -17,7 +19,16 @@ func (msgpackBinding) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (msgpackBinding) Bind(req *http.Request, obj interface{}) error {
|
func (msgpackBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
if err := codec.NewDecoder(req.Body, new(codec.MsgpackHandle)).Decode(&obj); err != nil {
|
return decodeMsgPack(req.Body, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msgpackBinding) BindBody(body []byte, obj interface{}) error {
|
||||||
|
return decodeMsgPack(bytes.NewReader(body), obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeMsgPack(r io.Reader, obj interface{}) error {
|
||||||
|
cdc := new(codec.MsgpackHandle)
|
||||||
|
if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return validate(obj)
|
return validate(obj)
|
||||||
|
@ -17,19 +17,20 @@ func (protobufBinding) Name() string {
|
|||||||
return "protobuf"
|
return "protobuf"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (protobufBinding) Bind(req *http.Request, obj interface{}) error {
|
func (b protobufBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
|
|
||||||
buf, err := ioutil.ReadAll(req.Body)
|
buf, err := ioutil.ReadAll(req.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return b.BindBody(buf, obj)
|
||||||
if err = proto.Unmarshal(buf, obj.(proto.Message)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Here it's same to return validate(obj), but util now we cann't add `binding:""` to the struct
|
func (protobufBinding) BindBody(body []byte, obj interface{}) error {
|
||||||
//which automatically generate by gen-proto
|
if err := proto.Unmarshal(body, obj.(proto.Message)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Here it's same to return validate(obj), but util now we cann't add
|
||||||
|
// `binding:""` to the struct which automatically generate by gen-proto
|
||||||
return nil
|
return nil
|
||||||
// return validate(obj)
|
// return validate(obj)
|
||||||
}
|
}
|
||||||
|
@ -176,7 +176,7 @@ func TestValidatePrimitives(t *testing.T) {
|
|||||||
obj := Object{"foo": "bar", "bar": 1}
|
obj := Object{"foo": "bar", "bar": 1}
|
||||||
assert.NoError(t, validate(obj))
|
assert.NoError(t, validate(obj))
|
||||||
assert.NoError(t, validate(&obj))
|
assert.NoError(t, validate(&obj))
|
||||||
assert.Equal(t, obj, Object{"foo": "bar", "bar": 1})
|
assert.Equal(t, Object{"foo": "bar", "bar": 1}, obj)
|
||||||
|
|
||||||
obj2 := []Object{{"foo": "bar", "bar": 1}, {"foo": "bar", "bar": 1}}
|
obj2 := []Object{{"foo": "bar", "bar": 1}, {"foo": "bar", "bar": 1}}
|
||||||
assert.NoError(t, validate(obj2))
|
assert.NoError(t, validate(obj2))
|
||||||
@ -185,12 +185,12 @@ func TestValidatePrimitives(t *testing.T) {
|
|||||||
nu := 10
|
nu := 10
|
||||||
assert.NoError(t, validate(nu))
|
assert.NoError(t, validate(nu))
|
||||||
assert.NoError(t, validate(&nu))
|
assert.NoError(t, validate(&nu))
|
||||||
assert.Equal(t, nu, 10)
|
assert.Equal(t, 10, nu)
|
||||||
|
|
||||||
str := "value"
|
str := "value"
|
||||||
assert.NoError(t, validate(str))
|
assert.NoError(t, validate(str))
|
||||||
assert.NoError(t, validate(&str))
|
assert.NoError(t, validate(&str))
|
||||||
assert.Equal(t, str, "value")
|
assert.Equal(t, "value", str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// structCustomValidation is a helper struct we use to check that
|
// structCustomValidation is a helper struct we use to check that
|
||||||
@ -214,11 +214,14 @@ func notOne(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegisterValidation(t *testing.T) {
|
func TestValidatorEngine(t *testing.T) {
|
||||||
// This validates that the function `notOne` matches
|
// This validates that the function `notOne` matches
|
||||||
// the expected function signature by `defaultValidator`
|
// the expected function signature by `defaultValidator`
|
||||||
// and by extension the validator library.
|
// and by extension the validator library.
|
||||||
err := Validator.RegisterValidation("notone", notOne)
|
engine, ok := Validator.Engine().(*validator.Validate)
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
err := engine.RegisterValidation("notone", notOne)
|
||||||
// Check that we can register custom validation without error
|
// Check that we can register custom validation without error
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@ -228,6 +231,6 @@ func TestRegisterValidation(t *testing.T) {
|
|||||||
|
|
||||||
// Check that we got back non-nil errs
|
// Check that we got back non-nil errs
|
||||||
assert.NotNil(t, errs)
|
assert.NotNil(t, errs)
|
||||||
// Check that the error matches expactation
|
// Check that the error matches expectation
|
||||||
assert.Error(t, errs, "", "", "notone")
|
assert.Error(t, errs, "", "", "notone")
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,7 +18,14 @@ func (xmlBinding) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (xmlBinding) Bind(req *http.Request, obj interface{}) error {
|
func (xmlBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
decoder := xml.NewDecoder(req.Body)
|
return decodeXML(req.Body, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (xmlBinding) BindBody(body []byte, obj interface{}) error {
|
||||||
|
return decodeXML(bytes.NewReader(body), obj)
|
||||||
|
}
|
||||||
|
func decodeXML(r io.Reader, obj interface{}) error {
|
||||||
|
decoder := xml.NewDecoder(r)
|
||||||
if err := decoder.Decode(obj); err != nil {
|
if err := decoder.Decode(obj); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
171
context.go
171
context.go
@ -31,6 +31,7 @@ const (
|
|||||||
MIMEPlain = binding.MIMEPlain
|
MIMEPlain = binding.MIMEPlain
|
||||||
MIMEPOSTForm = binding.MIMEPOSTForm
|
MIMEPOSTForm = binding.MIMEPOSTForm
|
||||||
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
|
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
|
||||||
|
BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
|
||||||
)
|
)
|
||||||
|
|
||||||
const abortIndex int8 = math.MaxInt8 / 2
|
const abortIndex int8 = math.MaxInt8 / 2
|
||||||
@ -158,16 +159,15 @@ func (c *Context) Error(err error) *Error {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
panic("err is nil")
|
panic("err is nil")
|
||||||
}
|
}
|
||||||
var parsedError *Error
|
|
||||||
switch err.(type) {
|
parsedError, ok := err.(*Error)
|
||||||
case *Error:
|
if !ok {
|
||||||
parsedError = err.(*Error)
|
|
||||||
default:
|
|
||||||
parsedError = &Error{
|
parsedError = &Error{
|
||||||
Err: err,
|
Err: err,
|
||||||
Type: ErrorTypePrivate,
|
Type: ErrorTypePrivate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Errors = append(c.Errors, parsedError)
|
c.Errors = append(c.Errors, parsedError)
|
||||||
return parsedError
|
return parsedError
|
||||||
}
|
}
|
||||||
@ -360,6 +360,18 @@ func (c *Context) GetQueryArray(key string) ([]string, bool) {
|
|||||||
return []string{}, false
|
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
|
// PostForm returns the specified key from a POST urlencoded form or multipart form
|
||||||
// when it exists, otherwise it returns an empty string `("")`.
|
// when it exists, otherwise it returns an empty string `("")`.
|
||||||
func (c *Context) PostForm(key string) string {
|
func (c *Context) PostForm(key string) string {
|
||||||
@ -402,7 +414,6 @@ func (c *Context) PostFormArray(key string) []string {
|
|||||||
// a boolean value whether at least one value exists for the given key.
|
// a boolean value whether at least one value exists for the given key.
|
||||||
func (c *Context) GetPostFormArray(key string) ([]string, bool) {
|
func (c *Context) GetPostFormArray(key string) ([]string, bool) {
|
||||||
req := c.Request
|
req := c.Request
|
||||||
req.ParseForm()
|
|
||||||
req.ParseMultipartForm(c.engine.MaxMultipartMemory)
|
req.ParseMultipartForm(c.engine.MaxMultipartMemory)
|
||||||
if values := req.PostForm[key]; len(values) > 0 {
|
if values := req.PostForm[key]; len(values) > 0 {
|
||||||
return values, true
|
return values, true
|
||||||
@ -415,8 +426,48 @@ func (c *Context) GetPostFormArray(key string) ([]string, bool) {
|
|||||||
return []string{}, false
|
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.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.
|
// FormFile returns the first file for the provided form key.
|
||||||
func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
|
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)
|
_, fh, err := c.Request.FormFile(name)
|
||||||
return fh, err
|
return fh, err
|
||||||
}
|
}
|
||||||
@ -463,6 +514,11 @@ func (c *Context) BindJSON(obj interface{}) error {
|
|||||||
return c.MustBindWith(obj, binding.JSON)
|
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).
|
// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
|
||||||
func (c *Context) BindQuery(obj interface{}) error {
|
func (c *Context) BindQuery(obj interface{}) error {
|
||||||
return c.MustBindWith(obj, binding.Query)
|
return c.MustBindWith(obj, binding.Query)
|
||||||
@ -473,7 +529,7 @@ func (c *Context) BindQuery(obj interface{}) error {
|
|||||||
// See the binding package.
|
// See the binding package.
|
||||||
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
|
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
|
||||||
if err = c.ShouldBindWith(obj, b); err != nil {
|
if err = c.ShouldBindWith(obj, b); err != nil {
|
||||||
c.AbortWithError(400, err).SetType(ErrorTypeBind)
|
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -497,6 +553,11 @@ func (c *Context) ShouldBindJSON(obj interface{}) error {
|
|||||||
return c.ShouldBindWith(obj, binding.JSON)
|
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).
|
// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
|
||||||
func (c *Context) ShouldBindQuery(obj interface{}) error {
|
func (c *Context) ShouldBindQuery(obj interface{}) error {
|
||||||
return c.ShouldBindWith(obj, binding.Query)
|
return c.ShouldBindWith(obj, binding.Query)
|
||||||
@ -508,20 +569,40 @@ func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
|
|||||||
return b.Bind(c.Request, obj)
|
return b.Bind(c.Request, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request
|
||||||
|
// body into the context, and reuse when it is called again.
|
||||||
|
//
|
||||||
|
// NOTE: This method reads the body before binding. So you should use
|
||||||
|
// ShouldBindWith for better performance if you need to call only once.
|
||||||
|
func (c *Context) ShouldBindBodyWith(
|
||||||
|
obj interface{}, bb binding.BindingBody,
|
||||||
|
) (err error) {
|
||||||
|
var body []byte
|
||||||
|
if cb, ok := c.Get(BodyBytesKey); ok {
|
||||||
|
if cbb, ok := cb.([]byte); ok {
|
||||||
|
body = cbb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if body == nil {
|
||||||
|
body, err = ioutil.ReadAll(c.Request.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Set(BodyBytesKey, body)
|
||||||
|
}
|
||||||
|
return bb.BindBody(body, obj)
|
||||||
|
}
|
||||||
|
|
||||||
// ClientIP implements a best effort algorithm to return the real client IP, it parses
|
// ClientIP implements a best effort algorithm to return the real client IP, it parses
|
||||||
// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.
|
// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.
|
||||||
// Use X-Forwarded-For before X-Real-Ip as nginx uses X-Real-Ip with the proxy's IP.
|
// Use X-Forwarded-For before X-Real-Ip as nginx uses X-Real-Ip with the proxy's IP.
|
||||||
func (c *Context) ClientIP() string {
|
func (c *Context) ClientIP() string {
|
||||||
if c.engine.ForwardedByClientIP {
|
if c.engine.ForwardedByClientIP {
|
||||||
clientIP := c.requestHeader("X-Forwarded-For")
|
clientIP := c.requestHeader("X-Forwarded-For")
|
||||||
if index := strings.IndexByte(clientIP, ','); index >= 0 {
|
clientIP = strings.TrimSpace(strings.Split(clientIP, ",")[0])
|
||||||
clientIP = clientIP[0:index]
|
if clientIP == "" {
|
||||||
}
|
|
||||||
clientIP = strings.TrimSpace(clientIP)
|
|
||||||
if clientIP != "" {
|
|
||||||
return clientIP
|
|
||||||
}
|
|
||||||
clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip"))
|
clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip"))
|
||||||
|
}
|
||||||
if clientIP != "" {
|
if clientIP != "" {
|
||||||
return clientIP
|
return clientIP
|
||||||
}
|
}
|
||||||
@ -568,9 +649,9 @@ func bodyAllowedForStatus(status int) bool {
|
|||||||
switch {
|
switch {
|
||||||
case status >= 100 && status <= 199:
|
case status >= 100 && status <= 199:
|
||||||
return false
|
return false
|
||||||
case status == 204:
|
case status == http.StatusNoContent:
|
||||||
return false
|
return false
|
||||||
case status == 304:
|
case status == http.StatusNotModified:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -587,9 +668,9 @@ func (c *Context) Status(code int) {
|
|||||||
func (c *Context) Header(key, value string) {
|
func (c *Context) Header(key, value string) {
|
||||||
if value == "" {
|
if value == "" {
|
||||||
c.Writer.Header().Del(key)
|
c.Writer.Header().Del(key)
|
||||||
} else {
|
return
|
||||||
c.Writer.Header().Set(key, value)
|
|
||||||
}
|
}
|
||||||
|
c.Writer.Header().Set(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHeader returns value from request headers.
|
// GetHeader returns value from request headers.
|
||||||
@ -633,6 +714,7 @@ func (c *Context) Cookie(name string) (string, error) {
|
|||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render writes the response headers and calls render.Render to render data.
|
||||||
func (c *Context) Render(code int, r render.Render) {
|
func (c *Context) Render(code int, r render.Render) {
|
||||||
c.Status(code)
|
c.Status(code)
|
||||||
|
|
||||||
@ -670,12 +752,30 @@ func (c *Context) SecureJSON(code int, obj interface{}) {
|
|||||||
c.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj})
|
c.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JSONP serializes the given struct as JSON into the response body.
|
||||||
|
// 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{}) {
|
||||||
|
callback := c.DefaultQuery("callback", "")
|
||||||
|
if callback == "" {
|
||||||
|
c.Render(code, render.JSON{Data: obj})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Render(code, render.JsonpJSON{Callback: callback, Data: obj})
|
||||||
|
}
|
||||||
|
|
||||||
// JSON serializes the given struct as JSON into the response body.
|
// JSON serializes the given struct as JSON into the response body.
|
||||||
// It also sets the Content-Type as "application/json".
|
// It also sets the Content-Type as "application/json".
|
||||||
func (c *Context) JSON(code int, obj interface{}) {
|
func (c *Context) JSON(code int, obj interface{}) {
|
||||||
c.Render(code, render.JSON{Data: obj})
|
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.
|
// XML serializes the given struct as XML into the response body.
|
||||||
// It also sets the Content-Type as "application/xml".
|
// It also sets the Content-Type as "application/xml".
|
||||||
func (c *Context) XML(code int, obj interface{}) {
|
func (c *Context) XML(code int, obj interface{}) {
|
||||||
@ -687,6 +787,11 @@ func (c *Context) YAML(code int, obj interface{}) {
|
|||||||
c.Render(code, render.YAML{Data: obj})
|
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.
|
// String writes the given string into the response body.
|
||||||
func (c *Context) String(code int, format string, values ...interface{}) {
|
func (c *Context) String(code int, format string, values ...interface{}) {
|
||||||
c.Render(code, render.String{Format: format, Data: values})
|
c.Render(code, render.String{Format: format, Data: values})
|
||||||
@ -709,6 +814,16 @@ func (c *Context) Data(code int, contentType string, data []byte) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DataFromReader writes the specified reader into the body stream and updates the HTTP code.
|
||||||
|
func (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string) {
|
||||||
|
c.Render(code, render.Reader{
|
||||||
|
Headers: extraHeaders,
|
||||||
|
ContentType: contentType,
|
||||||
|
ContentLength: contentLength,
|
||||||
|
Reader: reader,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// File writes the specified file into the body stream in a efficient way.
|
// File writes the specified file into the body stream in a efficient way.
|
||||||
func (c *Context) File(filepath string) {
|
func (c *Context) File(filepath string) {
|
||||||
http.ServeFile(c.Writer, c.Request, filepath)
|
http.ServeFile(c.Writer, c.Request, filepath)
|
||||||
@ -722,6 +837,7 @@ func (c *Context) SSEvent(name string, message interface{}) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stream sends a streaming response.
|
||||||
func (c *Context) Stream(step func(w io.Writer) bool) {
|
func (c *Context) Stream(step func(w io.Writer) bool) {
|
||||||
w := c.Writer
|
w := c.Writer
|
||||||
clientGone := w.CloseNotify()
|
clientGone := w.CloseNotify()
|
||||||
@ -743,6 +859,7 @@ func (c *Context) Stream(step func(w io.Writer) bool) {
|
|||||||
/******** CONTENT NEGOTIATION *******/
|
/******** CONTENT NEGOTIATION *******/
|
||||||
/************************************/
|
/************************************/
|
||||||
|
|
||||||
|
// Negotiate contains all negotiations data.
|
||||||
type Negotiate struct {
|
type Negotiate struct {
|
||||||
Offered []string
|
Offered []string
|
||||||
HTMLName string
|
HTMLName string
|
||||||
@ -752,6 +869,7 @@ type Negotiate struct {
|
|||||||
Data interface{}
|
Data interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Negotiate calls different Render according acceptable Accept format.
|
||||||
func (c *Context) Negotiate(code int, config Negotiate) {
|
func (c *Context) Negotiate(code int, config Negotiate) {
|
||||||
switch c.NegotiateFormat(config.Offered...) {
|
switch c.NegotiateFormat(config.Offered...) {
|
||||||
case binding.MIMEJSON:
|
case binding.MIMEJSON:
|
||||||
@ -771,6 +889,7 @@ func (c *Context) Negotiate(code int, config Negotiate) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NegotiateFormat returns an acceptable Accept format.
|
||||||
func (c *Context) NegotiateFormat(offered ...string) string {
|
func (c *Context) NegotiateFormat(offered ...string) string {
|
||||||
assert1(len(offered) > 0, "you must provide at least one offer")
|
assert1(len(offered) > 0, "you must provide at least one offer")
|
||||||
|
|
||||||
@ -790,6 +909,7 @@ func (c *Context) NegotiateFormat(offered ...string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetAccepted sets Accept header data.
|
||||||
func (c *Context) SetAccepted(formats ...string) {
|
func (c *Context) SetAccepted(formats ...string) {
|
||||||
c.Accepted = formats
|
c.Accepted = formats
|
||||||
}
|
}
|
||||||
@ -798,18 +918,33 @@ func (c *Context) SetAccepted(formats ...string) {
|
|||||||
/***** GOLANG.ORG/X/NET/CONTEXT *****/
|
/***** 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) {
|
func (c *Context) Deadline() (deadline time.Time, ok bool) {
|
||||||
return
|
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{} {
|
func (c *Context) Done() <-chan struct{} {
|
||||||
return nil
|
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 {
|
func (c *Context) Err() error {
|
||||||
return nil
|
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{} {
|
func (c *Context) Value(key interface{}) interface{} {
|
||||||
if key == 0 {
|
if key == 0 {
|
||||||
return c.Request
|
return c.Request
|
||||||
|
17
context_17.go
Normal file
17
context_17.go
Normal file
@ -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})
|
||||||
|
}
|
27
context_17_test.go
Normal file
27
context_17_test.go
Normal file
@ -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": "<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.Header().Get("Content-Type"))
|
||||||
|
}
|
524
context_test.go
524
context_test.go
@ -9,6 +9,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@ -18,8 +19,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-contrib/sse"
|
"github.com/gin-contrib/sse"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ context.Context = &Context{}
|
var _ context.Context = &Context{}
|
||||||
@ -46,6 +51,8 @@ func createMultipartRequest() *http.Request {
|
|||||||
must(mw.WriteField("time_local", "31/12/2016 14:55"))
|
must(mw.WriteField("time_local", "31/12/2016 14:55"))
|
||||||
must(mw.WriteField("time_utc", "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("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)
|
req, err := http.NewRequest("POST", "/", body)
|
||||||
must(err)
|
must(err)
|
||||||
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||||
@ -77,6 +84,19 @@ func TestContextFormFile(t *testing.T) {
|
|||||||
assert.NoError(t, c.SaveUploadedFile(f, "test"))
|
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) {
|
func TestContextMultipartForm(t *testing.T) {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
mw := multipart.NewWriter(buf)
|
mw := multipart.NewWriter(buf)
|
||||||
@ -370,7 +390,8 @@ func TestContextQuery(t *testing.T) {
|
|||||||
func TestContextQueryAndPostForm(t *testing.T) {
|
func TestContextQueryAndPostForm(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second")
|
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)
|
c.Request.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
|
|
||||||
assert.Equal(t, "bar", c.DefaultPostForm("foo", "none"))
|
assert.Equal(t, "bar", c.DefaultPostForm("foo", "none"))
|
||||||
@ -438,6 +459,30 @@ func TestContextQueryAndPostForm(t *testing.T) {
|
|||||||
values = c.QueryArray("both")
|
values = c.QueryArray("both")
|
||||||
assert.Equal(t, 1, len(values))
|
assert.Equal(t, 1, len(values))
|
||||||
assert.Equal(t, "GET", values[0])
|
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) {
|
func TestContextPostFormMultipart(t *testing.T) {
|
||||||
@ -514,6 +559,22 @@ func TestContextPostFormMultipart(t *testing.T) {
|
|||||||
values = c.PostFormArray("foo")
|
values = c.PostFormArray("foo")
|
||||||
assert.Equal(t, 1, len(values))
|
assert.Equal(t, 1, len(values))
|
||||||
assert.Equal(t, "bar", values[0])
|
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) {
|
func TestContextSetCookie(t *testing.T) {
|
||||||
@ -540,10 +601,11 @@ func TestContextGetCookie(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestContextBodyAllowedForStatus(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(102))
|
||||||
assert.False(t, false, bodyAllowedForStatus(204))
|
assert.False(t, false, bodyAllowedForStatus(http.StatusNoContent))
|
||||||
assert.False(t, false, bodyAllowedForStatus(304))
|
assert.False(t, false, bodyAllowedForStatus(http.StatusNotModified))
|
||||||
assert.True(t, true, bodyAllowedForStatus(500))
|
assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError))
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestPanicRender struct {
|
type TestPanicRender struct {
|
||||||
@ -570,15 +632,44 @@ func TestContextRenderPanicIfErr(t *testing.T) {
|
|||||||
|
|
||||||
// Tests that the response is serialized as JSON
|
// Tests that the response is serialized as JSON
|
||||||
// and Content-Type is set to application/json
|
// and Content-Type is set to application/json
|
||||||
|
// and special HTML characters are escaped
|
||||||
func TestContextRenderJSON(t *testing.T) {
|
func TestContextRenderJSON(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
c.JSON(201, H{"foo": "bar"})
|
c.JSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
|
||||||
|
|
||||||
assert.Equal(t, 201, w.Code)
|
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.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that the response is serialized as JSONP
|
||||||
|
// and Content-Type is set to application/javascript
|
||||||
|
func TestContextRenderJSONP(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
c.Request, _ = http.NewRequest("GET", "http://example.com/?callback=x", nil)
|
||||||
|
|
||||||
|
c.JSONP(http.StatusCreated, H{"foo": "bar"})
|
||||||
|
|
||||||
|
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.Header().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(http.StatusCreated, H{"foo": "bar"})
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusCreated, w.Code)
|
||||||
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
|
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
|
// Tests that no JSON is rendered if code is 204
|
||||||
@ -586,11 +677,11 @@ func TestContextRenderNoContentJSON(t *testing.T) {
|
|||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
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.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
|
// Tests that the response is serialized as JSON
|
||||||
@ -600,11 +691,11 @@ func TestContextRenderAPIJSON(t *testing.T) {
|
|||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
c.Header("Content-Type", "application/vnd.api+json")
|
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, "{\"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
|
// Tests that no Custom JSON is rendered if code is 204
|
||||||
@ -613,11 +704,11 @@ func TestContextRenderNoContentAPIJSON(t *testing.T) {
|
|||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
c.Header("Content-Type", "application/vnd.api+json")
|
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.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
|
// Tests that the response is serialized as JSON
|
||||||
@ -626,11 +717,11 @@ func TestContextRenderIndentedJSON(t *testing.T) {
|
|||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
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, "{\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
|
// Tests that no Custom JSON is rendered if code is 204
|
||||||
@ -638,11 +729,11 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) {
|
|||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
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.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
|
// Tests that the response is serialized as Secure JSON
|
||||||
@ -652,11 +743,11 @@ func TestContextRenderSecureJSON(t *testing.T) {
|
|||||||
c, router := CreateTestContext(w)
|
c, router := CreateTestContext(w)
|
||||||
|
|
||||||
router.SecureJsonPrefix("&&&START&&&")
|
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, "&&&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
|
// Tests that no Custom JSON is rendered if code is 204
|
||||||
@ -664,11 +755,22 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) {
|
|||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
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.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) {
|
||||||
|
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.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that the response executes the templates
|
// Tests that the response executes the templates
|
||||||
@ -680,11 +782,11 @@ func TestContextRenderHTML(t *testing.T) {
|
|||||||
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
||||||
router.SetHTMLTemplate(templ)
|
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, "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) {
|
func TestContextRenderHTML2(t *testing.T) {
|
||||||
@ -695,20 +797,20 @@ func TestContextRenderHTML2(t *testing.T) {
|
|||||||
router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}})
|
router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}})
|
||||||
assert.Len(t, router.trees, 1)
|
assert.Len(t, router.trees, 1)
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
setup(&b)
|
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
||||||
|
re := captureOutput(func() {
|
||||||
|
SetMode(DebugMode)
|
||||||
router.SetHTMLTemplate(templ)
|
router.SetHTMLTemplate(templ)
|
||||||
|
SetMode(TestMode)
|
||||||
|
})
|
||||||
|
|
||||||
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())
|
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", re)
|
||||||
|
|
||||||
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, "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
|
// Tests that no HTML is rendered if code is 204
|
||||||
@ -718,11 +820,11 @@ func TestContextRenderNoContentHTML(t *testing.T) {
|
|||||||
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
||||||
router.SetHTMLTemplate(templ)
|
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.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
|
// TestContextXML tests that the response is serialized as XML
|
||||||
@ -731,11 +833,11 @@ func TestContextRenderXML(t *testing.T) {
|
|||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
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, "<map><foo>bar</foo></map>", w.Body.String())
|
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
|
// Tests that no XML is rendered if code is 204
|
||||||
@ -743,11 +845,11 @@ func TestContextRenderNoContentXML(t *testing.T) {
|
|||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
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.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
|
// TestContextString tests that the response is returned
|
||||||
@ -756,11 +858,11 @@ func TestContextRenderString(t *testing.T) {
|
|||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
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, "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
|
// Tests that no String is rendered if code is 204
|
||||||
@ -768,11 +870,11 @@ func TestContextRenderNoContentString(t *testing.T) {
|
|||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
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.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
|
// TestContextString tests that the response is returned
|
||||||
@ -782,11 +884,11 @@ func TestContextRenderHTMLString(t *testing.T) {
|
|||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||||
c.String(201, "<html>%s %d</html>", "string", 3)
|
c.String(http.StatusCreated, "<html>%s %d</html>", "string", 3)
|
||||||
|
|
||||||
assert.Equal(t, 201, w.Code)
|
assert.Equal(t, http.StatusCreated, w.Code)
|
||||||
assert.Equal(t, "<html>string 3</html>", w.Body.String())
|
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
|
// Tests that no HTML String is rendered if code is 204
|
||||||
@ -795,11 +897,11 @@ func TestContextRenderNoContentHTMLString(t *testing.T) {
|
|||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||||
c.String(204, "<html>%s %d</html>", "string", 3)
|
c.String(http.StatusNoContent, "<html>%s %d</html>", "string", 3)
|
||||||
|
|
||||||
assert.Equal(t, 204, w.Code)
|
assert.Equal(t, http.StatusNoContent, w.Code)
|
||||||
assert.Empty(t, w.Body.String())
|
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`
|
// TestContextData tests that the response can be written from `bytesting`
|
||||||
@ -808,11 +910,11 @@ func TestContextRenderData(t *testing.T) {
|
|||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
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, "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
|
// Tests that no Custom Data is rendered if code is 204
|
||||||
@ -820,11 +922,11 @@ func TestContextRenderNoContentData(t *testing.T) {
|
|||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
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.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) {
|
func TestContextRenderSSE(t *testing.T) {
|
||||||
@ -851,9 +953,9 @@ func TestContextRenderFile(t *testing.T) {
|
|||||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||||
c.File("./gin.go")
|
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.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
|
// TestContextRenderYAML tests that the response is serialized as YAML
|
||||||
@ -862,11 +964,35 @@ func TestContextRenderYAML(t *testing.T) {
|
|||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
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, "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
|
||||||
|
// 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.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextHeaders(t *testing.T) {
|
func TestContextHeaders(t *testing.T) {
|
||||||
@ -894,9 +1020,9 @@ func TestContextRenderRedirectWithRelativePath(t *testing.T) {
|
|||||||
assert.Panics(t, func() { c.Redirect(299, "/new_path") })
|
assert.Panics(t, func() { c.Redirect(299, "/new_path") })
|
||||||
assert.Panics(t, func() { c.Redirect(309, "/new_path") })
|
assert.Panics(t, func() { c.Redirect(309, "/new_path") })
|
||||||
|
|
||||||
c.Redirect(301, "/path")
|
c.Redirect(http.StatusMovedPermanently, "/path")
|
||||||
c.Writer.WriteHeaderNow()
|
c.Writer.WriteHeaderNow()
|
||||||
assert.Equal(t, 301, w.Code)
|
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||||
assert.Equal(t, "/path", w.Header().Get("Location"))
|
assert.Equal(t, "/path", w.Header().Get("Location"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -905,10 +1031,10 @@ func TestContextRenderRedirectWithAbsolutePath(t *testing.T) {
|
|||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
c.Request, _ = http.NewRequest("POST", "http://example.com", nil)
|
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()
|
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"))
|
assert.Equal(t, "http://google.com", w.Header().Get("Location"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -917,21 +1043,23 @@ func TestContextRenderRedirectWith201(t *testing.T) {
|
|||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
c.Request, _ = http.NewRequest("POST", "http://example.com", nil)
|
c.Request, _ = http.NewRequest("POST", "http://example.com", nil)
|
||||||
c.Redirect(201, "/resource")
|
c.Redirect(http.StatusCreated, "/resource")
|
||||||
c.Writer.WriteHeaderNow()
|
c.Writer.WriteHeaderNow()
|
||||||
|
|
||||||
assert.Equal(t, 201, w.Code)
|
assert.Equal(t, http.StatusCreated, w.Code)
|
||||||
assert.Equal(t, "/resource", w.Header().Get("Location"))
|
assert.Equal(t, "/resource", w.Header().Get("Location"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextRenderRedirectAll(t *testing.T) {
|
func TestContextRenderRedirectAll(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
c.Request, _ = http.NewRequest("POST", "http://example.com", nil)
|
c.Request, _ = http.NewRequest("POST", "http://example.com", nil)
|
||||||
assert.Panics(t, func() { c.Redirect(200, "/resource") })
|
assert.Panics(t, func() { c.Redirect(http.StatusOK, "/resource") })
|
||||||
assert.Panics(t, func() { c.Redirect(202, "/resource") })
|
assert.Panics(t, func() { c.Redirect(http.StatusAccepted, "/resource") })
|
||||||
assert.Panics(t, func() { c.Redirect(299, "/resource") })
|
assert.Panics(t, func() { c.Redirect(299, "/resource") })
|
||||||
assert.Panics(t, func() { c.Redirect(309, "/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") })
|
assert.NotPanics(t, func() { c.Redirect(308, "/resource") })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -940,14 +1068,14 @@ func TestContextNegotiationWithJSON(t *testing.T) {
|
|||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
c.Request, _ = http.NewRequest("POST", "", nil)
|
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||||
|
|
||||||
c.Negotiate(200, Negotiate{
|
c.Negotiate(http.StatusOK, Negotiate{
|
||||||
Offered: []string{MIMEJSON, MIMEXML},
|
Offered: []string{MIMEJSON, MIMEXML},
|
||||||
Data: H{"foo": "bar"},
|
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, "{\"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) {
|
func TestContextNegotiationWithXML(t *testing.T) {
|
||||||
@ -955,14 +1083,14 @@ func TestContextNegotiationWithXML(t *testing.T) {
|
|||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
c.Request, _ = http.NewRequest("POST", "", nil)
|
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||||
|
|
||||||
c.Negotiate(200, Negotiate{
|
c.Negotiate(http.StatusOK, Negotiate{
|
||||||
Offered: []string{MIMEXML, MIMEJSON},
|
Offered: []string{MIMEXML, MIMEJSON},
|
||||||
Data: H{"foo": "bar"},
|
Data: H{"foo": "bar"},
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Equal(t, "<map><foo>bar</foo></map>", w.Body.String())
|
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) {
|
func TestContextNegotiationWithHTML(t *testing.T) {
|
||||||
@ -972,15 +1100,15 @@ func TestContextNegotiationWithHTML(t *testing.T) {
|
|||||||
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
||||||
router.SetHTMLTemplate(templ)
|
router.SetHTMLTemplate(templ)
|
||||||
|
|
||||||
c.Negotiate(200, Negotiate{
|
c.Negotiate(http.StatusOK, Negotiate{
|
||||||
Offered: []string{MIMEHTML},
|
Offered: []string{MIMEHTML},
|
||||||
Data: H{"name": "gin"},
|
Data: H{"name": "gin"},
|
||||||
HTMLName: "t",
|
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, "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) {
|
func TestContextNegotiationNotSupport(t *testing.T) {
|
||||||
@ -988,11 +1116,11 @@ func TestContextNegotiationNotSupport(t *testing.T) {
|
|||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
c.Request, _ = http.NewRequest("POST", "", nil)
|
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||||
|
|
||||||
c.Negotiate(200, Negotiate{
|
c.Negotiate(http.StatusOK, Negotiate{
|
||||||
Offered: []string{MIMEPOSTForm},
|
Offered: []string{MIMEPOSTForm},
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.Equal(t, 406, w.Code)
|
assert.Equal(t, http.StatusNotAcceptable, w.Code)
|
||||||
assert.Equal(t, c.index, abortIndex)
|
assert.Equal(t, c.index, abortIndex)
|
||||||
assert.True(t, c.IsAborted())
|
assert.True(t, c.IsAborted())
|
||||||
}
|
}
|
||||||
@ -1016,7 +1144,7 @@ func TestContextNegotiationFormatWithAccept(t *testing.T) {
|
|||||||
assert.Empty(t, c.NegotiateFormat(MIMEJSON))
|
assert.Empty(t, c.NegotiateFormat(MIMEJSON))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextNegotiationFormatCustum(t *testing.T) {
|
func TestContextNegotiationFormatCustom(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||||
c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
||||||
@ -1050,11 +1178,11 @@ func TestContextAbortWithStatus(t *testing.T) {
|
|||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
c.index = 4
|
c.index = 4
|
||||||
c.AbortWithStatus(401)
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
|
||||||
assert.Equal(t, abortIndex, c.index)
|
assert.Equal(t, abortIndex, c.index)
|
||||||
assert.Equal(t, 401, c.Writer.Status())
|
assert.Equal(t, http.StatusUnauthorized, c.Writer.Status())
|
||||||
assert.Equal(t, 401, w.Code)
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||||
assert.True(t, c.IsAborted())
|
assert.True(t, c.IsAborted())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1072,11 +1200,11 @@ func TestContextAbortWithStatusJSON(t *testing.T) {
|
|||||||
in.Bar = "barValue"
|
in.Bar = "barValue"
|
||||||
in.Foo = "fooValue"
|
in.Foo = "fooValue"
|
||||||
|
|
||||||
c.AbortWithStatusJSON(415, in)
|
c.AbortWithStatusJSON(http.StatusUnsupportedMediaType, in)
|
||||||
|
|
||||||
assert.Equal(t, abortIndex, c.index)
|
assert.Equal(t, abortIndex, c.index)
|
||||||
assert.Equal(t, 415, c.Writer.Status())
|
assert.Equal(t, http.StatusUnsupportedMediaType, c.Writer.Status())
|
||||||
assert.Equal(t, 415, w.Code)
|
assert.Equal(t, http.StatusUnsupportedMediaType, w.Code)
|
||||||
assert.True(t, c.IsAborted())
|
assert.True(t, c.IsAborted())
|
||||||
|
|
||||||
contentType := w.Header().Get("Content-Type")
|
contentType := w.Header().Get("Content-Type")
|
||||||
@ -1139,9 +1267,9 @@ func TestContextAbortWithError(t *testing.T) {
|
|||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
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.Equal(t, abortIndex, c.index)
|
||||||
assert.True(t, c.IsAborted())
|
assert.True(t, c.IsAborted())
|
||||||
}
|
}
|
||||||
@ -1215,6 +1343,26 @@ func TestContextBindWithJSON(t *testing.T) {
|
|||||||
assert.Equal(t, "bar", obj.Foo)
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
assert.Equal(t, 0, w.Body.Len())
|
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(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root>
|
||||||
|
<foo>FOO</foo>
|
||||||
|
<bar>BAR</bar>
|
||||||
|
</root>`))
|
||||||
|
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) {
|
func TestContextBindWithQuery(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
@ -1249,7 +1397,7 @@ func TestContextBadAutoBind(t *testing.T) {
|
|||||||
|
|
||||||
assert.Empty(t, obj.Bar)
|
assert.Empty(t, obj.Bar)
|
||||||
assert.Empty(t, obj.Foo)
|
assert.Empty(t, obj.Foo)
|
||||||
assert.Equal(t, 400, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.True(t, c.IsAborted())
|
assert.True(t, c.IsAborted())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1285,6 +1433,27 @@ func TestContextShouldBindWithJSON(t *testing.T) {
|
|||||||
assert.Equal(t, 0, w.Body.Len())
|
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(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root>
|
||||||
|
<foo>FOO</foo>
|
||||||
|
<bar>BAR</bar>
|
||||||
|
</root>`))
|
||||||
|
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) {
|
func TestContextShouldBindWithQuery(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
@ -1320,6 +1489,85 @@ func TestContextBadAutoShouldBind(t *testing.T) {
|
|||||||
assert.False(t, c.IsAborted())
|
assert.False(t, c.IsAborted())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextShouldBindBodyWith(t *testing.T) {
|
||||||
|
type typeA struct {
|
||||||
|
Foo string `json:"foo" xml:"foo" binding:"required"`
|
||||||
|
}
|
||||||
|
type typeB struct {
|
||||||
|
Bar string `json:"bar" xml:"bar" binding:"required"`
|
||||||
|
}
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
bindingA, bindingB binding.BindingBody
|
||||||
|
bodyA, bodyB string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "JSON & JSON",
|
||||||
|
bindingA: binding.JSON,
|
||||||
|
bindingB: binding.JSON,
|
||||||
|
bodyA: `{"foo":"FOO"}`,
|
||||||
|
bodyB: `{"bar":"BAR"}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "JSON & XML",
|
||||||
|
bindingA: binding.JSON,
|
||||||
|
bindingB: binding.XML,
|
||||||
|
bodyA: `{"foo":"FOO"}`,
|
||||||
|
bodyB: `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root>
|
||||||
|
<bar>BAR</bar>
|
||||||
|
</root>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "XML & XML",
|
||||||
|
bindingA: binding.XML,
|
||||||
|
bindingB: binding.XML,
|
||||||
|
bodyA: `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root>
|
||||||
|
<foo>FOO</foo>
|
||||||
|
</root>`,
|
||||||
|
bodyB: `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root>
|
||||||
|
<bar>BAR</bar>
|
||||||
|
</root>`,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Logf("testing: %s", tt.name)
|
||||||
|
// bodyA to typeA and typeB
|
||||||
|
{
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
c.Request, _ = http.NewRequest(
|
||||||
|
"POST", "http://example.com", bytes.NewBufferString(tt.bodyA),
|
||||||
|
)
|
||||||
|
// When it binds to typeA and typeB, it finds the body is
|
||||||
|
// not typeB but typeA.
|
||||||
|
objA := typeA{}
|
||||||
|
assert.NoError(t, c.ShouldBindBodyWith(&objA, tt.bindingA))
|
||||||
|
assert.Equal(t, typeA{"FOO"}, objA)
|
||||||
|
objB := typeB{}
|
||||||
|
assert.Error(t, c.ShouldBindBodyWith(&objB, tt.bindingB))
|
||||||
|
assert.NotEqual(t, typeB{"BAR"}, objB)
|
||||||
|
}
|
||||||
|
// bodyB to typeA and typeB
|
||||||
|
{
|
||||||
|
// When it binds to typeA and typeB, it finds the body is
|
||||||
|
// not typeA but typeB.
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
c.Request, _ = http.NewRequest(
|
||||||
|
"POST", "http://example.com", bytes.NewBufferString(tt.bodyB),
|
||||||
|
)
|
||||||
|
objA := typeA{}
|
||||||
|
assert.Error(t, c.ShouldBindBodyWith(&objA, tt.bindingA))
|
||||||
|
assert.NotEqual(t, typeA{"FOO"}, objA)
|
||||||
|
objB := typeB{}
|
||||||
|
assert.NoError(t, c.ShouldBindBodyWith(&objB, tt.bindingB))
|
||||||
|
assert.Equal(t, typeB{"BAR"}, objB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextGolangContext(t *testing.T) {
|
func TestContextGolangContext(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
|
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
|
||||||
@ -1377,3 +1625,77 @@ func TestContextGetRawData(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "Fetch binary post data", string(data))
|
assert.Equal(t, "Fetch binary post data", string(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextRenderDataFromReader(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
body := "#!PNG some raw data"
|
||||||
|
reader := strings.NewReader(body)
|
||||||
|
contentLength := int64(len(body))
|
||||||
|
contentType := "image/png"
|
||||||
|
extraHeaders := map[string]string{"Content-Disposition": `attachment; filename="gopher.png"`}
|
||||||
|
|
||||||
|
c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Equal(t, body, w.Body.String())
|
||||||
|
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 {
|
||||||
|
*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)
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
33
debug.go
33
debug.go
@ -6,13 +6,15 @@ package gin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
const ginSupportMinGoVer = 6
|
||||||
log.SetFlags(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDebugging returns true if the framework is running in debug mode.
|
// IsDebugging returns true if the framework is running in debug mode.
|
||||||
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
||||||
@ -20,11 +22,18 @@ func IsDebugging() bool {
|
|||||||
return ginMode == debugCode
|
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) {
|
func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) {
|
||||||
if IsDebugging() {
|
if IsDebugging() {
|
||||||
nuHandlers := len(handlers)
|
nuHandlers := len(handlers)
|
||||||
handlerName := nameOfFunction(handlers.Last())
|
handlerName := nameOfFunction(handlers.Last())
|
||||||
|
if DebugPrintRouteFunc == nil {
|
||||||
debugPrint("%-6s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers)
|
debugPrint("%-6s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers)
|
||||||
|
} else {
|
||||||
|
DebugPrintRouteFunc(httpMethod, absolutePath, handlerName, nuHandlers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,11 +51,25 @@ func debugPrintLoadTemplate(tmpl *template.Template) {
|
|||||||
|
|
||||||
func debugPrint(format string, values ...interface{}) {
|
func debugPrint(format string, values ...interface{}) {
|
||||||
if IsDebugging() {
|
if IsDebugging() {
|
||||||
log.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() {
|
func debugPrintWARNINGDefault() {
|
||||||
|
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.
|
debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
|
||||||
|
|
||||||
`)
|
`)
|
||||||
|
133
debug_test.go
133
debug_test.go
@ -11,6 +11,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -30,86 +32,121 @@ func TestIsDebugging(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDebugPrint(t *testing.T) {
|
func TestDebugPrint(t *testing.T) {
|
||||||
var w bytes.Buffer
|
re := captureOutput(func() {
|
||||||
setup(&w)
|
SetMode(DebugMode)
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
SetMode(ReleaseMode)
|
SetMode(ReleaseMode)
|
||||||
debugPrint("DEBUG this!")
|
debugPrint("DEBUG this!")
|
||||||
SetMode(TestMode)
|
SetMode(TestMode)
|
||||||
debugPrint("DEBUG this!")
|
debugPrint("DEBUG this!")
|
||||||
assert.Empty(t, w.String())
|
|
||||||
|
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
debugPrint("these are %d %s\n", 2, "error messages")
|
debugPrint("these are %d %s\n", 2, "error messages")
|
||||||
assert.Equal(t, "[GIN-debug] these are 2 error messages\n", w.String())
|
SetMode(TestMode)
|
||||||
|
})
|
||||||
|
assert.Equal(t, "[GIN-debug] these are 2 error messages\n", re)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDebugPrintError(t *testing.T) {
|
func TestDebugPrintError(t *testing.T) {
|
||||||
var w bytes.Buffer
|
re := captureOutput(func() {
|
||||||
setup(&w)
|
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
debugPrintError(nil)
|
debugPrintError(nil)
|
||||||
assert.Empty(t, w.String())
|
|
||||||
|
|
||||||
debugPrintError(errors.New("this is an error"))
|
debugPrintError(errors.New("this is an error"))
|
||||||
assert.Equal(t, "[GIN-debug] [ERROR] this is an error\n", w.String())
|
SetMode(TestMode)
|
||||||
|
})
|
||||||
|
assert.Equal(t, "[GIN-debug] [ERROR] this is an error\n", re)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDebugPrintRoutes(t *testing.T) {
|
func TestDebugPrintRoutes(t *testing.T) {
|
||||||
var w bytes.Buffer
|
re := captureOutput(func() {
|
||||||
setup(&w)
|
SetMode(DebugMode)
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest})
|
debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest})
|
||||||
assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, w.String())
|
SetMode(TestMode)
|
||||||
|
})
|
||||||
|
assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, re)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDebugPrintLoadTemplate(t *testing.T) {
|
func TestDebugPrintLoadTemplate(t *testing.T) {
|
||||||
var w bytes.Buffer
|
re := captureOutput(func() {
|
||||||
setup(&w)
|
SetMode(DebugMode)
|
||||||
defer teardown()
|
templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl"))
|
||||||
|
|
||||||
templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./fixtures/basic/hello.tmpl"))
|
|
||||||
debugPrintLoadTemplate(templ)
|
debugPrintLoadTemplate(templ)
|
||||||
assert.Regexp(t, `^\[GIN-debug\] Loaded HTML Templates \(2\): \n(\t- \n|\t- hello\.tmpl\n){2}\n`, w.String())
|
SetMode(TestMode)
|
||||||
|
})
|
||||||
|
assert.Regexp(t, `^\[GIN-debug\] Loaded HTML Templates \(2\): \n(\t- \n|\t- hello\.tmpl\n){2}\n`, re)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) {
|
func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) {
|
||||||
var w bytes.Buffer
|
re := captureOutput(func() {
|
||||||
setup(&w)
|
SetMode(DebugMode)
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
debugPrintWARNINGSetHTMLTemplate()
|
debugPrintWARNINGSetHTMLTemplate()
|
||||||
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", w.String())
|
SetMode(TestMode)
|
||||||
|
})
|
||||||
|
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", re)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDebugPrintWARNINGDefault(t *testing.T) {
|
func TestDebugPrintWARNINGDefault(t *testing.T) {
|
||||||
var w bytes.Buffer
|
re := captureOutput(func() {
|
||||||
setup(&w)
|
SetMode(DebugMode)
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
debugPrintWARNINGDefault()
|
debugPrintWARNINGDefault()
|
||||||
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", w.String())
|
SetMode(TestMode)
|
||||||
|
})
|
||||||
|
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) {
|
func TestDebugPrintWARNINGNew(t *testing.T) {
|
||||||
var w bytes.Buffer
|
re := captureOutput(func() {
|
||||||
setup(&w)
|
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
debugPrintWARNINGNew()
|
|
||||||
assert.Equal(t, "[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:\texport GIN_MODE=release\n - using code:\tgin.SetMode(gin.ReleaseMode)\n\n", w.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func setup(w io.Writer) {
|
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
log.SetOutput(w)
|
debugPrintWARNINGNew()
|
||||||
|
SetMode(TestMode)
|
||||||
|
})
|
||||||
|
assert.Equal(t, "[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:\texport GIN_MODE=release\n - using code:\tgin.SetMode(gin.ReleaseMode)\n\n", re)
|
||||||
}
|
}
|
||||||
|
|
||||||
func teardown() {
|
func captureOutput(f func()) string {
|
||||||
SetMode(TestMode)
|
reader, writer, err := os.Pipe()
|
||||||
log.SetOutput(os.Stdout)
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
stdout := os.Stdout
|
||||||
|
stderr := os.Stderr
|
||||||
|
defer func() {
|
||||||
|
os.Stdout = stdout
|
||||||
|
os.Stderr = stderr
|
||||||
|
log.SetOutput(os.Stderr)
|
||||||
|
}()
|
||||||
|
os.Stdout = writer
|
||||||
|
os.Stderr = writer
|
||||||
|
log.SetOutput(writer)
|
||||||
|
out := make(chan string)
|
||||||
|
wg := new(sync.WaitGroup)
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
wg.Done()
|
||||||
|
io.Copy(&buf, reader)
|
||||||
|
out <- buf.String()
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
f()
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
137
docs/how-to-build-an-effective-middleware.md
Normal file
137
docs/how-to-build-an-effective-middleware.md
Normal 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
|
||||||
|
```
|
20
errors.go
20
errors.go
@ -9,21 +9,27 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/json"
|
"github.com/gin-gonic/gin/internal/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrorType is an unsigned 64-bit error code as defined in the gin spec.
|
||||||
type ErrorType uint64
|
type ErrorType uint64
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ErrorTypeBind ErrorType = 1 << 63 // used when c.Bind() fails
|
// ErrorTypeBind is used when Context.Bind() fails.
|
||||||
ErrorTypeRender ErrorType = 1 << 62 // used when c.Render() 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
|
ErrorTypePrivate ErrorType = 1 << 0
|
||||||
|
// ErrorTypePublic indicates a public error.
|
||||||
ErrorTypePublic ErrorType = 1 << 1
|
ErrorTypePublic ErrorType = 1 << 1
|
||||||
|
// ErrorTypeAny indicates other any error.
|
||||||
ErrorTypeAny ErrorType = 1<<64 - 1
|
ErrorTypeAny ErrorType = 1<<64 - 1
|
||||||
ErrorTypeNu = 2
|
ErrorTypeNu = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Error represents a error's specification.
|
||||||
type Error struct {
|
type Error struct {
|
||||||
Err error
|
Err error
|
||||||
Type ErrorType
|
Type ErrorType
|
||||||
@ -34,11 +40,13 @@ type errorMsgs []*Error
|
|||||||
|
|
||||||
var _ error = &Error{}
|
var _ error = &Error{}
|
||||||
|
|
||||||
|
// SetType sets the error's type.
|
||||||
func (msg *Error) SetType(flags ErrorType) *Error {
|
func (msg *Error) SetType(flags ErrorType) *Error {
|
||||||
msg.Type = flags
|
msg.Type = flags
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetMeta sets the error's meta data.
|
||||||
func (msg *Error) SetMeta(data interface{}) *Error {
|
func (msg *Error) SetMeta(data interface{}) *Error {
|
||||||
msg.Meta = data
|
msg.Meta = data
|
||||||
return msg
|
return msg
|
||||||
@ -70,11 +78,12 @@ func (msg *Error) MarshalJSON() ([]byte, error) {
|
|||||||
return json.Marshal(msg.JSON())
|
return json.Marshal(msg.JSON())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error implements the error interface
|
// Error implements the error interface.
|
||||||
func (msg Error) Error() string {
|
func (msg Error) Error() string {
|
||||||
return msg.Err.Error()
|
return msg.Err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsType judges one error.
|
||||||
func (msg *Error) IsType(flags ErrorType) bool {
|
func (msg *Error) IsType(flags ErrorType) bool {
|
||||||
return (msg.Type & flags) > 0
|
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) {
|
func (a errorMsgs) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(a.JSON())
|
return json.Marshal(a.JSON())
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/json"
|
"github.com/gin-gonic/gin/internal/json"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,17 +19,17 @@ func TestError(t *testing.T) {
|
|||||||
Type: ErrorTypePrivate,
|
Type: ErrorTypePrivate,
|
||||||
}
|
}
|
||||||
assert.Equal(t, err.Error(), baseError.Error())
|
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.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.SetMeta("some data"), err)
|
||||||
assert.Equal(t, err.Meta, "some data")
|
assert.Equal(t, "some data", err.Meta)
|
||||||
assert.Equal(t, err.JSON(), H{
|
assert.Equal(t, H{
|
||||||
"error": baseError.Error(),
|
"error": baseError.Error(),
|
||||||
"meta": "some data",
|
"meta": "some data",
|
||||||
})
|
}, err.JSON())
|
||||||
|
|
||||||
jsonBytes, _ := json.Marshal(err)
|
jsonBytes, _ := json.Marshal(err)
|
||||||
assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes))
|
assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes))
|
||||||
@ -38,22 +38,22 @@ func TestError(t *testing.T) {
|
|||||||
"status": "200",
|
"status": "200",
|
||||||
"data": "some data",
|
"data": "some data",
|
||||||
})
|
})
|
||||||
assert.Equal(t, err.JSON(), H{
|
assert.Equal(t, H{
|
||||||
"error": baseError.Error(),
|
"error": baseError.Error(),
|
||||||
"status": "200",
|
"status": "200",
|
||||||
"data": "some data",
|
"data": "some data",
|
||||||
})
|
}, err.JSON())
|
||||||
|
|
||||||
err.SetMeta(H{
|
err.SetMeta(H{
|
||||||
"error": "custom error",
|
"error": "custom error",
|
||||||
"status": "200",
|
"status": "200",
|
||||||
"data": "some data",
|
"data": "some data",
|
||||||
})
|
})
|
||||||
assert.Equal(t, err.JSON(), H{
|
assert.Equal(t, H{
|
||||||
"error": "custom error",
|
"error": "custom error",
|
||||||
"status": "200",
|
"status": "200",
|
||||||
"data": "some data",
|
"data": "some data",
|
||||||
})
|
}, err.JSON())
|
||||||
|
|
||||||
type customError struct {
|
type customError struct {
|
||||||
status string
|
status string
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
# Guide to run Gin under App Engine LOCAL Development Server
|
# Guide to run Gin under App Engine LOCAL Development Server
|
||||||
|
|
||||||
1. Download, install and setup Go in your computer. (That includes setting your `$GOPATH`.)
|
1. Download, install and setup Go in your computer. (That includes setting your `$GOPATH`.)
|
||||||
2. Download SDK for your platform from here: `https://developers.google.com/appengine/downloads?hl=es#Google_App_Engine_SDK_for_Go`
|
2. Download SDK for your platform from [here](https://cloud.google.com/appengine/docs/standard/go/download): `https://cloud.google.com/appengine/docs/standard/go/download`
|
||||||
3. Download Gin source code using: `$ go get github.com/gin-gonic/gin`
|
3. Download Gin source code using: `$ go get github.com/gin-gonic/gin`
|
||||||
4. Navigate to examples folder: `$ cd $GOPATH/src/github.com/gin-gonic/gin/examples/`
|
4. Navigate to examples folder: `$ cd $GOPATH/src/github.com/gin-gonic/gin/examples/app-engine/`
|
||||||
5. Run it: `$ goapp serve app-engine/`
|
5. Run it: `$ dev_appserver.py .` (notice that you have to run this script by Python2)
|
||||||
|
|
||||||
|
33
examples/assets-in-binary/README.md
Normal file
33
examples/assets-in-binary/README.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Building a single binary containing templates
|
||||||
|
|
||||||
|
This is a complete example to create a single binary with the
|
||||||
|
[gin-gonic/gin][gin] Web Server with HTML templates.
|
||||||
|
|
||||||
|
[gin]: https://github.com/gin-gonic/gin
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
### Prepare Packages
|
||||||
|
|
||||||
|
```
|
||||||
|
go get github.com/gin-gonic/gin
|
||||||
|
go get github.com/jessevdk/go-assets-builder
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generate assets.go
|
||||||
|
|
||||||
|
```
|
||||||
|
go-assets-builder html -o assets.go
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build the server
|
||||||
|
|
||||||
|
```
|
||||||
|
go build -o assets-in-binary
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run
|
||||||
|
|
||||||
|
```
|
||||||
|
./assets-in-binary
|
||||||
|
```
|
34
examples/assets-in-binary/assets.go
Normal file
34
examples/assets-in-binary/assets.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jessevdk/go-assets"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _Assetsbfa8d115ce0617d89507412d5393a462f8e9b003 = "<!doctype html>\n<body>\n <p>Can you see this? → {{.Bar}}</p>\n</body>\n"
|
||||||
|
var _Assets3737a75b5254ed1f6d588b40a3449721f9ea86c2 = "<!doctype html>\n<body>\n <p>Hello, {{.Foo}}</p>\n</body>\n"
|
||||||
|
|
||||||
|
// Assets returns go-assets FileSystem
|
||||||
|
var Assets = assets.NewFileSystem(map[string][]string{"/": {"html"}, "/html": {"bar.tmpl", "index.tmpl"}}, map[string]*assets.File{
|
||||||
|
"/": {
|
||||||
|
Path: "/",
|
||||||
|
FileMode: 0x800001ed,
|
||||||
|
Mtime: time.Unix(1524365738, 1524365738517125470),
|
||||||
|
Data: nil,
|
||||||
|
}, "/html": {
|
||||||
|
Path: "/html",
|
||||||
|
FileMode: 0x800001ed,
|
||||||
|
Mtime: time.Unix(1524365491, 1524365491289799093),
|
||||||
|
Data: nil,
|
||||||
|
}, "/html/bar.tmpl": {
|
||||||
|
Path: "/html/bar.tmpl",
|
||||||
|
FileMode: 0x1a4,
|
||||||
|
Mtime: time.Unix(1524365491, 1524365491289611557),
|
||||||
|
Data: []byte(_Assetsbfa8d115ce0617d89507412d5393a462f8e9b003),
|
||||||
|
}, "/html/index.tmpl": {
|
||||||
|
Path: "/html/index.tmpl",
|
||||||
|
FileMode: 0x1a4,
|
||||||
|
Mtime: time.Unix(1524365491, 1524365491289995821),
|
||||||
|
Data: []byte(_Assets3737a75b5254ed1f6d588b40a3449721f9ea86c2),
|
||||||
|
}}, "")
|
4
examples/assets-in-binary/html/bar.tmpl
Normal file
4
examples/assets-in-binary/html/bar.tmpl
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<body>
|
||||||
|
<p>Can you see this? → {{.Bar}}</p>
|
||||||
|
</body>
|
4
examples/assets-in-binary/html/index.tmpl
Normal file
4
examples/assets-in-binary/html/index.tmpl
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<body>
|
||||||
|
<p>Hello, {{.Foo}}</p>
|
||||||
|
</body>
|
48
examples/assets-in-binary/main.go
Normal file
48
examples/assets-in-binary/main.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.New()
|
||||||
|
t, err := loadTemplate()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
r.SetHTMLTemplate(t)
|
||||||
|
r.GET("/", func(c *gin.Context) {
|
||||||
|
c.HTML(http.StatusOK, "/html/index.tmpl", gin.H{
|
||||||
|
"Foo": "World",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
r.GET("/bar", func(c *gin.Context) {
|
||||||
|
c.HTML(http.StatusOK, "/html/bar.tmpl", gin.H{
|
||||||
|
"Bar": "World",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadTemplate() (*template.Template, error) {
|
||||||
|
t := template.New("")
|
||||||
|
for name, file := range Assets.Files {
|
||||||
|
if file.IsDir() || !strings.HasSuffix(name, ".tmpl") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h, err := ioutil.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t, err = t.New(name).Parse(string(h))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var DB = make(map[string]string)
|
var db = make(map[string]string)
|
||||||
|
|
||||||
func setupRouter() *gin.Engine {
|
func setupRouter() *gin.Engine {
|
||||||
// Disable Console Color
|
// Disable Console Color
|
||||||
@ -13,17 +15,17 @@ func setupRouter() *gin.Engine {
|
|||||||
|
|
||||||
// Ping test
|
// Ping test
|
||||||
r.GET("/ping", func(c *gin.Context) {
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
c.String(200, "pong")
|
c.String(http.StatusOK, "pong")
|
||||||
})
|
})
|
||||||
|
|
||||||
// Get user value
|
// Get user value
|
||||||
r.GET("/user/:name", func(c *gin.Context) {
|
r.GET("/user/:name", func(c *gin.Context) {
|
||||||
user := c.Params.ByName("name")
|
user := c.Params.ByName("name")
|
||||||
value, ok := DB[user]
|
value, ok := db[user]
|
||||||
if ok {
|
if ok {
|
||||||
c.JSON(200, gin.H{"user": user, "value": value})
|
c.JSON(http.StatusOK, gin.H{"user": user, "value": value})
|
||||||
} else {
|
} else {
|
||||||
c.JSON(200, gin.H{"user": user, "status": "no value"})
|
c.JSON(http.StatusOK, gin.H{"user": user, "status": "no value"})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -48,8 +50,8 @@ func setupRouter() *gin.Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.Bind(&json) == nil {
|
if c.Bind(&json) == nil {
|
||||||
DB[user] = json.Value
|
db[user] = json.Value
|
||||||
c.JSON(200, gin.H{"status": "ok"})
|
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -15,6 +15,6 @@ func TestPingRoute(t *testing.T) {
|
|||||||
req, _ := http.NewRequest("GET", "/ping", nil)
|
req, _ := http.NewRequest("GET", "/ping", nil)
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Equal(t, "pong", w.Body.String())
|
assert.Equal(t, "pong", w.Body.String())
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"gopkg.in/go-playground/validator.v8"
|
"gopkg.in/go-playground/validator.v8"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Booking contains binded and validated data.
|
||||||
type Booking struct {
|
type Booking struct {
|
||||||
CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
|
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"`
|
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
|
||||||
@ -30,7 +31,11 @@ func bookableDate(
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
route := gin.Default()
|
route := gin.Default()
|
||||||
binding.Validator.RegisterValidation("bookabledate", bookableDate)
|
|
||||||
|
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
|
||||||
|
v.RegisterValidation("bookabledate", bookableDate)
|
||||||
|
}
|
||||||
|
|
||||||
route.GET("/bookable", getBookable)
|
route.GET("/bookable", getBookable)
|
||||||
route.Run(":8085")
|
route.Run(":8085")
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/thinkerou/favicon"
|
"github.com/thinkerou/favicon"
|
||||||
)
|
)
|
||||||
@ -9,7 +11,7 @@ func main() {
|
|||||||
app := gin.Default()
|
app := gin.Default()
|
||||||
app.Use(favicon.New("./favicon.ico"))
|
app.Use(favicon.New("./favicon.ico"))
|
||||||
app.GET("/ping", func(c *gin.Context) {
|
app.GET("/ping", func(c *gin.Context) {
|
||||||
c.String(200, "Hello favicon.")
|
c.String(http.StatusOK, "Hello favicon.")
|
||||||
})
|
})
|
||||||
app.Run(":8080")
|
app.Run(":8080")
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,8 @@ func main() {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
// service connections
|
// service connections
|
||||||
if err := srv.ListenAndServe(); err != nil {
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
log.Printf("listen: %s\n", err)
|
log.Fatalf("listen: %s\n", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
19
examples/grpc/README.md
Normal file
19
examples/grpc/README.md
Normal file
@ -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'
|
||||||
|
```
|
46
examples/grpc/gin/main.go
Normal file
46
examples/grpc/gin/main.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
34
examples/grpc/grpc/server.go
Normal file
34
examples/grpc/grpc/server.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
151
examples/grpc/pb/helloworld.pb.go
Normal file
151
examples/grpc/pb/helloworld.pb.go
Normal file
@ -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,
|
||||||
|
}
|
37
examples/grpc/pb/helloworld.proto
Normal file
37
examples/grpc/pb/helloworld.proto
Normal file
@ -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;
|
||||||
|
}
|
1
examples/http-pusher/assets/app.js
Normal file
1
examples/http-pusher/assets/app.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
console.log("http2 pusher");
|
41
examples/http-pusher/main.go
Normal file
41
examples/http-pusher/main.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
var html = template.Must(template.New("https").Parse(`
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Https Test</title>
|
||||||
|
<script src="/assets/app.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 style="color:red;">Welcome, Ginner!</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`))
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
15
examples/http-pusher/testdata/ca.pem
vendored
Normal file
15
examples/http-pusher/testdata/ca.pem
vendored
Normal file
@ -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-----
|
16
examples/http-pusher/testdata/server.key
vendored
Normal file
16
examples/http-pusher/testdata/server.key
vendored
Normal file
@ -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-----
|
16
examples/http-pusher/testdata/server.pem
vendored
Normal file
16
examples/http-pusher/testdata/server.pem
vendored
Normal file
@ -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-----
|
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@ -27,7 +28,7 @@ func main() {
|
|||||||
r.SetHTMLTemplate(html)
|
r.SetHTMLTemplate(html)
|
||||||
|
|
||||||
r.GET("/welcome", func(c *gin.Context) {
|
r.GET("/welcome", func(c *gin.Context) {
|
||||||
c.HTML(200, "https", gin.H{
|
c.HTML(http.StatusOK, "https", gin.H{
|
||||||
"status": "success",
|
"status": "success",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -13,16 +13,19 @@ func main() {
|
|||||||
StartGin()
|
StartGin()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConfigRuntime sets the number of operating system threads.
|
||||||
func ConfigRuntime() {
|
func ConfigRuntime() {
|
||||||
nuCPU := runtime.NumCPU()
|
nuCPU := runtime.NumCPU()
|
||||||
runtime.GOMAXPROCS(nuCPU)
|
runtime.GOMAXPROCS(nuCPU)
|
||||||
fmt.Printf("Running with %d CPUs\n", nuCPU)
|
fmt.Printf("Running with %d CPUs\n", nuCPU)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StartWorkers start starsWorker by goroutine.
|
||||||
func StartWorkers() {
|
func StartWorkers() {
|
||||||
go statsWorker()
|
go statsWorker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StartGin starts gin web server with setting router.
|
||||||
func StartGin() {
|
func StartGin() {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -21,12 +22,12 @@ func rateLimit(c *gin.Context) {
|
|||||||
fmt.Println("ip blocked")
|
fmt.Println("ip blocked")
|
||||||
}
|
}
|
||||||
c.Abort()
|
c.Abort()
|
||||||
c.String(503, "you were automatically banned :)")
|
c.String(http.StatusServiceUnavailable, "you were automatically banned :)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func index(c *gin.Context) {
|
func index(c *gin.Context) {
|
||||||
c.Redirect(301, "/room/hn")
|
c.Redirect(http.StatusMovedPermanently, "/room/hn")
|
||||||
}
|
}
|
||||||
|
|
||||||
func roomGET(c *gin.Context) {
|
func roomGET(c *gin.Context) {
|
||||||
@ -38,7 +39,7 @@ func roomGET(c *gin.Context) {
|
|||||||
if len(nick) > 13 {
|
if len(nick) > 13 {
|
||||||
nick = nick[0:12] + "..."
|
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,
|
"roomid": roomid,
|
||||||
"nick": nick,
|
"nick": nick,
|
||||||
"timestamp": time.Now().Unix(),
|
"timestamp": time.Now().Unix(),
|
||||||
@ -55,7 +56,7 @@ func roomPOST(c *gin.Context) {
|
|||||||
validMessage := len(message) > 1 && len(message) < 200
|
validMessage := len(message) > 1 && len(message) < 200
|
||||||
validNick := len(nick) > 1 && len(nick) < 14
|
validNick := len(nick) > 1 && len(nick) < 14
|
||||||
if !validMessage || !validNick {
|
if !validMessage || !validNick {
|
||||||
c.JSON(400, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
"status": "failed",
|
"status": "failed",
|
||||||
"error": "the message or nickname is too long",
|
"error": "the message or nickname is too long",
|
||||||
})
|
})
|
||||||
@ -68,7 +69,7 @@ func roomPOST(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
messages.Add("inbound", 1)
|
messages.Add("inbound", 1)
|
||||||
room(roomid).Submit(post)
|
room(roomid).Submit(post)
|
||||||
c.JSON(200, post)
|
c.JSON(http.StatusOK, post)
|
||||||
}
|
}
|
||||||
|
|
||||||
func streamRoom(c *gin.Context) {
|
func streamRoom(c *gin.Context) {
|
||||||
|
@ -50,6 +50,7 @@ func connectedUsers() uint64 {
|
|||||||
return uint64(connected)
|
return uint64(connected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stats returns savedStats data.
|
||||||
func Stats() map[string]uint64 {
|
func Stats() map[string]uint64 {
|
||||||
mutexStats.RLock()
|
mutexStats.RLock()
|
||||||
defer mutexStats.RUnlock()
|
defer mutexStats.RUnlock()
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@ -34,7 +35,7 @@ func stream(c *gin.Context) {
|
|||||||
func roomGET(c *gin.Context) {
|
func roomGET(c *gin.Context) {
|
||||||
roomid := c.Param("roomid")
|
roomid := c.Param("roomid")
|
||||||
userid := fmt.Sprint(rand.Int31())
|
userid := fmt.Sprint(rand.Int31())
|
||||||
c.HTML(200, "chat_room", gin.H{
|
c.HTML(http.StatusOK, "chat_room", gin.H{
|
||||||
"roomid": roomid,
|
"roomid": roomid,
|
||||||
"userid": userid,
|
"userid": userid,
|
||||||
})
|
})
|
||||||
@ -46,7 +47,7 @@ func roomPOST(c *gin.Context) {
|
|||||||
message := c.PostForm("message")
|
message := c.PostForm("message")
|
||||||
room(roomid).Submit(userid + ": " + message)
|
room(roomid).Submit(userid + ": " + message)
|
||||||
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"message": message,
|
"message": message,
|
||||||
})
|
})
|
||||||
|
50
examples/struct-lvl-validations/README.md
Normal file
50
examples/struct-lvl-validations/README.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
## Struct level validations
|
||||||
|
|
||||||
|
Validations can also be registered at the `struct` level when field level validations
|
||||||
|
don't make much sense. This can also be used to solve cross-field validation elegantly.
|
||||||
|
Additionally, it can be combined with tag validations. Struct Level validations run after
|
||||||
|
the structs tag validations.
|
||||||
|
|
||||||
|
### Example requests
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Validation errors are generated for struct tags as well as at the struct level
|
||||||
|
$ curl -s -X POST http://localhost:8085/user \
|
||||||
|
-H 'content-type: application/json' \
|
||||||
|
-d '{}' | jq
|
||||||
|
{
|
||||||
|
"error": "Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required' tag\nKey: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag\nKey: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag",
|
||||||
|
"message": "User validation failed!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validation fails at the struct level because neither first name nor last name are present
|
||||||
|
$ curl -s -X POST http://localhost:8085/user \
|
||||||
|
-H 'content-type: application/json' \
|
||||||
|
-d '{"email": "george@vandaley.com"}' | jq
|
||||||
|
{
|
||||||
|
"error": "Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag\nKey: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag",
|
||||||
|
"message": "User validation failed!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# No validation errors when either first name or last name is present
|
||||||
|
$ curl -X POST http://localhost:8085/user \
|
||||||
|
-H 'content-type: application/json' \
|
||||||
|
-d '{"fname": "George", "email": "george@vandaley.com"}'
|
||||||
|
{"message":"User validation successful."}
|
||||||
|
|
||||||
|
$ curl -X POST http://localhost:8085/user \
|
||||||
|
-H 'content-type: application/json' \
|
||||||
|
-d '{"lname": "Contanza", "email": "george@vandaley.com"}'
|
||||||
|
{"message":"User validation successful."}
|
||||||
|
|
||||||
|
$ curl -X POST http://localhost:8085/user \
|
||||||
|
-H 'content-type: application/json' \
|
||||||
|
-d '{"fname": "George", "lname": "Costanza", "email": "george@vandaley.com"}'
|
||||||
|
{"message":"User validation successful."}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Useful links
|
||||||
|
|
||||||
|
- Validator docs - https://godoc.org/gopkg.in/go-playground/validator.v8#Validate.RegisterStructValidation
|
||||||
|
- Struct level example - https://github.com/go-playground/validator/blob/v8.18.2/examples/struct-level/struct_level.go
|
||||||
|
- Validator release notes - https://github.com/go-playground/validator/releases/tag/v8.7
|
64
examples/struct-lvl-validations/server.go
Normal file
64
examples/struct-lvl-validations/server.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
validator "gopkg.in/go-playground/validator.v8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// User contains user information.
|
||||||
|
type User struct {
|
||||||
|
FirstName string `json:"fname"`
|
||||||
|
LastName string `json:"lname"`
|
||||||
|
Email string `binding:"required,email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserStructLevelValidation contains custom struct level validations that don't always
|
||||||
|
// make sense at the field validation level. For example, this function validates that either
|
||||||
|
// FirstName or LastName exist; could have done that with a custom field validation but then
|
||||||
|
// would have had to add it to both fields duplicating the logic + overhead, this way it's
|
||||||
|
// only validated once.
|
||||||
|
//
|
||||||
|
// NOTE: you may ask why wouldn't not just do this outside of validator. Doing this way
|
||||||
|
// hooks right into validator and you can combine with validation tags and still have a
|
||||||
|
// common error output format.
|
||||||
|
func UserStructLevelValidation(v *validator.Validate, structLevel *validator.StructLevel) {
|
||||||
|
user := structLevel.CurrentStruct.Interface().(User)
|
||||||
|
|
||||||
|
if len(user.FirstName) == 0 && len(user.LastName) == 0 {
|
||||||
|
structLevel.ReportError(
|
||||||
|
reflect.ValueOf(user.FirstName), "FirstName", "fname", "fnameorlname",
|
||||||
|
)
|
||||||
|
structLevel.ReportError(
|
||||||
|
reflect.ValueOf(user.LastName), "LastName", "lname", "fnameorlname",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// plus can to more, even with different tag than "fnameorlname"
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
route := gin.Default()
|
||||||
|
|
||||||
|
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
|
||||||
|
v.RegisterStructValidation(UserStructLevelValidation, User{})
|
||||||
|
}
|
||||||
|
|
||||||
|
route.POST("/user", validateUser)
|
||||||
|
route.Run(":8085")
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateUser(c *gin.Context) {
|
||||||
|
var u User
|
||||||
|
if err := c.ShouldBindJSON(&u); err == nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "User validation successful."})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"message": "User validation failed!",
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,7 @@ func main() {
|
|||||||
router.SetFuncMap(template.FuncMap{
|
router.SetFuncMap(template.FuncMap{
|
||||||
"formatAsDate": formatAsDate,
|
"formatAsDate": formatAsDate,
|
||||||
})
|
})
|
||||||
router.LoadHTMLFiles("../../fixtures/basic/raw.tmpl")
|
router.LoadHTMLFiles("../../testdata/template/raw.tmpl")
|
||||||
|
|
||||||
router.GET("/raw", func(c *gin.Context) {
|
router.GET("/raw", func(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
|
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
|
||||||
|
65
gin.go
65
gin.go
@ -14,11 +14,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin/render"
|
"github.com/gin-gonic/gin/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const defaultMultipartMemory = 32 << 20 // 32 MB
|
||||||
// Version is Framework's version.
|
|
||||||
Version = "v1.2"
|
|
||||||
defaultMultipartMemory = 32 << 20 // 32 MB
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
default404Body = []byte("404 page not found")
|
default404Body = []byte("404 page not found")
|
||||||
@ -26,7 +22,10 @@ var (
|
|||||||
defaultAppEngine bool
|
defaultAppEngine bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// HandlerFunc defines the handler used by gin middleware as return value.
|
||||||
type HandlerFunc func(*Context)
|
type HandlerFunc func(*Context)
|
||||||
|
|
||||||
|
// HandlersChain defines a HandlerFunc array.
|
||||||
type HandlersChain []HandlerFunc
|
type HandlersChain []HandlerFunc
|
||||||
|
|
||||||
// Last returns the last handler in the chain. ie. the last handler is the main own.
|
// Last returns the last handler in the chain. ie. the last handler is the main own.
|
||||||
@ -37,12 +36,15 @@ func (c HandlersChain) Last() HandlerFunc {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RouteInfo represents a request route's specification which contains method and path and its handler.
|
||||||
type RouteInfo struct {
|
type RouteInfo struct {
|
||||||
Method string
|
Method string
|
||||||
Path string
|
Path string
|
||||||
Handler string
|
Handler string
|
||||||
|
HandlerFunc HandlerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RoutesInfo defines a RouteInfo array.
|
||||||
type RoutesInfo []RouteInfo
|
type RoutesInfo []RouteInfo
|
||||||
|
|
||||||
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
||||||
@ -155,6 +157,7 @@ func (engine *Engine) allocateContext() *Context {
|
|||||||
return &Context{engine: engine}
|
return &Context{engine: engine}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delims sets template left and right delims and returns a Engine instance.
|
||||||
func (engine *Engine) Delims(left, right string) *Engine {
|
func (engine *Engine) Delims(left, right string) *Engine {
|
||||||
engine.delims = render.Delims{Left: left, Right: right}
|
engine.delims = render.Delims{Left: left, Right: right}
|
||||||
return engine
|
return engine
|
||||||
@ -171,14 +174,14 @@ func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
|
|||||||
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
||||||
left := engine.delims.Left
|
left := engine.delims.Left
|
||||||
right := engine.delims.Right
|
right := engine.delims.Right
|
||||||
|
templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
|
||||||
|
|
||||||
if IsDebugging() {
|
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}
|
engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
|
|
||||||
engine.SetHTMLTemplate(templ)
|
engine.SetHTMLTemplate(templ)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,10 +267,12 @@ func (engine *Engine) Routes() (routes RoutesInfo) {
|
|||||||
func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
|
func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
|
||||||
path += root.path
|
path += root.path
|
||||||
if len(root.handlers) > 0 {
|
if len(root.handlers) > 0 {
|
||||||
|
handlerFunc := root.handlers.Last()
|
||||||
routes = append(routes, RouteInfo{
|
routes = append(routes, RouteInfo{
|
||||||
Method: method,
|
Method: method,
|
||||||
Path: path,
|
Path: path,
|
||||||
Handler: nameOfFunction(root.handlers.Last()),
|
Handler: nameOfFunction(handlerFunc),
|
||||||
|
HandlerFunc: handlerFunc,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for _, child := range root.children {
|
for _, child := range root.children {
|
||||||
@ -329,12 +334,11 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleContext re-enter a context that has been rewritten.
|
// HandleContext re-enter a context that has been rewritten.
|
||||||
// This can be done by setting c.Request.Path to your new target.
|
// This can be done by setting c.Request.URL.Path to your new target.
|
||||||
// Disclaimer: You can loop yourself to death with this, use wisely.
|
// Disclaimer: You can loop yourself to death with this, use wisely.
|
||||||
func (engine *Engine) HandleContext(c *Context) {
|
func (engine *Engine) HandleContext(c *Context) {
|
||||||
c.reset()
|
c.reset()
|
||||||
engine.handleHTTPRequest(c)
|
engine.handleHTTPRequest(c)
|
||||||
engine.pool.Put(c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) handleHTTPRequest(c *Context) {
|
func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||||
@ -349,7 +353,9 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
|||||||
// Find root of the tree for the given HTTP method
|
// Find root of the tree for the given HTTP method
|
||||||
t := engine.trees
|
t := engine.trees
|
||||||
for i, tl := 0, len(t); i < tl; i++ {
|
for i, tl := 0, len(t); i < tl; i++ {
|
||||||
if t[i].method == httpMethod {
|
if t[i].method != httpMethod {
|
||||||
|
continue
|
||||||
|
}
|
||||||
root := t[i].root
|
root := t[i].root
|
||||||
// Find route in tree
|
// Find route in tree
|
||||||
handlers, params, tsr := root.getValue(path, c.Params, unescape)
|
handlers, params, tsr := root.getValue(path, c.Params, unescape)
|
||||||
@ -371,21 +377,21 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if engine.HandleMethodNotAllowed {
|
if engine.HandleMethodNotAllowed {
|
||||||
for _, tree := range engine.trees {
|
for _, tree := range engine.trees {
|
||||||
if tree.method != httpMethod {
|
if tree.method == httpMethod {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
|
if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
|
||||||
c.handlers = engine.allNoMethod
|
c.handlers = engine.allNoMethod
|
||||||
serveError(c, 405, default405Body)
|
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
c.handlers = engine.allNoRoute
|
c.handlers = engine.allNoRoute
|
||||||
serveError(c, 404, default404Body)
|
serveError(c, http.StatusNotFound, default404Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
var mimePlain = []string{MIMEPlain}
|
var mimePlain = []string{MIMEPlain}
|
||||||
@ -393,28 +399,29 @@ var mimePlain = []string{MIMEPlain}
|
|||||||
func serveError(c *Context, code int, defaultMessage []byte) {
|
func serveError(c *Context, code int, defaultMessage []byte) {
|
||||||
c.writermem.status = code
|
c.writermem.status = code
|
||||||
c.Next()
|
c.Next()
|
||||||
if !c.writermem.Written() {
|
if c.writermem.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
if c.writermem.Status() == code {
|
if c.writermem.Status() == code {
|
||||||
c.writermem.Header()["Content-Type"] = mimePlain
|
c.writermem.Header()["Content-Type"] = mimePlain
|
||||||
c.Writer.Write(defaultMessage)
|
c.Writer.Write(defaultMessage)
|
||||||
} else {
|
return
|
||||||
|
}
|
||||||
c.writermem.WriteHeaderNow()
|
c.writermem.WriteHeaderNow()
|
||||||
}
|
return
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func redirectTrailingSlash(c *Context) {
|
func redirectTrailingSlash(c *Context) {
|
||||||
req := c.Request
|
req := c.Request
|
||||||
path := req.URL.Path
|
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" {
|
if req.Method != "GET" {
|
||||||
code = 307
|
code = http.StatusTemporaryRedirect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.URL.Path = path + "/"
|
||||||
if length := len(path); length > 1 && path[length-1] == '/' {
|
if length := len(path); length > 1 && path[length-1] == '/' {
|
||||||
req.URL.Path = 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())
|
debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
|
||||||
http.Redirect(c.Writer, req, req.URL.String(), code)
|
http.Redirect(c.Writer, req, req.URL.String(), code)
|
||||||
@ -425,14 +432,10 @@ func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
|
|||||||
req := c.Request
|
req := c.Request
|
||||||
path := req.URL.Path
|
path := req.URL.Path
|
||||||
|
|
||||||
fixedPath, found := root.findCaseInsensitivePath(
|
if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(path), trailingSlash); ok {
|
||||||
cleanPath(path),
|
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
||||||
trailingSlash,
|
|
||||||
)
|
|
||||||
if found {
|
|
||||||
code := 301 // Permanent redirect, request with GET method
|
|
||||||
if req.Method != "GET" {
|
if req.Method != "GET" {
|
||||||
code = 307
|
code = http.StatusTemporaryRedirect
|
||||||
}
|
}
|
||||||
req.URL.Path = string(fixedPath)
|
req.URL.Path = string(fixedPath)
|
||||||
debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
|
debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
|
||||||
|
11
ginS/gins.go
11
ginS/gins.go
@ -22,14 +22,17 @@ func engine() *gin.Engine {
|
|||||||
return internalEngine
|
return internalEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadHTMLGlob is a wrapper for Engine.LoadHTMLGlob.
|
||||||
func LoadHTMLGlob(pattern string) {
|
func LoadHTMLGlob(pattern string) {
|
||||||
engine().LoadHTMLGlob(pattern)
|
engine().LoadHTMLGlob(pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadHTMLFiles is a wrapper for Engine.LoadHTMLFiles.
|
||||||
func LoadHTMLFiles(files ...string) {
|
func LoadHTMLFiles(files ...string) {
|
||||||
engine().LoadHTMLFiles(files...)
|
engine().LoadHTMLFiles(files...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetHTMLTemplate is a wrapper for Engine.SetHTMLTemplate.
|
||||||
func SetHTMLTemplate(templ *template.Template) {
|
func SetHTMLTemplate(templ *template.Template) {
|
||||||
engine().SetHTMLTemplate(templ)
|
engine().SetHTMLTemplate(templ)
|
||||||
}
|
}
|
||||||
@ -39,7 +42,7 @@ func NoRoute(handlers ...gin.HandlerFunc) {
|
|||||||
engine().NoRoute(handlers...)
|
engine().NoRoute(handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoMethod sets the handlers called when... TODO
|
// NoMethod is a wrapper for Engine.NoMethod.
|
||||||
func NoMethod(handlers ...gin.HandlerFunc) {
|
func NoMethod(handlers ...gin.HandlerFunc) {
|
||||||
engine().NoMethod(handlers...)
|
engine().NoMethod(handlers...)
|
||||||
}
|
}
|
||||||
@ -50,6 +53,7 @@ func Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup {
|
|||||||
return engine().Group(relativePath, handlers...)
|
return engine().Group(relativePath, handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle is a wrapper for Engine.Handle.
|
||||||
func Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
func Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||||
return engine().Handle(httpMethod, relativePath, handlers...)
|
return engine().Handle(httpMethod, relativePath, handlers...)
|
||||||
}
|
}
|
||||||
@ -89,10 +93,12 @@ func HEAD(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
|||||||
return engine().HEAD(relativePath, handlers...)
|
return engine().HEAD(relativePath, handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Any is a wrapper for Engine.Any.
|
||||||
func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||||
return engine().Any(relativePath, handlers...)
|
return engine().Any(relativePath, handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StaticFile is a wrapper for Engine.StaticFile.
|
||||||
func StaticFile(relativePath, filepath string) gin.IRoutes {
|
func StaticFile(relativePath, filepath string) gin.IRoutes {
|
||||||
return engine().StaticFile(relativePath, filepath)
|
return engine().StaticFile(relativePath, filepath)
|
||||||
}
|
}
|
||||||
@ -107,6 +113,7 @@ func Static(relativePath, root string) gin.IRoutes {
|
|||||||
return engine().Static(relativePath, root)
|
return engine().Static(relativePath, root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StaticFS is a wrapper for Engine.StaticFS.
|
||||||
func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes {
|
func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes {
|
||||||
return engine().StaticFS(relativePath, fs)
|
return engine().StaticFS(relativePath, fs)
|
||||||
}
|
}
|
||||||
@ -128,7 +135,7 @@ func Run(addr ...string) (err error) {
|
|||||||
// RunTLS : The router is attached to a http.Server and starts listening and serving HTTPS requests.
|
// 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)
|
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
|
||||||
// Note: this method will block the calling goroutine undefinitelly unless an error happens.
|
// 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)
|
return engine().RunTLS(addr, certFile, keyFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,12 +6,14 @@ package gin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -19,7 +21,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func testRequest(t *testing.T, url string) {
|
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)
|
assert.NoError(t, err)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@ -44,6 +53,22 @@ func TestRunEmpty(t *testing.T) {
|
|||||||
testRequest(t, "http://localhost:8080/example")
|
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) {
|
func TestRunEmptyWithEnv(t *testing.T) {
|
||||||
os.Setenv("PORT", "3123")
|
os.Setenv("PORT", "3123")
|
||||||
router := New()
|
router := New()
|
||||||
@ -119,6 +144,29 @@ func TestWithHttptestWithAutoSelectedPort(t *testing.T) {
|
|||||||
testRequest(t, ts.URL+"/example")
|
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) {
|
// func TestWithHttptestWithSpecifiedPort(t *testing.T) {
|
||||||
// router := New()
|
// router := New()
|
||||||
// router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
// router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||||
|
240
gin_test.go
240
gin_test.go
@ -10,6 +10,7 @@ import (
|
|||||||
"html/template"
|
"html/template"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -22,15 +23,14 @@ func formatAsDate(t time.Time) string {
|
|||||||
return fmt.Sprintf("%d/%02d/%02d", year, month, day)
|
return fmt.Sprintf("%d/%02d/%02d", year, month, day)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupHTMLFiles(t *testing.T, mode string, tls bool) func() {
|
func setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine)) *httptest.Server {
|
||||||
go func() {
|
|
||||||
SetMode(mode)
|
SetMode(mode)
|
||||||
router := New()
|
router := New()
|
||||||
router.Delims("{[{", "}]}")
|
router.Delims("{[{", "}]}")
|
||||||
router.SetFuncMap(template.FuncMap{
|
router.SetFuncMap(template.FuncMap{
|
||||||
"formatAsDate": formatAsDate,
|
"formatAsDate": formatAsDate,
|
||||||
})
|
})
|
||||||
router.LoadHTMLFiles("./fixtures/basic/hello.tmpl", "./fixtures/basic/raw.tmpl")
|
loadMethod(router)
|
||||||
router.GET("/test", func(c *Context) {
|
router.GET("/test", func(c *Context) {
|
||||||
c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
|
c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
|
||||||
})
|
})
|
||||||
@ -39,88 +39,89 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool) func() {
|
|||||||
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
|
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var ts *httptest.Server
|
||||||
|
|
||||||
if tls {
|
if tls {
|
||||||
// these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1`
|
ts = httptest.NewTLSServer(router)
|
||||||
router.RunTLS(":9999", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem")
|
|
||||||
} else {
|
} else {
|
||||||
router.Run(":8888")
|
ts = httptest.NewServer(router)
|
||||||
}
|
|
||||||
}()
|
|
||||||
t.Log("waiting 1 second for server startup")
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
return func() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupHTMLGlob(t *testing.T, mode string, tls bool) func() {
|
return ts
|
||||||
go func() {
|
|
||||||
SetMode(mode)
|
|
||||||
router := New()
|
|
||||||
router.Delims("{[{", "}]}")
|
|
||||||
router.SetFuncMap(template.FuncMap{
|
|
||||||
"formatAsDate": formatAsDate,
|
|
||||||
})
|
|
||||||
router.LoadHTMLGlob("./fixtures/basic/*")
|
|
||||||
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", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem")
|
|
||||||
} else {
|
|
||||||
router.Run(":8888")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
t.Log("waiting 1 second for server startup")
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
return func() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadHTMLGlob(t *testing.T) {
|
func TestLoadHTMLGlobDebugMode(t *testing.T) {
|
||||||
td := setupHTMLGlob(t, DebugMode, false)
|
ts := setupHTMLFiles(
|
||||||
res, err := http.Get("http://127.0.0.1:8888/test")
|
t,
|
||||||
|
DebugMode,
|
||||||
|
false,
|
||||||
|
func(router *Engine) {
|
||||||
|
router.LoadHTMLGlob("./testdata/template/*")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, _ := ioutil.ReadAll(res.Body)
|
resp, _ := ioutil.ReadAll(res.Body)
|
||||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||||
|
|
||||||
td()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadHTMLGlob2(t *testing.T) {
|
func TestLoadHTMLGlobTestMode(t *testing.T) {
|
||||||
td := setupHTMLGlob(t, TestMode, false)
|
ts := setupHTMLFiles(
|
||||||
res, err := http.Get("http://127.0.0.1:8888/test")
|
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 {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, _ := ioutil.ReadAll(res.Body)
|
resp, _ := ioutil.ReadAll(res.Body)
|
||||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||||
|
|
||||||
td()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadHTMLGlob3(t *testing.T) {
|
func TestLoadHTMLGlobReleaseMode(t *testing.T) {
|
||||||
td := setupHTMLGlob(t, ReleaseMode, false)
|
ts := setupHTMLFiles(
|
||||||
res, err := http.Get("http://127.0.0.1:8888/test")
|
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 {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, _ := ioutil.ReadAll(res.Body)
|
resp, _ := ioutil.ReadAll(res.Body)
|
||||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||||
|
|
||||||
td()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadHTMLGlobUsingTLS(t *testing.T) {
|
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
|
// Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{
|
TLSClientConfig: &tls.Config{
|
||||||
@ -128,29 +129,33 @@ func TestLoadHTMLGlobUsingTLS(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
client := &http.Client{Transport: tr}
|
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 {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, _ := ioutil.ReadAll(res.Body)
|
resp, _ := ioutil.ReadAll(res.Body)
|
||||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||||
|
|
||||||
td()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
|
func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
|
||||||
time.Now()
|
ts := setupHTMLFiles(
|
||||||
td := setupHTMLGlob(t, DebugMode, false)
|
t,
|
||||||
res, err := http.Get("http://127.0.0.1:8888/raw")
|
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 {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, _ := ioutil.ReadAll(res.Body)
|
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -164,59 +169,77 @@ func TestCreateEngine(t *testing.T) {
|
|||||||
assert.Empty(t, router.Handlers)
|
assert.Empty(t, router.Handlers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// func TestLoadHTMLDebugMode(t *testing.T) {
|
func TestLoadHTMLFilesTestMode(t *testing.T) {
|
||||||
// router := New()
|
ts := setupHTMLFiles(
|
||||||
// SetMode(DebugMode)
|
t,
|
||||||
// router.LoadHTMLGlob("*.testtmpl")
|
TestMode,
|
||||||
// r := router.HTMLRender.(render.HTMLDebug)
|
false,
|
||||||
// assert.Empty(t, r.Files)
|
func(router *Engine) {
|
||||||
// assert.Equal(t, "*.testtmpl", r.Glob)
|
router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl")
|
||||||
//
|
},
|
||||||
// router.LoadHTMLFiles("index.html.testtmpl", "login.html.testtmpl")
|
)
|
||||||
// r = router.HTMLRender.(render.HTMLDebug)
|
defer ts.Close()
|
||||||
// assert.Empty(t, r.Glob)
|
|
||||||
// assert.Equal(t, []string{"index.html", "login.html"}, r.Files)
|
|
||||||
// SetMode(TestMode)
|
|
||||||
// }
|
|
||||||
|
|
||||||
func TestLoadHTMLFiles(t *testing.T) {
|
res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
|
||||||
td := setupHTMLFiles(t, TestMode, false)
|
|
||||||
res, err := http.Get("http://127.0.0.1:8888/test")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, _ := ioutil.ReadAll(res.Body)
|
resp, _ := ioutil.ReadAll(res.Body)
|
||||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||||
td()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadHTMLFiles2(t *testing.T) {
|
func TestLoadHTMLFilesDebugMode(t *testing.T) {
|
||||||
td := setupHTMLFiles(t, DebugMode, false)
|
ts := setupHTMLFiles(
|
||||||
res, err := http.Get("http://127.0.0.1:8888/test")
|
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 {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, _ := ioutil.ReadAll(res.Body)
|
resp, _ := ioutil.ReadAll(res.Body)
|
||||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||||
td()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadHTMLFiles3(t *testing.T) {
|
func TestLoadHTMLFilesReleaseMode(t *testing.T) {
|
||||||
td := setupHTMLFiles(t, ReleaseMode, false)
|
ts := setupHTMLFiles(
|
||||||
res, err := http.Get("http://127.0.0.1:8888/test")
|
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 {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, _ := ioutil.ReadAll(res.Body)
|
resp, _ := ioutil.ReadAll(res.Body)
|
||||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||||
td()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadHTMLFilesUsingTLS(t *testing.T) {
|
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
|
// Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{
|
TLSClientConfig: &tls.Config{
|
||||||
@ -224,28 +247,33 @@ func TestLoadHTMLFilesUsingTLS(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
client := &http.Client{Transport: tr}
|
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 {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, _ := ioutil.ReadAll(res.Body)
|
resp, _ := ioutil.ReadAll(res.Body)
|
||||||
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
assert.Equal(t, "<h1>Hello world</h1>", string(resp))
|
||||||
td()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadHTMLFilesFuncMap(t *testing.T) {
|
func TestLoadHTMLFilesFuncMap(t *testing.T) {
|
||||||
time.Now()
|
ts := setupHTMLFiles(
|
||||||
td := setupHTMLFiles(t, TestMode, false)
|
t,
|
||||||
res, err := http.Get("http://127.0.0.1:8888/raw")
|
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 {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, _ := ioutil.ReadAll(res.Body)
|
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddRoute(t *testing.T) {
|
func TestAddRoute(t *testing.T) {
|
||||||
|
@ -293,7 +293,7 @@ func githubConfigRouter(router *Engine) {
|
|||||||
for _, param := range c.Params {
|
for _, param := range c.Params {
|
||||||
output[param.Key] = param.Value
|
output[param.Key] = param.Value
|
||||||
}
|
}
|
||||||
c.JSON(200, output)
|
c.JSON(http.StatusOK, output)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
30
go.mod
Normal file
30
go.mod
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
module github.com/gin-gonic/gin
|
||||||
|
|
||||||
|
require (
|
||||||
|
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 v1.1.5
|
||||||
|
github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227
|
||||||
|
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.1.0
|
||||||
|
github.com/ugorji/go v1.1.1
|
||||||
|
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-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
|
||||||
|
gopkg.in/yaml.v2 v2.2.1
|
||||||
|
)
|
72
go.sum
Normal file
72
go.sum
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
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 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=
|
||||||
|
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||||
|
github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b h1:dm/NYytoj7p8Jc6zMvyRz3PCQrTTCXnVRvEzyBcM890=
|
||||||
|
github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b/go.mod h1:vwfeXwKgEIWq63oVfwaBjoByS4dZzYbHHROHjV4IjNY=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
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 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.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=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
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.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-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/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-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=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/grpc v1.15.0 h1:Az/KuahOM4NAidTEuJCv/RonAA7rYsTPkqXVjr+8OOw=
|
||||||
|
google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||||
|
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||||
|
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
|
||||||
|
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
20
internal/json/json.go
Normal file
20
internal/json/json.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2017 Bo-Yi Wu. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !jsoniter
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Marshal is exported by gin/json package.
|
||||||
|
Marshal = json.Marshal
|
||||||
|
// MarshalIndent is exported by gin/json package.
|
||||||
|
MarshalIndent = json.MarshalIndent
|
||||||
|
// NewDecoder is exported by gin/json package.
|
||||||
|
NewDecoder = json.NewDecoder
|
||||||
|
// NewEncoder is exported by gin/json package.
|
||||||
|
NewEncoder = json.NewEncoder
|
||||||
|
)
|
21
internal/json/jsoniter.go
Normal file
21
internal/json/jsoniter.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2017 Bo-Yi Wu. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build jsoniter
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import "github.com/json-iterator/go"
|
||||||
|
|
||||||
|
var (
|
||||||
|
json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
|
// Marshal is exported by gin/json package.
|
||||||
|
Marshal = json.Marshal
|
||||||
|
// MarshalIndent is exported by gin/json package.
|
||||||
|
MarshalIndent = json.MarshalIndent
|
||||||
|
// NewDecoder is exported by gin/json package.
|
||||||
|
NewDecoder = json.NewDecoder
|
||||||
|
// NewEncoder is exported by gin/json package.
|
||||||
|
NewEncoder = json.NewEncoder
|
||||||
|
)
|
15
json/json.go
15
json/json.go
@ -1,15 +0,0 @@
|
|||||||
// Copyright 2017 Bo-Yi Wu. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !jsoniter
|
|
||||||
|
|
||||||
package json
|
|
||||||
|
|
||||||
import "encoding/json"
|
|
||||||
|
|
||||||
var (
|
|
||||||
Marshal = json.Marshal
|
|
||||||
MarshalIndent = json.MarshalIndent
|
|
||||||
NewDecoder = json.NewDecoder
|
|
||||||
)
|
|
@ -1,16 +0,0 @@
|
|||||||
// Copyright 2017 Bo-Yi Wu. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build jsoniter
|
|
||||||
|
|
||||||
package json
|
|
||||||
|
|
||||||
import "github.com/json-iterator/go"
|
|
||||||
|
|
||||||
var (
|
|
||||||
json = jsoniter.ConfigCompatibleWithStandardLibrary
|
|
||||||
Marshal = json.Marshal
|
|
||||||
MarshalIndent = json.MarshalIndent
|
|
||||||
NewDecoder = json.NewDecoder
|
|
||||||
)
|
|
@ -7,6 +7,7 @@ package gin
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -16,7 +17,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
|
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
|
||||||
white = string([]byte{27, 91, 57, 48, 59, 52, 55, 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})
|
red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
|
||||||
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109})
|
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109})
|
||||||
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109})
|
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109})
|
||||||
@ -118,11 +119,11 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
|
|||||||
|
|
||||||
func colorForStatus(code int) string {
|
func colorForStatus(code int) string {
|
||||||
switch {
|
switch {
|
||||||
case code >= 200 && code < 300:
|
case code >= http.StatusOK && code < http.StatusMultipleChoices:
|
||||||
return green
|
return green
|
||||||
case code >= 300 && code < 400:
|
case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
|
||||||
return white
|
return white
|
||||||
case code >= 400 && code < 500:
|
case code >= http.StatusBadRequest && code < http.StatusInternalServerError:
|
||||||
return yellow
|
return yellow
|
||||||
default:
|
default:
|
||||||
return red
|
return red
|
||||||
|
@ -7,6 +7,7 @@ package gin
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -84,7 +85,7 @@ func TestLogger(t *testing.T) {
|
|||||||
func TestColorForMethod(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, 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, 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, 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, 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")
|
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 53, 109}), colorForMethod("HEAD"), "head should be magenta")
|
||||||
@ -93,9 +94,9 @@ func TestColorForMethod(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestColorForStatus(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, 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(301), "3xx should be white")
|
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(404), "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")
|
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"))
|
c.Error(errors.New("this is an error"))
|
||||||
})
|
})
|
||||||
router.GET("/abort", func(c *Context) {
|
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) {
|
router.GET("/print", func(c *Context) {
|
||||||
c.Error(errors.New("this is an error"))
|
c.Error(errors.New("this is an error"))
|
||||||
c.String(500, "hola!")
|
c.String(http.StatusInternalServerError, "hola!")
|
||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(router, "GET", "/error")
|
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())
|
assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String())
|
||||||
|
|
||||||
w = performRequest(router, "GET", "/abort")
|
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())
|
assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String())
|
||||||
|
|
||||||
w = performRequest(router, "GET", "/print")
|
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())
|
assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ package gin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -37,7 +38,7 @@ func TestMiddlewareGeneralCase(t *testing.T) {
|
|||||||
w := performRequest(router, "GET", "/")
|
w := performRequest(router, "GET", "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Equal(t, "ACDB", signature)
|
assert.Equal(t, "ACDB", signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +74,7 @@ func TestMiddlewareNoRoute(t *testing.T) {
|
|||||||
w := performRequest(router, "GET", "/")
|
w := performRequest(router, "GET", "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, 404, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
assert.Equal(t, "ACEGHFDB", signature)
|
assert.Equal(t, "ACEGHFDB", signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +111,7 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) {
|
|||||||
w := performRequest(router, "GET", "/")
|
w := performRequest(router, "GET", "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, 405, w.Code)
|
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||||
assert.Equal(t, "ACEGHFDB", signature)
|
assert.Equal(t, "ACEGHFDB", signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +148,7 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) {
|
|||||||
w := performRequest(router, "GET", "/")
|
w := performRequest(router, "GET", "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, 404, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
assert.Equal(t, "AC X DB", signature)
|
assert.Equal(t, "AC X DB", signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +160,7 @@ func TestMiddlewareAbort(t *testing.T) {
|
|||||||
})
|
})
|
||||||
router.Use(func(c *Context) {
|
router.Use(func(c *Context) {
|
||||||
signature += "C"
|
signature += "C"
|
||||||
c.AbortWithStatus(401)
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
c.Next()
|
c.Next()
|
||||||
signature += "D"
|
signature += "D"
|
||||||
})
|
})
|
||||||
@ -173,7 +174,7 @@ func TestMiddlewareAbort(t *testing.T) {
|
|||||||
w := performRequest(router, "GET", "/")
|
w := performRequest(router, "GET", "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, 401, w.Code)
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||||
assert.Equal(t, "ACD", signature)
|
assert.Equal(t, "ACD", signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +184,7 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) {
|
|||||||
router.Use(func(c *Context) {
|
router.Use(func(c *Context) {
|
||||||
signature += "A"
|
signature += "A"
|
||||||
c.Next()
|
c.Next()
|
||||||
c.AbortWithStatus(410)
|
c.AbortWithStatus(http.StatusGone)
|
||||||
signature += "B"
|
signature += "B"
|
||||||
|
|
||||||
})
|
})
|
||||||
@ -195,7 +196,7 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) {
|
|||||||
w := performRequest(router, "GET", "/")
|
w := performRequest(router, "GET", "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, 410, w.Code)
|
assert.Equal(t, http.StatusGone, w.Code)
|
||||||
assert.Equal(t, "ACB", signature)
|
assert.Equal(t, "ACB", signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +208,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) {
|
|||||||
router := New()
|
router := New()
|
||||||
router.Use(func(context *Context) {
|
router.Use(func(context *Context) {
|
||||||
signature += "A"
|
signature += "A"
|
||||||
context.AbortWithError(500, errors.New("foo"))
|
context.AbortWithError(http.StatusInternalServerError, errors.New("foo"))
|
||||||
})
|
})
|
||||||
router.Use(func(context *Context) {
|
router.Use(func(context *Context) {
|
||||||
signature += "B"
|
signature += "B"
|
||||||
@ -218,25 +219,25 @@ func TestMiddlewareFailHandlersChain(t *testing.T) {
|
|||||||
w := performRequest(router, "GET", "/")
|
w := performRequest(router, "GET", "/")
|
||||||
|
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, 500, w.Code)
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
assert.Equal(t, "A", signature)
|
assert.Equal(t, "A", signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMiddlewareWrite(t *testing.T) {
|
func TestMiddlewareWrite(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
router.Use(func(c *Context) {
|
router.Use(func(c *Context) {
|
||||||
c.String(400, "hola\n")
|
c.String(http.StatusBadRequest, "hola\n")
|
||||||
})
|
})
|
||||||
router.Use(func(c *Context) {
|
router.Use(func(c *Context) {
|
||||||
c.XML(400, H{"foo": "bar"})
|
c.XML(http.StatusBadRequest, H{"foo": "bar"})
|
||||||
})
|
})
|
||||||
router.Use(func(c *Context) {
|
router.Use(func(c *Context) {
|
||||||
c.JSON(400, H{"foo": "bar"})
|
c.JSON(http.StatusBadRequest, H{"foo": "bar"})
|
||||||
})
|
})
|
||||||
router.GET("/", func(c *Context) {
|
router.GET("/", func(c *Context) {
|
||||||
c.JSON(400, H{"foo": "bar"})
|
c.JSON(http.StatusBadRequest, H{"foo": "bar"})
|
||||||
}, func(c *Context) {
|
}, func(c *Context) {
|
||||||
c.Render(400, sse.Event{
|
c.Render(http.StatusBadRequest, sse.Event{
|
||||||
Event: "test",
|
Event: "test",
|
||||||
Data: "message",
|
Data: "message",
|
||||||
})
|
})
|
||||||
@ -244,6 +245,6 @@ func TestMiddlewareWrite(t *testing.T) {
|
|||||||
|
|
||||||
w := performRequest(router, "GET", "/")
|
w := performRequest(router, "GET", "/")
|
||||||
|
|
||||||
assert.Equal(t, 400, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Equal(t, strings.Replace("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1))
|
assert.Equal(t, strings.Replace("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1))
|
||||||
}
|
}
|
||||||
|
12
mode.go
12
mode.go
@ -11,11 +11,15 @@ import (
|
|||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ENV_GIN_MODE indicates environment name for gin mode.
|
||||||
const ENV_GIN_MODE = "GIN_MODE"
|
const ENV_GIN_MODE = "GIN_MODE"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// DebugMode indicates gin mode is debug.
|
||||||
DebugMode = "debug"
|
DebugMode = "debug"
|
||||||
|
// ReleaseMode indicates gin mode is relase.
|
||||||
ReleaseMode = "release"
|
ReleaseMode = "release"
|
||||||
|
// TestMode indicates gin mode is test.
|
||||||
TestMode = "test"
|
TestMode = "test"
|
||||||
)
|
)
|
||||||
const (
|
const (
|
||||||
@ -42,6 +46,7 @@ func init() {
|
|||||||
SetMode(mode)
|
SetMode(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetMode sets gin mode according to input string.
|
||||||
func SetMode(value string) {
|
func SetMode(value string) {
|
||||||
switch value {
|
switch value {
|
||||||
case DebugMode, "":
|
case DebugMode, "":
|
||||||
@ -53,17 +58,24 @@ func SetMode(value string) {
|
|||||||
default:
|
default:
|
||||||
panic("gin mode unknown: " + value)
|
panic("gin mode unknown: " + value)
|
||||||
}
|
}
|
||||||
|
if value == "" {
|
||||||
|
value = DebugMode
|
||||||
|
}
|
||||||
modeName = value
|
modeName = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisableBindValidation closes the default validator.
|
||||||
func DisableBindValidation() {
|
func DisableBindValidation() {
|
||||||
binding.Validator = nil
|
binding.Validator = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumberto to
|
||||||
|
// call the UseNumber method on the JSON Decoder instance.
|
||||||
func EnableJsonDecoderUseNumber() {
|
func EnableJsonDecoderUseNumber() {
|
||||||
binding.EnableDecoderUseNumber = true
|
binding.EnableDecoderUseNumber = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mode returns currently gin mode.
|
||||||
func Mode() string {
|
func Mode() string {
|
||||||
return modeName
|
return modeName
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,10 @@ func TestSetMode(t *testing.T) {
|
|||||||
assert.Equal(t, TestMode, Mode())
|
assert.Equal(t, TestMode, Mode())
|
||||||
os.Unsetenv(ENV_GIN_MODE)
|
os.Unsetenv(ENV_GIN_MODE)
|
||||||
|
|
||||||
|
SetMode("")
|
||||||
|
assert.Equal(t, debugCode, ginMode)
|
||||||
|
assert.Equal(t, DebugMode, Mode())
|
||||||
|
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
assert.Equal(t, debugCode, ginMode)
|
assert.Equal(t, debugCode, ginMode)
|
||||||
assert.Equal(t, DebugMode, Mode())
|
assert.Equal(t, DebugMode, Mode())
|
||||||
|
6
path.go
6
path.go
@ -41,7 +41,7 @@ func cleanPath(p string) string {
|
|||||||
buf[0] = '/'
|
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
|
// 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
|
// gets completely inlined (bufApp). So in contrast to the path package this
|
||||||
@ -59,11 +59,11 @@ func cleanPath(p string) string {
|
|||||||
|
|
||||||
case p[r] == '.' && p[r+1] == '/':
|
case p[r] == '.' && p[r+1] == '/':
|
||||||
// . element
|
// . element
|
||||||
r++
|
r += 2
|
||||||
|
|
||||||
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
|
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
|
||||||
// .. element: remove to last /
|
// .. element: remove to last /
|
||||||
r += 2
|
r += 3
|
||||||
|
|
||||||
if w > 1 {
|
if w > 1 {
|
||||||
// can backtrack
|
// can backtrack
|
||||||
|
@ -24,6 +24,7 @@ var cleanTests = []struct {
|
|||||||
|
|
||||||
// missing root
|
// missing root
|
||||||
{"", "/"},
|
{"", "/"},
|
||||||
|
{"a/", "/a/"},
|
||||||
{"abc", "/abc"},
|
{"abc", "/abc"},
|
||||||
{"abc/def", "/abc/def"},
|
{"abc/def", "/abc/def"},
|
||||||
{"a/b/c", "/a/b/c"},
|
{"a/b/c", "/a/b/c"},
|
||||||
|
15
recovery.go
15
recovery.go
@ -10,8 +10,10 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -37,10 +39,14 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
|
|||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
if logger != nil {
|
if logger != nil {
|
||||||
stack := stack(3)
|
stack := stack(3)
|
||||||
|
if IsDebugging() {
|
||||||
httprequest, _ := httputil.DumpRequest(c.Request, false)
|
httprequest, _ := httputil.DumpRequest(c.Request, false)
|
||||||
logger.Printf("[Recovery] panic recovered:\n%s\n%s\n%s%s", string(httprequest), err, stack, reset)
|
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(500)
|
}
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
c.Next()
|
c.Next()
|
||||||
@ -107,3 +113,8 @@ func function(pc uintptr) []byte {
|
|||||||
name = bytes.Replace(name, centerDot, dot, -1)
|
name = bytes.Replace(name, centerDot, dot, -1)
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func timeFormat(t time.Time) string {
|
||||||
|
var timeString = t.Format("2006/01/02 - 15:04:05")
|
||||||
|
return timeString
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ package gin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -22,10 +23,20 @@ func TestPanicInHandler(t *testing.T) {
|
|||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/recovery")
|
w := performRequest(router, "GET", "/recovery")
|
||||||
// TEST
|
// 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(), "panic recovered")
|
||||||
assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem")
|
assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem")
|
||||||
assert.Contains(t, buffer.String(), "TestPanicInHandler")
|
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.
|
// TestPanicWithAbort assert that panic has been recovered even if context.Abort was used.
|
||||||
@ -33,11 +44,31 @@ func TestPanicWithAbort(t *testing.T) {
|
|||||||
router := New()
|
router := New()
|
||||||
router.Use(RecoveryWithWriter(nil))
|
router.Use(RecoveryWithWriter(nil))
|
||||||
router.GET("/recovery", func(c *Context) {
|
router.GET("/recovery", func(c *Context) {
|
||||||
c.AbortWithStatus(400)
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
panic("Oupps, Houston, we have a problem")
|
panic("Oupps, Houston, we have a problem")
|
||||||
})
|
})
|
||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/recovery")
|
w := performRequest(router, "GET", "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, 400, w.Code)
|
assert.Equal(t, http.StatusBadRequest, 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)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ package render
|
|||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
|
// Data contains ContentType and bytes data.
|
||||||
type Data struct {
|
type Data struct {
|
||||||
ContentType string
|
ContentType string
|
||||||
Data []byte
|
Data []byte
|
||||||
@ -18,6 +19,7 @@ func (r Data) Render(w http.ResponseWriter) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteContentType (Data) writes custom ContentType.
|
||||||
func (r Data) WriteContentType(w http.ResponseWriter) {
|
func (r Data) WriteContentType(w http.ResponseWriter) {
|
||||||
writeContentType(w, []string{r.ContentType})
|
writeContentType(w, []string{r.ContentType})
|
||||||
}
|
}
|
||||||
|
@ -9,20 +9,27 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Delims represents a set of Left and Right delimiters for HTML template rendering.
|
||||||
type Delims struct {
|
type Delims struct {
|
||||||
|
// Left delimiter, defaults to {{.
|
||||||
Left string
|
Left string
|
||||||
|
// Right delimiter, defaults to }}.
|
||||||
Right string
|
Right string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug.
|
||||||
type HTMLRender interface {
|
type HTMLRender interface {
|
||||||
|
// Instance returns an HTML instance.
|
||||||
Instance(string, interface{}) Render
|
Instance(string, interface{}) Render
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTMLProduction contains template reference and its delims.
|
||||||
type HTMLProduction struct {
|
type HTMLProduction struct {
|
||||||
Template *template.Template
|
Template *template.Template
|
||||||
Delims Delims
|
Delims Delims
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTMLDebug contains template delims and pattern and function with file list.
|
||||||
type HTMLDebug struct {
|
type HTMLDebug struct {
|
||||||
Files []string
|
Files []string
|
||||||
Glob string
|
Glob string
|
||||||
@ -30,6 +37,7 @@ type HTMLDebug struct {
|
|||||||
FuncMap template.FuncMap
|
FuncMap template.FuncMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTML contains template reference and its name with given interface object.
|
||||||
type HTML struct {
|
type HTML struct {
|
||||||
Template *template.Template
|
Template *template.Template
|
||||||
Name string
|
Name string
|
||||||
@ -38,6 +46,7 @@ type HTML struct {
|
|||||||
|
|
||||||
var htmlContentType = []string{"text/html; charset=utf-8"}
|
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 {
|
func (r HTMLProduction) Instance(name string, data interface{}) Render {
|
||||||
return HTML{
|
return HTML{
|
||||||
Template: r.Template,
|
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 {
|
func (r HTMLDebug) Instance(name string, data interface{}) Render {
|
||||||
return HTML{
|
return HTML{
|
||||||
Template: r.loadTemplate(),
|
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")
|
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 {
|
func (r HTML) Render(w http.ResponseWriter) error {
|
||||||
r.WriteContentType(w)
|
r.WriteContentType(w)
|
||||||
|
|
||||||
@ -75,6 +86,7 @@ func (r HTML) Render(w http.ResponseWriter) error {
|
|||||||
return r.Template.ExecuteTemplate(w, r.Name, r.Data)
|
return r.Template.ExecuteTemplate(w, r.Name, r.Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteContentType (HTML) writes HTML ContentType.
|
||||||
func (r HTML) WriteContentType(w http.ResponseWriter) {
|
func (r HTML) WriteContentType(w http.ResponseWriter) {
|
||||||
writeContentType(w, htmlContentType)
|
writeContentType(w, htmlContentType)
|
||||||
}
|
}
|
||||||
|
@ -6,28 +6,48 @@ package render
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/json"
|
"github.com/gin-gonic/gin/internal/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// JSON contains the given interface object.
|
||||||
type JSON struct {
|
type JSON struct {
|
||||||
Data interface{}
|
Data interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IndentedJSON contains the given interface object.
|
||||||
type IndentedJSON struct {
|
type IndentedJSON struct {
|
||||||
Data interface{}
|
Data interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SecureJSON contains the given interface object and its prefix.
|
||||||
type SecureJSON struct {
|
type SecureJSON struct {
|
||||||
Prefix string
|
Prefix string
|
||||||
Data interface{}
|
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
|
type SecureJSONPrefix string
|
||||||
|
|
||||||
var jsonContentType = []string{"application/json; charset=utf-8"}
|
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) {
|
func (r JSON) Render(w http.ResponseWriter) (err error) {
|
||||||
if err = WriteJSON(w, r.Data); err != nil {
|
if err = WriteJSON(w, r.Data); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -35,10 +55,12 @@ func (r JSON) Render(w http.ResponseWriter) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteContentType (JSON) writes JSON ContentType.
|
||||||
func (r JSON) WriteContentType(w http.ResponseWriter) {
|
func (r JSON) WriteContentType(w http.ResponseWriter) {
|
||||||
writeContentType(w, jsonContentType)
|
writeContentType(w, jsonContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteJSON marshals the given interface object and writes it with custom ContentType.
|
||||||
func WriteJSON(w http.ResponseWriter, obj interface{}) error {
|
func WriteJSON(w http.ResponseWriter, obj interface{}) error {
|
||||||
writeContentType(w, jsonContentType)
|
writeContentType(w, jsonContentType)
|
||||||
jsonBytes, err := json.Marshal(obj)
|
jsonBytes, err := json.Marshal(obj)
|
||||||
@ -49,6 +71,7 @@ func WriteJSON(w http.ResponseWriter, obj interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType.
|
||||||
func (r IndentedJSON) Render(w http.ResponseWriter) error {
|
func (r IndentedJSON) Render(w http.ResponseWriter) error {
|
||||||
r.WriteContentType(w)
|
r.WriteContentType(w)
|
||||||
jsonBytes, err := json.MarshalIndent(r.Data, "", " ")
|
jsonBytes, err := json.MarshalIndent(r.Data, "", " ")
|
||||||
@ -59,10 +82,12 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteContentType (IndentedJSON) writes JSON ContentType.
|
||||||
func (r IndentedJSON) WriteContentType(w http.ResponseWriter) {
|
func (r IndentedJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
writeContentType(w, jsonContentType)
|
writeContentType(w, jsonContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render (SecureJSON) marshals the given interface object and writes it with custom ContentType.
|
||||||
func (r SecureJSON) Render(w http.ResponseWriter) error {
|
func (r SecureJSON) Render(w http.ResponseWriter) error {
|
||||||
r.WriteContentType(w)
|
r.WriteContentType(w)
|
||||||
jsonBytes, err := json.Marshal(r.Data)
|
jsonBytes, err := json.Marshal(r.Data)
|
||||||
@ -77,6 +102,60 @@ func (r SecureJSON) Render(w http.ResponseWriter) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteContentType (SecureJSON) writes JSON ContentType.
|
||||||
func (r SecureJSON) WriteContentType(w http.ResponseWriter) {
|
func (r SecureJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
writeContentType(w, jsonContentType)
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Callback == "" {
|
||||||
|
w.Write(ret)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
callback := template.JSEscapeString(r.Callback)
|
||||||
|
w.Write([]byte(callback))
|
||||||
|
w.Write([]byte("("))
|
||||||
|
w.Write(ret)
|
||||||
|
w.Write([]byte(")"))
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
for _, r := range string(ret) {
|
||||||
|
cvt := string(r)
|
||||||
|
if r >= 128 {
|
||||||
|
cvt = fmt.Sprintf("\\u%04x", int64(r))
|
||||||
|
}
|
||||||
|
buffer.WriteString(cvt)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(buffer.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (AsciiJSON) writes JSON ContentType.
|
||||||
|
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, jsonAsciiContentType)
|
||||||
|
}
|
||||||
|
31
render/json_17.go
Normal file
31
render/json_17.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// 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/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)
|
||||||
|
encoder.SetEscapeHTML(false)
|
||||||
|
return encoder.Encode(r.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (PureJSON) writes custom ContentType.
|
||||||
|
func (r PureJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, jsonContentType)
|
||||||
|
}
|
@ -10,22 +10,26 @@ import (
|
|||||||
"github.com/ugorji/go/codec"
|
"github.com/ugorji/go/codec"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MsgPack contains the given interface object.
|
||||||
type MsgPack struct {
|
type MsgPack struct {
|
||||||
Data interface{}
|
Data interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var msgpackContentType = []string{"application/msgpack; charset=utf-8"}
|
var msgpackContentType = []string{"application/msgpack; charset=utf-8"}
|
||||||
|
|
||||||
|
// WriteContentType (MsgPack) writes MsgPack ContentType.
|
||||||
func (r MsgPack) WriteContentType(w http.ResponseWriter) {
|
func (r MsgPack) WriteContentType(w http.ResponseWriter) {
|
||||||
writeContentType(w, msgpackContentType)
|
writeContentType(w, msgpackContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render (MsgPack) encodes the given interface object and writes data with custom ContentType.
|
||||||
func (r MsgPack) Render(w http.ResponseWriter) error {
|
func (r MsgPack) Render(w http.ResponseWriter) error {
|
||||||
return WriteMsgPack(w, r.Data)
|
return WriteMsgPack(w, r.Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteMsgPack writes MsgPack ContentType and encodes the given interface object.
|
||||||
func WriteMsgPack(w http.ResponseWriter, obj interface{}) error {
|
func WriteMsgPack(w http.ResponseWriter, obj interface{}) error {
|
||||||
writeContentType(w, msgpackContentType)
|
writeContentType(w, msgpackContentType)
|
||||||
var h codec.Handle = new(codec.MsgpackHandle)
|
var mh codec.MsgpackHandle
|
||||||
return codec.NewEncoder(w, h).Encode(obj)
|
return codec.NewEncoder(w, &mh).Encode(obj)
|
||||||
}
|
}
|
||||||
|
36
render/protobuf.go
Normal file
36
render/protobuf.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
bytes, err := proto.Marshal(r.Data.(proto.Message))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(bytes)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (ProtoBuf) writes ProtoBuf ContentType.
|
||||||
|
func (r ProtoBuf) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, protobufContentType)
|
||||||
|
}
|
43
render/reader.go
Normal file
43
render/reader.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// 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 (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reader contains the IO reader and its length, and custom ContentType and other headers.
|
||||||
|
type Reader struct {
|
||||||
|
ContentType string
|
||||||
|
ContentLength int64
|
||||||
|
Reader io.Reader
|
||||||
|
Headers map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render (Reader) writes data with custom ContentType and headers.
|
||||||
|
func (r Reader) Render(w http.ResponseWriter) (err error) {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10)
|
||||||
|
r.writeHeaders(w, r.Headers)
|
||||||
|
_, err = io.Copy(w, r.Reader)
|
||||||
|
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 {
|
||||||
|
if val := header[k]; len(val) == 0 {
|
||||||
|
header[k] = []string{v}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,13 +9,17 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Redirect contains the http request reference and redirects status code and location.
|
||||||
type Redirect struct {
|
type Redirect struct {
|
||||||
Code int
|
Code int
|
||||||
Request *http.Request
|
Request *http.Request
|
||||||
Location string
|
Location string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render (Redirect) redirects the http request to new location and writes redirect response.
|
||||||
func (r Redirect) Render(w http.ResponseWriter) error {
|
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 {
|
if (r.Code < 300 || r.Code > 308) && r.Code != 201 {
|
||||||
panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
|
panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
|
||||||
}
|
}
|
||||||
@ -23,4 +27,5 @@ func (r Redirect) Render(w http.ResponseWriter) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteContentType (Redirect) don't write any ContentType.
|
||||||
func (r Redirect) WriteContentType(http.ResponseWriter) {}
|
func (r Redirect) WriteContentType(http.ResponseWriter) {}
|
||||||
|
@ -6,8 +6,11 @@ package render
|
|||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
|
// Render interface is to be implemented by JSON, XML, HTML, YAML and so on.
|
||||||
type Render interface {
|
type Render interface {
|
||||||
|
// Render writes data with custom ContentType.
|
||||||
Render(http.ResponseWriter) error
|
Render(http.ResponseWriter) error
|
||||||
|
// WriteContentType writes custom ContentType.
|
||||||
WriteContentType(w http.ResponseWriter)
|
WriteContentType(w http.ResponseWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15,6 +18,7 @@ var (
|
|||||||
_ Render = JSON{}
|
_ Render = JSON{}
|
||||||
_ Render = IndentedJSON{}
|
_ Render = IndentedJSON{}
|
||||||
_ Render = SecureJSON{}
|
_ Render = SecureJSON{}
|
||||||
|
_ Render = JsonpJSON{}
|
||||||
_ Render = XML{}
|
_ Render = XML{}
|
||||||
_ Render = String{}
|
_ Render = String{}
|
||||||
_ Render = Redirect{}
|
_ Render = Redirect{}
|
||||||
@ -24,6 +28,9 @@ var (
|
|||||||
_ HTMLRender = HTMLProduction{}
|
_ HTMLRender = HTMLProduction{}
|
||||||
_ Render = YAML{}
|
_ Render = YAML{}
|
||||||
_ Render = MsgPack{}
|
_ Render = MsgPack{}
|
||||||
|
_ Render = Reader{}
|
||||||
|
_ Render = AsciiJSON{}
|
||||||
|
_ Render = ProtoBuf{}
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeContentType(w http.ResponseWriter, value []string) {
|
func writeContentType(w http.ResponseWriter, value []string) {
|
||||||
|
26
render/render_17_test.go
Normal file
26
render/render_17_test.go
Normal file
@ -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": "<b>",
|
||||||
|
}
|
||||||
|
err := (PureJSON{data}).Render(w)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
|
||||||
|
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
@ -7,12 +7,19 @@ package render
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/ugorji/go/codec"
|
"github.com/ugorji/go/codec"
|
||||||
|
|
||||||
|
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO unit tests
|
// TODO unit tests
|
||||||
@ -24,6 +31,9 @@ func TestRenderMsgPack(t *testing.T) {
|
|||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(MsgPack{data}).WriteContentType(w)
|
||||||
|
assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
|
||||||
err := (MsgPack{data}).Render(w)
|
err := (MsgPack{data}).Render(w)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -36,22 +46,34 @@ func TestRenderMsgPack(t *testing.T) {
|
|||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, w.Body.String(), string(buf.Bytes()))
|
assert.Equal(t, w.Body.String(), string(buf.Bytes()))
|
||||||
assert.Equal(t, w.Header().Get("Content-Type"), "application/msgpack; charset=utf-8")
|
assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderJSON(t *testing.T) {
|
func TestRenderJSON(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
|
"html": "<b>",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(JSON{data}).WriteContentType(w)
|
||||||
|
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
|
||||||
err := (JSON{data}).Render(w)
|
err := (JSON{data}).Render(w)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
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"))
|
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderJSONPanics(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
data := make(chan int)
|
||||||
|
|
||||||
|
// json: unsupported type: chan int
|
||||||
|
assert.Panics(t, func() { (JSON{data}).Render(w) })
|
||||||
|
}
|
||||||
|
|
||||||
func TestRenderIndentedJSON(t *testing.T) {
|
func TestRenderIndentedJSON(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
@ -62,8 +84,17 @@ func TestRenderIndentedJSON(t *testing.T) {
|
|||||||
err := (IndentedJSON{data}).Render(w)
|
err := (IndentedJSON{data}).Render(w)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, w.Body.String(), "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}")
|
assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", w.Body.String())
|
||||||
assert.Equal(t, w.Header().Get("Content-Type"), "application/json; charset=utf-8")
|
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderIndentedJSONPanics(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
data := make(chan int)
|
||||||
|
|
||||||
|
// json: unsupported type: chan int
|
||||||
|
err := (IndentedJSON{data}).Render(w)
|
||||||
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderSecureJSON(t *testing.T) {
|
func TestRenderSecureJSON(t *testing.T) {
|
||||||
@ -72,6 +103,9 @@ func TestRenderSecureJSON(t *testing.T) {
|
|||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(SecureJSON{"while(1);", data}).WriteContentType(w1)
|
||||||
|
assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type"))
|
||||||
|
|
||||||
err1 := (SecureJSON{"while(1);", data}).Render(w1)
|
err1 := (SecureJSON{"while(1);", data}).Render(w1)
|
||||||
|
|
||||||
assert.NoError(t, err1)
|
assert.NoError(t, err1)
|
||||||
@ -91,6 +125,96 @@ func TestRenderSecureJSON(t *testing.T) {
|
|||||||
assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type"))
|
assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderSecureJSONFail(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
data := make(chan int)
|
||||||
|
|
||||||
|
// json: unsupported type: chan int
|
||||||
|
err := (SecureJSON{"while(1);", data}).Render(w)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderJsonpJSON(t *testing.T) {
|
||||||
|
w1 := httptest.NewRecorder()
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
|
||||||
|
(JsonpJSON{"x", data}).WriteContentType(w1)
|
||||||
|
assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type"))
|
||||||
|
|
||||||
|
err1 := (JsonpJSON{"x", data}).Render(w1)
|
||||||
|
|
||||||
|
assert.NoError(t, err1)
|
||||||
|
assert.Equal(t, "x({\"foo\":\"bar\"})", w1.Body.String())
|
||||||
|
assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type"))
|
||||||
|
|
||||||
|
w2 := httptest.NewRecorder()
|
||||||
|
datas := []map[string]interface{}{{
|
||||||
|
"foo": "bar",
|
||||||
|
}, {
|
||||||
|
"bar": "foo",
|
||||||
|
}}
|
||||||
|
|
||||||
|
err2 := (JsonpJSON{"x", datas}).Render(w2)
|
||||||
|
assert.NoError(t, err2)
|
||||||
|
assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}])", w2.Body.String())
|
||||||
|
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)
|
||||||
|
|
||||||
|
// json: unsupported type: chan int
|
||||||
|
err := (JsonpJSON{"x", data}).Render(w)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderAsciiJSON(t *testing.T) {
|
||||||
|
w1 := httptest.NewRecorder()
|
||||||
|
data1 := map[string]interface{}{
|
||||||
|
"lang": "GO语言",
|
||||||
|
"tag": "<br>",
|
||||||
|
}
|
||||||
|
|
||||||
|
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{}
|
type xmlmap map[string]interface{}
|
||||||
|
|
||||||
// Allows type H to be used with xml.Marshal
|
// Allows type H to be used with xml.Marshal
|
||||||
@ -115,21 +239,106 @@ func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|||||||
return e.EncodeToken(xml.EndElement{Name: start.Name})
|
return e.EncodeToken(xml.EndElement{Name: start.Name})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderYAML(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
data := `
|
||||||
|
a : Easy!
|
||||||
|
b:
|
||||||
|
c: 2
|
||||||
|
d: [3, 4]
|
||||||
|
`
|
||||||
|
(YAML{data}).WriteContentType(w)
|
||||||
|
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
|
||||||
|
err := (YAML{data}).Render(w)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n", w.Body.String())
|
||||||
|
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
type fail struct{}
|
||||||
|
|
||||||
|
// Hook MarshalYAML
|
||||||
|
func (ft *fail) MarshalYAML() (interface{}, error) {
|
||||||
|
return nil, errors.New("fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderYAMLFail(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
err := (YAML{&fail{}}).Render(w)
|
||||||
|
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) {
|
func TestRenderXML(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
data := xmlmap{
|
data := xmlmap{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(XML{data}).WriteContentType(w)
|
||||||
|
assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
|
||||||
err := (XML{data}).Render(w)
|
err := (XML{data}).Render(w)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, w.Body.String(), "<map><foo>bar</foo></map>")
|
assert.Equal(t, "<map><foo>bar</foo></map>", w.Body.String())
|
||||||
assert.Equal(t, w.Header().Get("Content-Type"), "application/xml; charset=utf-8")
|
assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderRedirect(t *testing.T) {
|
func TestRenderRedirect(t *testing.T) {
|
||||||
// TODO
|
req, err := http.NewRequest("GET", "/test-redirect", nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
data1 := Redirect{
|
||||||
|
Code: http.StatusMovedPermanently,
|
||||||
|
Request: req,
|
||||||
|
Location: "/new/location",
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
err = data1.Render(w)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
data2 := Redirect{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
Request: req,
|
||||||
|
Location: "/new/location",
|
||||||
|
}
|
||||||
|
|
||||||
|
w = httptest.NewRecorder()
|
||||||
|
assert.Panics(t, func() { data2.Render(w) })
|
||||||
|
|
||||||
|
// only improve coverage
|
||||||
|
data2.WriteContentType(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderData(t *testing.T) {
|
func TestRenderData(t *testing.T) {
|
||||||
@ -142,21 +351,40 @@ func TestRenderData(t *testing.T) {
|
|||||||
}).Render(w)
|
}).Render(w)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, w.Body.String(), "#!PNG some raw data")
|
assert.Equal(t, "#!PNG some raw data", w.Body.String())
|
||||||
assert.Equal(t, w.Header().Get("Content-Type"), "image/png")
|
assert.Equal(t, "image/png", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderString(t *testing.T) {
|
func TestRenderString(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
(String{
|
||||||
|
Format: "hello %s %d",
|
||||||
|
Data: []interface{}{},
|
||||||
|
}).WriteContentType(w)
|
||||||
|
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
|
||||||
err := (String{
|
err := (String{
|
||||||
Format: "hola %s %d",
|
Format: "hola %s %d",
|
||||||
Data: []interface{}{"manu", 2},
|
Data: []interface{}{"manu", 2},
|
||||||
}).Render(w)
|
}).Render(w)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, w.Body.String(), "hola manu 2")
|
assert.Equal(t, "hola manu 2", w.Body.String())
|
||||||
assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8")
|
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderStringLenZero(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
err := (String{
|
||||||
|
Format: "hola %s %d",
|
||||||
|
Data: []interface{}{},
|
||||||
|
}).Render(w)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "hola %s %d", w.Body.String())
|
||||||
|
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderHTMLTemplate(t *testing.T) {
|
func TestRenderHTMLTemplate(t *testing.T) {
|
||||||
@ -171,6 +399,88 @@ func TestRenderHTMLTemplate(t *testing.T) {
|
|||||||
err := instance.Render(w)
|
err := instance.Render(w)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, w.Body.String(), "Hello alexandernyquist")
|
assert.Equal(t, "Hello alexandernyquist", w.Body.String())
|
||||||
assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8")
|
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderHTMLTemplateEmptyName(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
templ := template.Must(template.New("").Parse(`Hello {{.name}}`))
|
||||||
|
|
||||||
|
htmlRender := HTMLProduction{Template: templ}
|
||||||
|
instance := htmlRender.Instance("", map[string]interface{}{
|
||||||
|
"name": "alexandernyquist",
|
||||||
|
})
|
||||||
|
|
||||||
|
err := instance.Render(w)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "Hello alexandernyquist", w.Body.String())
|
||||||
|
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderHTMLDebugFiles(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
htmlRender := HTMLDebug{Files: []string{"../testdata/template/hello.tmpl"},
|
||||||
|
Glob: "",
|
||||||
|
Delims: Delims{Left: "{[{", Right: "}]}"},
|
||||||
|
FuncMap: nil,
|
||||||
|
}
|
||||||
|
instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{
|
||||||
|
"name": "thinkerou",
|
||||||
|
})
|
||||||
|
|
||||||
|
err := instance.Render(w)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "<h1>Hello thinkerou</h1>", w.Body.String())
|
||||||
|
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderHTMLDebugGlob(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
htmlRender := HTMLDebug{Files: nil,
|
||||||
|
Glob: "../testdata/template/hello*",
|
||||||
|
Delims: Delims{Left: "{[{", Right: "}]}"},
|
||||||
|
FuncMap: nil,
|
||||||
|
}
|
||||||
|
instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{
|
||||||
|
"name": "thinkerou",
|
||||||
|
})
|
||||||
|
|
||||||
|
err := instance.Render(w)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "<h1>Hello thinkerou</h1>", w.Body.String())
|
||||||
|
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderHTMLDebugPanics(t *testing.T) {
|
||||||
|
htmlRender := HTMLDebug{Files: nil,
|
||||||
|
Glob: "",
|
||||||
|
Delims: Delims{"{{", "}}"},
|
||||||
|
FuncMap: nil,
|
||||||
|
}
|
||||||
|
assert.Panics(t, func() { htmlRender.Instance("", nil) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderReader(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
body := "#!PNG some raw data"
|
||||||
|
headers := make(map[string]string)
|
||||||
|
headers["Content-Disposition"] = `attachment; filename="filename.png"`
|
||||||
|
|
||||||
|
err := (Reader{
|
||||||
|
ContentLength: int64(len(body)),
|
||||||
|
ContentType: "image/png",
|
||||||
|
Reader: strings.NewReader(body),
|
||||||
|
Headers: headers,
|
||||||
|
}).Render(w)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, body, w.Body.String())
|
||||||
|
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"))
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// String contains the given interface object slice and its format.
|
||||||
type String struct {
|
type String struct {
|
||||||
Format string
|
Format string
|
||||||
Data []interface{}
|
Data []interface{}
|
||||||
@ -17,20 +18,23 @@ type String struct {
|
|||||||
|
|
||||||
var plainContentType = []string{"text/plain; charset=utf-8"}
|
var plainContentType = []string{"text/plain; charset=utf-8"}
|
||||||
|
|
||||||
|
// Render (String) writes data with custom ContentType.
|
||||||
func (r String) Render(w http.ResponseWriter) error {
|
func (r String) Render(w http.ResponseWriter) error {
|
||||||
WriteString(w, r.Format, r.Data)
|
WriteString(w, r.Format, r.Data)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteContentType (String) writes Plain ContentType.
|
||||||
func (r String) WriteContentType(w http.ResponseWriter) {
|
func (r String) WriteContentType(w http.ResponseWriter) {
|
||||||
writeContentType(w, plainContentType)
|
writeContentType(w, plainContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteString writes data according to its format and write custom ContentType.
|
||||||
func WriteString(w http.ResponseWriter, format string, data []interface{}) {
|
func WriteString(w http.ResponseWriter, format string, data []interface{}) {
|
||||||
writeContentType(w, plainContentType)
|
writeContentType(w, plainContentType)
|
||||||
if len(data) > 0 {
|
if len(data) > 0 {
|
||||||
fmt.Fprintf(w, format, data...)
|
fmt.Fprintf(w, format, data...)
|
||||||
} else {
|
return
|
||||||
|
}
|
||||||
io.WriteString(w, format)
|
io.WriteString(w, format)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -9,17 +9,20 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// XML contains the given interface object.
|
||||||
type XML struct {
|
type XML struct {
|
||||||
Data interface{}
|
Data interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var xmlContentType = []string{"application/xml; charset=utf-8"}
|
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 {
|
func (r XML) Render(w http.ResponseWriter) error {
|
||||||
r.WriteContentType(w)
|
r.WriteContentType(w)
|
||||||
return xml.NewEncoder(w).Encode(r.Data)
|
return xml.NewEncoder(w).Encode(r.Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteContentType (XML) writes XML ContentType for response.
|
||||||
func (r XML) WriteContentType(w http.ResponseWriter) {
|
func (r XML) WriteContentType(w http.ResponseWriter) {
|
||||||
writeContentType(w, xmlContentType)
|
writeContentType(w, xmlContentType)
|
||||||
}
|
}
|
||||||
|
@ -10,12 +10,14 @@ import (
|
|||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// YAML contains the given interface object.
|
||||||
type YAML struct {
|
type YAML struct {
|
||||||
Data interface{}
|
Data interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var yamlContentType = []string{"application/x-yaml; charset=utf-8"}
|
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 {
|
func (r YAML) Render(w http.ResponseWriter) error {
|
||||||
r.WriteContentType(w)
|
r.WriteContentType(w)
|
||||||
|
|
||||||
@ -28,6 +30,7 @@ func (r YAML) Render(w http.ResponseWriter) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteContentType (YAML) writes YAML ContentType for response.
|
||||||
func (r YAML) WriteContentType(w http.ResponseWriter) {
|
func (r YAML) WriteContentType(w http.ResponseWriter) {
|
||||||
writeContentType(w, yamlContentType)
|
writeContentType(w, yamlContentType)
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,10 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
noWritten = -1
|
noWritten = -1
|
||||||
defaultStatus = 200
|
defaultStatus = http.StatusOK
|
||||||
)
|
)
|
||||||
|
|
||||||
type ResponseWriter interface {
|
type responseWriterBase interface {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
http.Hijacker
|
http.Hijacker
|
||||||
http.Flusher
|
http.Flusher
|
||||||
@ -110,5 +110,6 @@ func (w *responseWriter) CloseNotify() <-chan bool {
|
|||||||
|
|
||||||
// Flush implements the http.Flush interface.
|
// Flush implements the http.Flush interface.
|
||||||
func (w *responseWriter) Flush() {
|
func (w *responseWriter) Flush() {
|
||||||
|
w.WriteHeaderNow()
|
||||||
w.ResponseWriter.(http.Flusher).Flush()
|
w.ResponseWriter.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
|
12
response_writer_1.7.go
Normal file
12
response_writer_1.7.go
Normal file
@ -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
|
||||||
|
}
|
25
response_writer_1.8.go
Normal file
25
response_writer_1.8.go
Normal file
@ -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
|
||||||
|
}
|
@ -35,10 +35,10 @@ func TestResponseWriterReset(t *testing.T) {
|
|||||||
|
|
||||||
writer.reset(testWritter)
|
writer.reset(testWritter)
|
||||||
assert.Equal(t, -1, writer.size)
|
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, testWritter, writer.ResponseWriter)
|
||||||
assert.Equal(t, -1, w.Size())
|
assert.Equal(t, -1, w.Size())
|
||||||
assert.Equal(t, 200, w.Status())
|
assert.Equal(t, http.StatusOK, w.Status())
|
||||||
assert.False(t, w.Written())
|
assert.False(t, w.Written())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,13 +48,13 @@ func TestResponseWriterWriteHeader(t *testing.T) {
|
|||||||
writer.reset(testWritter)
|
writer.reset(testWritter)
|
||||||
w := ResponseWriter(writer)
|
w := ResponseWriter(writer)
|
||||||
|
|
||||||
w.WriteHeader(300)
|
w.WriteHeader(http.StatusMultipleChoices)
|
||||||
assert.False(t, w.Written())
|
assert.False(t, w.Written())
|
||||||
assert.Equal(t, 300, w.Status())
|
assert.Equal(t, http.StatusMultipleChoices, w.Status())
|
||||||
assert.NotEqual(t, testWritter.Code, 300)
|
assert.NotEqual(t, http.StatusMultipleChoices, testWritter.Code)
|
||||||
|
|
||||||
w.WriteHeader(-1)
|
w.WriteHeader(-1)
|
||||||
assert.Equal(t, 300, w.Status())
|
assert.Equal(t, http.StatusMultipleChoices, w.Status())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResponseWriterWriteHeadersNow(t *testing.T) {
|
func TestResponseWriterWriteHeadersNow(t *testing.T) {
|
||||||
@ -63,12 +63,12 @@ func TestResponseWriterWriteHeadersNow(t *testing.T) {
|
|||||||
writer.reset(testWritter)
|
writer.reset(testWritter)
|
||||||
w := ResponseWriter(writer)
|
w := ResponseWriter(writer)
|
||||||
|
|
||||||
w.WriteHeader(300)
|
w.WriteHeader(http.StatusMultipleChoices)
|
||||||
w.WriteHeaderNow()
|
w.WriteHeaderNow()
|
||||||
|
|
||||||
assert.True(t, w.Written())
|
assert.True(t, w.Written())
|
||||||
assert.Equal(t, 0, w.Size())
|
assert.Equal(t, 0, w.Size())
|
||||||
assert.Equal(t, 300, testWritter.Code)
|
assert.Equal(t, http.StatusMultipleChoices, testWritter.Code)
|
||||||
|
|
||||||
writer.size = 10
|
writer.size = 10
|
||||||
w.WriteHeaderNow()
|
w.WriteHeaderNow()
|
||||||
@ -84,8 +84,8 @@ func TestResponseWriterWrite(t *testing.T) {
|
|||||||
n, err := w.Write([]byte("hola"))
|
n, err := w.Write([]byte("hola"))
|
||||||
assert.Equal(t, 4, n)
|
assert.Equal(t, 4, n)
|
||||||
assert.Equal(t, 4, w.Size())
|
assert.Equal(t, 4, w.Size())
|
||||||
assert.Equal(t, 200, w.Status())
|
assert.Equal(t, http.StatusOK, w.Status())
|
||||||
assert.Equal(t, 200, testWritter.Code)
|
assert.Equal(t, http.StatusOK, testWritter.Code)
|
||||||
assert.Equal(t, "hola", testWritter.Body.String())
|
assert.Equal(t, "hola", testWritter.Body.String())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
@ -113,3 +113,19 @@ func TestResponseWriterHijack(t *testing.T) {
|
|||||||
|
|
||||||
w.Flush()
|
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)
|
||||||
|
}
|
||||||
|
@ -11,11 +11,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IRouter defines all router handle interface includes single and group router.
|
||||||
type IRouter interface {
|
type IRouter interface {
|
||||||
IRoutes
|
IRoutes
|
||||||
Group(string, ...HandlerFunc) *RouterGroup
|
Group(string, ...HandlerFunc) *RouterGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IRoutes defines all router handle interface.
|
||||||
type IRoutes interface {
|
type IRoutes interface {
|
||||||
Use(...HandlerFunc) IRoutes
|
Use(...HandlerFunc) IRoutes
|
||||||
|
|
||||||
@ -34,8 +36,8 @@ type IRoutes interface {
|
|||||||
StaticFS(string, http.FileSystem) IRoutes
|
StaticFS(string, http.FileSystem) IRoutes
|
||||||
}
|
}
|
||||||
|
|
||||||
// RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix
|
// RouterGroup is used internally to configure router, a RouterGroup is associated with
|
||||||
// and an array of handlers (middleware).
|
// a prefix and an array of handlers (middleware).
|
||||||
type RouterGroup struct {
|
type RouterGroup struct {
|
||||||
Handlers HandlersChain
|
Handlers HandlersChain
|
||||||
basePath string
|
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 {
|
func (group *RouterGroup) BasePath() string {
|
||||||
return group.basePath
|
return group.basePath
|
||||||
}
|
}
|
||||||
@ -139,7 +143,7 @@ func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRou
|
|||||||
return group.returnObj()
|
return group.returnObj()
|
||||||
}
|
}
|
||||||
|
|
||||||
// StaticFile registers a single route in order to server a single file of the local filesystem.
|
// StaticFile registers a single route in order to serve a single file of the local filesystem.
|
||||||
// router.StaticFile("favicon.ico", "./resources/favicon.ico")
|
// router.StaticFile("favicon.ico", "./resources/favicon.ico")
|
||||||
func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {
|
func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {
|
||||||
if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
|
if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
|
||||||
@ -184,7 +188,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS
|
|||||||
_, nolisting := fs.(*onlyfilesFS)
|
_, nolisting := fs.(*onlyfilesFS)
|
||||||
return func(c *Context) {
|
return func(c *Context) {
|
||||||
if nolisting {
|
if nolisting {
|
||||||
c.Writer.WriteHeader(404)
|
c.Writer.WriteHeader(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
file := c.Param("filepath")
|
file := c.Param("filepath")
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -50,7 +51,7 @@ func performRequestInGroup(t *testing.T, method string) {
|
|||||||
assert.Equal(t, "/v1/login/", login.BasePath())
|
assert.Equal(t, "/v1/login/", login.BasePath())
|
||||||
|
|
||||||
handler := func(c *Context) {
|
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 {
|
switch method {
|
||||||
@ -80,11 +81,11 @@ func performRequestInGroup(t *testing.T, method string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
w := performRequest(router, method, "/v1/login/test")
|
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())
|
assert.Equal(t, "the method was "+method+" and index 3", w.Body.String())
|
||||||
|
|
||||||
w = performRequest(router, method, "/v1/test")
|
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())
|
assert.Equal(t, "the method was "+method+" and index 1", w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
110
routes_test.go
110
routes_test.go
@ -80,20 +80,20 @@ func testRouteNotOK2(method string, t *testing.T) {
|
|||||||
func TestRouterMethod(t *testing.T) {
|
func TestRouterMethod(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
router.PUT("/hey2", func(c *Context) {
|
router.PUT("/hey2", func(c *Context) {
|
||||||
c.String(200, "sup2")
|
c.String(http.StatusOK, "sup2")
|
||||||
})
|
})
|
||||||
|
|
||||||
router.PUT("/hey", func(c *Context) {
|
router.PUT("/hey", func(c *Context) {
|
||||||
c.String(200, "called")
|
c.String(http.StatusOK, "called")
|
||||||
})
|
})
|
||||||
|
|
||||||
router.PUT("/hey3", func(c *Context) {
|
router.PUT("/hey3", func(c *Context) {
|
||||||
c.String(200, "sup3")
|
c.String(http.StatusOK, "sup3")
|
||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(router, "PUT", "/hey")
|
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())
|
assert.Equal(t, "called", w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,42 +144,42 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
|
|||||||
|
|
||||||
w := performRequest(router, "GET", "/path/")
|
w := performRequest(router, "GET", "/path/")
|
||||||
assert.Equal(t, "/path", w.Header().Get("Location"))
|
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")
|
w = performRequest(router, "GET", "/path2")
|
||||||
assert.Equal(t, "/path2/", w.Header().Get("Location"))
|
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/")
|
w = performRequest(router, "POST", "/path3/")
|
||||||
assert.Equal(t, "/path3", w.Header().Get("Location"))
|
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")
|
w = performRequest(router, "PUT", "/path4")
|
||||||
assert.Equal(t, "/path4/", w.Header().Get("Location"))
|
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")
|
w = performRequest(router, "GET", "/path")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, "GET", "/path2/")
|
w = performRequest(router, "GET", "/path2/")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, "POST", "/path3")
|
w = performRequest(router, "POST", "/path3")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, "PUT", "/path4/")
|
w = performRequest(router, "PUT", "/path4/")
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
router.RedirectTrailingSlash = false
|
router.RedirectTrailingSlash = false
|
||||||
|
|
||||||
w = performRequest(router, "GET", "/path/")
|
w = performRequest(router, "GET", "/path/")
|
||||||
assert.Equal(t, 404, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
w = performRequest(router, "GET", "/path2")
|
w = performRequest(router, "GET", "/path2")
|
||||||
assert.Equal(t, 404, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
w = performRequest(router, "POST", "/path3/")
|
w = performRequest(router, "POST", "/path3/")
|
||||||
assert.Equal(t, 404, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
w = performRequest(router, "PUT", "/path4")
|
w = performRequest(router, "PUT", "/path4")
|
||||||
assert.Equal(t, 404, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouteRedirectFixedPath(t *testing.T) {
|
func TestRouteRedirectFixedPath(t *testing.T) {
|
||||||
@ -194,19 +194,19 @@ func TestRouteRedirectFixedPath(t *testing.T) {
|
|||||||
|
|
||||||
w := performRequest(router, "GET", "/PATH")
|
w := performRequest(router, "GET", "/PATH")
|
||||||
assert.Equal(t, "/path", w.Header().Get("Location"))
|
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")
|
w = performRequest(router, "GET", "/path2")
|
||||||
assert.Equal(t, "/Path2", w.Header().Get("Location"))
|
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")
|
w = performRequest(router, "POST", "/path3")
|
||||||
assert.Equal(t, "/PATH3", w.Header().Get("Location"))
|
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")
|
w = performRequest(router, "POST", "/path4")
|
||||||
assert.Equal(t, "/Path4/", w.Header().Get("Location"))
|
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.
|
// 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")
|
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, "john", name)
|
||||||
assert.Equal(t, "smith", lastName)
|
assert.Equal(t, "smith", lastName)
|
||||||
assert.Equal(t, "/is/super/great", wild)
|
assert.Equal(t, "/is/super/great", wild)
|
||||||
@ -265,15 +265,15 @@ func TestRouteStaticFile(t *testing.T) {
|
|||||||
w2 := performRequest(router, "GET", "/result")
|
w2 := performRequest(router, "GET", "/result")
|
||||||
|
|
||||||
assert.Equal(t, w, w2)
|
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, "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)
|
w3 := performRequest(router, "HEAD", "/using_static/"+filename)
|
||||||
w4 := performRequest(router, "HEAD", "/result")
|
w4 := performRequest(router, "HEAD", "/result")
|
||||||
|
|
||||||
assert.Equal(t, w3, w4)
|
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
|
// TestHandleStaticDir - ensure the root/sub dir handles properly
|
||||||
@ -283,9 +283,9 @@ func TestRouteStaticListingDir(t *testing.T) {
|
|||||||
|
|
||||||
w := performRequest(router, "GET", "/")
|
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.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
|
// TestHandleHeadToDir - ensure the root/sub dir handles properly
|
||||||
@ -295,7 +295,7 @@ func TestRouteStaticNoListing(t *testing.T) {
|
|||||||
|
|
||||||
w := performRequest(router, "GET", "/")
|
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")
|
assert.NotContains(t, w.Body.String(), "gin.go")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,12 +310,12 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
|
|||||||
|
|
||||||
w := performRequest(router, "GET", "/gin.go")
|
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.Contains(t, w.Body.String(), "package gin")
|
||||||
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"))
|
||||||
assert.NotEqual(t, w.HeaderMap.Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST")
|
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.HeaderMap.Get("Expires"))
|
assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires"))
|
||||||
assert.Equal(t, "Gin Framework", w.HeaderMap.Get("x-GIN"))
|
assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouteNotAllowedEnabled(t *testing.T) {
|
func TestRouteNotAllowedEnabled(t *testing.T) {
|
||||||
@ -333,19 +333,29 @@ func TestRouteNotAllowedEnabled(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusTeapot, w.Code)
|
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) {
|
func TestRouteNotAllowedDisabled(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
router.HandleMethodNotAllowed = false
|
router.HandleMethodNotAllowed = false
|
||||||
router.POST("/path", func(c *Context) {})
|
router.POST("/path", func(c *Context) {})
|
||||||
w := performRequest(router, "GET", "/path")
|
w := performRequest(router, "GET", "/path")
|
||||||
assert.Equal(t, 404, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
|
|
||||||
router.NoMethod(func(c *Context) {
|
router.NoMethod(func(c *Context) {
|
||||||
c.String(http.StatusTeapot, "responseText")
|
c.String(http.StatusTeapot, "responseText")
|
||||||
})
|
})
|
||||||
w = performRequest(router, "GET", "/path")
|
w = performRequest(router, "GET", "/path")
|
||||||
assert.Equal(t, "404 page not found", w.Body.String())
|
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) {
|
func TestRouterNotFound(t *testing.T) {
|
||||||
@ -360,20 +370,20 @@ func TestRouterNotFound(t *testing.T) {
|
|||||||
code int
|
code int
|
||||||
location string
|
location string
|
||||||
}{
|
}{
|
||||||
{"/path/", 301, "/path"}, // TSR -/
|
{"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/
|
||||||
{"/dir", 301, "/dir/"}, // TSR +/
|
{"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/
|
||||||
{"", 301, "/"}, // TSR +/
|
{"", http.StatusMovedPermanently, "/"}, // TSR +/
|
||||||
{"/PATH", 301, "/path"}, // Fixed Case
|
{"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case
|
||||||
{"/DIR/", 301, "/dir/"}, // Fixed Case
|
{"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case
|
||||||
{"/PATH/", 301, "/path"}, // Fixed Case -/
|
{"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/
|
||||||
{"/DIR", 301, "/dir/"}, // Fixed Case +/
|
{"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/
|
||||||
{"/../path", 301, "/path"}, // CleanPath
|
{"/../path", http.StatusMovedPermanently, "/path"}, // CleanPath
|
||||||
{"/nope", 404, ""}, // NotFound
|
{"/nope", http.StatusNotFound, ""}, // NotFound
|
||||||
}
|
}
|
||||||
for _, tr := range testRoutes {
|
for _, tr := range testRoutes {
|
||||||
w := performRequest(router, "GET", tr.route)
|
w := performRequest(router, "GET", tr.route)
|
||||||
assert.Equal(t, tr.code, w.Code)
|
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")))
|
assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -381,24 +391,24 @@ func TestRouterNotFound(t *testing.T) {
|
|||||||
// Test custom not found handler
|
// Test custom not found handler
|
||||||
var notFound bool
|
var notFound bool
|
||||||
router.NoRoute(func(c *Context) {
|
router.NoRoute(func(c *Context) {
|
||||||
c.AbortWithStatus(404)
|
c.AbortWithStatus(http.StatusNotFound)
|
||||||
notFound = true
|
notFound = true
|
||||||
})
|
})
|
||||||
w := performRequest(router, "GET", "/nope")
|
w := performRequest(router, "GET", "/nope")
|
||||||
assert.Equal(t, 404, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
assert.True(t, notFound)
|
assert.True(t, notFound)
|
||||||
|
|
||||||
// Test other method than GET (want 307 instead of 301)
|
// Test other method than GET (want 307 instead of 301)
|
||||||
router.PATCH("/path", func(c *Context) {})
|
router.PATCH("/path", func(c *Context) {})
|
||||||
w = performRequest(router, "PATCH", "/path/")
|
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()))
|
assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header()))
|
||||||
|
|
||||||
// Test special case where no node for the prefix "/" exists
|
// Test special case where no node for the prefix "/" exists
|
||||||
router = New()
|
router = New()
|
||||||
router.GET("/a", func(c *Context) {})
|
router.GET("/a", func(c *Context) {})
|
||||||
w = performRequest(router, "GET", "/")
|
w = performRequest(router, "GET", "/")
|
||||||
assert.Equal(t, 404, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouteRawPath(t *testing.T) {
|
func TestRouteRawPath(t *testing.T) {
|
||||||
@ -417,7 +427,7 @@ func TestRouteRawPath(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222")
|
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) {
|
func TestRouteRawPathNoUnescape(t *testing.T) {
|
||||||
@ -437,7 +447,7 @@ func TestRouteRawPathNoUnescape(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333")
|
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) {
|
func TestRouteServeErrorWithWriteHeader(t *testing.T) {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user