added some handler unit tests
This commit is contained in:
Родитель
00bc4a80fa
Коммит
2809c850e4
18
api/repos.go
18
api/repos.go
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/lgtmco/lgtm/shared/token"
|
||||
"github.com/lgtmco/lgtm/store"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
|
@ -20,7 +20,7 @@ func GetRepos(c *gin.Context) {
|
|||
user := session.User(c)
|
||||
repos, err := cache.GetRepos(c, user)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting remote repository list. %s", err)
|
||||
logrus.Errorf("Error getting remote repository list. %s", err)
|
||||
c.String(500, "Error getting remote repository list")
|
||||
return
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ func GetRepos(c *gin.Context) {
|
|||
|
||||
repom, err := store.GetRepoIntersectMap(c, repos)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting active repository list. %s", err)
|
||||
logrus.Errorf("Error getting active repository list. %s", err)
|
||||
c.String(500, "Error getting active repository list")
|
||||
return
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ func GetRepos(c *gin.Context) {
|
|||
repoc[i] = repo_
|
||||
}
|
||||
}
|
||||
c.IndentedJSON(200, repoc)
|
||||
c.JSON(200, repoc)
|
||||
}
|
||||
|
||||
// GetRepo gets the repository by slug.
|
||||
|
@ -56,7 +56,7 @@ func GetRepo(c *gin.Context) {
|
|||
)
|
||||
repo, err := store.GetRepoOwnerName(c, owner, name)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting repository %s. %s", name, err)
|
||||
logrus.Errorf("Error getting repository %s. %s", name, err)
|
||||
c.String(404, "Error getting repository %s", name)
|
||||
return
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ func PostRepo(c *gin.Context) {
|
|||
c.String(500, "Error activating the repository. %s", err)
|
||||
return
|
||||
}
|
||||
c.IndentedJSON(200, repo)
|
||||
c.JSON(200, repo)
|
||||
}
|
||||
|
||||
// DeleteRepo deletes a repository configuration.
|
||||
|
@ -123,13 +123,13 @@ func DeleteRepo(c *gin.Context) {
|
|||
)
|
||||
repo, err := store.GetRepoOwnerName(c, owner, name)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting repository %s. %s", name, err)
|
||||
logrus.Errorf("Error getting repository %s. %s", name, err)
|
||||
c.AbortWithStatus(404)
|
||||
return
|
||||
}
|
||||
err = store.DeleteRepo(c, repo)
|
||||
if err != nil {
|
||||
log.Errorf("Error deleting repository %s. %s", name, err)
|
||||
logrus.Errorf("Error deleting repository %s. %s", name, err)
|
||||
c.AbortWithStatus(500)
|
||||
return
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ func DeleteRepo(c *gin.Context) {
|
|||
)
|
||||
err = remote.DelHook(c, user, repo, link)
|
||||
if err != nil {
|
||||
log.Errorf("Error deleting repository hook for %s. %s", name, err)
|
||||
logrus.Errorf("Error deleting repository hook for %s. %s", name, err)
|
||||
}
|
||||
c.String(200, "")
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"github.com/lgtmco/lgtm/model"
|
||||
"github.com/lgtmco/lgtm/router/middleware/session"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
|
@ -14,7 +14,7 @@ func GetTeams(c *gin.Context) {
|
|||
user := session.User(c)
|
||||
teams, err := cache.GetTeams(c, user)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting team list. %s", err)
|
||||
logrus.Errorf("Error getting teams for user %s. %s", user.Login, err)
|
||||
c.String(500, "Error getting team list")
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/lgtmco/lgtm/model"
|
||||
|
||||
cache "github.com/lgtmco/lgtm/cache/mock"
|
||||
remote "github.com/lgtmco/lgtm/remote/mock"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/franela/goblin"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestTeams(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
logrus.SetOutput(ioutil.Discard)
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
|
||||
g.Describe("Team endpoint", func() {
|
||||
g.It("Should return the team list", func() {
|
||||
cache := new(cache.Cache)
|
||||
cache.On("Get", "teams:octocat").Return(fakeTeams, nil).Once()
|
||||
|
||||
e := gin.New()
|
||||
e.NoRoute(GetTeams)
|
||||
e.Use(func(c *gin.Context) {
|
||||
c.Set("user", fakeUser)
|
||||
c.Set("cache", cache)
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r, _ := http.NewRequest("GET", "/", nil)
|
||||
e.ServeHTTP(w, r)
|
||||
|
||||
// the user is appended to the team list so we retrieve a full list of
|
||||
// accounts to which the user has access.
|
||||
teams := append(fakeTeams, &model.Team{
|
||||
Login: fakeUser.Login,
|
||||
})
|
||||
|
||||
want, _ := json.Marshal(teams)
|
||||
got := strings.TrimSpace(w.Body.String())
|
||||
g.Assert(got).Equal(string(want))
|
||||
g.Assert(w.Code).Equal(200)
|
||||
})
|
||||
|
||||
g.It("Should return a 500 error", func() {
|
||||
remote := new(remote.Remote)
|
||||
cache := new(cache.Cache)
|
||||
cache.On("Get", "teams:octocat").Return(nil, fmt.Errorf("Not Found")).Once()
|
||||
remote.On("GetTeams", fakeUser).Return(nil, fmt.Errorf("Not Found")).Once()
|
||||
|
||||
e := gin.New()
|
||||
e.NoRoute(GetTeams)
|
||||
e.Use(func(c *gin.Context) {
|
||||
c.Set("user", fakeUser)
|
||||
c.Set("cache", cache)
|
||||
c.Set("remote", remote)
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r, _ := http.NewRequest("GET", "/", nil)
|
||||
e.ServeHTTP(w, r)
|
||||
|
||||
got := strings.TrimSpace(w.Body.String())
|
||||
g.Assert(got).Equal("Error getting team list")
|
||||
g.Assert(w.Code).Equal(500)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/lgtmco/lgtm/model"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestUsers(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
logrus.SetOutput(ioutil.Discard)
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
|
||||
g.Describe("User endpoint", func() {
|
||||
g.It("Should return the authenticated user", func() {
|
||||
|
||||
e := gin.New()
|
||||
e.NoRoute(GetUser)
|
||||
e.Use(func(c *gin.Context) {
|
||||
c.Set("user", fakeUser)
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r, _ := http.NewRequest("GET", "/", nil)
|
||||
e.ServeHTTP(w, r)
|
||||
|
||||
want, _ := json.Marshal(fakeUser)
|
||||
got := strings.TrimSpace(w.Body.String())
|
||||
g.Assert(got).Equal(string(want))
|
||||
g.Assert(w.Code).Equal(200)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
fakeUser = &model.User{Login: "octocat"}
|
||||
fakeTeams = []*model.Team{
|
||||
{Login: "drone"},
|
||||
{Login: "docker"},
|
||||
}
|
||||
)
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
###Gin 1.0rc2 (...)
|
||||
|
||||
- [PERFORMANCE] Fast path for writting Content-Type.
|
||||
- [PERFORMANCE] Fast path for writing Content-Type.
|
||||
- [PERFORMANCE] Much faster 404 routing
|
||||
- [PERFORMANCE] Allocation optimizations
|
||||
- [PERFORMANCE] Faster root tree lookup
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
|
||||
#Gin Web Framework
|
||||
<img align="right" src="https://s3.amazonaws.com/uploads.hipchat.com/36744/1498287/JVR32LgyEGCiy01/path4201%20copy%202.png">
|
||||
<img align="right" src="https://raw.githubusercontent.com/gin-gonic/gin/master/logo.jpg">
|
||||
[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin)
|
||||
[![Coverage Status](https://coveralls.io/repos/gin-gonic/gin/badge.svg?branch=master)](https://coveralls.io/r/gin-gonic/gin?branch=master)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin)
|
||||
[![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin)
|
||||
[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Gin is a web framework written in 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.
|
||||
|
||||
|
||||
|
||||
![Gin console logger](https://gin-gonic.github.io/gin/other/console.png)
|
||||
|
||||
```
|
||||
```sh
|
||||
$ cat test.go
|
||||
```
|
||||
```go
|
||||
|
@ -23,9 +24,11 @@ import "github.com/gin-gonic/gin"
|
|||
func main() {
|
||||
r := gin.Default()
|
||||
r.GET("/ping", func(c *gin.Context) {
|
||||
c.String(200, "pong")
|
||||
c.JSON(200, gin.H{
|
||||
"message": "pong",
|
||||
})
|
||||
})
|
||||
r.Run(":8080") // listen and serve on 0.0.0.0:8080
|
||||
r.Run() // listen and server on 0.0.0.0:8080
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -83,14 +86,21 @@ BenchmarkZeus_GithubAll | 2000 | 944234 | 300688 | 2648
|
|||
## Start using it
|
||||
1. Download and install it:
|
||||
|
||||
```sh
|
||||
go get github.com/gin-gonic/gin
|
||||
```
|
||||
```sh
|
||||
$ go get github.com/gin-gonic/gin
|
||||
```
|
||||
|
||||
2. Import it in your code:
|
||||
|
||||
```go
|
||||
import "github.com/gin-gonic/gin"
|
||||
```
|
||||
```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"
|
||||
```
|
||||
|
||||
##API Examples
|
||||
|
||||
|
@ -110,8 +120,10 @@ func main() {
|
|||
router.HEAD("/someHead", head)
|
||||
router.OPTIONS("/someOptions", options)
|
||||
|
||||
// Listen and server on 0.0.0.0:8080
|
||||
router.Run(":8080")
|
||||
// By default it serves on :8080 unless a
|
||||
// PORT environment variable was defined.
|
||||
router.Run()
|
||||
// router.Run(":3000") for a hard coded port
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -128,7 +140,7 @@ func main() {
|
|||
})
|
||||
|
||||
// However, this one will match /user/john/ and also /user/john/send
|
||||
// If no other routers match /user/john, it will redirect to /user/join/
|
||||
// If no other routers match /user/john, it will redirect to /user/john/
|
||||
router.GET("/user/:name/*action", func(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
action := c.Param("action")
|
||||
|
@ -143,17 +155,17 @@ func main() {
|
|||
#### Querystring parameters
|
||||
```go
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
router := gin.Default()
|
||||
|
||||
// Query string parameters are parsed using the existing underlying request object.
|
||||
// The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe
|
||||
router.GET("/welcome", func(c *gin.Context) {
|
||||
firstname := c.DefaultQuery("firstname", "Guest")
|
||||
lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
|
||||
// Query string parameters are parsed using the existing underlying request object.
|
||||
// The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe
|
||||
router.GET("/welcome", func(c *gin.Context) {
|
||||
firstname := c.DefaultQuery("firstname", "Guest")
|
||||
lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
|
||||
|
||||
c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
|
||||
})
|
||||
router.Run(":8080")
|
||||
c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
|
||||
})
|
||||
router.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -161,18 +173,19 @@ func main() {
|
|||
|
||||
```go
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
router := gin.Default()
|
||||
|
||||
router.POST("/form_post", func(c *gin.Context) {
|
||||
message := c.PostForm("message")
|
||||
nick := c.DefaultPostForm("nick", "anonymous")
|
||||
router.POST("/form_post", func(c *gin.Context) {
|
||||
message := c.PostForm("message")
|
||||
nick := c.DefaultPostForm("nick", "anonymous")
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"message": message,
|
||||
})
|
||||
})
|
||||
router.Run(":8080")
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"message": message,
|
||||
"nick": nick,
|
||||
})
|
||||
})
|
||||
router.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -190,21 +203,48 @@ func main() {
|
|||
router := gin.Default()
|
||||
|
||||
router.POST("/post", func(c *gin.Context) {
|
||||
id := c.Query("id")
|
||||
page := c.DefaultQuery("id", "0")
|
||||
name := c.PostForm("name")
|
||||
message := c.PostForm("message")
|
||||
|
||||
fmt.Println("id: %s; page: %s; name: %s; message: %s", id, page, name, message)
|
||||
id := c.Query("id")
|
||||
page := c.DefaultQuery("page", "0")
|
||||
name := c.PostForm("name")
|
||||
message := c.PostForm("message")
|
||||
|
||||
fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message)
|
||||
})
|
||||
router.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
id: 1234; page: 0; name: manu; message: this_is_great
|
||||
id: 1234; page: 1; name: manu; message: this_is_great
|
||||
```
|
||||
|
||||
### Another example: upload file
|
||||
|
||||
References issue [#548](https://github.com/gin-gonic/gin/issues/548).
|
||||
|
||||
```go
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
|
||||
router.POST("/upload", func(c *gin.Context) {
|
||||
|
||||
file, header , err := c.Request.FormFile("upload")
|
||||
filename := header.Filename
|
||||
fmt.Println(header.Filename)
|
||||
out, err := os.Create("./tmp/"+filename+".png")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer out.Close()
|
||||
_, err = io.Copy(out, file)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
})
|
||||
router.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
#### Grouping routes
|
||||
```go
|
||||
|
@ -261,7 +301,7 @@ func main() {
|
|||
|
||||
// Authorization group
|
||||
// authorized := r.Group("/", AuthRequired())
|
||||
// exactly the same than:
|
||||
// exactly the same as:
|
||||
authorized := r.Group("/")
|
||||
// per group middleware! in this case we use the custom created
|
||||
// AuthRequired() middleware just in the "authorized" group.
|
||||
|
@ -301,30 +341,30 @@ type Login struct {
|
|||
func main() {
|
||||
router := gin.Default()
|
||||
|
||||
// Example for binding JSON ({"user": "manu", "password": "123"})
|
||||
// Example for binding JSON ({"user": "manu", "password": "123"})
|
||||
router.POST("/loginJSON", func(c *gin.Context) {
|
||||
var json Login
|
||||
if c.BindJSON(&json) == 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"})
|
||||
}
|
||||
}
|
||||
if c.BindJSON(&json) == 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"})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Example for binding a HTML form (user=manu&password=123)
|
||||
router.POST("/loginForm", func(c *gin.Context) {
|
||||
var form Login
|
||||
// This will infer what binder to use depending on the content-type header.
|
||||
if c.Bind(&form) == 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"})
|
||||
}
|
||||
}
|
||||
})
|
||||
// Example for binding a HTML form (user=manu&password=123)
|
||||
router.POST("/loginForm", func(c *gin.Context) {
|
||||
var form Login
|
||||
// This will infer what binder to use depending on the content-type header.
|
||||
if c.Bind(&form) == 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"})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Listen and server on 0.0.0.0:8080
|
||||
router.Run(":8080")
|
||||
|
@ -353,21 +393,21 @@ func main() {
|
|||
// c.BindWith(&form, binding.Form)
|
||||
// or you can simply use autobinding with Bind method:
|
||||
var form LoginForm
|
||||
// in this case proper binding will be automatically selected
|
||||
// in this case proper binding will be automatically selected
|
||||
if c.Bind(&form) == nil {
|
||||
if form.User == "user" && form.Password == "password" {
|
||||
c.JSON(200, gin.H{"status": "you are logged in"})
|
||||
} else {
|
||||
c.JSON(401, gin.H{"status": "unauthorized"})
|
||||
}
|
||||
}
|
||||
if form.User == "user" && form.Password == "password" {
|
||||
c.JSON(200, gin.H{"status": "you are logged in"})
|
||||
} else {
|
||||
c.JSON(401, gin.H{"status": "unauthorized"})
|
||||
}
|
||||
}
|
||||
})
|
||||
router.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
Test it with:
|
||||
```bash
|
||||
```sh
|
||||
$ curl -v --form user=user --form password=password http://localhost:8080/login
|
||||
```
|
||||
|
||||
|
@ -411,13 +451,13 @@ func main() {
|
|||
|
||||
```go
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
router.Static("/assets", "./assets")
|
||||
router.StaticFS("/more_static", http.Dir("my_file_system"))
|
||||
router.StaticFile("/favicon.ico", "./resources/favicon.ico")
|
||||
router := gin.Default()
|
||||
router.Static("/assets", "./assets")
|
||||
router.StaticFS("/more_static", http.Dir("my_file_system"))
|
||||
router.StaticFile("/favicon.ico", "./resources/favicon.ico")
|
||||
|
||||
// Listen and server on 0.0.0.0:8080
|
||||
router.Run(":8080")
|
||||
// Listen and server on 0.0.0.0:8080
|
||||
router.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -438,11 +478,53 @@ func main() {
|
|||
router.Run(":8080")
|
||||
}
|
||||
```
|
||||
templates/index.tmpl
|
||||
```html
|
||||
<html>
|
||||
<h1>
|
||||
{{ .title }}
|
||||
</h1>
|
||||
</html>
|
||||
```
|
||||
|
||||
Using templates with same name in different directories
|
||||
|
||||
```go
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
router.LoadHTMLGlob("templates/**/*")
|
||||
router.GET("/posts/index", func(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
|
||||
"title": "Posts",
|
||||
})
|
||||
})
|
||||
router.GET("/users/index", func(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
|
||||
"title": "Users",
|
||||
})
|
||||
})
|
||||
router.Run(":8080")
|
||||
}
|
||||
```
|
||||
templates/posts/index.tmpl
|
||||
```html
|
||||
{{ define "posts/index.tmpl" }}
|
||||
<html><h1>
|
||||
{{ .title }}
|
||||
</h1>
|
||||
<p>Using posts/index.tmpl</p>
|
||||
</html>
|
||||
{{ end }}
|
||||
```
|
||||
templates/users/index.tmpl
|
||||
```html
|
||||
{{ define "users/index.tmpl" }}
|
||||
<html><h1>
|
||||
{{ .title }}
|
||||
</h1>
|
||||
<p>Using users/index.tmpl</p>
|
||||
</html>
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
You can also use your own html template render
|
||||
|
@ -535,7 +617,7 @@ func main() {
|
|||
// /admin/secrets endpoint
|
||||
// hit "localhost:8080/admin/secrets
|
||||
authorized.GET("/secrets", func(c *gin.Context) {
|
||||
// get user, it was setted by the BasicAuth middleware
|
||||
// get user, it was set by the BasicAuth middleware
|
||||
user := c.MustGet(gin.AuthUserKey).(string)
|
||||
if secret, ok := secrets[user]; ok {
|
||||
c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
|
||||
|
@ -559,17 +641,16 @@ func main() {
|
|||
|
||||
r.GET("/long_async", func(c *gin.Context) {
|
||||
// create copy to be used inside the goroutine
|
||||
c_cp := c.Copy()
|
||||
cCp := c.Copy()
|
||||
go func() {
|
||||
// simulate a long task with time.Sleep(). 5 seconds
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// note than you are using the copied context "c_cp", IMPORTANT
|
||||
log.Println("Done! in path " + c_cp.Request.URL.Path)
|
||||
// note that you are using the copied context "cCp", IMPORTANT
|
||||
log.Println("Done! in path " + cCp.Request.URL.Path)
|
||||
}()
|
||||
})
|
||||
|
||||
|
||||
r.GET("/long_sync", func(c *gin.Context) {
|
||||
// simulate a long task with time.Sleep(). 5 seconds
|
||||
time.Sleep(5 * time.Second)
|
||||
|
@ -578,8 +659,8 @@ func main() {
|
|||
log.Println("Done! in path " + c.Request.URL.Path)
|
||||
})
|
||||
|
||||
// Listen and server on 0.0.0.0:8080
|
||||
r.Run(":8080")
|
||||
// Listen and server on 0.0.0.0:8080
|
||||
r.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -609,3 +690,21 @@ func main() {
|
|||
s.ListenAndServe()
|
||||
}
|
||||
```
|
||||
|
||||
#### Graceful restart or stop
|
||||
|
||||
Do you want to graceful restart or stop your web server?
|
||||
There are some ways this can be done.
|
||||
|
||||
We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details.
|
||||
|
||||
```go
|
||||
router := gin.Default()
|
||||
router.GET("/", handler)
|
||||
// [...]
|
||||
endless.ListenAndServe(":4242", router)
|
||||
```
|
||||
|
||||
An alternative to endless:
|
||||
|
||||
* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully.
|
||||
|
|
|
@ -65,14 +65,10 @@ func BasicAuth(accounts Accounts) HandlerFunc {
|
|||
}
|
||||
|
||||
func processAccounts(accounts Accounts) authPairs {
|
||||
if len(accounts) == 0 {
|
||||
panic("Empty list of authorized credentials")
|
||||
}
|
||||
assert1(len(accounts) > 0, "Empty list of authorized credentials")
|
||||
pairs := make(authPairs, 0, len(accounts))
|
||||
for user, password := range accounts {
|
||||
if len(user) == 0 {
|
||||
panic("User can not be empty")
|
||||
}
|
||||
assert1(len(user) > 0, "User can not be empty")
|
||||
value := authorizationHeader(user, password)
|
||||
pairs = append(pairs, authPair{
|
||||
Value: value,
|
||||
|
|
|
@ -14,6 +14,7 @@ const (
|
|||
MIMEPlain = "text/plain"
|
||||
MIMEPOSTForm = "application/x-www-form-urlencoded"
|
||||
MIMEMultipartPOSTForm = "multipart/form-data"
|
||||
MIMEPROTOBUF = "application/x-protobuf"
|
||||
)
|
||||
|
||||
type Binding interface {
|
||||
|
@ -38,6 +39,7 @@ var (
|
|||
Form = formBinding{}
|
||||
FormPost = formPostBinding{}
|
||||
FormMultipart = formMultipartBinding{}
|
||||
ProtoBuf = protobufBinding{}
|
||||
)
|
||||
|
||||
func Default(method, contentType string) Binding {
|
||||
|
@ -49,6 +51,8 @@ func Default(method, contentType string) Binding {
|
|||
return JSON
|
||||
case MIMEXML, MIMEXML2:
|
||||
return XML
|
||||
case MIMEPROTOBUF:
|
||||
return ProtoBuf
|
||||
default: //case MIMEPOSTForm, MIMEMultipartPOSTForm:
|
||||
return Form
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"reflect"
|
||||
"sync"
|
||||
|
||||
"gopkg.in/bluesuncorp/validator.v5"
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
type defaultValidator struct {
|
||||
|
@ -26,7 +26,8 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
|||
|
||||
func (v *defaultValidator) lazyinit() {
|
||||
v.once.Do(func() {
|
||||
v.validate = validator.New("binding", validator.BakedInValidators)
|
||||
config := &validator.Config{TagName: "binding"}
|
||||
v.validate = validator.New(config)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package binding
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type protobufBinding struct{}
|
||||
|
||||
func (protobufBinding) Name() string {
|
||||
return "protobuf"
|
||||
}
|
||||
|
||||
func (protobufBinding) Bind(req *http.Request, obj interface{}) error {
|
||||
|
||||
buf, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
//which automatically generate by gen-proto
|
||||
return nil
|
||||
//return validate(obj)
|
||||
}
|
|
@ -8,7 +8,9 @@ import (
|
|||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -67,7 +69,7 @@ func (c *Context) reset() {
|
|||
// Copy returns a copy of the current context that can be safely used outside the request's scope.
|
||||
// This have to be used then the context has to be passed to a goroutine.
|
||||
func (c *Context) Copy() *Context {
|
||||
var cp Context = *c
|
||||
var cp = *c
|
||||
cp.writermem.ResponseWriter = nil
|
||||
cp.Writer = &cp.writermem
|
||||
cp.index = abortIndex
|
||||
|
@ -75,7 +77,7 @@ func (c *Context) Copy() *Context {
|
|||
return &cp
|
||||
}
|
||||
|
||||
// HandlerName returns the main handle's name. For example if the handler is "handleGetUsers()", this
|
||||
// HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()", this
|
||||
// function will return "main.handleGetUsers"
|
||||
func (c *Context) HandlerName() string {
|
||||
return nameOfFunction(c.handlers.Last())
|
||||
|
@ -96,15 +98,15 @@ func (c *Context) Next() {
|
|||
}
|
||||
}
|
||||
|
||||
// IsAborted returns true if the currect context was aborted.
|
||||
// IsAborted returns true if the current context was aborted.
|
||||
func (c *Context) IsAborted() bool {
|
||||
return c.index >= abortIndex
|
||||
}
|
||||
|
||||
// Abort stops the system to continue calling the pending handlers in the chain.
|
||||
// Let's say you have an authorization middleware that validates if the request is authorized
|
||||
// if the authorization fails (the password does not match). This method (Abort()) should be called
|
||||
// in order to stop the execution of the actual handler.
|
||||
// Abort prevents pending handlers from being called. Note that this will not stop the current handler.
|
||||
// Let's say you have an authorization middleware that validates that the current request is authorized. If the
|
||||
// authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers
|
||||
// for this request are not called.
|
||||
func (c *Context) Abort() {
|
||||
c.index = abortIndex
|
||||
}
|
||||
|
@ -112,7 +114,8 @@ func (c *Context) Abort() {
|
|||
// AbortWithStatus calls `Abort()` and writes the headers with the specified status code.
|
||||
// For example, a failed attempt to authentificate a request could use: context.AbortWithStatus(401).
|
||||
func (c *Context) AbortWithStatus(code int) {
|
||||
c.Writer.WriteHeader(code)
|
||||
c.Status(code)
|
||||
c.Writer.WriteHeaderNow()
|
||||
c.Abort()
|
||||
}
|
||||
|
||||
|
@ -169,7 +172,7 @@ func (c *Context) Get(key string) (value interface{}, exists bool) {
|
|||
return
|
||||
}
|
||||
|
||||
// Returns the value for the given key if it exists, otherwise it panics.
|
||||
// MustGet returns the value for the given key if it exists, otherwise it panics.
|
||||
func (c *Context) MustGet(key string) interface{} {
|
||||
if value, exists := c.Get(key); exists {
|
||||
return value
|
||||
|
@ -181,50 +184,52 @@ func (c *Context) MustGet(key string) interface{} {
|
|||
/************ INPUT DATA ************/
|
||||
/************************************/
|
||||
|
||||
// Query is a shortcut for c.Request.URL.Query().Get(key)
|
||||
// It is used to return the url query values.
|
||||
// ?id=1234&name=Manu
|
||||
// c.Query("id") == "1234"
|
||||
// c.Query("name") == "Manu"
|
||||
// c.Query("wtf") == ""
|
||||
func (c *Context) Query(key string) (va string) {
|
||||
va, _ = c.query(key)
|
||||
return
|
||||
}
|
||||
|
||||
// PostForm is a shortcut for c.Request.PostFormValue(key)
|
||||
func (c *Context) PostForm(key string) (va string) {
|
||||
va, _ = c.postForm(key)
|
||||
return
|
||||
}
|
||||
|
||||
// Param is a shortcut for c.Params.ByName(key)
|
||||
// Param returns the value of the URL param.
|
||||
// It is a shortcut for c.Params.ByName(key)
|
||||
// router.GET("/user/:id", func(c *gin.Context) {
|
||||
// // a GET request to /user/john
|
||||
// id := c.Param("id") // id == "john"
|
||||
// })
|
||||
func (c *Context) Param(key string) string {
|
||||
return c.Params.ByName(key)
|
||||
}
|
||||
|
||||
func (c *Context) DefaultPostForm(key, defaultValue string) string {
|
||||
if va, ok := c.postForm(key); ok {
|
||||
return va
|
||||
}
|
||||
return defaultValue
|
||||
// Query returns the keyed url query value if it exists,
|
||||
// othewise it returns an empty string `("")`.
|
||||
// It is shortcut for `c.Request.URL.Query().Get(key)`
|
||||
// GET /path?id=1234&name=Manu&value=
|
||||
// c.Query("id") == "1234"
|
||||
// c.Query("name") == "Manu"
|
||||
// c.Query("value") == ""
|
||||
// c.Query("wtf") == ""
|
||||
func (c *Context) Query(key string) string {
|
||||
value, _ := c.GetQuery(key)
|
||||
return value
|
||||
}
|
||||
|
||||
// DefaultQuery returns the keyed url query value if it exists, othewise it returns the
|
||||
// specified defaultValue.
|
||||
// ```
|
||||
// /?name=Manu
|
||||
// c.DefaultQuery("name", "unknown") == "Manu"
|
||||
// c.DefaultQuery("id", "none") == "none"
|
||||
// ```
|
||||
// DefaultQuery returns the keyed url query value if it exists,
|
||||
// othewise it returns the specified defaultValue string.
|
||||
// See: Query() and GetQuery() for further information.
|
||||
// GET /?name=Manu&lastname=
|
||||
// c.DefaultQuery("name", "unknown") == "Manu"
|
||||
// c.DefaultQuery("id", "none") == "none"
|
||||
// c.DefaultQuery("lastname", "none") == ""
|
||||
func (c *Context) DefaultQuery(key, defaultValue string) string {
|
||||
if va, ok := c.query(key); ok {
|
||||
return va
|
||||
if value, ok := c.GetQuery(key); ok {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func (c *Context) query(key string) (string, bool) {
|
||||
// GetQuery is like Query(), it returns the keyed url query value
|
||||
// if it exists `(value, true)` (even when the value is an empty string),
|
||||
// othewise it returns `("", false)`.
|
||||
// It is shortcut for `c.Request.URL.Query().Get(key)`
|
||||
// GET /?name=Manu&lastname=
|
||||
// ("Manu", true) == c.GetQuery("name")
|
||||
// ("", false) == c.GetQuery("id")
|
||||
// ("", true) == c.GetQuery("lastname")
|
||||
func (c *Context) GetQuery(key string) (string, bool) {
|
||||
req := c.Request
|
||||
if values, ok := req.URL.Query()[key]; ok && len(values) > 0 {
|
||||
return values[0], true
|
||||
|
@ -232,7 +237,31 @@ func (c *Context) query(key string) (string, bool) {
|
|||
return "", false
|
||||
}
|
||||
|
||||
func (c *Context) postForm(key string) (string, bool) {
|
||||
// PostForm returns the specified key from a POST urlencoded form or multipart form
|
||||
// when it exists, otherwise it returns an empty string `("")`.
|
||||
func (c *Context) PostForm(key string) string {
|
||||
value, _ := c.GetPostForm(key)
|
||||
return value
|
||||
}
|
||||
|
||||
// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form
|
||||
// when it exists, otherwise it returns the specified defaultValue string.
|
||||
// See: PostForm() and GetPostForm() for further information.
|
||||
func (c *Context) DefaultPostForm(key, defaultValue string) string {
|
||||
if value, ok := c.GetPostForm(key); ok {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// GetPostForm is like PostForm(key). It returns the specified key from a POST urlencoded
|
||||
// form or multipart form when it exists `(value, true)` (even when the value is an empty string),
|
||||
// otherwise it returns ("", false).
|
||||
// For example, during a PATCH request to update the user's email:
|
||||
// email=mail@example.com --> ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com"
|
||||
// email= --> ("", true) := GetPostForm("email") // set email to ""
|
||||
// --> ("", false) := GetPostForm("email") // do nothing with email
|
||||
func (c *Context) GetPostForm(key string) (string, bool) {
|
||||
req := c.Request
|
||||
req.ParseMultipartForm(32 << 20) // 32 MB
|
||||
if values := req.PostForm[key]; len(values) > 0 {
|
||||
|
@ -248,10 +277,10 @@ func (c *Context) postForm(key string) (string, bool) {
|
|||
|
||||
// Bind checks the Content-Type to select a binding engine automatically,
|
||||
// Depending the "Content-Type" header different bindings are used:
|
||||
// "application/json" --> JSON binding
|
||||
// "application/xml" --> XML binding
|
||||
// "application/json" --> JSON binding
|
||||
// "application/xml" --> XML binding
|
||||
// otherwise --> returns an error
|
||||
// If Parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
|
||||
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
|
||||
// It decodes the json payload into the struct specified as a pointer.
|
||||
// Like ParseBody() but this method also writes a 400 error if the json is not valid.
|
||||
func (c *Context) Bind(obj interface{}) error {
|
||||
|
@ -291,7 +320,10 @@ func (c *Context) ClientIP() string {
|
|||
return clientIP
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(c.Request.RemoteAddr)
|
||||
if ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)); err == nil {
|
||||
return ip
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ContentType returns the Content-Type header of the request.
|
||||
|
@ -310,6 +342,10 @@ func (c *Context) requestHeader(key string) string {
|
|||
/******** RESPONSE RENDERING ********/
|
||||
/************************************/
|
||||
|
||||
func (c *Context) Status(code int) {
|
||||
c.writermem.WriteHeader(code)
|
||||
}
|
||||
|
||||
// Header is a intelligent shortcut for c.Writer.Header().Set(key, value)
|
||||
// It writes a header in the response.
|
||||
// If value == "", this method removes the header `c.Writer.Header().Del(key)`
|
||||
|
@ -321,16 +357,43 @@ func (c *Context) Header(key, value string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Context) Render(code int, r render.Render) {
|
||||
c.writermem.WriteHeader(code)
|
||||
if err := r.Render(c.Writer); err != nil {
|
||||
c.renderError(err)
|
||||
func (c *Context) SetCookie(
|
||||
name string,
|
||||
value string,
|
||||
maxAge int,
|
||||
path string,
|
||||
domain string,
|
||||
secure bool,
|
||||
httpOnly bool,
|
||||
) {
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
http.SetCookie(c.Writer, &http.Cookie{
|
||||
Name: name,
|
||||
Value: url.QueryEscape(value),
|
||||
MaxAge: maxAge,
|
||||
Path: path,
|
||||
Domain: domain,
|
||||
Secure: secure,
|
||||
HttpOnly: httpOnly,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Context) renderError(err error) {
|
||||
debugPrintError(err)
|
||||
c.AbortWithError(500, err).SetType(ErrorTypeRender)
|
||||
func (c *Context) Cookie(name string) (string, error) {
|
||||
cookie, err := c.Request.Cookie(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
val, _ := url.QueryUnescape(cookie.Value)
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (c *Context) Render(code int, r render.Render) {
|
||||
c.Status(code)
|
||||
if err := r.Render(c.Writer); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// HTML renders the HTTP template specified by its file name.
|
||||
|
@ -352,9 +415,9 @@ func (c *Context) IndentedJSON(code int, obj interface{}) {
|
|||
// JSON serializes the given struct as JSON into the response body.
|
||||
// It also sets the Content-Type as "application/json".
|
||||
func (c *Context) JSON(code int, obj interface{}) {
|
||||
c.writermem.WriteHeader(code)
|
||||
c.Status(code)
|
||||
if err := render.WriteJSON(c.Writer, obj); err != nil {
|
||||
c.renderError(err)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -364,9 +427,14 @@ func (c *Context) XML(code int, obj interface{}) {
|
|||
c.Render(code, render.XML{Data: obj})
|
||||
}
|
||||
|
||||
// YAML serializes the given struct as YAML into the response body.
|
||||
func (c *Context) YAML(code int, obj interface{}) {
|
||||
c.Render(code, render.YAML{Data: obj})
|
||||
}
|
||||
|
||||
// String writes the given string into the response body.
|
||||
func (c *Context) String(code int, format string, values ...interface{}) {
|
||||
c.writermem.WriteHeader(code)
|
||||
c.Status(code)
|
||||
render.WriteString(c.Writer, format, values)
|
||||
}
|
||||
|
||||
|
@ -408,9 +476,9 @@ func (c *Context) Stream(step func(w io.Writer) bool) {
|
|||
case <-clientGone:
|
||||
return
|
||||
default:
|
||||
keepopen := step(w)
|
||||
keepOpen := step(w)
|
||||
w.Flush()
|
||||
if !keepopen {
|
||||
if !keepOpen {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -450,9 +518,8 @@ func (c *Context) Negotiate(code int, config Negotiate) {
|
|||
}
|
||||
|
||||
func (c *Context) NegotiateFormat(offered ...string) string {
|
||||
if len(offered) == 0 {
|
||||
panic("you must provide at least one offer")
|
||||
}
|
||||
assert1(len(offered) > 0, "you must provide at least one offer")
|
||||
|
||||
if c.Accepted == nil {
|
||||
c.Accepted = parseAccept(c.requestHeader("Accept"))
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) {
|
|||
if IsDebugging() {
|
||||
nuHandlers := len(handlers)
|
||||
handlerName := nameOfFunction(handlers.Last())
|
||||
debugPrint("%-5s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers)
|
||||
debugPrint("%-6s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,3 +3,10 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gin
|
||||
|
||||
import "log"
|
||||
|
||||
func (c *Context) GetCookie(name string) (string, error) {
|
||||
log.Println("GetCookie() method is deprecated. Use Cookie() instead.")
|
||||
return c.Cookie(name)
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ func (msg *Error) JSON() interface{} {
|
|||
return json
|
||||
}
|
||||
|
||||
// Implements the json.Marshaller interface
|
||||
// MarshalJSON implements the json.Marshaller interface
|
||||
func (msg *Error) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(msg.JSON())
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ func (a errorMsgs) ByType(typ ErrorType) errorMsgs {
|
|||
if typ == ErrorTypeAny {
|
||||
return a
|
||||
}
|
||||
var result errorMsgs = nil
|
||||
var result errorMsgs
|
||||
for _, msg := range a {
|
||||
if msg.IsType(typ) {
|
||||
result = append(result, msg)
|
||||
|
@ -109,13 +109,11 @@ func (a errorMsgs) Last() *Error {
|
|||
}
|
||||
|
||||
// Returns an array will all the error messages.
|
||||
// Example
|
||||
// ```
|
||||
// c.Error(errors.New("first"))
|
||||
// c.Error(errors.New("second"))
|
||||
// c.Error(errors.New("third"))
|
||||
// c.Errors.Errors() // == []string{"first", "second", "third"}
|
||||
// ``
|
||||
// Example:
|
||||
// c.Error(errors.New("first"))
|
||||
// c.Error(errors.New("second"))
|
||||
// c.Error(errors.New("third"))
|
||||
// c.Errors.Errors() // == []string{"first", "second", "third"}
|
||||
func (a errorMsgs) Errors() []string {
|
||||
if len(a) == 0 {
|
||||
return nil
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/gin-gonic/gin/render"
|
||||
)
|
||||
|
||||
// Framework's version
|
||||
// Version is Framework's version
|
||||
const Version = "v1.0rc2"
|
||||
|
||||
var default404Body = []byte("404 page not found")
|
||||
|
@ -113,7 +113,7 @@ func New() *Engine {
|
|||
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
|
||||
func Default() *Engine {
|
||||
engine := New()
|
||||
engine.Use(Recovery(), Logger())
|
||||
engine.Use(Logger(), Recovery())
|
||||
return engine
|
||||
}
|
||||
|
||||
|
@ -147,19 +147,19 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
|
|||
engine.HTMLRender = render.HTMLProduction{Template: templ}
|
||||
}
|
||||
|
||||
// Adds handlers for NoRoute. It return a 404 code by default.
|
||||
// NoRoute adds handlers for NoRoute. It return a 404 code by default.
|
||||
func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
|
||||
engine.noRoute = handlers
|
||||
engine.rebuild404Handlers()
|
||||
}
|
||||
|
||||
// Sets the handlers called when... TODO
|
||||
// NoMethod sets the handlers called when... TODO
|
||||
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
|
||||
engine.noMethod = handlers
|
||||
engine.rebuild405Handlers()
|
||||
}
|
||||
|
||||
// Attachs a global middleware to the router. ie. the middleware attached though Use() will be
|
||||
// Use attachs a global middleware to the router. ie. the middleware attached though Use() will be
|
||||
// included in the handlers chain for every single request. Even 404, 405, static files...
|
||||
// For example, this is the right place for a logger or error management middleware.
|
||||
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
|
||||
|
@ -178,25 +178,15 @@ func (engine *Engine) rebuild405Handlers() {
|
|||
}
|
||||
|
||||
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
||||
assert1(path[0] == '/', "path must begin with '/'")
|
||||
assert1(len(method) > 0, "HTTP method can not be empty")
|
||||
assert1(len(handlers) > 0, "there must be at least one handler")
|
||||
|
||||
debugPrintRoute(method, path, handlers)
|
||||
|
||||
if path[0] != '/' {
|
||||
panic("path must begin with '/'")
|
||||
}
|
||||
if method == "" {
|
||||
panic("HTTP method can not be empty")
|
||||
}
|
||||
if len(handlers) == 0 {
|
||||
panic("there must be at least one handler")
|
||||
}
|
||||
|
||||
root := engine.trees.get(method)
|
||||
if root == nil {
|
||||
root = new(node)
|
||||
engine.trees = append(engine.trees, methodTree{
|
||||
method: method,
|
||||
root: root,
|
||||
})
|
||||
engine.trees = append(engine.trees, methodTree{method: method, root: root})
|
||||
}
|
||||
root.addRoute(path, handlers)
|
||||
}
|
||||
|
@ -227,7 +217,7 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
|
|||
|
||||
// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
|
||||
// It is a shortcut for http.ListenAndServe(addr, router)
|
||||
// Note: this method will block the calling goroutine undefinitelly unless an error happens.
|
||||
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||
func (engine *Engine) Run(addr ...string) (err error) {
|
||||
defer func() { debugPrintError(err) }()
|
||||
|
||||
|
@ -239,7 +229,7 @@ func (engine *Engine) Run(addr ...string) (err error) {
|
|||
|
||||
// RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests.
|
||||
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
|
||||
// Note: this method will block the calling goroutine undefinitelly unless an error happens.
|
||||
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||
func (engine *Engine) RunTLS(addr string, certFile string, keyFile string) (err error) {
|
||||
debugPrint("Listening and serving HTTPS on %s\n", addr)
|
||||
defer func() { debugPrintError(err) }()
|
||||
|
@ -250,7 +240,7 @@ func (engine *Engine) RunTLS(addr string, certFile string, keyFile string) (err
|
|||
|
||||
// RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests
|
||||
// through the specified unix socket (ie. a file).
|
||||
// Note: this method will block the calling goroutine undefinitelly unless an error happens.
|
||||
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||
func (engine *Engine) RunUnix(file string) (err error) {
|
||||
debugPrint("Listening and serving HTTP on unix:/%s", file)
|
||||
defer func() { debugPrintError(err) }()
|
||||
|
|
|
@ -28,25 +28,32 @@ func ErrorLogger() HandlerFunc {
|
|||
func ErrorLoggerT(typ ErrorType) HandlerFunc {
|
||||
return func(c *Context) {
|
||||
c.Next()
|
||||
// avoid writting if we already wrote into the response body
|
||||
if !c.Writer.Written() {
|
||||
errors := c.Errors.ByType(typ)
|
||||
if len(errors) > 0 {
|
||||
c.JSON(-1, errors)
|
||||
}
|
||||
errors := c.Errors.ByType(typ)
|
||||
if len(errors) > 0 {
|
||||
c.JSON(-1, errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Instances a Logger middleware that will write the logs to gin.DefaultWriter
|
||||
// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter
|
||||
// By default gin.DefaultWriter = os.Stdout
|
||||
func Logger() HandlerFunc {
|
||||
return LoggerWithWriter(DefaultWriter)
|
||||
}
|
||||
|
||||
// Instance a Logger middleware with the specified writter buffer.
|
||||
// LoggerWithWriter instance a Logger middleware with the specified writter buffer.
|
||||
// Example: os.Stdout, a file opened in write mode, a socket...
|
||||
func LoggerWithWriter(out io.Writer) HandlerFunc {
|
||||
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
|
||||
var skip map[string]struct{}
|
||||
|
||||
if length := len(notlogged); length > 0 {
|
||||
skip = make(map[string]struct{}, length)
|
||||
|
||||
for _, path := range notlogged {
|
||||
skip[path] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return func(c *Context) {
|
||||
// Start timer
|
||||
start := time.Now()
|
||||
|
@ -55,26 +62,29 @@ func LoggerWithWriter(out io.Writer) HandlerFunc {
|
|||
// Process request
|
||||
c.Next()
|
||||
|
||||
// Stop timer
|
||||
end := time.Now()
|
||||
latency := end.Sub(start)
|
||||
// Log only when path is not being skipped
|
||||
if _, ok := skip[path]; !ok {
|
||||
// Stop timer
|
||||
end := time.Now()
|
||||
latency := end.Sub(start)
|
||||
|
||||
clientIP := c.ClientIP()
|
||||
method := c.Request.Method
|
||||
statusCode := c.Writer.Status()
|
||||
statusColor := colorForStatus(statusCode)
|
||||
methodColor := colorForMethod(method)
|
||||
comment := c.Errors.ByType(ErrorTypePrivate).String()
|
||||
clientIP := c.ClientIP()
|
||||
method := c.Request.Method
|
||||
statusCode := c.Writer.Status()
|
||||
statusColor := colorForStatus(statusCode)
|
||||
methodColor := colorForMethod(method)
|
||||
comment := c.Errors.ByType(ErrorTypePrivate).String()
|
||||
|
||||
fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %s |%s %s %-7s %s\n%s",
|
||||
end.Format("2006/01/02 - 15:04:05"),
|
||||
statusColor, statusCode, reset,
|
||||
latency,
|
||||
clientIP,
|
||||
methodColor, reset, method,
|
||||
path,
|
||||
comment,
|
||||
)
|
||||
fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %s |%s %s %-7s %s\n%s",
|
||||
end.Format("2006/01/02 - 15:04:05"),
|
||||
statusColor, statusCode, reset,
|
||||
latency,
|
||||
clientIP,
|
||||
methodColor, reset, method,
|
||||
path,
|
||||
comment,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 12 KiB |
|
@ -5,11 +5,9 @@
|
|||
package gin
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/mattn/go-colorable"
|
||||
)
|
||||
|
||||
const ENV_GIN_MODE = "GIN_MODE"
|
||||
|
@ -25,9 +23,18 @@ const (
|
|||
testCode = iota
|
||||
)
|
||||
|
||||
var DefaultWriter io.Writer = colorable.NewColorableStdout()
|
||||
var ginMode int = debugCode
|
||||
var modeName string = DebugMode
|
||||
// DefaultWriter is the default io.Writer used the Gin for debug output and
|
||||
// middleware output like Logger() or Recovery().
|
||||
// Note that both Logger and Recovery provides custom ways to configure their
|
||||
// output io.Writer.
|
||||
// To support coloring in Windows use:
|
||||
// import "github.com/mattn/go-colorable"
|
||||
// gin.DefaultWriter = colorable.NewColorableStdout()
|
||||
var DefaultWriter = os.Stdout
|
||||
var DefaultErrorWriter = os.Stderr
|
||||
|
||||
var ginMode = debugCode
|
||||
var modeName = DebugMode
|
||||
|
||||
func init() {
|
||||
mode := os.Getenv(ENV_GIN_MODE)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http/httputil"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
|
@ -22,20 +23,21 @@ var (
|
|||
|
||||
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
|
||||
func Recovery() HandlerFunc {
|
||||
return RecoveryWithWriter(DefaultWriter)
|
||||
return RecoveryWithWriter(DefaultErrorWriter)
|
||||
}
|
||||
|
||||
func RecoveryWithWriter(out io.Writer) HandlerFunc {
|
||||
var logger *log.Logger
|
||||
if out != nil {
|
||||
logger = log.New(out, "", log.LstdFlags)
|
||||
logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
|
||||
}
|
||||
return func(c *Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if logger != nil {
|
||||
stack := stack(3)
|
||||
logger.Printf("Panic recovery -> %s\n%s\n", err, stack)
|
||||
httprequest, _ := httputil.DumpRequest(c.Request, false)
|
||||
logger.Printf("[Recovery] panic recovered:\n%s\n%s\n%s%s", string(httprequest), err, stack, reset)
|
||||
}
|
||||
c.AbortWithStatus(500)
|
||||
}
|
||||
|
|
|
@ -61,7 +61,6 @@ func (r HTML) Render(w http.ResponseWriter) error {
|
|||
writeContentType(w, htmlContentType)
|
||||
if len(r.Name) == 0 {
|
||||
return r.Template.Execute(w, r.Data)
|
||||
} else {
|
||||
return r.Template.ExecuteTemplate(w, r.Name, r.Data)
|
||||
}
|
||||
return r.Template.ExecuteTemplate(w, r.Name, r.Data)
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ type Redirect struct {
|
|||
}
|
||||
|
||||
func (r Redirect) Render(w http.ResponseWriter) error {
|
||||
if r.Code < 300 || r.Code > 308 {
|
||||
if (r.Code < 300 || r.Code > 308) && r.Code != 201 {
|
||||
panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
|
||||
}
|
||||
http.Redirect(w, r.Request, r.Location, r.Code)
|
||||
|
|
|
@ -20,6 +20,7 @@ var (
|
|||
_ Render = HTML{}
|
||||
_ HTMLRender = HTMLDebug{}
|
||||
_ HTMLRender = HTMLProduction{}
|
||||
_ Render = YAML{}
|
||||
)
|
||||
|
||||
func writeContentType(w http.ResponseWriter, value []string) {
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2014 Manu Martinez-Almeida. 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"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type YAML struct {
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
var yamlContentType = []string{"application/x-yaml; charset=utf-8"}
|
||||
|
||||
func (r YAML) Render(w http.ResponseWriter) error {
|
||||
writeContentType(w, yamlContentType)
|
||||
|
||||
bytes, err := yaml.Marshal(r.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Write(bytes)
|
||||
return nil
|
||||
}
|
|
@ -20,7 +20,7 @@ type Param struct {
|
|||
// It is therefore safe to read values by the index.
|
||||
type Params []Param
|
||||
|
||||
// ByName returns the value of the first Param which key matches the given name.
|
||||
// Get returns the value of the first Param which key matches the given name.
|
||||
// If no matching Param is found, an empty string is returned.
|
||||
func (ps Params) Get(name string) (string, bool) {
|
||||
for _, entry := range ps {
|
||||
|
@ -31,6 +31,8 @@ func (ps Params) Get(name string) (string, bool) {
|
|||
return "", false
|
||||
}
|
||||
|
||||
// ByName returns the value of the first Param which key matches the given name.
|
||||
// If no matching Param is found, an empty string is returned.
|
||||
func (ps Params) ByName(name string) (va string) {
|
||||
va, _ = ps.Get(name)
|
||||
return
|
||||
|
@ -76,9 +78,10 @@ func countParams(path string) uint8 {
|
|||
type nodeType uint8
|
||||
|
||||
const (
|
||||
static nodeType = 0
|
||||
param nodeType = 1
|
||||
catchAll nodeType = 2
|
||||
static nodeType = iota // default
|
||||
root
|
||||
param
|
||||
catchAll
|
||||
)
|
||||
|
||||
type node struct {
|
||||
|
@ -238,6 +241,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) {
|
|||
}
|
||||
} else { // Empty tree
|
||||
n.insertChild(numParams, path, fullPath, handlers)
|
||||
n.nType = root
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -452,6 +456,11 @@ walk: // Outer loop for walking the tree
|
|||
return
|
||||
}
|
||||
|
||||
if path == "/" && n.wildChild && n.nType != root {
|
||||
tsr = true
|
||||
return
|
||||
}
|
||||
|
||||
// No handle found. Check if a handle for this path + a
|
||||
// trailing slash exists for trailing slash recommendation
|
||||
for i := 0; i < len(n.indices); i++ {
|
||||
|
|
|
@ -47,7 +47,7 @@ func WrapH(h http.Handler) HandlerFunc {
|
|||
|
||||
type H map[string]interface{}
|
||||
|
||||
// Allows type H to be used with xml.Marshal
|
||||
// MarshalXML allows type H to be used with xml.Marshal
|
||||
func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
start.Name = xml.Name{
|
||||
Space: "",
|
||||
|
@ -71,6 +71,12 @@ func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func assert1(guard bool, text string) {
|
||||
if !guard {
|
||||
panic(text)
|
||||
}
|
||||
}
|
||||
|
||||
func filterFlags(content string) string {
|
||||
for i, char := range content {
|
||||
if char == ' ' || char == ';' {
|
||||
|
@ -137,10 +143,9 @@ func resolveAddress(addr []string) string {
|
|||
if port := os.Getenv("PORT"); len(port) > 0 {
|
||||
debugPrint("Environment variable PORT=\"%s\"", port)
|
||||
return ":" + port
|
||||
} else {
|
||||
debugPrint("Environment variable PORT is undefined. Using port :8080 by default")
|
||||
return ":8080"
|
||||
}
|
||||
debugPrint("Environment variable PORT is undefined. Using port :8080 by default")
|
||||
return ":8080"
|
||||
case 1:
|
||||
return addr[0]
|
||||
default:
|
||||
|
|
|
@ -34,18 +34,18 @@
|
|||
},
|
||||
{
|
||||
"path": "github.com/gin-gonic/gin",
|
||||
"revision": "52fcc5dbf6e94df33ad313858fb94b713e9d1b4a",
|
||||
"revisionTime": "2015-09-25T12:14:45+02:00"
|
||||
"revision": "5caaac4c5c712a9e7a7de29e6c24ef46c753017f",
|
||||
"revisionTime": "2016-04-15T01:39:28+02:00"
|
||||
},
|
||||
{
|
||||
"path": "github.com/gin-gonic/gin/binding",
|
||||
"revision": "52fcc5dbf6e94df33ad313858fb94b713e9d1b4a",
|
||||
"revisionTime": "2015-09-25T12:14:45+02:00"
|
||||
"revision": "5caaac4c5c712a9e7a7de29e6c24ef46c753017f",
|
||||
"revisionTime": "2016-04-15T01:39:28+02:00"
|
||||
},
|
||||
{
|
||||
"path": "github.com/gin-gonic/gin/render",
|
||||
"revision": "52fcc5dbf6e94df33ad313858fb94b713e9d1b4a",
|
||||
"revisionTime": "2015-09-25T12:14:45+02:00"
|
||||
"revision": "5caaac4c5c712a9e7a7de29e6c24ef46c753017f",
|
||||
"revisionTime": "2016-04-15T01:39:28+02:00"
|
||||
},
|
||||
{
|
||||
"path": "github.com/go-sql-driver/mysql",
|
||||
|
|
Загрузка…
Ссылка в новой задаче