Adds website
277
README.md
@ -1,277 +0,0 @@
|
|||||||
#Gin Web Framework
|
|
||||||
Gin is a web framework written in Golang. It features a martini-like API with much better performance, up to 40 times faster. If you need performance and good productivity, you will love Gin.
|
|
||||||
[Check out the official web site](http://gingonic.github.com)
|
|
||||||
|
|
||||||
## Start using it
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```
|
|
||||||
go get github.com/gin-gonic/gin
|
|
||||||
```
|
|
||||||
Then import it in your Golang code:
|
|
||||||
|
|
||||||
```
|
|
||||||
import "github.com/gin-gonic/gin"
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
##API Examples
|
|
||||||
|
|
||||||
#### Create most basic PING/PONG HTTP endpoint
|
|
||||||
```
|
|
||||||
func main() {
|
|
||||||
// Creates a gin router + logger and recovery (crash-free) middlwares
|
|
||||||
r := gin.Default()
|
|
||||||
r.GET("/ping", func(c *gin.Context){
|
|
||||||
c.String("pong")
|
|
||||||
})
|
|
||||||
|
|
||||||
r.POST("/somePost", posting)
|
|
||||||
r.PUT("/somePut", putting)
|
|
||||||
r.DELETE("/someDelete", deleting)
|
|
||||||
r.PATCH("/somePATCH", patching)
|
|
||||||
|
|
||||||
// Listen and server on 0.0.0.0:8080
|
|
||||||
r.Run(":8080")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Parameters in path
|
|
||||||
|
|
||||||
```
|
|
||||||
func main() {
|
|
||||||
r := gin.Default()
|
|
||||||
|
|
||||||
r.GET("/user/:name", func(c *gin.Context) {
|
|
||||||
name := c.Params.ByName("name")
|
|
||||||
message := "Hello "+name
|
|
||||||
c.String(200, message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### Grouping routes
|
|
||||||
```
|
|
||||||
func main() {
|
|
||||||
r := gin.Default()
|
|
||||||
|
|
||||||
// Simple group: v1
|
|
||||||
v1 := r.Group("/v1")
|
|
||||||
{
|
|
||||||
v1.POST("/login", loginEndpoint)
|
|
||||||
v1.POST("/submit", submitEndpoint)
|
|
||||||
v1.POST("/read", readEndpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple group: v1
|
|
||||||
v2 := r.Group("/v2")
|
|
||||||
{
|
|
||||||
v2.POST("/login", loginEndpoint)
|
|
||||||
v2.POST("/submit"", submitEndpoint)
|
|
||||||
v2.POST("/read"", readEndpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen and server on 0.0.0.0:8080
|
|
||||||
r.Run(":8080")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### Blank Gin without middlewares by default
|
|
||||||
|
|
||||||
Use
|
|
||||||
|
|
||||||
```
|
|
||||||
r := gin.New()
|
|
||||||
```
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```
|
|
||||||
r := gin.Default()
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### Using middlewares
|
|
||||||
```
|
|
||||||
func main() {
|
|
||||||
// Creates a router without any middlware by default
|
|
||||||
r := gin.New()
|
|
||||||
|
|
||||||
// Global middlwares
|
|
||||||
r.Use(gin.Logger())
|
|
||||||
r.Use(gin.Recovery())
|
|
||||||
|
|
||||||
// Per route middlwares, you can add as many as you desire.
|
|
||||||
r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
|
|
||||||
|
|
||||||
// Authorization group
|
|
||||||
// authorized := r.Group("/", AuthRequired())
|
|
||||||
// exactly the same than:
|
|
||||||
authorized := r.Group("/")
|
|
||||||
// per group middlwares! in this case we use the custom created
|
|
||||||
// AuthRequired() middlware just in the "authorized" group.
|
|
||||||
authorized.Use(AuthRequired())
|
|
||||||
{
|
|
||||||
authorized.Use.POST("/login", loginEndpoint)
|
|
||||||
authorized.Use.POST("/submit", submitEndpoint)
|
|
||||||
authorized.Use.POST("/read", readEndpoint)
|
|
||||||
|
|
||||||
// nested group
|
|
||||||
testing := authorized.Group("testing")
|
|
||||||
testing.GET("/analytics", analyticsEndpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen and server on 0.0.0.0:8080
|
|
||||||
r.Run(":8080")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### JSON parsing and validation
|
|
||||||
```
|
|
||||||
|
|
||||||
type LoginJSON struct {
|
|
||||||
User string `json:"user" binding:"required"`
|
|
||||||
Password string `json:"password" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
r := gin.Default()
|
|
||||||
|
|
||||||
r.POST("/login", func(c *gin.Context) {
|
|
||||||
var json LoginJSON
|
|
||||||
|
|
||||||
// If EnsureBody returns false, it will write automatically the error
|
|
||||||
// in the HTTP stream and return a 400 error. If you want custom error
|
|
||||||
// handling you should use: c.ParseBody(interface{}) error
|
|
||||||
if c.EnsureBody(&json) {
|
|
||||||
if json.User=="manu" && json.Password=="123" {
|
|
||||||
c.JSON(200, gin.H{"status": "you are logged in"})
|
|
||||||
}else{
|
|
||||||
c.JSON(401, gin.H{"status": "unauthorized"})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### XML, and JSON rendering
|
|
||||||
|
|
||||||
```
|
|
||||||
func main() {
|
|
||||||
r := gin.Default()
|
|
||||||
|
|
||||||
// gin.H is a shortcup for map[string]interface{}
|
|
||||||
r.GET("/someJSON", func(c *gin.Context) {
|
|
||||||
c.JSON(200, gin.H{"message": "hey", "status": 200})
|
|
||||||
})
|
|
||||||
|
|
||||||
r.GET("/moreJSON", func(c *gin.Context) {
|
|
||||||
// You also can use a struct
|
|
||||||
var msg struct {
|
|
||||||
Message string
|
|
||||||
Status int
|
|
||||||
}
|
|
||||||
msg.Message = "hey"
|
|
||||||
msg.Status = 200
|
|
||||||
c.JSON(200, msg.Status)
|
|
||||||
})
|
|
||||||
|
|
||||||
r.GET("/someXML", func(c *gin.Context) {
|
|
||||||
c.XML(200, gin.H{"message": "hey", "status": 200})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
####HTML rendering
|
|
||||||
|
|
||||||
Using LoadHTMLTemplates()
|
|
||||||
|
|
||||||
```
|
|
||||||
func main() {
|
|
||||||
r := gin.Default()
|
|
||||||
r.LoadHTMLTemplates("templates/*")
|
|
||||||
r.GET("index", func(c *gin.Context) {
|
|
||||||
obj := gin.h{"title": "Main website"}
|
|
||||||
c.HTML(200, "templates/index.tmpl", obj)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also use your own html template render
|
|
||||||
|
|
||||||
```
|
|
||||||
import "html/template"
|
|
||||||
func main() {
|
|
||||||
r := gin.Default()
|
|
||||||
html := template.ParseFiles("file1", "file2")
|
|
||||||
r.HTMLTemplates = html
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### Custom Middlewares
|
|
||||||
|
|
||||||
```
|
|
||||||
func Logger() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
t : time.Now()
|
|
||||||
|
|
||||||
// Set example variable
|
|
||||||
c.Set("example", "12345")
|
|
||||||
|
|
||||||
// before request
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
|
|
||||||
// after request
|
|
||||||
latency := time.Since(t)
|
|
||||||
log.Print(latency)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
r := gin.New()
|
|
||||||
r.Use(Logger())
|
|
||||||
|
|
||||||
r.GET("test", func(c *gin.Context){
|
|
||||||
example := r.Get("example").(string)
|
|
||||||
|
|
||||||
// it would print: "12345"
|
|
||||||
log.Println(example)
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Custom HTTP configuration
|
|
||||||
|
|
||||||
Do not use the `Run()` method, instead use this:
|
|
||||||
|
|
||||||
```
|
|
||||||
func main() {
|
|
||||||
router := gin.Default()
|
|
||||||
http.ListenAndServe(":8080", router)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
or
|
|
||||||
|
|
||||||
```
|
|
||||||
func main() {
|
|
||||||
router := gin.Default()
|
|
||||||
|
|
||||||
s := &http.Server{
|
|
||||||
Addr: ":8080",
|
|
||||||
Handler: router,
|
|
||||||
ReadTimeout: 10 * time.Second,
|
|
||||||
WriteTimeout: 10 * time.Second,
|
|
||||||
MaxHeaderBytes: 1 << 20,
|
|
||||||
}
|
|
||||||
s.ListenAndServe()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
83
auth.go
@ -1,83 +0,0 @@
|
|||||||
package gin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/subtle"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
BasicAuthPair struct {
|
|
||||||
Code string
|
|
||||||
User string
|
|
||||||
}
|
|
||||||
Account struct {
|
|
||||||
User string
|
|
||||||
Password string
|
|
||||||
}
|
|
||||||
|
|
||||||
Accounts []Account
|
|
||||||
Pairs []BasicAuthPair
|
|
||||||
)
|
|
||||||
|
|
||||||
func (a Pairs) Len() int { return len(a) }
|
|
||||||
func (a Pairs) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
func (a Pairs) Less(i, j int) bool { return a[i].Code < a[j].Code }
|
|
||||||
|
|
||||||
func processCredentials(accounts Accounts) (Pairs, error) {
|
|
||||||
if len(accounts) == 0 {
|
|
||||||
return nil, errors.New("Empty list of authorized credentials.")
|
|
||||||
}
|
|
||||||
pairs := make(Pairs, 0, len(accounts))
|
|
||||||
for _, account := range accounts {
|
|
||||||
if len(account.User) == 0 || len(account.Password) == 0 {
|
|
||||||
return nil, errors.New("User or password is empty")
|
|
||||||
}
|
|
||||||
base := account.User + ":" + account.Password
|
|
||||||
code := "Basic " + base64.StdEncoding.EncodeToString([]byte(base))
|
|
||||||
pairs = append(pairs, BasicAuthPair{code, account.User})
|
|
||||||
}
|
|
||||||
// We have to sort the credentials in order to use bsearch later.
|
|
||||||
sort.Sort(pairs)
|
|
||||||
return pairs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func searchCredential(pairs Pairs, auth string) string {
|
|
||||||
if len(auth) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
// Search user in the slice of allowed credentials
|
|
||||||
r := sort.Search(len(pairs), func(i int) bool { return pairs[i].Code >= auth })
|
|
||||||
|
|
||||||
if r < len(pairs) && subtle.ConstantTimeCompare([]byte(pairs[r].Code), []byte(auth)) == 1 {
|
|
||||||
// user is allowed, set UserId to key "user" in this context, the userId can be read later using
|
|
||||||
// c.Get("user"
|
|
||||||
return pairs[r].User
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implements a basic Basic HTTP Authorization. It takes as argument a map[string]string where
|
|
||||||
// the key is the user name and the value is the password.
|
|
||||||
func BasicAuth(accounts Accounts) HandlerFunc {
|
|
||||||
|
|
||||||
pairs, err := processCredentials(accounts)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return func(c *Context) {
|
|
||||||
// Search user in the slice of allowed credentials
|
|
||||||
user := searchCredential(pairs, c.Req.Header.Get("Authorization"))
|
|
||||||
if len(user) == 0 {
|
|
||||||
// Credentials doesn't match, we return 401 Unauthorized and abort request.
|
|
||||||
c.Writer.Header().Set("WWW-Authenticate", "Basic realm=\"Authorization Required\"")
|
|
||||||
c.Fail(401, errors.New("Unauthorized"))
|
|
||||||
} else {
|
|
||||||
// user is allowed, set UserId to key "user" in this context, the userId can be read later using
|
|
||||||
// c.Get("user")
|
|
||||||
c.Set("user", user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
607
css/gin.webflow.css
Executable file
@ -0,0 +1,607 @@
|
|||||||
|
body {
|
||||||
|
background-color: #dbdbdb;
|
||||||
|
font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;
|
||||||
|
color: #333;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin: 10px 0px;
|
||||||
|
font-size: 38px;
|
||||||
|
line-height: 44px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin: 10px 0px;
|
||||||
|
font-size: 32px;
|
||||||
|
line-height: 36px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin: 10px 0px 1px;
|
||||||
|
font-family: Montserrat, sans-serif;
|
||||||
|
font-size: 21px;
|
||||||
|
line-height: 30px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
margin: 10px 0px;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
h5 {
|
||||||
|
margin: 10px 0px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
h6 {
|
||||||
|
margin: 10px 0px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin-bottom: 26px;
|
||||||
|
font-family:'Varela Round', sans-serif;
|
||||||
|
color: #848999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
width: 180px;
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-left: 8px;
|
||||||
|
padding: 14px 29px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #6d7280;
|
||||||
|
-webkit-transition: background-color 300ms ease;
|
||||||
|
-o-transition: background-color 300ms ease;
|
||||||
|
transition: background-color 300ms ease;
|
||||||
|
font-family: Montserrat, sans-serif;
|
||||||
|
color: white;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.button:hover {
|
||||||
|
background-color: #848a9c;
|
||||||
|
}
|
||||||
|
.button.nav {
|
||||||
|
margin-left: 18px;
|
||||||
|
-webkit-transition: all 200ms ease;
|
||||||
|
-o-transition: all 200ms ease;
|
||||||
|
transition: all 200ms ease;
|
||||||
|
}
|
||||||
|
.button.nav:hover {
|
||||||
|
background-color: #9299ad;
|
||||||
|
}
|
||||||
|
.button.big-green {
|
||||||
|
margin-top: 191px;
|
||||||
|
padding: 25px 22px;
|
||||||
|
background-color: #32ac97;
|
||||||
|
-webkit-transition: all 200ms ease;
|
||||||
|
-o-transition: all 200ms ease;
|
||||||
|
transition: all 200ms ease;
|
||||||
|
font-size: 29px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
.button.big-green:hover {
|
||||||
|
background-color: #258575;
|
||||||
|
}
|
||||||
|
.button.sign-up {
|
||||||
|
width: 180px;
|
||||||
|
background-color: #2c9986;
|
||||||
|
}
|
||||||
|
.button.sign-up:hover {
|
||||||
|
background-color: #33b59f;
|
||||||
|
}
|
||||||
|
.button.sentrybutton {
|
||||||
|
display: inline-block;
|
||||||
|
width: 315px;
|
||||||
|
margin-right: 0px;
|
||||||
|
margin-bottom: 26px;
|
||||||
|
margin-left: 0px;
|
||||||
|
background-image: url('../images/sentry2.png');
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-size: cover;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
.button.sentrybutton:hover {
|
||||||
|
width: 315px;
|
||||||
|
background-image: -webkit-radial-gradient(circle at 38% 56%, rgba(255, 255, 255, 0), rgba(242, 134, 27, 0.81)), url('../images/sentry2.png');
|
||||||
|
background-image: -o-radial-gradient(circle at 38% 56%, rgba(255, 255, 255, 0), rgba(242, 134, 27, 0.81)), url('../images/sentry2.png');
|
||||||
|
background-image: radial-gradient(circle at 38% 56%, rgba(255, 255, 255, 0), rgba(242, 134, 27, 0.81)), url('../images/sentry2.png');
|
||||||
|
background-position: 0% 0%, 50% 50%;
|
||||||
|
background-size: auto, cover;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
margin-top: 4px;
|
||||||
|
padding-top: 65px;
|
||||||
|
padding-bottom: 65px;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
.section.header {
|
||||||
|
padding-top: 22px;
|
||||||
|
padding-bottom: 22px;
|
||||||
|
background-color: #262933;
|
||||||
|
}
|
||||||
|
.section.hero {
|
||||||
|
width: auto;
|
||||||
|
height: 427px;
|
||||||
|
margin-top: 4px;
|
||||||
|
padding-top: 139px;
|
||||||
|
padding-right: 32%;
|
||||||
|
padding-bottom: 0px;
|
||||||
|
background-image: url('../images/gin.jpg');
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.section.grey {
|
||||||
|
background-color: #eff1f4;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.section.centered {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.section.footer {
|
||||||
|
background-color: #363b48;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.company {
|
||||||
|
margin-top: 11px;
|
||||||
|
float: left;
|
||||||
|
font-family: Montserrat, sans-serif;
|
||||||
|
color: white;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.nav-link {
|
||||||
|
margin-right: 11px;
|
||||||
|
margin-left: 11px;
|
||||||
|
-webkit-transition: color 300ms ease, background-color 300ms ease;
|
||||||
|
-o-transition: color 300ms ease, background-color 300ms ease;
|
||||||
|
transition: color 300ms ease, background-color 300ms ease;
|
||||||
|
font-family: Montserrat, sans-serif;
|
||||||
|
color: rgba(255, 255, 255, 0.81);
|
||||||
|
font-size: 14px;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.nav-link:hover {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.nav-link.sign-up {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #2c9986;
|
||||||
|
}
|
||||||
|
.nav-link.sign-up:hover {
|
||||||
|
background-color: #3ac2ab;
|
||||||
|
}
|
||||||
|
.nav-column {
|
||||||
|
padding-top: 10px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
margin-right: 11px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.hero-heading {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
float: none;
|
||||||
|
border-radius: 0px;
|
||||||
|
font-family:'Great Vibes', cursive;
|
||||||
|
color: white;
|
||||||
|
font-size: 70px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: none;
|
||||||
|
list-style-type: none;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
.hero-subhead {
|
||||||
|
color: #a3a8b6;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.section-title {
|
||||||
|
font-family: Montserrat, sans-serif;
|
||||||
|
font-size: 31px;
|
||||||
|
font-weight: 400;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.section-subtitle {
|
||||||
|
margin-bottom: 39px;
|
||||||
|
color: #848999;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.responsive-img {
|
||||||
|
display: block;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
.grey-icon {
|
||||||
|
margin-right: 9px;
|
||||||
|
}
|
||||||
|
.content-column {
|
||||||
|
padding-top: 79px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.circle {
|
||||||
|
height: 317px;
|
||||||
|
margin-bottom: 26px;
|
||||||
|
padding-top: 21px;
|
||||||
|
border-radius: 190px;
|
||||||
|
background-color: #f7f8fa;
|
||||||
|
}
|
||||||
|
.number {
|
||||||
|
display: block;
|
||||||
|
width: auto;
|
||||||
|
height: 40px;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-bottom: 17px;
|
||||||
|
margin-left: auto;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-radius: 50px;
|
||||||
|
background-color: #363b48;
|
||||||
|
font-family: Montserrat, sans-serif;
|
||||||
|
color: white;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.icons {
|
||||||
|
display: inline-block;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
margin-top: 30px;
|
||||||
|
padding-top: 14px;
|
||||||
|
border-radius: 40px;
|
||||||
|
background-color: #eff1f4;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.quote-box {
|
||||||
|
padding: 34px 40px 34px 98px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: white;
|
||||||
|
background-image: url('../images/12-quotes.png');
|
||||||
|
background-position: 8% 22%;
|
||||||
|
background-size: 50px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.quote {
|
||||||
|
color: #2d303b;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
.quote-thingy {
|
||||||
|
display: block;
|
||||||
|
margin-top: -2px;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: 45px;
|
||||||
|
}
|
||||||
|
.by-section {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.person-icon {
|
||||||
|
margin-right: 15px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.location {
|
||||||
|
font-family: Varela;
|
||||||
|
color: #848999;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
.logo-bottom {
|
||||||
|
margin-right: 14px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.footer-text {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 11px;
|
||||||
|
font-family: Montserrat, sans-serif;
|
||||||
|
color: #7d8391;
|
||||||
|
font-size: 15px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.social-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin-right: 7px;
|
||||||
|
margin-left: 7px;
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-right: 0px;
|
||||||
|
border-radius: 40px;
|
||||||
|
background-color: #596073;
|
||||||
|
-webkit-transition: all 200ms ease;
|
||||||
|
-o-transition: all 200ms ease;
|
||||||
|
transition: all 200ms ease;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.social-icon:hover {
|
||||||
|
background-color: #7f89a3;
|
||||||
|
}
|
||||||
|
.right-footer-col {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.nav-bar {
|
||||||
|
padding-top: 0px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
background-color: #d6d6d6;
|
||||||
|
}
|
||||||
|
.image-crop {
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: hidden;
|
||||||
|
height: 302px;
|
||||||
|
box-shadow: red 0px -11px 0px 0px inset;
|
||||||
|
-webkit-transition: height 300ms ease;
|
||||||
|
-o-transition: height 300ms ease;
|
||||||
|
transition: height 300ms ease;
|
||||||
|
}
|
||||||
|
.image-crop:hover {
|
||||||
|
height: 372px;
|
||||||
|
}
|
||||||
|
.button-group {
|
||||||
|
position: static;
|
||||||
|
margin-top: 26px;
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.presenting {
|
||||||
|
padding-right: 0px;
|
||||||
|
}
|
||||||
|
.graph-image {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.graph-block {
|
||||||
|
display: block;
|
||||||
|
overflow-x: visible;
|
||||||
|
overflow-y: visible;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
float: none;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.goget {
|
||||||
|
margin-top: 26px;
|
||||||
|
}
|
||||||
|
.goget-text {
|
||||||
|
padding: 4px 14px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: rgba(105, 105, 105, 0.65);
|
||||||
|
font-family:'Open Sans', sans-serif;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.footgraph {
|
||||||
|
margin-top: 22px;
|
||||||
|
}
|
||||||
|
.sentry-block {
|
||||||
|
display: block;
|
||||||
|
width: 319px;
|
||||||
|
margin: 17px auto 10px;
|
||||||
|
padding: 20px 25px;
|
||||||
|
float: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-image: url('../images/sentry2.png');
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-size: cover;
|
||||||
|
font-family: Vollkorn, serif;
|
||||||
|
color: white;
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 33px;
|
||||||
|
font-weight: 400;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.samples {
|
||||||
|
top: -6px;
|
||||||
|
}
|
||||||
|
.sliderexamples {
|
||||||
|
width: auto;
|
||||||
|
height: 558px;
|
||||||
|
background-color: #272822;
|
||||||
|
}
|
||||||
|
html.w-mod-js.w-mod-no-ios *[data-ix="slicefromleft"] {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transform: translate(-100px, 0px);
|
||||||
|
-ms-transform: translate(-100px, 0px);
|
||||||
|
-o-transform: translate(-100px, 0px);
|
||||||
|
transform: translate(-100px, 0px);
|
||||||
|
}
|
||||||
|
html.w-mod-js *[data-ix="fadein"] {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transform: scale(0.5) rotate(-25deg);
|
||||||
|
-ms-transform: scale(0.5) rotate(-25deg);
|
||||||
|
-o-transform: scale(0.5) rotate(-25deg);
|
||||||
|
transform: scale(0.5) rotate(-25deg);
|
||||||
|
}
|
||||||
|
html.w-mod-js.w-mod-no-ios *[data-ix="slicefromright"] {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transform: translate(100px, 0px);
|
||||||
|
-ms-transform: translate(100px, 0px);
|
||||||
|
-o-transform: translate(100px, 0px);
|
||||||
|
transform: translate(100px, 0px);
|
||||||
|
}
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
.button.nav {
|
||||||
|
margin-left: 11px;
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
.button.big-green {
|
||||||
|
margin-top: 148px;
|
||||||
|
}
|
||||||
|
.section.hero {
|
||||||
|
padding-right: 36%;
|
||||||
|
}
|
||||||
|
.nav-link {
|
||||||
|
margin-right: 7px;
|
||||||
|
margin-left: 7px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.hero-heading {
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
.grey-icon {
|
||||||
|
margin-top: 19px;
|
||||||
|
}
|
||||||
|
.content-column {
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
.circle {
|
||||||
|
height: 293px;
|
||||||
|
}
|
||||||
|
.frames {
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.button {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.button.nav {
|
||||||
|
display: block;
|
||||||
|
width: 60%;
|
||||||
|
margin: 23px auto 17px;
|
||||||
|
}
|
||||||
|
.button.big-green {
|
||||||
|
margin-top: 86px;
|
||||||
|
margin-bottom: 62px;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
padding: 32px 15px;
|
||||||
|
}
|
||||||
|
.section.hero {
|
||||||
|
padding-top: 91px;
|
||||||
|
}
|
||||||
|
.section.footer {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.company {
|
||||||
|
display: inline-block;
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
.nav-column {
|
||||||
|
padding-top: 19px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
margin-top: -14px;
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
.company-column {
|
||||||
|
margin-top: 35px;
|
||||||
|
margin-bottom: 23px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.hero-heading {
|
||||||
|
margin-bottom: 26px;
|
||||||
|
font-size: 65px;
|
||||||
|
line-height: 34px;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
.grey-icon {
|
||||||
|
margin-top: 28px;
|
||||||
|
}
|
||||||
|
.content-column {
|
||||||
|
padding-left: 0px;
|
||||||
|
}
|
||||||
|
.circle {
|
||||||
|
display: inline-block;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.frames {
|
||||||
|
width: 55%;
|
||||||
|
margin-bottom: 19px;
|
||||||
|
}
|
||||||
|
.number {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.quote-box {
|
||||||
|
background-image: url('../images/12-quotes.png');
|
||||||
|
}
|
||||||
|
.by-section.first {
|
||||||
|
margin-bottom: 35px;
|
||||||
|
}
|
||||||
|
.logo-bottom {
|
||||||
|
margin-right: -1px;
|
||||||
|
margin-bottom: 37px;
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
.footer-text {
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
.social-icon.first {
|
||||||
|
margin-left: 1px;
|
||||||
|
}
|
||||||
|
.right-footer-col {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.brand-column {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 479px) {
|
||||||
|
.button {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.button.nav {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
.button.big-green {
|
||||||
|
margin-top: 0px;
|
||||||
|
margin-right: 16px;
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
.button.sentrybutton {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 19px;
|
||||||
|
}
|
||||||
|
.button.sentrybutton:hover {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
padding-right: 11px;
|
||||||
|
}
|
||||||
|
.section.hero {
|
||||||
|
padding-top: 85px;
|
||||||
|
padding-right: 15px;
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
|
.nav-link {
|
||||||
|
display: block;
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
.hero-heading {
|
||||||
|
margin-top: 0px;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
.hero-subhead {
|
||||||
|
margin-bottom: 34px;
|
||||||
|
}
|
||||||
|
.grey-icon {
|
||||||
|
margin-top: 34px;
|
||||||
|
}
|
||||||
|
.content-column {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.frames {
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
.quote-box {
|
||||||
|
padding-left: 75px;
|
||||||
|
background-image: url('../images/12-quotes.png');
|
||||||
|
background-position: 8% 17%;
|
||||||
|
}
|
||||||
|
}
|
356
css/normalize.css
vendored
Executable file
@ -0,0 +1,356 @@
|
|||||||
|
/*! normalize.css v2.1.3 | MIT License | git.io/normalize */
|
||||||
|
/* ==========================================================================
|
||||||
|
HTML5 display definitions
|
||||||
|
========================================================================== */
|
||||||
|
/**
|
||||||
|
* Correct `block` display not defined in IE 8/9.
|
||||||
|
*/
|
||||||
|
article,
|
||||||
|
aside,
|
||||||
|
details,
|
||||||
|
figcaption,
|
||||||
|
figure,
|
||||||
|
footer,
|
||||||
|
header,
|
||||||
|
hgroup,
|
||||||
|
main,
|
||||||
|
nav,
|
||||||
|
section,
|
||||||
|
summary {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Correct `inline-block` display not defined in IE 8/9.
|
||||||
|
*/
|
||||||
|
audio,
|
||||||
|
canvas,
|
||||||
|
video {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Prevent modern browsers from displaying `audio` without controls.
|
||||||
|
* Remove excess height in iOS 5 devices.
|
||||||
|
*/
|
||||||
|
audio:not([controls]) {
|
||||||
|
display: none;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Address `[hidden]` styling not present in IE 8/9.
|
||||||
|
* Hide the `template` element in IE, Safari, and Firefox < 22.
|
||||||
|
*/
|
||||||
|
[hidden],
|
||||||
|
template {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
/* ==========================================================================
|
||||||
|
Base
|
||||||
|
========================================================================== */
|
||||||
|
/**
|
||||||
|
* 1. Set default font family to sans-serif.
|
||||||
|
* 2. Prevent iOS text size adjust after orientation change, without disabling
|
||||||
|
* user zoom.
|
||||||
|
*/
|
||||||
|
html {
|
||||||
|
font-family: sans-serif;
|
||||||
|
/* 1 */
|
||||||
|
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
/* 2 */
|
||||||
|
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
/* 2 */
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Remove default margin.
|
||||||
|
*/
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
/* ==========================================================================
|
||||||
|
Links
|
||||||
|
========================================================================== */
|
||||||
|
/**
|
||||||
|
* Remove the gray background color from active links in IE 10.
|
||||||
|
*/
|
||||||
|
a {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Address `outline` inconsistency between Chrome and other browsers.
|
||||||
|
*/
|
||||||
|
a:focus {
|
||||||
|
outline: thin dotted;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Improve readability when focused and also mouse hovered in all browsers.
|
||||||
|
*/
|
||||||
|
a:active,
|
||||||
|
a:hover {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
/* ==========================================================================
|
||||||
|
Typography
|
||||||
|
========================================================================== */
|
||||||
|
/**
|
||||||
|
* Address variable `h1` font-size and margin within `section` and `article`
|
||||||
|
* contexts in Firefox 4+, Safari 5, and Chrome.
|
||||||
|
*/
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
margin: 0.67em 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Address styling not present in IE 8/9, Safari 5, and Chrome.
|
||||||
|
*/
|
||||||
|
abbr[title] {
|
||||||
|
border-bottom: 1px dotted;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
|
||||||
|
*/
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Address styling not present in Safari 5 and Chrome.
|
||||||
|
*/
|
||||||
|
dfn {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Address differences between Firefox and other browsers.
|
||||||
|
*/
|
||||||
|
hr {
|
||||||
|
-moz-box-sizing: content-box;
|
||||||
|
box-sizing: content-box;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Address styling not present in IE 8/9.
|
||||||
|
*/
|
||||||
|
mark {
|
||||||
|
background: #ff0;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Correct font family set oddly in Safari 5 and Chrome.
|
||||||
|
*/
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
pre,
|
||||||
|
samp {
|
||||||
|
font-family: monospace, serif;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Improve readability of pre-formatted text in all browsers.
|
||||||
|
*/
|
||||||
|
pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Set consistent quote types.
|
||||||
|
*/
|
||||||
|
q {
|
||||||
|
quotes: "\201C" "\201D" "\2018" "\2019";
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Address inconsistent and variable font size in all browsers.
|
||||||
|
*/
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
|
||||||
|
*/
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
/* ==========================================================================
|
||||||
|
Embedded content
|
||||||
|
========================================================================== */
|
||||||
|
/**
|
||||||
|
* Remove border when inside `a` element in IE 8/9.
|
||||||
|
*/
|
||||||
|
img {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Correct overflow displayed oddly in IE 9.
|
||||||
|
*/
|
||||||
|
svg:not(:root) {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
/* ==========================================================================
|
||||||
|
Figures
|
||||||
|
========================================================================== */
|
||||||
|
/**
|
||||||
|
* Address margin not present in IE 8/9 and Safari 5.
|
||||||
|
*/
|
||||||
|
figure {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
/* ==========================================================================
|
||||||
|
Forms
|
||||||
|
========================================================================== */
|
||||||
|
/**
|
||||||
|
* Define consistent border, margin, and padding.
|
||||||
|
*/
|
||||||
|
fieldset {
|
||||||
|
border: 1px solid #c0c0c0;
|
||||||
|
margin: 0 2px;
|
||||||
|
padding: 0.35em 0.625em 0.75em;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 1. Correct `color` not being inherited in IE 8/9.
|
||||||
|
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
|
||||||
|
*/
|
||||||
|
legend {
|
||||||
|
border: 0;
|
||||||
|
/* 1 */
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
/* 2 */
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 1. Correct font family not being inherited in all browsers.
|
||||||
|
* 2. Correct font size not being inherited in all browsers.
|
||||||
|
* 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
|
||||||
|
*/
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
/* 1 */
|
||||||
|
|
||||||
|
font-size: 100%;
|
||||||
|
/* 2 */
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
/* 3 */
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
|
||||||
|
* the UA stylesheet.
|
||||||
|
*/
|
||||||
|
button,
|
||||||
|
input {
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Address inconsistent `text-transform` inheritance for `button` and `select`.
|
||||||
|
* All other form control elements do not inherit `text-transform` values.
|
||||||
|
* Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
|
||||||
|
* Correct `select` style inheritance in Firefox 4+ and Opera.
|
||||||
|
*/
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
|
||||||
|
* and `video` controls.
|
||||||
|
* 2. Correct inability to style clickable `input` types in iOS.
|
||||||
|
* 3. Improve usability and consistency of cursor style between image-type
|
||||||
|
* `input` and others.
|
||||||
|
*/
|
||||||
|
button,
|
||||||
|
html input[type="button"],
|
||||||
|
input[type="reset"],
|
||||||
|
input[type="submit"] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
/* 2 */
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
/* 3 */
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Re-set default cursor for disabled elements.
|
||||||
|
*/
|
||||||
|
button[disabled],
|
||||||
|
html input[disabled] {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 1. Address box sizing set to `content-box` in IE 8/9/10.
|
||||||
|
* 2. Remove excess padding in IE 8/9/10.
|
||||||
|
*/
|
||||||
|
input[type="checkbox"],
|
||||||
|
input[type="radio"] {
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* 1 */
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
/* 2 */
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
|
||||||
|
* 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
|
||||||
|
* (include `-moz` to future-proof).
|
||||||
|
*/
|
||||||
|
input[type="search"] {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
/* 1 */
|
||||||
|
|
||||||
|
-moz-box-sizing: content-box;
|
||||||
|
-webkit-box-sizing: content-box;
|
||||||
|
/* 2 */
|
||||||
|
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Remove inner padding and search cancel button in Safari 5 and Chrome
|
||||||
|
* on OS X.
|
||||||
|
*/
|
||||||
|
input[type="search"]::-webkit-search-cancel-button,
|
||||||
|
input[type="search"]::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Remove inner padding and border in Firefox 4+.
|
||||||
|
*/
|
||||||
|
button::-moz-focus-inner,
|
||||||
|
input::-moz-focus-inner {
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 1. Remove default vertical scrollbar in IE 8/9.
|
||||||
|
* 2. Improve readability and alignment in all browsers.
|
||||||
|
*/
|
||||||
|
textarea {
|
||||||
|
overflow: auto;
|
||||||
|
/* 1 */
|
||||||
|
|
||||||
|
vertical-align: top;
|
||||||
|
/* 2 */
|
||||||
|
|
||||||
|
}
|
||||||
|
/* ==========================================================================
|
||||||
|
Tables
|
||||||
|
========================================================================== */
|
||||||
|
/**
|
||||||
|
* Remove most spacing between table cells.
|
||||||
|
*/
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
1225
css/webflow.css
Executable file
@ -1,55 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
var DB = make(map[string]string)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
r := gin.Default()
|
|
||||||
|
|
||||||
// Ping test
|
|
||||||
r.GET("/ping", func(c *gin.Context) {
|
|
||||||
c.String(200, "pong")
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get user value
|
|
||||||
r.GET("/user/:name", func(c *gin.Context) {
|
|
||||||
user := c.Params.ByName("name")
|
|
||||||
value, ok := DB[user]
|
|
||||||
if ok {
|
|
||||||
c.JSON(200, gin.H{"user": user, "value": value})
|
|
||||||
} else {
|
|
||||||
c.JSON(200, gin.H{"user": user, "status": "no value"})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Authorized group (uses gin.BasicAuth() middleware)
|
|
||||||
// Same than:
|
|
||||||
// authorized := r.Group("/")
|
|
||||||
// authorized.Use(gin.BasicAuth(gin.Credentials{
|
|
||||||
// "foo": "bar",
|
|
||||||
// "manu": "123",
|
|
||||||
//}))
|
|
||||||
authorized := r.Group("/", gin.BasicAuth(gin.Accounts{
|
|
||||||
{"foo", "bar"}, //1. user:foo password:bar
|
|
||||||
{"manu", "123"}, //2. user:manu password:123
|
|
||||||
}))
|
|
||||||
|
|
||||||
authorized.POST("admin", func(c *gin.Context) {
|
|
||||||
user := c.Get("user").(string)
|
|
||||||
|
|
||||||
// Parse JSON
|
|
||||||
var json struct {
|
|
||||||
Value string `json:"value" binding:"required"`
|
|
||||||
}
|
|
||||||
if c.EnsureBody(&json) {
|
|
||||||
DB[user] = json.Value
|
|
||||||
c.JSON(200, gin.H{"status": "ok"})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Listen and Server in 0.0.0.0:8080
|
|
||||||
r.Run(":8081")
|
|
||||||
}
|
|
377
gin.go
@ -1,377 +0,0 @@
|
|||||||
package gin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/xml"
|
|
||||||
"github.com/julienschmidt/httprouter"
|
|
||||||
"html/template"
|
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
"net/http"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
AbortIndex = math.MaxInt8 / 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
HandlerFunc func(*Context)
|
|
||||||
|
|
||||||
H map[string]interface{}
|
|
||||||
|
|
||||||
// Used internally to collect a error ocurred during a http request.
|
|
||||||
ErrorMsg struct {
|
|
||||||
Message string `json:"msg"`
|
|
||||||
Meta interface{} `json:"meta"`
|
|
||||||
}
|
|
||||||
|
|
||||||
ResponseWriter interface {
|
|
||||||
http.ResponseWriter
|
|
||||||
Status() int
|
|
||||||
Written() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
responseWriter struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
status int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Context is the most important part of gin. It allows us to pass variables between middleware,
|
|
||||||
// manage the flow, validate the JSON of a request and render a JSON response for example.
|
|
||||||
Context struct {
|
|
||||||
Req *http.Request
|
|
||||||
Writer ResponseWriter
|
|
||||||
Keys map[string]interface{}
|
|
||||||
Errors []ErrorMsg
|
|
||||||
Params httprouter.Params
|
|
||||||
handlers []HandlerFunc
|
|
||||||
engine *Engine
|
|
||||||
index int8
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used internally to configure router, a RouterGroup is associated with a prefix
|
|
||||||
// and an array of handlers (middlewares)
|
|
||||||
RouterGroup struct {
|
|
||||||
Handlers []HandlerFunc
|
|
||||||
prefix string
|
|
||||||
parent *RouterGroup
|
|
||||||
engine *Engine
|
|
||||||
}
|
|
||||||
|
|
||||||
// Represents the web framework, it wrappers the blazing fast httprouter multiplexer and a list of global middlewares.
|
|
||||||
Engine struct {
|
|
||||||
*RouterGroup
|
|
||||||
handlers404 []HandlerFunc
|
|
||||||
router *httprouter.Router
|
|
||||||
HTMLTemplates *template.Template
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (rw *responseWriter) WriteHeader(s int) {
|
|
||||||
rw.ResponseWriter.WriteHeader(s)
|
|
||||||
rw.status = s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *responseWriter) Write(b []byte) (int, error) {
|
|
||||||
return rw.ResponseWriter.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *responseWriter) Status() int {
|
|
||||||
return rw.status
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *responseWriter) Written() bool {
|
|
||||||
return rw.status != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a new blank Engine instance without any middleware attached.
|
|
||||||
// The most basic configuration
|
|
||||||
func New() *Engine {
|
|
||||||
engine := &Engine{}
|
|
||||||
engine.RouterGroup = &RouterGroup{nil, "/", nil, engine}
|
|
||||||
engine.router = httprouter.New()
|
|
||||||
engine.router.NotFound = engine.handle404
|
|
||||||
return engine
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a Engine instance with the Logger and Recovery already attached.
|
|
||||||
func Default() *Engine {
|
|
||||||
engine := New()
|
|
||||||
engine.Use(Recovery(), Logger())
|
|
||||||
return engine
|
|
||||||
}
|
|
||||||
|
|
||||||
func (engine *Engine) LoadHTMLTemplates(pattern string) {
|
|
||||||
engine.HTMLTemplates = template.Must(template.ParseGlob(pattern))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds handlers for NotFound. It return a 404 code by default.
|
|
||||||
func (engine *Engine) NotFound404(handlers ...HandlerFunc) {
|
|
||||||
engine.handlers404 = handlers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (engine *Engine) handle404(w http.ResponseWriter, req *http.Request) {
|
|
||||||
|
|
||||||
handlers := engine.allHandlers(engine.handlers404)
|
|
||||||
c := engine.createContext(w, req, nil, handlers)
|
|
||||||
c.Next()
|
|
||||||
if !c.Writer.Written() {
|
|
||||||
http.NotFound(c.Writer, c.Req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeFiles serves files from the given file system root.
|
|
||||||
// The path must end with "/*filepath", files are then served from the local
|
|
||||||
// path /defined/root/dir/*filepath.
|
|
||||||
// For example if root is "/etc" and *filepath is "passwd", the local file
|
|
||||||
// "/etc/passwd" would be served.
|
|
||||||
// Internally a http.FileServer is used, therefore http.NotFound is used instead
|
|
||||||
// of the Router's NotFound handler.
|
|
||||||
// To use the operating system's file system implementation,
|
|
||||||
// use http.Dir:
|
|
||||||
// router.ServeFiles("/src/*filepath", http.Dir("/var/www"))
|
|
||||||
func (engine *Engine) ServeFiles(path string, root http.FileSystem) {
|
|
||||||
engine.router.ServeFiles(path, root)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP makes the router implement the http.Handler interface.
|
|
||||||
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
||||||
engine.router.ServeHTTP(w, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (engine *Engine) Run(addr string) {
|
|
||||||
http.ListenAndServe(addr, engine)
|
|
||||||
}
|
|
||||||
|
|
||||||
/************************************/
|
|
||||||
/********** ROUTES GROUPING *********/
|
|
||||||
/************************************/
|
|
||||||
|
|
||||||
func (group *RouterGroup) createContext(w http.ResponseWriter, req *http.Request, params httprouter.Params, handlers []HandlerFunc) *Context {
|
|
||||||
return &Context{
|
|
||||||
Writer: &responseWriter{w, 0},
|
|
||||||
Req: req,
|
|
||||||
index: -1,
|
|
||||||
engine: group.engine,
|
|
||||||
Params: params,
|
|
||||||
handlers: handlers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds middlewares to the group, see example code in github.
|
|
||||||
func (group *RouterGroup) Use(middlewares ...HandlerFunc) {
|
|
||||||
group.Handlers = append(group.Handlers, middlewares...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Greates a new router group. You should create add all the routes that share that have common middlwares or same path prefix.
|
|
||||||
// For example, all the routes that use a common middlware for authorization could be grouped.
|
|
||||||
func (group *RouterGroup) Group(component string, handlers ...HandlerFunc) *RouterGroup {
|
|
||||||
prefix := path.Join(group.prefix, component)
|
|
||||||
return &RouterGroup{
|
|
||||||
Handlers: handlers,
|
|
||||||
parent: group,
|
|
||||||
prefix: prefix,
|
|
||||||
engine: group.engine,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle registers a new request handle and middlewares with the given path and method.
|
|
||||||
// The last handler should be the real handler, the other ones should be middlewares that can and should be shared among different routes.
|
|
||||||
// See the example code in github.
|
|
||||||
//
|
|
||||||
// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
|
|
||||||
// functions can be used.
|
|
||||||
//
|
|
||||||
// This function is intended for bulk loading and to allow the usage of less
|
|
||||||
// frequently used, non-standardized or custom methods (e.g. for internal
|
|
||||||
// communication with a proxy).
|
|
||||||
func (group *RouterGroup) Handle(method, p string, handlers []HandlerFunc) {
|
|
||||||
p = path.Join(group.prefix, p)
|
|
||||||
handlers = group.allHandlers(handlers)
|
|
||||||
group.engine.router.Handle(method, p, func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
|
||||||
group.createContext(w, req, params, handlers).Next()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST is a shortcut for router.Handle("POST", path, handle)
|
|
||||||
func (group *RouterGroup) POST(path string, handlers ...HandlerFunc) {
|
|
||||||
group.Handle("POST", path, handlers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET is a shortcut for router.Handle("GET", path, handle)
|
|
||||||
func (group *RouterGroup) GET(path string, handlers ...HandlerFunc) {
|
|
||||||
group.Handle("GET", path, handlers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE is a shortcut for router.Handle("DELETE", path, handle)
|
|
||||||
func (group *RouterGroup) DELETE(path string, handlers ...HandlerFunc) {
|
|
||||||
group.Handle("DELETE", path, handlers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PATCH is a shortcut for router.Handle("PATCH", path, handle)
|
|
||||||
func (group *RouterGroup) PATCH(path string, handlers ...HandlerFunc) {
|
|
||||||
group.Handle("PATCH", path, handlers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PUT is a shortcut for router.Handle("PUT", path, handle)
|
|
||||||
func (group *RouterGroup) PUT(path string, handlers ...HandlerFunc) {
|
|
||||||
group.Handle("PUT", path, handlers)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (group *RouterGroup) allHandlers(handlers []HandlerFunc) []HandlerFunc {
|
|
||||||
local := append(group.Handlers, handlers...)
|
|
||||||
if group.parent != nil {
|
|
||||||
return group.parent.allHandlers(local)
|
|
||||||
} else {
|
|
||||||
return local
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/************************************/
|
|
||||||
/****** FLOW AND ERROR MANAGEMENT****/
|
|
||||||
/************************************/
|
|
||||||
|
|
||||||
// Next should be used only in the middlewares.
|
|
||||||
// It executes the pending handlers in the chain inside the calling handler.
|
|
||||||
// See example in github.
|
|
||||||
func (c *Context) Next() {
|
|
||||||
c.index++
|
|
||||||
s := int8(len(c.handlers))
|
|
||||||
for ; c.index < s; c.index++ {
|
|
||||||
c.handlers[c.index](c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forces the system to do not continue calling the pending handlers.
|
|
||||||
// For example, the first handler checks if the request is authorized. If it's not, context.Abort(401) should be called.
|
|
||||||
// The rest of pending handlers would never be called for that request.
|
|
||||||
func (c *Context) Abort(code int) {
|
|
||||||
c.Writer.WriteHeader(code)
|
|
||||||
c.index = AbortIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail is the same than Abort plus an error message.
|
|
||||||
// Calling `context.Fail(500, err)` is equivalent to:
|
|
||||||
// ```
|
|
||||||
// context.Error("Operation aborted", err)
|
|
||||||
// context.Abort(500)
|
|
||||||
// ```
|
|
||||||
func (c *Context) Fail(code int, err error) {
|
|
||||||
c.Error(err, "Operation aborted")
|
|
||||||
c.Abort(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attachs an error to the current context. The error is pushed to a list of errors.
|
|
||||||
// It's a gooc idea to call Error for each error ocurred 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.
|
|
||||||
func (c *Context) Error(err error, meta interface{}) {
|
|
||||||
c.Errors = append(c.Errors, ErrorMsg{
|
|
||||||
Message: err.Error(),
|
|
||||||
Meta: meta,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/************************************/
|
|
||||||
/******** METADATA MANAGEMENT********/
|
|
||||||
/************************************/
|
|
||||||
|
|
||||||
// Sets a new pair key/value just for the specefied context.
|
|
||||||
// It also lazy initializes the hashmap
|
|
||||||
func (c *Context) Set(key string, item interface{}) {
|
|
||||||
if c.Keys == nil {
|
|
||||||
c.Keys = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
c.Keys[key] = item
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the value for the given key.
|
|
||||||
// It panics if the value doesn't exist.
|
|
||||||
func (c *Context) Get(key string) interface{} {
|
|
||||||
var ok bool
|
|
||||||
var item interface{}
|
|
||||||
if c.Keys != nil {
|
|
||||||
item, ok = c.Keys[key]
|
|
||||||
} else {
|
|
||||||
item, ok = nil, false
|
|
||||||
}
|
|
||||||
if !ok || item == nil {
|
|
||||||
log.Panicf("Key %s doesn't exist", key)
|
|
||||||
}
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
|
|
||||||
/************************************/
|
|
||||||
/******** ENCOGING MANAGEMENT********/
|
|
||||||
/************************************/
|
|
||||||
|
|
||||||
// Like ParseBody() but this method also writes a 400 error if the json is not valid.
|
|
||||||
func (c *Context) EnsureBody(item interface{}) bool {
|
|
||||||
if err := c.ParseBody(item); err != nil {
|
|
||||||
c.Fail(400, err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parses the body content as a JSON input. It decodes the json payload into the struct specified as a pointer.
|
|
||||||
func (c *Context) ParseBody(item interface{}) error {
|
|
||||||
decoder := json.NewDecoder(c.Req.Body)
|
|
||||||
if err := decoder.Decode(&item); err == nil {
|
|
||||||
return Validate(c, item)
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serializes the given struct as a JSON into the response body in a fast and efficient way.
|
|
||||||
// It also sets the Content-Type as "application/json"
|
|
||||||
func (c *Context) JSON(code int, obj interface{}) {
|
|
||||||
c.Writer.WriteHeader(code)
|
|
||||||
c.Writer.Header().Set("Content-Type", "application/json")
|
|
||||||
encoder := json.NewEncoder(c.Writer)
|
|
||||||
if err := encoder.Encode(obj); err != nil {
|
|
||||||
c.Error(err, obj)
|
|
||||||
http.Error(c.Writer, err.Error(), 500)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serializes the given struct as a XML into the response body in a fast and efficient way.
|
|
||||||
// It also sets the Content-Type as "application/xml"
|
|
||||||
func (c *Context) XML(code int, obj interface{}) {
|
|
||||||
c.Writer.WriteHeader(code)
|
|
||||||
c.Writer.Header().Set("Content-Type", "application/xml")
|
|
||||||
encoder := xml.NewEncoder(c.Writer)
|
|
||||||
if err := encoder.Encode(obj); err != nil {
|
|
||||||
c.Error(err, obj)
|
|
||||||
http.Error(c.Writer, err.Error(), 500)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renders the HTTP template specified by his file name.
|
|
||||||
// It also update the HTTP code and sets the Content-Type as "text/html".
|
|
||||||
// See http://golang.org/doc/articles/wiki/
|
|
||||||
func (c *Context) HTML(code int, name string, data interface{}) {
|
|
||||||
c.Writer.WriteHeader(code)
|
|
||||||
c.Writer.Header().Set("Content-Type", "text/html")
|
|
||||||
if err := c.engine.HTMLTemplates.ExecuteTemplate(c.Writer, name, data); err != nil {
|
|
||||||
c.Error(err, map[string]interface{}{
|
|
||||||
"name": name,
|
|
||||||
"data": data,
|
|
||||||
})
|
|
||||||
http.Error(c.Writer, err.Error(), 500)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writes the given string into the response body and sets the Content-Type to "text/plain"
|
|
||||||
func (c *Context) String(code int, msg string) {
|
|
||||||
c.Writer.Header().Set("Content-Type", "text/plain")
|
|
||||||
c.Writer.WriteHeader(code)
|
|
||||||
c.Writer.Write([]byte(msg))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writes some data into the body stream and updates the HTTP code
|
|
||||||
func (c *Context) Data(code int, data []byte) {
|
|
||||||
c.Writer.WriteHeader(code)
|
|
||||||
c.Writer.Write(data)
|
|
||||||
}
|
|
BIN
images/12-quotes.png
Executable file
After Width: | Height: | Size: 1.5 KiB |
BIN
images/Git-Icon-Black2.png
Executable file
After Width: | Height: | Size: 2.5 KiB |
BIN
images/email.png
Executable file
After Width: | Height: | Size: 1.4 KiB |
BIN
images/gin.jpg
Executable file
After Width: | Height: | Size: 105 KiB |
BIN
images/globe2.png
Executable file
After Width: | Height: | Size: 2.7 KiB |
BIN
images/graph.png
Executable file
After Width: | Height: | Size: 56 KiB |
BIN
images/idea2.png
Executable file
After Width: | Height: | Size: 3.7 KiB |
BIN
images/pull2.png
Executable file
After Width: | Height: | Size: 6.5 KiB |
BIN
images/sample1.png
Executable file
After Width: | Height: | Size: 39 KiB |
BIN
images/sample11.png
Executable file
After Width: | Height: | Size: 21 KiB |
BIN
images/sentry2.png
Executable file
After Width: | Height: | Size: 2.2 KiB |
BIN
images/webclip-gallio.png
Executable file
After Width: | Height: | Size: 3.2 KiB |
184
index.html
Executable file
@ -0,0 +1,184 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- This site was created in Webflow. http://www.webflow.com-->
|
||||||
|
<!-- Last Published: Fri Jun 20 2014 17:48:26 GMT+0000 (UTC) -->
|
||||||
|
<html data-wf-site="539b89a7a7990e780bfd7c67">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Gin Web Framework</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="generator" content="Webflow">
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/normalize.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/webflow.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/gin.webflow.css">
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.4.7/webfont.js"></script>
|
||||||
|
<script>
|
||||||
|
WebFont.load({
|
||||||
|
google: {
|
||||||
|
families: ["Montserrat:400,700","Varela Round:400","Great Vibes:400","Varela:400","Open Sans:300,300italic,400,400italic,600,600italic,700,700italic,800,800italic","Vollkorn:400,400italic,700,700italic"]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="js/modernizr.js"></script>
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="https://y7v4p6k4.ssl.hwcdn.net/placeholder/favicon.ico">
|
||||||
|
<link rel="apple-touch-icon" href="images/webclip-gallio.png">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="section hero">
|
||||||
|
<div class="w-container presenting" data-ix="fadein">
|
||||||
|
<h1 class="hero-heading">Gin Gonic</h1>
|
||||||
|
<p class="hero-subhead">Fastest full-featured web framework for Golang. <strong>Crystal clear.</strong>
|
||||||
|
</p>
|
||||||
|
<div class="button-group"><a class="button sign-up" href="https://github.com/gin-gonic/gin" data-ix="shaking">github pagE</a><a class="button" href="http://godoc.org/github.com/gin-gonic/gin">API REFERENCE</a>
|
||||||
|
<p class="goget"><span class="goget-text">go get github.com/gin-gonic/gin</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section" id="mobile">
|
||||||
|
<div class="w-container">
|
||||||
|
<h2 class="section-title">Performance and productivity can work together</h2>
|
||||||
|
<p class="section-subtitle">Gin is a web framework written in Golang. It features a martini-like API with much better performance, up to 40 times faster. If you need performance and good productivity, you will love Gin.</p>
|
||||||
|
<div class="graph-block">
|
||||||
|
<img class="graph-image" src="images/graph.png" alt="Martini vs Gin" width="827">
|
||||||
|
<p class="footgraph"><a href="https://github.com/gin-gonic/go-http-routing-benchmark">Run the tests by yourself</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section grey" id="features">
|
||||||
|
<div class="w-container" data-ix="slicefromleft">
|
||||||
|
<h2 class="section-title">Low Overhead Powerful API</h2>
|
||||||
|
<p class="section-subtitle">You can add global, per-group, and per-route middlewares, thousands of nested groups, nice JSON validation and rendering. And the performance will be still great! Gin uses <strong>httprouter</strong> internally, the fastest HTTP router for
|
||||||
|
Golang. Httprouter was created by <em>Julien Schmidt</em> and it’s based in a <a href="http://en.wikipedia.org/wiki/Radix_tree">Radix Tree</a> algorithm. This explains the good performance and scalability of Gin.</p>
|
||||||
|
<div class="w-row">
|
||||||
|
<div class="w-col w-col-6">
|
||||||
|
<h3>Some cool middlewares</h3>
|
||||||
|
<p>If you used Martini before, Gin will be familiar to you. If you don’t, you will need 10 minutes to learn everything.</p><a class="button sentrybutton" href="https://github.com/gin-gonic/gin-sentry">Check out the<br>Sentry midDlEware</a>
|
||||||
|
<p>More coming soon!</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-col w-col-6">
|
||||||
|
<h3>Crystal Clear</h3>
|
||||||
|
<p>If you used Martini before, Gin will be familiar to you. If you don’t, you will need 10 minutes to learn everything.</p>
|
||||||
|
<div class="w-slider sliderexamples" data-animation="slide" data-duration="500" data-infinite="1" data-hide-arrows="1">
|
||||||
|
<div class="w-slider-mask">
|
||||||
|
<div class="w-slide">
|
||||||
|
<img src="images/sample1.png" width="550" alt="539f93b8f281585418675d91_sample1.png">
|
||||||
|
</div>
|
||||||
|
<div class="w-slide">
|
||||||
|
<img src="images/sample11.png" width="550" alt="539f93ccf281585418675d92_sample11.png">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-slider-arrow-left">
|
||||||
|
<div class="w-icon-slider-left"></div>
|
||||||
|
</div>
|
||||||
|
<div class="w-slider-arrow-right">
|
||||||
|
<div class="w-icon-slider-right"></div>
|
||||||
|
</div>
|
||||||
|
<div class="w-slider-nav w-round"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section centered">
|
||||||
|
<div class="w-container" data-ix="slicefromright">
|
||||||
|
<h2 class="section-title">Full Featured</h2>
|
||||||
|
<div class="w-row">
|
||||||
|
<div class="w-col w-col-4 w-col-small-4">
|
||||||
|
<div class="icons">I</div>
|
||||||
|
<h3>Fast</h3>
|
||||||
|
<p>Radix tree based routing, small memory foot print. No reflection. Predictable API performance. </p>
|
||||||
|
</div>
|
||||||
|
<div class="w-col w-col-4 w-col-small-4">
|
||||||
|
<div class="icons">II</div>
|
||||||
|
<h3>Middleware support</h3>
|
||||||
|
<p>A incoming HTTP request can by handled by a chain of middlewares and the final action.
|
||||||
|
<br>For example: Logger, Authorization, GZIP and finally post a message in the DB.</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-col w-col-4 w-col-small-4">
|
||||||
|
<div class="icons">III</div>
|
||||||
|
<h3>Crash-free</h3>
|
||||||
|
<p>Gin can catch a panic occurred during a HTTP request and recover it. This way, your server will be always available. It’s also possible to report this panic to Sentry for example!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-row">
|
||||||
|
<div class="w-col w-col-4 w-col-small-4">
|
||||||
|
<div class="icons">IV</div>
|
||||||
|
<h3>JSON validation</h3>
|
||||||
|
<p>Gin can parse and validate the JSON of a request, checking for example the existence of required values.</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-col w-col-4 w-col-small-4">
|
||||||
|
<div class="icons">V</div>
|
||||||
|
<h3>Routes grouping</h3>
|
||||||
|
<p>Organize your routes better. Authorization required vs non required, different API versions... In addition, the groups can be nested unlimitedly without degrading performance!</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-col w-col-4 w-col-small-4">
|
||||||
|
<div class="icons">VI</div>
|
||||||
|
<h3>Error management</h3>
|
||||||
|
<p>Gin provides a convenient way to collect all the errors occurred during a HTTP request. Eventually, a middleware can write them to a log file, to a database and send them through the network.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-row">
|
||||||
|
<div class="w-col w-col-4 w-col-small-4">
|
||||||
|
<div class="icons">VII</div>
|
||||||
|
<h3>Rendering built-in</h3>
|
||||||
|
<p>Gin provides a easy to use API for JSON, XML and HTML rendering.</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-col w-col-4 w-col-small-4">
|
||||||
|
<div class="icons">VIII</div>
|
||||||
|
<h3>Extendable</h3>
|
||||||
|
<p>Creating a new middleware is so easy, just check out the sample codes.</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-col w-col-4 w-col-small-4"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section grey" id="features">
|
||||||
|
<div class="w-container" data-ix="slicefromleft">
|
||||||
|
<h2 class="section-title">How to contribute?</h2>
|
||||||
|
<p class="section-subtitle">Gin uses a MIT license, this means that you can do whatever you want, but please, keep the reference to the original authors! To contribute you should <a href="https://github.com/gin-gonic/gin">fork it in Github</a>, add some changes and start posting
|
||||||
|
Pull Requests, we would love to merge them.</p>
|
||||||
|
<div class="w-row">
|
||||||
|
<div class="w-col w-col-4">
|
||||||
|
<div class="circle">
|
||||||
|
<div class="number">1. Fork</div>
|
||||||
|
<img class="frames" src="images/Git-Icon-Black2.png" width="175" alt="539e4fca30eda3837a903182_Git-Icon-Black2.png">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-col w-col-4">
|
||||||
|
<div class="circle">
|
||||||
|
<div class="number">2. Commit</div>
|
||||||
|
<img class="frames" src="images/idea2.png" width="134" height="169" alt="539e4fbbaa3db5690ebc0c8c_idea2.png">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-col w-col-4">
|
||||||
|
<div class="circle">
|
||||||
|
<div class="number">3. Pull request</div>
|
||||||
|
<img class="frames" src="images/pull2.png" width="218" alt="539e4fadaa3db5690ebc0c8b_pull2.png">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer class="section footer">
|
||||||
|
<div class="w-container">
|
||||||
|
<div class="w-row">
|
||||||
|
<div class="w-col w-col-6">
|
||||||
|
<div class="footer-text">Gin is developed and maintained by <em>Manu Martinez-Almeida</em>. It uses the fantastic <em>Julien Schmidt</em>’s httprouter.</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-col w-col-6 right-footer-col">
|
||||||
|
<a class="w-inline-block social-icon" href="mailto:manu.valladolid@gmail.com">
|
||||||
|
<img src="images/email.png" width="30" alt="539f9748f281585418675de9_email.png">
|
||||||
|
</a>
|
||||||
|
<a class="w-inline-block social-icon" href="http://forzefield.com">
|
||||||
|
<img src="images/globe2.png" width="30" alt="539f982eb9e5e952181edbc2_globe2.png">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
|
||||||
|
<script type="text/javascript" src="js/webflow.js"></script>
|
||||||
|
<!--[if lte IE 9]><script src="https://cdnjs.cloudflare.com/ajax/libs/placeholders/3.0.2/placeholders.min.js"></script><![endif]-->
|
||||||
|
</body>
|
||||||
|
</html>
|
8
js/modernizr.js
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
/* Modernizr 2.7.1 (Custom Build) | MIT & BSD
|
||||||
|
* Build: http://modernizr.com/download/#-video-touch-shiv-cssclasses-teststyles-prefixes-cssclassprefix:w!mod!
|
||||||
|
*/
|
||||||
|
;window.Modernizr=function(a,b,c){function w(a){j.cssText=a}function x(a,b){return w(m.join(a+";")+(b||""))}function y(a,b){return typeof a===b}function z(a,b){return!!~(""+a).indexOf(b)}function A(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:y(f,"function")?f.bind(d||b):f}return!1}var d="2.7.1",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n={},o={},p={},q=[],r=q.slice,s,t=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["­",'<style id="s',h,'">',a,"</style>"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},u={}.hasOwnProperty,v;!y(u,"undefined")&&!y(u.call,"undefined")?v=function(a,b){return u.call(a,b)}:v=function(a,b){return b in a&&y(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=r.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(r.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(r.call(arguments)))};return e}),n.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:t(["@media (",m.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},n.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c};for(var B in n)v(n,B)&&(s=B.toLowerCase(),e[s]=n[B](),q.push((e[s]?"":"no-")+s));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)v(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" w-mod-"+(b?"":"no-")+a),e[a]=b}return e},w(""),i=k=null,function(a,b){function l(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function m(){var a=s.elements;return typeof a=="string"?a.split(" "):a}function n(a){var b=j[a[h]];return b||(b={},i++,a[h]=i,j[i]=b),b}function o(a,c,d){c||(c=b);if(k)return c.createElement(a);d||(d=n(c));var g;return d.cache[a]?g=d.cache[a].cloneNode():f.test(a)?g=(d.cache[a]=d.createElem(a)).cloneNode():g=d.createElem(a),g.canHaveChildren&&!e.test(a)&&!g.tagUrn?d.frag.appendChild(g):g}function p(a,c){a||(a=b);if(k)return a.createDocumentFragment();c=c||n(a);var d=c.frag.cloneNode(),e=0,f=m(),g=f.length;for(;e<g;e++)d.createElement(f[e]);return d}function q(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return s.shivMethods?o(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/[\w\-]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(s,b.frag)}function r(a){a||(a=b);var c=n(a);return s.shivCSS&&!g&&!c.hasCSS&&(c.hasCSS=!!l(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),k||q(a,c),a}var c="3.7.0",d=a.html5||{},e=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,f=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,g,h="_html5shiv",i=0,j={},k;(function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",g="hidden"in a,k=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){g=!0,k=!0}})();var s={elements:d.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:c,shivCSS:d.shivCSS!==!1,supportsUnknownElements:k,shivMethods:d.shivMethods!==!1,type:"default",shivDocument:r,createElement:o,createDocumentFragment:p};a.html5=s,r(b)}(this,b),e._version=d,e._prefixes=m,e.testStyles=t,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" w-mod-js w-mod-"+q.join(" w-mod-"):""),e}(this,this.document);
|
||||||
|
/**
|
||||||
|
* Webflow: Custom tests
|
||||||
|
*/
|
||||||
|
Modernizr.addTest('ios', /(ipod|iphone|ipad)/i.test(navigator.userAgent));
|
2775
js/webflow.js
Executable file
20
logger.go
@ -1,20 +0,0 @@
|
|||||||
package gin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Logger() HandlerFunc {
|
|
||||||
return func(c *Context) {
|
|
||||||
|
|
||||||
// Start timer
|
|
||||||
t := time.Now()
|
|
||||||
|
|
||||||
// Process request
|
|
||||||
c.Next()
|
|
||||||
|
|
||||||
// Calculate resolution time
|
|
||||||
log.Printf("[%d] %s in %v", c.Writer.Status(), c.Req.RequestURI, time.Since(t))
|
|
||||||
}
|
|
||||||
}
|
|
97
recovery.go
@ -1,97 +0,0 @@
|
|||||||
package gin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
dunno = []byte("???")
|
|
||||||
centerDot = []byte("·")
|
|
||||||
dot = []byte(".")
|
|
||||||
slash = []byte("/")
|
|
||||||
)
|
|
||||||
|
|
||||||
// stack returns a nicely formated stack frame, skipping skip frames
|
|
||||||
func stack(skip int) []byte {
|
|
||||||
buf := new(bytes.Buffer) // the returned data
|
|
||||||
// As we loop, we open files and read them. These variables record the currently
|
|
||||||
// loaded file.
|
|
||||||
var lines [][]byte
|
|
||||||
var lastFile string
|
|
||||||
for i := skip; ; i++ { // Skip the expected number of frames
|
|
||||||
pc, file, line, ok := runtime.Caller(i)
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Print this much at least. If we can't find the source, it won't show.
|
|
||||||
fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
|
|
||||||
if file != lastFile {
|
|
||||||
data, err := ioutil.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
lines = bytes.Split(data, []byte{'\n'})
|
|
||||||
lastFile = file
|
|
||||||
}
|
|
||||||
fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
|
|
||||||
}
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// source returns a space-trimmed slice of the n'th line.
|
|
||||||
func source(lines [][]byte, n int) []byte {
|
|
||||||
n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
|
|
||||||
if n < 0 || n >= len(lines) {
|
|
||||||
return dunno
|
|
||||||
}
|
|
||||||
return bytes.TrimSpace(lines[n])
|
|
||||||
}
|
|
||||||
|
|
||||||
// function returns, if possible, the name of the function containing the PC.
|
|
||||||
func function(pc uintptr) []byte {
|
|
||||||
fn := runtime.FuncForPC(pc)
|
|
||||||
if fn == nil {
|
|
||||||
return dunno
|
|
||||||
}
|
|
||||||
name := []byte(fn.Name())
|
|
||||||
// The name includes the path name to the package, which is unnecessary
|
|
||||||
// since the file name is already included. Plus, it has center dots.
|
|
||||||
// That is, we see
|
|
||||||
// runtime/debug.*T·ptrmethod
|
|
||||||
// and want
|
|
||||||
// *T.ptrmethod
|
|
||||||
// Also the package path might contains dot (e.g. code.google.com/...),
|
|
||||||
// so first eliminate the path prefix
|
|
||||||
if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
|
|
||||||
name = name[lastslash+1:]
|
|
||||||
}
|
|
||||||
if period := bytes.Index(name, dot); period >= 0 {
|
|
||||||
name = name[period+1:]
|
|
||||||
}
|
|
||||||
name = bytes.Replace(name, centerDot, dot, -1)
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
|
|
||||||
// While Martini is in development mode, Recovery will also output the panic as HTML.
|
|
||||||
func Recovery() HandlerFunc {
|
|
||||||
return func(c *Context) {
|
|
||||||
defer func() {
|
|
||||||
if len(c.Errors) > 0 {
|
|
||||||
log.Println(c.Errors)
|
|
||||||
}
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
stack := stack(3)
|
|
||||||
log.Printf("PANIC: %s\n%s", err, stack)
|
|
||||||
c.Writer.WriteHeader(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
package gin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Context) ErrorRender() HandlerFunc {
|
|
||||||
return func(c *Context) {
|
|
||||||
defer func() {
|
|
||||||
if len(c.Errors) > 0 {
|
|
||||||
c.JSON(-1, c.Errors)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Validate(c *Context, obj interface{}) error {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
typ := reflect.TypeOf(obj)
|
|
||||||
val := reflect.ValueOf(obj)
|
|
||||||
|
|
||||||
if typ.Kind() == reflect.Ptr {
|
|
||||||
typ = typ.Elem()
|
|
||||||
val = val.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < typ.NumField(); i++ {
|
|
||||||
field := typ.Field(i)
|
|
||||||
fieldValue := val.Field(i).Interface()
|
|
||||||
zero := reflect.Zero(field.Type).Interface()
|
|
||||||
|
|
||||||
// Validate nested and embedded structs (if pointer, only do so if not nil)
|
|
||||||
if field.Type.Kind() == reflect.Struct ||
|
|
||||||
(field.Type.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, fieldValue)) {
|
|
||||||
err = Validate(c, fieldValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Index(field.Tag.Get("binding"), "required") > -1 {
|
|
||||||
if reflect.DeepEqual(zero, fieldValue) {
|
|
||||||
name := field.Name
|
|
||||||
if j := field.Tag.Get("json"); j != "" {
|
|
||||||
name = j
|
|
||||||
} else if f := field.Tag.Get("form"); f != "" {
|
|
||||||
name = f
|
|
||||||
}
|
|
||||||
err = errors.New("Required " + name)
|
|
||||||
c.Error(err, "json validation")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|