vendorize: update all vendorized packages. (#2206)
Bring in new changes from upstream for all the packages. Important ones include - gorilla/mux - logrus - jwtmaster
parent
b090c7112e
commit
35d438e0ff
@ -0,0 +1,15 @@ |
||||
// +build solaris
|
||||
|
||||
package logrus |
||||
|
||||
import ( |
||||
"os" |
||||
|
||||
"golang.org/x/sys/unix" |
||||
) |
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal() bool { |
||||
_, err := unix.IoctlGetTermios(int(os.Stdout.Fd()), unix.TCGETA) |
||||
return err == nil |
||||
} |
@ -0,0 +1,96 @@ |
||||
## Migration Guide from v2 -> v3 |
||||
|
||||
Version 3 adds several new, frequently requested features. To do so, it introduces a few breaking changes. We've worked to keep these as minimal as possible. This guide explains the breaking changes and how you can quickly update your code. |
||||
|
||||
### `Token.Claims` is now an interface type |
||||
|
||||
The most requested feature from the 2.0 verison of this library was the ability to provide a custom type to the JSON parser for claims. This was implemented by introducing a new interface, `Claims`, to replace `map[string]interface{}`. We also included two concrete implementations of `Claims`: `MapClaims` and `StandardClaims`. |
||||
|
||||
`MapClaims` is an alias for `map[string]interface{}` with built in validation behavior. It is the default claims type when using `Parse`. The usage is unchanged except you must type cast the claims property. |
||||
|
||||
The old example for parsing a token looked like this.. |
||||
|
||||
```go |
||||
if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil { |
||||
fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"]) |
||||
} |
||||
``` |
||||
|
||||
is now directly mapped to... |
||||
|
||||
```go |
||||
if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil { |
||||
claims := token.Claims.(jwt.MapClaims) |
||||
fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"]) |
||||
} |
||||
``` |
||||
|
||||
`StandardClaims` is designed to be embedded in your custom type. You can supply a custom claims type with the new `ParseWithClaims` function. Here's an example of using a custom claims type. |
||||
|
||||
```go |
||||
type MyCustomClaims struct { |
||||
User string |
||||
*StandardClaims |
||||
} |
||||
|
||||
if token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, keyLookupFunc); err == nil { |
||||
claims := token.Claims.(*MyCustomClaims) |
||||
fmt.Printf("Token for user %v expires %v", claims.User, claims.StandardClaims.ExpiresAt) |
||||
} |
||||
``` |
||||
|
||||
### `ParseFromRequest` has been moved |
||||
|
||||
To keep this library focused on the tokens without becoming overburdened with complex request processing logic, `ParseFromRequest` and its new companion `ParseFromRequestWithClaims` have been moved to a subpackage, `request`. The method signatues have also been augmented to receive a new argument: `Extractor`. |
||||
|
||||
`Extractors` do the work of picking the token string out of a request. The interface is simple and composable. |
||||
|
||||
This simple parsing example: |
||||
|
||||
```go |
||||
if token, err := jwt.ParseFromRequest(tokenString, req, keyLookupFunc); err == nil { |
||||
fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"]) |
||||
} |
||||
``` |
||||
|
||||
is directly mapped to: |
||||
|
||||
```go |
||||
if token, err := request.ParseFromRequest(tokenString, request.OAuth2Extractor, req, keyLookupFunc); err == nil { |
||||
fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"]) |
||||
} |
||||
``` |
||||
|
||||
There are several concrete `Extractor` types provided for your convenience: |
||||
|
||||
* `HeaderExtractor` will search a list of headers until one contains content. |
||||
* `ArgumentExtractor` will search a list of keys in request query and form arguments until one contains content. |
||||
* `MultiExtractor` will try a list of `Extractors` in order until one returns content. |
||||
* `AuthorizationHeaderExtractor` will look in the `Authorization` header for a `Bearer` token. |
||||
* `OAuth2Extractor` searches the places an OAuth2 token would be specified (per the spec): `Authorization` header and `access_token` argument |
||||
* `PostExtractionFilter` wraps an `Extractor`, allowing you to process the content before it's parsed. A simple example is stripping the `Bearer ` text from a header |
||||
|
||||
|
||||
### RSA signing methods no longer accept `[]byte` keys |
||||
|
||||
Due to a [critical vulnerability](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/), we've decided the convenience of accepting `[]byte` instead of `rsa.PublicKey` or `rsa.PrivateKey` isn't worth the risk of misuse. |
||||
|
||||
To replace this behavior, we've added two helper methods: `ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error)` and `ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error)`. These are just simple helpers for unpacking PEM encoded PKCS1 and PKCS8 keys. If your keys are encoded any other way, all you need to do is convert them to the `crypto/rsa` package's types. |
||||
|
||||
```go |
||||
func keyLookupFunc(*Token) (interface{}, error) { |
||||
// Don't forget to validate the alg is what you expect: |
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { |
||||
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) |
||||
} |
||||
|
||||
// Look up key |
||||
key, err := lookupPublicKey(token.Header["kid"]) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// Unpack key from PEM encoded PKCS8 |
||||
return jwt.ParseRSAPublicKeyFromPEM(key) |
||||
} |
||||
``` |
@ -0,0 +1,134 @@ |
||||
package jwt |
||||
|
||||
import ( |
||||
"crypto/subtle" |
||||
"fmt" |
||||
"time" |
||||
) |
||||
|
||||
// For a type to be a Claims object, it must just have a Valid method that determines
|
||||
// if the token is invalid for any supported reason
|
||||
type Claims interface { |
||||
Valid() error |
||||
} |
||||
|
||||
// Structured version of Claims Section, as referenced at
|
||||
// https://tools.ietf.org/html/rfc7519#section-4.1
|
||||
// See examples for how to use this with your own claim types
|
||||
type StandardClaims struct { |
||||
Audience string `json:"aud,omitempty"` |
||||
ExpiresAt int64 `json:"exp,omitempty"` |
||||
Id string `json:"jti,omitempty"` |
||||
IssuedAt int64 `json:"iat,omitempty"` |
||||
Issuer string `json:"iss,omitempty"` |
||||
NotBefore int64 `json:"nbf,omitempty"` |
||||
Subject string `json:"sub,omitempty"` |
||||
} |
||||
|
||||
// Validates time based claims "exp, iat, nbf".
|
||||
// There is no accounting for clock skew.
|
||||
// As well, if any of the above claims are not in the token, it will still
|
||||
// be considered a valid claim.
|
||||
func (c StandardClaims) Valid() error { |
||||
vErr := new(ValidationError) |
||||
now := TimeFunc().Unix() |
||||
|
||||
// The claims below are optional, by default, so if they are set to the
|
||||
// default value in Go, let's not fail the verification for them.
|
||||
if c.VerifyExpiresAt(now, false) == false { |
||||
delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0)) |
||||
vErr.Inner = fmt.Errorf("token is expired by %v", delta) |
||||
vErr.Errors |= ValidationErrorExpired |
||||
} |
||||
|
||||
if c.VerifyIssuedAt(now, false) == false { |
||||
vErr.Inner = fmt.Errorf("Token used before issued") |
||||
vErr.Errors |= ValidationErrorIssuedAt |
||||
} |
||||
|
||||
if c.VerifyNotBefore(now, false) == false { |
||||
vErr.Inner = fmt.Errorf("token is not valid yet") |
||||
vErr.Errors |= ValidationErrorNotValidYet |
||||
} |
||||
|
||||
if vErr.valid() { |
||||
return nil |
||||
} |
||||
|
||||
return vErr |
||||
} |
||||
|
||||
// Compares the aud claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool { |
||||
return verifyAud(c.Audience, cmp, req) |
||||
} |
||||
|
||||
// Compares the exp claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool { |
||||
return verifyExp(c.ExpiresAt, cmp, req) |
||||
} |
||||
|
||||
// Compares the iat claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool { |
||||
return verifyIat(c.IssuedAt, cmp, req) |
||||
} |
||||
|
||||
// Compares the iss claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (c *StandardClaims) VerifyIssuer(cmp string, req bool) bool { |
||||
return verifyIss(c.Issuer, cmp, req) |
||||
} |
||||
|
||||
// Compares the nbf claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool { |
||||
return verifyNbf(c.NotBefore, cmp, req) |
||||
} |
||||
|
||||
// ----- helpers
|
||||
|
||||
func verifyAud(aud string, cmp string, required bool) bool { |
||||
if aud == "" { |
||||
return !required |
||||
} |
||||
if subtle.ConstantTimeCompare([]byte(aud), []byte(cmp)) != 0 { |
||||
return true |
||||
} else { |
||||
return false |
||||
} |
||||
} |
||||
|
||||
func verifyExp(exp int64, now int64, required bool) bool { |
||||
if exp == 0 { |
||||
return !required |
||||
} |
||||
return now <= exp |
||||
} |
||||
|
||||
func verifyIat(iat int64, now int64, required bool) bool { |
||||
if iat == 0 { |
||||
return !required |
||||
} |
||||
return now >= iat |
||||
} |
||||
|
||||
func verifyIss(iss string, cmp string, required bool) bool { |
||||
if iss == "" { |
||||
return !required |
||||
} |
||||
if subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 { |
||||
return true |
||||
} else { |
||||
return false |
||||
} |
||||
} |
||||
|
||||
func verifyNbf(nbf int64, now int64, required bool) bool { |
||||
if nbf == 0 { |
||||
return !required |
||||
} |
||||
return now >= nbf |
||||
} |
@ -1,100 +0,0 @@ |
||||
package jwt_test |
||||
|
||||
import ( |
||||
"crypto/ecdsa" |
||||
"io/ioutil" |
||||
"strings" |
||||
"testing" |
||||
|
||||
"github.com/dgrijalva/jwt-go" |
||||
) |
||||
|
||||
var ecdsaTestData = []struct { |
||||
name string |
||||
keys map[string]string |
||||
tokenString string |
||||
alg string |
||||
claims map[string]interface{} |
||||
valid bool |
||||
}{ |
||||
{ |
||||
"Basic ES256", |
||||
map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"}, |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJmb28iOiJiYXIifQ.feG39E-bn8HXAKhzDZq7yEAPWYDhZlwTn3sePJnU9VrGMmwdXAIEyoOnrjreYlVM_Z4N13eK9-TmMTWyfKJtHQ", |
||||
"ES256", |
||||
map[string]interface{}{"foo": "bar"}, |
||||
true, |
||||
}, |
||||
{ |
||||
"Basic ES384", |
||||
map[string]string{"private": "test/ec384-private.pem", "public": "test/ec384-public.pem"}, |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJmb28iOiJiYXIifQ.ngAfKMbJUh0WWubSIYe5GMsA-aHNKwFbJk_wq3lq23aPp8H2anb1rRILIzVR0gUf4a8WzDtrzmiikuPWyCS6CN4-PwdgTk-5nehC7JXqlaBZU05p3toM3nWCwm_LXcld", |
||||
"ES384", |
||||
map[string]interface{}{"foo": "bar"}, |
||||
true, |
||||
}, |
||||
{ |
||||
"Basic ES512", |
||||
map[string]string{"private": "test/ec512-private.pem", "public": "test/ec512-public.pem"}, |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJmb28iOiJiYXIifQ.AAU0TvGQOcdg2OvrwY73NHKgfk26UDekh9Prz-L_iWuTBIBqOFCWwwLsRiHB1JOddfKAls5do1W0jR_F30JpVd-6AJeTjGKA4C1A1H6gIKwRY0o_tFDIydZCl_lMBMeG5VNFAjO86-WCSKwc3hqaGkq1MugPRq_qrF9AVbuEB4JPLyL5", |
||||
"ES512", |
||||
map[string]interface{}{"foo": "bar"}, |
||||
true, |
||||
}, |
||||
{ |
||||
"basic ES256 invalid: foo => bar", |
||||
map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"}, |
||||
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MEQCIHoSJnmGlPaVQDqacx_2XlXEhhqtWceVopjomc2PJLtdAiAUTeGPoNYxZw0z8mgOnnIcjoxRuNDVZvybRZF3wR1l8W", |
||||
"ES256", |
||||
map[string]interface{}{"foo": "bar"}, |
||||
false, |
||||
}, |
||||
} |
||||
|
||||
func TestECDSAVerify(t *testing.T) { |
||||
for _, data := range ecdsaTestData { |
||||
var err error |
||||
|
||||
key, _ := ioutil.ReadFile(data.keys["public"]) |
||||
|
||||
var ecdsaKey *ecdsa.PublicKey |
||||
if ecdsaKey, err = jwt.ParseECPublicKeyFromPEM(key); err != nil { |
||||
t.Errorf("Unable to parse ECDSA public key: %v", err) |
||||
} |
||||
|
||||
parts := strings.Split(data.tokenString, ".") |
||||
|
||||
method := jwt.GetSigningMethod(data.alg) |
||||
err = method.Verify(strings.Join(parts[0:2], "."), parts[2], ecdsaKey) |
||||
if data.valid && err != nil { |
||||
t.Errorf("[%v] Error while verifying key: %v", data.name, err) |
||||
} |
||||
if !data.valid && err == nil { |
||||
t.Errorf("[%v] Invalid key passed validation", data.name) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestECDSASign(t *testing.T) { |
||||
for _, data := range ecdsaTestData { |
||||
var err error |
||||
key, _ := ioutil.ReadFile(data.keys["private"]) |
||||
|
||||
var ecdsaKey *ecdsa.PrivateKey |
||||
if ecdsaKey, err = jwt.ParseECPrivateKeyFromPEM(key); err != nil { |
||||
t.Errorf("Unable to parse ECDSA private key: %v", err) |
||||
} |
||||
|
||||
if data.valid { |
||||
parts := strings.Split(data.tokenString, ".") |
||||
method := jwt.GetSigningMethod(data.alg) |
||||
sig, err := method.Sign(strings.Join(parts[0:2], "."), ecdsaKey) |
||||
if err != nil { |
||||
t.Errorf("[%v] Error signing token: %v", data.name, err) |
||||
} |
||||
if sig == parts[2] { |
||||
t.Errorf("[%v] Identical signatures\nbefore:\n%v\nafter:\n%v", data.name, parts[2], sig) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,52 +0,0 @@ |
||||
package jwt_test |
||||
|
||||
import ( |
||||
"fmt" |
||||
"github.com/dgrijalva/jwt-go" |
||||
"time" |
||||
) |
||||
|
||||
func ExampleParse(myToken string, myLookupKey func(interface{}) (interface{}, error)) { |
||||
token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) { |
||||
return myLookupKey(token.Header["kid"]) |
||||
}) |
||||
|
||||
if err == nil && token.Valid { |
||||
fmt.Println("Your token is valid. I like your style.") |
||||
} else { |
||||
fmt.Println("This token is terrible! I cannot accept this.") |
||||
} |
||||
} |
||||
|
||||
func ExampleNew(mySigningKey []byte) (string, error) { |
||||
// Create the token
|
||||
token := jwt.New(jwt.SigningMethodHS256) |
||||
// Set some claims
|
||||
token.Claims["foo"] = "bar" |
||||
token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix() |
||||
// Sign and get the complete encoded token as a string
|
||||
tokenString, err := token.SignedString(mySigningKey) |
||||
return tokenString, err |
||||
} |
||||
|
||||
func ExampleParse_errorChecking(myToken string, myLookupKey func(interface{}) (interface{}, error)) { |
||||
token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) { |
||||
return myLookupKey(token.Header["kid"]) |
||||
}) |
||||
|
||||
if token.Valid { |
||||
fmt.Println("You look nice today") |
||||
} else if ve, ok := err.(*jwt.ValidationError); ok { |
||||
if ve.Errors&jwt.ValidationErrorMalformed != 0 { |
||||
fmt.Println("That's not even a token") |
||||
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { |
||||
// Token is either expired or not active yet
|
||||
fmt.Println("Timing is everything") |
||||
} else { |
||||
fmt.Println("Couldn't handle this token:", err) |
||||
} |
||||
} else { |
||||
fmt.Println("Couldn't handle this token:", err) |
||||
} |
||||
|
||||
} |
@ -1,91 +0,0 @@ |
||||
package jwt_test |
||||
|
||||
import ( |
||||
"github.com/dgrijalva/jwt-go" |
||||
"io/ioutil" |
||||
"strings" |
||||
"testing" |
||||
) |
||||
|
||||
var hmacTestData = []struct { |
||||
name string |
||||
tokenString string |
||||
alg string |
||||
claims map[string]interface{} |
||||
valid bool |
||||
}{ |
||||
{ |
||||
"web sample", |
||||
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk", |
||||
"HS256", |
||||
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, |
||||
true, |
||||
}, |
||||
{ |
||||
"HS384", |
||||
"eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.KWZEuOD5lbBxZ34g7F-SlVLAQ_r5KApWNWlZIIMyQVz5Zs58a7XdNzj5_0EcNoOy", |
||||
"HS384", |
||||
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, |
||||
true, |
||||
}, |
||||
{ |
||||
"HS512", |
||||
"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.CN7YijRX6Aw1n2jyI2Id1w90ja-DEMYiWixhYCyHnrZ1VfJRaFQz1bEbjjA5Fn4CLYaUG432dEYmSbS4Saokmw", |
||||
"HS512", |
||||
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, |
||||
true, |
||||
}, |
||||
{ |
||||
"web sample: invalid", |
||||
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXo", |
||||
"HS256", |
||||
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, |
||||
false, |
||||
}, |
||||
} |
||||
|
||||
// Sample data from http://tools.ietf.org/html/draft-jones-json-web-signature-04#appendix-A.1
|
||||
var hmacTestKey, _ = ioutil.ReadFile("test/hmacTestKey") |
||||
|
||||
func TestHMACVerify(t *testing.T) { |
||||
for _, data := range hmacTestData { |
||||
parts := strings.Split(data.tokenString, ".") |
||||
|
||||
method := jwt.GetSigningMethod(data.alg) |
||||
err := method.Verify(strings.Join(parts[0:2], "."), parts[2], hmacTestKey) |
||||
if data.valid && err != nil { |
||||
t.Errorf("[%v] Error while verifying key: %v", data.name, err) |
||||
} |
||||
if !data.valid && err == nil { |
||||
t.Errorf("[%v] Invalid key passed validation", data.name) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestHMACSign(t *testing.T) { |
||||
for _, data := range hmacTestData { |
||||
if data.valid { |
||||
parts := strings.Split(data.tokenString, ".") |
||||
method := jwt.GetSigningMethod(data.alg) |
||||
sig, err := method.Sign(strings.Join(parts[0:2], "."), hmacTestKey) |
||||
if err != nil { |
||||
t.Errorf("[%v] Error signing token: %v", data.name, err) |
||||
} |
||||
if sig != parts[2] { |
||||
t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2]) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func BenchmarkHS256Signing(b *testing.B) { |
||||
benchmarkSigning(b, jwt.SigningMethodHS256, hmacTestKey) |
||||
} |
||||
|
||||
func BenchmarkHS384Signing(b *testing.B) { |
||||
benchmarkSigning(b, jwt.SigningMethodHS384, hmacTestKey) |
||||
} |
||||
|
||||
func BenchmarkHS512Signing(b *testing.B) { |
||||
benchmarkSigning(b, jwt.SigningMethodHS512, hmacTestKey) |
||||
} |
@ -0,0 +1,94 @@ |
||||
package jwt |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"errors" |
||||
// "fmt"
|
||||
) |
||||
|
||||
// Claims type that uses the map[string]interface{} for JSON decoding
|
||||
// This is the default claims type if you don't supply one
|
||||
type MapClaims map[string]interface{} |
||||
|
||||
// Compares the aud claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (m MapClaims) VerifyAudience(cmp string, req bool) bool { |
||||
aud, _ := m["aud"].(string) |
||||
return verifyAud(aud, cmp, req) |
||||
} |
||||
|
||||
// Compares the exp claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (m MapClaims) VerifyExpiresAt(cmp int64, req bool) bool { |
||||
switch exp := m["exp"].(type) { |
||||
case float64: |
||||
return verifyExp(int64(exp), cmp, req) |
||||
case json.Number: |
||||
v, _ := exp.Int64() |
||||
return verifyExp(v, cmp, req) |
||||
} |
||||
return req == false |
||||
} |
||||
|
||||
// Compares the iat claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool { |
||||
switch iat := m["iat"].(type) { |
||||
case float64: |
||||
return verifyIat(int64(iat), cmp, req) |
||||
case json.Number: |
||||
v, _ := iat.Int64() |
||||
return verifyIat(v, cmp, req) |
||||
} |
||||
return req == false |
||||
} |
||||
|
||||
// Compares the iss claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (m MapClaims) VerifyIssuer(cmp string, req bool) bool { |
||||
iss, _ := m["iss"].(string) |
||||
return verifyIss(iss, cmp, req) |
||||
} |
||||
|
||||
// Compares the nbf claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (m MapClaims) VerifyNotBefore(cmp int64, req bool) bool { |
||||
switch nbf := m["nbf"].(type) { |
||||
case float64: |
||||
return verifyNbf(int64(nbf), cmp, req) |
||||
case json.Number: |
||||
v, _ := nbf.Int64() |
||||
return verifyNbf(v, cmp, req) |
||||
} |
||||
return req == false |
||||
} |
||||
|
||||
// Validates time based claims "exp, iat, nbf".
|
||||
// There is no accounting for clock skew.
|
||||
// As well, if any of the above claims are not in the token, it will still
|
||||
// be considered a valid claim.
|
||||
func (m MapClaims) Valid() error { |
||||
vErr := new(ValidationError) |
||||
now := TimeFunc().Unix() |
||||
|
||||
if m.VerifyExpiresAt(now, false) == false { |
||||
vErr.Inner = errors.New("Token is expired") |
||||
vErr.Errors |= ValidationErrorExpired |
||||
} |
||||
|
||||
if m.VerifyIssuedAt(now, false) == false { |
||||
vErr.Inner = errors.New("Token used before issued") |
||||
vErr.Errors |= ValidationErrorIssuedAt |
||||
} |
||||
|
||||
if m.VerifyNotBefore(now, false) == false { |
||||
vErr.Inner = errors.New("Token is not valid yet") |
||||
vErr.Errors |= ValidationErrorNotValidYet |
||||
} |
||||
|
||||
if vErr.valid() { |
||||
return nil |
||||
} |
||||
|
||||
return vErr |
||||
} |
@ -0,0 +1,52 @@ |
||||
package jwt |
||||
|
||||
// Implements the none signing method. This is required by the spec
|
||||
// but you probably should never use it.
|
||||
var SigningMethodNone *signingMethodNone |
||||
|
||||
const UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed" |
||||
|
||||
var NoneSignatureTypeDisallowedError error |
||||
|
||||
type signingMethodNone struct{} |
||||
type unsafeNoneMagicConstant string |
||||
|
||||
func init() { |
||||
SigningMethodNone = &signingMethodNone{} |
||||
NoneSignatureTypeDisallowedError = NewValidationError("'none' signature type is not allowed", ValidationErrorSignatureInvalid) |
||||
|
||||
RegisterSigningMethod(SigningMethodNone.Alg(), func() SigningMethod { |
||||
return SigningMethodNone |
||||
}) |
||||
} |
||||
|
||||
func (m *signingMethodNone) Alg() string { |
||||
return "none" |
||||
} |
||||
|
||||
// Only allow 'none' alg type if UnsafeAllowNoneSignatureType is specified as the key
|
||||
func (m *signingMethodNone) Verify(signingString, signature string, key interface{}) (err error) { |
||||
// Key must be UnsafeAllowNoneSignatureType to prevent accidentally
|
||||
// accepting 'none' signing method
|
||||
if _, ok := key.(unsafeNoneMagicConstant); !ok { |
||||
return NoneSignatureTypeDisallowedError |
||||
} |
||||
// If signing method is none, signature must be an empty string
|
||||
if signature != "" { |
||||
return NewValidationError( |
||||
"'none' signing method with non-empty signature", |
||||
ValidationErrorSignatureInvalid, |
||||
) |
||||
} |
||||
|
||||
// Accept 'none' signing method.
|
||||
return nil |
||||
} |
||||
|
||||
// Only allow 'none' signing if UnsafeAllowNoneSignatureType is specified as the key
|
||||
func (m *signingMethodNone) Sign(signingString string, key interface{}) (string, error) { |
||||
if _, ok := key.(unsafeNoneMagicConstant); ok { |
||||
return "", nil |
||||
} |
||||
return "", NoneSignatureTypeDisallowedError |
||||
} |
@ -1,239 +0,0 @@ |
||||
package jwt_test |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"github.com/dgrijalva/jwt-go" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"reflect" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
var ( |
||||
jwtTestDefaultKey []byte |
||||
defaultKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestDefaultKey, nil } |
||||
emptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, nil } |
||||
errorKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, fmt.Errorf("error loading key") } |
||||
nilKeyFunc jwt.Keyfunc = nil |
||||
) |
||||
|
||||
var jwtTestData = []struct { |
||||
name string |
||||
tokenString string |
||||
keyfunc jwt.Keyfunc |
||||
claims map[string]interface{} |
||||
valid bool |
||||
errors uint32 |
||||
parser *jwt.Parser |
||||
}{ |
||||
{ |
||||
"basic", |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", |
||||
defaultKeyFunc, |
||||
map[string]interface{}{"foo": "bar"}, |
||||
true, |
||||
0, |
||||
nil, |
||||
}, |
||||
{ |
||||
"basic expired", |
||||
"", // autogen
|
||||
defaultKeyFunc, |
||||
map[string]interface{}{"foo": "bar", "exp": float64(time.Now().Unix() - 100)}, |
||||
false, |
||||
jwt.ValidationErrorExpired, |
||||
nil, |
||||
}, |
||||
{ |
||||
"basic nbf", |
||||
"", // autogen
|
||||
defaultKeyFunc, |
||||
map[string]interface{}{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)}, |
||||
false, |
||||
jwt.ValidationErrorNotValidYet, |
||||
nil, |
||||
}, |
||||
{ |
||||
"expired and nbf", |
||||
"", // autogen
|
||||
defaultKeyFunc, |
||||
map[string]interface{}{"foo": "bar", "nbf": float64(time.Now().Unix() + 100), "exp": float64(time.Now().Unix() - 100)}, |
||||
false, |
||||
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired, |
||||
nil, |
||||
}, |
||||
{ |
||||
"basic invalid", |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", |
||||
defaultKeyFunc, |
||||
map[string]interface{}{"foo": "bar"}, |
||||
false, |
||||
jwt.ValidationErrorSignatureInvalid, |
||||
nil, |
||||
}, |
||||
{ |
||||
"basic nokeyfunc", |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", |
||||
nilKeyFunc, |
||||
map[string]interface{}{"foo": "bar"}, |
||||
false, |
||||
jwt.ValidationErrorUnverifiable, |
||||
nil, |
||||
}, |
||||
{ |
||||
"basic nokey", |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", |
||||
emptyKeyFunc, |
||||
map[string]interface{}{"foo": "bar"}, |
||||
false, |
||||
jwt.ValidationErrorSignatureInvalid, |
||||
nil, |
||||
}, |
||||
{ |
||||
"basic errorkey", |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", |
||||
errorKeyFunc, |
||||
map[string]interface{}{"foo": "bar"}, |
||||
false, |
||||
jwt.ValidationErrorUnverifiable, |
||||
nil, |
||||
}, |
||||
{ |
||||
"invalid signing method", |
||||
"", |
||||
defaultKeyFunc, |
||||
map[string]interface{}{"foo": "bar"}, |
||||
false, |
||||
jwt.ValidationErrorSignatureInvalid, |
||||
&jwt.Parser{ValidMethods: []string{"HS256"}}, |
||||
}, |
||||
{ |
||||
"valid signing method", |
||||
"", |
||||
defaultKeyFunc, |
||||
map[string]interface{}{"foo": "bar"}, |
||||
true, |
||||
0, |
||||
&jwt.Parser{ValidMethods: []string{"RS256", "HS256"}}, |
||||
}, |
||||
{ |
||||
"JSON Number", |
||||
"", |
||||
defaultKeyFunc, |
||||
map[string]interface{}{"foo": json.Number("123.4")}, |
||||
true, |
||||
0, |
||||
&jwt.Parser{UseJSONNumber: true}, |
||||
}, |
||||
} |
||||
|
||||
func init() { |
||||
var e error |
||||
if jwtTestDefaultKey, e = ioutil.ReadFile("test/sample_key.pub"); e != nil { |
||||
panic(e) |
||||
} |
||||
} |
||||
|
||||
func makeSample(c map[string]interface{}) string { |
||||
key, e := ioutil.ReadFile("test/sample_key") |
||||
if e != nil { |
||||
panic(e.Error()) |
||||
} |
||||
|
||||
token := jwt.New(jwt.SigningMethodRS256) |
||||
token.Claims = c |
||||
s, e := token.SignedString(key) |
||||
|
||||
if e != nil { |
||||
panic(e.Error()) |
||||
} |
||||
|
||||
return s |
||||
} |
||||
|
||||
func TestParser_Parse(t *testing.T) { |
||||
for _, data := range jwtTestData { |
||||
if data.tokenString == "" { |
||||
data.tokenString = makeSample(data.claims) |
||||
} |
||||
|
||||
var token *jwt.Token |
||||
var err error |
||||
if data.parser != nil { |
||||
token, err = data.parser.Parse(data.tokenString, data.keyfunc) |
||||
} else { |
||||
token, err = jwt.Parse(data.tokenString, data.keyfunc) |
||||
} |
||||
|
||||
if !reflect.DeepEqual(data.claims, token.Claims) { |
||||
t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims) |
||||
} |
||||
if data.valid && err != nil { |
||||
t.Errorf("[%v] Error while verifying token: %T:%v", data.name, err, err) |
||||
} |
||||
if !data.valid && err == nil { |
||||
t.Errorf("[%v] Invalid token passed validation", data.name) |
||||
} |
||||
if data.errors != 0 { |
||||
if err == nil { |
||||
t.Errorf("[%v] Expecting error. Didn't get one.", data.name) |
||||
} else { |
||||
// compare the bitfield part of the error
|
||||
if e := err.(*jwt.ValidationError).Errors; e != data.errors { |
||||
t.Errorf("[%v] Errors don't match expectation. %v != %v", data.name, e, data.errors) |
||||
} |
||||
} |
||||
} |
||||
if data.valid && token.Signature == "" { |
||||
t.Errorf("[%v] Signature is left unpopulated after parsing", data.name) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestParseRequest(t *testing.T) { |
||||
// Bearer token request
|
||||
for _, data := range jwtTestData { |
||||
// FIXME: custom parsers are not supported by this helper. skip tests that require them
|
||||
if data.parser != nil { |
||||
t.Logf("Skipping [%v]. Custom parsers are not supported by ParseRequest", data.name) |
||||
continue |
||||
} |
||||
|
||||
if data.tokenString == "" { |
||||
data.tokenString = makeSample(data.claims) |
||||
} |
||||
|
||||
r, _ := http.NewRequest("GET", "/", nil) |
||||
r.Header.Set("Authorization", fmt.Sprintf("Bearer %v", data.tokenString)) |
||||
token, err := jwt.ParseFromRequest(r, data.keyfunc) |
||||
|
||||
if token == nil { |
||||
t.Errorf("[%v] Token was not found: %v", data.name, err) |
||||
continue |
||||
} |
||||
if !reflect.DeepEqual(data.claims, token.Claims) { |
||||
t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims) |
||||
} |
||||
if data.valid && err != nil { |
||||
t.Errorf("[%v] Error while verifying token: %v", data.name, err) |
||||
} |
||||
if !data.valid && err == nil { |
||||
t.Errorf("[%v] Invalid token passed validation", data.name) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Helper method for benchmarking various methods
|
||||
func benchmarkSigning(b *testing.B, method jwt.SigningMethod, key interface{}) { |
||||
t := jwt.New(method) |
||||
b.RunParallel(func(pb *testing.PB) { |
||||
for pb.Next() { |
||||
if _, err := t.SignedString(key); err != nil { |
||||
b.Fatal(err) |
||||
} |
||||
} |
||||
}) |
||||
|
||||
} |
@ -0,0 +1,7 @@ |
||||
// Utility package for extracting JWT tokens from
|
||||
// HTTP requests.
|
||||
//
|
||||
// The main function is ParseFromRequest and it's WithClaims variant.
|
||||
// See examples for how to use the various Extractor implementations
|
||||
// or roll your own.
|
||||
package request |
@ -0,0 +1,81 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"errors" |
||||
"net/http" |
||||
) |
||||
|
||||
// Errors
|
||||
var ( |
||||
ErrNoTokenInRequest = errors.New("no token present in request") |
||||
) |
||||
|
||||
// Interface for extracting a token from an HTTP request.
|
||||
// The ExtractToken method should return a token string or an error.
|
||||
// If no token is present, you must return ErrNoTokenInRequest.
|
||||
type Extractor interface { |
||||
ExtractToken(*http.Request) (string, error) |
||||
} |
||||
|
||||
// Extractor for finding a token in a header. Looks at each specified
|
||||
// header in order until there's a match
|
||||
type HeaderExtractor []string |
||||
|
||||
func (e HeaderExtractor) ExtractToken(req *http.Request) (string, error) { |
||||
// loop over header names and return the first one that contains data
|
||||
for _, header := range e { |
||||
if ah := req.Header.Get(header); ah != "" { |
||||
return ah, nil |
||||
} |
||||
} |
||||
return "", ErrNoTokenInRequest |
||||
} |
||||
|
||||
// Extract token from request arguments. This includes a POSTed form or
|
||||
// GET URL arguments. Argument names are tried in order until there's a match.
|
||||
// This extractor calls `ParseMultipartForm` on the request
|
||||
type ArgumentExtractor []string |
||||
|
||||
func (e ArgumentExtractor) ExtractToken(req *http.Request) (string, error) { |
||||
// Make sure form is parsed
|
||||
req.ParseMultipartForm(10e6) |
||||
|
||||
// loop over arg names and return the first one that contains data
|
||||
for _, arg := range e { |
||||
if ah := req.Form.Get(arg); ah != "" { |
||||
return ah, nil |
||||
} |
||||
} |
||||
|
||||
return "", ErrNoTokenInRequest |
||||
} |
||||
|
||||
// Tries Extractors in order until one returns a token string or an error occurs
|
||||
type MultiExtractor []Extractor |
||||
|
||||
func (e MultiExtractor) ExtractToken(req *http.Request) (string, error) { |
||||
// loop over header names and return the first one that contains data
|
||||
for _, extractor := range e { |
||||
if tok, err := extractor.ExtractToken(req); tok != "" { |
||||
return tok, nil |
||||
} else if err != ErrNoTokenInRequest { |
||||
return "", err |
||||
} |
||||
} |
||||
return "", ErrNoTokenInRequest |
||||
} |
||||
|
||||
// Wrap an Extractor in this to post-process the value before it's handed off.
|
||||
// See AuthorizationHeaderExtractor for an example
|
||||
type PostExtractionFilter struct { |
||||
Extractor |
||||
Filter func(string) (string, error) |
||||
} |
||||
|
||||
func (e *PostExtractionFilter) ExtractToken(req *http.Request) (string, error) { |
||||
if tok, err := e.Extractor.ExtractToken(req); tok != "" { |
||||
return e.Filter(tok) |
||||
} else { |
||||
return "", err |
||||
} |
||||
} |
@ -0,0 +1,28 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"strings" |
||||
) |
||||
|
||||
// Strips 'Bearer ' prefix from bearer token string
|
||||
func stripBearerPrefixFromTokenString(tok string) (string, error) { |
||||
// Should be a bearer token
|
||||
if len(tok) > 6 && strings.ToUpper(tok[0:7]) == "BEARER " { |
||||
return tok[7:], nil |
||||
} |
||||
return tok, nil |
||||
} |
||||
|
||||
// Extract bearer token from Authorization header
|
||||
// Uses PostExtractionFilter to strip "Bearer " prefix from header
|
||||
var AuthorizationHeaderExtractor = &PostExtractionFilter{ |
||||
HeaderExtractor{"Authorization"}, |
||||
stripBearerPrefixFromTokenString, |
||||
} |
||||
|
||||
// Extractor for OAuth2 access tokens. Looks in 'Authorization'
|
||||
// header then 'access_token' argument for a token.
|
||||
var OAuth2Extractor = &MultiExtractor{ |
||||
AuthorizationHeaderExtractor, |
||||
ArgumentExtractor{"access_token"}, |
||||
} |
@ -0,0 +1,24 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"github.com/dgrijalva/jwt-go" |
||||
"net/http" |
||||
) |
||||
|
||||
// Extract and parse a JWT token from an HTTP request.
|
||||
// This behaves the same as Parse, but accepts a request and an extractor
|
||||
// instead of a token string. The Extractor interface allows you to define
|
||||
// the logic for extracting a token. Several useful implementations are provided.
|
||||
func ParseFromRequest(req *http.Request, extractor Extractor, keyFunc jwt.Keyfunc) (token *jwt.Token, err error) { |
||||
return ParseFromRequestWithClaims(req, extractor, jwt.MapClaims{}, keyFunc) |
||||
} |
||||
|
||||
// ParseFromRequest but with custom Claims type
|
||||
func ParseFromRequestWithClaims(req *http.Request, extractor Extractor, claims jwt.Claims, keyFunc jwt.Keyfunc) (token *jwt.Token, err error) { |
||||
// Extract token from request
|
||||
if tokStr, err := extractor.ExtractToken(req); err == nil { |
||||
return jwt.ParseWithClaims(tokStr, claims, keyFunc) |
||||
} else { |
||||
return nil, err |
||||
} |
||||
} |
@ -1,96 +0,0 @@ |
||||
// +build go1.4
|
||||
|
||||
package jwt_test |
||||
|
||||
import ( |
||||
"crypto/rsa" |
||||
"io/ioutil" |
||||
"strings" |
||||
"testing" |
||||
|
||||
"github.com/dgrijalva/jwt-go" |
||||
) |
||||
|
||||
var rsaPSSTestData = []struct { |
||||
name string |
||||
tokenString string |
||||
alg string |
||||
claims map[string]interface{} |
||||
valid bool |
||||
}{ |
||||
{ |
||||
"Basic PS256", |
||||
"eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.PPG4xyDVY8ffp4CcxofNmsTDXsrVG2npdQuibLhJbv4ClyPTUtR5giNSvuxo03kB6I8VXVr0Y9X7UxhJVEoJOmULAwRWaUsDnIewQa101cVhMa6iR8X37kfFoiZ6NkS-c7henVkkQWu2HtotkEtQvN5hFlk8IevXXPmvZlhQhwzB1sGzGYnoi1zOfuL98d3BIjUjtlwii5w6gYG2AEEzp7HnHCsb3jIwUPdq86Oe6hIFjtBwduIK90ca4UqzARpcfwxHwVLMpatKask00AgGVI0ysdk0BLMjmLutquD03XbThHScC2C2_Pp4cHWgMzvbgLU2RYYZcZRKr46QeNgz9w", |
||||
"PS256", |
||||
map[string]interface{}{"foo": "bar"}, |
||||
true, |
||||
}, |
||||
{ |
||||
"Basic PS384", |
||||
"eyJhbGciOiJQUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.w7-qqgj97gK4fJsq_DCqdYQiylJjzWONvD0qWWWhqEOFk2P1eDULPnqHRnjgTXoO4HAw4YIWCsZPet7nR3Xxq4ZhMqvKW8b7KlfRTb9cH8zqFvzMmybQ4jv2hKc3bXYqVow3AoR7hN_CWXI3Dv6Kd2X5xhtxRHI6IL39oTVDUQ74LACe-9t4c3QRPuj6Pq1H4FAT2E2kW_0KOc6EQhCLWEhm2Z2__OZskDC8AiPpP8Kv4k2vB7l0IKQu8Pr4RcNBlqJdq8dA5D3hk5TLxP8V5nG1Ib80MOMMqoS3FQvSLyolFX-R_jZ3-zfq6Ebsqr0yEb0AH2CfsECF7935Pa0FKQ", |
||||
"PS384", |
||||
map[string]interface{}{"foo": "bar"}, |
||||
true, |
||||
}, |
||||
{ |
||||
"Basic PS512", |
||||
"eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.GX1HWGzFaJevuSLavqqFYaW8_TpvcjQ8KfC5fXiSDzSiT9UD9nB_ikSmDNyDILNdtjZLSvVKfXxZJqCfefxAtiozEDDdJthZ-F0uO4SPFHlGiXszvKeodh7BuTWRI2wL9-ZO4mFa8nq3GMeQAfo9cx11i7nfN8n2YNQ9SHGovG7_T_AvaMZB_jT6jkDHpwGR9mz7x1sycckEo6teLdHRnH_ZdlHlxqknmyTu8Odr5Xh0sJFOL8BepWbbvIIn-P161rRHHiDWFv6nhlHwZnVzjx7HQrWSGb6-s2cdLie9QL_8XaMcUpjLkfOMKkDOfHo6AvpL7Jbwi83Z2ZTHjJWB-A", |
||||
"PS512", |
||||
map[string]interface{}{"foo": "bar"}, |
||||
true, |
||||
}, |
||||
{ |
||||
"basic PS256 invalid: foo => bar", |
||||
"eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.PPG4xyDVY8ffp4CcxofNmsTDXsrVG2npdQuibLhJbv4ClyPTUtR5giNSvuxo03kB6I8VXVr0Y9X7UxhJVEoJOmULAwRWaUsDnIewQa101cVhMa6iR8X37kfFoiZ6NkS-c7henVkkQWu2HtotkEtQvN5hFlk8IevXXPmvZlhQhwzB1sGzGYnoi1zOfuL98d3BIjUjtlwii5w6gYG2AEEzp7HnHCsb3jIwUPdq86Oe6hIFjtBwduIK90ca4UqzARpcfwxHwVLMpatKask00AgGVI0ysdk0BLMjmLutquD03XbThHScC2C2_Pp4cHWgMzvbgLU2RYYZcZRKr46QeNgz9W", |
||||
"PS256", |
||||
map[string]interface{}{"foo": "bar"}, |
||||
false, |
||||
}, |
||||
} |
||||
|
||||
func TestRSAPSSVerify(t *testing.T) { |
||||
var err error |
||||
|
||||
key, _ := ioutil.ReadFile("test/sample_key.pub") |
||||
var rsaPSSKey *rsa.PublicKey |
||||
if rsaPSSKey, err = jwt.ParseRSAPublicKeyFromPEM(key); err != nil { |
||||
t.Errorf("Unable to parse RSA public key: %v", err) |
||||
} |
||||
|
||||
for _, data := range rsaPSSTestData { |
||||
parts := strings.Split(data.tokenString, ".") |
||||
|
||||
method := jwt.GetSigningMethod(data.alg) |
||||
err := method.Verify(strings.Join(parts[0:2], "."), parts[2], rsaPSSKey) |
||||
if data.valid && err != nil { |
||||
t.Errorf("[%v] Error while verifying key: %v", data.name, err) |
||||
} |
||||
if !data.valid && err == nil { |
||||
t.Errorf("[%v] Invalid key passed validation", data.name) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestRSAPSSSign(t *testing.T) { |
||||
var err error |
||||
|
||||
key, _ := ioutil.ReadFile("test/sample_key") |
||||
var rsaPSSKey *rsa.PrivateKey |
||||
if rsaPSSKey, err = jwt.ParseRSAPrivateKeyFromPEM(key); err != nil { |
||||
t.Errorf("Unable to parse RSA private key: %v", err) |
||||
} |
||||
|
||||
for _, data := range rsaPSSTestData { |
||||
if data.valid { |
||||
parts := strings.Split(data.tokenString, ".") |
||||
method := jwt.GetSigningMethod(data.alg) |
||||
sig, err := method.Sign(strings.Join(parts[0:2], "."), rsaPSSKey) |
||||
if err != nil { |
||||
t.Errorf("[%v] Error signing token: %v", data.name, err) |
||||
} |
||||
if sig == parts[2] { |
||||
t.Errorf("[%v] Signatures shouldn't match\nnew:\n%v\noriginal:\n%v", data.name, sig, parts[2]) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,174 +0,0 @@ |
||||
package jwt_test |
||||
|
||||
import ( |
||||
"github.com/dgrijalva/jwt-go" |
||||
"io/ioutil" |
||||
"strings" |
||||
"testing" |
||||
) |
||||
|
||||
var rsaTestData = []struct { |
||||
name string |
||||
tokenString string |
||||
alg string |
||||
claims map[string]interface{} |
||||
valid bool |
||||
}{ |
||||
{ |
||||
"Basic RS256", |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", |
||||
"RS256", |
||||
map[string]interface{}{"foo": "bar"}, |
||||
true, |
||||
}, |
||||
{ |
||||
"Basic RS384", |
||||
"eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw", |
||||
"RS384", |
||||
map[string]interface{}{"foo": "bar"}, |
||||
true, |
||||
}, |
||||
{ |
||||
"Basic RS512", |
||||
"eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.zBlLlmRrUxx4SJPUbV37Q1joRcI9EW13grnKduK3wtYKmDXbgDpF1cZ6B-2Jsm5RB8REmMiLpGms-EjXhgnyh2TSHE-9W2gA_jvshegLWtwRVDX40ODSkTb7OVuaWgiy9y7llvcknFBTIg-FnVPVpXMmeV_pvwQyhaz1SSwSPrDyxEmksz1hq7YONXhXPpGaNbMMeDTNP_1oj8DZaqTIL9TwV8_1wb2Odt_Fy58Ke2RVFijsOLdnyEAjt2n9Mxihu9i3PhNBkkxa2GbnXBfq3kzvZ_xxGGopLdHhJjcGWXO-NiwI9_tiu14NRv4L2xC0ItD9Yz68v2ZIZEp_DuzwRQ", |
||||
"RS512", |
||||
map[string]interface{}{"foo": "bar"}, |
||||
true, |
||||
}, |
||||
{ |
||||
"basic invalid: foo => bar", |
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", |
||||
"RS256", |
||||
map[string]interface{}{"foo": "bar"}, |
||||
false, |
||||
}, |
||||
} |
||||
|
||||
func TestRSAVerify(t *testing.T) { |
||||
key, _ := ioutil.ReadFile("test/sample_key.pub") |
||||
|
||||
for _, data := range rsaTestData { |
||||
parts := strings.Split(data.tokenString, ".") |
||||
|
||||
method := jwt.GetSigningMethod(data.alg) |
||||
err := method.Verify(strings.Join(parts[0:2], "."), parts[2], key) |
||||
if data.valid && err != nil { |
||||
t.Errorf("[%v] Error while verifying key: %v", data.name, err) |
||||
} |
||||
if !data.valid && err == nil { |
||||
t.Errorf("[%v] Invalid key passed validation", data.name) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestRSASign(t *testing.T) { |
||||
key, _ := ioutil.ReadFile("test/sample_key") |
||||
|
||||
for _, data := range rsaTestData { |
||||
if data.valid { |
||||
parts := strings.Split(data.tokenString, ".") |
||||
method := jwt.GetSigningMethod(data.alg) |
||||
sig, err := method.Sign(strings.Join(parts[0:2], "."), key) |
||||
if err != nil { |
||||
t.Errorf("[%v] Error signing token: %v", data.name, err) |
||||
} |
||||
if sig != parts[2] { |
||||
t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2]) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestRSAVerifyWithPreParsedPrivateKey(t *testing.T) { |
||||
key, _ := ioutil.ReadFile("test/sample_key.pub") |
||||
parsedKey, err := jwt.ParseRSAPublicKeyFromPEM(key) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
testData := rsaTestData[0] |
||||
parts := strings.Split(testData.tokenString, ".") |
||||
err = jwt.SigningMethodRS256.Verify(strings.Join(parts[0:2], "."), parts[2], parsedKey) |
||||
if err != nil { |
||||
t.Errorf("[%v] Error while verifying key: %v", testData.name, err) |
||||
} |
||||
} |
||||
|
||||
func TestRSAWithPreParsedPrivateKey(t *testing.T) { |
||||
key, _ := ioutil.ReadFile("test/sample_key") |
||||
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
testData := rsaTestData[0] |
||||
parts := strings.Split(testData.tokenString, ".") |
||||
sig, err := jwt.SigningMethodRS256.Sign(strings.Join(parts[0:2], "."), parsedKey) |
||||
if err != nil { |
||||
t.Errorf("[%v] Error signing token: %v", testData.name, err) |
||||
} |
||||
if sig != parts[2] { |
||||
t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", testData.name, sig, parts[2]) |
||||
} |
||||
} |
||||
|
||||
func TestRSAKeyParsing(t *testing.T) { |
||||
key, _ := ioutil.ReadFile("test/sample_key") |
||||
pubKey, _ := ioutil.ReadFile("test/sample_key.pub") |
||||
badKey := []byte("All your base are belong to key") |
||||
|
||||
// Test parsePrivateKey
|
||||
if _, e := jwt.ParseRSAPrivateKeyFromPEM(key); e != nil { |
||||
t.Errorf("Failed to parse valid private key: %v", e) |
||||
} |
||||
|
||||
if k, e := jwt.ParseRSAPrivateKeyFromPEM(pubKey); e == nil { |
||||
t.Errorf("Parsed public key as valid private key: %v", k) |
||||
} |
||||
|
||||
if k, e := jwt.ParseRSAPrivateKeyFromPEM(badKey); e == nil { |
||||
t.Errorf("Parsed invalid key as valid private key: %v", k) |
||||
} |
||||
|
||||
// Test parsePublicKey
|
||||
if _, e := jwt.ParseRSAPublicKeyFromPEM(pubKey); e != nil { |
||||
t.Errorf("Failed to parse valid public key: %v", e) |
||||
} |
||||
|
||||
if k, e := jwt.ParseRSAPublicKeyFromPEM(key); e == nil { |
||||
t.Errorf("Parsed private key as valid public key: %v", k) |
||||
} |
||||
|
||||
if k, e := jwt.ParseRSAPublicKeyFromPEM(badKey); e == nil { |
||||
t.Errorf("Parsed invalid key as valid private key: %v", k) |
||||
} |
||||
|
||||
} |
||||
|
||||
func BenchmarkRS256Signing(b *testing.B) { |
||||
key, _ := ioutil.ReadFile("test/sample_key") |
||||
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key) |
||||
if err != nil { |
||||
b.Fatal(err) |
||||
} |
||||
|
||||
benchmarkSigning(b, jwt.SigningMethodRS256, parsedKey) |
||||
} |
||||
|
||||
func BenchmarkRS384Signing(b *testing.B) { |
||||
key, _ := ioutil.ReadFile("test/sample_key") |
||||
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key) |
||||
if err != nil { |
||||
b.Fatal(err) |
||||
} |
||||
|
||||
benchmarkSigning(b, jwt.SigningMethodRS384, parsedKey) |
||||
} |
||||
|
||||
func BenchmarkRS512Signing(b *testing.B) { |
||||
key, _ := ioutil.ReadFile("test/sample_key") |
||||
parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key) |
||||
if err != nil { |
||||
b.Fatal(err) |
||||
} |
||||
|
||||
benchmarkSigning(b, jwt.SigningMethodRS512, parsedKey) |
||||
} |
@ -0,0 +1,40 @@ |
||||
// +build go1.6
|
||||
|
||||
package humanize |
||||
|
||||
import ( |
||||
"bytes" |
||||
"math/big" |
||||
"strings" |
||||
) |
||||
|
||||
// BigCommaf produces a string form of the given big.Float in base 10
|
||||
// with commas after every three orders of magnitude.
|
||||
func BigCommaf(v *big.Float) string { |
||||
buf := &bytes.Buffer{} |
||||
if v.Sign() < 0 { |
||||
buf.Write([]byte{'-'}) |
||||
v.Abs(v) |
||||
} |
||||
|
||||
comma := []byte{','} |
||||
|
||||
parts := strings.Split(v.Text('f', -1), ".") |
||||
pos := 0 |
||||
if len(parts[0])%3 != 0 { |
||||
pos += len(parts[0]) % 3 |
||||
buf.WriteString(parts[0][:pos]) |
||||
buf.Write(comma) |
||||
} |
||||
for ; pos < len(parts[0]); pos += 3 { |
||||
buf.WriteString(parts[0][pos : pos+3]) |
||||
buf.Write(comma) |
||||
} |
||||
buf.Truncate(buf.Len() - 1) |
||||
|
||||
if len(parts) > 1 { |
||||
buf.Write([]byte{'.'}) |
||||
buf.WriteString(parts[1]) |
||||
} |
||||
return buf.String() |
||||
} |
@ -0,0 +1,26 @@ |
||||
// +build !go1.7
|
||||
|
||||
package mux |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"github.com/gorilla/context" |
||||
) |
||||
|
||||
func contextGet(r *http.Request, key interface{}) interface{} { |
||||
return context.Get(r, key) |
||||
} |
||||
|
||||
func contextSet(r *http.Request, key, val interface{}) *http.Request { |
||||
if val == nil { |
||||
return r |
||||
} |
||||
|
||||
context.Set(r, key, val) |
||||
return r |
||||
} |
||||
|
||||
func contextClear(r *http.Request) { |
||||
context.Clear(r) |
||||
} |
@ -0,0 +1,24 @@ |
||||
// +build go1.7
|
||||
|
||||
package mux |
||||
|
||||
import ( |
||||
"context" |
||||
"net/http" |
||||
) |
||||
|
||||
func contextGet(r *http.Request, key interface{}) interface{} { |
||||
return r.Context().Value(key) |
||||
} |
||||
|
||||
func contextSet(r *http.Request, key, val interface{}) *http.Request { |
||||
if val == nil { |
||||
return r |
||||
} |
||||
|
||||
return r.WithContext(context.WithValue(r.Context(), key, val)) |
||||
} |
||||
|
||||
func contextClear(r *http.Request) { |
||||
return |
||||
} |
@ -0,0 +1,27 @@ |
||||
Copyright (c) 2012 Rodrigo Moraes. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are |
||||
met: |
||||
|
||||
* Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above |
||||
copyright notice, this list of conditions and the following disclaimer |
||||
in the documentation and/or other materials provided with the |
||||
distribution. |
||||
* Neither the name of Google Inc. nor the names of its |
||||
contributors may be used to endorse or promote products derived from |
||||
this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -1,173 +0,0 @@ |
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package json2 |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"errors" |
||||
"net/http" |
||||
"testing" |
||||
|
||||
"github.com/gorilla/rpc/v2" |
||||
) |
||||
|
||||
// ResponseRecorder is an implementation of http.ResponseWriter that
|
||||
// records its mutations for later inspection in tests.
|
||||
type ResponseRecorder struct { |
||||
Code int // the HTTP response code from WriteHeader
|
||||
HeaderMap http.Header // the HTTP response headers
|
||||
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
|
||||
Flushed bool |
||||
} |
||||
|
||||
// NewRecorder returns an initialized ResponseRecorder.
|
||||
func NewRecorder() *ResponseRecorder { |
||||
return &ResponseRecorder{ |
||||
HeaderMap: make(http.Header), |
||||
Body: new(bytes.Buffer), |
||||
} |
||||
} |
||||
|
||||
// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
|
||||
// an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
|
||||
const DefaultRemoteAddr = "1.2.3.4" |
||||
|
||||
// Header returns the response headers.
|
||||
func (rw *ResponseRecorder) Header() http.Header { |
||||
return rw.HeaderMap |
||||
} |
||||
|
||||
// Write always succeeds and writes to rw.Body, if not nil.
|
||||
func (rw *ResponseRecorder) Write(buf []byte) (int, error) { |
||||
if rw.Body != nil { |
||||
rw.Body.Write(buf) |
||||
} |
||||
if rw.Code == 0 { |
||||
rw.Code = http.StatusOK |
||||
} |
||||
return len(buf), nil |
||||
} |
||||
|
||||
// WriteHeader sets rw.Code.
|
||||
func (rw *ResponseRecorder) WriteHeader(code int) { |
||||
rw.Code = code |
||||
} |
||||
|
||||
// Flush sets rw.Flushed to true.
|
||||
func (rw *ResponseRecorder) Flush() { |
||||
rw.Flushed = true |
||||
} |
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var ErrResponseError = errors.New("response error") |
||||
|
||||
type Service1Request struct { |
||||
A int |
||||
B int |
||||
} |
||||
|
||||
type Service1NoParamsRequest struct { |
||||
V string `json:"jsonrpc"` |
||||
M string `json:"method"` |
||||
ID uint64 `json:"id"` |
||||
} |
||||
|
||||
type Service1Response struct { |
||||
Result int |
||||
} |
||||
|
||||
type Service1 struct { |
||||
} |
||||
|
||||
const Service1DefaultResponse = 9999 |
||||
|
||||
func (t *Service1) Multiply(r *http.Request, req *Service1Request, res *Service1Response) error { |
||||
if req.A == 0 && req.B == 0 { |
||||
// Sentinel value for test with no params.
|
||||
res.Result = Service1DefaultResponse |
||||
} else { |
||||
res.Result = req.A * req.B |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (t *Service1) ResponseError(r *http.Request, req *Service1Request, res *Service1Response) error { |
||||
return ErrResponseError |
||||
} |
||||
|
||||
func execute(t *testing.T, s *rpc.Server, method string, req, res interface{}) error { |
||||
if !s.HasMethod(method) { |
||||
t.Fatal("Expected to be registered:", method) |
||||
} |
||||
|
||||
buf, _ := EncodeClientRequest(method, req) |
||||
body := bytes.NewBuffer(buf) |
||||
r, _ := http.NewRequest("POST", "http://localhost:8080/", body) |
||||
r.Header.Set("Content-Type", "application/json") |
||||
|
||||
w := NewRecorder() |
||||
s.ServeHTTP(w, r) |
||||
|
||||
return DecodeClientResponse(w.Body, res) |
||||
} |
||||
|
||||
func executeRaw(t *testing.T, s *rpc.Server, req interface{}, res interface{}) error { |
||||
j, _ := json.Marshal(req) |
||||
r, _ := http.NewRequest("POST", "http://localhost:8080/", bytes.NewBuffer(j)) |
||||
r.Header.Set("Content-Type", "application/json") |
||||
|
||||
w := NewRecorder() |
||||
s.ServeHTTP(w, r) |
||||
|
||||
return DecodeClientResponse(w.Body, res) |
||||
} |
||||
|
||||
func TestService(t *testing.T) { |
||||
s := rpc.NewServer() |
||||
s.RegisterCodec(NewCodec(), "application/json") |
||||
s.RegisterService(new(Service1), "") |
||||
|
||||
var res Service1Response |
||||
if err := execute(t, s, "Service1.Multiply", &Service1Request{4, 2}, &res); err != nil { |
||||
t.Error("Expected err to be nil, but got:", err) |
||||
} |
||||
if res.Result != 8 { |
||||
t.Errorf("Wrong response: %v.", res.Result) |
||||
} |
||||
|
||||
if err := execute(t, s, "Service1.ResponseError", &Service1Request{4, 2}, &res); err == nil { |
||||
t.Errorf("Expected to get %q, but got nil", ErrResponseError) |
||||
} else if err.Error() != ErrResponseError.Error() { |
||||
t.Errorf("Expected to get %q, but got %q", ErrResponseError, err) |
||||
} |
||||
|
||||
// No parameters.
|
||||
res = Service1Response{} |
||||
if err := executeRaw(t, s, &Service1NoParamsRequest{"2.0", "Service1.Multiply", 1}, &res); err != nil { |
||||
t.Error(err) |
||||
} |
||||
if res.Result != Service1DefaultResponse { |
||||
t.Errorf("Wrong response: got %v, want %v", res.Result, Service1DefaultResponse) |
||||
} |
||||
} |
||||
|
||||
func TestDecodeNullResult(t *testing.T) { |
||||
data := `{"jsonrpc": "2.0", "id": 12345, "result": null}` |
||||
reader := bytes.NewReader([]byte(data)) |
||||
var result interface{} |
||||
|
||||
err := DecodeClientResponse(reader, &result) |
||||
|
||||
if err != ErrNullResult { |
||||
t.Error("Expected err no be ErrNullResult, but got:", err) |
||||
} |
||||
|
||||
if result != nil { |
||||
t.Error("Expected result to be nil, but got:", result) |
||||
} |
||||
} |
@ -1,54 +0,0 @@ |
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package rpc |
||||
|
||||
import ( |
||||
"net/http" |
||||
"testing" |
||||
) |
||||
|
||||
type Service1Request struct { |
||||
A int |
||||
B int |
||||
} |
||||
|
||||
type Service1Response struct { |
||||
Result int |
||||
} |
||||
|
||||
type Service1 struct { |
||||
} |
||||
|
||||
func (t *Service1) Multiply(r *http.Request, req *Service1Request, res *Service1Response) error { |
||||
res.Result = req.A * req.B |
||||
return nil |
||||
} |
||||
|
||||
type Service2 struct { |
||||
} |
||||
|
||||
func TestRegisterService(t *testing.T) { |
||||
var err error |
||||
s := NewServer() |
||||
service1 := new(Service1) |
||||
service2 := new(Service2) |
||||
|
||||
// Inferred name.
|
||||
err = s.RegisterService(service1, "") |
||||
if err != nil || !s.HasMethod("Service1.Multiply") { |
||||
t.Errorf("Expected to be registered: Service1.Multiply") |
||||
} |
||||
// Provided name.
|
||||
err = s.RegisterService(service1, "Foo") |
||||
if err != nil || !s.HasMethod("Foo.Multiply") { |
||||
t.Errorf("Expected to be registered: Foo.Multiply") |
||||
} |
||||
// No methods.
|
||||
err = s.RegisterService(service2, "") |
||||
if err == nil { |
||||
t.Errorf("Expected error on service2") |
||||
} |
||||
} |
@ -0,0 +1,57 @@ |
||||
package colorable |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"io" |
||||
) |
||||
|
||||
type NonColorable struct { |
||||
out io.Writer |
||||
lastbuf bytes.Buffer |
||||
} |
||||
|
||||
func NewNonColorable(w io.Writer) io.Writer { |
||||
return &NonColorable{out: w} |
||||
} |
||||
|
||||
func (w *NonColorable) Write(data []byte) (n int, err error) { |
||||
er := bytes.NewBuffer(data) |
||||
loop: |
||||
for { |
||||
c1, _, err := er.ReadRune() |
||||
if err != nil { |
||||
break loop |
||||
} |
||||
if c1 != 0x1b { |
||||
fmt.Fprint(w.out, string(c1)) |
||||
continue |
||||
} |
||||
c2, _, err := er.ReadRune() |
||||
if err != nil { |
||||
w.lastbuf.WriteRune(c1) |
||||
break loop |
||||
} |
||||
if c2 != 0x5b { |
||||
w.lastbuf.WriteRune(c1) |
||||
w.lastbuf.WriteRune(c2) |
||||
continue |
||||
} |
||||
|
||||
var buf bytes.Buffer |
||||
for { |
||||
c, _, err := er.ReadRune() |
||||
if err != nil { |
||||
w.lastbuf.WriteRune(c1) |
||||
w.lastbuf.WriteRune(c2) |
||||
w.lastbuf.Write(buf.Bytes()) |
||||
break loop |
||||
} |
||||
if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { |
||||
break |
||||
} |
||||
buf.Write([]byte(string(c))) |
||||
} |
||||
} |
||||
return len(data) - w.lastbuf.Len(), nil |
||||
} |
@ -0,0 +1,16 @@ |
||||
// +build solaris
|
||||
// +build !appengine
|
||||
|
||||
package isatty |
||||
|
||||
import ( |
||||
"golang.org/x/sys/unix" |
||||
) |
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c
|
||||
func IsTerminal(fd uintptr) bool { |
||||
var termio unix.Termio |
||||
err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio) |
||||
return err == nil |
||||
} |
@ -0,0 +1,19 @@ |
||||
Copyright (c) 2015 Olivier Poitrey <rs@dailymotion.com> |
||||
|
||||
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. |
@ -0,0 +1,134 @@ |
||||
# XHandler |
||||
|
||||
[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/xhandler) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/xhandler/master/LICENSE) [![Build Status](https://travis-ci.org/rs/xhandler.svg?branch=master)](https://travis-ci.org/rs/xhandler) [![Coverage](http://gocover.io/_badge/github.com/rs/xhandler)](http://gocover.io/github.com/rs/xhandler) |
||||
|
||||
XHandler is a bridge between [net/context](https://godoc.org/golang.org/x/net/context) and `http.Handler`. |
||||
|
||||
It lets you enforce `net/context` in your handlers without sacrificing compatibility with existing `http.Handlers` nor imposing a specific router. |
||||
|
||||
Thanks to `net/context` deadline management, `xhandler` is able to enforce a per request deadline and will cancel the context when the client closes the connection unexpectedly. |
||||
|
||||
You may create your own `net/context` aware handler pretty much the same way as you would do with http.Handler. |
||||
|
||||
Read more about xhandler on [Dailymotion engineering blog](http://engineering.dailymotion.com/our-way-to-go/). |
||||
|
||||
## Installing |
||||
|
||||
go get -u github.com/rs/xhandler |
||||
|
||||
## Usage |
||||
|
||||
```go |
||||
package main |
||||
|
||||
import ( |
||||
"log" |
||||
"net/http" |
||||
"time" |
||||
|
||||
"github.com/rs/cors" |
||||
"github.com/rs/xhandler" |
||||
"golang.org/x/net/context" |
||||
) |
||||
|
||||
type myMiddleware struct { |
||||
next xhandler.HandlerC |
||||
} |
||||
|
||||
func (h myMiddleware) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) { |
||||
ctx = context.WithValue(ctx, "test", "World") |
||||
h.next.ServeHTTPC(ctx, w, r) |
||||
} |
||||
|
||||
func main() { |
||||
c := xhandler.Chain{} |
||||
|
||||
// Add close notifier handler so context is cancelled when the client closes |
||||
// the connection |
||||
c.UseC(xhandler.CloseHandler) |
||||
|
||||
// Add timeout handler |
||||
c.UseC(xhandler.TimeoutHandler(2 * time.Second)) |
||||
|
||||
// Middleware putting something in the context |
||||
c.UseC(func(next xhandler.HandlerC) xhandler.HandlerC { |
||||
return myMiddleware{next: next} |
||||
}) |
||||
|
||||
// Mix it with a non-context-aware middleware handler |
||||
c.Use(cors.Default().Handler) |
||||
|
||||
// Final handler (using handlerFuncC), reading from the context |
||||
xh := xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { |
||||
value := ctx.Value("test").(string) |
||||
w.Write([]byte("Hello " + value)) |
||||
}) |
||||
|
||||
// Bridge context aware handlers with http.Handler using xhandler.Handle() |
||||
http.Handle("/test", c.Handler(xh)) |
||||
|
||||
if err := http.ListenAndServe(":8080", nil); err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
} |
||||
``` |
||||
|
||||
### Using xmux |
||||
|
||||
Xhandler comes with an optional context aware [muxer](https://github.com/rs/xmux) forked from [httprouter](https://github.com/julienschmidt/httprouter): |
||||
|
||||
```go |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
"net/http" |
||||
"time" |
||||
|
||||
"github.com/rs/xhandler" |
||||
"github.com/rs/xmux" |
||||
"golang.org/x/net/context" |
||||
) |
||||
|
||||
func main() { |
||||
c := xhandler.Chain{} |
||||
|
||||
// Append a context-aware middleware handler |
||||
c.UseC(xhandler.CloseHandler) |
||||
|
||||
// Another context-aware middleware handler |
||||
c.UseC(xhandler.TimeoutHandler(2 * time.Second)) |
||||
|
||||
mux := xmux.New() |
||||
|
||||
// Use c.Handler to terminate the chain with your final handler |
||||
mux.GET("/welcome/:name", xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, req *http.Request) { |
||||
fmt.Fprintf(w, "Welcome %s!", xmux.Params(ctx).Get("name")) |
||||
})) |
||||
|
||||
if err := http.ListenAndServe(":8080", c.Handler(mux)); err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
} |
||||
``` |
||||
|
||||
See [xmux](https://github.com/rs/xmux) for more examples. |
||||
|
||||
## Context Aware Middleware |
||||
|
||||
Here is a list of `net/context` aware middleware handlers implementing `xhandler.HandlerC` interface. |
||||
|
||||
Feel free to put up a PR linking your middleware if you have built one: |
||||
|
||||
| Middleware | Author | Description | |
||||
| ---------- | ------ | ----------- | |
||||
| [xmux](https://github.com/rs/xmux) | [Olivier Poitrey](https://github.com/rs) | HTTP request muxer | |
||||
| [xlog](https://github.com/rs/xlog) | [Olivier Poitrey](https://github.com/rs) | HTTP handler logger | |
||||
| [xstats](https://github.com/rs/xstats) | [Olivier Poitrey](https://github.com/rs) | A generic client for service instrumentation | |
||||
| [xaccess](https://github.com/rs/xaccess) | [Olivier Poitrey](https://github.com/rs) | HTTP handler access logger with [xlog](https://github.com/rs/xlog) and [xstats](https://github.com/rs/xstats) | |
||||
| [cors](https://github.com/rs/cors) | [Olivier Poitrey](https://github.com/rs) | [Cross Origin Resource Sharing](http://www.w3.org/TR/cors/) (CORS) support | |
||||
|
||||
## Licenses |
||||
|
||||
All source code is licensed under the [MIT License](https://raw.github.com/rs/xhandler/master/LICENSE). |
@ -0,0 +1,121 @@ |
||||
package xhandler |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"golang.org/x/net/context" |
||||
) |
||||
|
||||
// Chain is a helper for chaining middleware handlers together for easier
|
||||
// management.
|
||||
type Chain []func(next HandlerC) HandlerC |
||||
|
||||
// Add appends a variable number of additional middleware handlers
|
||||
// to the middleware chain. Middleware handlers can either be
|
||||
// context-aware or non-context aware handlers with the appropriate
|
||||
// function signatures.
|
||||
func (c *Chain) Add(f ...interface{}) { |
||||
for _, h := range f { |
||||
switch v := h.(type) { |
||||
case func(http.Handler) http.Handler: |
||||
c.Use(v) |
||||
case func(HandlerC) HandlerC: |
||||
c.UseC(v) |
||||
default: |
||||
panic("Adding invalid handler to the middleware chain") |
||||
} |
||||
} |
||||
} |
||||
|
||||
// With creates a new middleware chain from an existing chain,
|
||||
// extending it with additional middleware. Middleware handlers
|
||||
// can either be context-aware or non-context aware handlers
|
||||
// with the appropriate function signatures.
|
||||
func (c *Chain) With(f ...interface{}) *Chain { |
||||
n := make(Chain, len(*c)) |
||||
copy(n, *c) |
||||
n.Add(f...) |
||||
return &n |
||||
} |
||||
|
||||
// UseC appends a context-aware handler to the middleware chain.
|
||||
func (c *Chain) UseC(f func(next HandlerC) HandlerC) { |
||||
*c = append(*c, f) |
||||
} |
||||
|
||||
// Use appends a standard http.Handler to the middleware chain without
|
||||
// losing track of the context when inserted between two context aware handlers.
|
||||
//
|
||||
// Caveat: the f function will be called on each request so you are better off putting
|
||||
// any initialization sequence outside of this function.
|
||||
func (c *Chain) Use(f func(next http.Handler) http.Handler) { |
||||
xf := func(next HandlerC) HandlerC { |
||||
return HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { |
||||
n := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
||||
next.ServeHTTPC(ctx, w, r) |
||||
}) |
||||
f(n).ServeHTTP(w, r) |
||||
}) |
||||
} |
||||
*c = append(*c, xf) |
||||
} |
||||
|
||||
// Handler wraps the provided final handler with all the middleware appended to
|
||||
// the chain and returns a new standard http.Handler instance.
|
||||
// The context.Background() context is injected automatically.
|
||||
func (c Chain) Handler(xh HandlerC) http.Handler { |
||||
ctx := context.Background() |
||||
return c.HandlerCtx(ctx, xh) |
||||
} |
||||
|
||||
// HandlerFC is a helper to provide a function (HandlerFuncC) to Handler().
|
||||
//
|
||||
// HandlerFC is equivalent to:
|
||||
// c.Handler(xhandler.HandlerFuncC(xhc))
|
||||
func (c Chain) HandlerFC(xhf HandlerFuncC) http.Handler { |
||||
ctx := context.Background() |
||||
return c.HandlerCtx(ctx, HandlerFuncC(xhf)) |
||||
} |
||||
|
||||
// HandlerH is a helper to provide a standard http handler (http.HandlerFunc)
|
||||
// to Handler(). Your final handler won't have access to the context though.
|
||||
func (c Chain) HandlerH(h http.Handler) http.Handler { |
||||
ctx := context.Background() |
||||
return c.HandlerCtx(ctx, HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { |
||||
h.ServeHTTP(w, r) |
||||
})) |
||||
} |
||||
|
||||
// HandlerF is a helper to provide a standard http handler function
|
||||
// (http.HandlerFunc) to Handler(). Your final handler won't have access
|
||||
// to the context though.
|
||||
func (c Chain) HandlerF(hf http.HandlerFunc) http.Handler { |
||||
ctx := context.Background() |
||||
return c.HandlerCtx(ctx, HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { |
||||
hf(w, r) |
||||
})) |
||||
} |
||||
|
||||
// HandlerCtx wraps the provided final handler with all the middleware appended to
|
||||
// the chain and returns a new standard http.Handler instance.
|
||||
func (c Chain) HandlerCtx(ctx context.Context, xh HandlerC) http.Handler { |
||||
return New(ctx, c.HandlerC(xh)) |
||||
} |
||||
|
||||
// HandlerC wraps the provided final handler with all the middleware appended to
|
||||
// the chain and returns a HandlerC instance.
|
||||
func (c Chain) HandlerC(xh HandlerC) HandlerC { |
||||
for i := len(c) - 1; i >= 0; i-- { |
||||
xh = c[i](xh) |
||||
} |
||||
return xh |
||||
} |
||||
|
||||
// HandlerCF wraps the provided final handler func with all the middleware appended to
|
||||
// the chain and returns a HandlerC instance.
|
||||
//
|
||||
// HandlerCF is equivalent to:
|
||||
// c.HandlerC(xhandler.HandlerFuncC(xhc))
|
||||
func (c Chain) HandlerCF(xhc HandlerFuncC) HandlerC { |
||||
return c.HandlerC(HandlerFuncC(xhc)) |
||||
} |
@ -0,0 +1,59 @@ |
||||
package xhandler |
||||
|
||||
import ( |
||||
"net/http" |
||||
"time" |
||||
|
||||
"golang.org/x/net/context" |
||||
) |
||||
|
||||
// CloseHandler returns a Handler, cancelling the context when the client
|
||||
// connection closes unexpectedly.
|
||||
func CloseHandler(next HandlerC) HandlerC { |
||||
return HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { |
||||
// Cancel the context if the client closes the connection
|
||||
if wcn, ok := w.(http.CloseNotifier); ok { |
||||
var cancel context.CancelFunc |
||||
ctx, cancel = context.WithCancel(ctx) |
||||
defer cancel() |
||||
|
||||
notify := wcn.CloseNotify() |
||||
go func() { |
||||
select { |
||||
case <-notify: |
||||
cancel() |
||||
case <-ctx.Done(): |
||||
} |
||||
}() |
||||
} |
||||
|
||||
next.ServeHTTPC(ctx, w, r) |
||||
}) |
||||
} |
||||
|
||||
// TimeoutHandler returns a Handler which adds a timeout to the context.
|
||||
//
|
||||
// Child handlers have the responsability of obeying the context deadline and to return
|
||||
// an appropriate error (or not) response in case of timeout.
|
||||
func TimeoutHandler(timeout time.Duration) func(next HandlerC) HandlerC { |
||||
return func(next HandlerC) HandlerC { |
||||
return HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { |
||||
ctx, _ = context.WithTimeout(ctx, timeout) |
||||
next.ServeHTTPC(ctx, w, r) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
// If is a special handler that will skip insert the condNext handler only if a condition
|
||||
// applies at runtime.
|
||||
func If(cond func(ctx context.Context, w http.ResponseWriter, r *http.Request) bool, condNext func(next HandlerC) HandlerC) func(next HandlerC) HandlerC { |
||||
return func(next HandlerC) HandlerC { |
||||
return HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { |
||||
if cond(ctx, w, r) { |
||||
condNext(next).ServeHTTPC(ctx, w, r) |
||||
} else { |
||||
next.ServeHTTPC(ctx, w, r) |
||||
} |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,42 @@ |
||||
// Package xhandler provides a bridge between http.Handler and net/context.
|
||||
//
|
||||
// xhandler enforces net/context in your handlers without sacrificing
|
||||
// compatibility with existing http.Handlers nor imposing a specific router.
|
||||
//
|
||||
// Thanks to net/context deadline management, xhandler is able to enforce
|
||||
// a per request deadline and will cancel the context in when the client close
|
||||
// the connection unexpectedly.
|
||||
//
|
||||
// You may create net/context aware middlewares pretty much the same way as
|
||||
// you would with http.Handler.
|
||||
package xhandler // import "github.com/rs/xhandler"
|
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"golang.org/x/net/context" |
||||
) |
||||
|
||||
// HandlerC is a net/context aware http.Handler
|
||||
type HandlerC interface { |
||||
ServeHTTPC(context.Context, http.ResponseWriter, *http.Request) |
||||
} |
||||
|
||||
// HandlerFuncC type is an adapter to allow the use of ordinary functions
|
||||
// as an xhandler.Handler. If f is a function with the appropriate signature,
|
||||
// xhandler.HandlerFuncC(f) is a xhandler.Handler object that calls f.
|
||||
type HandlerFuncC func(context.Context, http.ResponseWriter, *http.Request) |
||||
|
||||
// ServeHTTPC calls f(ctx, w, r).
|
||||
func (f HandlerFuncC) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) { |
||||
f(ctx, w, r) |
||||
} |
||||
|
||||
// New creates a conventional http.Handler injecting the provided root
|
||||
// context to sub handlers. This handler is used as a bridge between conventional
|
||||
// http.Handler and context aware handlers.
|
||||
func New(ctx context.Context, h HandlerC) http.Handler { |
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
||||
h.ServeHTTPC(ctx, w, r) |
||||
}) |
||||
} |
@ -0,0 +1,27 @@ |
||||
Copyright (c) 2009 The Go Authors. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are |
||||
met: |
||||
|
||||
* Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above |
||||
copyright notice, this list of conditions and the following disclaimer |
||||
in the documentation and/or other materials provided with the |
||||
distribution. |
||||
* Neither the name of Google Inc. nor the names of its |
||||
contributors may be used to endorse or promote products derived from |
||||
this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,22 @@ |
||||
Additional IP Rights Grant (Patents) |
||||
|
||||
"This implementation" means the copyrightable works distributed by |
||||
Google as part of the Go project. |
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive, |
||||
no-charge, royalty-free, irrevocable (except as stated in this section) |
||||
patent license to make, have made, use, offer to sell, sell, import, |
||||
transfer and otherwise run, modify and propagate the contents of this |
||||
implementation of Go, where such license applies only to those patent |
||||
claims, both currently owned or controlled by Google and acquired in |
||||
the future, licensable by Google that are necessarily infringed by this |
||||
implementation of Go. This grant does not include claims that would be |
||||
infringed only as a consequence of further modification of this |
||||
implementation. If you or your agent or exclusive licensee institute or |
||||
order or agree to the institution of patent litigation against any |
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging |
||||
that this implementation of Go or any code incorporated within this |
||||
implementation of Go constitutes direct or contributory patent |
||||
infringement, or inducement of patent infringement, then any patent |
||||
rights granted to you under this License for this implementation of Go |
||||
shall terminate as of the date such litigation is filed. |
@ -0,0 +1,156 @@ |
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package context defines the Context type, which carries deadlines,
|
||||
// cancelation signals, and other request-scoped values across API boundaries
|
||||
// and between processes.
|
||||
//
|
||||
// Incoming requests to a server should create a Context, and outgoing calls to
|
||||
// servers should accept a Context. The chain of function calls between must
|
||||
// propagate the Context, optionally replacing it with a modified copy created
|
||||
// using WithDeadline, WithTimeout, WithCancel, or WithValue.
|
||||
//
|
||||
// Programs that use Contexts should follow these rules to keep interfaces
|
||||
// consistent across packages and enable static analysis tools to check context
|
||||
// propagation:
|
||||
//
|
||||
// Do not store Contexts inside a struct type; instead, pass a Context
|
||||
// explicitly to each function that needs it. The Context should be the first
|
||||
// parameter, typically named ctx:
|
||||
//
|
||||
// func DoSomething(ctx context.Context, arg Arg) error {
|
||||
// // ... use ctx ...
|
||||
// }
|
||||
//
|
||||
// Do not pass a nil Context, even if a function permits it. Pass context.TODO
|
||||
// if you are unsure about which Context to use.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
//
|
||||
// The same Context may be passed to functions running in different goroutines;
|
||||
// Contexts are safe for simultaneous use by multiple goroutines.
|
||||
//
|
||||
// See http://blog.golang.org/context for example code for a server that uses
|
||||
// Contexts.
|
||||
package context // import "golang.org/x/net/context"
|
||||
|
||||
import "time" |
||||
|
||||
// A Context carries a deadline, a cancelation signal, and other values across
|
||||
// API boundaries.
|
||||
//
|
||||
// Context's methods may be called by multiple goroutines simultaneously.
|
||||
type Context interface { |
||||
// Deadline returns the time when work done on behalf of this context
|
||||
// should be canceled. Deadline returns ok==false when no deadline is
|
||||
// set. Successive calls to Deadline return the same results.
|
||||
Deadline() (deadline time.Time, ok bool) |
||||
|
||||
// Done returns a channel that's closed when work done on behalf of this
|
||||
// context should be canceled. Done may return nil if this context can
|
||||
// never be canceled. Successive calls to Done return the same value.
|
||||
//
|
||||
// WithCancel arranges for Done to be closed when cancel is called;
|
||||
// WithDeadline arranges for Done to be closed when the deadline
|
||||
// expires; WithTimeout arranges for Done to be closed when the timeout
|
||||
// elapses.
|
||||
//
|
||||
// Done is provided for use in select statements:
|
||||
//
|
||||
// // Stream generates values with DoSomething and sends them to out
|
||||
// // until DoSomething returns an error or ctx.Done is closed.
|
||||
// func Stream(ctx context.Context, out chan<- Value) error {
|
||||
// for {
|
||||
// v, err := DoSomething(ctx)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// select {
|
||||
// case <-ctx.Done():
|
||||
// return ctx.Err()
|
||||
// case out <- v:
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// See http://blog.golang.org/pipelines for more examples of how to use
|
||||
// a Done channel for cancelation.
|
||||
Done() <-chan struct{} |
||||
|
||||
// Err returns a non-nil error value after Done is closed. Err returns
|
||||
// Canceled if the context was canceled or DeadlineExceeded if the
|
||||
// context's deadline passed. No other values for Err are defined.
|
||||
// After Done is closed, successive calls to Err return the same value.
|
||||
Err() error |
||||
|
||||
// Value returns the value associated with this context for key, or nil
|
||||
// if no value is associated with key. Successive calls to Value with
|
||||
// the same key returns the same result.
|
||||
//
|
||||
// Use context values only for request-scoped data that transits
|
||||
// processes and API boundaries, not for passing optional parameters to
|
||||
// functions.
|
||||
//
|
||||
// A key identifies a specific value in a Context. Functions that wish
|
||||
// to store values in Context typically allocate a key in a global
|
||||
// variable then use that key as the argument to context.WithValue and
|
||||
// Context.Value. A key can be any type that supports equality;
|
||||
// packages should define keys as an unexported type to avoid
|
||||
// collisions.
|
||||
//
|
||||
// Packages that define a Context key should provide type-safe accessors
|
||||
// for the values stores using that key:
|
||||
//
|
||||
// // Package user defines a User type that's stored in Contexts.
|
||||
// package user
|
||||
//
|
||||
// import "golang.org/x/net/context"
|
||||
//
|
||||
// // User is the type of value stored in the Contexts.
|
||||
// type User struct {...}
|
||||
//
|
||||
// // key is an unexported type for keys defined in this package.
|
||||
// // This prevents collisions with keys defined in other packages.
|
||||
// type key int
|
||||
//
|
||||
// // userKey is the key for user.User values in Contexts. It is
|
||||
// // unexported; clients use user.NewContext and user.FromContext
|
||||
// // instead of using this key directly.
|
||||
// var userKey key = 0
|
||||
//
|
||||
// // NewContext returns a new Context that carries value u.
|
||||
// func NewContext(ctx context.Context, u *User) context.Context {
|
||||
// return context.WithValue(ctx, userKey, u)
|
||||
// }
|
||||
//
|
||||
// // FromContext returns the User value stored in ctx, if any.
|
||||
// func FromContext(ctx context.Context) (*User, bool) {
|
||||
// u, ok := ctx.Value(userKey).(*User)
|
||||
// return u, ok
|
||||
// }
|
||||
Value(key interface{}) interface{} |
||||
} |
||||
|
||||
// Background returns a non-nil, empty Context. It is never canceled, has no
|
||||
// values, and has no deadline. It is typically used by the main function,
|
||||
// initialization, and tests, and as the top-level Context for incoming
|
||||
// requests.
|
||||
func Background() Context { |
||||
return background |
||||
} |
||||
|
||||
// TODO returns a non-nil, empty Context. Code should use context.TODO when
|
||||
// it's unclear which Context to use or it is not yet available (because the
|
||||
// surrounding function has not yet been extended to accept a Context
|
||||
// parameter). TODO is recognized by static analysis tools that determine
|
||||
// whether Contexts are propagated correctly in a program.
|
||||
func TODO() Context { |
||||
return todo |
||||
} |
||||
|
||||
// A CancelFunc tells an operation to abandon its work.
|
||||
// A CancelFunc does not wait for the work to stop.
|
||||
// After the first call, subsequent calls to a CancelFunc do nothing.
|
||||
type CancelFunc func() |
@ -0,0 +1,72 @@ |
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package context |
||||
|
||||
import ( |
||||
"context" // standard library's context, as of Go 1.7
|
||||
"time" |
||||
) |
||||
|
||||
var ( |
||||
todo = context.TODO() |
||||
background = context.Background() |
||||
) |
||||
|
||||
// Canceled is the error returned by Context.Err when the context is canceled.
|
||||
var Canceled = context.Canceled |
||||
|
||||
// DeadlineExceeded is the error returned by Context.Err when the context's
|
||||
// deadline passes.
|
||||
var DeadlineExceeded = context.DeadlineExceeded |
||||
|
||||
// WithCancel returns a copy of parent with a new Done channel. The returned
|
||||
// context's Done channel is closed when the returned cancel function is called
|
||||
// or when the parent context's Done channel is closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { |
||||
ctx, f := context.WithCancel(parent) |
||||
return ctx, CancelFunc(f) |
||||
} |
||||
|
||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
||||
// to be no later than d. If the parent's deadline is already earlier than d,
|
||||
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
||||
// context's Done channel is closed when the deadline expires, when the returned
|
||||
// cancel function is called, or when the parent context's Done channel is
|
||||
// closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { |
||||
ctx, f := context.WithDeadline(parent, deadline) |
||||
return ctx, CancelFunc(f) |
||||
} |
||||
|
||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete:
|
||||
//
|
||||
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
||||
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||
// return slowOperation(ctx)
|
||||
// }
|
||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { |
||||
return WithDeadline(parent, time.Now().Add(timeout)) |
||||
} |
||||
|
||||
// WithValue returns a copy of parent in which the value associated with key is
|
||||
// val.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
func WithValue(parent Context, key interface{}, val interface{}) Context { |
||||
return context.WithValue(parent, key, val) |
||||
} |
@ -0,0 +1,300 @@ |
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.7
|
||||
|
||||
package context |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
|
||||
// struct{}, since vars of this type must have distinct addresses.
|
||||
type emptyCtx int |
||||
|
||||
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { |
||||
return |
||||
} |
||||
|
||||
func (*emptyCtx) Done() <-chan struct{} { |
||||
return nil |
||||
} |
||||
|
||||
func (*emptyCtx) Err() error { |
||||
return nil |
||||
} |
||||
|
||||
func (*emptyCtx) Value(key interface{}) interface{} { |
||||
return nil |
||||
} |
||||
|
||||
func (e *emptyCtx) String() string { |
||||
switch e { |
||||
case background: |
||||
return "context.Background" |
||||
case todo: |
||||
return "context.TODO" |
||||
} |
||||
return "unknown empty Context" |
||||
} |
||||
|
||||
var ( |
||||
background = new(emptyCtx) |
||||
todo = new(emptyCtx) |
||||
) |
||||
|
||||
// Canceled is the error returned by Context.Err when the context is canceled.
|
||||
var Canceled = errors.New("context canceled") |
||||
|
||||
// DeadlineExceeded is the error returned by Context.Err when the context's
|
||||
// deadline passes.
|
||||
var DeadlineExceeded = errors.New("context deadline exceeded") |
||||
|
||||
// WithCancel returns a copy of parent with a new Done channel. The returned
|
||||
// context's Done channel is closed when the returned cancel function is called
|
||||
// or when the parent context's Done channel is closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { |
||||
c := newCancelCtx(parent) |
||||
propagateCancel(parent, c) |
||||
return c, func() { c.cancel(true, Canceled) } |
||||
} |
||||
|
||||
// newCancelCtx returns an initialized cancelCtx.
|
||||
func newCancelCtx(parent Context) *cancelCtx { |
||||
return &cancelCtx{ |
||||
Context: parent, |
||||
done: make(chan struct{}), |
||||
} |
||||
} |
||||
|
||||
// propagateCancel arranges for child to be canceled when parent is.
|
||||
func propagateCancel(parent Context, child canceler) { |
||||
if parent.Done() == nil { |
||||
return // parent is never canceled
|
||||
} |
||||
if p, ok := parentCancelCtx(parent); ok { |
||||
p.mu.Lock() |
||||
if p.err != nil { |
||||
// parent has already been canceled
|
||||
child.cancel(false, p.err) |
||||
} else { |
||||
if p.children == nil { |
||||
p.children = make(map[canceler]bool) |
||||
} |
||||
p.children[child] = true |
||||
} |
||||
p.mu.Unlock() |
||||
} else { |
||||
go func() { |
||||
select { |
||||
case <-parent.Done(): |
||||
child.cancel(false, parent.Err()) |
||||
case <-child.Done(): |
||||
} |
||||
}() |
||||
} |
||||
} |
||||
|
||||
// parentCancelCtx follows a chain of parent references until it finds a
|
||||
// *cancelCtx. This function understands how each of the concrete types in this
|
||||
// package represents its parent.
|
||||
func parentCancelCtx(parent Context) (*cancelCtx, bool) { |
||||
for { |
||||
switch c := parent.(type) { |
||||
case *cancelCtx: |
||||
return c, true |
||||
case *timerCtx: |
||||
return c.cancelCtx, true |
||||
case *valueCtx: |
||||
parent = c.Context |
||||
default: |
||||
return nil, false |
||||
} |
||||
} |
||||
} |
||||
|
||||
// removeChild removes a context from its parent.
|
||||
func removeChild(parent Context, child canceler) { |
||||
p, ok := parentCancelCtx(parent) |
||||
if !ok { |
||||
return |
||||
} |
||||
p.mu.Lock() |
||||
if p.children != nil { |
||||
delete(p.children, child) |
||||
} |
||||
p.mu.Unlock() |
||||
} |
||||
|
||||
// A canceler is a context type that can be canceled directly. The
|
||||
// implementations are *cancelCtx and *timerCtx.
|
||||
type canceler interface { |
||||
cancel(removeFromParent bool, err error) |
||||
Done() <-chan struct{} |
||||
} |
||||
|
||||
// A cancelCtx can be canceled. When canceled, it also cancels any children
|
||||
// that implement canceler.
|
||||
type cancelCtx struct { |
||||
Context |
||||
|
||||
done chan struct{} // closed by the first cancel call.
|
||||
|
||||
mu sync.Mutex |
||||
children map[canceler]bool // set to nil by the first cancel call
|
||||
err error // set to non-nil by the first cancel call
|
||||
} |
||||
|
||||
func (c *cancelCtx) Done() <-chan struct{} { |
||||
return c.done |
||||
} |
||||
|
||||
func (c *cancelCtx) Err() error { |
||||
c.mu.Lock() |
||||
defer c.mu.Unlock() |
||||
return c.err |
||||
} |
||||
|
||||
func (c *cancelCtx) String() string { |
||||
return fmt.Sprintf("%v.WithCancel", c.Context) |
||||
} |
||||
|
||||
// cancel closes c.done, cancels each of c's children, and, if
|
||||
// removeFromParent is true, removes c from its parent's children.
|
||||
func (c *cancelCtx) cancel(removeFromParent bool, err error) { |
||||
if err == nil { |
||||
panic("context: internal error: missing cancel error") |
||||
} |
||||
c.mu.Lock() |
||||
if c.err != nil { |
||||
c.mu.Unlock() |
||||
return // already canceled
|
||||
} |
||||
c.err = err |
||||
close(c.done) |
||||
for child := range c.children { |
||||
// NOTE: acquiring the child's lock while holding parent's lock.
|
||||
child.cancel(false, err) |
||||
} |
||||
c.children = nil |
||||
c.mu.Unlock() |
||||
|
||||
if removeFromParent { |
||||
removeChild(c.Context, c) |
||||
} |
||||
} |
||||
|
||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
||||
// to be no later than d. If the parent's deadline is already earlier than d,
|
||||
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
||||
// context's Done channel is closed when the deadline expires, when the returned
|
||||
// cancel function is called, or when the parent context's Done channel is
|
||||
// closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { |
||||
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { |
||||
// The current deadline is already sooner than the new one.
|
||||
return WithCancel(parent) |
||||
} |
||||
c := &timerCtx{ |
||||
cancelCtx: newCancelCtx(parent), |
||||
deadline: deadline, |
||||
} |
||||
propagateCancel(parent, c) |
||||
d := deadline.Sub(time.Now()) |
||||
if d <= 0 { |
||||
c.cancel(true, DeadlineExceeded) // deadline has already passed
|
||||
return c, func() { c.cancel(true, Canceled) } |
||||
} |
||||
c.mu.Lock() |
||||
defer c.mu.Unlock() |
||||
if c.err == nil { |
||||
c.timer = time.AfterFunc(d, func() { |
||||
c.cancel(true, DeadlineExceeded) |
||||
}) |
||||
} |
||||
return c, func() { c.cancel(true, Canceled) } |
||||
} |
||||
|
||||
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
|
||||
// implement Done and Err. It implements cancel by stopping its timer then
|
||||
// delegating to cancelCtx.cancel.
|
||||
type timerCtx struct { |
||||
*cancelCtx |
||||
timer *time.Timer // Under cancelCtx.mu.
|
||||
|
||||
deadline time.Time |
||||
} |
||||
|
||||
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { |
||||
return c.deadline, true |
||||
} |
||||
|
||||
func (c *timerCtx) String() string { |
||||
return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now())) |
||||
} |
||||
|
||||
func (c *timerCtx) cancel(removeFromParent bool, err error) { |
||||
c.cancelCtx.cancel(false, err) |
||||
if removeFromParent { |
||||
// Remove this timerCtx from its parent cancelCtx's children.
|
||||
removeChild(c.cancelCtx.Context, c) |
||||
} |
||||
c.mu.Lock() |
||||
if c.timer != nil { |
||||
c.timer.Stop() |
||||
c.timer = nil |
||||
} |
||||
c.mu.Unlock() |
||||
} |
||||
|
||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete:
|
||||
//
|
||||
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
||||
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||
// return slowOperation(ctx)
|
||||
// }
|
||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { |
||||
return WithDeadline(parent, time.Now().Add(timeout)) |
||||
} |
||||
|
||||
// WithValue returns a copy of parent in which the value associated with key is
|
||||
// val.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
func WithValue(parent Context, key interface{}, val interface{}) Context { |
||||
return &valueCtx{parent, key, val} |
||||
} |
||||
|
||||
// A valueCtx carries a key-value pair. It implements Value for that key and
|
||||
// delegates all other calls to the embedded Context.
|
||||
type valueCtx struct { |
||||
Context |
||||
key, val interface{} |
||||
} |
||||
|
||||
func (c *valueCtx) String() string { |
||||
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) |
||||
} |
||||
|
||||
func (c *valueCtx) Value(key interface{}) interface{} { |
||||
if c.key == key { |
||||
return c.val |
||||
} |
||||
return c.Context.Value(key) |
||||
} |
Loading…
Reference in new issue