update gin to v1.2
Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
This commit is contained in:
parent
6864ffe9f0
commit
b9104de2c5
|
@ -1,46 +0,0 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at teamgingonic@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
|
@ -1,13 +0,0 @@
|
|||
## Contributing
|
||||
|
||||
- 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.
|
||||
|
||||
- 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,6 +1,6 @@
|
|||
# Gin Web Framework
|
||||
|
||||
<img align="right" width="159px" src="https://raw.githubusercontent.com/gin-gonic/logo/master/color.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)
|
||||
[![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin)
|
||||
|
@ -13,8 +13,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
|||
![Gin console logger](https://gin-gonic.github.io/gin/other/console.png)
|
||||
|
||||
```sh
|
||||
# assume the following codes in example.go file
|
||||
$ cat example.go
|
||||
$ cat test.go
|
||||
```
|
||||
|
||||
```go
|
||||
|
@ -33,11 +32,6 @@ func main() {
|
|||
}
|
||||
```
|
||||
|
||||
```
|
||||
# run example.go and visit 0.0.0.0:8080/ping on browser
|
||||
$ go run example.go
|
||||
```
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter)
|
||||
|
@ -109,38 +103,6 @@ import "github.com/gin-gonic/gin"
|
|||
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 ~/go/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
|
||||
```
|
||||
|
||||
## API Examples
|
||||
|
||||
### Using GET, POST, PUT, PATCH, DELETE and OPTIONS
|
||||
|
@ -276,7 +238,7 @@ func main() {
|
|||
file, _ := c.FormFile("file")
|
||||
log.Println(file.Filename)
|
||||
|
||||
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
|
||||
c.String(http.StatusOK, fmt.Printf("'%s' uploaded!", file.Filename))
|
||||
})
|
||||
router.Run(":8080")
|
||||
}
|
||||
|
@ -305,7 +267,7 @@ func main() {
|
|||
for _, file := range files {
|
||||
log.Println(file.Filename)
|
||||
}
|
||||
c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
|
||||
c.String(http.StatusOK, fmt.Printf("%d files uploaded!", len(files)))
|
||||
})
|
||||
router.Run(":8080")
|
||||
}
|
||||
|
@ -471,7 +433,7 @@ func startPage(c *gin.Context) {
|
|||
var person Person
|
||||
// If `GET`, only `Form` binding engine (`query`) used.
|
||||
// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
|
||||
// See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
|
||||
// See more at https://github.com/gin-gonic/gin/blob/develop/binding/binding.go#L45
|
||||
if c.Bind(&person) == nil {
|
||||
log.Println(person.Name)
|
||||
log.Println(person.Address)
|
||||
|
@ -481,52 +443,6 @@ func startPage(c *gin.Context) {
|
|||
}
|
||||
```
|
||||
|
||||
### Bind HTML checkboxes
|
||||
|
||||
See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092)
|
||||
|
||||
main.go
|
||||
|
||||
```go
|
||||
...
|
||||
|
||||
type myForm struct {
|
||||
Colors []string `form:"colors[]"`
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
func formHandler(c *gin.Context) {
|
||||
var fakeForm myForm
|
||||
c.Bind(&fakeForm)
|
||||
c.JSON(200, gin.H{"color": fakeForm.Colors})
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
form.html
|
||||
|
||||
```html
|
||||
<form action="/" method="POST">
|
||||
<p>Check some colors</p>
|
||||
<label for="red">Red</label>
|
||||
<input type="checkbox" name="colors[]" value="red" id="red" />
|
||||
<label for="green">Green</label>
|
||||
<input type="checkbox" name="colors[]" value="green" id="green" />
|
||||
<label for="blue">Blue</label>
|
||||
<input type="checkbox" name="colors[]" value="blue" id="blue" />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
```
|
||||
|
||||
result:
|
||||
|
||||
```
|
||||
{"color":["red","green","blue"]}
|
||||
```
|
||||
|
||||
### Multipart/Urlencoded binding
|
||||
|
||||
```go
|
||||
|
@ -605,29 +521,6 @@ func main() {
|
|||
}
|
||||
```
|
||||
|
||||
#### SecureJSON
|
||||
|
||||
Using SecureJSON to prevent json hijacking. Default prepends `"while(1),"` to response body if the given struct is array values.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
// You can also use your own secure json prefix
|
||||
// r.SecureJsonPrefix(")]}',\n")
|
||||
|
||||
r.GET("/someJSON", func(c *gin.Context) {
|
||||
names := []string{"lena", "austin", "foo"}
|
||||
|
||||
// Will output : while(1);["lena","austin","foo"]
|
||||
c.SecureJSON(http.StatusOK, names)
|
||||
})
|
||||
|
||||
// Listen and serve on 0.0.0.0:8080
|
||||
r.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
### Serving static files
|
||||
|
||||
```go
|
||||
|
@ -714,8 +607,6 @@ templates/users/index.tmpl
|
|||
{{ end }}
|
||||
```
|
||||
|
||||
#### Custom Template renderer
|
||||
|
||||
You can also use your own html template render
|
||||
|
||||
```go
|
||||
|
@ -729,8 +620,6 @@ func main() {
|
|||
}
|
||||
```
|
||||
|
||||
#### Custom Delimiters
|
||||
|
||||
You may use custom delims
|
||||
|
||||
```go
|
||||
|
@ -739,7 +628,7 @@ You may use custom delims
|
|||
r.LoadHTMLGlob("/path/to/templates"))
|
||||
```
|
||||
|
||||
#### Custom Template Funcs
|
||||
#### Add custom template funcs
|
||||
|
||||
main.go
|
||||
|
||||
|
@ -989,7 +878,7 @@ func main() {
|
|||
Cache: autocert.DirCache("/var/www/.cache"),
|
||||
}
|
||||
|
||||
log.Fatal(autotls.RunWithManager(r, &m))
|
||||
log.Fatal(autotls.RunWithManager(r, m))
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -1067,7 +956,20 @@ func main() {
|
|||
}
|
||||
```
|
||||
|
||||
## Users [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
|
||||
## Contributing
|
||||
|
||||
- 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.
|
||||
- With pull requests:
|
||||
- Open your pull request against develop
|
||||
- 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.
|
||||
|
||||
## Users
|
||||
|
||||
Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework.
|
||||
|
||||
|
|
|
@ -10,18 +10,16 @@ import (
|
|||
"strconv"
|
||||
)
|
||||
|
||||
// AuthUserKey is the cookie name for user credential in basic auth
|
||||
const AuthUserKey = "user"
|
||||
|
||||
// Accounts defines a key/value for user/pass list of authorized logins
|
||||
type Accounts map[string]string
|
||||
|
||||
type authPair struct {
|
||||
Value string
|
||||
User string
|
||||
}
|
||||
|
||||
type authPairs []authPair
|
||||
type (
|
||||
Accounts map[string]string
|
||||
authPair struct {
|
||||
Value string
|
||||
User string
|
||||
}
|
||||
authPairs []authPair
|
||||
)
|
||||
|
||||
func (a authPairs) searchCredential(authValue string) (string, bool) {
|
||||
if len(authValue) == 0 {
|
||||
|
@ -89,6 +87,6 @@ func secureCompare(given, actual string) bool {
|
|||
if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 {
|
||||
return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1
|
||||
}
|
||||
// Securely compare actual to itself to keep constant time, but always return false
|
||||
/* Securely compare actual to itself to keep constant time, but always return false */
|
||||
return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false
|
||||
}
|
||||
|
|
|
@ -48,15 +48,9 @@ type Context struct {
|
|||
handlers HandlersChain
|
||||
index int8
|
||||
|
||||
engine *Engine
|
||||
|
||||
// Keys is a key/value pair exclusively for the context of each request
|
||||
Keys map[string]interface{}
|
||||
|
||||
// Errors is a list of errors attached to all the handlers/middlewares who used this context
|
||||
Errors errorMsgs
|
||||
|
||||
// Accepted defines a list of manually accepted formats for content negotiation
|
||||
engine *Engine
|
||||
Keys map[string]interface{}
|
||||
Errors errorMsgs
|
||||
Accepted []string
|
||||
}
|
||||
|
||||
|
@ -85,8 +79,8 @@ func (c *Context) Copy() *Context {
|
|||
return &cp
|
||||
}
|
||||
|
||||
// HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()",
|
||||
// this function will return "main.handleGetUsers"
|
||||
// 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())
|
||||
}
|
||||
|
@ -117,8 +111,8 @@ func (c *Context) IsAborted() bool {
|
|||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
|
@ -132,16 +126,15 @@ func (c *Context) AbortWithStatus(code int) {
|
|||
c.Abort()
|
||||
}
|
||||
|
||||
// AbortWithStatusJSON calls `Abort()` and then `JSON` internally.
|
||||
// This method stops the chain, writes the status code and return a JSON body.
|
||||
// AbortWithStatusJSON calls `Abort()` and then `JSON` internally. This method stops the chain, writes the status code and return a JSON body
|
||||
// It also sets the Content-Type as "application/json".
|
||||
func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{}) {
|
||||
c.Abort()
|
||||
c.JSON(code, jsonObj)
|
||||
}
|
||||
|
||||
// AbortWithError calls `AbortWithStatus()` and `Error()` internally.
|
||||
// This method stops the chain, writes the status code and pushes the specified error to `c.Errors`.
|
||||
// AbortWithError calls `AbortWithStatus()` and `Error()` internally. This method stops the chain, writes the status code and
|
||||
// pushes the specified error to `c.Errors`.
|
||||
// See Context.Error() for more details.
|
||||
func (c *Context) AbortWithError(code int, err error) *Error {
|
||||
c.AbortWithStatus(code)
|
||||
|
@ -152,15 +145,11 @@ func (c *Context) AbortWithError(code int, err error) *Error {
|
|||
/********* ERROR MANAGEMENT *********/
|
||||
/************************************/
|
||||
|
||||
// Error attaches an error to the current context. The error is pushed to a list of errors.
|
||||
// Attaches an error to the current context. The error is pushed to a list of errors.
|
||||
// It's a good idea to call Error for each error that occurred during the resolution of a request.
|
||||
// A middleware can be used to collect all the errors and push them to a database together,
|
||||
// print a log, or append it in the HTTP response.
|
||||
// Error will panic if err is nil.
|
||||
// A middleware can be used to collect all the errors
|
||||
// and push them to a database together, print a log, or append it in the HTTP response.
|
||||
func (c *Context) Error(err error) *Error {
|
||||
if err == nil {
|
||||
panic("err is nil")
|
||||
}
|
||||
var parsedError *Error
|
||||
switch err.(type) {
|
||||
case *Error:
|
||||
|
@ -297,10 +286,10 @@ func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string)
|
|||
|
||||
// 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"
|
||||
// })
|
||||
// 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)
|
||||
}
|
||||
|
@ -308,11 +297,11 @@ func (c *Context) Param(key string) string {
|
|||
// Query returns the keyed url query value if it exists,
|
||||
// otherwise 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") == ""
|
||||
// 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
|
||||
|
@ -321,10 +310,10 @@ func (c *Context) Query(key string) string {
|
|||
// DefaultQuery returns the keyed url query value if it exists,
|
||||
// otherwise 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") == ""
|
||||
// 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 value, ok := c.GetQuery(key); ok {
|
||||
return value
|
||||
|
@ -336,10 +325,10 @@ func (c *Context) DefaultQuery(key, defaultValue string) string {
|
|||
// if it exists `(value, true)` (even when the value is an empty string),
|
||||
// otherwise 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")
|
||||
// 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) {
|
||||
if values, ok := c.GetQueryArray(key); ok {
|
||||
return values[0], ok
|
||||
|
@ -385,9 +374,9 @@ func (c *Context) DefaultPostForm(key, defaultValue string) string {
|
|||
// 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
|
||||
// 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) {
|
||||
if values, ok := c.GetPostFormArray(key); ok {
|
||||
return values[0], ok
|
||||
|
@ -433,8 +422,8 @@ func (c *Context) MultipartForm() (*multipart.Form, error) {
|
|||
|
||||
// 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
|
||||
// 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.
|
||||
|
@ -563,7 +552,15 @@ func (c *Context) GetRawData() ([]byte, error) {
|
|||
return ioutil.ReadAll(c.Request.Body)
|
||||
}
|
||||
|
||||
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {
|
||||
func (c *Context) SetCookie(
|
||||
name string,
|
||||
value string,
|
||||
maxAge int,
|
||||
path string,
|
||||
domain string,
|
||||
secure bool,
|
||||
httpOnly bool,
|
||||
) {
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
|
@ -617,13 +614,6 @@ func (c *Context) IndentedJSON(code int, obj interface{}) {
|
|||
c.Render(code, render.IndentedJSON{Data: obj})
|
||||
}
|
||||
|
||||
// SecureJSON serializes the given struct as Secure JSON into the response body.
|
||||
// Default prepends "while(1)," to response body if the given struct is array values.
|
||||
// It also sets the Content-Type as "application/json".
|
||||
func (c *Context) SecureJSON(code int, obj interface{}) {
|
||||
c.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj})
|
||||
}
|
||||
|
||||
// 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{}) {
|
||||
|
|
|
@ -15,7 +15,7 @@ func init() {
|
|||
}
|
||||
|
||||
// IsDebugging returns true if the framework is running in debug mode.
|
||||
// Use SetMode(gin.Release) to disable debug mode.
|
||||
// Use SetMode(gin.Release) to switch to disable the debug mode.
|
||||
func IsDebugging() bool {
|
||||
return ginMode == debugCode
|
||||
}
|
||||
|
|
|
@ -5,11 +5,15 @@
|
|||
package gin
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"log"
|
||||
)
|
||||
|
||||
func (c *Context) GetCookie(name string) (string, error) {
|
||||
log.Println("GetCookie() method is deprecated. Use Cookie() instead.")
|
||||
return c.Cookie(name)
|
||||
}
|
||||
|
||||
// BindWith binds the passed struct pointer using the specified binding engine.
|
||||
// See the binding package.
|
||||
func (c *Context) BindWith(obj interface{}, b binding.Binding) error {
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
/*
|
||||
Package gin implements a HTTP web framework called gin.
|
||||
|
||||
See https://gin-gonic.github.io/gin/ for more information about gin.
|
||||
*/
|
||||
package gin // import "github.com/gin-gonic/gin"
|
|
@ -6,14 +6,11 @@ package gin
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
|
||||
type ErrorType uint64
|
||||
|
||||
const (
|
||||
|
@ -26,13 +23,15 @@ const (
|
|||
ErrorTypeNu = 2
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
Err error
|
||||
Type ErrorType
|
||||
Meta interface{}
|
||||
}
|
||||
type (
|
||||
Error struct {
|
||||
Err error
|
||||
Type ErrorType
|
||||
Meta interface{}
|
||||
}
|
||||
|
||||
type errorMsgs []*Error
|
||||
errorMsgs []*Error
|
||||
)
|
||||
|
||||
var _ error = &Error{}
|
||||
|
||||
|
@ -72,7 +71,7 @@ func (msg *Error) MarshalJSON() ([]byte, error) {
|
|||
return json.Marshal(msg.JSON())
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
// Implements the error interface
|
||||
func (msg Error) Error() string {
|
||||
return msg.Err.Error()
|
||||
}
|
||||
|
@ -81,7 +80,7 @@ func (msg *Error) IsType(flags ErrorType) bool {
|
|||
return (msg.Type & flags) > 0
|
||||
}
|
||||
|
||||
// ByType returns a readonly copy filtered the byte.
|
||||
// Returns a readonly copy filtered the byte.
|
||||
// ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic
|
||||
func (a errorMsgs) ByType(typ ErrorType) errorMsgs {
|
||||
if len(a) == 0 {
|
||||
|
@ -99,16 +98,17 @@ func (a errorMsgs) ByType(typ ErrorType) errorMsgs {
|
|||
return result
|
||||
}
|
||||
|
||||
// Last returns the last error in the slice. It returns nil if the array is empty.
|
||||
// Returns the last error in the slice. It returns nil if the array is empty.
|
||||
// Shortcut for errors[len(errors)-1]
|
||||
func (a errorMsgs) Last() *Error {
|
||||
if length := len(a); length > 0 {
|
||||
length := len(a)
|
||||
if length > 0 {
|
||||
return a[length-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Errors returns an array will all the error messages.
|
||||
// Returns an array will all the error messages.
|
||||
// Example:
|
||||
// c.Error(errors.New("first"))
|
||||
// c.Error(errors.New("second"))
|
||||
|
|
|
@ -9,13 +9,14 @@ import (
|
|||
"os"
|
||||
)
|
||||
|
||||
type onlyfilesFS struct {
|
||||
fs http.FileSystem
|
||||
}
|
||||
|
||||
type neuteredReaddirFile struct {
|
||||
http.File
|
||||
}
|
||||
type (
|
||||
onlyfilesFS struct {
|
||||
fs http.FileSystem
|
||||
}
|
||||
neuteredReaddirFile struct {
|
||||
http.File
|
||||
}
|
||||
)
|
||||
|
||||
// Dir returns a http.Filesystem that can be used by http.FileServer(). It is used internally
|
||||
// in router.Static().
|
||||
|
@ -29,7 +30,7 @@ func Dir(root string, listDirectory bool) http.FileSystem {
|
|||
return &onlyfilesFS{fs}
|
||||
}
|
||||
|
||||
// Open conforms to http.Filesystem
|
||||
// Conforms to http.Filesystem
|
||||
func (fs onlyfilesFS) Open(name string) (http.File, error) {
|
||||
f, err := fs.fs.Open(name)
|
||||
if err != nil {
|
||||
|
@ -38,7 +39,7 @@ func (fs onlyfilesFS) Open(name string) (http.File, error) {
|
|||
return neuteredReaddirFile{f}, nil
|
||||
}
|
||||
|
||||
// Readdir overrides the http.File default implementation
|
||||
// Overrides the http.File default implementation
|
||||
func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) {
|
||||
// this disables directory listing
|
||||
return nil, nil
|
||||
|
|
|
@ -26,73 +26,74 @@ type HandlersChain []HandlerFunc
|
|||
|
||||
// Last returns the last handler in the chain. ie. the last handler is the main own.
|
||||
func (c HandlersChain) Last() HandlerFunc {
|
||||
if length := len(c); length > 0 {
|
||||
length := len(c)
|
||||
if length > 0 {
|
||||
return c[length-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RouteInfo struct {
|
||||
Method string
|
||||
Path string
|
||||
Handler string
|
||||
}
|
||||
type (
|
||||
RoutesInfo []RouteInfo
|
||||
RouteInfo struct {
|
||||
Method string
|
||||
Path string
|
||||
Handler string
|
||||
}
|
||||
|
||||
type RoutesInfo []RouteInfo
|
||||
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
||||
// Create an instance of Engine, by using New() or Default()
|
||||
Engine struct {
|
||||
RouterGroup
|
||||
delims render.Delims
|
||||
HTMLRender render.HTMLRender
|
||||
FuncMap template.FuncMap
|
||||
allNoRoute HandlersChain
|
||||
allNoMethod HandlersChain
|
||||
noRoute HandlersChain
|
||||
noMethod HandlersChain
|
||||
pool sync.Pool
|
||||
trees methodTrees
|
||||
|
||||
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
||||
// Create an instance of Engine, by using New() or Default()
|
||||
type Engine struct {
|
||||
RouterGroup
|
||||
delims render.Delims
|
||||
secureJsonPrefix string
|
||||
HTMLRender render.HTMLRender
|
||||
FuncMap template.FuncMap
|
||||
allNoRoute HandlersChain
|
||||
allNoMethod HandlersChain
|
||||
noRoute HandlersChain
|
||||
noMethod HandlersChain
|
||||
pool sync.Pool
|
||||
trees methodTrees
|
||||
// Enables automatic redirection if the current route can't be matched but a
|
||||
// handler for the path with (without) the trailing slash exists.
|
||||
// For example if /foo/ is requested but a route only exists for /foo, the
|
||||
// client is redirected to /foo with http status code 301 for GET requests
|
||||
// and 307 for all other request methods.
|
||||
RedirectTrailingSlash bool
|
||||
|
||||
// Enables automatic redirection if the current route can't be matched but a
|
||||
// handler for the path with (without) the trailing slash exists.
|
||||
// For example if /foo/ is requested but a route only exists for /foo, the
|
||||
// client is redirected to /foo with http status code 301 for GET requests
|
||||
// and 307 for all other request methods.
|
||||
RedirectTrailingSlash bool
|
||||
// If enabled, the router tries to fix the current request path, if no
|
||||
// handle is registered for it.
|
||||
// First superfluous path elements like ../ or // are removed.
|
||||
// Afterwards the router does a case-insensitive lookup of the cleaned path.
|
||||
// If a handle can be found for this route, the router makes a redirection
|
||||
// to the corrected path with status code 301 for GET requests and 307 for
|
||||
// all other request methods.
|
||||
// For example /FOO and /..//Foo could be redirected to /foo.
|
||||
// RedirectTrailingSlash is independent of this option.
|
||||
RedirectFixedPath bool
|
||||
|
||||
// If enabled, the router tries to fix the current request path, if no
|
||||
// handle is registered for it.
|
||||
// First superfluous path elements like ../ or // are removed.
|
||||
// Afterwards the router does a case-insensitive lookup of the cleaned path.
|
||||
// If a handle can be found for this route, the router makes a redirection
|
||||
// to the corrected path with status code 301 for GET requests and 307 for
|
||||
// all other request methods.
|
||||
// For example /FOO and /..//Foo could be redirected to /foo.
|
||||
// RedirectTrailingSlash is independent of this option.
|
||||
RedirectFixedPath bool
|
||||
// If enabled, the router checks if another method is allowed for the
|
||||
// current route, if the current request can not be routed.
|
||||
// If this is the case, the request is answered with 'Method Not Allowed'
|
||||
// and HTTP status code 405.
|
||||
// If no other Method is allowed, the request is delegated to the NotFound
|
||||
// handler.
|
||||
HandleMethodNotAllowed bool
|
||||
ForwardedByClientIP bool
|
||||
|
||||
// If enabled, the router checks if another method is allowed for the
|
||||
// current route, if the current request can not be routed.
|
||||
// If this is the case, the request is answered with 'Method Not Allowed'
|
||||
// and HTTP status code 405.
|
||||
// If no other Method is allowed, the request is delegated to the NotFound
|
||||
// handler.
|
||||
HandleMethodNotAllowed bool
|
||||
ForwardedByClientIP bool
|
||||
// #726 #755 If enabled, it will thrust some headers starting with
|
||||
// 'X-AppEngine...' for better integration with that PaaS.
|
||||
AppEngine bool
|
||||
|
||||
// #726 #755 If enabled, it will thrust some headers starting with
|
||||
// 'X-AppEngine...' for better integration with that PaaS.
|
||||
AppEngine bool
|
||||
|
||||
// If enabled, the url.RawPath will be used to find parameters.
|
||||
UseRawPath bool
|
||||
// If true, the path value will be unescaped.
|
||||
// If UseRawPath is false (by default), the UnescapePathValues effectively is true,
|
||||
// as url.Path gonna be used, which is already unescaped.
|
||||
UnescapePathValues bool
|
||||
}
|
||||
// If enabled, the url.RawPath will be used to find parameters.
|
||||
UseRawPath bool
|
||||
// If true, the path value will be unescaped.
|
||||
// If UseRawPath is false (by default), the UnescapePathValues effectively is true,
|
||||
// as url.Path gonna be used, which is already unescaped.
|
||||
UnescapePathValues bool
|
||||
}
|
||||
)
|
||||
|
||||
var _ IRouter = &Engine{}
|
||||
|
||||
|
@ -122,7 +123,6 @@ func New() *Engine {
|
|||
UnescapePathValues: true,
|
||||
trees: make(methodTrees, 0, 9),
|
||||
delims: render.Delims{"{{", "}}"},
|
||||
secureJsonPrefix: "while(1);",
|
||||
}
|
||||
engine.RouterGroup.engine = engine
|
||||
engine.pool.New = func() interface{} {
|
||||
|
@ -147,11 +147,6 @@ func (engine *Engine) Delims(left, right string) *Engine {
|
|||
return engine
|
||||
}
|
||||
|
||||
func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
|
||||
engine.secureJsonPrefix = prefix
|
||||
return engine
|
||||
}
|
||||
|
||||
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
||||
if IsDebugging() {
|
||||
debugPrintLoadTemplate(template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern)))
|
||||
|
@ -291,7 +286,7 @@ func (engine *Engine) RunUnix(file string) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// ServeHTTP conforms to the http.Handler interface.
|
||||
// Conforms to the http.Handler interface.
|
||||
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
c := engine.pool.Get().(*Context)
|
||||
c.writermem.reset(w)
|
||||
|
@ -303,7 +298,7 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
engine.pool.Put(c)
|
||||
}
|
||||
|
||||
// HandleContext re-enter a context that has been rewritten.
|
||||
// Re-enter a context that has been rewritten.
|
||||
// This can be done by setting c.Request.Path to your new target.
|
||||
// Disclaimer: You can loop yourself to death with this, use wisely.
|
||||
func (engine *Engine) HandleContext(c *Context) {
|
||||
|
@ -351,6 +346,7 @@ func (engine *Engine) handleHTTPRequest(context *Context) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: unit test
|
||||
if engine.HandleMethodNotAllowed {
|
||||
for _, tree := range engine.trees {
|
||||
if tree.method != httpMethod {
|
||||
|
|
|
@ -25,17 +25,14 @@ var (
|
|||
disableColor = false
|
||||
)
|
||||
|
||||
// DisableConsoleColor disables color output in the console
|
||||
func DisableConsoleColor() {
|
||||
disableColor = true
|
||||
}
|
||||
|
||||
// ErrorLogger returns a handlerfunc for any error type
|
||||
func ErrorLogger() HandlerFunc {
|
||||
return ErrorLoggerT(ErrorTypeAny)
|
||||
}
|
||||
|
||||
// ErrorLoggerT returns a handlerfunc for a given error type
|
||||
func ErrorLoggerT(typ ErrorType) HandlerFunc {
|
||||
return func(c *Context) {
|
||||
c.Next()
|
||||
|
@ -77,7 +74,6 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
|
|||
// Start timer
|
||||
start := time.Now()
|
||||
path := c.Request.URL.Path
|
||||
raw := c.Request.URL.RawQuery
|
||||
|
||||
// Process request
|
||||
c.Next()
|
||||
|
@ -98,10 +94,6 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
|
|||
}
|
||||
comment := c.Errors.ByType(ErrorTypePrivate).String()
|
||||
|
||||
if raw != "" {
|
||||
path = path + "?" + raw
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %s %-7s %s\n%s",
|
||||
end.Format("2006/01/02 - 15:04:05"),
|
||||
statusColor, statusCode, reset,
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -64,10 +64,6 @@ func DisableBindValidation() {
|
|||
binding.Validator = nil
|
||||
}
|
||||
|
||||
func EnableJsonDecoderUseNumber() {
|
||||
binding.EnableDecoderUseNumber = true
|
||||
}
|
||||
|
||||
func Mode() string {
|
||||
return modeName
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
package gin
|
||||
|
||||
// cleanPath is the URL version of path.Clean, it returns a canonical URL path
|
||||
// CleanPath is the URL version of path.Clean, it returns a canonical URL path
|
||||
// for p, eliminating . and .. elements.
|
||||
//
|
||||
// The following rules are applied iteratively until no further processing can
|
||||
|
|
|
@ -16,34 +16,36 @@ const (
|
|||
defaultStatus = 200
|
||||
)
|
||||
|
||||
type ResponseWriter interface {
|
||||
http.ResponseWriter
|
||||
http.Hijacker
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
type (
|
||||
ResponseWriter interface {
|
||||
http.ResponseWriter
|
||||
http.Hijacker
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
|
||||
// Returns the HTTP response status code of the current request.
|
||||
Status() int
|
||||
// Returns the HTTP response status code of the current request.
|
||||
Status() int
|
||||
|
||||
// Returns the number of bytes already written into the response http body.
|
||||
// See Written()
|
||||
Size() int
|
||||
// Returns the number of bytes already written into the response http body.
|
||||
// See Written()
|
||||
Size() int
|
||||
|
||||
// Writes the string into the response body.
|
||||
WriteString(string) (int, error)
|
||||
// Writes the string into the response body.
|
||||
WriteString(string) (int, error)
|
||||
|
||||
// Returns true if the response body was already written.
|
||||
Written() bool
|
||||
// Returns true if the response body was already written.
|
||||
Written() bool
|
||||
|
||||
// Forces to write the http header (status code + headers).
|
||||
WriteHeaderNow()
|
||||
}
|
||||
// Forces to write the http header (status code + headers).
|
||||
WriteHeaderNow()
|
||||
}
|
||||
|
||||
type responseWriter struct {
|
||||
http.ResponseWriter
|
||||
size int
|
||||
status int
|
||||
}
|
||||
responseWriter struct {
|
||||
http.ResponseWriter
|
||||
size int
|
||||
status int
|
||||
}
|
||||
)
|
||||
|
||||
var _ ResponseWriter = &responseWriter{}
|
||||
|
||||
|
@ -95,7 +97,7 @@ func (w *responseWriter) Written() bool {
|
|||
return w.size != noWritten
|
||||
}
|
||||
|
||||
// Hijack implements the http.Hijacker interface
|
||||
// Implements the http.Hijacker interface
|
||||
func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if w.size < 0 {
|
||||
w.size = 0
|
||||
|
@ -103,12 +105,12 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|||
return w.ResponseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
// CloseNotify implements the http.CloseNotify interface
|
||||
// Implements the http.CloseNotify interface
|
||||
func (w *responseWriter) CloseNotify() <-chan bool {
|
||||
return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
// Flush implements the http.Flush interface
|
||||
// Implements the http.Flush interface
|
||||
func (w *responseWriter) Flush() {
|
||||
w.ResponseWriter.(http.Flusher).Flush()
|
||||
}
|
||||
|
|
|
@ -11,37 +11,39 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
type IRouter interface {
|
||||
IRoutes
|
||||
Group(string, ...HandlerFunc) *RouterGroup
|
||||
}
|
||||
type (
|
||||
IRouter interface {
|
||||
IRoutes
|
||||
Group(string, ...HandlerFunc) *RouterGroup
|
||||
}
|
||||
|
||||
type IRoutes interface {
|
||||
Use(...HandlerFunc) IRoutes
|
||||
IRoutes interface {
|
||||
Use(...HandlerFunc) IRoutes
|
||||
|
||||
Handle(string, string, ...HandlerFunc) IRoutes
|
||||
Any(string, ...HandlerFunc) IRoutes
|
||||
GET(string, ...HandlerFunc) IRoutes
|
||||
POST(string, ...HandlerFunc) IRoutes
|
||||
DELETE(string, ...HandlerFunc) IRoutes
|
||||
PATCH(string, ...HandlerFunc) IRoutes
|
||||
PUT(string, ...HandlerFunc) IRoutes
|
||||
OPTIONS(string, ...HandlerFunc) IRoutes
|
||||
HEAD(string, ...HandlerFunc) IRoutes
|
||||
Handle(string, string, ...HandlerFunc) IRoutes
|
||||
Any(string, ...HandlerFunc) IRoutes
|
||||
GET(string, ...HandlerFunc) IRoutes
|
||||
POST(string, ...HandlerFunc) IRoutes
|
||||
DELETE(string, ...HandlerFunc) IRoutes
|
||||
PATCH(string, ...HandlerFunc) IRoutes
|
||||
PUT(string, ...HandlerFunc) IRoutes
|
||||
OPTIONS(string, ...HandlerFunc) IRoutes
|
||||
HEAD(string, ...HandlerFunc) IRoutes
|
||||
|
||||
StaticFile(string, string) IRoutes
|
||||
Static(string, string) IRoutes
|
||||
StaticFS(string, http.FileSystem) IRoutes
|
||||
}
|
||||
StaticFile(string, string) IRoutes
|
||||
Static(string, string) IRoutes
|
||||
StaticFS(string, http.FileSystem) IRoutes
|
||||
}
|
||||
|
||||
// RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix
|
||||
// and an array of handlers (middleware)
|
||||
type RouterGroup struct {
|
||||
Handlers HandlersChain
|
||||
basePath string
|
||||
engine *Engine
|
||||
root bool
|
||||
}
|
||||
// RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix
|
||||
// and an array of handlers (middleware)
|
||||
RouterGroup struct {
|
||||
Handlers HandlersChain
|
||||
basePath string
|
||||
engine *Engine
|
||||
root bool
|
||||
}
|
||||
)
|
||||
|
||||
var _ IRouter = &RouterGroup{}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"net/http"
|
||||
)
|
||||
|
||||
// CreateTestContext returns a fresh engine and context for testing purposes
|
||||
func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) {
|
||||
r = New()
|
||||
c = r.allocateContext()
|
||||
|
|
|
@ -105,7 +105,9 @@ func (n *node) incrementChildPrio(pos int) int {
|
|||
newPos := pos
|
||||
for newPos > 0 && n.children[newPos-1].priority < prio {
|
||||
// swap node positions
|
||||
n.children[newPos-1], n.children[newPos] = n.children[newPos], n.children[newPos-1]
|
||||
tmpN := n.children[newPos-1]
|
||||
n.children[newPos-1] = n.children[newPos]
|
||||
n.children[newPos] = tmpN
|
||||
|
||||
newPos--
|
||||
}
|
||||
|
@ -357,7 +359,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle
|
|||
n.handlers = handlers
|
||||
}
|
||||
|
||||
// getValue returns the handle registered with the given path (key). The values of
|
||||
// Returns the handle registered with the given path (key). The values of
|
||||
// wildcards are saved to a map.
|
||||
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
|
||||
// made if a handle exists with an extra (without the) trailing slash for the
|
||||
|
@ -499,7 +501,7 @@ walk: // Outer loop for walking the tree
|
|||
}
|
||||
}
|
||||
|
||||
// findCaseInsensitivePath makes a case-insensitive lookup of the given path and tries to find a handler.
|
||||
// Makes a case-insensitive lookup of the given path and tries to find a handler.
|
||||
// It can optionally also fix trailing slashes.
|
||||
// It returns the case-corrected path and a bool indicating whether the lookup
|
||||
// was successful.
|
||||
|
|
|
@ -100,10 +100,12 @@ func parseAccept(acceptHeader string) []string {
|
|||
parts := strings.Split(acceptHeader, ",")
|
||||
out := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
if index := strings.IndexByte(part, ';'); index >= 0 {
|
||||
index := strings.IndexByte(part, ';')
|
||||
if index >= 0 {
|
||||
part = part[0:index]
|
||||
}
|
||||
if part = strings.TrimSpace(part); len(part) > 0 {
|
||||
part = strings.TrimSpace(part)
|
||||
if len(part) > 0 {
|
||||
out = append(out, part)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,10 +129,12 @@
|
|||
"revisionTime": "2017-01-09T09:34:21Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "YYaHHK72ywsjJG11nEnz6JdGqSg=",
|
||||
"checksumSHA1": "cpELhz30Sco7IBwvI6FyuShnjnQ=",
|
||||
"path": "github.com/gin-gonic/gin",
|
||||
"revision": "5cb25a6410a299dcc31e7f31b41c60c90e30b3f6",
|
||||
"revisionTime": "2017-07-14T17:28:16Z"
|
||||
"revision": "d459835d2b077e44f7c9b453505ee29881d5d12d",
|
||||
"revisionTime": "2017-07-02T09:28:26Z",
|
||||
"version": "v1.2",
|
||||
"versionExact": "v1.2"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "A09l4+1MIuaXhh3Nn6aoOm+rr3Q=",
|
||||
|
|
Loading…
Reference in New Issue