bump go-pkgz modules, bbolt new url
This commit is contained in:
committed by
Umputun
parent
40eb39f8f0
commit
dba77d403d
@@ -38,8 +38,6 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
|
||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -61,21 +59,23 @@ github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv
|
||||
github.com/gavv/monotime v0.0.0-20171021193802-6f8212e8d10d/go.mod h1:vmp8DIyckQMXOPl0AQVHt+7n5h7Gb7hS6CUydiV8QeA=
|
||||
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
|
||||
github.com/go-chi/chi v4.1.0+incompatible h1:ETj3cggsVIY2Xao5ExCu6YhEh5MD6JTfcBzS37R260w=
|
||||
github.com/go-chi/chi v4.1.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
|
||||
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-pkgz/auth v0.9.0/go.mod h1:8Kiold3xA9VUiYZwMyMhwBgiGImZFoL/EmtO9lSIQxo=
|
||||
github.com/go-pkgz/auth v0.10.1/go.mod h1:wxyQqc0UUP1jT4l6zk1r6XPcVdcgIzW2OiQ8hBEHd64=
|
||||
github.com/go-pkgz/jrpc v0.1.0 h1:hNg/IyfEqJcSWOKkuHw0ZwcuGc9TDp7QZREsD2ycmiM=
|
||||
github.com/go-pkgz/jrpc v0.1.0/go.mod h1:JxZsvoBklA50DNhELVJnJ567Rt+KrMH9rR3u515wvE8=
|
||||
github.com/go-pkgz/lcw v0.5.0/go.mod h1:CSdQRQthxJQ4iDD4wTPPuWFbFdknJzwJ8WXu1nfxb10=
|
||||
github.com/go-pkgz/lgr v0.6.3 h1:n9pGk2paBV8w/Y/FVEq5MkwDmP33dnUPKbY4CyyygwM=
|
||||
github.com/go-pkgz/lgr v0.6.3/go.mod h1:hBM1NM/SoYdlrykgdgJWGrZ/TM/XaZIjRbJfx7NkMm8=
|
||||
github.com/go-pkgz/lgr v0.7.0 h1:S/AAPwt/RE9a5mNJskA7dGVp+Dq6SMIW6LYjG3ITxY8=
|
||||
github.com/go-pkgz/lgr v0.7.0/go.mod h1:yMgxU+GobMRJgIEbSzDKy/67W18S7qmGx/7BVL5AB8Q=
|
||||
github.com/go-pkgz/repeater v1.1.3/go.mod h1:hVTavuO5x3Gxnu8zW7d6sQBfAneKV8X2FjU48kGfpKw=
|
||||
github.com/go-pkgz/rest v1.4.1 h1:DmaVLPH2O7yLehrWOW0uz01d2mVHz9fBR/iuTiPRzaw=
|
||||
github.com/go-pkgz/rest v1.4.1/go.mod h1:COazNj35u3RXAgQNBr6neR599tYP3URiOpsu9p0rOtk=
|
||||
github.com/go-pkgz/rest v1.5.0 h1:C8SxXcXza4GiUUAn/95iCkvoIrGbS30qpwK19iqlrWQ=
|
||||
github.com/go-pkgz/rest v1.5.0/go.mod h1:nQaM3RhSTUAmbBZWY4hfe4buyeC9VckvhoCktiQXJxI=
|
||||
github.com/go-pkgz/syncs v1.1.1/go.mod h1:bt9lxWRRJ9vOCMGc8Big8ttjYHLKP88ofj1y38UlaHE=
|
||||
github.com/go-redis/redis/v7 v7.0.0-beta.4/go.mod h1:xhhSbUMTsleRPur+Vgx9sUHtyN33bdjxY+9/0n9Ig8s=
|
||||
github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0=
|
||||
@@ -115,6 +115,7 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
|
||||
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
|
||||
github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc h1:0L2sGkaj6MWuV1BfXsrLJ/+XA8RzKKVsYlLVXNkK1Lw=
|
||||
github.com/jessevdk/go-flags v0.0.0-20180331124232-1c38ed7ad0cc/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
@@ -209,8 +210,8 @@ github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FB
|
||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
|
||||
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
|
||||
github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
|
||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
@@ -281,11 +282,10 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
||||
@@ -3,7 +3,7 @@ package cmd
|
||||
import (
|
||||
"path"
|
||||
|
||||
bolt "github.com/coreos/bbolt"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
log "github.com/go-pkgz/lgr"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
bolt "github.com/coreos/bbolt"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/go-pkgz/jrpc"
|
||||
log "github.com/go-pkgz/lgr"
|
||||
@@ -860,7 +860,7 @@ func (s *ServerCommand) makeAuthenticator(ds *service.DataStore, avas avatar.Sto
|
||||
TokenDuration: s.Auth.TTL.JWT,
|
||||
CookieDuration: s.Auth.TTL.Cookie,
|
||||
SecureCookies: strings.HasPrefix(s.RemarkURL, "https://"),
|
||||
SecretReader: token.SecretFunc(func() (string, error) { // get secret per site
|
||||
SecretReader: token.SecretFunc(func(aud string) (string, error) { // get secret per site
|
||||
return admns.Key()
|
||||
}),
|
||||
ClaimsUpd: token.ClaimsUpdFunc(func(c token.Claims) token.Claims { // set attributes, on new token or refresh
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
bolt "github.com/coreos/bbolt"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
bolt "github.com/coreos/bbolt"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
bolt "github.com/coreos/bbolt"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
log "github.com/go-pkgz/lgr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
bolt "github.com/coreos/bbolt"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
bolt "github.com/coreos/bbolt"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"github.com/go-pkgz/auth"
|
||||
"github.com/go-pkgz/auth/avatar"
|
||||
"github.com/go-pkgz/auth/token"
|
||||
@@ -365,7 +365,7 @@ func startupT(t *testing.T) (ts *httptest.Server, srv *Rest, teardown func()) {
|
||||
DataService: dataStore,
|
||||
Authenticator: auth.NewService(auth.Opts{
|
||||
AdminPasswd: "password",
|
||||
SecretReader: token.SecretFunc(func() (string, error) { return "secret", nil }),
|
||||
SecretReader: token.SecretFunc(func(aud string) (string, error) { return "secret", nil }),
|
||||
AvatarStore: avatar.NewLocalFS(tmp + "/ava-remark42"),
|
||||
}),
|
||||
Cache: memCache,
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
bolt "github.com/coreos/bbolt"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
log "github.com/go-pkgz/lgr"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
bolt "github.com/coreos/bbolt"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
bolt "github.com/coreos/bbolt"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
log "github.com/go-pkgz/lgr"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
bolt "github.com/coreos/bbolt"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
bolt "github.com/coreos/bbolt"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"github.com/go-pkgz/lgr"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
+7
-8
@@ -9,21 +9,20 @@ require (
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/alecthomas/chroma v0.6.0
|
||||
github.com/andybalholm/cascadia v1.1.0 // indirect
|
||||
github.com/coreos/bbolt v1.3.3
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/didip/tollbooth v4.0.2+incompatible
|
||||
github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d
|
||||
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 // indirect
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible // indirect
|
||||
github.com/go-chi/chi v4.0.2+incompatible
|
||||
github.com/go-chi/cors v1.0.0
|
||||
github.com/go-chi/chi v4.1.0+incompatible
|
||||
github.com/go-chi/cors v1.1.1
|
||||
github.com/go-chi/render v1.0.1
|
||||
github.com/go-pkgz/auth v0.9.0
|
||||
github.com/go-pkgz/auth v0.10.1
|
||||
github.com/go-pkgz/jrpc v0.1.0
|
||||
github.com/go-pkgz/lcw v0.5.0
|
||||
github.com/go-pkgz/lgr v0.6.3
|
||||
github.com/go-pkgz/lgr v0.7.0
|
||||
github.com/go-pkgz/repeater v1.1.3
|
||||
github.com/go-pkgz/rest v1.4.1
|
||||
github.com/go-pkgz/rest v1.5.0
|
||||
github.com/go-pkgz/syncs v1.1.1
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f // indirect
|
||||
@@ -40,7 +39,7 @@ require (
|
||||
github.com/onsi/ginkgo v1.9.0 // indirect
|
||||
github.com/onsi/gomega v1.6.0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rakyll/statik v0.1.6
|
||||
github.com/rs/xid v1.2.1
|
||||
github.com/russross/blackfriday/v2 v2.0.1
|
||||
@@ -52,12 +51,12 @@ require (
|
||||
github.com/valyala/fasthttp v1.4.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190809123943-df4f5c81cb3b // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.1.0 // indirect
|
||||
go.etcd.io/bbolt v1.3.4
|
||||
go.mongodb.org/mongo-driver v1.1.2 // indirect
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8
|
||||
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
||||
google.golang.org/appengine v1.6.5 // indirect
|
||||
gopkg.in/oauth2.v3 v3.11.0 // indirect
|
||||
|
||||
+16
-13
@@ -43,8 +43,6 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
|
||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -72,23 +70,27 @@ github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv
|
||||
github.com/gavv/monotime v0.0.0-20171021193802-6f8212e8d10d/go.mod h1:vmp8DIyckQMXOPl0AQVHt+7n5h7Gb7hS6CUydiV8QeA=
|
||||
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0=
|
||||
github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
|
||||
github.com/go-chi/chi v4.1.0+incompatible h1:ETj3cggsVIY2Xao5ExCu6YhEh5MD6JTfcBzS37R260w=
|
||||
github.com/go-chi/chi v4.1.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw=
|
||||
github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
|
||||
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-pkgz/auth v0.9.0 h1:1BR+wtbkHzLg4BH1CQUpEO/WzgihrDqzW8XN4g9Pq9g=
|
||||
github.com/go-pkgz/auth v0.9.0/go.mod h1:8Kiold3xA9VUiYZwMyMhwBgiGImZFoL/EmtO9lSIQxo=
|
||||
github.com/go-pkgz/auth v0.10.1 h1:GYf64js5n/oVEPXhRShyDDiR/oahdZOMLbxmnnGX2IU=
|
||||
github.com/go-pkgz/auth v0.10.1/go.mod h1:wxyQqc0UUP1jT4l6zk1r6XPcVdcgIzW2OiQ8hBEHd64=
|
||||
github.com/go-pkgz/jrpc v0.1.0 h1:hNg/IyfEqJcSWOKkuHw0ZwcuGc9TDp7QZREsD2ycmiM=
|
||||
github.com/go-pkgz/jrpc v0.1.0/go.mod h1:JxZsvoBklA50DNhELVJnJ567Rt+KrMH9rR3u515wvE8=
|
||||
github.com/go-pkgz/lcw v0.5.0 h1:dSf98y5067zRmBA6fo+gbqLGLy08RGUHoaecwtjDyOE=
|
||||
github.com/go-pkgz/lcw v0.5.0/go.mod h1:CSdQRQthxJQ4iDD4wTPPuWFbFdknJzwJ8WXu1nfxb10=
|
||||
github.com/go-pkgz/lgr v0.6.3 h1:n9pGk2paBV8w/Y/FVEq5MkwDmP33dnUPKbY4CyyygwM=
|
||||
github.com/go-pkgz/lgr v0.6.3/go.mod h1:hBM1NM/SoYdlrykgdgJWGrZ/TM/XaZIjRbJfx7NkMm8=
|
||||
github.com/go-pkgz/lgr v0.7.0 h1:S/AAPwt/RE9a5mNJskA7dGVp+Dq6SMIW6LYjG3ITxY8=
|
||||
github.com/go-pkgz/lgr v0.7.0/go.mod h1:yMgxU+GobMRJgIEbSzDKy/67W18S7qmGx/7BVL5AB8Q=
|
||||
github.com/go-pkgz/repeater v1.1.3 h1:q6+JQF14ESSy28Dd7F+wRelY4F+41HJ0LEy/szNnMiE=
|
||||
github.com/go-pkgz/repeater v1.1.3/go.mod h1:hVTavuO5x3Gxnu8zW7d6sQBfAneKV8X2FjU48kGfpKw=
|
||||
github.com/go-pkgz/rest v1.4.1 h1:DmaVLPH2O7yLehrWOW0uz01d2mVHz9fBR/iuTiPRzaw=
|
||||
github.com/go-pkgz/rest v1.4.1/go.mod h1:COazNj35u3RXAgQNBr6neR599tYP3URiOpsu9p0rOtk=
|
||||
github.com/go-pkgz/rest v1.5.0 h1:C8SxXcXza4GiUUAn/95iCkvoIrGbS30qpwK19iqlrWQ=
|
||||
github.com/go-pkgz/rest v1.5.0/go.mod h1:nQaM3RhSTUAmbBZWY4hfe4buyeC9VckvhoCktiQXJxI=
|
||||
github.com/go-pkgz/syncs v1.1.1 h1:jWN+y6FS/Xe+8z4l3QMbSnODGyaxDHGojIS+wyKIjxg=
|
||||
github.com/go-pkgz/syncs v1.1.1/go.mod h1:bt9lxWRRJ9vOCMGc8Big8ttjYHLKP88ofj1y38UlaHE=
|
||||
github.com/go-redis/redis/v7 v7.0.0-beta.4 h1:p6z7Pde69EGRWvlC++y8aFcaWegyrKHzOBGo0zUACTQ=
|
||||
@@ -197,6 +199,8 @@ github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTK
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rakyll/statik v0.1.6 h1:uICcfUXpgqtw2VopbIncslhAmE5hwc4g20TEyEENBNs=
|
||||
@@ -270,8 +274,8 @@ github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcm
|
||||
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
|
||||
github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036 h1:1b6PAtenNyhsmo/NKXVe34h7JEZKva1YB/ne7K7mqKM=
|
||||
github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
|
||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.mongodb.org/mongo-driver v1.1.1 h1:Sq1fR+0c58RME5EoqKdjkiQAmPjmfHlZOoRI6fTUOcs=
|
||||
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.1.2 h1:jxcFYjlkl8xaERsgLo+RNquI0epW6zuy/ZRQs6jnrFA=
|
||||
@@ -352,12 +356,11 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
package bbolt
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||
const maxMapSize = 0x7FFFFFFF // 2GB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0xFFFFFFF
|
||||
|
||||
// Are unaligned load/stores broken on this arch?
|
||||
var brokenUnaligned bool
|
||||
|
||||
func init() {
|
||||
// Simple check to see whether this arch handles unaligned load/stores
|
||||
// correctly.
|
||||
|
||||
// ARM9 and older devices require load/stores to be from/to aligned
|
||||
// addresses. If not, the lower 2 bits are cleared and that address is
|
||||
// read in a jumbled up order.
|
||||
|
||||
// See http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka15414.html
|
||||
|
||||
raw := [6]byte{0xfe, 0xef, 0x11, 0x22, 0x22, 0x11}
|
||||
val := *(*uint32)(unsafe.Pointer(uintptr(unsafe.Pointer(&raw)) + 2))
|
||||
|
||||
brokenUnaligned = val != 0x11222211
|
||||
}
|
||||
+2
@@ -4,6 +4,8 @@ go:
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
- 1.14.x
|
||||
|
||||
script:
|
||||
- go get -d -t ./...
|
||||
|
||||
+36
@@ -1,5 +1,41 @@
|
||||
# Changelog
|
||||
|
||||
## v4.1.0 (2020-04-1)
|
||||
|
||||
- middleware.LogEntry: Write method on interface now passes the response header
|
||||
and an extra interface type useful for custom logger implementations.
|
||||
- middleware.WrapResponseWriter: minor fix
|
||||
- middleware.Recoverer: a bit prettier
|
||||
- History of changes: see https://github.com/go-chi/chi/compare/v4.0.3...v4.1.0
|
||||
|
||||
|
||||
## v4.0.4 (2020-03-24)
|
||||
|
||||
- middleware.Recoverer: new pretty stack trace printing (https://github.com/go-chi/chi/pull/496)
|
||||
- a few minor improvements and fixes
|
||||
- History of changes: see https://github.com/go-chi/chi/compare/v4.0.3...v4.0.4
|
||||
|
||||
|
||||
## v4.0.3 (2020-01-09)
|
||||
|
||||
- core: fix regexp routing to include default value when param is not matched
|
||||
- middleware: rewrite of middleware.Compress
|
||||
- middleware: suppress http.ErrAbortHandler in middleware.Recoverer
|
||||
- History of changes: see https://github.com/go-chi/chi/compare/v4.0.2...v4.0.3
|
||||
|
||||
|
||||
## v4.0.2 (2019-02-26)
|
||||
|
||||
- Minor fixes
|
||||
- History of changes: see https://github.com/go-chi/chi/compare/v4.0.1...v4.0.2
|
||||
|
||||
|
||||
## v4.0.1 (2019-01-21)
|
||||
|
||||
- Fixes issue with compress middleware: #382 #385
|
||||
- History of changes: see https://github.com/go-chi/chi/compare/v4.0.0...v4.0.1
|
||||
|
||||
|
||||
## v4.0.0 (2019-01-10)
|
||||
|
||||
- chi v4 requires Go 1.10.3+ (or Go 1.9.7+) - we have deprecated support for Go 1.7 and 1.8
|
||||
|
||||
+12
-14
@@ -28,7 +28,7 @@ included some useful/optional subpackages: [middleware](/middleware), [render](h
|
||||
* **Fast** - yes, see [benchmarks](#benchmarks)
|
||||
* **100% compatible with net/http** - use any http or middleware pkg in the ecosystem that is also compatible with `net/http`
|
||||
* **Designed for modular/composable APIs** - middlewares, inline middlewares, route groups and subrouter mounting
|
||||
* **Context control** - built on new `context` package, providing value chaining, cancelations and timeouts
|
||||
* **Context control** - built on new `context` package, providing value chaining, cancellations and timeouts
|
||||
* **Robust** - in production at Pressly, CloudFlare, Heroku, 99Designs, and many others (see [discussion](https://github.com/go-chi/chi/issues/91))
|
||||
* **Doc generation** - `docgen` auto-generates routing documentation from your source to JSON or Markdown
|
||||
* **No external dependencies** - plain ol' Go stdlib + net/http
|
||||
@@ -179,7 +179,7 @@ type Router interface {
|
||||
http.Handler
|
||||
Routes
|
||||
|
||||
// Use appends one of more middlewares onto the Router stack.
|
||||
// Use appends one or more middlewares onto the Router stack.
|
||||
Use(middlewares ...func(http.Handler) http.Handler)
|
||||
|
||||
// With adds inline middlewares for an endpoint handler.
|
||||
@@ -312,9 +312,10 @@ with `net/http` can be used with chi's mux.
|
||||
### Core middlewares
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------
|
||||
| chi/middleware Handler | description |
|
||||
| chi/middleware Handler | description |
|
||||
|:----------------------|:---------------------------------------------------------------------------------
|
||||
| AllowContentType | Explicit whitelist of accepted request Content-Types |
|
||||
| BasicAuth | Basic HTTP authentication |
|
||||
| Compress | Gzip compression for clients that accept compressed responses |
|
||||
| GetHead | Automatically route undefined HEAD requests to GET handlers |
|
||||
| Heartbeat | Monitoring endpoint to check the servers pulse |
|
||||
@@ -412,18 +413,15 @@ We'll be more than happy to see [your contributions](./CONTRIBUTING.md)!
|
||||
## Beyond REST
|
||||
|
||||
chi is just a http router that lets you decompose request handling into many smaller layers.
|
||||
Many companies including Pressly.com (of course) use chi to write REST services for their public
|
||||
APIs. But, REST is just a convention for managing state via HTTP, and there's a lot of other pieces
|
||||
required to write a complete client-server system or network of microservices.
|
||||
Many companies use chi to write REST services for their public APIs. But, REST is just a convention
|
||||
for managing state via HTTP, and there's a lot of other pieces required to write a complete client-server
|
||||
system or network of microservices.
|
||||
|
||||
Looking ahead beyond REST, I also recommend some newer works in the field coming from
|
||||
[gRPC](https://github.com/grpc/grpc-go), [NATS](https://nats.io), [go-kit](https://github.com/go-kit/kit)
|
||||
and even [graphql](https://github.com/graphql-go/graphql). They're all pretty cool with their
|
||||
own unique approaches and benefits. Specifically, I'd look at gRPC since it makes client-server
|
||||
communication feel like a single program on a single computer, no need to hand-write a client library
|
||||
and the request/response payloads are typed contracts. NATS is pretty amazing too as a super
|
||||
fast and lightweight pub-sub transport that can speak protobufs, with nice service discovery -
|
||||
an excellent combination with gRPC.
|
||||
Looking beyond REST, I also recommend some newer works in the field:
|
||||
* [webrpc](https://github.com/webrpc/webrpc) - Web-focused RPC client+server framework with code-gen
|
||||
* [gRPC](https://github.com/grpc/grpc-go) - Google's RPC framework via protobufs
|
||||
* [graphql](https://github.com/99designs/gqlgen) - Declarative query language
|
||||
* [NATS](https://nats.io) - lightweight pub-sub
|
||||
|
||||
|
||||
## License
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
//
|
||||
// Package chi is a small, idiomatic and composable router for building HTTP services.
|
||||
//
|
||||
// chi requires Go 1.7 or newer.
|
||||
// chi requires Go 1.10 or newer.
|
||||
//
|
||||
// Example:
|
||||
// package main
|
||||
@@ -68,7 +68,7 @@ type Router interface {
|
||||
http.Handler
|
||||
Routes
|
||||
|
||||
// Use appends one of more middlewares onto the Router stack.
|
||||
// Use appends one or more middlewares onto the Router stack.
|
||||
Use(middlewares ...func(http.Handler) http.Handler)
|
||||
|
||||
// With adds inline middlewares for an endpoint handler.
|
||||
|
||||
+4
-3
@@ -99,7 +99,8 @@ func (x *Context) RoutePattern() string {
|
||||
// RouteContext returns chi's routing Context object from a
|
||||
// http.Request Context.
|
||||
func RouteContext(ctx context.Context) *Context {
|
||||
return ctx.Value(RouteCtxKey).(*Context)
|
||||
val, _ := ctx.Value(RouteCtxKey).(*Context)
|
||||
return val
|
||||
}
|
||||
|
||||
// URLParam returns the url parameter from a http.Request object.
|
||||
@@ -125,8 +126,8 @@ type RouteParams struct {
|
||||
|
||||
// Add will append a URL parameter to the end of the route param
|
||||
func (s *RouteParams) Add(key, value string) {
|
||||
(*s).Keys = append((*s).Keys, key)
|
||||
(*s).Values = append((*s).Values, value)
|
||||
s.Keys = append(s.Keys, key)
|
||||
s.Values = append(s.Values, value)
|
||||
}
|
||||
|
||||
// ServerBaseContext wraps an http.Handler to set the request context to the
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// BasicAuth implements a simple middleware handler for adding basic http auth to a route.
|
||||
func BasicAuth(realm string, creds map[string]string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
user, pass, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
basicAuthFailed(w, realm)
|
||||
return
|
||||
}
|
||||
|
||||
credPass, credUserOk := creds[user]
|
||||
if !credUserOk || pass != credPass {
|
||||
basicAuthFailed(w, realm)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func basicAuthFailed(w http.ResponseWriter, realm string) {
|
||||
w.Header().Add("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s"`, realm))
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}
|
||||
+241
-117
@@ -5,26 +5,101 @@ import (
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var encoders = map[string]EncoderFunc{}
|
||||
var defaultCompressibleContentTypes = []string{
|
||||
"text/html",
|
||||
"text/css",
|
||||
"text/plain",
|
||||
"text/javascript",
|
||||
"application/javascript",
|
||||
"application/x-javascript",
|
||||
"application/json",
|
||||
"application/atom+xml",
|
||||
"application/rss+xml",
|
||||
"image/svg+xml",
|
||||
}
|
||||
|
||||
var encodingPrecedence = []string{"br", "gzip", "deflate"}
|
||||
// Compress is a middleware that compresses response
|
||||
// body of a given content types to a data format based
|
||||
// on Accept-Encoding request header. It uses a given
|
||||
// compression level.
|
||||
//
|
||||
// NOTE: make sure to set the Content-Type header on your response
|
||||
// otherwise this middleware will not compress the response body. For ex, in
|
||||
// your handler you should set w.Header().Set("Content-Type", http.DetectContentType(yourBody))
|
||||
// or set it manually.
|
||||
//
|
||||
// Passing a compression level of 5 is sensible value
|
||||
func Compress(level int, types ...string) func(next http.Handler) http.Handler {
|
||||
compressor := NewCompressor(level, types...)
|
||||
return compressor.Handler
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Compressor represents a set of encoding configurations.
|
||||
type Compressor struct {
|
||||
level int // The compression level.
|
||||
// The mapping of encoder names to encoder functions.
|
||||
encoders map[string]EncoderFunc
|
||||
// The mapping of pooled encoders to pools.
|
||||
pooledEncoders map[string]*sync.Pool
|
||||
// The set of content types allowed to be compressed.
|
||||
allowedTypes map[string]struct{}
|
||||
allowedWildcards map[string]struct{}
|
||||
// The list of encoders in order of decreasing precedence.
|
||||
encodingPrecedence []string
|
||||
}
|
||||
|
||||
// NewCompressor creates a new Compressor that will handle encoding responses.
|
||||
//
|
||||
// The level should be one of the ones defined in the flate package.
|
||||
// The types are the content types that are allowed to be compressed.
|
||||
func NewCompressor(level int, types ...string) *Compressor {
|
||||
// If types are provided, set those as the allowed types. If none are
|
||||
// provided, use the default list.
|
||||
allowedTypes := make(map[string]struct{})
|
||||
allowedWildcards := make(map[string]struct{})
|
||||
if len(types) > 0 {
|
||||
for _, t := range types {
|
||||
if strings.Contains(strings.TrimSuffix(t, "/*"), "*") {
|
||||
panic(fmt.Sprintf("middleware/compress: Unsupported content-type wildcard pattern '%s'. Only '/*' supported", t))
|
||||
}
|
||||
if strings.HasSuffix(t, "/*") {
|
||||
allowedWildcards[strings.TrimSuffix(t, "/*")] = struct{}{}
|
||||
} else {
|
||||
allowedTypes[t] = struct{}{}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, t := range defaultCompressibleContentTypes {
|
||||
allowedTypes[t] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
c := &Compressor{
|
||||
level: level,
|
||||
encoders: make(map[string]EncoderFunc),
|
||||
pooledEncoders: make(map[string]*sync.Pool),
|
||||
allowedTypes: allowedTypes,
|
||||
allowedWildcards: allowedWildcards,
|
||||
}
|
||||
|
||||
// Set the default encoders. The precedence order uses the reverse
|
||||
// ordering that the encoders were added. This means adding new encoders
|
||||
// will move them to the front of the order.
|
||||
//
|
||||
// TODO:
|
||||
// lzma: Opera.
|
||||
// sdch: Chrome, Android. Gzip output + dictionary header.
|
||||
// br: Brotli, see https://github.com/go-chi/chi/pull/326
|
||||
|
||||
// TODO: Exception for old MSIE browsers that can't handle non-HTML?
|
||||
// https://zoompf.com/blog/2012/02/lose-the-wait-http-compression
|
||||
SetEncoder("gzip", encoderGzip)
|
||||
|
||||
// HTTP 1.1 "deflate" (RFC 2616) stands for DEFLATE data (RFC 1951)
|
||||
// wrapped with zlib (RFC 1950). The zlib wrapper uses Adler-32
|
||||
// checksum compared to CRC-32 used in "gzip" and thus is faster.
|
||||
@@ -41,21 +116,20 @@ func init() {
|
||||
//
|
||||
// That's why we prefer gzip over deflate. It's just more reliable
|
||||
// and not significantly slower than gzip.
|
||||
SetEncoder("deflate", encoderDeflate)
|
||||
c.SetEncoder("deflate", encoderDeflate)
|
||||
|
||||
// TODO: Exception for old MSIE browsers that can't handle non-HTML?
|
||||
// https://zoompf.com/blog/2012/02/lose-the-wait-http-compression
|
||||
c.SetEncoder("gzip", encoderGzip)
|
||||
|
||||
// NOTE: Not implemented, intentionally:
|
||||
// case "compress": // LZW. Deprecated.
|
||||
// case "bzip2": // Too slow on-the-fly.
|
||||
// case "zopfli": // Too slow on-the-fly.
|
||||
// case "xz": // Too slow on-the-fly.
|
||||
return c
|
||||
}
|
||||
|
||||
// An EncoderFunc is a function that wraps the provided ResponseWriter with a
|
||||
// streaming compression algorithm and returns it.
|
||||
//
|
||||
// In case of failure, the function should return nil.
|
||||
type EncoderFunc func(w http.ResponseWriter, level int) io.Writer
|
||||
|
||||
// SetEncoder can be used to set the implementation of a compression algorithm.
|
||||
//
|
||||
// The encoding should be a standardised identifier. See:
|
||||
@@ -65,12 +139,13 @@ type EncoderFunc func(w http.ResponseWriter, level int) io.Writer
|
||||
//
|
||||
// import brotli_enc "gopkg.in/kothar/brotli-go.v0/enc"
|
||||
//
|
||||
// middleware.SetEncoder("br", func(w http.ResponseWriter, level int) io.Writer {
|
||||
// compressor := middleware.NewCompressor(5, "text/html")
|
||||
// compressor.SetEncoder("br", func(w http.ResponseWriter, level int) io.Writer {
|
||||
// params := brotli_enc.NewBrotliParams()
|
||||
// params.SetQuality(level)
|
||||
// return brotli_enc.NewBrotliWriter(params, w)
|
||||
// })
|
||||
func SetEncoder(encoding string, fn EncoderFunc) {
|
||||
func (c *Compressor) SetEncoder(encoding string, fn EncoderFunc) {
|
||||
encoding = strings.ToLower(encoding)
|
||||
if encoding == "" {
|
||||
panic("the encoding can not be empty")
|
||||
@@ -78,118 +153,153 @@ func SetEncoder(encoding string, fn EncoderFunc) {
|
||||
if fn == nil {
|
||||
panic("attempted to set a nil encoder function")
|
||||
}
|
||||
encoders[encoding] = fn
|
||||
|
||||
var e string
|
||||
for _, v := range encodingPrecedence {
|
||||
if v == encoding {
|
||||
e = v
|
||||
}
|
||||
// If we are adding a new encoder that is already registered, we have to
|
||||
// clear that one out first.
|
||||
if _, ok := c.pooledEncoders[encoding]; ok {
|
||||
delete(c.pooledEncoders, encoding)
|
||||
}
|
||||
if _, ok := c.encoders[encoding]; ok {
|
||||
delete(c.encoders, encoding)
|
||||
}
|
||||
|
||||
if e == "" {
|
||||
encodingPrecedence = append([]string{e}, encodingPrecedence...)
|
||||
}
|
||||
}
|
||||
|
||||
var defaultContentTypes = map[string]struct{}{
|
||||
"text/html": {},
|
||||
"text/css": {},
|
||||
"text/plain": {},
|
||||
"text/javascript": {},
|
||||
"application/javascript": {},
|
||||
"application/x-javascript": {},
|
||||
"application/json": {},
|
||||
"application/atom+xml": {},
|
||||
"application/rss+xml": {},
|
||||
"image/svg+xml": {},
|
||||
}
|
||||
|
||||
// DefaultCompress is a middleware that compresses response
|
||||
// body of predefined content types to a data format based
|
||||
// on Accept-Encoding request header. It uses a default
|
||||
// compression level.
|
||||
func DefaultCompress(next http.Handler) http.Handler {
|
||||
return Compress(flate.DefaultCompression)(next)
|
||||
}
|
||||
|
||||
// Compress is a middleware that compresses response
|
||||
// body of a given content types to a data format based
|
||||
// on Accept-Encoding request header. It uses a given
|
||||
// compression level.
|
||||
//
|
||||
// NOTE: make sure to set the Content-Type header on your response
|
||||
// otherwise this middleware will not compress the response body. For ex, in
|
||||
// your handler you should set w.Header().Set("Content-Type", http.DetectContentType(yourBody))
|
||||
// or set it manually.
|
||||
func Compress(level int, types ...string) func(next http.Handler) http.Handler {
|
||||
contentTypes := defaultContentTypes
|
||||
if len(types) > 0 {
|
||||
contentTypes = make(map[string]struct{}, len(types))
|
||||
for _, t := range types {
|
||||
contentTypes[t] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
encoder, encoding := selectEncoder(r.Header)
|
||||
|
||||
cw := &compressResponseWriter{
|
||||
ResponseWriter: w,
|
||||
w: w,
|
||||
contentTypes: contentTypes,
|
||||
encoder: encoder,
|
||||
encoding: encoding,
|
||||
level: level,
|
||||
// If the encoder supports Resetting (IoReseterWriter), then it can be pooled.
|
||||
encoder := fn(ioutil.Discard, c.level)
|
||||
if encoder != nil {
|
||||
if _, ok := encoder.(ioResetterWriter); ok {
|
||||
pool := &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return fn(ioutil.Discard, c.level)
|
||||
},
|
||||
}
|
||||
defer cw.Close()
|
||||
|
||||
next.ServeHTTP(cw, r)
|
||||
c.pooledEncoders[encoding] = pool
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
// If the encoder is not in the pooledEncoders, add it to the normal encoders.
|
||||
if _, ok := c.pooledEncoders[encoding]; !ok {
|
||||
c.encoders[encoding] = fn
|
||||
}
|
||||
|
||||
for i, v := range c.encodingPrecedence {
|
||||
if v == encoding {
|
||||
c.encodingPrecedence = append(c.encodingPrecedence[:i], c.encodingPrecedence[i+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
c.encodingPrecedence = append([]string{encoding}, c.encodingPrecedence...)
|
||||
}
|
||||
|
||||
func selectEncoder(h http.Header) (EncoderFunc, string) {
|
||||
// Handler returns a new middleware that will compress the response based on the
|
||||
// current Compressor.
|
||||
func (c *Compressor) Handler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
encoder, encoding, cleanup := c.selectEncoder(r.Header, w)
|
||||
|
||||
cw := &compressResponseWriter{
|
||||
ResponseWriter: w,
|
||||
w: w,
|
||||
contentTypes: c.allowedTypes,
|
||||
contentWildcards: c.allowedWildcards,
|
||||
encoding: encoding,
|
||||
compressable: false, // determined in post-handler
|
||||
}
|
||||
if encoder != nil {
|
||||
cw.w = encoder
|
||||
}
|
||||
// Re-add the encoder to the pool if applicable.
|
||||
defer cleanup()
|
||||
defer cw.Close()
|
||||
|
||||
next.ServeHTTP(cw, r)
|
||||
})
|
||||
}
|
||||
|
||||
// selectEncoder returns the encoder, the name of the encoder, and a closer function.
|
||||
func (c *Compressor) selectEncoder(h http.Header, w io.Writer) (io.Writer, string, func()) {
|
||||
header := h.Get("Accept-Encoding")
|
||||
|
||||
// Parse the names of all accepted algorithms from the header.
|
||||
accepted := strings.Split(strings.ToLower(header), ",")
|
||||
|
||||
// Find supported encoder by accepted list by precedence
|
||||
for _, name := range encodingPrecedence {
|
||||
if fn, ok := encoders[name]; ok && matchAcceptEncoding(accepted, name) {
|
||||
return fn, name
|
||||
for _, name := range c.encodingPrecedence {
|
||||
if matchAcceptEncoding(accepted, name) {
|
||||
if pool, ok := c.pooledEncoders[name]; ok {
|
||||
encoder := pool.Get().(ioResetterWriter)
|
||||
cleanup := func() {
|
||||
pool.Put(encoder)
|
||||
}
|
||||
encoder.Reset(w)
|
||||
return encoder, name, cleanup
|
||||
|
||||
}
|
||||
if fn, ok := c.encoders[name]; ok {
|
||||
return fn(w, c.level), name, func() {}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// No encoder found to match the accepted encoding
|
||||
return nil, ""
|
||||
return nil, "", func() {}
|
||||
}
|
||||
|
||||
func matchAcceptEncoding(accepted []string, encoding string) bool {
|
||||
for _, v := range accepted {
|
||||
if strings.Index(v, encoding) >= 0 {
|
||||
if strings.Contains(v, encoding) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// An EncoderFunc is a function that wraps the provided io.Writer with a
|
||||
// streaming compression algorithm and returns it.
|
||||
//
|
||||
// In case of failure, the function should return nil.
|
||||
type EncoderFunc func(w io.Writer, level int) io.Writer
|
||||
|
||||
// Interface for types that allow resetting io.Writers.
|
||||
type ioResetterWriter interface {
|
||||
io.Writer
|
||||
Reset(w io.Writer)
|
||||
}
|
||||
|
||||
type compressResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
w io.Writer
|
||||
encoder EncoderFunc
|
||||
encoding string
|
||||
contentTypes map[string]struct{}
|
||||
level int
|
||||
wroteHeader bool
|
||||
|
||||
// The streaming encoder writer to be used if there is one. Otherwise,
|
||||
// this is just the normal writer.
|
||||
w io.Writer
|
||||
encoding string
|
||||
contentTypes map[string]struct{}
|
||||
contentWildcards map[string]struct{}
|
||||
wroteHeader bool
|
||||
compressable bool
|
||||
}
|
||||
|
||||
func (cw *compressResponseWriter) isCompressable() bool {
|
||||
// Parse the first part of the Content-Type response header.
|
||||
contentType := cw.Header().Get("Content-Type")
|
||||
if idx := strings.Index(contentType, ";"); idx >= 0 {
|
||||
contentType = contentType[0:idx]
|
||||
}
|
||||
|
||||
// Is the content type compressable?
|
||||
if _, ok := cw.contentTypes[contentType]; ok {
|
||||
return true
|
||||
}
|
||||
if idx := strings.Index(contentType, "/"); idx > 0 {
|
||||
contentType = contentType[0:idx]
|
||||
_, ok := cw.contentWildcards[contentType]
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (cw *compressResponseWriter) WriteHeader(code int) {
|
||||
if cw.wroteHeader {
|
||||
cw.ResponseWriter.WriteHeader(code) // Allow multiple calls to propagate.
|
||||
return
|
||||
}
|
||||
cw.wroteHeader = true
|
||||
@@ -200,26 +310,18 @@ func (cw *compressResponseWriter) WriteHeader(code int) {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the first part of the Content-Type response header.
|
||||
contentType := ""
|
||||
parts := strings.Split(cw.Header().Get("Content-Type"), ";")
|
||||
if len(parts) > 0 {
|
||||
contentType = parts[0]
|
||||
}
|
||||
|
||||
// Is the content type compressable?
|
||||
if _, ok := cw.contentTypes[contentType]; !ok {
|
||||
if !cw.isCompressable() {
|
||||
cw.compressable = false
|
||||
return
|
||||
}
|
||||
|
||||
if cw.encoder != nil && cw.encoding != "" {
|
||||
if wr := cw.encoder(cw.ResponseWriter, cw.level); wr != nil {
|
||||
cw.w = wr
|
||||
cw.Header().Set("Content-Encoding", cw.encoding)
|
||||
if cw.encoding != "" {
|
||||
cw.compressable = true
|
||||
cw.Header().Set("Content-Encoding", cw.encoding)
|
||||
cw.Header().Set("Vary", "Accept-Encoding")
|
||||
|
||||
// The content-length after compression is unknown
|
||||
cw.Header().Del("Content-Length")
|
||||
}
|
||||
// The content-length after compression is unknown
|
||||
cw.Header().Del("Content-Length")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,37 +330,59 @@ func (cw *compressResponseWriter) Write(p []byte) (int, error) {
|
||||
cw.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
return cw.w.Write(p)
|
||||
return cw.writer().Write(p)
|
||||
}
|
||||
|
||||
func (cw *compressResponseWriter) writer() io.Writer {
|
||||
if cw.compressable {
|
||||
return cw.w
|
||||
} else {
|
||||
return cw.ResponseWriter
|
||||
}
|
||||
}
|
||||
|
||||
type compressFlusher interface {
|
||||
Flush() error
|
||||
}
|
||||
|
||||
func (cw *compressResponseWriter) Flush() {
|
||||
if f, ok := cw.w.(http.Flusher); ok {
|
||||
if f, ok := cw.writer().(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
// If the underlying writer has a compression flush signature,
|
||||
// call this Flush() method instead
|
||||
if f, ok := cw.writer().(compressFlusher); ok {
|
||||
f.Flush()
|
||||
|
||||
// Also flush the underlying response writer
|
||||
if f, ok := cw.ResponseWriter.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cw *compressResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if hj, ok := cw.w.(http.Hijacker); ok {
|
||||
if hj, ok := cw.writer().(http.Hijacker); ok {
|
||||
return hj.Hijack()
|
||||
}
|
||||
return nil, nil, errors.New("chi/middleware: http.Hijacker is unavailable on the writer")
|
||||
}
|
||||
|
||||
func (cw *compressResponseWriter) Push(target string, opts *http.PushOptions) error {
|
||||
if ps, ok := cw.w.(http.Pusher); ok {
|
||||
if ps, ok := cw.writer().(http.Pusher); ok {
|
||||
return ps.Push(target, opts)
|
||||
}
|
||||
return errors.New("chi/middleware: http.Pusher is unavailable on the writer")
|
||||
}
|
||||
|
||||
func (cw *compressResponseWriter) Close() error {
|
||||
if c, ok := cw.w.(io.WriteCloser); ok {
|
||||
if c, ok := cw.writer().(io.WriteCloser); ok {
|
||||
return c.Close()
|
||||
}
|
||||
return errors.New("chi/middleware: io.WriteCloser is unavailable on the writer")
|
||||
}
|
||||
|
||||
func encoderGzip(w http.ResponseWriter, level int) io.Writer {
|
||||
func encoderGzip(w io.Writer, level int) io.Writer {
|
||||
gw, err := gzip.NewWriterLevel(w, level)
|
||||
if err != nil {
|
||||
return nil
|
||||
@@ -266,7 +390,7 @@ func encoderGzip(w http.ResponseWriter, level int) io.Writer {
|
||||
return gw
|
||||
}
|
||||
|
||||
func encoderDeflate(w http.ResponseWriter, level int) io.Writer {
|
||||
func encoderDeflate(w io.Writer, level int) io.Writer {
|
||||
dw, err := flate.NewWriter(w, level)
|
||||
if err != nil {
|
||||
return nil
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AllowContentEncoding enforces a whitelist of request Content-Encoding otherwise responds
|
||||
// with a 415 Unsupported Media Type status.
|
||||
func AllowContentEncoding(contentEncoding ...string) func(next http.Handler) http.Handler {
|
||||
allowedEncodings := make(map[string]struct{}, len(contentEncoding))
|
||||
for _, encoding := range contentEncoding {
|
||||
allowedEncodings[strings.TrimSpace(strings.ToLower(encoding))] = struct{}{}
|
||||
}
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
requestEncodings := r.Header["Content-Encoding"]
|
||||
// skip check for empty content body or no Content-Encoding
|
||||
if r.ContentLength == 0 {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
// All encodings in the request must be allowed
|
||||
for _, encoding := range requestEncodings {
|
||||
if _, ok := allowedEncodings[strings.TrimSpace(strings.ToLower(encoding))]; !ok {
|
||||
w.WriteHeader(http.StatusUnsupportedMediaType)
|
||||
return
|
||||
}
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
+6
-9
@@ -25,8 +25,8 @@ var (
|
||||
// print in color, otherwise it will print in black and white. Logger prints a
|
||||
// request ID if one is provided.
|
||||
//
|
||||
// Alternatively, look at https://github.com/pressly/lg and the `lg.RequestLogger`
|
||||
// middleware pkg.
|
||||
// Alternatively, look at https://github.com/goware/httplog for a more in-depth
|
||||
// http logger with structured logging support.
|
||||
func Logger(next http.Handler) http.Handler {
|
||||
return DefaultLogger(next)
|
||||
}
|
||||
@@ -40,7 +40,7 @@ func RequestLogger(f LogFormatter) func(next http.Handler) http.Handler {
|
||||
|
||||
t1 := time.Now()
|
||||
defer func() {
|
||||
entry.Write(ww.Status(), ww.BytesWritten(), time.Since(t1))
|
||||
entry.Write(ww.Status(), ww.BytesWritten(), ww.Header(), time.Since(t1), nil)
|
||||
}()
|
||||
|
||||
next.ServeHTTP(ww, WithLogEntry(r, entry))
|
||||
@@ -58,7 +58,7 @@ type LogFormatter interface {
|
||||
// LogEntry records the final log when a request completes.
|
||||
// See defaultLogEntry for an example implementation.
|
||||
type LogEntry interface {
|
||||
Write(status, bytes int, elapsed time.Duration)
|
||||
Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{})
|
||||
Panic(v interface{}, stack []byte)
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ type defaultLogEntry struct {
|
||||
useColor bool
|
||||
}
|
||||
|
||||
func (l *defaultLogEntry) Write(status, bytes int, elapsed time.Duration) {
|
||||
func (l *defaultLogEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) {
|
||||
switch {
|
||||
case status < 200:
|
||||
cW(l.buf, l.useColor, bBlue, "%03d", status)
|
||||
@@ -151,8 +151,5 @@ func (l *defaultLogEntry) Write(status, bytes int, elapsed time.Duration) {
|
||||
}
|
||||
|
||||
func (l *defaultLogEntry) Panic(v interface{}, stack []byte) {
|
||||
panicEntry := l.NewLogEntry(l.request).(*defaultLogEntry)
|
||||
cW(panicEntry.buf, l.useColor, bRed, "panic: %+v", v)
|
||||
l.Logger.Print(panicEntry.buf.String())
|
||||
l.Logger.Print(string(stack))
|
||||
PrintPrettyStack(v)
|
||||
}
|
||||
|
||||
+3
-3
@@ -40,14 +40,14 @@ func RealIP(h http.Handler) http.Handler {
|
||||
func realIP(r *http.Request) string {
|
||||
var ip string
|
||||
|
||||
if xff := r.Header.Get(xForwardedFor); xff != "" {
|
||||
if xrip := r.Header.Get(xRealIP); xrip != "" {
|
||||
ip = xrip
|
||||
} else if xff := r.Header.Get(xForwardedFor); xff != "" {
|
||||
i := strings.Index(xff, ", ")
|
||||
if i == -1 {
|
||||
i = len(xff)
|
||||
}
|
||||
ip = xff[:i]
|
||||
} else if xrip := r.Header.Get(xRealIP); xrip != "" {
|
||||
ip = xrip
|
||||
}
|
||||
|
||||
return ip
|
||||
|
||||
+157
-4
@@ -4,10 +4,13 @@ package middleware
|
||||
// https://github.com/zenazn/goji/tree/master/web/middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Recoverer is a middleware that recovers from panics, logs the panic (and a
|
||||
@@ -18,17 +21,16 @@ import (
|
||||
func Recoverer(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if rvr := recover(); rvr != nil {
|
||||
if rvr := recover(); rvr != nil && rvr != http.ErrAbortHandler {
|
||||
|
||||
logEntry := GetLogEntry(r)
|
||||
if logEntry != nil {
|
||||
logEntry.Panic(rvr, debug.Stack())
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Panic: %+v\n", rvr)
|
||||
debug.PrintStack()
|
||||
PrintPrettyStack(rvr)
|
||||
}
|
||||
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -37,3 +39,154 @@ func Recoverer(next http.Handler) http.Handler {
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
func PrintPrettyStack(rvr interface{}) {
|
||||
debugStack := debug.Stack()
|
||||
s := prettyStack{}
|
||||
out, err := s.parse(debugStack, rvr)
|
||||
if err == nil {
|
||||
os.Stderr.Write(out)
|
||||
} else {
|
||||
// print stdlib output as a fallback
|
||||
os.Stderr.Write(debugStack)
|
||||
}
|
||||
}
|
||||
|
||||
type prettyStack struct {
|
||||
}
|
||||
|
||||
func (s prettyStack) parse(debugStack []byte, rvr interface{}) ([]byte, error) {
|
||||
var err error
|
||||
useColor := true
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
cW(buf, false, bRed, "\n")
|
||||
cW(buf, useColor, bCyan, " panic: ")
|
||||
cW(buf, useColor, bBlue, "%v", rvr)
|
||||
cW(buf, false, bWhite, "\n \n")
|
||||
|
||||
// process debug stack info
|
||||
stack := strings.Split(string(debugStack), "\n")
|
||||
lines := []string{}
|
||||
|
||||
// locate panic line, as we may have nested panics
|
||||
for i := len(stack) - 1; i > 0; i-- {
|
||||
lines = append(lines, stack[i])
|
||||
if strings.HasPrefix(stack[i], "panic(0x") {
|
||||
lines = lines[0 : len(lines)-2] // remove boilerplate
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// reverse
|
||||
for i := len(lines)/2 - 1; i >= 0; i-- {
|
||||
opp := len(lines) - 1 - i
|
||||
lines[i], lines[opp] = lines[opp], lines[i]
|
||||
}
|
||||
|
||||
// decorate
|
||||
for i, line := range lines {
|
||||
lines[i], err = s.decorateLine(line, useColor, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, l := range lines {
|
||||
fmt.Fprintf(buf, "%s", l)
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (s prettyStack) decorateLine(line string, useColor bool, num int) (string, error) {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "\t") || strings.Contains(line, ".go:") {
|
||||
return s.decorateSourceLine(line, useColor, num)
|
||||
} else if strings.HasSuffix(line, ")") {
|
||||
return s.decorateFuncCallLine(line, useColor, num)
|
||||
} else {
|
||||
if strings.HasPrefix(line, "\t") {
|
||||
return strings.Replace(line, "\t", " ", 1), nil
|
||||
} else {
|
||||
return fmt.Sprintf(" %s\n", line), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s prettyStack) decorateFuncCallLine(line string, useColor bool, num int) (string, error) {
|
||||
idx := strings.LastIndex(line, "(")
|
||||
if idx < 0 {
|
||||
return "", errors.New("not a func call line")
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
pkg := line[0:idx]
|
||||
// addr := line[idx:]
|
||||
method := ""
|
||||
|
||||
idx = strings.LastIndex(pkg, string(os.PathSeparator))
|
||||
if idx < 0 {
|
||||
idx = strings.Index(pkg, ".")
|
||||
method = pkg[idx:]
|
||||
pkg = pkg[0:idx]
|
||||
} else {
|
||||
method = pkg[idx+1:]
|
||||
pkg = pkg[0 : idx+1]
|
||||
idx = strings.Index(method, ".")
|
||||
pkg += method[0:idx]
|
||||
method = method[idx:]
|
||||
}
|
||||
pkgColor := nYellow
|
||||
methodColor := bGreen
|
||||
|
||||
if num == 0 {
|
||||
cW(buf, useColor, bRed, " -> ")
|
||||
pkgColor = bMagenta
|
||||
methodColor = bRed
|
||||
} else {
|
||||
cW(buf, useColor, bWhite, " ")
|
||||
}
|
||||
cW(buf, useColor, pkgColor, "%s", pkg)
|
||||
cW(buf, useColor, methodColor, "%s\n", method)
|
||||
// cW(buf, useColor, nBlack, "%s", addr)
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func (s prettyStack) decorateSourceLine(line string, useColor bool, num int) (string, error) {
|
||||
idx := strings.LastIndex(line, ".go:")
|
||||
if idx < 0 {
|
||||
return "", errors.New("not a source line")
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
path := line[0 : idx+3]
|
||||
lineno := line[idx+3:]
|
||||
|
||||
idx = strings.LastIndex(path, string(os.PathSeparator))
|
||||
dir := path[0 : idx+1]
|
||||
file := path[idx+1:]
|
||||
|
||||
idx = strings.Index(lineno, " ")
|
||||
if idx > 0 {
|
||||
lineno = lineno[0:idx]
|
||||
}
|
||||
fileColor := bCyan
|
||||
lineColor := bGreen
|
||||
|
||||
if num == 1 {
|
||||
cW(buf, useColor, bRed, " -> ")
|
||||
fileColor = bRed
|
||||
lineColor = bMagenta
|
||||
} else {
|
||||
cW(buf, false, bWhite, " ")
|
||||
}
|
||||
cW(buf, useColor, bWhite, "%s", dir)
|
||||
cW(buf, useColor, fileColor, "%s", file)
|
||||
cW(buf, useColor, lineColor, "%s", lineno)
|
||||
if num == 1 {
|
||||
cW(buf, false, bWhite, "\n")
|
||||
}
|
||||
cW(buf, false, bWhite, "\n")
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
+5
-1
@@ -20,6 +20,10 @@ type ctxKeyRequestID int
|
||||
// RequestIDKey is the key that holds the unique request ID in a request context.
|
||||
const RequestIDKey ctxKeyRequestID = 0
|
||||
|
||||
// RequestIDHeader is the name of the HTTP Header which contains the request id.
|
||||
// Exported so that it can be changed by developers
|
||||
var RequestIDHeader = "X-Request-Id"
|
||||
|
||||
var prefix string
|
||||
var reqid uint64
|
||||
|
||||
@@ -63,7 +67,7 @@ func init() {
|
||||
func RequestID(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
requestID := r.Header.Get("X-Request-Id")
|
||||
requestID := r.Header.Get(RequestIDHeader)
|
||||
if requestID == "" {
|
||||
myid := atomic.AddUint64(&reqid, 1)
|
||||
requestID = fmt.Sprintf("%s-%06d", prefix, myid)
|
||||
|
||||
+4
-4
@@ -32,7 +32,7 @@ var (
|
||||
reset = []byte{'\033', '[', '0', 'm'}
|
||||
)
|
||||
|
||||
var isTTY bool
|
||||
var IsTTY bool
|
||||
|
||||
func init() {
|
||||
// This is sort of cheating: if stdout is a character device, we assume
|
||||
@@ -47,17 +47,17 @@ func init() {
|
||||
fi, err := os.Stdout.Stat()
|
||||
if err == nil {
|
||||
m := os.ModeDevice | os.ModeCharDevice
|
||||
isTTY = fi.Mode()&m == m
|
||||
IsTTY = fi.Mode()&m == m
|
||||
}
|
||||
}
|
||||
|
||||
// colorWrite
|
||||
func cW(w io.Writer, useColor bool, color []byte, s string, args ...interface{}) {
|
||||
if isTTY && useColor {
|
||||
if IsTTY && useColor {
|
||||
w.Write(color)
|
||||
}
|
||||
fmt.Fprintf(w, s, args...)
|
||||
if isTTY && useColor {
|
||||
if IsTTY && useColor {
|
||||
w.Write(reset)
|
||||
}
|
||||
}
|
||||
|
||||
+76
-45
@@ -2,6 +2,7 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -15,44 +16,100 @@ var (
|
||||
defaultBacklogTimeout = time.Second * 60
|
||||
)
|
||||
|
||||
// ThrottleOpts represents a set of throttling options.
|
||||
type ThrottleOpts struct {
|
||||
Limit int
|
||||
BacklogLimit int
|
||||
BacklogTimeout time.Duration
|
||||
RetryAfterFn func(ctxDone bool) time.Duration
|
||||
}
|
||||
|
||||
// Throttle is a middleware that limits number of currently processed requests
|
||||
// at a time.
|
||||
// at a time across all users. Note: Throttle is not a rate-limiter per user,
|
||||
// instead it just puts a ceiling on the number of currentl in-flight requests
|
||||
// being processed from the point from where the Throttle middleware is mounted.
|
||||
func Throttle(limit int) func(http.Handler) http.Handler {
|
||||
return ThrottleBacklog(limit, 0, defaultBacklogTimeout)
|
||||
return ThrottleWithOpts(ThrottleOpts{Limit: limit, BacklogTimeout: defaultBacklogTimeout})
|
||||
}
|
||||
|
||||
// ThrottleBacklog is a middleware that limits number of currently processed
|
||||
// requests at a time and provides a backlog for holding a finite number of
|
||||
// pending requests.
|
||||
func ThrottleBacklog(limit int, backlogLimit int, backlogTimeout time.Duration) func(http.Handler) http.Handler {
|
||||
if limit < 1 {
|
||||
return ThrottleWithOpts(ThrottleOpts{Limit: limit, BacklogLimit: backlogLimit, BacklogTimeout: backlogTimeout})
|
||||
}
|
||||
|
||||
// ThrottleWithOpts is a middleware that limits number of currently processed requests using passed ThrottleOpts.
|
||||
func ThrottleWithOpts(opts ThrottleOpts) func(http.Handler) http.Handler {
|
||||
if opts.Limit < 1 {
|
||||
panic("chi/middleware: Throttle expects limit > 0")
|
||||
}
|
||||
|
||||
if backlogLimit < 0 {
|
||||
if opts.BacklogLimit < 0 {
|
||||
panic("chi/middleware: Throttle expects backlogLimit to be positive")
|
||||
}
|
||||
|
||||
t := throttler{
|
||||
tokens: make(chan token, limit),
|
||||
backlogTokens: make(chan token, limit+backlogLimit),
|
||||
backlogTimeout: backlogTimeout,
|
||||
tokens: make(chan token, opts.Limit),
|
||||
backlogTokens: make(chan token, opts.Limit+opts.BacklogLimit),
|
||||
backlogTimeout: opts.BacklogTimeout,
|
||||
retryAfterFn: opts.RetryAfterFn,
|
||||
}
|
||||
|
||||
// Filling tokens.
|
||||
for i := 0; i < limit+backlogLimit; i++ {
|
||||
if i < limit {
|
||||
for i := 0; i < opts.Limit+opts.BacklogLimit; i++ {
|
||||
if i < opts.Limit {
|
||||
t.tokens <- token{}
|
||||
}
|
||||
t.backlogTokens <- token{}
|
||||
}
|
||||
|
||||
fn := func(h http.Handler) http.Handler {
|
||||
t.h = h
|
||||
return &t
|
||||
}
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
return fn
|
||||
select {
|
||||
|
||||
case <-ctx.Done():
|
||||
t.setRetryAfterHeaderIfNeeded(w, true)
|
||||
http.Error(w, errContextCanceled, http.StatusServiceUnavailable)
|
||||
return
|
||||
|
||||
case btok := <-t.backlogTokens:
|
||||
timer := time.NewTimer(t.backlogTimeout)
|
||||
|
||||
defer func() {
|
||||
t.backlogTokens <- btok
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-timer.C:
|
||||
t.setRetryAfterHeaderIfNeeded(w, false)
|
||||
http.Error(w, errTimedOut, http.StatusServiceUnavailable)
|
||||
return
|
||||
case <-ctx.Done():
|
||||
timer.Stop()
|
||||
t.setRetryAfterHeaderIfNeeded(w, true)
|
||||
http.Error(w, errContextCanceled, http.StatusServiceUnavailable)
|
||||
return
|
||||
case tok := <-t.tokens:
|
||||
defer func() {
|
||||
timer.Stop()
|
||||
t.tokens <- tok
|
||||
}()
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
return
|
||||
|
||||
default:
|
||||
t.setRetryAfterHeaderIfNeeded(w, false)
|
||||
http.Error(w, errCapacityExceeded, http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
// token represents a request that is being processed.
|
||||
@@ -60,42 +117,16 @@ type token struct{}
|
||||
|
||||
// throttler limits number of currently processed requests at a time.
|
||||
type throttler struct {
|
||||
h http.Handler
|
||||
tokens chan token
|
||||
backlogTokens chan token
|
||||
backlogTimeout time.Duration
|
||||
retryAfterFn func(ctxDone bool) time.Duration
|
||||
}
|
||||
|
||||
// ServeHTTP is the primary throttler request handler
|
||||
func (t *throttler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
http.Error(w, errContextCanceled, http.StatusServiceUnavailable)
|
||||
return
|
||||
case btok := <-t.backlogTokens:
|
||||
timer := time.NewTimer(t.backlogTimeout)
|
||||
|
||||
defer func() {
|
||||
t.backlogTokens <- btok
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-timer.C:
|
||||
http.Error(w, errTimedOut, http.StatusServiceUnavailable)
|
||||
return
|
||||
case <-ctx.Done():
|
||||
http.Error(w, errContextCanceled, http.StatusServiceUnavailable)
|
||||
return
|
||||
case tok := <-t.tokens:
|
||||
defer func() {
|
||||
t.tokens <- tok
|
||||
}()
|
||||
t.h.ServeHTTP(w, r)
|
||||
}
|
||||
return
|
||||
default:
|
||||
http.Error(w, errCapacityExceeded, http.StatusServiceUnavailable)
|
||||
// setRetryAfterHeaderIfNeeded sets Retry-After HTTP header if corresponding retryAfterFn option of throttler is initialized.
|
||||
func (t throttler) setRetryAfterHeaderIfNeeded(w http.ResponseWriter, ctxDone bool) {
|
||||
if t.retryAfterFn == nil {
|
||||
return
|
||||
}
|
||||
w.Header().Set("Retry-After", strconv.Itoa(int(t.retryAfterFn(ctxDone).Seconds())))
|
||||
}
|
||||
|
||||
+1
-4
@@ -75,7 +75,7 @@ func (b *basicWriter) WriteHeader(code int) {
|
||||
}
|
||||
|
||||
func (b *basicWriter) Write(buf []byte) (int, error) {
|
||||
b.WriteHeader(http.StatusOK)
|
||||
b.maybeWriteHeader()
|
||||
n, err := b.ResponseWriter.Write(buf)
|
||||
if b.tee != nil {
|
||||
_, err2 := b.tee.Write(buf[:n])
|
||||
@@ -116,7 +116,6 @@ type flushWriter struct {
|
||||
|
||||
func (f *flushWriter) Flush() {
|
||||
f.wroteHeader = true
|
||||
|
||||
fl := f.basicWriter.ResponseWriter.(http.Flusher)
|
||||
fl.Flush()
|
||||
}
|
||||
@@ -133,7 +132,6 @@ type httpFancyWriter struct {
|
||||
|
||||
func (f *httpFancyWriter) Flush() {
|
||||
f.wroteHeader = true
|
||||
|
||||
fl := f.basicWriter.ResponseWriter.(http.Flusher)
|
||||
fl.Flush()
|
||||
}
|
||||
@@ -175,7 +173,6 @@ type http2FancyWriter struct {
|
||||
|
||||
func (f *http2FancyWriter) Flush() {
|
||||
f.wroteHeader = true
|
||||
|
||||
fl := f.basicWriter.ResponseWriter.(http.Flusher)
|
||||
fl.Flush()
|
||||
}
|
||||
|
||||
+11
-5
@@ -78,7 +78,11 @@ func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
rctx = mx.pool.Get().(*Context)
|
||||
rctx.Reset()
|
||||
rctx.Routes = mx
|
||||
|
||||
// NOTE: r.WithContext() causes 2 allocations and context.WithValue() causes 1 allocation
|
||||
r = r.WithContext(context.WithValue(r.Context(), RouteCtxKey, rctx))
|
||||
|
||||
// Serve the request and once its done, put the request context back in the sync pool
|
||||
mx.handler.ServeHTTP(w, r)
|
||||
mx.pool.Put(rctx)
|
||||
}
|
||||
@@ -220,7 +224,7 @@ func (mx *Mux) MethodNotAllowed(handlerFn http.HandlerFunc) {
|
||||
|
||||
// With adds inline middlewares for an endpoint handler.
|
||||
func (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router {
|
||||
// Similarly as in handle(), we must build the mux handler once further
|
||||
// Similarly as in handle(), we must build the mux handler once additional
|
||||
// middleware registration isn't allowed for this stack, like now.
|
||||
if !mx.inline && mx.handler == nil {
|
||||
mx.buildRouteHandler()
|
||||
@@ -234,7 +238,10 @@ func (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router {
|
||||
}
|
||||
mws = append(mws, middlewares...)
|
||||
|
||||
im := &Mux{pool: mx.pool, inline: true, parent: mx, tree: mx.tree, middlewares: mws}
|
||||
im := &Mux{
|
||||
pool: mx.pool, inline: true, parent: mx, tree: mx.tree, middlewares: mws,
|
||||
notFoundHandler: mx.notFoundHandler, methodNotAllowedHandler: mx.methodNotAllowedHandler,
|
||||
}
|
||||
|
||||
return im
|
||||
}
|
||||
@@ -285,7 +292,6 @@ func (mx *Mux) Mount(pattern string, handler http.Handler) {
|
||||
subr.MethodNotAllowed(mx.methodNotAllowedHandler)
|
||||
}
|
||||
|
||||
// Wrap the sub-router in a handlerFunc to scope the request path for routing.
|
||||
mountHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
rctx := RouteContext(r.Context())
|
||||
rctx.RoutePath = mx.nextRoutePath(rctx)
|
||||
@@ -376,7 +382,7 @@ func (mx *Mux) handle(method methodTyp, pattern string, handler http.Handler) *n
|
||||
panic(fmt.Sprintf("chi: routing pattern must begin with '/' in '%s'", pattern))
|
||||
}
|
||||
|
||||
// Build the final routing handler for this Mux.
|
||||
// Build the computed routing handler for this routing pattern.
|
||||
if !mx.inline && mx.handler == nil {
|
||||
mx.buildRouteHandler()
|
||||
}
|
||||
@@ -436,7 +442,7 @@ func (mx *Mux) nextRoutePath(rctx *Context) string {
|
||||
routePath := "/"
|
||||
nx := len(rctx.routeParams.Keys) - 1 // index of last param in list
|
||||
if nx >= 0 && rctx.routeParams.Keys[nx] == "*" && len(rctx.routeParams.Values) > nx {
|
||||
routePath += rctx.routeParams.Values[nx]
|
||||
routePath = "/" + rctx.routeParams.Values[nx]
|
||||
}
|
||||
return routePath
|
||||
}
|
||||
|
||||
+15
-15
@@ -331,7 +331,7 @@ func (n *node) getEdge(ntyp nodeTyp, label, tail byte, prefix string) *node {
|
||||
func (n *node) setEndpoint(method methodTyp, handler http.Handler, pattern string) {
|
||||
// Set the handler for the method type on the node
|
||||
if n.endpoints == nil {
|
||||
n.endpoints = make(endpoints, 0)
|
||||
n.endpoints = make(endpoints)
|
||||
}
|
||||
|
||||
paramKeys := patParamKeys(pattern)
|
||||
@@ -417,6 +417,8 @@ func (n *node) findRoute(rctx *Context, method methodTyp, path string) *node {
|
||||
continue
|
||||
}
|
||||
|
||||
found := false
|
||||
|
||||
// serially loop through each node grouped by the tail delimiter
|
||||
for idx := 0; idx < len(nds); idx++ {
|
||||
xn = nds[idx]
|
||||
@@ -433,7 +435,7 @@ func (n *node) findRoute(rctx *Context, method methodTyp, path string) *node {
|
||||
}
|
||||
|
||||
if ntyp == ntRegexp && xn.rex != nil {
|
||||
if xn.rex.Match([]byte(xsearch[:p])) == false {
|
||||
if !xn.rex.Match([]byte(xsearch[:p])) {
|
||||
continue
|
||||
}
|
||||
} else if strings.IndexByte(xsearch[:p], '/') != -1 {
|
||||
@@ -443,9 +445,14 @@ func (n *node) findRoute(rctx *Context, method methodTyp, path string) *node {
|
||||
|
||||
rctx.routeParams.Values = append(rctx.routeParams.Values, xsearch[:p])
|
||||
xsearch = xsearch[p:]
|
||||
found = true
|
||||
break
|
||||
}
|
||||
|
||||
if !found {
|
||||
rctx.routeParams.Values = append(rctx.routeParams.Values, "")
|
||||
}
|
||||
|
||||
default:
|
||||
// catch-all nodes
|
||||
rctx.routeParams.Values = append(rctx.routeParams.Values, search)
|
||||
@@ -460,7 +467,7 @@ func (n *node) findRoute(rctx *Context, method methodTyp, path string) *node {
|
||||
// did we find it yet?
|
||||
if len(xsearch) == 0 {
|
||||
if xn.isLeaf() {
|
||||
h, _ := xn.endpoints[method]
|
||||
h := xn.endpoints[method]
|
||||
if h != nil && h.handler != nil {
|
||||
rctx.routeParams.Keys = append(rctx.routeParams.Keys, h.paramKeys...)
|
||||
return xn
|
||||
@@ -518,15 +525,6 @@ func (n *node) findEdge(ntyp nodeTyp, label byte) *node {
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) isEmpty() bool {
|
||||
for _, nds := range n.children {
|
||||
if len(nds) > 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (n *node) isLeaf() bool {
|
||||
return n.endpoints != nil
|
||||
}
|
||||
@@ -582,7 +580,7 @@ func (n *node) routes() []Route {
|
||||
}
|
||||
|
||||
// Group methodHandlers by unique patterns
|
||||
pats := make(map[string]endpoints, 0)
|
||||
pats := make(map[string]endpoints)
|
||||
|
||||
for mt, h := range eps {
|
||||
if h.pattern == "" {
|
||||
@@ -597,7 +595,7 @@ func (n *node) routes() []Route {
|
||||
}
|
||||
|
||||
for p, mh := range pats {
|
||||
hs := make(map[string]http.Handler, 0)
|
||||
hs := make(map[string]http.Handler)
|
||||
if mh[mALL] != nil && mh[mALL].handler != nil {
|
||||
hs["*"] = mh[mALL].handler
|
||||
}
|
||||
@@ -698,7 +696,7 @@ func patNextSegment(pattern string) (nodeTyp, string, string, byte, int, int) {
|
||||
rexpat = "^" + rexpat
|
||||
}
|
||||
if rexpat[len(rexpat)-1] != '$' {
|
||||
rexpat = rexpat + "$"
|
||||
rexpat += "$"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -795,6 +793,7 @@ func (ns nodes) findEdge(label byte) *node {
|
||||
}
|
||||
|
||||
// Route describes the details of a routing handler.
|
||||
// Handlers map key is an HTTP method
|
||||
type Route struct {
|
||||
Pattern string
|
||||
Handlers map[string]http.Handler
|
||||
@@ -829,6 +828,7 @@ func walk(r Routes, walkFn WalkFunc, parentRoute string, parentMw ...func(http.H
|
||||
}
|
||||
|
||||
fullRoute := parentRoute + route.Pattern
|
||||
fullRoute = strings.Replace(fullRoute, "/*/", "/", -1)
|
||||
|
||||
if chain, ok := handler.(*ChainHandler); ok {
|
||||
if err := walkFn(method, fullRoute, chain.Endpoint, append(mws, chain.Middlewares...)...); err != nil {
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
Copyright (c) 2014 Olivier Poitrey <rs@dailymotion.com>
|
||||
Copyright (c) 2016-Present https://github.com/go-chi authors
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+14
-7
@@ -1,6 +1,11 @@
|
||||
# CORS net/http middleware
|
||||
|
||||
(fork of github.com/rs/cors)
|
||||
[go-chi/cors](https://github.com/go-chi/cors) is a fork of [github.com/rs/cors](https://github.com/rs/cors) that
|
||||
provides a `net/http` compatible middleware for performing preflight CORS checks on the server side. These headers
|
||||
are required for using the browser native [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
|
||||
|
||||
This middleware is designed to be used as a top-level middleware on the [chi](https://github.com/go-chi/chi) router.
|
||||
Applying with within a `r.Group()` or using `With()` will not work without routes matching `OPTIONS` added.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -10,23 +15,25 @@ func main() {
|
||||
|
||||
// Basic CORS
|
||||
// for more ideas, see: https://developer.github.com/v3/#cross-origin-resource-sharing
|
||||
cors := cors.New(cors.Options{
|
||||
r.Use(cors.Handler(cors.Options{
|
||||
// AllowedOrigins: []string{"https://foo.com"}, // Use this to allow specific origin hosts
|
||||
AllowedOrigins: []string{"*"},
|
||||
// AllowOriginFunc: func(r *http.Request, origin string) bool { return true },
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
||||
ExposedHeaders: []string{"Link"},
|
||||
AllowCredentials: true,
|
||||
AllowCredentials: false,
|
||||
MaxAge: 300, // Maximum value not ignored by any of major browsers
|
||||
})
|
||||
r.Use(cors.Handler)
|
||||
|
||||
}))
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("welcome"))
|
||||
})
|
||||
|
||||
|
||||
http.ListenAndServe(":3000", r)
|
||||
}
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
||||
All credit for the original work of this middleware goes out to [github.com/rs](github.com/rs).
|
||||
|
||||
+69
-38
@@ -31,18 +31,18 @@ type Options struct {
|
||||
// AllowedOrigins is a list of origins a cross-domain request can be executed from.
|
||||
// If the special "*" value is present in the list, all origins will be allowed.
|
||||
// An origin may contain a wildcard (*) to replace 0 or more characters
|
||||
// (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penality.
|
||||
// (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penalty.
|
||||
// Only one wildcard can be used per origin.
|
||||
// Default value is ["*"]
|
||||
AllowedOrigins []string
|
||||
|
||||
// AllowOriginFunc is a custom function to validate the origin. It take the origin
|
||||
// AllowOriginFunc is a custom function to validate the origin. It takes the origin
|
||||
// as argument and returns true if allowed or false otherwise. If this option is
|
||||
// set, the content of AllowedOrigins is ignored.
|
||||
AllowOriginFunc func(r *http.Request, origin string) bool
|
||||
|
||||
// AllowedMethods is a list of methods the client is allowed to use with
|
||||
// cross-domain requests. Default value is simple methods (GET and POST)
|
||||
// cross-domain requests. Default value is simple methods (HEAD, GET and POST).
|
||||
AllowedMethods []string
|
||||
|
||||
// AllowedHeaders is list of non simple headers the client is allowed to use with
|
||||
@@ -71,13 +71,15 @@ type Options struct {
|
||||
Debug bool
|
||||
}
|
||||
|
||||
// Logger generic interface for logger
|
||||
type Logger interface {
|
||||
Printf(string, ...interface{})
|
||||
}
|
||||
|
||||
// Cors http handler
|
||||
type Cors struct {
|
||||
// Debug logger
|
||||
log *log.Logger
|
||||
|
||||
// Set to true when allowed origins contains a "*"
|
||||
allowedOriginsAll bool
|
||||
Log Logger
|
||||
|
||||
// Normalized list of plain allowed origins
|
||||
allowedOrigins []string
|
||||
@@ -88,9 +90,6 @@ type Cors struct {
|
||||
// Optional origin validator function
|
||||
allowOriginFunc func(r *http.Request, origin string) bool
|
||||
|
||||
// Set to true when allowed headers contains a "*"
|
||||
allowedHeadersAll bool
|
||||
|
||||
// Normalized list of allowed headers
|
||||
allowedHeaders []string
|
||||
|
||||
@@ -98,9 +97,16 @@ type Cors struct {
|
||||
allowedMethods []string
|
||||
|
||||
// Normalized list of exposed headers
|
||||
exposedHeaders []string
|
||||
exposedHeaders []string
|
||||
maxAge int
|
||||
|
||||
// Set to true when allowed origins contains a "*"
|
||||
allowedOriginsAll bool
|
||||
|
||||
// Set to true when allowed headers contains a "*"
|
||||
allowedHeadersAll bool
|
||||
|
||||
allowCredentials bool
|
||||
maxAge int
|
||||
optionPassthrough bool
|
||||
}
|
||||
|
||||
@@ -113,8 +119,8 @@ func New(options Options) *Cors {
|
||||
maxAge: options.MaxAge,
|
||||
optionPassthrough: options.OptionsPassthrough,
|
||||
}
|
||||
if options.Debug {
|
||||
c.log = log.New(os.Stdout, "[cors] ", log.LstdFlags)
|
||||
if options.Debug && c.Log == nil {
|
||||
c.Log = log.New(os.Stdout, "[cors] ", log.LstdFlags)
|
||||
}
|
||||
|
||||
// Normalize options
|
||||
@@ -123,8 +129,10 @@ func New(options Options) *Cors {
|
||||
|
||||
// Allowed Origins
|
||||
if len(options.AllowedOrigins) == 0 {
|
||||
// Default is all origins
|
||||
c.allowedOriginsAll = true
|
||||
if options.AllowOriginFunc == nil {
|
||||
// Default is all origins
|
||||
c.allowedOriginsAll = true
|
||||
}
|
||||
} else {
|
||||
c.allowedOrigins = []string{}
|
||||
c.allowedWOrigins = []wildcard{}
|
||||
@@ -139,7 +147,7 @@ func New(options Options) *Cors {
|
||||
break
|
||||
} else if i := strings.IndexByte(origin, '*'); i >= 0 {
|
||||
// Split the origin in two: start and end string without the *
|
||||
w := wildcard{origin[0:i], origin[i+1 : len(origin)]}
|
||||
w := wildcard{origin[0:i], origin[i+1:]}
|
||||
c.allowedWOrigins = append(c.allowedWOrigins, w)
|
||||
} else {
|
||||
c.allowedOrigins = append(c.allowedOrigins, origin)
|
||||
@@ -166,7 +174,7 @@ func New(options Options) *Cors {
|
||||
// Allowed Methods
|
||||
if len(options.AllowedMethods) == 0 {
|
||||
// Default is spec's "simple" methods
|
||||
c.allowedMethods = []string{"GET", "POST"}
|
||||
c.allowedMethods = []string{http.MethodGet, http.MethodPost, http.MethodHead}
|
||||
} else {
|
||||
c.allowedMethods = convert(options.AllowedMethods, strings.ToUpper)
|
||||
}
|
||||
@@ -174,16 +182,35 @@ func New(options Options) *Cors {
|
||||
return c
|
||||
}
|
||||
|
||||
// Default creates a new Cors handler with default options
|
||||
func Default() *Cors {
|
||||
return New(Options{})
|
||||
// Handler creates a new Cors handler with passed options.
|
||||
func Handler(options Options) func(next http.Handler) http.Handler {
|
||||
c := New(options)
|
||||
return c.Handler
|
||||
}
|
||||
|
||||
// AllowAll create a new Cors handler with permissive configuration allowing all
|
||||
// origins with all standard methods with any header and credentials.
|
||||
func AllowAll() *Cors {
|
||||
return New(Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedMethods: []string{
|
||||
http.MethodHead,
|
||||
http.MethodGet,
|
||||
http.MethodPost,
|
||||
http.MethodPut,
|
||||
http.MethodPatch,
|
||||
http.MethodDelete,
|
||||
},
|
||||
AllowedHeaders: []string{"*"},
|
||||
AllowCredentials: false,
|
||||
})
|
||||
}
|
||||
|
||||
// Handler apply the CORS specification on the request, and add relevant CORS headers
|
||||
// as necessary.
|
||||
func (c *Cors) Handler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "OPTIONS" {
|
||||
if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {
|
||||
c.logf("Handler: Preflight request")
|
||||
c.handlePreflight(w, r)
|
||||
// Preflight requests are standalone and should stop the chain as some other
|
||||
@@ -192,6 +219,8 @@ func (c *Cors) Handler(next http.Handler) http.Handler {
|
||||
// headers (see #1)
|
||||
if c.optionPassthrough {
|
||||
next.ServeHTTP(w, r)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
} else {
|
||||
c.logf("Handler: Actual request")
|
||||
@@ -206,7 +235,7 @@ func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) {
|
||||
headers := w.Header()
|
||||
origin := r.Header.Get("Origin")
|
||||
|
||||
if r.Method != "OPTIONS" {
|
||||
if r.Method != http.MethodOptions {
|
||||
c.logf("Preflight aborted: %s!=OPTIONS", r.Method)
|
||||
return
|
||||
}
|
||||
@@ -236,7 +265,11 @@ func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) {
|
||||
c.logf("Preflight aborted: headers '%v' not allowed", reqHeaders)
|
||||
return
|
||||
}
|
||||
headers.Set("Access-Control-Allow-Origin", origin)
|
||||
if c.allowedOriginsAll {
|
||||
headers.Set("Access-Control-Allow-Origin", "*")
|
||||
} else {
|
||||
headers.Set("Access-Control-Allow-Origin", origin)
|
||||
}
|
||||
// Spec says: Since the list of methods can be unbounded, simply returning the method indicated
|
||||
// by Access-Control-Request-Method (if supported) can be enough
|
||||
headers.Set("Access-Control-Allow-Methods", strings.ToUpper(reqMethod))
|
||||
@@ -260,10 +293,6 @@ func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) {
|
||||
headers := w.Header()
|
||||
origin := r.Header.Get("Origin")
|
||||
|
||||
if r.Method == "OPTIONS" {
|
||||
c.logf("Actual request no headers added: method == %s", r.Method)
|
||||
return
|
||||
}
|
||||
// Always set Vary, see https://github.com/rs/cors/issues/10
|
||||
headers.Add("Vary", "Origin")
|
||||
if origin == "" {
|
||||
@@ -280,14 +309,15 @@ func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) {
|
||||
// spec doesn't instruct to check the allowed methods for simple cross-origin requests.
|
||||
// We think it's a nice feature to be able to have control on those methods though.
|
||||
if !c.isMethodAllowed(r.Method) {
|
||||
if c.log != nil {
|
||||
c.logf("Actual request no headers added: method '%s' not allowed",
|
||||
r.Method)
|
||||
}
|
||||
c.logf("Actual request no headers added: method '%s' not allowed", r.Method)
|
||||
|
||||
return
|
||||
}
|
||||
headers.Set("Access-Control-Allow-Origin", origin)
|
||||
if c.allowedOriginsAll {
|
||||
headers.Set("Access-Control-Allow-Origin", "*")
|
||||
} else {
|
||||
headers.Set("Access-Control-Allow-Origin", origin)
|
||||
}
|
||||
if len(c.exposedHeaders) > 0 {
|
||||
headers.Set("Access-Control-Expose-Headers", strings.Join(c.exposedHeaders, ", "))
|
||||
}
|
||||
@@ -297,10 +327,10 @@ func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) {
|
||||
c.logf("Actual response added headers: %v", headers)
|
||||
}
|
||||
|
||||
// convenience method. checks if debugging is turned on before printing
|
||||
// convenience method. checks if a logger is set.
|
||||
func (c *Cors) logf(format string, a ...interface{}) {
|
||||
if c.log != nil {
|
||||
c.log.Printf(format, a...)
|
||||
if c.Log != nil {
|
||||
c.Log.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,14 +358,14 @@ func (c *Cors) isOriginAllowed(r *http.Request, origin string) bool {
|
||||
}
|
||||
|
||||
// isMethodAllowed checks if a given method can be used as part of a cross-domain request
|
||||
// on the endpoing
|
||||
// on the endpoint
|
||||
func (c *Cors) isMethodAllowed(method string) bool {
|
||||
if len(c.allowedMethods) == 0 {
|
||||
// If no method allowed, always return false, even for preflight request
|
||||
return false
|
||||
}
|
||||
method = strings.ToUpper(method)
|
||||
if method == "OPTIONS" {
|
||||
if method == http.MethodOptions {
|
||||
// Always allow preflight requests
|
||||
return true
|
||||
}
|
||||
@@ -359,6 +389,7 @@ func (c *Cors) areHeadersAllowed(requestedHeaders []string) bool {
|
||||
for _, h := range c.allowedHeaders {
|
||||
if h == header {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
language: go
|
||||
|
||||
services:
|
||||
- mongodb
|
||||
|
||||
go:
|
||||
- "1.13.x"
|
||||
|
||||
install: true
|
||||
|
||||
before_install:
|
||||
- export TZ=America/Chicago
|
||||
- curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.19.1
|
||||
- golangci-lint --version
|
||||
- go get github.com/mattn/goveralls
|
||||
- export MONGO_TEST=mongodb://127.0.0.1:27017
|
||||
- export PATH=$(pwd)/bin:$PATH
|
||||
|
||||
script:
|
||||
- go get ./...
|
||||
- go mod vendor
|
||||
- go test -v -mod=vendor -covermode=count -coverprofile=profile.cov ./... || travis_terminate 1;
|
||||
- golangci-lint run --tests=false || travis_terminate 1;
|
||||
- $GOPATH/bin/goveralls -coverprofile=profile.cov -service=travis-ci
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Umputun
|
||||
Copyright (c) 2020 Umputun
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
# auth - authentication via oauth2, direct and email
|
||||
[](https://travis-ci.org/go-pkgz/auth) [](https://coveralls.io/github/go-pkgz/auth?branch=master) [](https://godoc.org/github.com/go-pkgz/auth)
|
||||
[](https://github.com/go-pkgz/auth/actions) [](https://coveralls.io/github/go-pkgz/auth?branch=master) [](https://pkg.go.dev/github.com/go-pkgz/auth?tab=doc)
|
||||
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ Direct links to avatars won't survive any real-life usage if they linked from a
|
||||
- API for avatar removal provided as a part of `AvatarStore`
|
||||
- User can leverage one of the provided stores:
|
||||
- `avatar.LocalFS` - file system, each avatar in a separate file
|
||||
- `avatar.BoltDB` - single [boltdb](https://github.com/coreos/bbolt) file (embedded KV store).
|
||||
- `avatar.BoltDB` - single [boltdb](https://go.etcd.io/bbolt) file (embedded KV store).
|
||||
- `avatar.GridFS` - external [GridFS](https://docs.mongodb.com/manual/core/gridfs/) (mongo db).
|
||||
- In case of need custom implementations of other stores can be passed in and used by `auth` library. Each store has to implement `avatar.Store` [interface](https://github.com/go-pkgz/auth/blob/master/avatar/store.go#L25).
|
||||
- All avatar-related setup done as a part of `auth.Opts` and needs:
|
||||
|
||||
+4
-2
@@ -58,13 +58,14 @@ type Opts struct {
|
||||
URL string // root url for the rest service, i.e. http://blah.example.com, required
|
||||
Validator token.Validator // validator allows to reject some valid tokens with user-defined logic
|
||||
|
||||
AvatarStore avatar.Store // store to save/load avatars, required
|
||||
AvatarStore avatar.Store // store to save/load avatars, required (use avatar.NoOp to disable avatars support)
|
||||
AvatarResizeLimit int // resize avatar's limit in pixels
|
||||
AvatarRoutePath string // avatar routing prefix, i.e. "/api/v1/avatar", default `/avatar`
|
||||
UseGravatar bool // for email based auth (verified provider) use gravatar service
|
||||
|
||||
AdminPasswd string // if presented, allows basic auth with user admin and given password
|
||||
AudienceReader token.Audience // list of allowed aud values, default (empty) allows any
|
||||
AudSecrets bool // allow multiple secrets (secret per aud)
|
||||
Logger logger.L // logger interface, default is no logging at all
|
||||
RefreshCache middleware.RefreshCache // optional cache to keep refreshed tokens
|
||||
}
|
||||
@@ -107,10 +108,11 @@ func NewService(opts Opts) (res *Service) {
|
||||
JWTQuery: opts.JWTQuery,
|
||||
Issuer: res.issuer,
|
||||
AudienceReader: opts.AudienceReader,
|
||||
AudSecrets: opts.AudSecrets,
|
||||
})
|
||||
|
||||
if opts.SecretReader == nil {
|
||||
jwtService.SecretReader = token.SecretFunc(func() (string, error) {
|
||||
jwtService.SecretReader = token.SecretFunc(func(string) (string, error) {
|
||||
return "", errors.New("secrets reader not available")
|
||||
})
|
||||
res.logger.Logf("[WARN] no secret reader defined")
|
||||
|
||||
+1
-1
@@ -7,8 +7,8 @@ import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
bolt "github.com/coreos/bbolt"
|
||||
"github.com/pkg/errors"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// BoltDB implements avatar store with bolt
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package avatar
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// NoOp is an empty (no-op) implementation of Store interface
|
||||
type NoOp struct{}
|
||||
|
||||
// NewNoOp provides an empty (no-op) implementation of Store interface
|
||||
func NewNoOp() *NoOp { return &NoOp{} }
|
||||
|
||||
// String is a NoOp implementation
|
||||
func (s *NoOp) String() string { return "" }
|
||||
|
||||
// Put is a NoOp implementation
|
||||
func (s *NoOp) Put(userID string, reader io.Reader) (avatarID string, err error) { return "", nil }
|
||||
|
||||
// Get is a NoOp implementation
|
||||
func (s *NoOp) Get(avatarID string) (reader io.ReadCloser, size int, err error) {
|
||||
return ioutil.NopCloser(bytes.NewBuffer([]byte(""))), 0, nil
|
||||
}
|
||||
|
||||
// ID is a NoOp implementation
|
||||
func (s *NoOp) ID(avatarID string) (id string) { return "" }
|
||||
|
||||
// Remove is a NoOp implementation
|
||||
func (s *NoOp) Remove(avatarID string) error { return nil }
|
||||
|
||||
// List is a NoOp implementation
|
||||
func (s *NoOp) List() (ids []string, err error) { return nil, nil }
|
||||
|
||||
// Close is a NoOp implementation
|
||||
func (s *NoOp) Close() error { return nil }
|
||||
+4
-3
@@ -17,11 +17,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
bolt "github.com/coreos/bbolt"
|
||||
"github.com/go-pkgz/auth/token"
|
||||
"github.com/pkg/errors"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
|
||||
"github.com/go-pkgz/auth/token"
|
||||
)
|
||||
|
||||
// imgSfx for avatars
|
||||
@@ -29,7 +30,7 @@ const imgSfx = ".image"
|
||||
|
||||
var reValidAvatarID = regexp.MustCompile(`^[a-fA-F0-9]{40}\.image$`)
|
||||
|
||||
// Store defines interface to store and and load avatars
|
||||
// Store defines interface to store and load avatars
|
||||
type Store interface {
|
||||
fmt.Stringer
|
||||
Put(userID string, reader io.Reader) (avatarID string, err error) // save avatar data from the reader and return base name
|
||||
|
||||
+1
-3
@@ -2,7 +2,6 @@ module github.com/go-pkgz/auth
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.40.0 // indirect
|
||||
github.com/coreos/bbolt v1.3.3
|
||||
github.com/dghubble/oauth1 v0.6.0
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/go-pkgz/rest v1.4.1
|
||||
@@ -16,12 +15,11 @@ require (
|
||||
github.com/tidwall/pretty v1.0.0 // indirect
|
||||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect
|
||||
github.com/xdg/stringprep v1.0.0 // indirect
|
||||
go.etcd.io/bbolt v1.3.3 // indirect
|
||||
go.etcd.io/bbolt v1.3.4
|
||||
go.mongodb.org/mongo-driver v1.1.1
|
||||
golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff
|
||||
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae // indirect
|
||||
google.golang.org/appengine v1.6.1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
gopkg.in/oauth2.v3 v3.10.1
|
||||
|
||||
+4
-6
@@ -8,8 +8,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f h1:zvClvFQwU++UpIUBGC8YmDlfhUrweEy1R1Fj1gu5iIM=
|
||||
github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
|
||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -141,8 +139,8 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3Ifn
|
||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
|
||||
github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI=
|
||||
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
|
||||
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
|
||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.mongodb.org/mongo-driver v1.1.1 h1:Sq1fR+0c58RME5EoqKdjkiQAmPjmfHlZOoRI6fTUOcs=
|
||||
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
@@ -188,8 +186,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae h1:xiXzMMEQdQcric9hXtr1QU98MHunKK7OTtsoU6bYWs4=
|
||||
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
|
||||
+4
-3
@@ -11,12 +11,13 @@ import (
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/go-pkgz/auth/avatar"
|
||||
"github.com/go-pkgz/auth/logger"
|
||||
"github.com/go-pkgz/auth/token"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"golang.org/x/oauth2"
|
||||
goauth2 "gopkg.in/oauth2.v3/server"
|
||||
|
||||
"github.com/go-pkgz/auth/avatar"
|
||||
"github.com/go-pkgz/auth/logger"
|
||||
"github.com/go-pkgz/auth/token"
|
||||
)
|
||||
|
||||
// CustomHandlerOpt are options to initialize a handler for oauth2 server
|
||||
|
||||
+2
-1
@@ -10,9 +10,10 @@ import (
|
||||
|
||||
"github.com/dghubble/oauth1"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/go-pkgz/rest"
|
||||
|
||||
"github.com/go-pkgz/auth/logger"
|
||||
"github.com/go-pkgz/auth/token"
|
||||
"github.com/go-pkgz/rest"
|
||||
)
|
||||
|
||||
// Oauth1Handler implements /login, /callback and /logout handlers for oauth1 flow
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/go-pkgz/rest"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
|
||||
+1
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/dghubble/oauth1"
|
||||
"github.com/dghubble/oauth1/twitter"
|
||||
|
||||
"github.com/go-pkgz/auth/token"
|
||||
)
|
||||
|
||||
|
||||
+2
-3
@@ -59,7 +59,7 @@ func (e VerifyHandler) Name() string { return e.ProviderName }
|
||||
// In case if confirmation token presented in the query uses it to create auth token
|
||||
func (e VerifyHandler) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// GET /login?site=site&&user=name&address=someone@example.com
|
||||
// GET /login?site=site&user=name&address=someone@example.com
|
||||
tkn := r.URL.Query().Get("token")
|
||||
if tkn == "" { // no token, ask confirmation via email
|
||||
e.sendConfirmation(w, r)
|
||||
@@ -130,7 +130,7 @@ func (e VerifyHandler) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
rest.RenderJSON(w, r, claims.User)
|
||||
}
|
||||
|
||||
// GET /login?site=site&&user=name&address=someone@example.com
|
||||
// GET /login?site=site&user=name&address=someone@example.com
|
||||
func (e VerifyHandler) sendConfirmation(w http.ResponseWriter, r *http.Request) {
|
||||
user, address := r.URL.Query().Get("user"), r.URL.Query().Get("address")
|
||||
if user == "" || address == "" {
|
||||
@@ -140,7 +140,6 @@ func (e VerifyHandler) sendConfirmation(w http.ResponseWriter, r *http.Request)
|
||||
claims := token.Claims{
|
||||
Handshake: &token.Handshake{
|
||||
State: "",
|
||||
From: r.URL.Query().Get("from"),
|
||||
ID: user + "::" + address,
|
||||
},
|
||||
SessionOnly: r.URL.Query().Get("session") != "" && r.URL.Query().Get("session") != "0",
|
||||
|
||||
+34
-6
@@ -65,6 +65,7 @@ type Opts struct {
|
||||
JWTQuery string
|
||||
AudienceReader Audience // allowed aud values
|
||||
Issuer string // optional value for iss claim, usually application name
|
||||
AudSecrets bool // uses different secret for differed auds. important: adds pre-parsing of unverified token
|
||||
}
|
||||
|
||||
// NewService makes JWT service
|
||||
@@ -115,7 +116,7 @@ func (j *Service) Token(claims Claims) (string, error) {
|
||||
return "", errors.Wrap(err, "aud rejected")
|
||||
}
|
||||
|
||||
secret, err := j.SecretReader.Get() // get secret via consumer defined SecretReader
|
||||
secret, err := j.SecretReader.Get(claims.Audience) // get secret via consumer defined SecretReader
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "can't get secret")
|
||||
}
|
||||
@@ -135,7 +136,16 @@ func (j *Service) Parse(tokenString string) (Claims, error) {
|
||||
return Claims{}, errors.New("secret reader not defined")
|
||||
}
|
||||
|
||||
secret, err := j.SecretReader.Get()
|
||||
aud := "ignore"
|
||||
if j.AudSecrets {
|
||||
var err error
|
||||
aud, err = j.aud(tokenString)
|
||||
if err != nil {
|
||||
return Claims{}, errors.New("can't retrieve audience from the token")
|
||||
}
|
||||
}
|
||||
|
||||
secret, err := j.SecretReader.Get(aud)
|
||||
if err != nil {
|
||||
return Claims{}, errors.Wrap(err, "can't get secret")
|
||||
}
|
||||
@@ -161,6 +171,24 @@ func (j *Service) Parse(tokenString string) (Claims, error) {
|
||||
return *claims, j.validate(claims)
|
||||
}
|
||||
|
||||
// aud pre-parse token and extracts aud from the claim
|
||||
// important! this step ignores token verification, should not be used for any validations
|
||||
func (j *Service) aud(tokenString string) (string, error) {
|
||||
parser := jwt.Parser{}
|
||||
token, _, err := parser.ParseUnverified(tokenString, &Claims{})
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "can't pre-parse token")
|
||||
}
|
||||
claims, ok := token.Claims.(*Claims)
|
||||
if !ok {
|
||||
return "", errors.New("invalid token")
|
||||
}
|
||||
if strings.TrimSpace(claims.Audience) == "" {
|
||||
return "", errors.New("empty aud")
|
||||
}
|
||||
return claims.Audience, nil
|
||||
}
|
||||
|
||||
func (j *Service) validate(claims *Claims) error {
|
||||
cerr := claims.Valid()
|
||||
|
||||
@@ -312,16 +340,16 @@ func (c Claims) String() string {
|
||||
|
||||
// Secret defines interface returning secret key for given id (aud)
|
||||
type Secret interface {
|
||||
Get() (string, error)
|
||||
Get(aud string) (string, error) // aud matching is optional. Implementation may decide if supported or ignored
|
||||
}
|
||||
|
||||
// SecretFunc type is an adapter to allow the use of ordinary functions as Secret. If f is a function
|
||||
// with the appropriate signature, SecretFunc(f) is a Handler that calls f.
|
||||
type SecretFunc func() (string, error)
|
||||
type SecretFunc func(aud string) (string, error)
|
||||
|
||||
// Get calls f()
|
||||
func (f SecretFunc) Get() (string, error) {
|
||||
return f()
|
||||
func (f SecretFunc) Get(aud string) (string, error) {
|
||||
return f(aud)
|
||||
}
|
||||
|
||||
// ClaimsUpdater defines interface adding extras to claims
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.12.x"
|
||||
|
||||
install: true
|
||||
|
||||
before_install:
|
||||
- export TZ=America/Chicago
|
||||
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.13.2
|
||||
- go get github.com/mattn/goveralls
|
||||
- export PATH=$(pwd)/bin:$PATH
|
||||
|
||||
script:
|
||||
- GO111MODULE=on go get ./...
|
||||
- GO111MODULE=on go mod vendor
|
||||
- GO111MODULE=on go test -v -mod=vendor -covermode=count -coverprofile=profile.cov ./... || travis_terminate 1;
|
||||
- GO111MODULE=on go test -v -covermode=count -coverprofile=profile.cov ./... || travis_terminate 1;
|
||||
- golangci-lint run || travis_terminate 1;
|
||||
- $GOPATH/bin/goveralls -coverprofile=profile.cov -service=travis-ci
|
||||
|
||||
+3
-1
@@ -1,4 +1,4 @@
|
||||
# lgr - simple logger with some extras [](https://travis-ci.org/go-pkgz/lgr) [](https://coveralls.io/github/go-pkgz/lgr?branch=master) [](https://godoc.org/github.com/go-pkgz/lgr)
|
||||
# lgr - simple logger with some extras [](https://github.com/go-pkgz/lgr/actions) [](https://coveralls.io/github/go-pkgz/lgr?branch=master) [](https://godoc.org/github.com/go-pkgz/lgr)
|
||||
|
||||
## install
|
||||
|
||||
@@ -33,6 +33,7 @@ _Without `lgr.Caller*` it will drop `{caller}` part_
|
||||
`lgr.New` call accepts functional options:
|
||||
|
||||
- `lgr.Debug` - turn debug mode on to allow messages with "DEBUG" level (filtered otherwise)
|
||||
- `lgr.Trace` - turn trace mode on to allow messages with "TRACE" abd "DEBUG" levels both (filtered otherwise)
|
||||
- `lgr.Out(io.Writer)` - sets the output writer, default `os.Stdout`
|
||||
- `lgr.Err(io.Writer)` - sets the error writer, default `os.Stderr`
|
||||
- `lgr.CallerFile` - adds the caller file info
|
||||
@@ -41,6 +42,7 @@ _Without `lgr.Caller*` it will drop `{caller}` part_
|
||||
- `lgr.LevelBraces` - wraps levels with "[" and "]"
|
||||
- `lgr.Msec` - adds milliseconds to timestamp
|
||||
- `lgr.Format` - sets custom template, overwrite all other formatting modifiers.
|
||||
- `lgr.Secret(secret ...)` - sets list of the secrets to hide from the logging outputs.
|
||||
|
||||
example: `l := lgr.New(lgr.Debug, lgr.Msec)`
|
||||
|
||||
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
module github.com/go-pkgz/lgr
|
||||
|
||||
require github.com/stretchr/testify v1.3.0
|
||||
|
||||
go 1.13
|
||||
|
||||
+11
@@ -34,6 +34,8 @@ const (
|
||||
FullDebug = `{{.DT.Format "2006/01/02 15:04:05.000"}} {{.Level}} ({{.CallerFile}}:{{.CallerLine}} {{.CallerFunc}}) {{.Message}}`
|
||||
)
|
||||
|
||||
var secretReplacement = []byte("******")
|
||||
|
||||
// Logger provided simple logger with basic support of levels. Thread safe
|
||||
type Logger struct {
|
||||
// set with Option calls
|
||||
@@ -46,6 +48,7 @@ type Logger struct {
|
||||
levelBraces bool // encloses level with [], i.e. [INFO]
|
||||
callerDepth int // how many stack frames to skip, relative to the real (reported) frame
|
||||
format string // layout template
|
||||
secrets []string // sub-strings to secrets by matching
|
||||
|
||||
// internal use
|
||||
now nowFn
|
||||
@@ -163,6 +166,7 @@ func (l *Logger) logf(format string, args ...interface{}) {
|
||||
data = bytes.Replace(data, []byte("[WARN ]"), []byte("[WARN] "), 1)
|
||||
data = bytes.Replace(data, []byte("[INFO ]"), []byte("[INFO] "), 1)
|
||||
}
|
||||
data = l.hideSecrets(data)
|
||||
|
||||
l.lock.Lock()
|
||||
_, _ = l.stdout.Write(data)
|
||||
@@ -189,6 +193,13 @@ func (l *Logger) logf(format string, args ...interface{}) {
|
||||
l.lock.Unlock()
|
||||
}
|
||||
|
||||
func (l *Logger) hideSecrets(data []byte) []byte {
|
||||
for _, h := range l.secrets {
|
||||
data = bytes.Replace(data, []byte(h), secretReplacement, -1)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
type callerInfo struct {
|
||||
File string
|
||||
Line int
|
||||
|
||||
+8
@@ -68,3 +68,11 @@ func CallerFile(l *Logger) {
|
||||
func Msec(l *Logger) {
|
||||
l.msec = true
|
||||
}
|
||||
|
||||
// Secret sets list of substring to be hidden, i.e. replaced by "******"
|
||||
// Useful to prevent passwords or other sensitive tokens to be logged.
|
||||
func Secret(vals ...string) Option {
|
||||
return func(l *Logger) {
|
||||
l.secrets = vals
|
||||
}
|
||||
}
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
golint:
|
||||
min-confidence: 0.8
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
maligned:
|
||||
suggest-new: true
|
||||
dupl:
|
||||
threshold: 100
|
||||
goconst:
|
||||
min-len: 2
|
||||
min-occurrences: 2
|
||||
misspell:
|
||||
locale: US
|
||||
lll:
|
||||
line-length: 140
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- performance
|
||||
- style
|
||||
- experimental
|
||||
disabled-checks:
|
||||
- wrapperFunc
|
||||
- hugeParam
|
||||
- rangeValCopy
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- megacheck
|
||||
- golint
|
||||
- govet
|
||||
- unconvert
|
||||
- megacheck
|
||||
- structcheck
|
||||
- gas
|
||||
- gocyclo
|
||||
- misspell
|
||||
- unparam
|
||||
- varcheck
|
||||
- deadcode
|
||||
- typecheck
|
||||
- ineffassign
|
||||
- varcheck
|
||||
- stylecheck
|
||||
- gochecknoinits
|
||||
- golint
|
||||
- nakedret
|
||||
- gosimple
|
||||
- prealloc
|
||||
|
||||
fast: false
|
||||
|
||||
|
||||
run:
|
||||
# modules-download-mode: vendor
|
||||
skip-dirs:
|
||||
- vendor
|
||||
concurrency: 4
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- text: "weak cryptographic primitive"
|
||||
linters:
|
||||
- gosec
|
||||
exclude-use-default: false
|
||||
|
||||
service:
|
||||
golangci-lint-version: 1.24.x
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.11.x"
|
||||
|
||||
install: true
|
||||
|
||||
before_install:
|
||||
- export TZ=America/Chicago
|
||||
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.13.2
|
||||
- go get github.com/mattn/goveralls
|
||||
- export PATH=$(pwd)/bin:$PATH
|
||||
|
||||
|
||||
script:
|
||||
- GO111MODULE=on go get ./...
|
||||
- GO111MODULE=on go mod vendor
|
||||
- GO111MODULE=on go test -v -mod=vendor -covermode=count -coverprofile=profile.cov ./... || travis_terminate 1
|
||||
- golangci-lint run || travis_terminate 1;
|
||||
- $GOPATH/bin/goveralls -coverprofile=profile.cov -service=travis-ci
|
||||
+2
-5
@@ -1,4 +1,4 @@
|
||||
## REST helpers and middleware [](https://travis-ci.org/go-pkgz/rest) [](https://goreportcard.com/report/github.com/go-pkgz/rest) [](https://coveralls.io/github/go-pkgz/rest?branch=master) [](https://godoc.org/github.com/go-pkgz/rest)
|
||||
## REST helpers and middleware [](https://github.com/go-pkgz/rest/actions) [](https://goreportcard.com/report/github.com/go-pkgz/rest) [](https://coveralls.io/github/go-pkgz/rest?branch=master) [](https://godoc.org/github.com/go-pkgz/rest)
|
||||
|
||||
|
||||
## Install and update
|
||||
@@ -90,7 +90,4 @@ It looks for `X-Request-ID` header and makes it as a random id
|
||||
- `rest.RenderJSONFromBytes` - renders json response from `[]byte`
|
||||
- `rest.RenderJSONWithHTML` - renders json response with html tags and forced `charset=utf-8`
|
||||
- `rest.SendErrorJSON` - makes `{error: blah, details: blah}` json body and responds with given error code. Also adds context to logged message
|
||||
|
||||
## Caching
|
||||
|
||||
Cache wrapper provides loading cache for rest/http responses. See [cache readme](https://github.com/go-pkgz/rest/tree/master/cache) for more details and examples.
|
||||
- `rest.NewErrorLogger(l logger.Backend)` creates a struct providing shorter form of logger call
|
||||
|
||||
+3
-2
@@ -1,8 +1,9 @@
|
||||
module github.com/go-pkgz/rest
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.0
|
||||
github.com/pkg/errors v0.8.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.3.0
|
||||
)
|
||||
|
||||
+2
-4
@@ -1,10 +1,8 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
||||
+23
@@ -11,6 +11,29 @@ import (
|
||||
"github.com/go-pkgz/rest/logger"
|
||||
)
|
||||
|
||||
// ErrorLogger wraps logger.Backend
|
||||
type ErrorLogger struct {
|
||||
l logger.Backend
|
||||
}
|
||||
|
||||
// NewErrorLogger creates ErrorLogger for given Backend
|
||||
func NewErrorLogger(l logger.Backend) *ErrorLogger {
|
||||
return &ErrorLogger{l: l}
|
||||
}
|
||||
|
||||
// Log sends json error message {error: msg} with error code and logging error and caller
|
||||
func (e *ErrorLogger) Log(w http.ResponseWriter, r *http.Request, httpCode int, err error, msg ...string) {
|
||||
m := ""
|
||||
if len(msg) > 0 {
|
||||
m = strings.Join(msg, ". ")
|
||||
}
|
||||
if e.l != nil {
|
||||
e.l.Logf("%s", errDetailsMsg(r, httpCode, err, m))
|
||||
}
|
||||
w.WriteHeader(httpCode)
|
||||
RenderJSON(w, r, JSON{"error": m})
|
||||
}
|
||||
|
||||
// SendErrorJSON sends {error: msg} with error code and logging error and caller
|
||||
func SendErrorJSON(w http.ResponseWriter, r *http.Request, l logger.Backend, code int, err error, msg string) {
|
||||
if l != nil {
|
||||
|
||||
+2
@@ -1,3 +1,4 @@
|
||||
// Package logger implements logging middleware
|
||||
package logger
|
||||
|
||||
import (
|
||||
@@ -58,6 +59,7 @@ func New(options ...Option) *Middleware {
|
||||
}
|
||||
|
||||
// Handler middleware prints http log
|
||||
//nolint gosec
|
||||
func (l *Middleware) Handler(next http.Handler) http.Handler {
|
||||
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// Package rest provides common middlewares and helpers for rest services
|
||||
package rest
|
||||
|
||||
import (
|
||||
|
||||
+3
-8
@@ -1,15 +1,10 @@
|
||||
language: go
|
||||
go_import_path: github.com/pkg/errors
|
||||
go:
|
||||
- 1.4.x
|
||||
- 1.5.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -v ./...
|
||||
- make check
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
PKGS := github.com/pkg/errors
|
||||
SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS))
|
||||
GO := go
|
||||
|
||||
check: test vet gofmt misspell unconvert staticcheck ineffassign unparam
|
||||
|
||||
test:
|
||||
$(GO) test $(PKGS)
|
||||
|
||||
vet: | test
|
||||
$(GO) vet $(PKGS)
|
||||
|
||||
staticcheck:
|
||||
$(GO) get honnef.co/go/tools/cmd/staticcheck
|
||||
staticcheck -checks all $(PKGS)
|
||||
|
||||
misspell:
|
||||
$(GO) get github.com/client9/misspell/cmd/misspell
|
||||
misspell \
|
||||
-locale GB \
|
||||
-error \
|
||||
*.md *.go
|
||||
|
||||
unconvert:
|
||||
$(GO) get github.com/mdempsky/unconvert
|
||||
unconvert -v $(PKGS)
|
||||
|
||||
ineffassign:
|
||||
$(GO) get github.com/gordonklaus/ineffassign
|
||||
find $(SRCDIRS) -name '*.go' | xargs ineffassign
|
||||
|
||||
pedantic: check errcheck
|
||||
|
||||
unparam:
|
||||
$(GO) get mvdan.cc/unparam
|
||||
unparam ./...
|
||||
|
||||
errcheck:
|
||||
$(GO) get github.com/kisielk/errcheck
|
||||
errcheck $(PKGS)
|
||||
|
||||
gofmt:
|
||||
@echo Checking code is gofmted
|
||||
@test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)"
|
||||
+9
-2
@@ -41,11 +41,18 @@ default:
|
||||
|
||||
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
|
||||
|
||||
## Roadmap
|
||||
|
||||
With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows:
|
||||
|
||||
- 0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible)
|
||||
- 1.0. Final release.
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
|
||||
Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports.
|
||||
|
||||
Before proposing a change, please discuss your change by raising an issue.
|
||||
Before sending a PR, please discuss your change by raising an issue.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+7
-1
@@ -82,7 +82,7 @@
|
||||
//
|
||||
// if err, ok := err.(stackTracer); ok {
|
||||
// for _, f := range err.StackTrace() {
|
||||
// fmt.Printf("%+s:%d", f)
|
||||
// fmt.Printf("%+s:%d\n", f, f)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
@@ -159,6 +159,9 @@ type withStack struct {
|
||||
|
||||
func (w *withStack) Cause() error { return w.error }
|
||||
|
||||
// Unwrap provides compatibility for Go 1.13 error chains.
|
||||
func (w *withStack) Unwrap() error { return w.error }
|
||||
|
||||
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
@@ -241,6 +244,9 @@ type withMessage struct {
|
||||
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||
func (w *withMessage) Cause() error { return w.cause }
|
||||
|
||||
// Unwrap provides compatibility for Go 1.13 error chains.
|
||||
func (w *withMessage) Unwrap() error { return w.cause }
|
||||
|
||||
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// +build go1.13
|
||||
|
||||
package errors
|
||||
|
||||
import (
|
||||
stderrors "errors"
|
||||
)
|
||||
|
||||
// Is reports whether any error in err's chain matches target.
|
||||
//
|
||||
// The chain consists of err itself followed by the sequence of errors obtained by
|
||||
// repeatedly calling Unwrap.
|
||||
//
|
||||
// An error is considered to match a target if it is equal to that target or if
|
||||
// it implements a method Is(error) bool such that Is(target) returns true.
|
||||
func Is(err, target error) bool { return stderrors.Is(err, target) }
|
||||
|
||||
// As finds the first error in err's chain that matches target, and if so, sets
|
||||
// target to that error value and returns true.
|
||||
//
|
||||
// The chain consists of err itself followed by the sequence of errors obtained by
|
||||
// repeatedly calling Unwrap.
|
||||
//
|
||||
// An error matches target if the error's concrete value is assignable to the value
|
||||
// pointed to by target, or if the error has a method As(interface{}) bool such that
|
||||
// As(target) returns true. In the latter case, the As method is responsible for
|
||||
// setting target.
|
||||
//
|
||||
// As will panic if target is not a non-nil pointer to either a type that implements
|
||||
// error, or to any interface type. As returns false if err is nil.
|
||||
func As(err error, target interface{}) bool { return stderrors.As(err, target) }
|
||||
|
||||
// Unwrap returns the result of calling the Unwrap method on err, if err's
|
||||
// type contains an Unwrap method returning error.
|
||||
// Otherwise, Unwrap returns nil.
|
||||
func Unwrap(err error) error {
|
||||
return stderrors.Unwrap(err)
|
||||
}
|
||||
+44
-14
@@ -5,10 +5,13 @@ import (
|
||||
"io"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Frame represents a program counter inside a stack frame.
|
||||
// For historical reasons if Frame is interpreted as a uintptr
|
||||
// its value represents the program counter + 1.
|
||||
type Frame uintptr
|
||||
|
||||
// pc returns the program counter for this frame;
|
||||
@@ -37,6 +40,15 @@ func (f Frame) line() int {
|
||||
return line
|
||||
}
|
||||
|
||||
// name returns the name of this function, if known.
|
||||
func (f Frame) name() string {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
return fn.Name()
|
||||
}
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s source file
|
||||
@@ -54,22 +66,16 @@ func (f Frame) Format(s fmt.State, verb rune) {
|
||||
case 's':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
pc := f.pc()
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
io.WriteString(s, "unknown")
|
||||
} else {
|
||||
file, _ := fn.FileLine(pc)
|
||||
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
||||
}
|
||||
io.WriteString(s, f.name())
|
||||
io.WriteString(s, "\n\t")
|
||||
io.WriteString(s, f.file())
|
||||
default:
|
||||
io.WriteString(s, path.Base(f.file()))
|
||||
}
|
||||
case 'd':
|
||||
fmt.Fprintf(s, "%d", f.line())
|
||||
io.WriteString(s, strconv.Itoa(f.line()))
|
||||
case 'n':
|
||||
name := runtime.FuncForPC(f.pc()).Name()
|
||||
io.WriteString(s, funcname(name))
|
||||
io.WriteString(s, funcname(f.name()))
|
||||
case 'v':
|
||||
f.Format(s, 's')
|
||||
io.WriteString(s, ":")
|
||||
@@ -77,6 +83,16 @@ func (f Frame) Format(s fmt.State, verb rune) {
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalText formats a stacktrace Frame as a text string. The output is the
|
||||
// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs.
|
||||
func (f Frame) MarshalText() ([]byte, error) {
|
||||
name := f.name()
|
||||
if name == "unknown" {
|
||||
return []byte(name), nil
|
||||
}
|
||||
return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil
|
||||
}
|
||||
|
||||
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||
type StackTrace []Frame
|
||||
|
||||
@@ -94,18 +110,32 @@ func (st StackTrace) Format(s fmt.State, verb rune) {
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
for _, f := range st {
|
||||
fmt.Fprintf(s, "\n%+v", f)
|
||||
io.WriteString(s, "\n")
|
||||
f.Format(s, verb)
|
||||
}
|
||||
case s.Flag('#'):
|
||||
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||
default:
|
||||
fmt.Fprintf(s, "%v", []Frame(st))
|
||||
st.formatSlice(s, verb)
|
||||
}
|
||||
case 's':
|
||||
fmt.Fprintf(s, "%s", []Frame(st))
|
||||
st.formatSlice(s, verb)
|
||||
}
|
||||
}
|
||||
|
||||
// formatSlice will format this StackTrace into the given buffer as a slice of
|
||||
// Frame, only valid when called with '%s' or '%v'.
|
||||
func (st StackTrace) formatSlice(s fmt.State, verb rune) {
|
||||
io.WriteString(s, "[")
|
||||
for i, f := range st {
|
||||
if i > 0 {
|
||||
io.WriteString(s, " ")
|
||||
}
|
||||
f.Format(s, verb)
|
||||
}
|
||||
io.WriteString(s, "]")
|
||||
}
|
||||
|
||||
// stack represents a stack of program counters.
|
||||
type stack []uintptr
|
||||
|
||||
|
||||
Generated
Generated
Vendored
+1
-1
@@ -4,7 +4,7 @@ go_import_path: go.etcd.io/bbolt
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.11
|
||||
- 1.12
|
||||
|
||||
before_install:
|
||||
- go get -v honnef.co/go/tools/...
|
||||
Generated
Vendored
Generated
Vendored
Generated
Vendored
+3
-1
@@ -275,7 +275,7 @@ should be writable.
|
||||
### Using buckets
|
||||
|
||||
Buckets are collections of key/value pairs within the database. All keys in a
|
||||
bucket must be unique. You can create a bucket using the `DB.CreateBucket()`
|
||||
bucket must be unique. You can create a bucket using the `Tx.CreateBucket()`
|
||||
function:
|
||||
|
||||
```go
|
||||
@@ -923,6 +923,7 @@ Below is a list of public, open source projects that use Bolt:
|
||||
* [GoWebApp](https://github.com/josephspurrier/gowebapp) - A basic MVC web application in Go using BoltDB.
|
||||
* [GoShort](https://github.com/pankajkhairnar/goShort) - GoShort is a URL shortener written in Golang and BoltDB for persistent key/value storage and for routing it's using high performent HTTPRouter.
|
||||
* [gopherpit](https://github.com/gopherpit/gopherpit) - A web service to manage Go remote import paths with custom domains
|
||||
* [gokv](https://github.com/philippgille/gokv) - Simple key-value store abstraction and implementations for Go (Redis, Consul, etcd, bbolt, BadgerDB, LevelDB, Memcached, DynamoDB, S3, PostgreSQL, MongoDB, CockroachDB and many more)
|
||||
* [Gitchain](https://github.com/gitchain/gitchain) - Decentralized, peer-to-peer Git repositories aka "Git meets Bitcoin".
|
||||
* [InfluxDB](https://influxdata.com) - Scalable datastore for metrics, events, and real-time analytics.
|
||||
* [ipLocator](https://github.com/AndreasBriese/ipLocator) - A fast ip-geo-location-server using bolt with bloom filters.
|
||||
@@ -935,6 +936,7 @@ Below is a list of public, open source projects that use Bolt:
|
||||
* [mbuckets](https://github.com/abhigupta912/mbuckets) - A Bolt wrapper that allows easy operations on multi level (nested) buckets.
|
||||
* [MetricBase](https://github.com/msiebuhr/MetricBase) - Single-binary version of Graphite.
|
||||
* [MuLiFS](https://github.com/dankomiocevic/mulifs) - Music Library Filesystem creates a filesystem to organise your music files.
|
||||
* [NATS](https://github.com/nats-io/nats-streaming-server) - NATS Streaming uses bbolt for message and metadata storage.
|
||||
* [Operation Go: A Routine Mission](http://gocode.io) - An online programming game for Golang using Bolt for user accounts and a leaderboard.
|
||||
* [photosite/session](https://godoc.org/bitbucket.org/kardianos/photosite/session) - Sessions for a photo viewing site.
|
||||
* [Prometheus Annotation Server](https://github.com/oliver006/prom_annotation_server) - Annotation server for PromDash & Prometheus service monitoring system.
|
||||
Generated
Vendored
-3
@@ -5,6 +5,3 @@ const maxMapSize = 0x7FFFFFFF // 2GB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0xFFFFFFF
|
||||
|
||||
// Are unaligned load/stores broken on this arch?
|
||||
var brokenUnaligned = false
|
||||
backend/vendor/github.com/coreos/bbolt/bolt_amd64.go → backend/vendor/go.etcd.io/bbolt/bolt_amd64.go
Generated
Vendored
-3
@@ -5,6 +5,3 @@ const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0x7FFFFFFF
|
||||
|
||||
// Are unaligned load/stores broken on this arch?
|
||||
var brokenUnaligned = false
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package bbolt
|
||||
|
||||
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||
const maxMapSize = 0x7FFFFFFF // 2GB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0xFFFFFFF
|
||||
backend/vendor/github.com/coreos/bbolt/bolt_arm64.go → backend/vendor/go.etcd.io/bbolt/bolt_arm64.go
Generated
Vendored
-3
@@ -7,6 +7,3 @@ const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0x7FFFFFFF
|
||||
|
||||
// Are unaligned load/stores broken on this arch?
|
||||
var brokenUnaligned = false
|
||||
Generated
Vendored
-3
@@ -7,6 +7,3 @@ const maxMapSize = 0x8000000000 // 512GB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0x7FFFFFFF
|
||||
|
||||
// Are unaligned load/stores broken on this arch?
|
||||
var brokenUnaligned = false
|
||||
backend/vendor/github.com/coreos/bbolt/bolt_mipsx.go → backend/vendor/go.etcd.io/bbolt/bolt_mipsx.go
Generated
Vendored
-3
@@ -7,6 +7,3 @@ const maxMapSize = 0x40000000 // 1GB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0xFFFFFFF
|
||||
|
||||
// Are unaligned load/stores broken on this arch?
|
||||
var brokenUnaligned = false
|
||||
Generated
Vendored
Generated
Vendored
-3
@@ -7,6 +7,3 @@ const maxMapSize = 0x7FFFFFFF // 2GB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0xFFFFFFF
|
||||
|
||||
// Are unaligned load/stores broken on this arch?
|
||||
var brokenUnaligned = false
|
||||
backend/vendor/github.com/coreos/bbolt/bolt_ppc64.go → backend/vendor/go.etcd.io/bbolt/bolt_ppc64.go
Generated
Vendored
-3
@@ -7,6 +7,3 @@ const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0x7FFFFFFF
|
||||
|
||||
// Are unaligned load/stores broken on this arch?
|
||||
var brokenUnaligned = false
|
||||
Generated
Vendored
-3
@@ -7,6 +7,3 @@ const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0x7FFFFFFF
|
||||
|
||||
// Are unaligned load/stores broken on this arch?
|
||||
var brokenUnaligned = false
|
||||
Generated
Vendored
-3
@@ -7,6 +7,3 @@ const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0x7FFFFFFF
|
||||
|
||||
// Are unaligned load/stores broken on this arch?
|
||||
var brokenUnaligned = true
|
||||
backend/vendor/github.com/coreos/bbolt/bolt_s390x.go → backend/vendor/go.etcd.io/bbolt/bolt_s390x.go
Generated
Vendored
-3
@@ -7,6 +7,3 @@ const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||
|
||||
// maxAllocSize is the size used when creating array pointers.
|
||||
const maxAllocSize = 0x7FFFFFFF
|
||||
|
||||
// Are unaligned load/stores broken on this arch?
|
||||
var brokenUnaligned = false
|
||||
Generated
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
// +build !windows,!plan9,!solaris
|
||||
// +build !windows,!plan9,!solaris,!aix
|
||||
|
||||
package bbolt
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// +build aix
|
||||
|
||||
package bbolt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// flock acquires an advisory lock on a file descriptor.
|
||||
func flock(db *DB, exclusive bool, timeout time.Duration) error {
|
||||
var t time.Time
|
||||
if timeout != 0 {
|
||||
t = time.Now()
|
||||
}
|
||||
fd := db.file.Fd()
|
||||
var lockType int16
|
||||
if exclusive {
|
||||
lockType = syscall.F_WRLCK
|
||||
} else {
|
||||
lockType = syscall.F_RDLCK
|
||||
}
|
||||
for {
|
||||
// Attempt to obtain an exclusive lock.
|
||||
lock := syscall.Flock_t{Type: lockType}
|
||||
err := syscall.FcntlFlock(fd, syscall.F_SETLK, &lock)
|
||||
if err == nil {
|
||||
return nil
|
||||
} else if err != syscall.EAGAIN {
|
||||
return err
|
||||
}
|
||||
|
||||
// If we timed out then return an error.
|
||||
if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout {
|
||||
return ErrTimeout
|
||||
}
|
||||
|
||||
// Wait for a bit and try again.
|
||||
time.Sleep(flockRetryTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
// funlock releases an advisory lock on a file descriptor.
|
||||
func funlock(db *DB) error {
|
||||
var lock syscall.Flock_t
|
||||
lock.Start = 0
|
||||
lock.Len = 0
|
||||
lock.Type = syscall.F_UNLCK
|
||||
lock.Whence = 0
|
||||
return syscall.FcntlFlock(uintptr(db.file.Fd()), syscall.F_SETLK, &lock)
|
||||
}
|
||||
|
||||
// mmap memory maps a DB's data file.
|
||||
func mmap(db *DB, sz int) error {
|
||||
// Map the data file to memory.
|
||||
b, err := unix.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED|db.MmapFlags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Advise the kernel that the mmap is accessed randomly.
|
||||
if err := unix.Madvise(b, syscall.MADV_RANDOM); err != nil {
|
||||
return fmt.Errorf("madvise: %s", err)
|
||||
}
|
||||
|
||||
// Save the original byte slice and convert to a byte array pointer.
|
||||
db.dataref = b
|
||||
db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0]))
|
||||
db.datasz = sz
|
||||
return nil
|
||||
}
|
||||
|
||||
// munmap unmaps a DB's data file from memory.
|
||||
func munmap(db *DB) error {
|
||||
// Ignore the unmap if we have no mapped data.
|
||||
if db.dataref == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmap using the original byte slice.
|
||||
err := unix.Munmap(db.dataref)
|
||||
db.dataref = nil
|
||||
db.data = nil
|
||||
db.datasz = 0
|
||||
return err
|
||||
}
|
||||
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
+18
-16
@@ -123,10 +123,12 @@ func (b *Bucket) Bucket(name []byte) *Bucket {
|
||||
func (b *Bucket) openBucket(value []byte) *Bucket {
|
||||
var child = newBucket(b.tx)
|
||||
|
||||
// If unaligned load/stores are broken on this arch and value is
|
||||
// unaligned simply clone to an aligned byte array.
|
||||
unaligned := brokenUnaligned && uintptr(unsafe.Pointer(&value[0]))&3 != 0
|
||||
|
||||
// Unaligned access requires a copy to be made.
|
||||
const unalignedMask = unsafe.Alignof(struct {
|
||||
bucket
|
||||
page
|
||||
}{}) - 1
|
||||
unaligned := uintptr(unsafe.Pointer(&value[0]))&unalignedMask != 0
|
||||
if unaligned {
|
||||
value = cloneBytes(value)
|
||||
}
|
||||
@@ -206,7 +208,7 @@ func (b *Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error) {
|
||||
}
|
||||
|
||||
// DeleteBucket deletes a bucket at the given key.
|
||||
// Returns an error if the bucket does not exists, or if the key represents a non-bucket value.
|
||||
// Returns an error if the bucket does not exist, or if the key represents a non-bucket value.
|
||||
func (b *Bucket) DeleteBucket(key []byte) error {
|
||||
if b.tx.db == nil {
|
||||
return ErrTxClosed
|
||||
@@ -228,7 +230,7 @@ func (b *Bucket) DeleteBucket(key []byte) error {
|
||||
// Recursively delete all child buckets.
|
||||
child := b.Bucket(key)
|
||||
err := child.ForEach(func(k, v []byte) error {
|
||||
if v == nil {
|
||||
if _, _, childFlags := child.Cursor().seek(k); (childFlags & bucketLeafFlag) != 0 {
|
||||
if err := child.DeleteBucket(k); err != nil {
|
||||
return fmt.Errorf("delete bucket: %s", err)
|
||||
}
|
||||
@@ -409,7 +411,7 @@ func (b *Bucket) Stats() BucketStats {
|
||||
|
||||
if p.count != 0 {
|
||||
// If page has any elements, add all element headers.
|
||||
used += leafPageElementSize * int(p.count-1)
|
||||
used += leafPageElementSize * uintptr(p.count-1)
|
||||
|
||||
// Add all element key, value sizes.
|
||||
// The computation takes advantage of the fact that the position
|
||||
@@ -417,16 +419,16 @@ func (b *Bucket) Stats() BucketStats {
|
||||
// of all previous elements' keys and values.
|
||||
// It also includes the last element's header.
|
||||
lastElement := p.leafPageElement(p.count - 1)
|
||||
used += int(lastElement.pos + lastElement.ksize + lastElement.vsize)
|
||||
used += uintptr(lastElement.pos + lastElement.ksize + lastElement.vsize)
|
||||
}
|
||||
|
||||
if b.root == 0 {
|
||||
// For inlined bucket just update the inline stats
|
||||
s.InlineBucketInuse += used
|
||||
s.InlineBucketInuse += int(used)
|
||||
} else {
|
||||
// For non-inlined bucket update all the leaf stats
|
||||
s.LeafPageN++
|
||||
s.LeafInuse += used
|
||||
s.LeafInuse += int(used)
|
||||
s.LeafOverflowN += int(p.overflow)
|
||||
|
||||
// Collect stats from sub-buckets.
|
||||
@@ -447,13 +449,13 @@ func (b *Bucket) Stats() BucketStats {
|
||||
|
||||
// used totals the used bytes for the page
|
||||
// Add header and all element headers.
|
||||
used := pageHeaderSize + (branchPageElementSize * int(p.count-1))
|
||||
used := pageHeaderSize + (branchPageElementSize * uintptr(p.count-1))
|
||||
|
||||
// Add size of all keys and values.
|
||||
// Again, use the fact that last element's position equals to
|
||||
// the total of key, value sizes of all previous elements.
|
||||
used += int(lastElement.pos + lastElement.ksize)
|
||||
s.BranchInuse += used
|
||||
used += uintptr(lastElement.pos + lastElement.ksize)
|
||||
s.BranchInuse += int(used)
|
||||
s.BranchOverflowN += int(p.overflow)
|
||||
}
|
||||
|
||||
@@ -593,7 +595,7 @@ func (b *Bucket) inlineable() bool {
|
||||
// our threshold for inline bucket size.
|
||||
var size = pageHeaderSize
|
||||
for _, inode := range n.inodes {
|
||||
size += leafPageElementSize + len(inode.key) + len(inode.value)
|
||||
size += leafPageElementSize + uintptr(len(inode.key)) + uintptr(len(inode.value))
|
||||
|
||||
if inode.flags&bucketLeafFlag != 0 {
|
||||
return false
|
||||
@@ -606,8 +608,8 @@ func (b *Bucket) inlineable() bool {
|
||||
}
|
||||
|
||||
// Returns the maximum total size of a bucket to make it a candidate for inlining.
|
||||
func (b *Bucket) maxInlineBucketSize() int {
|
||||
return b.tx.db.pageSize / 4
|
||||
func (b *Bucket) maxInlineBucketSize() uintptr {
|
||||
return uintptr(b.tx.db.pageSize / 4)
|
||||
}
|
||||
|
||||
// write allocates and writes a bucket to a byte slice.
|
||||
Generated
Vendored
+1
-1
@@ -366,7 +366,7 @@ func (c *Cursor) node() *node {
|
||||
}
|
||||
for _, ref := range c.stack[:len(c.stack)-1] {
|
||||
_assert(!n.isLeaf, "expected branch node")
|
||||
n = n.childAt(int(ref.index))
|
||||
n = n.childAt(ref.index)
|
||||
}
|
||||
_assert(n.isLeaf, "expected leaf node")
|
||||
return n
|
||||
Generated
Vendored
+2
-2
@@ -206,12 +206,12 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||
}
|
||||
|
||||
// Open data file and separate sync handler for metadata writes.
|
||||
db.path = path
|
||||
var err error
|
||||
if db.file, err = db.openFile(db.path, flag|os.O_CREATE, mode); err != nil {
|
||||
if db.file, err = db.openFile(path, flag|os.O_CREATE, mode); err != nil {
|
||||
_ = db.close()
|
||||
return nil, err
|
||||
}
|
||||
db.path = db.file.Name()
|
||||
|
||||
// Lock file so that other processes using Bolt in read-write mode cannot
|
||||
// use the database at the same time. This would cause corruption since
|
||||
Generated
Vendored
Generated
Vendored
Generated
Vendored
+29
-8
@@ -2,6 +2,7 @@ package bbolt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"unsafe"
|
||||
)
|
||||
@@ -71,7 +72,7 @@ func (f *freelist) size() int {
|
||||
// The first element will be used to store the count. See freelist.write.
|
||||
n++
|
||||
}
|
||||
return pageHeaderSize + (int(unsafe.Sizeof(pgid(0))) * n)
|
||||
return int(pageHeaderSize) + (int(unsafe.Sizeof(pgid(0))) * n)
|
||||
}
|
||||
|
||||
// count returns count of pages on the freelist
|
||||
@@ -93,8 +94,24 @@ func (f *freelist) pending_count() int {
|
||||
return count
|
||||
}
|
||||
|
||||
// copyall copies into dst a list of all free ids and all pending ids in one sorted list.
|
||||
// copyallunsafe copies a list of all free ids and all pending ids in one sorted list.
|
||||
// f.count returns the minimum length required for dst.
|
||||
func (f *freelist) copyallunsafe(dstptr unsafe.Pointer) { // dstptr is []pgid data pointer
|
||||
m := make(pgids, 0, f.pending_count())
|
||||
for _, txp := range f.pending {
|
||||
m = append(m, txp.ids...)
|
||||
}
|
||||
sort.Sort(m)
|
||||
fpgids := f.getFreePageIDs()
|
||||
sz := len(fpgids) + len(m)
|
||||
dst := *(*[]pgid)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: uintptr(dstptr),
|
||||
Len: sz,
|
||||
Cap: sz,
|
||||
}))
|
||||
mergepgids(dst, fpgids, m)
|
||||
}
|
||||
|
||||
func (f *freelist) copyall(dst []pgid) {
|
||||
m := make(pgids, 0, f.pending_count())
|
||||
for _, txp := range f.pending {
|
||||
@@ -267,17 +284,21 @@ func (f *freelist) read(p *page) {
|
||||
}
|
||||
// If the page.count is at the max uint16 value (64k) then it's considered
|
||||
// an overflow and the size of the freelist is stored as the first element.
|
||||
idx, count := 0, int(p.count)
|
||||
var idx, count uintptr = 0, uintptr(p.count)
|
||||
if count == 0xFFFF {
|
||||
idx = 1
|
||||
count = int(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0])
|
||||
count = uintptr(*(*pgid)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(*p))))
|
||||
}
|
||||
|
||||
// Copy the list of page ids from the freelist.
|
||||
if count == 0 {
|
||||
f.ids = nil
|
||||
} else {
|
||||
ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[idx : idx+count]
|
||||
ids := *(*[]pgid)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(*p) + idx*unsafe.Sizeof(pgid(0)),
|
||||
Len: int(count),
|
||||
Cap: int(count),
|
||||
}))
|
||||
|
||||
// copy the ids, so we don't modify on the freelist page directly
|
||||
idsCopy := make([]pgid, count)
|
||||
@@ -315,11 +336,11 @@ func (f *freelist) write(p *page) error {
|
||||
p.count = uint16(lenids)
|
||||
} else if lenids < 0xFFFF {
|
||||
p.count = uint16(lenids)
|
||||
f.copyall(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[:])
|
||||
f.copyallunsafe(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(*p)))
|
||||
} else {
|
||||
p.count = 0xFFFF
|
||||
((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0] = pgid(lenids)
|
||||
f.copyall(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[1:])
|
||||
*(*pgid)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(*p))) = pgid(lenids)
|
||||
f.copyallunsafe(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(*p) + unsafe.Sizeof(pgid(0))))
|
||||
}
|
||||
|
||||
return nil
|
||||
Generated
Vendored
+1
-1
@@ -27,7 +27,7 @@ func (f *freelist) hashmapAllocate(txid txid, n int) pgid {
|
||||
f.allocs[pid] = txid
|
||||
|
||||
for i := pgid(0); i < pgid(n); i++ {
|
||||
delete(f.cache, pid+pgid(i))
|
||||
delete(f.cache, pid+i)
|
||||
}
|
||||
return pid
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user