Removed vendor directory, now requires Go 1.13+

This commit is contained in:
Brendan C. Ward 2021-05-13 20:01:52 -07:00
parent a14c94fad7
commit eb93ab424e
No known key found for this signature in database
GPG Key ID: 37A8A7A2D61DFE13
623 changed files with 16 additions and 539094 deletions

View File

@ -48,6 +48,8 @@ Most of the updates are demonstrated in `main.go`.
### General changes
- Upgraded Docker containers to Go 1.16
- Now requires Go 1.13+
- Removed `vendor` directory
### Command-line interface

View File

@ -33,23 +33,16 @@ virtual machine without any issues.
## Supported Go versions
_Requires Go 1.10+._ Go 1.13 is recommended for full support.
_Requires Go 1.13+._
`mbtileserver` uses go modules and follows standard practices as of Go 1.13.
Due to varying degrees of go module support between Go versions 1.10 and 1.13, we provide a `vendor` directory with
dependencies for older versions. This is deprecated and will be removed in an upcoming version.
_Note:_
Go versions 1.11.0 - 1.11.3 are not supported, use Go 1.11.4. This is due to differences in how those versions handled go modules
([see](https://github.com/golang/go/issues/30446)).
## Installation
You can install this project with
```sh
go get github.com/consbio/mbtileserver
$ go get github.com/consbio/mbtileserver
```
This will create and install an executable called `mbtileserver`.
@ -125,7 +118,7 @@ You can also use environment variables instead of flags, which may be more helpf
Example:
```
$ PORT=7777 TILE_DIR=./path/to/your/tiles VERBOSE=true mbtileserver
$ PORT=7777 TILE_DIR=./path/to/your/tiles VERBOSE=true mbtileserver
```
In a docker-compose.yml file it will look like:
@ -149,7 +142,7 @@ mbtileserver optionally supports graceful reload (without interrupting any in-pr
must be enabled with the `--enable-reload-signal` flag. When enabled, the server can be reloaded by sending it a `HUP` signal:
```
$ kill -HUP <pid>
$ kill -HUP <pid>
```
Reloading the server will cause it to pick up changes to the tiles directory, adding new tilesets and removing any that
@ -209,20 +202,20 @@ server {
Pull the latest image from [Docker Hub](https://hub.docker.com/r/consbio/mbtileserver):
```
docker pull consbio/mbtileserver:latest
$ docker pull consbio/mbtileserver:latest
```
To build the Docker image locally (named `mbtileserver`):
```
docker build -t mbtileserver -f Dockerfile .
$ docker build -t mbtileserver -f Dockerfile .
```
To run the Docker container on port 8080 with your tilesets in `<host tile dir>`.
Note that by default, `mbtileserver` runs on port 8000 in the container.
```
docker run --rm -p 8080:8000 -v <host tile dir>:/tilesets consbio/mbtileserver
$ docker run --rm -p 8080:8000 -v <host tile dir>:/tilesets consbio/mbtileserver
```
You can pass in additional command-line arguments to `mbtileserver`, for example, to use
@ -230,13 +223,13 @@ certificates and files in `<host cert dir>` so that you can access the server vi
[`mkcert`](https://github.com/FiloSottile/mkcert). This example uses automatic redirects, which causes `mbtileserver` to also listen on port 80 and automatically redirect to 443.
```
docker run --rm -p 80:80 443:443 -v <host tile dir>:/tilesets -v <host cert dir>:/certs/ consbio/mbtileserver -c /certs/localhost.pem -k /certs/localhost-key.pem -p 443 --redirect
$ docker run --rm -p 80:80 443:443 -v <host tile dir>:/tilesets -v <host cert dir>:/certs/ consbio/mbtileserver -c /certs/localhost.pem -k /certs/localhost-key.pem -p 443 --redirect
```
Alternately, use `docker-compose` to run:
```
docker-compose up -d
$ docker-compose up -d
```
The default `docker-compose.yml` configures `mbtileserver` to connect to port 8080 on the host, and uses the `./mbtiles/testdata` folder for tilesets. You can use your own `docker-compose.override.yml` or [environment specific files](https://docs.docker.com/compose/extends/) to set these how you like.
@ -244,7 +237,7 @@ The default `docker-compose.yml` configures `mbtileserver` to connect to port 80
To reload the server:
```
docker exec -it mbtileserver sh -c "kill -HUP 1"
$ docker exec -it mbtileserver sh -c "kill -HUP 1"
```
## Specifications
@ -423,7 +416,7 @@ MinGW or [TDM-GCC](https://sourceforge.net/projects/tdm-gcc/) should work fine.
If you experience very slow builds each time, it may be that you need to first run
```
go build -a .
$ go build -a .
```
to make subsequent builds much faster.
@ -434,7 +427,7 @@ Development of the templates and static assets likely requires using
From the `handlers/templates/static` folder, run
```bash
$npm install
$ npm install
```
to pull in the static dependencies. These are referenced in the
@ -443,7 +436,7 @@ to pull in the static dependencies. These are referenced in the
Then to build the minified version, run:
```bash
$gulp build
$ gulp build
```
Modifying the `.go` files always requires re-running `go build .`.
@ -453,7 +446,7 @@ are embedded into the executable. For this to work, you must have
[github.com/shurcooL/vfsgen)[https://github.com/shurcooL/vfsgen) installed.
```bash
go generate ./handlers/templates.go
$ go generate ./handlers/templates.go
```
This will rewrite the `assets_vfsdata.go` which you must commit along with your

View File

@ -1,3 +0,0 @@
This Source Code Form is subject to the terms of the Mozilla Public License,
v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain
one at http://mozilla.org/MPL/2.0/.

View File

@ -1,60 +0,0 @@
# GoCertifi: SSL Certificates for Golang
This Go package contains a CA bundle that you can reference in your Go code.
This is useful for systems that do not have CA bundles that Golang can find
itself, or where a uniform set of CAs is valuable.
This is the same CA bundle that ships with the
[Python Requests](https://github.com/kennethreitz/requests) library, and is a
Golang specific port of [certifi](https://github.com/kennethreitz/certifi). The
CA bundle is derived from Mozilla's canonical set.
## Usage
You can use the `gocertifi` package as follows:
```go
import "github.com/certifi/gocertifi"
cert_pool, err := gocertifi.CACerts()
```
You can use the returned `*x509.CertPool` as part of an HTTP transport, for example:
```go
import (
"net/http"
"crypto/tls"
)
// Setup an HTTP client with a custom transport
transport := &http.Transport{
TLSClientConfig: &tls.Config{RootCAs: cert_pool},
}
client := &http.Client{Transport: transport}
// Make an HTTP request using our custom transport
resp, err := client.Get("https://example.com")
```
## Detailed Documentation
Import as follows:
```go
import "github.com/certifi/gocertifi"
```
### Errors
```go
var ErrParseFailed = errors.New("gocertifi: error when parsing certificates")
```
### Functions
```go
func CACerts() (*x509.CertPool, error)
```
CACerts builds an X.509 certificate pool containing the Mozilla CA Certificate
bundle. Returns nil on error along with an appropriate error code.

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +0,0 @@
.DS_Store
bin

View File

@ -1,13 +0,0 @@
language: go
script:
- go vet ./...
- go test -v ./...
go:
- 1.3
- 1.4
- 1.5
- 1.6
- 1.7
- tip

View File

@ -1,8 +0,0 @@
Copyright (c) 2012 Dave Grijalva
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.

View File

@ -1,97 +0,0 @@
## 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(req, request.OAuth2Extractor, keyLookupFunc); err == nil {
claims := token.Claims.(jwt.MapClaims)
fmt.Printf("Token for user %v expires %v", claims["user"], 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)
}
```

View File

@ -1,100 +0,0 @@
# jwt-go
[![Build Status](https://travis-ci.org/dgrijalva/jwt-go.svg?branch=master)](https://travis-ci.org/dgrijalva/jwt-go)
[![GoDoc](https://godoc.org/github.com/dgrijalva/jwt-go?status.svg)](https://godoc.org/github.com/dgrijalva/jwt-go)
A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html)
**NEW VERSION COMING:** There have been a lot of improvements suggested since the version 3.0.0 released in 2016. I'm working now on cutting two different releases: 3.2.0 will contain any non-breaking changes or enhancements. 4.0.0 will follow shortly which will include breaking changes. See the 4.0.0 milestone to get an idea of what's coming. If you have other ideas, or would like to participate in 4.0.0, now's the time. If you depend on this library and don't want to be interrupted, I recommend you use your dependency mangement tool to pin to version 3.
**SECURITY NOTICE:** Some older versions of Go have a security issue in the cryotp/elliptic. Recommendation is to upgrade to at least 1.8.3. See issue #216 for more detail.
**SECURITY NOTICE:** It's important that you [validate the `alg` presented is what you expect](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). This library attempts to make it easy to do the right thing by requiring key types match the expected alg, but you should take the extra step to verify it in your usage. See the examples provided.
## What the heck is a JWT?
JWT.io has [a great introduction](https://jwt.io/introduction) to JSON Web Tokens.
In short, it's a signed JSON object that does something useful (for example, authentication). It's commonly used for `Bearer` tokens in Oauth 2. A token is made of three parts, separated by `.`'s. The first two parts are JSON objects, that have been [base64url](http://tools.ietf.org/html/rfc4648) encoded. The last part is the signature, encoded the same way.
The first part is called the header. It contains the necessary information for verifying the last part, the signature. For example, which encryption method was used for signing and what key was used.
The part in the middle is the interesting bit. It's called the Claims and contains the actual stuff you care about. Refer to [the RFC](http://self-issued.info/docs/draft-jones-json-web-token.html) for information about reserved keys and the proper way to add your own.
## What's in the box?
This library supports the parsing and verification as well as the generation and signing of JWTs. Current supported signing algorithms are HMAC SHA, RSA, RSA-PSS, and ECDSA, though hooks are present for adding your own.
## Examples
See [the project documentation](https://godoc.org/github.com/dgrijalva/jwt-go) for examples of usage:
* [Simple example of parsing and validating a token](https://godoc.org/github.com/dgrijalva/jwt-go#example-Parse--Hmac)
* [Simple example of building and signing a token](https://godoc.org/github.com/dgrijalva/jwt-go#example-New--Hmac)
* [Directory of Examples](https://godoc.org/github.com/dgrijalva/jwt-go#pkg-examples)
## Extensions
This library publishes all the necessary components for adding your own signing methods. Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod`.
Here's an example of an extension that integrates with the Google App Engine signing tools: https://github.com/someone1/gcp-jwt-go
## Compliance
This library was last reviewed to comply with [RTF 7519](http://www.rfc-editor.org/info/rfc7519) dated May 2015 with a few notable differences:
* In order to protect against accidental use of [Unsecured JWTs](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#UnsecuredJWT), tokens using `alg=none` will only be accepted if the constant `jwt.UnsafeAllowNoneSignatureType` is provided as the key.
## Project Status & Versioning
This library is considered production ready. Feedback and feature requests are appreciated. The API should be considered stable. There should be very few backwards-incompatible changes outside of major version updates (and only with good reason).
This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull requests will land on `master`. Periodically, versions will be tagged from `master`. You can find all the releases on [the project releases page](https://github.com/dgrijalva/jwt-go/releases).
While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users. You may want to use this alternative package include: `gopkg.in/dgrijalva/jwt-go.v3`. It will do the right thing WRT semantic versioning.
**BREAKING CHANGES:***
* Version 3.0.0 includes _a lot_ of changes from the 2.x line, including a few that break the API. We've tried to break as few things as possible, so there should just be a few type signature changes. A full list of breaking changes is available in `VERSION_HISTORY.md`. See `MIGRATION_GUIDE.md` for more information on updating your code.
## Usage Tips
### Signing vs Encryption
A token is simply a JSON object that is signed by its author. this tells you exactly two things about the data:
* The author of the token was in the possession of the signing secret
* The data has not been modified since it was signed
It's important to know that JWT does not provide encryption, which means anyone who has access to the token can read its contents. If you need to protect (encrypt) the data, there is a companion spec, `JWE`, that provides this functionality. JWE is currently outside the scope of this library.
### Choosing a Signing Method
There are several signing methods available, and you should probably take the time to learn about the various options before choosing one. The principal design decision is most likely going to be symmetric vs asymmetric.
Symmetric signing methods, such as HSA, use only a single secret. This is probably the simplest signing method to use since any `[]byte` can be used as a valid secret. They are also slightly computationally faster to use, though this rarely is enough to matter. Symmetric signing methods work the best when both producers and consumers of tokens are trusted, or even the same system. Since the same secret is used to both sign and validate tokens, you can't easily distribute the key for validation.
Asymmetric signing methods, such as RSA, use different keys for signing and verifying tokens. This makes it possible to produce tokens with a private key, and allow any consumer to access the public key for verification.
### Signing Methods and Key Types
Each signing method expects a different object type for its signing keys. See the package documentation for details. Here are the most common ones:
* The [HMAC signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodHMAC) (`HS256`,`HS384`,`HS512`) expect `[]byte` values for signing and validation
* The [RSA signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for validation
* The [ECDSA signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.PublicKey` for validation
### JWT and OAuth
It's worth mentioning that OAuth and JWT are not the same thing. A JWT token is simply a signed JSON object. It can be used anywhere such a thing is useful. There is some confusion, though, as JWT is the most common type of bearer token used in OAuth2 authentication.
Without going too far down the rabbit hole, here's a description of the interaction of these technologies:
* OAuth is a protocol for allowing an identity provider to be separate from the service a user is logging in to. For example, whenever you use Facebook to log into a different service (Yelp, Spotify, etc), you are using OAuth.
* OAuth defines several options for passing around authentication data. One popular method is called a "bearer token". A bearer token is simply a string that _should_ only be held by an authenticated user. Thus, simply presenting this token proves your identity. You can probably derive from here why a JWT might make a good bearer token.
* Because bearer tokens are used for authentication, it's important they're kept secret. This is why transactions that use bearer tokens typically happen over SSL.
## More
Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go).
The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. You'll also find several implementation examples in the documentation.

View File

@ -1,118 +0,0 @@
## `jwt-go` Version History
#### 3.2.0
* Added method `ParseUnverified` to allow users to split up the tasks of parsing and validation
* HMAC signing method returns `ErrInvalidKeyType` instead of `ErrInvalidKey` where appropriate
* Added options to `request.ParseFromRequest`, which allows for an arbitrary list of modifiers to parsing behavior. Initial set include `WithClaims` and `WithParser`. Existing usage of this function will continue to work as before.
* Deprecated `ParseFromRequestWithClaims` to simplify API in the future.
#### 3.1.0
* Improvements to `jwt` command line tool
* Added `SkipClaimsValidation` option to `Parser`
* Documentation updates
#### 3.0.0
* **Compatibility Breaking Changes**: See MIGRATION_GUIDE.md for tips on updating your code
* Dropped support for `[]byte` keys when using RSA signing methods. This convenience feature could contribute to security vulnerabilities involving mismatched key types with signing methods.
* `ParseFromRequest` has been moved to `request` subpackage and usage has changed
* The `Claims` property on `Token` is now type `Claims` instead of `map[string]interface{}`. The default value is type `MapClaims`, which is an alias to `map[string]interface{}`. This makes it possible to use a custom type when decoding claims.
* Other Additions and Changes
* Added `Claims` interface type to allow users to decode the claims into a custom type
* Added `ParseWithClaims`, which takes a third argument of type `Claims`. Use this function instead of `Parse` if you have a custom type you'd like to decode into.
* Dramatically improved the functionality and flexibility of `ParseFromRequest`, which is now in the `request` subpackage
* Added `ParseFromRequestWithClaims` which is the `FromRequest` equivalent of `ParseWithClaims`
* Added new interface type `Extractor`, which is used for extracting JWT strings from http requests. Used with `ParseFromRequest` and `ParseFromRequestWithClaims`.
* Added several new, more specific, validation errors to error type bitmask
* Moved examples from README to executable example files
* Signing method registry is now thread safe
* Added new property to `ValidationError`, which contains the raw error returned by calls made by parse/verify (such as those returned by keyfunc or json parser)
#### 2.7.0
This will likely be the last backwards compatible release before 3.0.0, excluding essential bug fixes.
* Added new option `-show` to the `jwt` command that will just output the decoded token without verifying
* Error text for expired tokens includes how long it's been expired
* Fixed incorrect error returned from `ParseRSAPublicKeyFromPEM`
* Documentation updates
#### 2.6.0
* Exposed inner error within ValidationError
* Fixed validation errors when using UseJSONNumber flag
* Added several unit tests
#### 2.5.0
* Added support for signing method none. You shouldn't use this. The API tries to make this clear.
* Updated/fixed some documentation
* Added more helpful error message when trying to parse tokens that begin with `BEARER `
#### 2.4.0
* Added new type, Parser, to allow for configuration of various parsing parameters
* You can now specify a list of valid signing methods. Anything outside this set will be rejected.
* You can now opt to use the `json.Number` type instead of `float64` when parsing token JSON
* Added support for [Travis CI](https://travis-ci.org/dgrijalva/jwt-go)
* Fixed some bugs with ECDSA parsing
#### 2.3.0
* Added support for ECDSA signing methods
* Added support for RSA PSS signing methods (requires go v1.4)
#### 2.2.0
* Gracefully handle a `nil` `Keyfunc` being passed to `Parse`. Result will now be the parsed token and an error, instead of a panic.
#### 2.1.0
Backwards compatible API change that was missed in 2.0.0.
* The `SignedString` method on `Token` now takes `interface{}` instead of `[]byte`
#### 2.0.0
There were two major reasons for breaking backwards compatibility with this update. The first was a refactor required to expand the width of the RSA and HMAC-SHA signing implementations. There will likely be no required code changes to support this change.
The second update, while unfortunately requiring a small change in integration, is required to open up this library to other signing methods. Not all keys used for all signing methods have a single standard on-disk representation. Requiring `[]byte` as the type for all keys proved too limiting. Additionally, this implementation allows for pre-parsed tokens to be reused, which might matter in an application that parses a high volume of tokens with a small set of keys. Backwards compatibilty has been maintained for passing `[]byte` to the RSA signing methods, but they will also accept `*rsa.PublicKey` and `*rsa.PrivateKey`.
It is likely the only integration change required here will be to change `func(t *jwt.Token) ([]byte, error)` to `func(t *jwt.Token) (interface{}, error)` when calling `Parse`.
* **Compatibility Breaking Changes**
* `SigningMethodHS256` is now `*SigningMethodHMAC` instead of `type struct`
* `SigningMethodRS256` is now `*SigningMethodRSA` instead of `type struct`
* `KeyFunc` now returns `interface{}` instead of `[]byte`
* `SigningMethod.Sign` now takes `interface{}` instead of `[]byte` for the key
* `SigningMethod.Verify` now takes `interface{}` instead of `[]byte` for the key
* Renamed type `SigningMethodHS256` to `SigningMethodHMAC`. Specific sizes are now just instances of this type.
* Added public package global `SigningMethodHS256`
* Added public package global `SigningMethodHS384`
* Added public package global `SigningMethodHS512`
* Renamed type `SigningMethodRS256` to `SigningMethodRSA`. Specific sizes are now just instances of this type.
* Added public package global `SigningMethodRS256`
* Added public package global `SigningMethodRS384`
* Added public package global `SigningMethodRS512`
* Moved sample private key for HMAC tests from an inline value to a file on disk. Value is unchanged.
* Refactored the RSA implementation to be easier to read
* Exposed helper methods `ParseRSAPrivateKeyFromPEM` and `ParseRSAPublicKeyFromPEM`
#### 1.0.2
* Fixed bug in parsing public keys from certificates
* Added more tests around the parsing of keys for RS256
* Code refactoring in RS256 implementation. No functional changes
#### 1.0.1
* Fixed panic if RS256 signing method was passed an invalid key
#### 1.0.0
* First versioned release
* API stabilized
* Supports creating, signing, parsing, and validating JWT tokens
* Supports RS256 and HS256 signing methods

View File

@ -1,134 +0,0 @@
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
}

View File

@ -1,4 +0,0 @@
// Package jwt is a Go implementation of JSON Web Tokens: http://self-issued.info/docs/draft-jones-json-web-token.html
//
// See README.md for more info.
package jwt

View File

@ -1,148 +0,0 @@
package jwt
import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"errors"
"math/big"
)
var (
// Sadly this is missing from crypto/ecdsa compared to crypto/rsa
ErrECDSAVerification = errors.New("crypto/ecdsa: verification error")
)
// Implements the ECDSA family of signing methods signing methods
// Expects *ecdsa.PrivateKey for signing and *ecdsa.PublicKey for verification
type SigningMethodECDSA struct {
Name string
Hash crypto.Hash
KeySize int
CurveBits int
}
// Specific instances for EC256 and company
var (
SigningMethodES256 *SigningMethodECDSA
SigningMethodES384 *SigningMethodECDSA
SigningMethodES512 *SigningMethodECDSA
)
func init() {
// ES256
SigningMethodES256 = &SigningMethodECDSA{"ES256", crypto.SHA256, 32, 256}
RegisterSigningMethod(SigningMethodES256.Alg(), func() SigningMethod {
return SigningMethodES256
})
// ES384
SigningMethodES384 = &SigningMethodECDSA{"ES384", crypto.SHA384, 48, 384}
RegisterSigningMethod(SigningMethodES384.Alg(), func() SigningMethod {
return SigningMethodES384
})
// ES512
SigningMethodES512 = &SigningMethodECDSA{"ES512", crypto.SHA512, 66, 521}
RegisterSigningMethod(SigningMethodES512.Alg(), func() SigningMethod {
return SigningMethodES512
})
}
func (m *SigningMethodECDSA) Alg() string {
return m.Name
}
// Implements the Verify method from SigningMethod
// For this verify method, key must be an ecdsa.PublicKey struct
func (m *SigningMethodECDSA) Verify(signingString, signature string, key interface{}) error {
var err error
// Decode the signature
var sig []byte
if sig, err = DecodeSegment(signature); err != nil {
return err
}
// Get the key
var ecdsaKey *ecdsa.PublicKey
switch k := key.(type) {
case *ecdsa.PublicKey:
ecdsaKey = k
default:
return ErrInvalidKeyType
}
if len(sig) != 2*m.KeySize {
return ErrECDSAVerification
}
r := big.NewInt(0).SetBytes(sig[:m.KeySize])
s := big.NewInt(0).SetBytes(sig[m.KeySize:])
// Create hasher
if !m.Hash.Available() {
return ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Verify the signature
if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus == true {
return nil
} else {
return ErrECDSAVerification
}
}
// Implements the Sign method from SigningMethod
// For this signing method, key must be an ecdsa.PrivateKey struct
func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string, error) {
// Get the key
var ecdsaKey *ecdsa.PrivateKey
switch k := key.(type) {
case *ecdsa.PrivateKey:
ecdsaKey = k
default:
return "", ErrInvalidKeyType
}
// Create the hasher
if !m.Hash.Available() {
return "", ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Sign the string and return r, s
if r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, hasher.Sum(nil)); err == nil {
curveBits := ecdsaKey.Curve.Params().BitSize
if m.CurveBits != curveBits {
return "", ErrInvalidKey
}
keyBytes := curveBits / 8
if curveBits%8 > 0 {
keyBytes += 1
}
// We serialize the outpus (r and s) into big-endian byte arrays and pad
// them with zeros on the left to make sure the sizes work out. Both arrays
// must be keyBytes long, and the output must be 2*keyBytes long.
rBytes := r.Bytes()
rBytesPadded := make([]byte, keyBytes)
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
sBytes := s.Bytes()
sBytesPadded := make([]byte, keyBytes)
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
out := append(rBytesPadded, sBytesPadded...)
return EncodeSegment(out), nil
} else {
return "", err
}
}

View File

@ -1,67 +0,0 @@
package jwt
import (
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
"errors"
)
var (
ErrNotECPublicKey = errors.New("Key is not a valid ECDSA public key")
ErrNotECPrivateKey = errors.New("Key is not a valid ECDSA private key")
)
// Parse PEM encoded Elliptic Curve Private Key Structure
func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
// Parse the key
var parsedKey interface{}
if parsedKey, err = x509.ParseECPrivateKey(block.Bytes); err != nil {
return nil, err
}
var pkey *ecdsa.PrivateKey
var ok bool
if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok {
return nil, ErrNotECPrivateKey
}
return pkey, nil
}
// Parse PEM encoded PKCS1 or PKCS8 public key
func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
// Parse the key
var parsedKey interface{}
if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
parsedKey = cert.PublicKey
} else {
return nil, err
}
}
var pkey *ecdsa.PublicKey
var ok bool
if pkey, ok = parsedKey.(*ecdsa.PublicKey); !ok {
return nil, ErrNotECPublicKey
}
return pkey, nil
}

View File

@ -1,59 +0,0 @@
package jwt
import (
"errors"
)
// Error constants
var (
ErrInvalidKey = errors.New("key is invalid")
ErrInvalidKeyType = errors.New("key is of invalid type")
ErrHashUnavailable = errors.New("the requested hash function is unavailable")
)
// The errors that might occur when parsing and validating a token
const (
ValidationErrorMalformed uint32 = 1 << iota // Token is malformed
ValidationErrorUnverifiable // Token could not be verified because of signing problems
ValidationErrorSignatureInvalid // Signature validation failed
// Standard Claim validation errors
ValidationErrorAudience // AUD validation failed
ValidationErrorExpired // EXP validation failed
ValidationErrorIssuedAt // IAT validation failed
ValidationErrorIssuer // ISS validation failed
ValidationErrorNotValidYet // NBF validation failed
ValidationErrorId // JTI validation failed
ValidationErrorClaimsInvalid // Generic claims validation error
)
// Helper for constructing a ValidationError with a string error message
func NewValidationError(errorText string, errorFlags uint32) *ValidationError {
return &ValidationError{
text: errorText,
Errors: errorFlags,
}
}
// The error from Parse if token is not valid
type ValidationError struct {
Inner error // stores the error returned by external dependencies, i.e.: KeyFunc
Errors uint32 // bitfield. see ValidationError... constants
text string // errors that do not have a valid error just have text
}
// Validation error is an error type
func (e ValidationError) Error() string {
if e.Inner != nil {
return e.Inner.Error()
} else if e.text != "" {
return e.text
} else {
return "token is invalid"
}
}
// No errors
func (e *ValidationError) valid() bool {
return e.Errors == 0
}

View File

@ -1,95 +0,0 @@
package jwt
import (
"crypto"
"crypto/hmac"
"errors"
)
// Implements the HMAC-SHA family of signing methods signing methods
// Expects key type of []byte for both signing and validation
type SigningMethodHMAC struct {
Name string
Hash crypto.Hash
}
// Specific instances for HS256 and company
var (
SigningMethodHS256 *SigningMethodHMAC
SigningMethodHS384 *SigningMethodHMAC
SigningMethodHS512 *SigningMethodHMAC
ErrSignatureInvalid = errors.New("signature is invalid")
)
func init() {
// HS256
SigningMethodHS256 = &SigningMethodHMAC{"HS256", crypto.SHA256}
RegisterSigningMethod(SigningMethodHS256.Alg(), func() SigningMethod {
return SigningMethodHS256
})
// HS384
SigningMethodHS384 = &SigningMethodHMAC{"HS384", crypto.SHA384}
RegisterSigningMethod(SigningMethodHS384.Alg(), func() SigningMethod {
return SigningMethodHS384
})
// HS512
SigningMethodHS512 = &SigningMethodHMAC{"HS512", crypto.SHA512}
RegisterSigningMethod(SigningMethodHS512.Alg(), func() SigningMethod {
return SigningMethodHS512
})
}
func (m *SigningMethodHMAC) Alg() string {
return m.Name
}
// Verify the signature of HSXXX tokens. Returns nil if the signature is valid.
func (m *SigningMethodHMAC) Verify(signingString, signature string, key interface{}) error {
// Verify the key is the right type
keyBytes, ok := key.([]byte)
if !ok {
return ErrInvalidKeyType
}
// Decode signature, for comparison
sig, err := DecodeSegment(signature)
if err != nil {
return err
}
// Can we use the specified hashing method?
if !m.Hash.Available() {
return ErrHashUnavailable
}
// This signing method is symmetric, so we validate the signature
// by reproducing the signature from the signing string and key, then
// comparing that against the provided signature.
hasher := hmac.New(m.Hash.New, keyBytes)
hasher.Write([]byte(signingString))
if !hmac.Equal(sig, hasher.Sum(nil)) {
return ErrSignatureInvalid
}
// No validation errors. Signature is good.
return nil
}
// Implements the Sign method from SigningMethod for this signing method.
// Key must be []byte
func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) {
if keyBytes, ok := key.([]byte); ok {
if !m.Hash.Available() {
return "", ErrHashUnavailable
}
hasher := hmac.New(m.Hash.New, keyBytes)
hasher.Write([]byte(signingString))
return EncodeSegment(hasher.Sum(nil)), nil
}
return "", ErrInvalidKeyType
}

View File

@ -1,94 +0,0 @@
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
}

View File

@ -1,52 +0,0 @@
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
}

View File

@ -1,148 +0,0 @@
package jwt
import (
"bytes"
"encoding/json"
"fmt"
"strings"
)
type Parser struct {
ValidMethods []string // If populated, only these methods will be considered valid
UseJSONNumber bool // Use JSON Number format in JSON decoder
SkipClaimsValidation bool // Skip claims validation during token parsing
}
// Parse, validate, and return a token.
// keyFunc will receive the parsed token and should return the key for validating.
// If everything is kosher, err will be nil
func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc)
}
func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
token, parts, err := p.ParseUnverified(tokenString, claims)
if err != nil {
return token, err
}
// Verify signing method is in the required set
if p.ValidMethods != nil {
var signingMethodValid = false
var alg = token.Method.Alg()
for _, m := range p.ValidMethods {
if m == alg {
signingMethodValid = true
break
}
}
if !signingMethodValid {
// signing method is not in the listed set
return token, NewValidationError(fmt.Sprintf("signing method %v is invalid", alg), ValidationErrorSignatureInvalid)
}
}
// Lookup key
var key interface{}
if keyFunc == nil {
// keyFunc was not provided. short circuiting validation
return token, NewValidationError("no Keyfunc was provided.", ValidationErrorUnverifiable)
}
if key, err = keyFunc(token); err != nil {
// keyFunc returned an error
if ve, ok := err.(*ValidationError); ok {
return token, ve
}
return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}
}
vErr := &ValidationError{}
// Validate Claims
if !p.SkipClaimsValidation {
if err := token.Claims.Valid(); err != nil {
// If the Claims Valid returned an error, check if it is a validation error,
// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
if e, ok := err.(*ValidationError); !ok {
vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
} else {
vErr = e
}
}
}
// Perform validation
token.Signature = parts[2]
if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil {
vErr.Inner = err
vErr.Errors |= ValidationErrorSignatureInvalid
}
if vErr.valid() {
token.Valid = true
return token, nil
}
return token, vErr
}
// WARNING: Don't use this method unless you know what you're doing
//
// This method parses the token but doesn't validate the signature. It's only
// ever useful in cases where you know the signature is valid (because it has
// been checked previously in the stack) and you want to extract values from
// it.
func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) {
parts = strings.Split(tokenString, ".")
if len(parts) != 3 {
return nil, parts, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed)
}
token = &Token{Raw: tokenString}
// parse Header
var headerBytes []byte
if headerBytes, err = DecodeSegment(parts[0]); err != nil {
if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") {
return token, parts, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed)
}
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
if err = json.Unmarshal(headerBytes, &token.Header); err != nil {
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
// parse Claims
var claimBytes []byte
token.Claims = claims
if claimBytes, err = DecodeSegment(parts[1]); err != nil {
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
dec := json.NewDecoder(bytes.NewBuffer(claimBytes))
if p.UseJSONNumber {
dec.UseNumber()
}
// JSON Decode. Special case for map type to avoid weird pointer behavior
if c, ok := token.Claims.(MapClaims); ok {
err = dec.Decode(&c)
} else {
err = dec.Decode(&claims)
}
// Handle decode error
if err != nil {
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
}
// Lookup signature method
if method, ok := token.Header["alg"].(string); ok {
if token.Method = GetSigningMethod(method); token.Method == nil {
return token, parts, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable)
}
} else {
return token, parts, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable)
}
return token, parts, nil
}

View File

@ -1,101 +0,0 @@
package jwt
import (
"crypto"
"crypto/rand"
"crypto/rsa"
)
// Implements the RSA family of signing methods signing methods
// Expects *rsa.PrivateKey for signing and *rsa.PublicKey for validation
type SigningMethodRSA struct {
Name string
Hash crypto.Hash
}
// Specific instances for RS256 and company
var (
SigningMethodRS256 *SigningMethodRSA
SigningMethodRS384 *SigningMethodRSA
SigningMethodRS512 *SigningMethodRSA
)
func init() {
// RS256
SigningMethodRS256 = &SigningMethodRSA{"RS256", crypto.SHA256}
RegisterSigningMethod(SigningMethodRS256.Alg(), func() SigningMethod {
return SigningMethodRS256
})
// RS384
SigningMethodRS384 = &SigningMethodRSA{"RS384", crypto.SHA384}
RegisterSigningMethod(SigningMethodRS384.Alg(), func() SigningMethod {
return SigningMethodRS384
})
// RS512
SigningMethodRS512 = &SigningMethodRSA{"RS512", crypto.SHA512}
RegisterSigningMethod(SigningMethodRS512.Alg(), func() SigningMethod {
return SigningMethodRS512
})
}
func (m *SigningMethodRSA) Alg() string {
return m.Name
}
// Implements the Verify method from SigningMethod
// For this signing method, must be an *rsa.PublicKey structure.
func (m *SigningMethodRSA) Verify(signingString, signature string, key interface{}) error {
var err error
// Decode the signature
var sig []byte
if sig, err = DecodeSegment(signature); err != nil {
return err
}
var rsaKey *rsa.PublicKey
var ok bool
if rsaKey, ok = key.(*rsa.PublicKey); !ok {
return ErrInvalidKeyType
}
// Create hasher
if !m.Hash.Available() {
return ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Verify the signature
return rsa.VerifyPKCS1v15(rsaKey, m.Hash, hasher.Sum(nil), sig)
}
// Implements the Sign method from SigningMethod
// For this signing method, must be an *rsa.PrivateKey structure.
func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) {
var rsaKey *rsa.PrivateKey
var ok bool
// Validate type of key
if rsaKey, ok = key.(*rsa.PrivateKey); !ok {
return "", ErrInvalidKey
}
// Create the hasher
if !m.Hash.Available() {
return "", ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Sign the string and return the encoded bytes
if sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil)); err == nil {
return EncodeSegment(sigBytes), nil
} else {
return "", err
}
}

View File

@ -1,126 +0,0 @@
// +build go1.4
package jwt
import (
"crypto"
"crypto/rand"
"crypto/rsa"
)
// Implements the RSAPSS family of signing methods signing methods
type SigningMethodRSAPSS struct {
*SigningMethodRSA
Options *rsa.PSSOptions
}
// Specific instances for RS/PS and company
var (
SigningMethodPS256 *SigningMethodRSAPSS
SigningMethodPS384 *SigningMethodRSAPSS
SigningMethodPS512 *SigningMethodRSAPSS
)
func init() {
// PS256
SigningMethodPS256 = &SigningMethodRSAPSS{
&SigningMethodRSA{
Name: "PS256",
Hash: crypto.SHA256,
},
&rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
Hash: crypto.SHA256,
},
}
RegisterSigningMethod(SigningMethodPS256.Alg(), func() SigningMethod {
return SigningMethodPS256
})
// PS384
SigningMethodPS384 = &SigningMethodRSAPSS{
&SigningMethodRSA{
Name: "PS384",
Hash: crypto.SHA384,
},
&rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
Hash: crypto.SHA384,
},
}
RegisterSigningMethod(SigningMethodPS384.Alg(), func() SigningMethod {
return SigningMethodPS384
})
// PS512
SigningMethodPS512 = &SigningMethodRSAPSS{
&SigningMethodRSA{
Name: "PS512",
Hash: crypto.SHA512,
},
&rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
Hash: crypto.SHA512,
},
}
RegisterSigningMethod(SigningMethodPS512.Alg(), func() SigningMethod {
return SigningMethodPS512
})
}
// Implements the Verify method from SigningMethod
// For this verify method, key must be an rsa.PublicKey struct
func (m *SigningMethodRSAPSS) Verify(signingString, signature string, key interface{}) error {
var err error
// Decode the signature
var sig []byte
if sig, err = DecodeSegment(signature); err != nil {
return err
}
var rsaKey *rsa.PublicKey
switch k := key.(type) {
case *rsa.PublicKey:
rsaKey = k
default:
return ErrInvalidKey
}
// Create hasher
if !m.Hash.Available() {
return ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
return rsa.VerifyPSS(rsaKey, m.Hash, hasher.Sum(nil), sig, m.Options)
}
// Implements the Sign method from SigningMethod
// For this signing method, key must be an rsa.PrivateKey struct
func (m *SigningMethodRSAPSS) Sign(signingString string, key interface{}) (string, error) {
var rsaKey *rsa.PrivateKey
switch k := key.(type) {
case *rsa.PrivateKey:
rsaKey = k
default:
return "", ErrInvalidKeyType
}
// Create the hasher
if !m.Hash.Available() {
return "", ErrHashUnavailable
}
hasher := m.Hash.New()
hasher.Write([]byte(signingString))
// Sign the string and return the encoded bytes
if sigBytes, err := rsa.SignPSS(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil), m.Options); err == nil {
return EncodeSegment(sigBytes), nil
} else {
return "", err
}
}

View File

@ -1,101 +0,0 @@
package jwt
import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
)
var (
ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key")
ErrNotRSAPrivateKey = errors.New("Key is not a valid RSA private key")
ErrNotRSAPublicKey = errors.New("Key is not a valid RSA public key")
)
// Parse PEM encoded PKCS1 or PKCS8 private key
func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
var parsedKey interface{}
if parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
return nil, err
}
}
var pkey *rsa.PrivateKey
var ok bool
if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok {
return nil, ErrNotRSAPrivateKey
}
return pkey, nil
}
// Parse PEM encoded PKCS1 or PKCS8 private key protected with password
func ParseRSAPrivateKeyFromPEMWithPassword(key []byte, password string) (*rsa.PrivateKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
var parsedKey interface{}
var blockDecrypted []byte
if blockDecrypted, err = x509.DecryptPEMBlock(block, []byte(password)); err != nil {
return nil, err
}
if parsedKey, err = x509.ParsePKCS1PrivateKey(blockDecrypted); err != nil {
if parsedKey, err = x509.ParsePKCS8PrivateKey(blockDecrypted); err != nil {
return nil, err
}
}
var pkey *rsa.PrivateKey
var ok bool
if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok {
return nil, ErrNotRSAPrivateKey
}
return pkey, nil
}
// Parse PEM encoded PKCS1 or PKCS8 public key
func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
// Parse the key
var parsedKey interface{}
if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
parsedKey = cert.PublicKey
} else {
return nil, err
}
}
var pkey *rsa.PublicKey
var ok bool
if pkey, ok = parsedKey.(*rsa.PublicKey); !ok {
return nil, ErrNotRSAPublicKey
}
return pkey, nil
}

View File

@ -1,35 +0,0 @@
package jwt
import (
"sync"
)
var signingMethods = map[string]func() SigningMethod{}
var signingMethodLock = new(sync.RWMutex)
// Implement SigningMethod to add new methods for signing or verifying tokens.
type SigningMethod interface {
Verify(signingString, signature string, key interface{}) error // Returns nil if signature is valid
Sign(signingString string, key interface{}) (string, error) // Returns encoded signature or error
Alg() string // returns the alg identifier for this method (example: 'HS256')
}
// Register the "alg" name and a factory function for signing method.
// This is typically done during init() in the method's implementation
func RegisterSigningMethod(alg string, f func() SigningMethod) {
signingMethodLock.Lock()
defer signingMethodLock.Unlock()
signingMethods[alg] = f
}
// Get a signing method from an "alg" string
func GetSigningMethod(alg string) (method SigningMethod) {
signingMethodLock.RLock()
defer signingMethodLock.RUnlock()
if methodF, ok := signingMethods[alg]; ok {
method = methodF()
}
return
}

View File

@ -1,108 +0,0 @@
package jwt
import (
"encoding/base64"
"encoding/json"
"strings"
"time"
)
// TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time).
// You can override it to use another time value. This is useful for testing or if your
// server uses a different time zone than your tokens.
var TimeFunc = time.Now
// Parse methods use this callback function to supply
// the key for verification. The function receives the parsed,
// but unverified Token. This allows you to use properties in the
// Header of the token (such as `kid`) to identify which key to use.
type Keyfunc func(*Token) (interface{}, error)
// A JWT Token. Different fields will be used depending on whether you're
// creating or parsing/verifying a token.
type Token struct {
Raw string // The raw token. Populated when you Parse a token
Method SigningMethod // The signing method used or to be used
Header map[string]interface{} // The first segment of the token
Claims Claims // The second segment of the token
Signature string // The third segment of the token. Populated when you Parse a token
Valid bool // Is the token valid? Populated when you Parse/Verify a token
}
// Create a new Token. Takes a signing method
func New(method SigningMethod) *Token {
return NewWithClaims(method, MapClaims{})
}
func NewWithClaims(method SigningMethod, claims Claims) *Token {
return &Token{
Header: map[string]interface{}{
"typ": "JWT",
"alg": method.Alg(),
},
Claims: claims,
Method: method,
}
}
// Get the complete, signed token
func (t *Token) SignedString(key interface{}) (string, error) {
var sig, sstr string
var err error
if sstr, err = t.SigningString(); err != nil {
return "", err
}
if sig, err = t.Method.Sign(sstr, key); err != nil {
return "", err
}
return strings.Join([]string{sstr, sig}, "."), nil
}
// Generate the signing string. This is the
// most expensive part of the whole deal. Unless you
// need this for something special, just go straight for
// the SignedString.
func (t *Token) SigningString() (string, error) {
var err error
parts := make([]string, 2)
for i, _ := range parts {
var jsonValue []byte
if i == 0 {
if jsonValue, err = json.Marshal(t.Header); err != nil {
return "", err
}
} else {
if jsonValue, err = json.Marshal(t.Claims); err != nil {
return "", err
}
}
parts[i] = EncodeSegment(jsonValue)
}
return strings.Join(parts, "."), nil
}
// Parse, validate, and return a token.
// keyFunc will receive the parsed token and should return the key for validating.
// If everything is kosher, err will be nil
func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
return new(Parser).Parse(tokenString, keyFunc)
}
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
return new(Parser).ParseWithClaims(tokenString, claims, keyFunc)
}
// Encode JWT specific base64url encoding with padding stripped
func EncodeSegment(seg []byte) string {
return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=")
}
// Decode JWT specific base64url encoding with padding stripped
func DecodeSegment(seg string) ([]byte, error) {
if l := len(seg) % 4; l > 0 {
seg += strings.Repeat("=", 4-l)
}
return base64.URLEncoding.DecodeString(seg)
}

View File

@ -1,17 +0,0 @@
sudo: false
language: go
go:
- 1.10.x
- 1.x
- tip
matrix:
allow_failures:
- go: tip
before_install:
- go get github.com/axw/gocov/gocov
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover
- test -z "$(gofmt -s -l . | tee /dev/stderr)"
- go tool vet -all -structtags -shadow .
script:
- $HOME/gopath/bin/goveralls -service=travis-ci

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2016 logrus_sentry Authors
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.

View File

@ -1,163 +0,0 @@
Sentry Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
----
[![GoDoc][1]][2] [![Release][5]][6] [![Build Status][7]][8] [![Coverage Status][9]][10] [![Go Report Card][13]][14] [![Code Climate][19]][20] [![BCH compliance][21]][22]
[1]: https://godoc.org/github.com/evalphobia/logrus_sentry?status.svg
[2]: https://godoc.org/github.com/evalphobia/logrus_sentry
[4]: LICENSE.md
[5]: https://img.shields.io/github/release/evalphobia/logrus_sentry.svg
[6]: https://github.com/evalphobia/logrus_sentry/releases/latest
[7]: https://travis-ci.org/evalphobia/logrus_sentry.svg?branch=master
[8]: https://travis-ci.org/evalphobia/logrus_sentry
[9]: https://coveralls.io/repos/evalphobia/logrus_sentry/badge.svg?branch=master&service=github
[10]: https://coveralls.io/github/evalphobia/logrus_sentry?branch=master
[11]: https://codecov.io/github/evalphobia/logrus_sentry/coverage.svg?branch=master
[12]: https://codecov.io/github/evalphobia/logrus_sentry?branch=master
[13]: https://goreportcard.com/badge/github.com/evalphobia/logrus_sentry
[14]: https://goreportcard.com/report/github.com/evalphobia/logrus_sentry
[15]: https://img.shields.io/github/downloads/evalphobia/logrus_sentry/total.svg?maxAge=1800
[16]: https://github.com/evalphobia/logrus_sentry/releases
[17]: https://img.shields.io/github/stars/evalphobia/logrus_sentry.svg
[18]: https://github.com/evalphobia/logrus_sentry/stargazers
[19]: https://codeclimate.com/github/evalphobia/logrus_sentry/badges/gpa.svg
[20]: https://codeclimate.com/github/evalphobia/logrus_sentry
[21]: https://bettercodehub.com/edge/badge/evalphobia/logrus_sentry?branch=master
[22]: https://bettercodehub.com/
[Sentry](https://getsentry.com) provides both self-hosted and hosted
solutions for exception tracking.
Both client and server are
[open source](https://github.com/getsentry/sentry).
## Usage
Every sentry application defined on the server gets a different
[DSN](https://www.getsentry.com/docs/). In the example below replace
`YOUR_DSN` with the one created for your application.
```go
import (
"github.com/sirupsen/logrus"
"github.com/evalphobia/logrus_sentry"
)
func main() {
log := logrus.New()
hook, err := logrus_sentry.NewSentryHook(YOUR_DSN, []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
})
if err == nil {
log.Hooks.Add(hook)
}
}
```
If you wish to initialize a SentryHook with tags, you can use the `NewWithTagsSentryHook` constructor to provide default tags:
```go
tags := map[string]string{
"site": "example.com",
}
levels := []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
}
hook, err := logrus_sentry.NewWithTagsSentryHook(YOUR_DSN, tags, levels)
```
If you wish to initialize a SentryHook with an already initialized raven client, you can use
the `NewWithClientSentryHook` constructor:
```go
import (
"github.com/sirupsen/logrus"
"github.com/evalphobia/logrus_sentry"
"github.com/getsentry/raven-go"
)
func main() {
log := logrus.New()
client, err := raven.New(YOUR_DSN)
if err != nil {
log.Fatal(err)
}
hook, err := logrus_sentry.NewWithClientSentryHook(client, []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
})
if err == nil {
log.Hooks.Add(hook)
}
}
hook, err := NewWithClientSentryHook(client, []logrus.Level{
logrus.ErrorLevel,
})
```
## Special fields
Some logrus fields have a special meaning in this hook, and they will be especially processed by Sentry.
| Field key | Description |
| ------------- | ------------- |
| `event_id` | Each logged event is identified by the `event_id`, which is hexadecimal string representing a UUID4 value. You can manually specify the identifier of a log event by supplying this field. The `event_id` string should be in one of the following UUID format: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` and `urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`)|
| `user_name` | Name of the user who is in the context of the event |
| `user_email` | Email of the user who is in the context of the event |
| `user_id` | ID of the user who is in the context of the event |
| `user_ip` | IP of the user who is in the context of the event |
| `server_name` | Also known as hostname, it is the name of the server which is logging the event (hostname.example.com) |
| `tags` | `tags` are `raven.Tags` struct from `github.com/getsentry/raven-go` and override default tags data |
| `fingerprint` | `fingerprint` is an string array, that allows you to affect sentry's grouping of events as detailed in the [sentry documentation](https://docs.sentry.io/learn/rollups/#customize-grouping-with-fingerprints) |
| `logger` | `logger` is the part of the application which is logging the event. In go this usually means setting it to the name of the package. |
| `http_request` | `http_request` is the in-coming request(*http.Request). The detailed request data are sent to Sentry. |
## Timeout
`Timeout` is the time the sentry hook will wait for a response
from the sentry server.
If this time elapses with no response from
the server an error will be returned.
If `Timeout` is set to 0 the SentryHook will not wait for a reply
and will assume a correct delivery.
The SentryHook has a default timeout of `100 milliseconds` when created
with a call to `NewSentryHook`. This can be changed by assigning a value to the `Timeout` field:
```go
hook, _ := logrus_sentry.NewSentryHook(...)
hook.Timeout = 20*time.Second
```
## Enabling Stacktraces
By default the hook will not send any stacktraces. However, this can be enabled
with:
```go
hook, _ := logrus_sentry.NewSentryHook(...)
hook.StacktraceConfiguration.Enable = true
```
Subsequent calls to `logger.Error` and above will create a stacktrace.
Other configuration options are:
- `StacktraceConfiguration.Level` the logrus level at which to start capturing stacktraces.
- `StacktraceConfiguration.Skip` how many stack frames to skip before stacktrace starts recording.
- `StacktraceConfiguration.Context` the number of lines to include around a stack frame for context.
- `StacktraceConfiguration.InAppPrefixes` the prefixes that will be matched against the stack frame to identify it as in_app
- `StacktraceConfiguration.IncludeErrorBreadcrumb` whether to create a breadcrumb with the full text of error

View File

@ -1,137 +0,0 @@
package logrus_sentry
import (
"net/http"
"github.com/getsentry/raven-go"
"github.com/sirupsen/logrus"
)
const (
fieldEventID = "event_id"
fieldFingerprint = "fingerprint"
fieldLogger = "logger"
fieldServerName = "server_name"
fieldTags = "tags"
fieldHTTPRequest = "http_request"
fieldUser = "user"
)
type dataField struct {
data logrus.Fields
omitList map[string]struct{}
}
func newDataField(data logrus.Fields) *dataField {
return &dataField{
data: data,
omitList: make(map[string]struct{}),
}
}
func (d *dataField) len() int {
return len(d.data)
}
func (d *dataField) isOmit(key string) bool {
_, ok := d.omitList[key]
return ok
}
func (d *dataField) getLogger() (string, bool) {
if logger, ok := d.data[fieldLogger].(string); ok {
d.omitList[fieldLogger] = struct{}{}
return logger, true
}
return "", false
}
func (d *dataField) getServerName() (string, bool) {
if serverName, ok := d.data[fieldServerName].(string); ok {
d.omitList[fieldServerName] = struct{}{}
return serverName, true
}
return "", false
}
func (d *dataField) getTags() (raven.Tags, bool) {
if tags, ok := d.data[fieldTags].(raven.Tags); ok {
d.omitList[fieldTags] = struct{}{}
return tags, true
}
return nil, false
}
func (d *dataField) getFingerprint() ([]string, bool) {
if fingerprint, ok := d.data[fieldFingerprint].([]string); ok {
d.omitList[fieldFingerprint] = struct{}{}
return fingerprint, true
}
return nil, false
}
func (d *dataField) getError() (error, bool) {
if err, ok := d.data[logrus.ErrorKey].(error); ok {
d.omitList[logrus.ErrorKey] = struct{}{}
return err, true
}
return nil, false
}
func (d *dataField) getHTTPRequest() (*raven.Http, bool) {
if req, ok := d.data[fieldHTTPRequest].(*http.Request); ok {
d.omitList[fieldHTTPRequest] = struct{}{}
return raven.NewHttp(req), true
}
if req, ok := d.data[fieldHTTPRequest].(*raven.Http); ok {
d.omitList[fieldHTTPRequest] = struct{}{}
return req, true
}
return nil, false
}
func (d *dataField) getEventID() (string, bool) {
eventID, ok := d.data[fieldEventID].(string)
if !ok {
return "", false
}
//verify eventID is 32 characters hexadecimal string (UUID4)
uuid := parseUUID(eventID)
if uuid == nil {
return "", false
}
d.omitList[fieldEventID] = struct{}{}
return uuid.noDashString(), true
}
func (d *dataField) getUser() (*raven.User, bool) {
data := d.data
if v, ok := data[fieldUser]; ok {
switch val := v.(type) {
case *raven.User:
d.omitList[fieldUser] = struct{}{}
return val, true
case raven.User:
d.omitList[fieldUser] = struct{}{}
return &val, true
}
}
username, _ := data["user_name"].(string)
email, _ := data["user_email"].(string)
id, _ := data["user_id"].(string)
ip, _ := data["user_ip"].(string)
if username == "" && email == "" && id == "" && ip == "" {
return nil, false
}
return &raven.User{
ID: id,
Username: username,
Email: email,
IP: ip,
}, true
}

View File

@ -1,424 +0,0 @@
package logrus_sentry
import (
"encoding/json"
"fmt"
"runtime"
"sync"
"time"
raven "github.com/getsentry/raven-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
var (
severityMap = map[logrus.Level]raven.Severity{
logrus.TraceLevel: raven.DEBUG,
logrus.DebugLevel: raven.DEBUG,
logrus.InfoLevel: raven.INFO,
logrus.WarnLevel: raven.WARNING,
logrus.ErrorLevel: raven.ERROR,
logrus.FatalLevel: raven.FATAL,
logrus.PanicLevel: raven.FATAL,
}
)
// SentryHook delivers logs to a sentry server.
type SentryHook struct {
// Timeout sets the time to wait for a delivery error from the sentry server.
// If this is set to zero the server will not wait for any response and will
// consider the message correctly sent.
//
// This is ignored for asynchronous hooks. If you want to set a timeout when
// using an async hook (to bound the length of time that hook.Flush can take),
// you probably want to create your own raven.Client and set
// ravenClient.Transport.(*raven.HTTPTransport).Client.Timeout to set a
// timeout on the underlying HTTP request instead.
Timeout time.Duration
StacktraceConfiguration StackTraceConfiguration
client *raven.Client
levels []logrus.Level
serverName string
ignoreFields map[string]struct{}
extraFilters map[string]func(interface{}) interface{}
errorHandlers []func(entry *logrus.Entry, err error)
asynchronous bool
mu sync.RWMutex
wg sync.WaitGroup
}
// The Stacktracer interface allows an error type to return a raven.Stacktrace.
type Stacktracer interface {
GetStacktrace() *raven.Stacktrace
}
type causer interface {
Cause() error
}
type pkgErrorStackTracer interface {
StackTrace() errors.StackTrace
}
// StackTraceConfiguration allows for configuring stacktraces
type StackTraceConfiguration struct {
// whether stacktraces should be enabled
Enable bool
// the level at which to start capturing stacktraces
Level logrus.Level
// how many stack frames to skip before stacktrace starts recording
Skip int
// the number of lines to include around a stack frame for context
Context int
// the prefixes that will be matched against the stack frame.
// if the stack frame's package matches one of these prefixes
// sentry will identify the stack frame as "in_app"
InAppPrefixes []string
// whether sending exception type should be enabled.
SendExceptionType bool
// whether the exception type and message should be switched.
SwitchExceptionTypeAndMessage bool
// whether to include a breadcrumb with the full error stack
IncludeErrorBreadcrumb bool
}
// NewSentryHook creates a hook to be added to an instance of logger
// and initializes the raven client.
// This method sets the timeout to 100 milliseconds.
func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
client, err := raven.New(DSN)
if err != nil {
return nil, err
}
return NewWithClientSentryHook(client, levels)
}
// NewWithTagsSentryHook creates a hook with tags to be added to an instance
// of logger and initializes the raven client. This method sets the timeout to
// 100 milliseconds.
func NewWithTagsSentryHook(DSN string, tags map[string]string, levels []logrus.Level) (*SentryHook, error) {
client, err := raven.NewWithTags(DSN, tags)
if err != nil {
return nil, err
}
return NewWithClientSentryHook(client, levels)
}
// NewWithClientSentryHook creates a hook using an initialized raven client.
// This method sets the timeout to 100 milliseconds.
func NewWithClientSentryHook(client *raven.Client, levels []logrus.Level) (*SentryHook, error) {
return &SentryHook{
Timeout: 100 * time.Millisecond,
StacktraceConfiguration: StackTraceConfiguration{
Enable: false,
Level: logrus.ErrorLevel,
Skip: 6,
Context: 0,
InAppPrefixes: nil,
SendExceptionType: true,
},
client: client,
levels: levels,
ignoreFields: make(map[string]struct{}),
extraFilters: make(map[string]func(interface{}) interface{}),
}, nil
}
// NewAsyncSentryHook creates a hook same as NewSentryHook, but in asynchronous
// mode.
func NewAsyncSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
hook, err := NewSentryHook(DSN, levels)
return setAsync(hook), err
}
// NewAsyncWithTagsSentryHook creates a hook same as NewWithTagsSentryHook, but
// in asynchronous mode.
func NewAsyncWithTagsSentryHook(DSN string, tags map[string]string, levels []logrus.Level) (*SentryHook, error) {
hook, err := NewWithTagsSentryHook(DSN, tags, levels)
return setAsync(hook), err
}
// NewAsyncWithClientSentryHook creates a hook same as NewWithClientSentryHook,
// but in asynchronous mode.
func NewAsyncWithClientSentryHook(client *raven.Client, levels []logrus.Level) (*SentryHook, error) {
hook, err := NewWithClientSentryHook(client, levels)
return setAsync(hook), err
}
func setAsync(hook *SentryHook) *SentryHook {
if hook == nil {
return nil
}
hook.asynchronous = true
return hook
}
// Fire is called when an event should be sent to sentry
// Special fields that sentry uses to give more information to the server
// are extracted from entry.Data (if they are found)
// These fields are: error, logger, server_name, http_request, tags
func (hook *SentryHook) Fire(entry *logrus.Entry) error {
hook.mu.RLock() // Allow multiple go routines to log simultaneously
defer hook.mu.RUnlock()
df := newDataField(entry.Data)
err, hasError := df.getError()
var crumbs *Breadcrumbs
if hasError && hook.StacktraceConfiguration.IncludeErrorBreadcrumb {
crumbs = &Breadcrumbs{
Values: []Value{{
Timestamp: int64(time.Now().Unix()),
Type: "error",
Message: fmt.Sprintf("%+v", err),
}},
}
}
packet := raven.NewPacketWithExtra(entry.Message, nil, crumbs)
packet.Timestamp = raven.Timestamp(entry.Time)
packet.Level = severityMap[entry.Level]
packet.Platform = "go"
// set special fields
if hook.serverName != "" {
packet.ServerName = hook.serverName
}
if logger, ok := df.getLogger(); ok {
packet.Logger = logger
}
if serverName, ok := df.getServerName(); ok {
packet.ServerName = serverName
}
if eventID, ok := df.getEventID(); ok {
packet.EventID = eventID
}
if tags, ok := df.getTags(); ok {
packet.Tags = tags
}
if fingerprint, ok := df.getFingerprint(); ok {
packet.Fingerprint = fingerprint
}
if req, ok := df.getHTTPRequest(); ok {
packet.Interfaces = append(packet.Interfaces, req)
}
if user, ok := df.getUser(); ok {
packet.Interfaces = append(packet.Interfaces, user)
}
// set stacktrace data
stConfig := &hook.StacktraceConfiguration
if stConfig.Enable && entry.Level <= stConfig.Level {
if err, ok := df.getError(); ok {
var currentStacktrace *raven.Stacktrace
currentStacktrace = hook.findStacktrace(err)
if currentStacktrace == nil {
currentStacktrace = raven.NewStacktrace(stConfig.Skip, stConfig.Context, stConfig.InAppPrefixes)
}
cause := errors.Cause(err)
if cause == nil {
cause = err
}
exc := raven.NewException(cause, currentStacktrace)
if !stConfig.SendExceptionType {
exc.Type = ""
}
if stConfig.SwitchExceptionTypeAndMessage {
packet.Interfaces = append(packet.Interfaces, currentStacktrace)
packet.Culprit = exc.Type + ": " + currentStacktrace.Culprit()
} else {
packet.Interfaces = append(packet.Interfaces, exc)
packet.Culprit = err.Error()
}
} else {
currentStacktrace := raven.NewStacktrace(stConfig.Skip, stConfig.Context, stConfig.InAppPrefixes)
if currentStacktrace != nil {
packet.Interfaces = append(packet.Interfaces, currentStacktrace)
}
}
} else {
// set the culprit even when the stack trace is disabled, as long as we have an error
if err, ok := df.getError(); ok {
packet.Culprit = err.Error()
}
}
// set other fields
dataExtra := hook.formatExtraData(df)
if packet.Extra == nil {
packet.Extra = dataExtra
} else {
for k, v := range dataExtra {
packet.Extra[k] = v
}
}
_, errCh := hook.client.Capture(packet, nil)
switch {
case hook.asynchronous:
// Our use of hook.mu guarantees that we are following the WaitGroup rule of
// not calling Add in parallel with Wait.
hook.wg.Add(1)
go func() {
if err := <-errCh; err != nil {
for _, handlerFn := range hook.errorHandlers {
handlerFn(entry, err)
}
}
hook.wg.Done()
}()
return nil
case hook.Timeout == 0:
return nil
default:
timeout := hook.Timeout
timeoutCh := time.After(timeout)
select {
case err := <-errCh:
for _, handlerFn := range hook.errorHandlers {
handlerFn(entry, err)
}
return err
case <-timeoutCh:
return fmt.Errorf("no response from sentry server in %s", timeout)
}
}
}
// Flush waits for the log queue to empty. This function only does anything in
// asynchronous mode.
func (hook *SentryHook) Flush() {
if !hook.asynchronous {
return
}
hook.mu.Lock() // Claim exclusive access; any logging goroutines will block until the flush completes
defer hook.mu.Unlock()
hook.wg.Wait()
}
func (hook *SentryHook) findStacktrace(err error) *raven.Stacktrace {
var stacktrace *raven.Stacktrace
var stackErr errors.StackTrace
for err != nil {
// Find the earliest *raven.Stacktrace, or error.StackTrace
if tracer, ok := err.(Stacktracer); ok {
stacktrace = tracer.GetStacktrace()
stackErr = nil
} else if tracer, ok := err.(pkgErrorStackTracer); ok {
stacktrace = nil
stackErr = tracer.StackTrace()
}
if cause, ok := err.(causer); ok {
err = cause.Cause()
} else {
break
}
}
if stackErr != nil {
stacktrace = hook.convertStackTrace(stackErr)
}
return stacktrace
}
// convertStackTrace converts an errors.StackTrace into a natively consumable
// *raven.Stacktrace
func (hook *SentryHook) convertStackTrace(st errors.StackTrace) *raven.Stacktrace {
stConfig := &hook.StacktraceConfiguration
stFrames := []errors.Frame(st)
frames := make([]*raven.StacktraceFrame, 0, len(stFrames))
for i := range stFrames {
pc := uintptr(stFrames[i])
fn := runtime.FuncForPC(pc)
file, line := fn.FileLine(pc)
frame := raven.NewStacktraceFrame(pc, fn.Name(), file, line, stConfig.Context, stConfig.InAppPrefixes)
if frame != nil {
frames = append(frames, frame)
}
}
// Sentry wants the frames with the oldest first, so reverse them
for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 {
frames[i], frames[j] = frames[j], frames[i]
}
return &raven.Stacktrace{Frames: frames}
}
// Levels returns the available logging levels.
func (hook *SentryHook) Levels() []logrus.Level {
return hook.levels
}
// AddIgnore adds field name to ignore.
func (hook *SentryHook) AddIgnore(name string) {
hook.ignoreFields[name] = struct{}{}
}
// AddExtraFilter adds a custom filter function.
func (hook *SentryHook) AddExtraFilter(name string, fn func(interface{}) interface{}) {
hook.extraFilters[name] = fn
}
// AddErrorHandler adds a error handler function used when Sentry returns error.
func (hook *SentryHook) AddErrorHandler(fn func(entry *logrus.Entry, err error)) {
hook.errorHandlers = append(hook.errorHandlers, fn)
}
func (hook *SentryHook) formatExtraData(df *dataField) (result map[string]interface{}) {
// create a map for passing to Sentry's extra data
result = make(map[string]interface{}, df.len())
for k, v := range df.data {
if df.isOmit(k) {
continue // skip already used special fields
}
if _, ok := hook.ignoreFields[k]; ok {
continue
}
if fn, ok := hook.extraFilters[k]; ok {
v = fn(v) // apply custom filter
} else {
v = formatData(v) // use default formatter
}
result[k] = v
}
return result
}
// formatData returns value as a suitable format.
func formatData(value interface{}) (formatted interface{}) {
switch value := value.(type) {
case json.Marshaler:
return value
case error:
return value.Error()
case fmt.Stringer:
return value.String()
default:
return value
}
}
// utility classes for breadcrumb support
type Breadcrumbs struct {
Values []Value `json:"values"`
}
type Value struct {
Timestamp int64 `json:"timestamp"`
Type string `json:"type"`
Message string `json:"message"`
Category string `json:"category"`
Level string `json:"string"`
Data interface{} `json:"data"`
}
func (b *Breadcrumbs) Class() string {
return "breadcrumbs"
}

View File

@ -1,55 +0,0 @@
package logrus_sentry
import (
"github.com/getsentry/raven-go"
)
// SetDefaultLoggerName sets default logger name tag.
func (hook *SentryHook) SetDefaultLoggerName(name string) {
hook.client.SetDefaultLoggerName(name)
}
// SetEnvironment sets environment tag.
func (hook *SentryHook) SetEnvironment(environment string) {
hook.client.SetEnvironment(environment)
}
// SetHttpContext sets http client.
func (hook *SentryHook) SetHttpContext(h *raven.Http) {
hook.client.SetHttpContext(h)
}
// SetIgnoreErrors sets ignoreErrorsRegexp.
func (hook *SentryHook) SetIgnoreErrors(errs ...string) error {
return hook.client.SetIgnoreErrors(errs)
}
// SetIncludePaths sets includePaths.
func (hook *SentryHook) SetIncludePaths(p []string) {
hook.client.SetIncludePaths(p)
}
// SetRelease sets release tag.
func (hook *SentryHook) SetRelease(release string) {
hook.client.SetRelease(release)
}
// SetSampleRate sets sampling rate.
func (hook *SentryHook) SetSampleRate(rate float32) error {
return hook.client.SetSampleRate(rate)
}
// SetTagsContext sets tags.
func (hook *SentryHook) SetTagsContext(t map[string]string) {
hook.client.SetTagsContext(t)
}
// SetUserContext sets user.
func (hook *SentryHook) SetUserContext(u *raven.User) {
hook.client.SetUserContext(u)
}
// SetServerName sets server_name tag.
func (hook *SentryHook) SetServerName(serverName string) {
hook.serverName = serverName
}

View File

@ -1,135 +0,0 @@
package logrus_sentry
import (
"fmt"
"strings"
)
/*
Copyright (c) 2009,2014 Google Inc. 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.
*/
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
// 4122.
type uuid []byte
// parseUUID decodes s into a UUID or returns nil. Both the UUID form of
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx and
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded.
func parseUUID(s string) uuid {
//If it is in no dash format "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
if len(s) == 32 {
uuid := make([]byte, 16)
for i, x := range []int{
0, 2, 4, 6, 8, 10,
12, 14, 16, 18, 20,
22, 24, 26, 28, 30} {
if v, ok := xtob(s[x:]); !ok {
return nil
} else {
uuid[i] = v
}
}
return uuid
}
if len(s) == 36+9 {
if strings.ToLower(s[:9]) != "urn:uuid:" {
return nil
}
s = s[9:]
} else if len(s) != 36 {
return nil
}
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return nil
}
uuid := make([]byte, 16)
for i, x := range []int{
0, 2, 4, 6,
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34} {
if v, ok := xtob(s[x:]); !ok {
return nil
} else {
uuid[i] = v
}
}
return uuid
}
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// , or "" if uuid is invalid.
func (uuid uuid) string() string {
if uuid == nil || len(uuid) != 16 {
return ""
}
b := []byte(uuid)
return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
b[:4], b[4:6], b[6:8], b[8:10], b[10:])
}
func (uuid uuid) noDashString() string {
if uuid == nil || len(uuid) != 16 {
return ""
}
b := []byte(uuid)
return fmt.Sprintf("%08x%04x%04x%04x%012x",
b[:4], b[4:6], b[6:8], b[8:10], b[10:])
}
// xvalues returns the value of a byte as a hexadecimal digit or 255.
var xvalues = []byte{
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
}
// xtob converts the the first two hex bytes of x into a byte.
func xtob(x string) (byte, bool) {
b1 := xvalues[x[0]]
b2 := xvalues[x[1]]
return (b1 << 4) | b2, b1 != 255 && b2 != 255
}

View File

@ -1 +0,0 @@
.git

View File

@ -1,5 +0,0 @@
*.test
*.out
example/example
/xunit.xml
/coverage.xml

View File

View File

@ -1,41 +0,0 @@
sudo: false
language: go
go:
- 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- 1.11.x
- tip
before_install:
- go install -race std
- go get golang.org/x/tools/cmd/cover
- go get github.com/tebeka/go2xunit
- go get github.com/t-yuki/gocover-cobertura
- go get -v ./...
script:
- go test -v -race ./... | tee gotest.out
- $GOPATH/bin/go2xunit -fail -input gotest.out -output xunit.xml
- go test -v -coverprofile=coverage.txt -covermode count .
- $GOPATH/bin/gocover-cobertura < coverage.txt > coverage.xml
after_script:
- npm install -g @zeus-ci/cli
- zeus upload -t "application/x-cobertura+xml" coverage.xml
- zeus upload -t "application/x-xunit+xml" xunit.xml
matrix:
allow_failures:
- go: tip
notifications:
webhooks:
urls:
- https://zeus.ci/hooks/cd949996-d30a-11e8-ba53-0a580a28042d/public/provider/travis/webhook
on_success: always
on_failure: always
on_start: always
on_cancel: always
on_error: always

View File

@ -1,13 +0,0 @@
FROM golang:1.7
RUN mkdir -p /go/src/github.com/getsentry/raven-go
WORKDIR /go/src/github.com/getsentry/raven-go
ENV GOPATH /go
RUN go install -race std && go get golang.org/x/tools/cmd/cover
COPY . /go/src/github.com/getsentry/raven-go
RUN go get -v ./...
CMD ["./runtests.sh"]

View File

@ -1,28 +0,0 @@
Copyright (c) 2013 Apollic Software, LLC. All rights reserved.
Copyright (c) 2015 Functional Software, Inc. 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 Apollic Software, LLC 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.

View File

@ -1,19 +0,0 @@
# raven
[![Build Status](https://api.travis-ci.org/getsentry/raven-go.svg?branch=master)](https://travis-ci.org/getsentry/raven-go)
[![Go Report Card](https://goreportcard.com/badge/github.com/getsentry/raven-go)](https://goreportcard.com/report/github.com/getsentry/raven-go)
[![GoDoc](https://godoc.org/github.com/getsentry/raven-go?status.svg)](https://godoc.org/github.com/getsentry/raven-go)
raven is the official Go SDK for the [Sentry](https://github.com/getsentry/sentry)
event/error logging system.
- [**API Documentation**](https://godoc.org/github.com/getsentry/raven-go)
- [**Usage and Examples**](https://docs.sentry.io/clients/go/)
## Installation
```text
go get github.com/getsentry/raven-go
```
Note: Go 1.7 and newer are supported.

View File

@ -1,977 +0,0 @@
// Package raven implements a client for the Sentry error logging service.
package raven
import (
"bytes"
"compress/zlib"
"crypto/rand"
"crypto/tls"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
mrand "math/rand"
"net/http"
"net/url"
"os"
"regexp"
"runtime"
"strings"
"sync"
"time"
"github.com/certifi/gocertifi"
pkgErrors "github.com/pkg/errors"
)
const (
userAgent = "raven-go/1.0"
timestampFormat = `"2006-01-02T15:04:05.00"`
)
var (
ErrPacketDropped = errors.New("raven: packet dropped")
ErrUnableToUnmarshalJSON = errors.New("raven: unable to unmarshal JSON")
ErrMissingUser = errors.New("raven: dsn missing public key and/or password")
ErrMissingProjectID = errors.New("raven: dsn missing project id")
ErrInvalidSampleRate = errors.New("raven: sample rate should be between 0 and 1")
)
type Severity string
// http://docs.python.org/2/howto/logging.html#logging-levels
const (
DEBUG = Severity("debug")
INFO = Severity("info")
WARNING = Severity("warning")
ERROR = Severity("error")
FATAL = Severity("fatal")
)
type Timestamp time.Time
func (t Timestamp) MarshalJSON() ([]byte, error) {
return []byte(time.Time(t).UTC().Format(timestampFormat)), nil
}
func (timestamp *Timestamp) UnmarshalJSON(data []byte) error {
t, err := time.Parse(timestampFormat, string(data))
if err != nil {
return err
}
*timestamp = Timestamp(t)
return nil
}
func (timestamp Timestamp) Format(format string) string {
t := time.Time(timestamp)
return t.Format(format)
}
// An Interface is a Sentry interface that will be serialized as JSON.
// It must implement json.Marshaler or use json struct tags.
type Interface interface {
// The Sentry class name. Example: sentry.interfaces.Stacktrace
Class() string
}
type Culpriter interface {
Culprit() string
}
type Transport interface {
Send(url, authHeader string, packet *Packet) error
}
type Extra map[string]interface{}
type outgoingPacket struct {
packet *Packet
ch chan error
}
type Tag struct {
Key string
Value string
}
type Tags []Tag
func (tag *Tag) MarshalJSON() ([]byte, error) {
return json.Marshal([2]string{tag.Key, tag.Value})
}
func (t *Tag) UnmarshalJSON(data []byte) error {
var tag [2]string
if err := json.Unmarshal(data, &tag); err != nil {
return err
}
*t = Tag{tag[0], tag[1]}
return nil
}
func (t *Tags) UnmarshalJSON(data []byte) error {
var tags []Tag
switch data[0] {
case '[':
// Unmarshal into []Tag
if err := json.Unmarshal(data, &tags); err != nil {
return err
}
case '{':
// Unmarshal into map[string]string
tagMap := make(map[string]string)
if err := json.Unmarshal(data, &tagMap); err != nil {
return err
}
// Convert to []Tag
for k, v := range tagMap {
tags = append(tags, Tag{k, v})
}
default:
return ErrUnableToUnmarshalJSON
}
*t = tags
return nil
}
// https://docs.getsentry.com/hosted/clientdev/#building-the-json-packet
type Packet struct {
// Required
Message string `json:"message"`
// Required, set automatically by Client.Send/Report via Packet.Init if blank
EventID string `json:"event_id"`
Project string `json:"project"`
Timestamp Timestamp `json:"timestamp"`
Level Severity `json:"level"`
Logger string `json:"logger"`
// Optional
Platform string `json:"platform,omitempty"`
Culprit string `json:"culprit,omitempty"`
ServerName string `json:"server_name,omitempty"`
Release string `json:"release,omitempty"`
Environment string `json:"environment,omitempty"`
Tags Tags `json:"tags,omitempty"`
Modules map[string]string `json:"modules,omitempty"`
Fingerprint []string `json:"fingerprint,omitempty"`
Extra Extra `json:"extra,omitempty"`
Interfaces []Interface `json:"-"`
}
// NewPacket constructs a packet with the specified message and interfaces.
func NewPacket(message string, interfaces ...Interface) *Packet {
extra := Extra{}
setExtraDefaults(extra)
return &Packet{
Message: message,
Interfaces: interfaces,
Extra: extra,
}
}
// NewPacketWithExtra constructs a packet with the specified message, extra information, and interfaces.
func NewPacketWithExtra(message string, extra Extra, interfaces ...Interface) *Packet {
if extra == nil {
extra = Extra{}
}
setExtraDefaults(extra)
return &Packet{
Message: message,
Interfaces: interfaces,
Extra: extra,
}
}
func setExtraDefaults(extra Extra) Extra {
extra["runtime.Version"] = runtime.Version()
extra["runtime.NumCPU"] = runtime.NumCPU()
extra["runtime.GOMAXPROCS"] = runtime.GOMAXPROCS(0) // 0 just returns the current value
extra["runtime.NumGoroutine"] = runtime.NumGoroutine()
return extra
}
// Init initializes required fields in a packet. It is typically called by
// Client.Send/Report automatically.
func (packet *Packet) Init(project string) error {
if packet.Project == "" {
packet.Project = project
}
if packet.EventID == "" {
var err error
packet.EventID, err = uuid()
if err != nil {
return err
}
}
if time.Time(packet.Timestamp).IsZero() {
packet.Timestamp = Timestamp(time.Now())
}
if packet.Level == "" {
packet.Level = ERROR
}
if packet.Logger == "" {
packet.Logger = "root"
}
if packet.ServerName == "" {
packet.ServerName = hostname
}
if packet.Platform == "" {
packet.Platform = "go"
}
if packet.Culprit == "" {
for _, inter := range packet.Interfaces {
if c, ok := inter.(Culpriter); ok {
packet.Culprit = c.Culprit()
if packet.Culprit != "" {
break
}
}
}
}
return nil
}
func (packet *Packet) AddTags(tags map[string]string) {
for k, v := range tags {
packet.Tags = append(packet.Tags, Tag{k, v})
}
}
func uuid() (string, error) {
id := make([]byte, 16)
_, err := io.ReadFull(rand.Reader, id)
if err != nil {
return "", err
}
id[6] &= 0x0F // clear version
id[6] |= 0x40 // set version to 4 (random uuid)
id[8] &= 0x3F // clear variant
id[8] |= 0x80 // set to IETF variant
return hex.EncodeToString(id), nil
}
func (packet *Packet) JSON() ([]byte, error) {
packetJSON, err := json.Marshal(packet)
if err != nil {
return nil, err
}
interfaces := make(map[string]Interface, len(packet.Interfaces))
for _, inter := range packet.Interfaces {
if inter != nil {
interfaces[inter.Class()] = inter
}
}
if len(interfaces) > 0 {
interfaceJSON, err := json.Marshal(interfaces)
if err != nil {
return nil, err
}
packetJSON[len(packetJSON)-1] = ','
packetJSON = append(packetJSON, interfaceJSON[1:]...)
}
return packetJSON, nil
}
type context struct {
user *User
http *Http
tags map[string]string
}
func (c *context) setUser(u *User) { c.user = u }
func (c *context) setHttp(h *Http) { c.http = h }
func (c *context) setTags(t map[string]string) {
if c.tags == nil {
c.tags = make(map[string]string)
}
for k, v := range t {
c.tags[k] = v
}
}
func (c *context) clear() {
c.user = nil
c.http = nil
c.tags = nil
}
// Return a list of interfaces to be used in appending with the rest
func (c *context) interfaces() []Interface {
len, i := 0, 0
if c.user != nil {
len++
}
if c.http != nil {
len++
}
interfaces := make([]Interface, len)
if c.user != nil {
interfaces[i] = c.user
i++
}
if c.http != nil {
interfaces[i] = c.http
i++
}
return interfaces
}
// The maximum number of packets that will be buffered waiting to be delivered.
// Packets will be dropped if the buffer is full. Used by NewClient.
var MaxQueueBuffer = 100
func newTransport() Transport {
t := &HTTPTransport{}
rootCAs, err := gocertifi.CACerts()
if err != nil {
log.Println("raven: failed to load root TLS certificates:", err)
} else {
t.Client = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{RootCAs: rootCAs},
},
}
}
return t
}
func newClient(tags map[string]string) *Client {
client := &Client{
Transport: newTransport(),
Tags: tags,
context: &context{},
sampleRate: 1.0,
queue: make(chan *outgoingPacket, MaxQueueBuffer),
}
client.SetDSN(os.Getenv("SENTRY_DSN"))
client.SetRelease(os.Getenv("SENTRY_RELEASE"))
client.SetEnvironment(os.Getenv("SENTRY_ENVIRONMENT"))
return client
}
// New constructs a new Sentry client instance
func New(dsn string) (*Client, error) {
client := newClient(nil)
return client, client.SetDSN(dsn)
}
// NewWithTags constructs a new Sentry client instance with default tags.
func NewWithTags(dsn string, tags map[string]string) (*Client, error) {
client := newClient(tags)
return client, client.SetDSN(dsn)
}
// NewClient constructs a Sentry client and spawns a background goroutine to
// handle packets sent by Client.Report.
//
// Deprecated: use New and NewWithTags instead
func NewClient(dsn string, tags map[string]string) (*Client, error) {
client := newClient(tags)
return client, client.SetDSN(dsn)
}
// Client encapsulates a connection to a Sentry server. It must be initialized
// by calling NewClient. Modification of fields concurrently with Send or after
// calling Report for the first time is not thread-safe.
type Client struct {
Tags map[string]string
Transport Transport
// DropHandler is called when a packet is dropped because the buffer is full.
DropHandler func(*Packet)
// Context that will get appending to all packets
context *context
mu sync.RWMutex
url string
projectID string
authHeader string
release string
environment string
sampleRate float32
// default logger name (leave empty for 'root')
defaultLoggerName string
includePaths []string
ignoreErrorsRegexp *regexp.Regexp
queue chan *outgoingPacket
// A WaitGroup to keep track of all currently in-progress captures
// This is intended to be used with Client.Wait() to assure that
// all messages have been transported before exiting the process.
wg sync.WaitGroup
// A Once to track only starting up the background worker once
start sync.Once
}
// Initialize a default *Client instance
var DefaultClient = newClient(nil)
func (c *Client) SetIgnoreErrors(errs []string) error {
joinedRegexp := strings.Join(errs, "|")
r, err := regexp.Compile(joinedRegexp)
if err != nil {
return fmt.Errorf("failed to compile regexp %q for %q: %v", joinedRegexp, errs, err)
}
c.mu.Lock()
c.ignoreErrorsRegexp = r
c.mu.Unlock()
return nil
}
func (c *Client) shouldExcludeErr(errStr string) bool {
c.mu.RLock()
defer c.mu.RUnlock()
return c.ignoreErrorsRegexp != nil && c.ignoreErrorsRegexp.MatchString(errStr)
}
func SetIgnoreErrors(errs ...string) error {
return DefaultClient.SetIgnoreErrors(errs)
}
// SetDSN updates a client with a new DSN. It safe to call after and
// concurrently with calls to Report and Send.
func (client *Client) SetDSN(dsn string) error {
if dsn == "" {
return nil
}
client.mu.Lock()
defer client.mu.Unlock()
uri, err := url.Parse(dsn)
if err != nil {
return err
}
if uri.User == nil {
return ErrMissingUser
}
publicKey := uri.User.Username()
secretKey, hasSecretKey := uri.User.Password()
uri.User = nil
if idx := strings.LastIndex(uri.Path, "/"); idx != -1 {
client.projectID = uri.Path[idx+1:]
uri.Path = uri.Path[:idx+1] + "api/" + client.projectID + "/store/"
}
if client.projectID == "" {
return ErrMissingProjectID
}
client.url = uri.String()
if hasSecretKey {
client.authHeader = fmt.Sprintf("Sentry sentry_version=4, sentry_key=%s, sentry_secret=%s", publicKey, secretKey)
} else {
client.authHeader = fmt.Sprintf("Sentry sentry_version=4, sentry_key=%s", publicKey)
}
return nil
}
// Sets the DSN for the default *Client instance
func SetDSN(dsn string) error { return DefaultClient.SetDSN(dsn) }
// SetRelease sets the "release" tag.
func (client *Client) SetRelease(release string) {
client.mu.Lock()
defer client.mu.Unlock()
client.release = release
}
// SetEnvironment sets the "environment" tag.
func (client *Client) SetEnvironment(environment string) {
client.mu.Lock()
defer client.mu.Unlock()
client.environment = environment
}
// SetDefaultLoggerName sets the default logger name.
func (client *Client) SetDefaultLoggerName(name string) {
client.mu.Lock()
defer client.mu.Unlock()
client.defaultLoggerName = name
}
// SetSampleRate sets how much sampling we want on client side
func (client *Client) SetSampleRate(rate float32) error {
client.mu.Lock()
defer client.mu.Unlock()
if rate < 0 || rate > 1 {
return ErrInvalidSampleRate
}
client.sampleRate = rate
return nil
}
// SetRelease sets the "release" tag on the default *Client
func SetRelease(release string) { DefaultClient.SetRelease(release) }
// SetEnvironment sets the "environment" tag on the default *Client
func SetEnvironment(environment string) { DefaultClient.SetEnvironment(environment) }
// SetDefaultLoggerName sets the "defaultLoggerName" on the default *Client
func SetDefaultLoggerName(name string) {
DefaultClient.SetDefaultLoggerName(name)
}
// SetSampleRate sets the "sample rate" on the degault *Client
func SetSampleRate(rate float32) error { return DefaultClient.SetSampleRate(rate) }
func (client *Client) worker() {
for outgoingPacket := range client.queue {
client.mu.RLock()
url, authHeader := client.url, client.authHeader
client.mu.RUnlock()
outgoingPacket.ch <- client.Transport.Send(url, authHeader, outgoingPacket.packet)
client.wg.Done()
}
}
// Capture asynchronously delivers a packet to the Sentry server. It is a no-op
// when client is nil. A channel is provided if it is important to check for a
// send's success.
func (client *Client) Capture(packet *Packet, captureTags map[string]string) (eventID string, ch chan error) {
ch = make(chan error, 1)
if client == nil {
// return a chan that always returns nil when the caller receives from it
close(ch)
return
}
if client.sampleRate < 1.0 && mrand.Float32() > client.sampleRate {
return
}
if packet == nil {
close(ch)
return
}
if client.shouldExcludeErr(packet.Message) {
return
}
// Keep track of all running Captures so that we can wait for them all to finish
// *Must* call client.wg.Done() on any path that indicates that an event was
// finished being acted upon, whether success or failure
client.wg.Add(1)
// Merge capture tags and client tags
packet.AddTags(captureTags)
packet.AddTags(client.Tags)
// Initialize any required packet fields
client.mu.RLock()
packet.AddTags(client.context.tags)
projectID := client.projectID
release := client.release
environment := client.environment
defaultLoggerName := client.defaultLoggerName
client.mu.RUnlock()
// set the global logger name on the packet if we must
if packet.Logger == "" && defaultLoggerName != "" {
packet.Logger = defaultLoggerName
}
err := packet.Init(projectID)
if err != nil {
ch <- err
client.wg.Done()
return
}
if packet.Release == "" {
packet.Release = release
}
if packet.Environment == "" {
packet.Environment = environment
}
outgoingPacket := &outgoingPacket{packet, ch}
// Lazily start background worker until we
// do our first write into the queue.
client.start.Do(func() {
go client.worker()
})
select {
case client.queue <- outgoingPacket:
default:
// Send would block, drop the packet
if client.DropHandler != nil {
client.DropHandler(packet)
}
ch <- ErrPacketDropped
client.wg.Done()
}
return packet.EventID, ch
}
// Capture asynchronously delivers a packet to the Sentry server with the default *Client.
// It is a no-op when client is nil. A channel is provided if it is important to check for a
// send's success.
func Capture(packet *Packet, captureTags map[string]string) (eventID string, ch chan error) {
return DefaultClient.Capture(packet, captureTags)
}
// CaptureMessage formats and delivers a string message to the Sentry server.
func (client *Client) CaptureMessage(message string, tags map[string]string, interfaces ...Interface) string {
if client == nil {
return ""
}
if client.shouldExcludeErr(message) {
return ""
}
packet := NewPacket(message, append(append(interfaces, client.context.interfaces()...), &Message{message, nil})...)
eventID, _ := client.Capture(packet, tags)
return eventID
}
// CaptureMessage formats and delivers a string message to the Sentry server with the default *Client
func CaptureMessage(message string, tags map[string]string, interfaces ...Interface) string {
return DefaultClient.CaptureMessage(message, tags, interfaces...)
}
// CaptureMessageAndWait is identical to CaptureMessage except it blocks and waits for the message to be sent.
func (client *Client) CaptureMessageAndWait(message string, tags map[string]string, interfaces ...Interface) string {
if client == nil {
return ""
}
if client.shouldExcludeErr(message) {
return ""
}
packet := NewPacket(message, append(append(interfaces, client.context.interfaces()...), &Message{message, nil})...)
eventID, ch := client.Capture(packet, tags)
if eventID != "" {
<-ch
}
return eventID
}
// CaptureMessageAndWait is identical to CaptureMessage except it blocks and waits for the message to be sent.
func CaptureMessageAndWait(message string, tags map[string]string, interfaces ...Interface) string {
return DefaultClient.CaptureMessageAndWait(message, tags, interfaces...)
}
// CaptureErrors formats and delivers an error to the Sentry server.
// Adds a stacktrace to the packet, excluding the call to this method.
func (client *Client) CaptureError(err error, tags map[string]string, interfaces ...Interface) string {
if client == nil {
return ""
}
if err == nil {
return ""
}
if client.shouldExcludeErr(err.Error()) {
return ""
}
extra := extractExtra(err)
cause := pkgErrors.Cause(err)
packet := NewPacketWithExtra(err.Error(), extra, append(append(interfaces, client.context.interfaces()...), NewException(cause, GetOrNewStacktrace(cause, 1, 3, client.includePaths)))...)
eventID, _ := client.Capture(packet, tags)
return eventID
}
// CaptureErrors formats and delivers an error to the Sentry server using the default *Client.
// Adds a stacktrace to the packet, excluding the call to this method.
func CaptureError(err error, tags map[string]string, interfaces ...Interface) string {
return DefaultClient.CaptureError(err, tags, interfaces...)
}
// CaptureErrorAndWait is identical to CaptureError, except it blocks and assures that the event was sent
func (client *Client) CaptureErrorAndWait(err error, tags map[string]string, interfaces ...Interface) string {
if client == nil {
return ""
}
if client.shouldExcludeErr(err.Error()) {
return ""
}
extra := extractExtra(err)
cause := pkgErrors.Cause(err)
packet := NewPacketWithExtra(err.Error(), extra, append(append(interfaces, client.context.interfaces()...), NewException(cause, GetOrNewStacktrace(cause, 1, 3, client.includePaths)))...)
eventID, ch := client.Capture(packet, tags)
if eventID != "" {
<-ch
}
return eventID
}
// CaptureErrorAndWait is identical to CaptureError, except it blocks and assures that the event was sent
func CaptureErrorAndWait(err error, tags map[string]string, interfaces ...Interface) string {
return DefaultClient.CaptureErrorAndWait(err, tags, interfaces...)
}
// CapturePanic calls f and then recovers and reports a panic to the Sentry server if it occurs.
// If an error is captured, both the error and the reported Sentry error ID are returned.
func (client *Client) CapturePanic(f func(), tags map[string]string, interfaces ...Interface) (err interface{}, errorID string) {
// Note: This doesn't need to check for client, because we still want to go through the defer/recover path
// Down the line, Capture will be noop'd, so while this does a _tiny_ bit of overhead constructing the
// *Packet just to be thrown away, this should not be the normal case. Could be refactored to
// be completely noop though if we cared.
defer func() {
var packet *Packet
err = recover()
switch rval := err.(type) {
case nil:
return
case error:
if client.shouldExcludeErr(rval.Error()) {
return
}
packet = NewPacket(rval.Error(), append(append(interfaces, client.context.interfaces()...), NewException(rval, NewStacktrace(2, 3, client.includePaths)))...)
default:
rvalStr := fmt.Sprint(rval)
if client.shouldExcludeErr(rvalStr) {
return
}
packet = NewPacket(rvalStr, append(append(interfaces, client.context.interfaces()...), NewException(errors.New(rvalStr), NewStacktrace(2, 3, client.includePaths)))...)
}
errorID, _ = client.Capture(packet, tags)
}()
f()
return
}
// CapturePanic calls f and then recovers and reports a panic to the Sentry server if it occurs.
// If an error is captured, both the error and the reported Sentry error ID are returned.
func CapturePanic(f func(), tags map[string]string, interfaces ...Interface) (interface{}, string) {
return DefaultClient.CapturePanic(f, tags, interfaces...)
}
// CapturePanicAndWait is identical to CaptureError, except it blocks and assures that the event was sent
func (client *Client) CapturePanicAndWait(f func(), tags map[string]string, interfaces ...Interface) (err interface{}, errorID string) {
// Note: This doesn't need to check for client, because we still want to go through the defer/recover path
// Down the line, Capture will be noop'd, so while this does a _tiny_ bit of overhead constructing the
// *Packet just to be thrown away, this should not be the normal case. Could be refactored to
// be completely noop though if we cared.
defer func() {
var packet *Packet
err = recover()
switch rval := err.(type) {
case nil:
return
case error:
if client.shouldExcludeErr(rval.Error()) {
return
}
packet = NewPacket(rval.Error(), append(append(interfaces, client.context.interfaces()...), NewException(rval, NewStacktrace(2, 3, client.includePaths)))...)
default:
rvalStr := fmt.Sprint(rval)
if client.shouldExcludeErr(rvalStr) {
return
}
packet = NewPacket(rvalStr, append(append(interfaces, client.context.interfaces()...), NewException(errors.New(rvalStr), NewStacktrace(2, 3, client.includePaths)))...)
}
var ch chan error
errorID, ch = client.Capture(packet, tags)
if errorID != "" {
<-ch
}
}()
f()
return
}
// CapturePanicAndWait is identical to CaptureError, except it blocks and assures that the event was sent
func CapturePanicAndWait(f func(), tags map[string]string, interfaces ...Interface) (interface{}, string) {
return DefaultClient.CapturePanicAndWait(f, tags, interfaces...)
}
func (client *Client) Close() {
close(client.queue)
}
func Close() { DefaultClient.Close() }
// Wait blocks and waits for all events to finish being sent to Sentry server
func (client *Client) Wait() {
client.wg.Wait()
}
// Wait blocks and waits for all events to finish being sent to Sentry server
func Wait() { DefaultClient.Wait() }
func (client *Client) URL() string {
client.mu.RLock()
defer client.mu.RUnlock()
return client.url
}
func URL() string { return DefaultClient.URL() }
func (client *Client) ProjectID() string {
client.mu.RLock()
defer client.mu.RUnlock()
return client.projectID
}
func ProjectID() string { return DefaultClient.ProjectID() }
func (client *Client) Release() string {
client.mu.RLock()
defer client.mu.RUnlock()
return client.release
}
func Release() string { return DefaultClient.Release() }
func IncludePaths() []string { return DefaultClient.IncludePaths() }
func (client *Client) IncludePaths() []string {
client.mu.RLock()
defer client.mu.RUnlock()
return client.includePaths
}
func SetIncludePaths(p []string) { DefaultClient.SetIncludePaths(p) }
func (client *Client) SetIncludePaths(p []string) {
client.mu.Lock()
defer client.mu.Unlock()
client.includePaths = p
}
func (c *Client) SetUserContext(u *User) {
c.mu.Lock()
defer c.mu.Unlock()
c.context.setUser(u)
}
func (c *Client) SetHttpContext(h *Http) {
c.mu.Lock()
defer c.mu.Unlock()
c.context.setHttp(h)
}
func (c *Client) SetTagsContext(t map[string]string) {
c.mu.Lock()
defer c.mu.Unlock()
c.context.setTags(t)
}
func (c *Client) ClearContext() {
c.mu.Lock()
defer c.mu.Unlock()
c.context.clear()
}
func SetUserContext(u *User) { DefaultClient.SetUserContext(u) }
func SetHttpContext(h *Http) { DefaultClient.SetHttpContext(h) }
func SetTagsContext(t map[string]string) { DefaultClient.SetTagsContext(t) }
func ClearContext() { DefaultClient.ClearContext() }
// HTTPTransport is the default transport, delivering packets to Sentry via the
// HTTP API.
type HTTPTransport struct {
*http.Client
}
func (t *HTTPTransport) Send(url, authHeader string, packet *Packet) error {
if url == "" {
return nil
}
body, contentType, err := serializedPacket(packet)
if err != nil {
return fmt.Errorf("error serializing packet: %v", err)
}
req, err := http.NewRequest("POST", url, body)
if err != nil {
return fmt.Errorf("can't create new request: %v", err)
}
req.Header.Set("X-Sentry-Auth", authHeader)
req.Header.Set("User-Agent", userAgent)
req.Header.Set("Content-Type", contentType)
res, err := t.Do(req)
if err != nil {
return err
}
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()
if res.StatusCode != 200 {
return fmt.Errorf("raven: got http status %d - x-sentry-error: %s", res.StatusCode, res.Header.Get("X-Sentry-Error"))
}
return nil
}
func serializedPacket(packet *Packet) (io.Reader, string, error) {
packetJSON, err := packet.JSON()
if err != nil {
return nil, "", fmt.Errorf("error marshaling packet %+v to JSON: %v", packet, err)
}
// Only deflate/base64 the packet if it is bigger than 1KB, as there is
// overhead.
if len(packetJSON) > 1000 {
buf := &bytes.Buffer{}
b64 := base64.NewEncoder(base64.StdEncoding, buf)
deflate, _ := zlib.NewWriterLevel(b64, zlib.BestCompression)
deflate.Write(packetJSON)
deflate.Close()
b64.Close()
return buf, "application/octet-stream", nil
}
return bytes.NewReader(packetJSON), "application/json", nil
}
var hostname string
func init() {
hostname, _ = os.Hostname()
}

View File

@ -1,60 +0,0 @@
package raven
type causer interface {
Cause() error
}
type errWrappedWithExtra struct {
err error
extraInfo map[string]interface{}
}
func (ewx *errWrappedWithExtra) Error() string {
return ewx.err.Error()
}
func (ewx *errWrappedWithExtra) Cause() error {
return ewx.err
}
func (ewx *errWrappedWithExtra) ExtraInfo() Extra {
return ewx.extraInfo
}
// Adds extra data to an error before reporting to Sentry
func WrapWithExtra(err error, extraInfo map[string]interface{}) error {
return &errWrappedWithExtra{
err: err,
extraInfo: extraInfo,
}
}
type ErrWithExtra interface {
Error() string
Cause() error
ExtraInfo() Extra
}
// Iteratively fetches all the Extra data added to an error,
// and it's underlying errors. Extra data defined first is
// respected, and is not overridden when extracting.
func extractExtra(err error) Extra {
extra := Extra{}
currentErr := err
for currentErr != nil {
if errWithExtra, ok := currentErr.(ErrWithExtra); ok {
for k, v := range errWithExtra.ExtraInfo() {
extra[k] = v
}
}
if errWithCause, ok := currentErr.(causer); ok {
currentErr = errWithCause.Cause()
} else {
currentErr = nil
}
}
return extra
}

View File

@ -1,50 +0,0 @@
package raven
import (
"reflect"
"regexp"
)
var errorMsgPattern = regexp.MustCompile(`\A(\w+): (.+)\z`)
func NewException(err error, stacktrace *Stacktrace) *Exception {
msg := err.Error()
ex := &Exception{
Stacktrace: stacktrace,
Value: msg,
Type: reflect.TypeOf(err).String(),
}
if m := errorMsgPattern.FindStringSubmatch(msg); m != nil {
ex.Module, ex.Value = m[1], m[2]
}
return ex
}
// https://docs.getsentry.com/hosted/clientdev/interfaces/#failure-interfaces
type Exception struct {
// Required
Value string `json:"value"`
// Optional
Type string `json:"type,omitempty"`
Module string `json:"module,omitempty"`
Stacktrace *Stacktrace `json:"stacktrace,omitempty"`
}
func (e *Exception) Class() string { return "exception" }
func (e *Exception) Culprit() string {
if e.Stacktrace == nil {
return ""
}
return e.Stacktrace.Culprit()
}
// Exceptions allows for chained errors
// https://docs.sentry.io/clientdev/interfaces/exception/
type Exceptions struct {
// Required
Values []*Exception `json:"values"`
}
func (es Exceptions) Class() string { return "exception" }

View File

@ -1,99 +0,0 @@
package raven
import (
"errors"
"fmt"
"net"
"net/http"
"net/url"
"runtime/debug"
"strings"
)
func NewHttp(req *http.Request) *Http {
proto := "http"
if req.TLS != nil || req.Header.Get("X-Forwarded-Proto") == "https" {
proto = "https"
}
h := &Http{
Method: req.Method,
Cookies: req.Header.Get("Cookie"),
Query: sanitizeQuery(req.URL.Query()).Encode(),
URL: proto + "://" + req.Host + req.URL.Path,
Headers: make(map[string]string, len(req.Header)),
}
if addr, port, err := net.SplitHostPort(req.RemoteAddr); err == nil {
h.Env = map[string]string{"REMOTE_ADDR": addr, "REMOTE_PORT": port}
}
for k, v := range req.Header {
h.Headers[k] = strings.Join(v, ",")
}
h.Headers["Host"] = req.Host
return h
}
var querySecretFields = []string{"password", "passphrase", "passwd", "secret"}
func sanitizeQuery(query url.Values) url.Values {
for _, keyword := range querySecretFields {
for field := range query {
if strings.Contains(field, keyword) {
query[field] = []string{"********"}
}
}
}
return query
}
// https://docs.getsentry.com/hosted/clientdev/interfaces/#context-interfaces
type Http struct {
// Required
URL string `json:"url"`
Method string `json:"method"`
Query string `json:"query_string,omitempty"`
// Optional
Cookies string `json:"cookies,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Env map[string]string `json:"env,omitempty"`
// Must be either a string or map[string]string
Data interface{} `json:"data,omitempty"`
}
func (h *Http) Class() string { return "request" }
// Recovery handler to wrap the stdlib net/http Mux.
// Example:
// http.HandleFunc("/", raven.RecoveryHandler(func(w http.ResponseWriter, r *http.Request) {
// ...
// }))
func RecoveryHandler(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return Recoverer(http.HandlerFunc(handler)).ServeHTTP
}
// Recovery handler to wrap the stdlib net/http Mux.
// Example:
// mux := http.NewServeMux
// ...
// http.Handle("/", raven.Recoverer(mux))
func Recoverer(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rval := recover(); rval != nil {
debug.PrintStack()
rvalStr := fmt.Sprint(rval)
var packet *Packet
if err, ok := rval.(error); ok {
packet = NewPacket(rvalStr, NewException(errors.New(rvalStr), GetOrNewStacktrace(err, 2, 3, nil)), NewHttp(r))
} else {
packet = NewPacket(rvalStr, NewException(errors.New(rvalStr), NewStacktrace(2, 3, nil)), NewHttp(r))
}
Capture(packet, nil)
w.WriteHeader(http.StatusInternalServerError)
}
}()
handler.ServeHTTP(w, r)
})
}

View File

@ -1,49 +0,0 @@
package raven
// https://docs.getsentry.com/hosted/clientdev/interfaces/#message-interface
type Message struct {
// Required
Message string `json:"message"`
// Optional
Params []interface{} `json:"params,omitempty"`
}
func (m *Message) Class() string { return "logentry" }
// https://docs.getsentry.com/hosted/clientdev/interfaces/#template-interface
type Template struct {
// Required
Filename string `json:"filename"`
Lineno int `json:"lineno"`
ContextLine string `json:"context_line"`
// Optional
PreContext []string `json:"pre_context,omitempty"`
PostContext []string `json:"post_context,omitempty"`
AbsolutePath string `json:"abs_path,omitempty"`
}
func (t *Template) Class() string { return "template" }
// https://docs.getsentry.com/hosted/clientdev/interfaces/#context-interfaces
type User struct {
// All fields are optional
ID string `json:"id,omitempty"`
Username string `json:"username,omitempty"`
Email string `json:"email,omitempty"`
IP string `json:"ip_address,omitempty"`
}
func (h *User) Class() string { return "user" }
// https://docs.getsentry.com/hosted/clientdev/interfaces/#context-interfaces
type Query struct {
// Required
Query string `json:"query"`
// Optional
Engine string `json:"engine,omitempty"`
}
func (q *Query) Class() string { return "query" }

View File

@ -1,4 +0,0 @@
#!/bin/bash
go test -race ./...
go test -cover ./...
go test -v ./...

View File

@ -1,277 +0,0 @@
// Copyright 2011 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.
// Some code from the runtime/debug package of the Go standard library.
package raven
import (
"bytes"
"go/build"
"io/ioutil"
"path/filepath"
"runtime"
"strings"
"sync"
"github.com/pkg/errors"
)
// https://docs.getsentry.com/hosted/clientdev/interfaces/#failure-interfaces
type Stacktrace struct {
// Required
Frames []*StacktraceFrame `json:"frames"`
}
func (s *Stacktrace) Class() string { return "stacktrace" }
func (s *Stacktrace) Culprit() string {
for i := len(s.Frames) - 1; i >= 0; i-- {
frame := s.Frames[i]
if frame.InApp == true && frame.Module != "" && frame.Function != "" {
return frame.Module + "." + frame.Function
}
}
return ""
}
type StacktraceFrame struct {
// At least one required
Filename string `json:"filename,omitempty"`
Function string `json:"function,omitempty"`
Module string `json:"module,omitempty"`
// Optional
Lineno int `json:"lineno,omitempty"`
Colno int `json:"colno,omitempty"`
AbsolutePath string `json:"abs_path,omitempty"`
ContextLine string `json:"context_line,omitempty"`
PreContext []string `json:"pre_context,omitempty"`
PostContext []string `json:"post_context,omitempty"`
InApp bool `json:"in_app"`
}
// Try to get stacktrace from err as an interface of github.com/pkg/errors, or else NewStacktrace()
func GetOrNewStacktrace(err error, skip int, context int, appPackagePrefixes []string) *Stacktrace {
stacktracer, errHasStacktrace := err.(interface {
StackTrace() errors.StackTrace
})
if errHasStacktrace {
var frames []*StacktraceFrame
for _, f := range stacktracer.StackTrace() {
pc := uintptr(f) - 1
fn := runtime.FuncForPC(pc)
var fName string
var file string
var line int
if fn != nil {
file, line = fn.FileLine(pc)
fName = fn.Name()
} else {
file = "unknown"
fName = "unknown"
}
frame := NewStacktraceFrame(pc, fName, file, line, context, appPackagePrefixes)
if frame != nil {
frames = append([]*StacktraceFrame{frame}, frames...)
}
}
return &Stacktrace{Frames: frames}
} else {
return NewStacktrace(skip+1, context, appPackagePrefixes)
}
}
// Intialize and populate a new stacktrace, skipping skip frames.
//
// context is the number of surrounding lines that should be included for context.
// Setting context to 3 would try to get seven lines. Setting context to -1 returns
// one line with no surrounding context, and 0 returns no context.
//
// appPackagePrefixes is a list of prefixes used to check whether a package should
// be considered "in app".
func NewStacktrace(skip int, context int, appPackagePrefixes []string) *Stacktrace {
var frames []*StacktraceFrame
callerPcs := make([]uintptr, 100)
numCallers := runtime.Callers(skip+2, callerPcs)
// If there are no callers, the entire stacktrace is nil
if numCallers == 0 {
return nil
}
callersFrames := runtime.CallersFrames(callerPcs)
for {
fr, more := callersFrames.Next()
if fr.Func != nil {
frame := NewStacktraceFrame(fr.PC, fr.Function, fr.File, fr.Line, context, appPackagePrefixes)
if frame != nil {
frames = append(frames, frame)
}
}
if !more {
break
}
}
// If there are no frames, the entire stacktrace is nil
if len(frames) == 0 {
return nil
}
// Optimize the path where there's only 1 frame
if len(frames) == 1 {
return &Stacktrace{frames}
}
// Sentry wants the frames with the oldest first, so reverse them
for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 {
frames[i], frames[j] = frames[j], frames[i]
}
return &Stacktrace{frames}
}
// Build a single frame using data returned from runtime.Caller.
//
// context is the number of surrounding lines that should be included for context.
// Setting context to 3 would try to get seven lines. Setting context to -1 returns
// one line with no surrounding context, and 0 returns no context.
//
// appPackagePrefixes is a list of prefixes used to check whether a package should
// be considered "in app".
func NewStacktraceFrame(pc uintptr, fName, file string, line, context int, appPackagePrefixes []string) *StacktraceFrame {
frame := &StacktraceFrame{AbsolutePath: file, Filename: trimPath(file), Lineno: line, InApp: false}
frame.Module, frame.Function = functionName(fName)
// `runtime.goexit` is effectively a placeholder that comes from
// runtime/asm_amd64.s and is meaningless.
if frame.Module == "runtime" && frame.Function == "goexit" {
return nil
}
if frame.Module == "main" {
frame.InApp = true
} else {
for _, prefix := range appPackagePrefixes {
if strings.HasPrefix(frame.Module, prefix) && !strings.Contains(frame.Module, "vendor") && !strings.Contains(frame.Module, "third_party") {
frame.InApp = true
}
}
}
if context > 0 {
contextLines, lineIdx := sourceCodeLoader.Load(file, line, context)
if len(contextLines) > 0 {
for i, line := range contextLines {
switch {
case i < lineIdx:
frame.PreContext = append(frame.PreContext, string(line))
case i == lineIdx:
frame.ContextLine = string(line)
default:
frame.PostContext = append(frame.PostContext, string(line))
}
}
}
} else if context == -1 {
contextLine, _ := sourceCodeLoader.Load(file, line, 0)
if len(contextLine) > 0 {
frame.ContextLine = string(contextLine[0])
}
}
return frame
}
// Retrieve the name of the package and function containing the PC.
func functionName(fName string) (pack string, name string) {
name = fName
// We get this:
// runtime/debug.*T·ptrmethod
// and want this:
// pack = runtime/debug
// name = *T.ptrmethod
if idx := strings.LastIndex(name, "."); idx != -1 {
pack = name[:idx]
name = name[idx+1:]
}
name = strings.Replace(name, "·", ".", -1)
return
}
type SourceCodeLoader interface {
Load(filename string, line, context int) ([][]byte, int)
}
var sourceCodeLoader SourceCodeLoader = &fsLoader{cache: make(map[string][][]byte)}
func SetSourceCodeLoader(loader SourceCodeLoader) {
sourceCodeLoader = loader
}
type fsLoader struct {
mu sync.Mutex
cache map[string][][]byte
}
func (fs *fsLoader) Load(filename string, line, context int) ([][]byte, int) {
fs.mu.Lock()
defer fs.mu.Unlock()
lines, ok := fs.cache[filename]
if !ok {
data, err := ioutil.ReadFile(filename)
if err != nil {
// cache errors as nil slice: code below handles it correctly
// otherwise when missing the source or running as a different user, we try
// reading the file on each error which is unnecessary
fs.cache[filename] = nil
return nil, 0
}
lines = bytes.Split(data, []byte{'\n'})
fs.cache[filename] = lines
}
if lines == nil {
// cached error from ReadFile: return no lines
return nil, 0
}
line-- // stack trace lines are 1-indexed
start := line - context
var idx int
if start < 0 {
start = 0
idx = line
} else {
idx = context
}
end := line + context + 1
if line >= len(lines) {
return nil, 0
}
if end > len(lines) {
end = len(lines)
}
return lines[start:end], idx
}
var trimPaths []string
// Try to trim the GOROOT or GOPATH prefix off of a filename
func trimPath(filename string) string {
for _, prefix := range trimPaths {
if trimmed := strings.TrimPrefix(filename, prefix); len(trimmed) < len(filename) {
return trimmed
}
}
return filename
}
func init() {
// Collect all source directories, and make sure they
// end in a trailing "separator"
for _, prefix := range build.Default.SrcDirs() {
if prefix[len(prefix)-1] != filepath.Separator {
prefix += string(filepath.Separator)
}
trimPaths = append(trimPaths, prefix)
}
}

View File

@ -1,20 +0,0 @@
package raven
type Writer struct {
Client *Client
Level Severity
Logger string // Logger name reported to Sentry
}
// Write formats the byte slice p into a string, and sends a message to
// Sentry at the severity level indicated by the Writer w.
func (w *Writer) Write(p []byte) (int, error) {
message := string(p)
packet := NewPacket(message, &Message{message, nil})
packet.Level = w.Level
packet.Logger = w.Logger
w.Client.Capture(packet, nil)
return len(p), nil
}

View File

@ -1,13 +0,0 @@
Copyright 2014 Alan Shreve
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,23 +0,0 @@
# mousetrap
mousetrap is a tiny library that answers a single question.
On a Windows machine, was the process invoked by someone double clicking on
the executable file while browsing in explorer?
### Motivation
Windows developers unfamiliar with command line tools will often "double-click"
the executable for a tool. Because most CLI tools print the help and then exit
when invoked without arguments, this is often very frustrating for those users.
mousetrap provides a way to detect these invocations so that you can provide
more helpful behavior and instructions on how to run the CLI tool. To see what
this looks like, both from an organizational and a technical perspective, see
https://inconshreveable.com/09-09-2014/sweat-the-small-stuff/
### The interface
The library exposes a single interface:
func StartedByExplorer() (bool)

View File

@ -1,15 +0,0 @@
// +build !windows
package mousetrap
// StartedByExplorer returns true if the program was invoked by the user
// double-clicking on the executable from explorer.exe
//
// It is conservative and returns false if any of the internal calls fail.
// It does not guarantee that the program was run from a terminal. It only can tell you
// whether it was launched from explorer.exe
//
// On non-Windows platforms, it always returns false.
func StartedByExplorer() bool {
return false
}

View File

@ -1,98 +0,0 @@
// +build windows
// +build !go1.4
package mousetrap
import (
"fmt"
"os"
"syscall"
"unsafe"
)
const (
// defined by the Win32 API
th32cs_snapprocess uintptr = 0x2
)
var (
kernel = syscall.MustLoadDLL("kernel32.dll")
CreateToolhelp32Snapshot = kernel.MustFindProc("CreateToolhelp32Snapshot")
Process32First = kernel.MustFindProc("Process32FirstW")
Process32Next = kernel.MustFindProc("Process32NextW")
)
// ProcessEntry32 structure defined by the Win32 API
type processEntry32 struct {
dwSize uint32
cntUsage uint32
th32ProcessID uint32
th32DefaultHeapID int
th32ModuleID uint32
cntThreads uint32
th32ParentProcessID uint32
pcPriClassBase int32
dwFlags uint32
szExeFile [syscall.MAX_PATH]uint16
}
func getProcessEntry(pid int) (pe *processEntry32, err error) {
snapshot, _, e1 := CreateToolhelp32Snapshot.Call(th32cs_snapprocess, uintptr(0))
if snapshot == uintptr(syscall.InvalidHandle) {
err = fmt.Errorf("CreateToolhelp32Snapshot: %v", e1)
return
}
defer syscall.CloseHandle(syscall.Handle(snapshot))
var processEntry processEntry32
processEntry.dwSize = uint32(unsafe.Sizeof(processEntry))
ok, _, e1 := Process32First.Call(snapshot, uintptr(unsafe.Pointer(&processEntry)))
if ok == 0 {
err = fmt.Errorf("Process32First: %v", e1)
return
}
for {
if processEntry.th32ProcessID == uint32(pid) {
pe = &processEntry
return
}
ok, _, e1 = Process32Next.Call(snapshot, uintptr(unsafe.Pointer(&processEntry)))
if ok == 0 {
err = fmt.Errorf("Process32Next: %v", e1)
return
}
}
}
func getppid() (pid int, err error) {
pe, err := getProcessEntry(os.Getpid())
if err != nil {
return
}
pid = int(pe.th32ParentProcessID)
return
}
// StartedByExplorer returns true if the program was invoked by the user double-clicking
// on the executable from explorer.exe
//
// It is conservative and returns false if any of the internal calls fail.
// It does not guarantee that the program was run from a terminal. It only can tell you
// whether it was launched from explorer.exe
func StartedByExplorer() bool {
ppid, err := getppid()
if err != nil {
return false
}
pe, err := getProcessEntry(ppid)
if err != nil {
return false
}
name := syscall.UTF16ToString(pe.szExeFile[:])
return name == "explorer.exe"
}

View File

@ -1,46 +0,0 @@
// +build windows
// +build go1.4
package mousetrap
import (
"os"
"syscall"
"unsafe"
)
func getProcessEntry(pid int) (*syscall.ProcessEntry32, error) {
snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
if err != nil {
return nil, err
}
defer syscall.CloseHandle(snapshot)
var procEntry syscall.ProcessEntry32
procEntry.Size = uint32(unsafe.Sizeof(procEntry))
if err = syscall.Process32First(snapshot, &procEntry); err != nil {
return nil, err
}
for {
if procEntry.ProcessID == uint32(pid) {
return &procEntry, nil
}
err = syscall.Process32Next(snapshot, &procEntry)
if err != nil {
return nil, err
}
}
}
// StartedByExplorer returns true if the program was invoked by the user double-clicking
// on the executable from explorer.exe
//
// It is conservative and returns false if any of the internal calls fail.
// It does not guarantee that the program was run from a terminal. It only can tell you
// whether it was launched from explorer.exe
func StartedByExplorer() bool {
pe, err := getProcessEntry(os.Getppid())
if err != nil {
return false
}
return "explorer.exe" == syscall.UTF16ToString(pe.ExeFile[:])
}

View File

@ -1,9 +0,0 @@
(The MIT License)
Copyright (c) 2017 marvin + konsorten GmbH (open-source@konsorten.de)
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.

View File

@ -1,40 +0,0 @@
# Windows Terminal Sequences
This library allow for enabling Windows terminal color support for Go.
See [Console Virtual Terminal Sequences](https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences) for details.
## Usage
```go
import (
"syscall"
sequences "github.com/konsorten/go-windows-terminal-sequences"
)
func main() {
sequences.EnableVirtualTerminalProcessing(syscall.Stdout, true)
}
```
## Authors
The tool is sponsored by the [marvin + konsorten GmbH](http://www.konsorten.de).
We thank all the authors who provided code to this library:
* Felix Kollmann
## License
(The MIT License)
Copyright (c) 2018 marvin + konsorten GmbH (open-source@konsorten.de)
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.

View File

@ -1 +0,0 @@
module github.com/konsorten/go-windows-terminal-sequences

View File

@ -1,36 +0,0 @@
// +build windows
package sequences
import (
"syscall"
"unsafe"
)
var (
kernel32Dll *syscall.LazyDLL = syscall.NewLazyDLL("Kernel32.dll")
setConsoleMode *syscall.LazyProc = kernel32Dll.NewProc("SetConsoleMode")
)
func EnableVirtualTerminalProcessing(stream syscall.Handle, enable bool) error {
const ENABLE_VIRTUAL_TERMINAL_PROCESSING uint32 = 0x4
var mode uint32
err := syscall.GetConsoleMode(syscall.Stdout, &mode)
if err != nil {
return err
}
if enable {
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING
} else {
mode &^= ENABLE_VIRTUAL_TERMINAL_PROCESSING
}
ret, _, err := setConsoleMode.Call(uintptr(unsafe.Pointer(stream)), uintptr(mode))
if ret == 0 {
return err
}
return nil
}

View File

@ -1,25 +0,0 @@
# EditorConfig coding styles definitions. For more information about the
# properties used in this file, please see the EditorConfig documentation:
# http://editorconfig.org/
# indicate this is the root of the project
root = true
[*]
charset = utf-8
end_of_line = LF
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[Makefile]
indent_style = tab
[*.md]
trim_trailing_whitespace = false
[*.go]
indent_style = tab

View File

@ -1,20 +0,0 @@
# Automatically normalize line endings for all text-based files
# http://git-scm.com/docs/gitattributes#_end_of_line_conversion
* text=auto
# For the following file types, normalize line endings to LF on checking and
# prevent conversion to CRLF when they are checked out (this is required in
# order to prevent newline related issues)
.* text eol=lf
*.go text eol=lf
*.yml text eol=lf
*.html text eol=lf
*.css text eol=lf
*.js text eol=lf
*.json text eol=lf
LICENSE text eol=lf
# Exclude `website` and `cookbook` from GitHub's language statistics
# https://github.com/github/linguist#using-gitattributes
cookbook/* linguist-documentation
website/* linguist-documentation

View File

@ -1,7 +0,0 @@
.DS_Store
coverage.txt
_test
vendor
.idea
*.iml
*.out

View File

@ -1,17 +0,0 @@
language: go
go:
- 1.11.x
- 1.12.x
- tip
env:
- GO111MODULE=on
install:
- go get -v golang.org/x/lint/golint
script:
- golint -set_exit_status ./...
- go test -race -coverprofile=coverage.txt -covermode=atomic ./...
after_success:
- bash <(curl -s https://codecov.io/bash)
matrix:
allow_failures:
- go: tip

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2017 LabStack
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.

View File

@ -1,3 +0,0 @@
tag:
@git tag `grep -P '^\tversion = ' echo.go|cut -f2 -d'"'`
@git tag|grep -v ^v

View File

@ -1,114 +0,0 @@
<a href="https://echo.labstack.com"><img height="80" src="https://cdn.labstack.com/images/echo-logo.svg"></a>
[![Sourcegraph](https://sourcegraph.com/github.com/labstack/echo/-/badge.svg?style=flat-square)](https://sourcegraph.com/github.com/labstack/echo?badge)
[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/labstack/echo)
[![Go Report Card](https://goreportcard.com/badge/github.com/labstack/echo?style=flat-square)](https://goreportcard.com/report/github.com/labstack/echo)
[![Build Status](http://img.shields.io/travis/labstack/echo.svg?style=flat-square)](https://travis-ci.org/labstack/echo)
[![Codecov](https://img.shields.io/codecov/c/github/labstack/echo.svg?style=flat-square)](https://codecov.io/gh/labstack/echo)
[![Join the chat at https://gitter.im/labstack/echo](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg?style=flat-square)](https://gitter.im/labstack/echo)
[![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://forum.labstack.com)
[![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack)
[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo/master/LICENSE)
## Supported Go versions
As of version 4.0.0, Echo is available as a [Go module](https://github.com/golang/go/wiki/Modules).
Therefore a Go version capable of understanding /vN suffixed imports is required:
- 1.9.7+
- 1.10.3+
- 1.11+
Any of these versions will allow you to import Echo as `github.com/labstack/echo/v4` which is the recommended
way of using Echo going forward.
For older versions, please use the latest v3 tag.
## Feature Overview
- Optimized HTTP router which smartly prioritize routes
- Build robust and scalable RESTful APIs
- Group APIs
- Extensible middleware framework
- Define middleware at root, group or route level
- Data binding for JSON, XML and form payload
- Handy functions to send variety of HTTP responses
- Centralized HTTP error handling
- Template rendering with any template engine
- Define your format for the logger
- Highly customizable
- Automatic TLS via Lets Encrypt
- HTTP/2 support
## Benchmarks
Date: 2018/03/15<br>
Source: https://github.com/vishr/web-framework-benchmark<br>
Lower is better!
<img src="https://i.imgur.com/I32VdMJ.png">
## [Guide](https://echo.labstack.com/guide)
### Example
```go
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
// Echo instance
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Routes
e.GET("/", hello)
// Start server
e.Logger.Fatal(e.Start(":1323"))
}
// Handler
func hello(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}
```
## Help
- [Forum](https://forum.labstack.com)
- [Chat](https://gitter.im/labstack/echo)
## Contribute
**Use issues for everything**
- For a small change, just send a PR.
- For bigger changes open an issue for discussion before sending a PR.
- PR should have:
- Test case
- Documentation
- Example (If it makes sense)
- You can also contribute by:
- Reporting issues
- Suggesting new features or enhancements
- Improve/fix documentation
## Credits
- [Vishal Rana](https://github.com/vishr) - Author
- [Nitin Rana](https://github.com/nr17) - Consultant
- [Contributors](https://github.com/labstack/echo/graphs/contributors)
## License
[MIT](https://github.com/labstack/echo/blob/master/LICENSE)

View File

@ -1,273 +0,0 @@
package echo
import (
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"net/http"
"reflect"
"strconv"
"strings"
)
type (
// Binder is the interface that wraps the Bind method.
Binder interface {
Bind(i interface{}, c Context) error
}
// DefaultBinder is the default implementation of the Binder interface.
DefaultBinder struct{}
// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
BindUnmarshaler interface {
// UnmarshalParam decodes and assigns a value from an form or query param.
UnmarshalParam(param string) error
}
)
// Bind implements the `Binder#Bind` function.
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
req := c.Request()
if req.ContentLength == 0 {
if req.Method == http.MethodGet || req.Method == http.MethodDelete {
if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
return
}
return NewHTTPError(http.StatusBadRequest, "Request body can't be empty")
}
ctype := req.Header.Get(HeaderContentType)
switch {
case strings.HasPrefix(ctype, MIMEApplicationJSON):
if err = json.NewDecoder(req.Body).Decode(i); err != nil {
if ute, ok := err.(*json.UnmarshalTypeError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)).SetInternal(err)
} else if se, ok := err.(*json.SyntaxError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)
}
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML):
if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
if ute, ok := err.(*xml.UnsupportedTypeError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())).SetInternal(err)
} else if se, ok := err.(*xml.SyntaxError); ok {
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())).SetInternal(err)
}
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
params, err := c.FormParams()
if err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
if err = b.bindData(i, params, "form"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
}
default:
return ErrUnsupportedMediaType
}
return
}
func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag string) error {
typ := reflect.TypeOf(ptr).Elem()
val := reflect.ValueOf(ptr).Elem()
if typ.Kind() != reflect.Struct {
return errors.New("binding element must be a struct")
}
for i := 0; i < typ.NumField(); i++ {
typeField := typ.Field(i)
structField := val.Field(i)
if !structField.CanSet() {
continue
}
structFieldKind := structField.Kind()
inputFieldName := typeField.Tag.Get(tag)
if inputFieldName == "" {
inputFieldName = typeField.Name
// If tag is nil, we inspect if the field is a struct.
if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct {
if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
return err
}
continue
}
}
inputValue, exists := data[inputFieldName]
if !exists {
// Go json.Unmarshal supports case insensitive binding. However the
// url params are bound case sensitive which is inconsistent. To
// fix this we must check all of the map values in a
// case-insensitive search.
inputFieldName = strings.ToLower(inputFieldName)
for k, v := range data {
if strings.ToLower(k) == inputFieldName {
inputValue = v
exists = true
break
}
}
}
if !exists {
continue
}
// Call this first, in case we're dealing with an alias to an array type
if ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok {
if err != nil {
return err
}
continue
}
numElems := len(inputValue)
if structFieldKind == reflect.Slice && numElems > 0 {
sliceOf := structField.Type().Elem().Kind()
slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
for j := 0; j < numElems; j++ {
if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {
return err
}
}
val.Field(i).Set(slice)
} else if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
return err
}
}
return nil
}
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
// But also call it here, in case we're dealing with an array of BindUnmarshalers
if ok, err := unmarshalField(valueKind, val, structField); ok {
return err
}
switch valueKind {
case reflect.Ptr:
return setWithProperType(structField.Elem().Kind(), val, structField.Elem())
case reflect.Int:
return setIntField(val, 0, structField)
case reflect.Int8:
return setIntField(val, 8, structField)
case reflect.Int16:
return setIntField(val, 16, structField)
case reflect.Int32:
return setIntField(val, 32, structField)
case reflect.Int64:
return setIntField(val, 64, structField)
case reflect.Uint:
return setUintField(val, 0, structField)
case reflect.Uint8:
return setUintField(val, 8, structField)
case reflect.Uint16:
return setUintField(val, 16, structField)
case reflect.Uint32:
return setUintField(val, 32, structField)
case reflect.Uint64:
return setUintField(val, 64, structField)
case reflect.Bool:
return setBoolField(val, structField)
case reflect.Float32:
return setFloatField(val, 32, structField)
case reflect.Float64:
return setFloatField(val, 64, structField)
case reflect.String:
structField.SetString(val)
default:
return errors.New("unknown type")
}
return nil
}
func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) {
switch valueKind {
case reflect.Ptr:
return unmarshalFieldPtr(val, field)
default:
return unmarshalFieldNonPtr(val, field)
}
}
// bindUnmarshaler attempts to unmarshal a reflect.Value into a BindUnmarshaler
func bindUnmarshaler(field reflect.Value) (BindUnmarshaler, bool) {
ptr := reflect.New(field.Type())
if ptr.CanInterface() {
iface := ptr.Interface()
if unmarshaler, ok := iface.(BindUnmarshaler); ok {
return unmarshaler, ok
}
}
return nil, false
}
func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) {
if unmarshaler, ok := bindUnmarshaler(field); ok {
err := unmarshaler.UnmarshalParam(value)
field.Set(reflect.ValueOf(unmarshaler).Elem())
return true, err
}
return false, nil
}
func unmarshalFieldPtr(value string, field reflect.Value) (bool, error) {
if field.IsNil() {
// Initialize the pointer to a nil value
field.Set(reflect.New(field.Type().Elem()))
}
return unmarshalFieldNonPtr(value, field.Elem())
}
func setIntField(value string, bitSize int, field reflect.Value) error {
if value == "" {
value = "0"
}
intVal, err := strconv.ParseInt(value, 10, bitSize)
if err == nil {
field.SetInt(intVal)
}
return err
}
func setUintField(value string, bitSize int, field reflect.Value) error {
if value == "" {
value = "0"
}
uintVal, err := strconv.ParseUint(value, 10, bitSize)
if err == nil {
field.SetUint(uintVal)
}
return err
}
func setBoolField(value string, field reflect.Value) error {
if value == "" {
value = "false"
}
boolVal, err := strconv.ParseBool(value)
if err == nil {
field.SetBool(boolVal)
}
return err
}
func setFloatField(value string, bitSize int, field reflect.Value) error {
if value == "" {
value = "0.0"
}
floatVal, err := strconv.ParseFloat(value, bitSize)
if err == nil {
field.SetFloat(floatVal)
}
return err
}

View File

@ -1,606 +0,0 @@
package echo
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"mime/multipart"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
)
type (
// Context represents the context of the current HTTP request. It holds request and
// response objects, path, path parameters, data and registered handler.
Context interface {
// Request returns `*http.Request`.
Request() *http.Request
// SetRequest sets `*http.Request`.
SetRequest(r *http.Request)
// Response returns `*Response`.
Response() *Response
// IsTLS returns true if HTTP connection is TLS otherwise false.
IsTLS() bool
// IsWebSocket returns true if HTTP connection is WebSocket otherwise false.
IsWebSocket() bool
// Scheme returns the HTTP protocol scheme, `http` or `https`.
Scheme() string
// RealIP returns the client's network address based on `X-Forwarded-For`
// or `X-Real-IP` request header.
RealIP() string
// Path returns the registered path for the handler.
Path() string
// SetPath sets the registered path for the handler.
SetPath(p string)
// Param returns path parameter by name.
Param(name string) string
// ParamNames returns path parameter names.
ParamNames() []string
// SetParamNames sets path parameter names.
SetParamNames(names ...string)
// ParamValues returns path parameter values.
ParamValues() []string
// SetParamValues sets path parameter values.
SetParamValues(values ...string)
// QueryParam returns the query param for the provided name.
QueryParam(name string) string
// QueryParams returns the query parameters as `url.Values`.
QueryParams() url.Values
// QueryString returns the URL query string.
QueryString() string
// FormValue returns the form field value for the provided name.
FormValue(name string) string
// FormParams returns the form parameters as `url.Values`.
FormParams() (url.Values, error)
// FormFile returns the multipart form file for the provided name.
FormFile(name string) (*multipart.FileHeader, error)
// MultipartForm returns the multipart form.
MultipartForm() (*multipart.Form, error)
// Cookie returns the named cookie provided in the request.
Cookie(name string) (*http.Cookie, error)
// SetCookie adds a `Set-Cookie` header in HTTP response.
SetCookie(cookie *http.Cookie)
// Cookies returns the HTTP cookies sent with the request.
Cookies() []*http.Cookie
// Get retrieves data from the context.
Get(key string) interface{}
// Set saves data in the context.
Set(key string, val interface{})
// Bind binds the request body into provided type `i`. The default binder
// does it based on Content-Type header.
Bind(i interface{}) error
// Validate validates provided `i`. It is usually called after `Context#Bind()`.
// Validator must be registered using `Echo#Validator`.
Validate(i interface{}) error
// Render renders a template with data and sends a text/html response with status
// code. Renderer must be registered using `Echo.Renderer`.
Render(code int, name string, data interface{}) error
// HTML sends an HTTP response with status code.
HTML(code int, html string) error
// HTMLBlob sends an HTTP blob response with status code.
HTMLBlob(code int, b []byte) error
// String sends a string response with status code.
String(code int, s string) error
// JSON sends a JSON response with status code.
JSON(code int, i interface{}) error
// JSONPretty sends a pretty-print JSON with status code.
JSONPretty(code int, i interface{}, indent string) error
// JSONBlob sends a JSON blob response with status code.
JSONBlob(code int, b []byte) error
// JSONP sends a JSONP response with status code. It uses `callback` to construct
// the JSONP payload.
JSONP(code int, callback string, i interface{}) error
// JSONPBlob sends a JSONP blob response with status code. It uses `callback`
// to construct the JSONP payload.
JSONPBlob(code int, callback string, b []byte) error
// XML sends an XML response with status code.
XML(code int, i interface{}) error
// XMLPretty sends a pretty-print XML with status code.
XMLPretty(code int, i interface{}, indent string) error
// XMLBlob sends an XML blob response with status code.
XMLBlob(code int, b []byte) error
// Blob sends a blob response with status code and content type.
Blob(code int, contentType string, b []byte) error
// Stream sends a streaming response with status code and content type.
Stream(code int, contentType string, r io.Reader) error
// File sends a response with the content of the file.
File(file string) error
// Attachment sends a response as attachment, prompting client to save the
// file.
Attachment(file string, name string) error
// Inline sends a response as inline, opening the file in the browser.
Inline(file string, name string) error
// NoContent sends a response with no body and a status code.
NoContent(code int) error
// Redirect redirects the request to a provided URL with status code.
Redirect(code int, url string) error
// Error invokes the registered HTTP error handler. Generally used by middleware.
Error(err error)
// Handler returns the matched handler by router.
Handler() HandlerFunc
// SetHandler sets the matched handler by router.
SetHandler(h HandlerFunc)
// Logger returns the `Logger` instance.
Logger() Logger
// Echo returns the `Echo` instance.
Echo() *Echo
// Reset resets the context after request completes. It must be called along
// with `Echo#AcquireContext()` and `Echo#ReleaseContext()`.
// See `Echo#ServeHTTP()`
Reset(r *http.Request, w http.ResponseWriter)
}
context struct {
request *http.Request
response *Response
path string
pnames []string
pvalues []string
query url.Values
handler HandlerFunc
store Map
echo *Echo
lock sync.RWMutex
}
)
const (
defaultMemory = 32 << 20 // 32 MB
indexPage = "index.html"
defaultIndent = " "
)
func (c *context) writeContentType(value string) {
header := c.Response().Header()
if header.Get(HeaderContentType) == "" {
header.Set(HeaderContentType, value)
}
}
func (c *context) Request() *http.Request {
return c.request
}
func (c *context) SetRequest(r *http.Request) {
c.request = r
}
func (c *context) Response() *Response {
return c.response
}
func (c *context) IsTLS() bool {
return c.request.TLS != nil
}
func (c *context) IsWebSocket() bool {
upgrade := c.request.Header.Get(HeaderUpgrade)
return strings.ToLower(upgrade) == "websocket"
}
func (c *context) Scheme() string {
// Can't use `r.Request.URL.Scheme`
// See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0
if c.IsTLS() {
return "https"
}
if scheme := c.request.Header.Get(HeaderXForwardedProto); scheme != "" {
return scheme
}
if scheme := c.request.Header.Get(HeaderXForwardedProtocol); scheme != "" {
return scheme
}
if ssl := c.request.Header.Get(HeaderXForwardedSsl); ssl == "on" {
return "https"
}
if scheme := c.request.Header.Get(HeaderXUrlScheme); scheme != "" {
return scheme
}
return "http"
}
func (c *context) RealIP() string {
if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
return strings.Split(ip, ", ")[0]
}
if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
return ip
}
ra, _, _ := net.SplitHostPort(c.request.RemoteAddr)
return ra
}
func (c *context) Path() string {
return c.path
}
func (c *context) SetPath(p string) {
c.path = p
}
func (c *context) Param(name string) string {
for i, n := range c.pnames {
if i < len(c.pvalues) {
if n == name {
return c.pvalues[i]
}
}
}
return ""
}
func (c *context) ParamNames() []string {
return c.pnames
}
func (c *context) SetParamNames(names ...string) {
c.pnames = names
}
func (c *context) ParamValues() []string {
return c.pvalues[:len(c.pnames)]
}
func (c *context) SetParamValues(values ...string) {
c.pvalues = values
}
func (c *context) QueryParam(name string) string {
if c.query == nil {
c.query = c.request.URL.Query()
}
return c.query.Get(name)
}
func (c *context) QueryParams() url.Values {
if c.query == nil {
c.query = c.request.URL.Query()
}
return c.query
}
func (c *context) QueryString() string {
return c.request.URL.RawQuery
}
func (c *context) FormValue(name string) string {
return c.request.FormValue(name)
}
func (c *context) FormParams() (url.Values, error) {
if strings.HasPrefix(c.request.Header.Get(HeaderContentType), MIMEMultipartForm) {
if err := c.request.ParseMultipartForm(defaultMemory); err != nil {
return nil, err
}
} else {
if err := c.request.ParseForm(); err != nil {
return nil, err
}
}
return c.request.Form, nil
}
func (c *context) FormFile(name string) (*multipart.FileHeader, error) {
_, fh, err := c.request.FormFile(name)
return fh, err
}
func (c *context) MultipartForm() (*multipart.Form, error) {
err := c.request.ParseMultipartForm(defaultMemory)
return c.request.MultipartForm, err
}
func (c *context) Cookie(name string) (*http.Cookie, error) {
return c.request.Cookie(name)
}
func (c *context) SetCookie(cookie *http.Cookie) {
http.SetCookie(c.Response(), cookie)
}
func (c *context) Cookies() []*http.Cookie {
return c.request.Cookies()
}
func (c *context) Get(key string) interface{} {
c.lock.RLock()
defer c.lock.RUnlock()
return c.store[key]
}
func (c *context) Set(key string, val interface{}) {
c.lock.Lock()
defer c.lock.Unlock()
if c.store == nil {
c.store = make(Map)
}
c.store[key] = val
}
func (c *context) Bind(i interface{}) error {
return c.echo.Binder.Bind(i, c)
}
func (c *context) Validate(i interface{}) error {
if c.echo.Validator == nil {
return ErrValidatorNotRegistered
}
return c.echo.Validator.Validate(i)
}
func (c *context) Render(code int, name string, data interface{}) (err error) {
if c.echo.Renderer == nil {
return ErrRendererNotRegistered
}
buf := new(bytes.Buffer)
if err = c.echo.Renderer.Render(buf, name, data, c); err != nil {
return
}
return c.HTMLBlob(code, buf.Bytes())
}
func (c *context) HTML(code int, html string) (err error) {
return c.HTMLBlob(code, []byte(html))
}
func (c *context) HTMLBlob(code int, b []byte) (err error) {
return c.Blob(code, MIMETextHTMLCharsetUTF8, b)
}
func (c *context) String(code int, s string) (err error) {
return c.Blob(code, MIMETextPlainCharsetUTF8, []byte(s))
}
func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error) {
enc := json.NewEncoder(c.response)
_, pretty := c.QueryParams()["pretty"]
if c.echo.Debug || pretty {
enc.SetIndent("", " ")
}
c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8)
c.response.WriteHeader(code)
if _, err = c.response.Write([]byte(callback + "(")); err != nil {
return
}
if err = enc.Encode(i); err != nil {
return
}
if _, err = c.response.Write([]byte(");")); err != nil {
return
}
return
}
func (c *context) json(code int, i interface{}, indent string) error {
enc := json.NewEncoder(c.response)
if indent != "" {
enc.SetIndent("", indent)
}
c.writeContentType(MIMEApplicationJSONCharsetUTF8)
c.response.WriteHeader(code)
return enc.Encode(i)
}
func (c *context) JSON(code int, i interface{}) (err error) {
indent := ""
if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
indent = defaultIndent
}
return c.json(code, i, indent)
}
func (c *context) JSONPretty(code int, i interface{}, indent string) (err error) {
return c.json(code, i, indent)
}
func (c *context) JSONBlob(code int, b []byte) (err error) {
return c.Blob(code, MIMEApplicationJSONCharsetUTF8, b)
}
func (c *context) JSONP(code int, callback string, i interface{}) (err error) {
return c.jsonPBlob(code, callback, i)
}
func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) {
c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8)
c.response.WriteHeader(code)
if _, err = c.response.Write([]byte(callback + "(")); err != nil {
return
}
if _, err = c.response.Write(b); err != nil {
return
}
_, err = c.response.Write([]byte(");"))
return
}
func (c *context) xml(code int, i interface{}, indent string) (err error) {
c.writeContentType(MIMEApplicationXMLCharsetUTF8)
c.response.WriteHeader(code)
enc := xml.NewEncoder(c.response)
if indent != "" {
enc.Indent("", indent)
}
if _, err = c.response.Write([]byte(xml.Header)); err != nil {
return
}
return enc.Encode(i)
}
func (c *context) XML(code int, i interface{}) (err error) {
indent := ""
if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
indent = defaultIndent
}
return c.xml(code, i, indent)
}
func (c *context) XMLPretty(code int, i interface{}, indent string) (err error) {
return c.xml(code, i, indent)
}
func (c *context) XMLBlob(code int, b []byte) (err error) {
c.writeContentType(MIMEApplicationXMLCharsetUTF8)
c.response.WriteHeader(code)
if _, err = c.response.Write([]byte(xml.Header)); err != nil {
return
}
_, err = c.response.Write(b)
return
}
func (c *context) Blob(code int, contentType string, b []byte) (err error) {
c.writeContentType(contentType)
c.response.WriteHeader(code)
_, err = c.response.Write(b)
return
}
func (c *context) Stream(code int, contentType string, r io.Reader) (err error) {
c.writeContentType(contentType)
c.response.WriteHeader(code)
_, err = io.Copy(c.response, r)
return
}
func (c *context) File(file string) (err error) {
f, err := os.Open(file)
if err != nil {
return NotFoundHandler(c)
}
defer f.Close()
fi, _ := f.Stat()
if fi.IsDir() {
file = filepath.Join(file, indexPage)
f, err = os.Open(file)
if err != nil {
return NotFoundHandler(c)
}
defer f.Close()
if fi, err = f.Stat(); err != nil {
return
}
}
http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f)
return
}
func (c *context) Attachment(file, name string) error {
return c.contentDisposition(file, name, "attachment")
}
func (c *context) Inline(file, name string) error {
return c.contentDisposition(file, name, "inline")
}
func (c *context) contentDisposition(file, name, dispositionType string) error {
c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", dispositionType, name))
return c.File(file)
}
func (c *context) NoContent(code int) error {
c.response.WriteHeader(code)
return nil
}
func (c *context) Redirect(code int, url string) error {
if code < 300 || code > 308 {
return ErrInvalidRedirectCode
}
c.response.Header().Set(HeaderLocation, url)
c.response.WriteHeader(code)
return nil
}
func (c *context) Error(err error) {
c.echo.HTTPErrorHandler(err, c)
}
func (c *context) Echo() *Echo {
return c.echo
}
func (c *context) Handler() HandlerFunc {
return c.handler
}
func (c *context) SetHandler(h HandlerFunc) {
c.handler = h
}
func (c *context) Logger() Logger {
return c.echo.Logger
}
func (c *context) Reset(r *http.Request, w http.ResponseWriter) {
c.request = r
c.response.reset(w)
c.query = nil
c.handler = NotFoundHandler
c.store = nil
c.path = ""
c.pnames = nil
// NOTE: Don't reset because it has to have length c.echo.maxParam at all times
// c.pvalues = nil
}

View File

@ -1,809 +0,0 @@
/*
Package echo implements high performance, minimalist Go web framework.
Example:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
// Handler
func hello(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
}
func main() {
// Echo instance
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Routes
e.GET("/", hello)
// Start server
e.Logger.Fatal(e.Start(":1323"))
}
Learn more at https://echo.labstack.com
*/
package echo
import (
"bytes"
stdContext "context"
"crypto/tls"
"errors"
"fmt"
"io"
"io/ioutil"
stdLog "log"
"net"
"net/http"
"net/url"
"path"
"path/filepath"
"reflect"
"runtime"
"sync"
"time"
"github.com/labstack/gommon/color"
"github.com/labstack/gommon/log"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
)
type (
// Echo is the top-level framework instance.
Echo struct {
StdLogger *stdLog.Logger
colorer *color.Color
premiddleware []MiddlewareFunc
middleware []MiddlewareFunc
maxParam *int
router *Router
notFoundHandler HandlerFunc
pool sync.Pool
Server *http.Server
TLSServer *http.Server
Listener net.Listener
TLSListener net.Listener
AutoTLSManager autocert.Manager
DisableHTTP2 bool
Debug bool
HideBanner bool
HidePort bool
HTTPErrorHandler HTTPErrorHandler
Binder Binder
Validator Validator
Renderer Renderer
Logger Logger
}
// Route contains a handler and information for matching against requests.
Route struct {
Method string `json:"method"`
Path string `json:"path"`
Name string `json:"name"`
}
// HTTPError represents an error that occurred while handling a request.
HTTPError struct {
Code int
Message interface{}
Internal error // Stores the error returned by an external dependency
}
// MiddlewareFunc defines a function to process middleware.
MiddlewareFunc func(HandlerFunc) HandlerFunc
// HandlerFunc defines a function to serve HTTP requests.
HandlerFunc func(Context) error
// HTTPErrorHandler is a centralized HTTP error handler.
HTTPErrorHandler func(error, Context)
// Validator is the interface that wraps the Validate function.
Validator interface {
Validate(i interface{}) error
}
// Renderer is the interface that wraps the Render function.
Renderer interface {
Render(io.Writer, string, interface{}, Context) error
}
// Map defines a generic map of type `map[string]interface{}`.
Map map[string]interface{}
// i is the interface for Echo and Group.
i interface {
GET(string, HandlerFunc, ...MiddlewareFunc) *Route
}
)
// HTTP methods
// NOTE: Deprecated, please use the stdlib constants directly instead.
const (
CONNECT = http.MethodConnect
DELETE = http.MethodDelete
GET = http.MethodGet
HEAD = http.MethodHead
OPTIONS = http.MethodOptions
PATCH = http.MethodPatch
POST = http.MethodPost
// PROPFIND = "PROPFIND"
PUT = http.MethodPut
TRACE = http.MethodTrace
)
// MIME types
const (
MIMEApplicationJSON = "application/json"
MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8
MIMEApplicationJavaScript = "application/javascript"
MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8
MIMEApplicationXML = "application/xml"
MIMEApplicationXMLCharsetUTF8 = MIMEApplicationXML + "; " + charsetUTF8
MIMETextXML = "text/xml"
MIMETextXMLCharsetUTF8 = MIMETextXML + "; " + charsetUTF8
MIMEApplicationForm = "application/x-www-form-urlencoded"
MIMEApplicationProtobuf = "application/protobuf"
MIMEApplicationMsgpack = "application/msgpack"
MIMETextHTML = "text/html"
MIMETextHTMLCharsetUTF8 = MIMETextHTML + "; " + charsetUTF8
MIMETextPlain = "text/plain"
MIMETextPlainCharsetUTF8 = MIMETextPlain + "; " + charsetUTF8
MIMEMultipartForm = "multipart/form-data"
MIMEOctetStream = "application/octet-stream"
)
const (
charsetUTF8 = "charset=UTF-8"
// PROPFIND Method can be used on collection and property resources.
PROPFIND = "PROPFIND"
)
// Headers
const (
HeaderAccept = "Accept"
HeaderAcceptEncoding = "Accept-Encoding"
HeaderAllow = "Allow"
HeaderAuthorization = "Authorization"
HeaderContentDisposition = "Content-Disposition"
HeaderContentEncoding = "Content-Encoding"
HeaderContentLength = "Content-Length"
HeaderContentType = "Content-Type"
HeaderCookie = "Cookie"
HeaderSetCookie = "Set-Cookie"
HeaderIfModifiedSince = "If-Modified-Since"
HeaderLastModified = "Last-Modified"
HeaderLocation = "Location"
HeaderUpgrade = "Upgrade"
HeaderVary = "Vary"
HeaderWWWAuthenticate = "WWW-Authenticate"
HeaderXForwardedFor = "X-Forwarded-For"
HeaderXForwardedProto = "X-Forwarded-Proto"
HeaderXForwardedProtocol = "X-Forwarded-Protocol"
HeaderXForwardedSsl = "X-Forwarded-Ssl"
HeaderXUrlScheme = "X-Url-Scheme"
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
HeaderXRealIP = "X-Real-IP"
HeaderXRequestID = "X-Request-ID"
HeaderXRequestedWith = "X-Requested-With"
HeaderServer = "Server"
HeaderOrigin = "Origin"
// Access control
HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers"
HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
HeaderAccessControlMaxAge = "Access-Control-Max-Age"
// Security
HeaderStrictTransportSecurity = "Strict-Transport-Security"
HeaderXContentTypeOptions = "X-Content-Type-Options"
HeaderXXSSProtection = "X-XSS-Protection"
HeaderXFrameOptions = "X-Frame-Options"
HeaderContentSecurityPolicy = "Content-Security-Policy"
HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
HeaderXCSRFToken = "X-CSRF-Token"
)
const (
// Version of Echo
Version = "4.0.0"
website = "https://echo.labstack.com"
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
banner = `
____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ %s
High performance, minimalist Go web framework
%s
____________________________________O/_______
O\
`
)
var (
methods = [...]string{
http.MethodConnect,
http.MethodDelete,
http.MethodGet,
http.MethodHead,
http.MethodOptions,
http.MethodPatch,
http.MethodPost,
PROPFIND,
http.MethodPut,
http.MethodTrace,
}
)
// Errors
var (
ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType)
ErrNotFound = NewHTTPError(http.StatusNotFound)
ErrUnauthorized = NewHTTPError(http.StatusUnauthorized)
ErrForbidden = NewHTTPError(http.StatusForbidden)
ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed)
ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge)
ErrTooManyRequests = NewHTTPError(http.StatusTooManyRequests)
ErrBadRequest = NewHTTPError(http.StatusBadRequest)
ErrBadGateway = NewHTTPError(http.StatusBadGateway)
ErrInternalServerError = NewHTTPError(http.StatusInternalServerError)
ErrRequestTimeout = NewHTTPError(http.StatusRequestTimeout)
ErrServiceUnavailable = NewHTTPError(http.StatusServiceUnavailable)
ErrValidatorNotRegistered = errors.New("validator not registered")
ErrRendererNotRegistered = errors.New("renderer not registered")
ErrInvalidRedirectCode = errors.New("invalid redirect status code")
ErrCookieNotFound = errors.New("cookie not found")
ErrInvalidCertOrKeyType = errors.New("invalid cert or key type, must be string or []byte")
)
// Error handlers
var (
NotFoundHandler = func(c Context) error {
return ErrNotFound
}
MethodNotAllowedHandler = func(c Context) error {
return ErrMethodNotAllowed
}
)
// New creates an instance of Echo.
func New() (e *Echo) {
e = &Echo{
Server: new(http.Server),
TLSServer: new(http.Server),
AutoTLSManager: autocert.Manager{
Prompt: autocert.AcceptTOS,
},
Logger: log.New("echo"),
colorer: color.New(),
maxParam: new(int),
}
e.Server.Handler = e
e.TLSServer.Handler = e
e.HTTPErrorHandler = e.DefaultHTTPErrorHandler
e.Binder = &DefaultBinder{}
e.Logger.SetLevel(log.ERROR)
e.StdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)
e.pool.New = func() interface{} {
return e.NewContext(nil, nil)
}
e.router = NewRouter(e)
return
}
// NewContext returns a Context instance.
func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) Context {
return &context{
request: r,
response: NewResponse(w, e),
store: make(Map),
echo: e,
pvalues: make([]string, *e.maxParam),
handler: NotFoundHandler,
}
}
// Router returns router.
func (e *Echo) Router() *Router {
return e.router
}
// DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response
// with status code.
func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
var (
code = http.StatusInternalServerError
msg interface{}
)
if he, ok := err.(*HTTPError); ok {
code = he.Code
msg = he.Message
if he.Internal != nil {
err = fmt.Errorf("%v, %v", err, he.Internal)
}
} else if e.Debug {
msg = err.Error()
} else {
msg = http.StatusText(code)
}
if _, ok := msg.(string); ok {
msg = Map{"message": msg}
}
// Send response
if !c.Response().Committed {
if c.Request().Method == http.MethodHead { // Issue #608
err = c.NoContent(code)
} else {
err = c.JSON(code, msg)
}
if err != nil {
e.Logger.Error(err)
}
}
}
// Pre adds middleware to the chain which is run before router.
func (e *Echo) Pre(middleware ...MiddlewareFunc) {
e.premiddleware = append(e.premiddleware, middleware...)
}
// Use adds middleware to the chain which is run after router.
func (e *Echo) Use(middleware ...MiddlewareFunc) {
e.middleware = append(e.middleware, middleware...)
}
// CONNECT registers a new CONNECT route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(http.MethodConnect, path, h, m...)
}
// DELETE registers a new DELETE route for a path with matching handler in the router
// with optional route-level middleware.
func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(http.MethodDelete, path, h, m...)
}
// GET registers a new GET route for a path with matching handler in the router
// with optional route-level middleware.
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(http.MethodGet, path, h, m...)
}
// HEAD registers a new HEAD route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(http.MethodHead, path, h, m...)
}
// OPTIONS registers a new OPTIONS route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(http.MethodOptions, path, h, m...)
}
// PATCH registers a new PATCH route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(http.MethodPatch, path, h, m...)
}
// POST registers a new POST route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) POST(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(http.MethodPost, path, h, m...)
}
// PUT registers a new PUT route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(http.MethodPut, path, h, m...)
}
// TRACE registers a new TRACE route for a path with matching handler in the
// router with optional route-level middleware.
func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return e.Add(http.MethodTrace, path, h, m...)
}
// Any registers a new route for all HTTP methods and path with matching handler
// in the router with optional route-level middleware.
func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
routes := make([]*Route, len(methods))
for i, m := range methods {
routes[i] = e.Add(m, path, handler, middleware...)
}
return routes
}
// Match registers a new route for multiple HTTP methods and path with matching
// handler in the router with optional route-level middleware.
func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
routes := make([]*Route, len(methods))
for i, m := range methods {
routes[i] = e.Add(m, path, handler, middleware...)
}
return routes
}
// Static registers a new route with path prefix to serve static files from the
// provided root directory.
func (e *Echo) Static(prefix, root string) *Route {
if root == "" {
root = "." // For security we want to restrict to CWD.
}
return static(e, prefix, root)
}
func static(i i, prefix, root string) *Route {
h := func(c Context) error {
p, err := url.PathUnescape(c.Param("*"))
if err != nil {
return err
}
name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security
return c.File(name)
}
i.GET(prefix, h)
if prefix == "/" {
return i.GET(prefix+"*", h)
}
return i.GET(prefix+"/*", h)
}
// File registers a new route with path to serve a static file with optional route-level middleware.
func (e *Echo) File(path, file string, m ...MiddlewareFunc) *Route {
return e.GET(path, func(c Context) error {
return c.File(file)
}, m...)
}
// Add registers a new route for an HTTP method and path with matching handler
// in the router with optional route-level middleware.
func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
name := handlerName(handler)
e.router.Add(method, path, func(c Context) error {
h := handler
// Chain middleware
for i := len(middleware) - 1; i >= 0; i-- {
h = middleware[i](h)
}
return h(c)
})
r := &Route{
Method: method,
Path: path,
Name: name,
}
e.router.routes[method+path] = r
return r
}
// Group creates a new router group with prefix and optional group-level middleware.
func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) {
g = &Group{prefix: prefix, echo: e}
g.Use(m...)
return
}
// URI generates a URI from handler.
func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string {
name := handlerName(handler)
return e.Reverse(name, params...)
}
// URL is an alias for `URI` function.
func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
return e.URI(h, params...)
}
// Reverse generates an URL from route name and provided parameters.
func (e *Echo) Reverse(name string, params ...interface{}) string {
uri := new(bytes.Buffer)
ln := len(params)
n := 0
for _, r := range e.router.routes {
if r.Name == name {
for i, l := 0, len(r.Path); i < l; i++ {
if r.Path[i] == ':' && n < ln {
for ; i < l && r.Path[i] != '/'; i++ {
}
uri.WriteString(fmt.Sprintf("%v", params[n]))
n++
}
if i < l {
uri.WriteByte(r.Path[i])
}
}
break
}
}
return uri.String()
}
// Routes returns the registered routes.
func (e *Echo) Routes() []*Route {
routes := make([]*Route, 0, len(e.router.routes))
for _, v := range e.router.routes {
routes = append(routes, v)
}
return routes
}
// AcquireContext returns an empty `Context` instance from the pool.
// You must return the context by calling `ReleaseContext()`.
func (e *Echo) AcquireContext() Context {
return e.pool.Get().(Context)
}
// ReleaseContext returns the `Context` instance back to the pool.
// You must call it after `AcquireContext()`.
func (e *Echo) ReleaseContext(c Context) {
e.pool.Put(c)
}
// ServeHTTP implements `http.Handler` interface, which serves HTTP requests.
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Acquire context
c := e.pool.Get().(*context)
c.Reset(r, w)
h := NotFoundHandler
if e.premiddleware == nil {
e.router.Find(r.Method, getPath(r), c)
h = c.Handler()
h = applyMiddleware(h, e.middleware...)
} else {
h = func(c Context) error {
e.router.Find(r.Method, getPath(r), c)
h := c.Handler()
h = applyMiddleware(h, e.middleware...)
return h(c)
}
h = applyMiddleware(h, e.premiddleware...)
}
// Execute chain
if err := h(c); err != nil {
e.HTTPErrorHandler(err, c)
}
// Release context
e.pool.Put(c)
}
// Start starts an HTTP server.
func (e *Echo) Start(address string) error {
e.Server.Addr = address
return e.StartServer(e.Server)
}
// StartTLS starts an HTTPS server.
// If `certFile` or `keyFile` is `string` the values are treated as file paths.
// If `certFile` or `keyFile` is `[]byte` the values are treated as the certificate or key as-is.
func (e *Echo) StartTLS(address string, certFile, keyFile interface{}) (err error) {
var cert []byte
if cert, err = filepathOrContent(certFile); err != nil {
return
}
var key []byte
if key, err = filepathOrContent(keyFile); err != nil {
return
}
s := e.TLSServer
s.TLSConfig = new(tls.Config)
s.TLSConfig.Certificates = make([]tls.Certificate, 1)
if s.TLSConfig.Certificates[0], err = tls.X509KeyPair(cert, key); err != nil {
return
}
return e.startTLS(address)
}
func filepathOrContent(fileOrContent interface{}) (content []byte, err error) {
switch v := fileOrContent.(type) {
case string:
return ioutil.ReadFile(v)
case []byte:
return v, nil
default:
return nil, ErrInvalidCertOrKeyType
}
}
// StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org.
func (e *Echo) StartAutoTLS(address string) error {
s := e.TLSServer
s.TLSConfig = new(tls.Config)
s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, acme.ALPNProto)
return e.startTLS(address)
}
func (e *Echo) startTLS(address string) error {
s := e.TLSServer
s.Addr = address
if !e.DisableHTTP2 {
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "h2")
}
return e.StartServer(e.TLSServer)
}
// StartServer starts a custom http server.
func (e *Echo) StartServer(s *http.Server) (err error) {
// Setup
e.colorer.SetOutput(e.Logger.Output())
s.ErrorLog = e.StdLogger
s.Handler = e
if e.Debug {
e.Logger.SetLevel(log.DEBUG)
}
if !e.HideBanner {
e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website))
}
if s.TLSConfig == nil {
if e.Listener == nil {
e.Listener, err = newListener(s.Addr)
if err != nil {
return err
}
}
if !e.HidePort {
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
}
return s.Serve(e.Listener)
}
if e.TLSListener == nil {
l, err := newListener(s.Addr)
if err != nil {
return err
}
e.TLSListener = tls.NewListener(l, s.TLSConfig)
}
if !e.HidePort {
e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
}
return s.Serve(e.TLSListener)
}
// Close immediately stops the server.
// It internally calls `http.Server#Close()`.
func (e *Echo) Close() error {
if err := e.TLSServer.Close(); err != nil {
return err
}
return e.Server.Close()
}
// Shutdown stops the server gracefully.
// It internally calls `http.Server#Shutdown()`.
func (e *Echo) Shutdown(ctx stdContext.Context) error {
if err := e.TLSServer.Shutdown(ctx); err != nil {
return err
}
return e.Server.Shutdown(ctx)
}
// NewHTTPError creates a new HTTPError instance.
func NewHTTPError(code int, message ...interface{}) *HTTPError {
he := &HTTPError{Code: code, Message: http.StatusText(code)}
if len(message) > 0 {
he.Message = message[0]
}
return he
}
// Error makes it compatible with `error` interface.
func (he *HTTPError) Error() string {
return fmt.Sprintf("code=%d, message=%v", he.Code, he.Message)
}
// SetInternal sets error to HTTPError.Internal
func (he *HTTPError) SetInternal(err error) *HTTPError {
he.Internal = err
return he
}
// WrapHandler wraps `http.Handler` into `echo.HandlerFunc`.
func WrapHandler(h http.Handler) HandlerFunc {
return func(c Context) error {
h.ServeHTTP(c.Response(), c.Request())
return nil
}
}
// WrapMiddleware wraps `func(http.Handler) http.Handler` into `echo.MiddlewareFunc`
func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc {
return func(next HandlerFunc) HandlerFunc {
return func(c Context) (err error) {
m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.SetRequest(r)
err = next(c)
})).ServeHTTP(c.Response(), c.Request())
return
}
}
}
func getPath(r *http.Request) string {
path := r.URL.RawPath
if path == "" {
path = r.URL.Path
}
return path
}
func handlerName(h HandlerFunc) string {
t := reflect.ValueOf(h).Type()
if t.Kind() == reflect.Func {
return runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
}
return t.String()
}
// // PathUnescape is wraps `url.PathUnescape`
// func PathUnescape(s string) (string, error) {
// return url.PathUnescape(s)
// }
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
type tcpKeepAliveListener struct {
*net.TCPListener
}
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
if c, err = ln.AcceptTCP(); err != nil {
return
} else if err = c.(*net.TCPConn).SetKeepAlive(true); err != nil {
return
} else if err = c.(*net.TCPConn).SetKeepAlivePeriod(3 * time.Minute); err != nil {
return
}
return
}
func newListener(address string) (*tcpKeepAliveListener, error) {
l, err := net.Listen("tcp", address)
if err != nil {
return nil, err
}
return &tcpKeepAliveListener{l.(*net.TCPListener)}, nil
}
func applyMiddleware(h HandlerFunc, middleware ...MiddlewareFunc) HandlerFunc {
for i := len(middleware) - 1; i >= 0; i-- {
h = middleware[i](h)
}
return h
}

View File

@ -1,13 +0,0 @@
module github.com/labstack/echo/v4
require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/labstack/gommon v0.2.8
github.com/mattn/go-colorable v0.0.9 // indirect
github.com/mattn/go-isatty v0.0.4 // indirect
github.com/stretchr/testify v1.3.0
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4
golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc // indirect
)

View File

@ -1,23 +0,0 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0=
github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8=
github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4/go.mod h1:50wTf68f99/Zt14pr046Tgt3Lp2vLyFZKzbFXTOabXw=
golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664 h1:YbZJ76lQ1BqNhVe7dKTSB67wDrc2VPRR75IyGyyPDX8=
golang.org/x/crypto v0.0.0-20190130090550-b01c7a725664/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc h1:WiYx1rIFmx8c0mXAFtv5D/mHyKe1+jmuP7PViuwqwuQ=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View File

@ -1,121 +0,0 @@
package echo
import (
"net/http"
"path"
)
type (
// Group is a set of sub-routes for a specified route. It can be used for inner
// routes that share a common middleware or functionality that should be separate
// from the parent echo instance while still inheriting from it.
Group struct {
prefix string
middleware []MiddlewareFunc
echo *Echo
}
)
// Use implements `Echo#Use()` for sub-routes within the Group.
func (g *Group) Use(middleware ...MiddlewareFunc) {
g.middleware = append(g.middleware, middleware...)
// Allow all requests to reach the group as they might get dropped if router
// doesn't find a match, making none of the group middleware process.
for _, p := range []string{"", "/*"} {
g.echo.Any(path.Clean(g.prefix+p), func(c Context) error {
return NotFoundHandler(c)
}, g.middleware...)
}
}
// CONNECT implements `Echo#CONNECT()` for sub-routes within the Group.
func (g *Group) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodConnect, path, h, m...)
}
// DELETE implements `Echo#DELETE()` for sub-routes within the Group.
func (g *Group) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodDelete, path, h, m...)
}
// GET implements `Echo#GET()` for sub-routes within the Group.
func (g *Group) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodGet, path, h, m...)
}
// HEAD implements `Echo#HEAD()` for sub-routes within the Group.
func (g *Group) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodHead, path, h, m...)
}
// OPTIONS implements `Echo#OPTIONS()` for sub-routes within the Group.
func (g *Group) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodOptions, path, h, m...)
}
// PATCH implements `Echo#PATCH()` for sub-routes within the Group.
func (g *Group) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodPatch, path, h, m...)
}
// POST implements `Echo#POST()` for sub-routes within the Group.
func (g *Group) POST(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodPost, path, h, m...)
}
// PUT implements `Echo#PUT()` for sub-routes within the Group.
func (g *Group) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodPut, path, h, m...)
}
// TRACE implements `Echo#TRACE()` for sub-routes within the Group.
func (g *Group) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
return g.Add(http.MethodTrace, path, h, m...)
}
// Any implements `Echo#Any()` for sub-routes within the Group.
func (g *Group) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
routes := make([]*Route, len(methods))
for i, m := range methods {
routes[i] = g.Add(m, path, handler, middleware...)
}
return routes
}
// Match implements `Echo#Match()` for sub-routes within the Group.
func (g *Group) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
routes := make([]*Route, len(methods))
for i, m := range methods {
routes[i] = g.Add(m, path, handler, middleware...)
}
return routes
}
// Group creates a new sub-group with prefix and optional sub-group-level middleware.
func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) *Group {
m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
m = append(m, g.middleware...)
m = append(m, middleware...)
return g.echo.Group(g.prefix+prefix, m...)
}
// Static implements `Echo#Static()` for sub-routes within the Group.
func (g *Group) Static(prefix, root string) {
static(g, prefix, root)
}
// File implements `Echo#File()` for sub-routes within the Group.
func (g *Group) File(path, file string) {
g.echo.File(g.prefix+path, file)
}
// Add implements `Echo#Add()` for sub-routes within the Group.
func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
// Combine into a new slice to avoid accidentally passing the same slice for
// multiple routes, which would lead to later add() calls overwriting the
// middleware from earlier calls.
m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
m = append(m, g.middleware...)
m = append(m, middleware...)
return g.echo.Add(method, g.prefix+path, handler, m...)
}

View File

@ -1,41 +0,0 @@
package echo
import (
"io"
"github.com/labstack/gommon/log"
)
type (
// Logger defines the logging interface.
Logger interface {
Output() io.Writer
SetOutput(w io.Writer)
Prefix() string
SetPrefix(p string)
Level() log.Lvl
SetLevel(v log.Lvl)
SetHeader(h string)
Print(i ...interface{})
Printf(format string, args ...interface{})
Printj(j log.JSON)
Debug(i ...interface{})
Debugf(format string, args ...interface{})
Debugj(j log.JSON)
Info(i ...interface{})
Infof(format string, args ...interface{})
Infoj(j log.JSON)
Warn(i ...interface{})
Warnf(format string, args ...interface{})
Warnj(j log.JSON)
Error(i ...interface{})
Errorf(format string, args ...interface{})
Errorj(j log.JSON)
Fatal(i ...interface{})
Fatalj(j log.JSON)
Fatalf(format string, args ...interface{})
Panic(i ...interface{})
Panicj(j log.JSON)
Panicf(format string, args ...interface{})
}
)

View File

@ -1,106 +0,0 @@
package middleware
import (
"encoding/base64"
"strconv"
"strings"
"github.com/labstack/echo/v4"
)
type (
// BasicAuthConfig defines the config for BasicAuth middleware.
BasicAuthConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Validator is a function to validate BasicAuth credentials.
// Required.
Validator BasicAuthValidator
// Realm is a string to define realm attribute of BasicAuth.
// Default value "Restricted".
Realm string
}
// BasicAuthValidator defines a function to validate BasicAuth credentials.
BasicAuthValidator func(string, string, echo.Context) (bool, error)
)
const (
basic = "basic"
defaultRealm = "Restricted"
)
var (
// DefaultBasicAuthConfig is the default BasicAuth middleware config.
DefaultBasicAuthConfig = BasicAuthConfig{
Skipper: DefaultSkipper,
Realm: defaultRealm,
}
)
// BasicAuth returns an BasicAuth middleware.
//
// For valid credentials it calls the next handler.
// For missing or invalid credentials, it sends "401 - Unauthorized" response.
func BasicAuth(fn BasicAuthValidator) echo.MiddlewareFunc {
c := DefaultBasicAuthConfig
c.Validator = fn
return BasicAuthWithConfig(c)
}
// BasicAuthWithConfig returns an BasicAuth middleware with config.
// See `BasicAuth()`.
func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
// Defaults
if config.Validator == nil {
panic("echo: basic-auth middleware requires a validator function")
}
if config.Skipper == nil {
config.Skipper = DefaultBasicAuthConfig.Skipper
}
if config.Realm == "" {
config.Realm = defaultRealm
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
auth := c.Request().Header.Get(echo.HeaderAuthorization)
l := len(basic)
if len(auth) > l+1 && strings.ToLower(auth[:l]) == basic {
b, err := base64.StdEncoding.DecodeString(auth[l+1:])
if err != nil {
return err
}
cred := string(b)
for i := 0; i < len(cred); i++ {
if cred[i] == ':' {
// Verify credentials
valid, err := config.Validator(cred[:i], cred[i+1:], c)
if err != nil {
return err
} else if valid {
return next(c)
}
break
}
}
}
realm := defaultRealm
if config.Realm != defaultRealm {
realm = strconv.Quote(config.Realm)
}
// Need to return `401` for browsers to pop-up login box.
c.Response().Header().Set(echo.HeaderWWWAuthenticate, basic+" realm="+realm)
return echo.ErrUnauthorized
}
}
}

View File

@ -1,107 +0,0 @@
package middleware
import (
"bufio"
"bytes"
"io"
"io/ioutil"
"net"
"net/http"
"github.com/labstack/echo/v4"
)
type (
// BodyDumpConfig defines the config for BodyDump middleware.
BodyDumpConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Handler receives request and response payload.
// Required.
Handler BodyDumpHandler
}
// BodyDumpHandler receives the request and response payload.
BodyDumpHandler func(echo.Context, []byte, []byte)
bodyDumpResponseWriter struct {
io.Writer
http.ResponseWriter
}
)
var (
// DefaultBodyDumpConfig is the default BodyDump middleware config.
DefaultBodyDumpConfig = BodyDumpConfig{
Skipper: DefaultSkipper,
}
)
// BodyDump returns a BodyDump middleware.
//
// BodyLimit middleware captures the request and response payload and calls the
// registered handler.
func BodyDump(handler BodyDumpHandler) echo.MiddlewareFunc {
c := DefaultBodyDumpConfig
c.Handler = handler
return BodyDumpWithConfig(c)
}
// BodyDumpWithConfig returns a BodyDump middleware with config.
// See: `BodyDump()`.
func BodyDumpWithConfig(config BodyDumpConfig) echo.MiddlewareFunc {
// Defaults
if config.Handler == nil {
panic("echo: body-dump middleware requires a handler function")
}
if config.Skipper == nil {
config.Skipper = DefaultBodyDumpConfig.Skipper
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) (err error) {
if config.Skipper(c) {
return next(c)
}
// Request
reqBody := []byte{}
if c.Request().Body != nil { // Read
reqBody, _ = ioutil.ReadAll(c.Request().Body)
}
c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(reqBody)) // Reset
// Response
resBody := new(bytes.Buffer)
mw := io.MultiWriter(c.Response().Writer, resBody)
writer := &bodyDumpResponseWriter{Writer: mw, ResponseWriter: c.Response().Writer}
c.Response().Writer = writer
if err = next(c); err != nil {
c.Error(err)
}
// Callback
config.Handler(c, reqBody, resBody.Bytes())
return
}
}
}
func (w *bodyDumpResponseWriter) WriteHeader(code int) {
w.ResponseWriter.WriteHeader(code)
}
func (w *bodyDumpResponseWriter) Write(b []byte) (int, error) {
return w.Writer.Write(b)
}
func (w *bodyDumpResponseWriter) Flush() {
w.ResponseWriter.(http.Flusher).Flush()
}
func (w *bodyDumpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.ResponseWriter.(http.Hijacker).Hijack()
}

View File

@ -1,117 +0,0 @@
package middleware
import (
"fmt"
"io"
"sync"
"github.com/labstack/echo/v4"
"github.com/labstack/gommon/bytes"
)
type (
// BodyLimitConfig defines the config for BodyLimit middleware.
BodyLimitConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Maximum allowed size for a request body, it can be specified
// as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P.
Limit string `yaml:"limit"`
limit int64
}
limitedReader struct {
BodyLimitConfig
reader io.ReadCloser
read int64
context echo.Context
}
)
var (
// DefaultBodyLimitConfig is the default BodyLimit middleware config.
DefaultBodyLimitConfig = BodyLimitConfig{
Skipper: DefaultSkipper,
}
)
// BodyLimit returns a BodyLimit middleware.
//
// BodyLimit middleware sets the maximum allowed size for a request body, if the
// size exceeds the configured limit, it sends "413 - Request Entity Too Large"
// response. The BodyLimit is determined based on both `Content-Length` request
// header and actual content read, which makes it super secure.
// Limit can be specified as `4x` or `4xB`, where x is one of the multiple from K, M,
// G, T or P.
func BodyLimit(limit string) echo.MiddlewareFunc {
c := DefaultBodyLimitConfig
c.Limit = limit
return BodyLimitWithConfig(c)
}
// BodyLimitWithConfig returns a BodyLimit middleware with config.
// See: `BodyLimit()`.
func BodyLimitWithConfig(config BodyLimitConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultBodyLimitConfig.Skipper
}
limit, err := bytes.Parse(config.Limit)
if err != nil {
panic(fmt.Errorf("echo: invalid body-limit=%s", config.Limit))
}
config.limit = limit
pool := limitedReaderPool(config)
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
// Based on content length
if req.ContentLength > config.limit {
return echo.ErrStatusRequestEntityTooLarge
}
// Based on content read
r := pool.Get().(*limitedReader)
r.Reset(req.Body, c)
defer pool.Put(r)
req.Body = r
return next(c)
}
}
}
func (r *limitedReader) Read(b []byte) (n int, err error) {
n, err = r.reader.Read(b)
r.read += int64(n)
if r.read > r.limit {
return n, echo.ErrStatusRequestEntityTooLarge
}
return
}
func (r *limitedReader) Close() error {
return r.reader.Close()
}
func (r *limitedReader) Reset(reader io.ReadCloser, context echo.Context) {
r.reader = reader
r.context = context
r.read = 0
}
func limitedReaderPool(c BodyLimitConfig) sync.Pool {
return sync.Pool{
New: func() interface{} {
return &limitedReader{BodyLimitConfig: c}
},
}
}

View File

@ -1,118 +0,0 @@
package middleware
import (
"bufio"
"compress/gzip"
"io"
"io/ioutil"
"net"
"net/http"
"strings"
"github.com/labstack/echo/v4"
)
type (
// GzipConfig defines the config for Gzip middleware.
GzipConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Gzip compression level.
// Optional. Default value -1.
Level int `yaml:"level"`
}
gzipResponseWriter struct {
io.Writer
http.ResponseWriter
}
)
const (
gzipScheme = "gzip"
)
var (
// DefaultGzipConfig is the default Gzip middleware config.
DefaultGzipConfig = GzipConfig{
Skipper: DefaultSkipper,
Level: -1,
}
)
// Gzip returns a middleware which compresses HTTP response using gzip compression
// scheme.
func Gzip() echo.MiddlewareFunc {
return GzipWithConfig(DefaultGzipConfig)
}
// GzipWithConfig return Gzip middleware with config.
// See: `Gzip()`.
func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultGzipConfig.Skipper
}
if config.Level == 0 {
config.Level = DefaultGzipConfig.Level
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
res := c.Response()
res.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding)
if strings.Contains(c.Request().Header.Get(echo.HeaderAcceptEncoding), gzipScheme) {
res.Header().Set(echo.HeaderContentEncoding, gzipScheme) // Issue #806
rw := res.Writer
w, err := gzip.NewWriterLevel(rw, config.Level)
if err != nil {
return err
}
defer func() {
if res.Size == 0 {
if res.Header().Get(echo.HeaderContentEncoding) == gzipScheme {
res.Header().Del(echo.HeaderContentEncoding)
}
// We have to reset response to it's pristine state when
// nothing is written to body or error is returned.
// See issue #424, #407.
res.Writer = rw
w.Reset(ioutil.Discard)
}
w.Close()
}()
grw := &gzipResponseWriter{Writer: w, ResponseWriter: rw}
res.Writer = grw
}
return next(c)
}
}
}
func (w *gzipResponseWriter) WriteHeader(code int) {
if code == http.StatusNoContent { // Issue #489
w.ResponseWriter.Header().Del(echo.HeaderContentEncoding)
}
w.Header().Del(echo.HeaderContentLength) // Issue #444
w.ResponseWriter.WriteHeader(code)
}
func (w *gzipResponseWriter) Write(b []byte) (int, error) {
if w.Header().Get(echo.HeaderContentType) == "" {
w.Header().Set(echo.HeaderContentType, http.DetectContentType(b))
}
return w.Writer.Write(b)
}
func (w *gzipResponseWriter) Flush() {
w.Writer.(*gzip.Writer).Flush()
}
func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.ResponseWriter.(http.Hijacker).Hijack()
}

View File

@ -1,147 +0,0 @@
package middleware
import (
"net/http"
"strconv"
"strings"
"github.com/labstack/echo/v4"
)
type (
// CORSConfig defines the config for CORS middleware.
CORSConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// AllowOrigin defines a list of origins that may access the resource.
// Optional. Default value []string{"*"}.
AllowOrigins []string `yaml:"allow_origins"`
// AllowMethods defines a list methods allowed when accessing the resource.
// This is used in response to a preflight request.
// Optional. Default value DefaultCORSConfig.AllowMethods.
AllowMethods []string `yaml:"allow_methods"`
// AllowHeaders defines a list of request headers that can be used when
// making the actual request. This is in response to a preflight request.
// Optional. Default value []string{}.
AllowHeaders []string `yaml:"allow_headers"`
// AllowCredentials indicates whether or not the response to the request
// can be exposed when the credentials flag is true. When used as part of
// a response to a preflight request, this indicates whether or not the
// actual request can be made using credentials.
// Optional. Default value false.
AllowCredentials bool `yaml:"allow_credentials"`
// ExposeHeaders defines a whitelist headers that clients are allowed to
// access.
// Optional. Default value []string{}.
ExposeHeaders []string `yaml:"expose_headers"`
// MaxAge indicates how long (in seconds) the results of a preflight request
// can be cached.
// Optional. Default value 0.
MaxAge int `yaml:"max_age"`
}
)
var (
// DefaultCORSConfig is the default CORS middleware config.
DefaultCORSConfig = CORSConfig{
Skipper: DefaultSkipper,
AllowOrigins: []string{"*"},
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
}
)
// CORS returns a Cross-Origin Resource Sharing (CORS) middleware.
// See: https://developer.mozilla.org/en/docs/Web/HTTP/Access_control_CORS
func CORS() echo.MiddlewareFunc {
return CORSWithConfig(DefaultCORSConfig)
}
// CORSWithConfig returns a CORS middleware with config.
// See: `CORS()`.
func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultCORSConfig.Skipper
}
if len(config.AllowOrigins) == 0 {
config.AllowOrigins = DefaultCORSConfig.AllowOrigins
}
if len(config.AllowMethods) == 0 {
config.AllowMethods = DefaultCORSConfig.AllowMethods
}
allowMethods := strings.Join(config.AllowMethods, ",")
allowHeaders := strings.Join(config.AllowHeaders, ",")
exposeHeaders := strings.Join(config.ExposeHeaders, ",")
maxAge := strconv.Itoa(config.MaxAge)
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
res := c.Response()
origin := req.Header.Get(echo.HeaderOrigin)
allowOrigin := ""
// Check allowed origins
for _, o := range config.AllowOrigins {
if o == "*" && config.AllowCredentials {
allowOrigin = origin
break
}
if o == "*" || o == origin {
allowOrigin = o
break
}
if matchSubdomain(origin, o) {
allowOrigin = origin
break
}
}
// Simple request
if req.Method != http.MethodOptions {
res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
if config.AllowCredentials {
res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
}
if exposeHeaders != "" {
res.Header().Set(echo.HeaderAccessControlExposeHeaders, exposeHeaders)
}
return next(c)
}
// Preflight request
res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestMethod)
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestHeaders)
res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
res.Header().Set(echo.HeaderAccessControlAllowMethods, allowMethods)
if config.AllowCredentials {
res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
}
if allowHeaders != "" {
res.Header().Set(echo.HeaderAccessControlAllowHeaders, allowHeaders)
} else {
h := req.Header.Get(echo.HeaderAccessControlRequestHeaders)
if h != "" {
res.Header().Set(echo.HeaderAccessControlAllowHeaders, h)
}
}
if config.MaxAge > 0 {
res.Header().Set(echo.HeaderAccessControlMaxAge, maxAge)
}
return c.NoContent(http.StatusNoContent)
}
}
}

View File

@ -1,210 +0,0 @@
package middleware
import (
"crypto/subtle"
"errors"
"net/http"
"strings"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/gommon/random"
)
type (
// CSRFConfig defines the config for CSRF middleware.
CSRFConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// TokenLength is the length of the generated token.
TokenLength uint8 `yaml:"token_length"`
// Optional. Default value 32.
// TokenLookup is a string in the form of "<source>:<key>" that is used
// to extract token from the request.
// Optional. Default value "header:X-CSRF-Token".
// Possible values:
// - "header:<name>"
// - "form:<name>"
// - "query:<name>"
TokenLookup string `yaml:"token_lookup"`
// Context key to store generated CSRF token into context.
// Optional. Default value "csrf".
ContextKey string `yaml:"context_key"`
// Name of the CSRF cookie. This cookie will store CSRF token.
// Optional. Default value "csrf".
CookieName string `yaml:"cookie_name"`
// Domain of the CSRF cookie.
// Optional. Default value none.
CookieDomain string `yaml:"cookie_domain"`
// Path of the CSRF cookie.
// Optional. Default value none.
CookiePath string `yaml:"cookie_path"`
// Max age (in seconds) of the CSRF cookie.
// Optional. Default value 86400 (24hr).
CookieMaxAge int `yaml:"cookie_max_age"`
// Indicates if CSRF cookie is secure.
// Optional. Default value false.
CookieSecure bool `yaml:"cookie_secure"`
// Indicates if CSRF cookie is HTTP only.
// Optional. Default value false.
CookieHTTPOnly bool `yaml:"cookie_http_only"`
}
// csrfTokenExtractor defines a function that takes `echo.Context` and returns
// either a token or an error.
csrfTokenExtractor func(echo.Context) (string, error)
)
var (
// DefaultCSRFConfig is the default CSRF middleware config.
DefaultCSRFConfig = CSRFConfig{
Skipper: DefaultSkipper,
TokenLength: 32,
TokenLookup: "header:" + echo.HeaderXCSRFToken,
ContextKey: "csrf",
CookieName: "_csrf",
CookieMaxAge: 86400,
}
)
// CSRF returns a Cross-Site Request Forgery (CSRF) middleware.
// See: https://en.wikipedia.org/wiki/Cross-site_request_forgery
func CSRF() echo.MiddlewareFunc {
c := DefaultCSRFConfig
return CSRFWithConfig(c)
}
// CSRFWithConfig returns a CSRF middleware with config.
// See `CSRF()`.
func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultCSRFConfig.Skipper
}
if config.TokenLength == 0 {
config.TokenLength = DefaultCSRFConfig.TokenLength
}
if config.TokenLookup == "" {
config.TokenLookup = DefaultCSRFConfig.TokenLookup
}
if config.ContextKey == "" {
config.ContextKey = DefaultCSRFConfig.ContextKey
}
if config.CookieName == "" {
config.CookieName = DefaultCSRFConfig.CookieName
}
if config.CookieMaxAge == 0 {
config.CookieMaxAge = DefaultCSRFConfig.CookieMaxAge
}
// Initialize
parts := strings.Split(config.TokenLookup, ":")
extractor := csrfTokenFromHeader(parts[1])
switch parts[0] {
case "form":
extractor = csrfTokenFromForm(parts[1])
case "query":
extractor = csrfTokenFromQuery(parts[1])
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
k, err := c.Cookie(config.CookieName)
token := ""
// Generate token
if err != nil {
token = random.String(config.TokenLength)
} else {
// Reuse token
token = k.Value
}
switch req.Method {
case http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace:
default:
// Validate token only for requests which are not defined as 'safe' by RFC7231
clientToken, err := extractor(c)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if !validateCSRFToken(token, clientToken) {
return echo.NewHTTPError(http.StatusForbidden, "invalid csrf token")
}
}
// Set CSRF cookie
cookie := new(http.Cookie)
cookie.Name = config.CookieName
cookie.Value = token
if config.CookiePath != "" {
cookie.Path = config.CookiePath
}
if config.CookieDomain != "" {
cookie.Domain = config.CookieDomain
}
cookie.Expires = time.Now().Add(time.Duration(config.CookieMaxAge) * time.Second)
cookie.Secure = config.CookieSecure
cookie.HttpOnly = config.CookieHTTPOnly
c.SetCookie(cookie)
// Store token in the context
c.Set(config.ContextKey, token)
// Protect clients from caching the response
c.Response().Header().Add(echo.HeaderVary, echo.HeaderCookie)
return next(c)
}
}
}
// csrfTokenFromForm returns a `csrfTokenExtractor` that extracts token from the
// provided request header.
func csrfTokenFromHeader(header string) csrfTokenExtractor {
return func(c echo.Context) (string, error) {
return c.Request().Header.Get(header), nil
}
}
// csrfTokenFromForm returns a `csrfTokenExtractor` that extracts token from the
// provided form parameter.
func csrfTokenFromForm(param string) csrfTokenExtractor {
return func(c echo.Context) (string, error) {
token := c.FormValue(param)
if token == "" {
return "", errors.New("missing csrf token in the form parameter")
}
return token, nil
}
}
// csrfTokenFromQuery returns a `csrfTokenExtractor` that extracts token from the
// provided query parameter.
func csrfTokenFromQuery(param string) csrfTokenExtractor {
return func(c echo.Context) (string, error) {
token := c.QueryParam(param)
if token == "" {
return "", errors.New("missing csrf token in the query string")
}
return token, nil
}
}
func validateCSRFToken(token, clientToken string) bool {
return subtle.ConstantTimeCompare([]byte(token), []byte(clientToken)) == 1
}

View File

@ -1,227 +0,0 @@
package middleware
import (
"fmt"
"net/http"
"reflect"
"strings"
"github.com/dgrijalva/jwt-go"
"github.com/labstack/echo/v4"
)
type (
// JWTConfig defines the config for JWT middleware.
JWTConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// BeforeFunc defines a function which is executed just before the middleware.
BeforeFunc BeforeFunc
// SuccessHandler defines a function which is executed for a valid token.
SuccessHandler JWTSuccessHandler
// ErrorHandler defines a function which is executed for an invalid token.
// It may be used to define a custom JWT error.
ErrorHandler JWTErrorHandler
// Signing key to validate token.
// Required.
SigningKey interface{}
// Signing method, used to check token signing method.
// Optional. Default value HS256.
SigningMethod string
// Context key to store user information from the token into context.
// Optional. Default value "user".
ContextKey string
// Claims are extendable claims data defining token content.
// Optional. Default value jwt.MapClaims
Claims jwt.Claims
// TokenLookup is a string in the form of "<source>:<name>" that is used
// to extract token from the request.
// Optional. Default value "header:Authorization".
// Possible values:
// - "header:<name>"
// - "query:<name>"
// - "cookie:<name>"
TokenLookup string
// AuthScheme to be used in the Authorization header.
// Optional. Default value "Bearer".
AuthScheme string
keyFunc jwt.Keyfunc
}
// JWTSuccessHandler defines a function which is executed for a valid token.
JWTSuccessHandler func(echo.Context)
// JWTErrorHandler defines a function which is executed for an invalid token.
JWTErrorHandler func(error) error
jwtExtractor func(echo.Context) (string, error)
)
// Algorithms
const (
AlgorithmHS256 = "HS256"
)
// Errors
var (
ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt")
)
var (
// DefaultJWTConfig is the default JWT auth middleware config.
DefaultJWTConfig = JWTConfig{
Skipper: DefaultSkipper,
SigningMethod: AlgorithmHS256,
ContextKey: "user",
TokenLookup: "header:" + echo.HeaderAuthorization,
AuthScheme: "Bearer",
Claims: jwt.MapClaims{},
}
)
// JWT returns a JSON Web Token (JWT) auth middleware.
//
// For valid token, it sets the user in context and calls next handler.
// For invalid token, it returns "401 - Unauthorized" error.
// For missing token, it returns "400 - Bad Request" error.
//
// See: https://jwt.io/introduction
// See `JWTConfig.TokenLookup`
func JWT(key interface{}) echo.MiddlewareFunc {
c := DefaultJWTConfig
c.SigningKey = key
return JWTWithConfig(c)
}
// JWTWithConfig returns a JWT auth middleware with config.
// See: `JWT()`.
func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultJWTConfig.Skipper
}
if config.SigningKey == nil {
panic("echo: jwt middleware requires signing key")
}
if config.SigningMethod == "" {
config.SigningMethod = DefaultJWTConfig.SigningMethod
}
if config.ContextKey == "" {
config.ContextKey = DefaultJWTConfig.ContextKey
}
if config.Claims == nil {
config.Claims = DefaultJWTConfig.Claims
}
if config.TokenLookup == "" {
config.TokenLookup = DefaultJWTConfig.TokenLookup
}
if config.AuthScheme == "" {
config.AuthScheme = DefaultJWTConfig.AuthScheme
}
config.keyFunc = func(t *jwt.Token) (interface{}, error) {
// Check the signing method
if t.Method.Alg() != config.SigningMethod {
return nil, fmt.Errorf("unexpected jwt signing method=%v", t.Header["alg"])
}
return config.SigningKey, nil
}
// Initialize
parts := strings.Split(config.TokenLookup, ":")
extractor := jwtFromHeader(parts[1], config.AuthScheme)
switch parts[0] {
case "query":
extractor = jwtFromQuery(parts[1])
case "cookie":
extractor = jwtFromCookie(parts[1])
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
if config.BeforeFunc != nil {
config.BeforeFunc(c)
}
auth, err := extractor(c)
if err != nil {
if config.ErrorHandler != nil {
return config.ErrorHandler(err)
}
return err
}
token := new(jwt.Token)
// Issue #647, #656
if _, ok := config.Claims.(jwt.MapClaims); ok {
token, err = jwt.Parse(auth, config.keyFunc)
} else {
t := reflect.ValueOf(config.Claims).Type().Elem()
claims := reflect.New(t).Interface().(jwt.Claims)
token, err = jwt.ParseWithClaims(auth, claims, config.keyFunc)
}
if err == nil && token.Valid {
// Store user information from token into context.
c.Set(config.ContextKey, token)
if config.SuccessHandler != nil {
config.SuccessHandler(c)
}
return next(c)
}
if config.ErrorHandler != nil {
return config.ErrorHandler(err)
}
return &echo.HTTPError{
Code: http.StatusUnauthorized,
Message: "invalid or expired jwt",
Internal: err,
}
}
}
}
// jwtFromHeader returns a `jwtExtractor` that extracts token from the request header.
func jwtFromHeader(header string, authScheme string) jwtExtractor {
return func(c echo.Context) (string, error) {
auth := c.Request().Header.Get(header)
l := len(authScheme)
if len(auth) > l+1 && auth[:l] == authScheme {
return auth[l+1:], nil
}
return "", ErrJWTMissing
}
}
// jwtFromQuery returns a `jwtExtractor` that extracts token from the query string.
func jwtFromQuery(param string) jwtExtractor {
return func(c echo.Context) (string, error) {
token := c.QueryParam(param)
if token == "" {
return "", ErrJWTMissing
}
return token, nil
}
}
// jwtFromCookie returns a `jwtExtractor` that extracts token from the named cookie.
func jwtFromCookie(name string) jwtExtractor {
return func(c echo.Context) (string, error) {
cookie, err := c.Cookie(name)
if err != nil {
return "", ErrJWTMissing
}
return cookie.Value, nil
}
}

View File

@ -1,150 +0,0 @@
package middleware
import (
"errors"
"net/http"
"strings"
"github.com/labstack/echo/v4"
)
type (
// KeyAuthConfig defines the config for KeyAuth middleware.
KeyAuthConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// KeyLookup is a string in the form of "<source>:<name>" that is used
// to extract key from the request.
// Optional. Default value "header:Authorization".
// Possible values:
// - "header:<name>"
// - "query:<name>"
// - "form:<name>"
KeyLookup string `yaml:"key_lookup"`
// AuthScheme to be used in the Authorization header.
// Optional. Default value "Bearer".
AuthScheme string
// Validator is a function to validate key.
// Required.
Validator KeyAuthValidator
}
// KeyAuthValidator defines a function to validate KeyAuth credentials.
KeyAuthValidator func(string, echo.Context) (bool, error)
keyExtractor func(echo.Context) (string, error)
)
var (
// DefaultKeyAuthConfig is the default KeyAuth middleware config.
DefaultKeyAuthConfig = KeyAuthConfig{
Skipper: DefaultSkipper,
KeyLookup: "header:" + echo.HeaderAuthorization,
AuthScheme: "Bearer",
}
)
// KeyAuth returns an KeyAuth middleware.
//
// For valid key it calls the next handler.
// For invalid key, it sends "401 - Unauthorized" response.
// For missing key, it sends "400 - Bad Request" response.
func KeyAuth(fn KeyAuthValidator) echo.MiddlewareFunc {
c := DefaultKeyAuthConfig
c.Validator = fn
return KeyAuthWithConfig(c)
}
// KeyAuthWithConfig returns an KeyAuth middleware with config.
// See `KeyAuth()`.
func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultKeyAuthConfig.Skipper
}
// Defaults
if config.AuthScheme == "" {
config.AuthScheme = DefaultKeyAuthConfig.AuthScheme
}
if config.KeyLookup == "" {
config.KeyLookup = DefaultKeyAuthConfig.KeyLookup
}
if config.Validator == nil {
panic("echo: key-auth middleware requires a validator function")
}
// Initialize
parts := strings.Split(config.KeyLookup, ":")
extractor := keyFromHeader(parts[1], config.AuthScheme)
switch parts[0] {
case "query":
extractor = keyFromQuery(parts[1])
case "form":
extractor = keyFromForm(parts[1])
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
// Extract and verify key
key, err := extractor(c)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
valid, err := config.Validator(key, c)
if err != nil {
return err
} else if valid {
return next(c)
}
return echo.ErrUnauthorized
}
}
}
// keyFromHeader returns a `keyExtractor` that extracts key from the request header.
func keyFromHeader(header string, authScheme string) keyExtractor {
return func(c echo.Context) (string, error) {
auth := c.Request().Header.Get(header)
if auth == "" {
return "", errors.New("missing key in request header")
}
if header == echo.HeaderAuthorization {
l := len(authScheme)
if len(auth) > l+1 && auth[:l] == authScheme {
return auth[l+1:], nil
}
return "", errors.New("invalid key in the request header")
}
return auth, nil
}
}
// keyFromQuery returns a `keyExtractor` that extracts key from the query string.
func keyFromQuery(param string) keyExtractor {
return func(c echo.Context) (string, error) {
key := c.QueryParam(param)
if key == "" {
return "", errors.New("missing key in the query string")
}
return key, nil
}
}
// keyFromForm returns a `keyExtractor` that extracts key from the form.
func keyFromForm(param string) keyExtractor {
return func(c echo.Context) (string, error) {
key := c.FormValue(param)
if key == "" {
return "", errors.New("missing key in the form")
}
return key, nil
}
}

View File

@ -1,217 +0,0 @@
package middleware
import (
"bytes"
"io"
"os"
"strconv"
"strings"
"sync"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/gommon/color"
"github.com/valyala/fasttemplate"
)
type (
// LoggerConfig defines the config for Logger middleware.
LoggerConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Tags to constructed the logger format.
//
// - time_unix
// - time_unix_nano
// - time_rfc3339
// - time_rfc3339_nano
// - time_custom
// - id (Request ID)
// - remote_ip
// - uri
// - host
// - method
// - path
// - protocol
// - referer
// - user_agent
// - status
// - error
// - latency (In nanoseconds)
// - latency_human (Human readable)
// - bytes_in (Bytes received)
// - bytes_out (Bytes sent)
// - header:<NAME>
// - query:<NAME>
// - form:<NAME>
//
// Example "${remote_ip} ${status}"
//
// Optional. Default value DefaultLoggerConfig.Format.
Format string `yaml:"format"`
// Optional. Default value DefaultLoggerConfig.CustomTimeFormat.
CustomTimeFormat string `yaml:"custom_time_format"`
// Output is a writer where logs in JSON format are written.
// Optional. Default value os.Stdout.
Output io.Writer
template *fasttemplate.Template
colorer *color.Color
pool *sync.Pool
}
)
var (
// DefaultLoggerConfig is the default Logger middleware config.
DefaultLoggerConfig = LoggerConfig{
Skipper: DefaultSkipper,
Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` +
`"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` +
`"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` +
`,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n",
CustomTimeFormat: "2006-01-02 15:04:05.00000",
Output: os.Stdout,
colorer: color.New(),
}
)
// Logger returns a middleware that logs HTTP requests.
func Logger() echo.MiddlewareFunc {
return LoggerWithConfig(DefaultLoggerConfig)
}
// LoggerWithConfig returns a Logger middleware with config.
// See: `Logger()`.
func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultLoggerConfig.Skipper
}
if config.Format == "" {
config.Format = DefaultLoggerConfig.Format
}
if config.Output == nil {
config.Output = DefaultLoggerConfig.Output
}
config.template = fasttemplate.New(config.Format, "${", "}")
config.colorer = color.New()
config.colorer.SetOutput(config.Output)
config.pool = &sync.Pool{
New: func() interface{} {
return bytes.NewBuffer(make([]byte, 256))
},
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) (err error) {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
res := c.Response()
start := time.Now()
if err = next(c); err != nil {
c.Error(err)
}
stop := time.Now()
buf := config.pool.Get().(*bytes.Buffer)
buf.Reset()
defer config.pool.Put(buf)
if _, err = config.template.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) {
switch tag {
case "time_unix":
return buf.WriteString(strconv.FormatInt(time.Now().Unix(), 10))
case "time_unix_nano":
return buf.WriteString(strconv.FormatInt(time.Now().UnixNano(), 10))
case "time_rfc3339":
return buf.WriteString(time.Now().Format(time.RFC3339))
case "time_rfc3339_nano":
return buf.WriteString(time.Now().Format(time.RFC3339Nano))
case "time_custom":
return buf.WriteString(time.Now().Format(config.CustomTimeFormat))
case "id":
id := req.Header.Get(echo.HeaderXRequestID)
if id == "" {
id = res.Header().Get(echo.HeaderXRequestID)
}
return buf.WriteString(id)
case "remote_ip":
return buf.WriteString(c.RealIP())
case "host":
return buf.WriteString(req.Host)
case "uri":
return buf.WriteString(req.RequestURI)
case "method":
return buf.WriteString(req.Method)
case "path":
p := req.URL.Path
if p == "" {
p = "/"
}
return buf.WriteString(p)
case "protocol":
return buf.WriteString(req.Proto)
case "referer":
return buf.WriteString(req.Referer())
case "user_agent":
return buf.WriteString(req.UserAgent())
case "status":
n := res.Status
s := config.colorer.Green(n)
switch {
case n >= 500:
s = config.colorer.Red(n)
case n >= 400:
s = config.colorer.Yellow(n)
case n >= 300:
s = config.colorer.Cyan(n)
}
return buf.WriteString(s)
case "error":
if err != nil {
return buf.WriteString(err.Error())
}
case "latency":
l := stop.Sub(start)
return buf.WriteString(strconv.FormatInt(int64(l), 10))
case "latency_human":
return buf.WriteString(stop.Sub(start).String())
case "bytes_in":
cl := req.Header.Get(echo.HeaderContentLength)
if cl == "" {
cl = "0"
}
return buf.WriteString(cl)
case "bytes_out":
return buf.WriteString(strconv.FormatInt(res.Size, 10))
default:
switch {
case strings.HasPrefix(tag, "header:"):
return buf.Write([]byte(c.Request().Header.Get(tag[7:])))
case strings.HasPrefix(tag, "query:"):
return buf.Write([]byte(c.QueryParam(tag[6:])))
case strings.HasPrefix(tag, "form:"):
return buf.Write([]byte(c.FormValue(tag[5:])))
case strings.HasPrefix(tag, "cookie:"):
cookie, err := c.Cookie(tag[7:])
if err == nil {
return buf.Write([]byte(cookie.Value))
}
}
}
return 0, nil
}); err != nil {
return
}
_, err = config.Output.Write(buf.Bytes())
return
}
}
}

View File

@ -1,92 +0,0 @@
package middleware
import (
"net/http"
"github.com/labstack/echo/v4"
)
type (
// MethodOverrideConfig defines the config for MethodOverride middleware.
MethodOverrideConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Getter is a function that gets overridden method from the request.
// Optional. Default values MethodFromHeader(echo.HeaderXHTTPMethodOverride).
Getter MethodOverrideGetter
}
// MethodOverrideGetter is a function that gets overridden method from the request
MethodOverrideGetter func(echo.Context) string
)
var (
// DefaultMethodOverrideConfig is the default MethodOverride middleware config.
DefaultMethodOverrideConfig = MethodOverrideConfig{
Skipper: DefaultSkipper,
Getter: MethodFromHeader(echo.HeaderXHTTPMethodOverride),
}
)
// MethodOverride returns a MethodOverride middleware.
// MethodOverride middleware checks for the overridden method from the request and
// uses it instead of the original method.
//
// For security reasons, only `POST` method can be overridden.
func MethodOverride() echo.MiddlewareFunc {
return MethodOverrideWithConfig(DefaultMethodOverrideConfig)
}
// MethodOverrideWithConfig returns a MethodOverride middleware with config.
// See: `MethodOverride()`.
func MethodOverrideWithConfig(config MethodOverrideConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultMethodOverrideConfig.Skipper
}
if config.Getter == nil {
config.Getter = DefaultMethodOverrideConfig.Getter
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
if req.Method == http.MethodPost {
m := config.Getter(c)
if m != "" {
req.Method = m
}
}
return next(c)
}
}
}
// MethodFromHeader is a `MethodOverrideGetter` that gets overridden method from
// the request header.
func MethodFromHeader(header string) MethodOverrideGetter {
return func(c echo.Context) string {
return c.Request().Header.Get(header)
}
}
// MethodFromForm is a `MethodOverrideGetter` that gets overridden method from the
// form parameter.
func MethodFromForm(param string) MethodOverrideGetter {
return func(c echo.Context) string {
return c.FormValue(param)
}
}
// MethodFromQuery is a `MethodOverrideGetter` that gets overridden method from
// the query parameter.
func MethodFromQuery(param string) MethodOverrideGetter {
return func(c echo.Context) string {
return c.QueryParam(param)
}
}

View File

@ -1,38 +0,0 @@
package middleware
import (
"regexp"
"strconv"
"strings"
"github.com/labstack/echo/v4"
)
type (
// Skipper defines a function to skip middleware. Returning true skips processing
// the middleware.
Skipper func(echo.Context) bool
// BeforeFunc defines a function which is executed just before the middleware.
BeforeFunc func(echo.Context)
)
func captureTokens(pattern *regexp.Regexp, input string) *strings.Replacer {
groups := pattern.FindAllStringSubmatch(input, -1)
if groups == nil {
return nil
}
values := groups[0][1:]
replace := make([]string, 2*len(values))
for i, v := range values {
j := 2 * i
replace[j] = "$" + strconv.Itoa(i+1)
replace[j+1] = v
}
return strings.NewReplacer(replace...)
}
// DefaultSkipper returns false which processes the middleware.
func DefaultSkipper(echo.Context) bool {
return false
}

View File

@ -1,258 +0,0 @@
package middleware
import (
"fmt"
"io"
"math/rand"
"net"
"net/http"
"net/url"
"regexp"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/labstack/echo/v4"
)
// TODO: Handle TLS proxy
type (
// ProxyConfig defines the config for Proxy middleware.
ProxyConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Balancer defines a load balancing technique.
// Required.
Balancer ProxyBalancer
// Rewrite defines URL path rewrite rules. The values captured in asterisk can be
// retrieved by index e.g. $1, $2 and so on.
// Examples:
// "/old": "/new",
// "/api/*": "/$1",
// "/js/*": "/public/javascripts/$1",
// "/users/*/orders/*": "/user/$1/order/$2",
Rewrite map[string]string
// Context key to store selected ProxyTarget into context.
// Optional. Default value "target".
ContextKey string
// To customize the transport to remote.
// Examples: If custom TLS certificates are required.
Transport http.RoundTripper
rewriteRegex map[*regexp.Regexp]string
}
// ProxyTarget defines the upstream target.
ProxyTarget struct {
Name string
URL *url.URL
Meta echo.Map
}
// ProxyBalancer defines an interface to implement a load balancing technique.
ProxyBalancer interface {
AddTarget(*ProxyTarget) bool
RemoveTarget(string) bool
Next(echo.Context) *ProxyTarget
}
commonBalancer struct {
targets []*ProxyTarget
mutex sync.RWMutex
}
// RandomBalancer implements a random load balancing technique.
randomBalancer struct {
*commonBalancer
random *rand.Rand
}
// RoundRobinBalancer implements a round-robin load balancing technique.
roundRobinBalancer struct {
*commonBalancer
i uint32
}
)
var (
// DefaultProxyConfig is the default Proxy middleware config.
DefaultProxyConfig = ProxyConfig{
Skipper: DefaultSkipper,
ContextKey: "target",
}
)
func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
in, _, err := c.Response().Hijack()
if err != nil {
c.Error(fmt.Errorf("proxy raw, hijack error=%v, url=%s", t.URL, err))
return
}
defer in.Close()
out, err := net.Dial("tcp", t.URL.Host)
if err != nil {
he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", t.URL, err))
c.Error(he)
return
}
defer out.Close()
// Write header
err = r.Write(out)
if err != nil {
he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request header copy error=%v, url=%s", t.URL, err))
c.Error(he)
return
}
errCh := make(chan error, 2)
cp := func(dst io.Writer, src io.Reader) {
_, err = io.Copy(dst, src)
errCh <- err
}
go cp(out, in)
go cp(in, out)
err = <-errCh
if err != nil && err != io.EOF {
c.Logger().Errorf("proxy raw, copy body error=%v, url=%s", t.URL, err)
}
})
}
// NewRandomBalancer returns a random proxy balancer.
func NewRandomBalancer(targets []*ProxyTarget) ProxyBalancer {
b := &randomBalancer{commonBalancer: new(commonBalancer)}
b.targets = targets
return b
}
// NewRoundRobinBalancer returns a round-robin proxy balancer.
func NewRoundRobinBalancer(targets []*ProxyTarget) ProxyBalancer {
b := &roundRobinBalancer{commonBalancer: new(commonBalancer)}
b.targets = targets
return b
}
// AddTarget adds an upstream target to the list.
func (b *commonBalancer) AddTarget(target *ProxyTarget) bool {
for _, t := range b.targets {
if t.Name == target.Name {
return false
}
}
b.mutex.Lock()
defer b.mutex.Unlock()
b.targets = append(b.targets, target)
return true
}
// RemoveTarget removes an upstream target from the list.
func (b *commonBalancer) RemoveTarget(name string) bool {
b.mutex.Lock()
defer b.mutex.Unlock()
for i, t := range b.targets {
if t.Name == name {
b.targets = append(b.targets[:i], b.targets[i+1:]...)
return true
}
}
return false
}
// Next randomly returns an upstream target.
func (b *randomBalancer) Next(c echo.Context) *ProxyTarget {
if b.random == nil {
b.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
}
b.mutex.RLock()
defer b.mutex.RUnlock()
return b.targets[b.random.Intn(len(b.targets))]
}
// Next returns an upstream target using round-robin technique.
func (b *roundRobinBalancer) Next(c echo.Context) *ProxyTarget {
b.i = b.i % uint32(len(b.targets))
t := b.targets[b.i]
atomic.AddUint32(&b.i, 1)
return t
}
// Proxy returns a Proxy middleware.
//
// Proxy middleware forwards the request to upstream server using a configured load balancing technique.
func Proxy(balancer ProxyBalancer) echo.MiddlewareFunc {
c := DefaultProxyConfig
c.Balancer = balancer
return ProxyWithConfig(c)
}
// ProxyWithConfig returns a Proxy middleware with config.
// See: `Proxy()`
func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultProxyConfig.Skipper
}
if config.Balancer == nil {
panic("echo: proxy middleware requires balancer")
}
config.rewriteRegex = map[*regexp.Regexp]string{}
// Initialize
for k, v := range config.Rewrite {
k = strings.Replace(k, "*", "(\\S*)", -1)
config.rewriteRegex[regexp.MustCompile(k)] = v
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) (err error) {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
res := c.Response()
tgt := config.Balancer.Next(c)
c.Set(config.ContextKey, tgt)
// Rewrite
for k, v := range config.rewriteRegex {
replacer := captureTokens(k, req.URL.Path)
if replacer != nil {
req.URL.Path = replacer.Replace(v)
}
}
// Fix header
if req.Header.Get(echo.HeaderXRealIP) == "" {
req.Header.Set(echo.HeaderXRealIP, c.RealIP())
}
if req.Header.Get(echo.HeaderXForwardedProto) == "" {
req.Header.Set(echo.HeaderXForwardedProto, c.Scheme())
}
if c.IsWebSocket() && req.Header.Get(echo.HeaderXForwardedFor) == "" { // For HTTP, it is automatically set by Go HTTP reverse proxy.
req.Header.Set(echo.HeaderXForwardedFor, c.RealIP())
}
// Proxy
switch {
case c.IsWebSocket():
proxyRaw(tgt, c).ServeHTTP(res, req)
case req.Header.Get(echo.HeaderAccept) == "text/event-stream":
default:
proxyHTTP(tgt, c, config).ServeHTTP(res, req)
}
return
}
}
}

View File

@ -1,25 +0,0 @@
// +build go1.11
package middleware
import (
"fmt"
"net/http"
"net/http/httputil"
"github.com/labstack/echo/v4"
)
func proxyHTTP(tgt *ProxyTarget, c echo.Context, config ProxyConfig) http.Handler {
proxy := httputil.NewSingleHostReverseProxy(tgt.URL)
proxy.ErrorHandler = func(resp http.ResponseWriter, req *http.Request, err error) {
desc := tgt.URL.String()
if tgt.Name != "" {
desc = fmt.Sprintf("%s(%s)", tgt.Name, tgt.URL.String())
}
c.Logger().Errorf("remote %s unreachable, could not forward: %v", desc, err)
c.Error(echo.NewHTTPError(http.StatusServiceUnavailable))
}
proxy.Transport = config.Transport
return proxy
}

View File

@ -1,14 +0,0 @@
// +build !go1.11
package middleware
import (
"net/http"
"net/http/httputil"
"github.com/labstack/echo/v4"
)
func proxyHTTP(t *ProxyTarget, c echo.Context, config ProxyConfig) http.Handler {
return httputil.NewSingleHostReverseProxy(t.URL)
}

View File

@ -1,81 +0,0 @@
package middleware
import (
"fmt"
"runtime"
"github.com/labstack/echo/v4"
)
type (
// RecoverConfig defines the config for Recover middleware.
RecoverConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Size of the stack to be printed.
// Optional. Default value 4KB.
StackSize int `yaml:"stack_size"`
// DisableStackAll disables formatting stack traces of all other goroutines
// into buffer after the trace for the current goroutine.
// Optional. Default value false.
DisableStackAll bool `yaml:"disable_stack_all"`
// DisablePrintStack disables printing stack trace.
// Optional. Default value as false.
DisablePrintStack bool `yaml:"disable_print_stack"`
}
)
var (
// DefaultRecoverConfig is the default Recover middleware config.
DefaultRecoverConfig = RecoverConfig{
Skipper: DefaultSkipper,
StackSize: 4 << 10, // 4 KB
DisableStackAll: false,
DisablePrintStack: false,
}
)
// Recover returns a middleware which recovers from panics anywhere in the chain
// and handles the control to the centralized HTTPErrorHandler.
func Recover() echo.MiddlewareFunc {
return RecoverWithConfig(DefaultRecoverConfig)
}
// RecoverWithConfig returns a Recover middleware with config.
// See: `Recover()`.
func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultRecoverConfig.Skipper
}
if config.StackSize == 0 {
config.StackSize = DefaultRecoverConfig.StackSize
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
defer func() {
if r := recover(); r != nil {
err, ok := r.(error)
if !ok {
err = fmt.Errorf("%v", r)
}
stack := make([]byte, config.StackSize)
length := runtime.Stack(stack, !config.DisableStackAll)
if !config.DisablePrintStack {
c.Logger().Printf("[PANIC RECOVER] %v %s\n", err, stack[:length])
}
c.Error(err)
}
}()
return next(c)
}
}
}

View File

@ -1,153 +0,0 @@
package middleware
import (
"net/http"
"github.com/labstack/echo/v4"
)
// RedirectConfig defines the config for Redirect middleware.
type RedirectConfig struct {
// Skipper defines a function to skip middleware.
Skipper
// Status code to be used when redirecting the request.
// Optional. Default value http.StatusMovedPermanently.
Code int `yaml:"code"`
}
// redirectLogic represents a function that given a scheme, host and uri
// can both: 1) determine if redirect is needed (will set ok accordingly) and
// 2) return the appropriate redirect url.
type redirectLogic func(scheme, host, uri string) (ok bool, url string)
const www = "www."
// DefaultRedirectConfig is the default Redirect middleware config.
var DefaultRedirectConfig = RedirectConfig{
Skipper: DefaultSkipper,
Code: http.StatusMovedPermanently,
}
// HTTPSRedirect redirects http requests to https.
// For example, http://labstack.com will be redirect to https://labstack.com.
//
// Usage `Echo#Pre(HTTPSRedirect())`
func HTTPSRedirect() echo.MiddlewareFunc {
return HTTPSRedirectWithConfig(DefaultRedirectConfig)
}
// HTTPSRedirectWithConfig returns an HTTPSRedirect middleware with config.
// See `HTTPSRedirect()`.
func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
if ok = scheme != "https"; ok {
url = "https://" + host + uri
}
return
})
}
// HTTPSWWWRedirect redirects http requests to https www.
// For example, http://labstack.com will be redirect to https://www.labstack.com.
//
// Usage `Echo#Pre(HTTPSWWWRedirect())`
func HTTPSWWWRedirect() echo.MiddlewareFunc {
return HTTPSWWWRedirectWithConfig(DefaultRedirectConfig)
}
// HTTPSWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
// See `HTTPSWWWRedirect()`.
func HTTPSWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
if ok = scheme != "https" && host[:4] != www; ok {
url = "https://www." + host + uri
}
return
})
}
// HTTPSNonWWWRedirect redirects http requests to https non www.
// For example, http://www.labstack.com will be redirect to https://labstack.com.
//
// Usage `Echo#Pre(HTTPSNonWWWRedirect())`
func HTTPSNonWWWRedirect() echo.MiddlewareFunc {
return HTTPSNonWWWRedirectWithConfig(DefaultRedirectConfig)
}
// HTTPSNonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
// See `HTTPSNonWWWRedirect()`.
func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
if ok = scheme != "https"; ok {
if host[:4] == www {
host = host[4:]
}
url = "https://" + host + uri
}
return
})
}
// WWWRedirect redirects non www requests to www.
// For example, http://labstack.com will be redirect to http://www.labstack.com.
//
// Usage `Echo#Pre(WWWRedirect())`
func WWWRedirect() echo.MiddlewareFunc {
return WWWRedirectWithConfig(DefaultRedirectConfig)
}
// WWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
// See `WWWRedirect()`.
func WWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
if ok = host[:4] != www; ok {
url = scheme + "://www." + host + uri
}
return
})
}
// NonWWWRedirect redirects www requests to non www.
// For example, http://www.labstack.com will be redirect to http://labstack.com.
//
// Usage `Echo#Pre(NonWWWRedirect())`
func NonWWWRedirect() echo.MiddlewareFunc {
return NonWWWRedirectWithConfig(DefaultRedirectConfig)
}
// NonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
// See `NonWWWRedirect()`.
func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
if ok = host[:4] == www; ok {
url = scheme + "://" + host[4:] + uri
}
return
})
}
func redirect(config RedirectConfig, cb redirectLogic) echo.MiddlewareFunc {
if config.Skipper == nil {
config.Skipper = DefaultTrailingSlashConfig.Skipper
}
if config.Code == 0 {
config.Code = DefaultRedirectConfig.Code
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
req, scheme := c.Request(), c.Scheme()
host := req.Host
if ok, url := cb(scheme, host, req.RequestURI); ok {
return c.Redirect(config.Code, url)
}
return next(c)
}
}
}

View File

@ -1,64 +0,0 @@
package middleware
import (
"github.com/labstack/echo/v4"
"github.com/labstack/gommon/random"
)
type (
// RequestIDConfig defines the config for RequestID middleware.
RequestIDConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Generator defines a function to generate an ID.
// Optional. Default value random.String(32).
Generator func() string
}
)
var (
// DefaultRequestIDConfig is the default RequestID middleware config.
DefaultRequestIDConfig = RequestIDConfig{
Skipper: DefaultSkipper,
Generator: generator,
}
)
// RequestID returns a X-Request-ID middleware.
func RequestID() echo.MiddlewareFunc {
return RequestIDWithConfig(DefaultRequestIDConfig)
}
// RequestIDWithConfig returns a X-Request-ID middleware with config.
func RequestIDWithConfig(config RequestIDConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultRequestIDConfig.Skipper
}
if config.Generator == nil {
config.Generator = generator
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
res := c.Response()
rid := req.Header.Get(echo.HeaderXRequestID)
if rid == "" {
rid = config.Generator()
}
res.Header().Set(echo.HeaderXRequestID, rid)
return next(c)
}
}
}
func generator() string {
return random.String(32)
}

View File

@ -1,84 +0,0 @@
package middleware
import (
"regexp"
"strings"
"github.com/labstack/echo/v4"
)
type (
// RewriteConfig defines the config for Rewrite middleware.
RewriteConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Rules defines the URL path rewrite rules. The values captured in asterisk can be
// retrieved by index e.g. $1, $2 and so on.
// Example:
// "/old": "/new",
// "/api/*": "/$1",
// "/js/*": "/public/javascripts/$1",
// "/users/*/orders/*": "/user/$1/order/$2",
// Required.
Rules map[string]string `yaml:"rules"`
rulesRegex map[*regexp.Regexp]string
}
)
var (
// DefaultRewriteConfig is the default Rewrite middleware config.
DefaultRewriteConfig = RewriteConfig{
Skipper: DefaultSkipper,
}
)
// Rewrite returns a Rewrite middleware.
//
// Rewrite middleware rewrites the URL path based on the provided rules.
func Rewrite(rules map[string]string) echo.MiddlewareFunc {
c := DefaultRewriteConfig
c.Rules = rules
return RewriteWithConfig(c)
}
// RewriteWithConfig returns a Rewrite middleware with config.
// See: `Rewrite()`.
func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc {
// Defaults
if config.Rules == nil {
panic("echo: rewrite middleware requires url path rewrite rules")
}
if config.Skipper == nil {
config.Skipper = DefaultBodyDumpConfig.Skipper
}
config.rulesRegex = map[*regexp.Regexp]string{}
// Initialize
for k, v := range config.Rules {
k = strings.Replace(k, "*", "(.*)", -1)
k = k + "$"
config.rulesRegex[regexp.MustCompile(k)] = v
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) (err error) {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
// Rewrite
for k, v := range config.rulesRegex {
replacer := captureTokens(k, req.URL.Path)
if replacer != nil {
req.URL.Path = replacer.Replace(v)
break
}
}
return next(c)
}
}
}

View File

@ -1,137 +0,0 @@
package middleware
import (
"fmt"
"github.com/labstack/echo/v4"
)
type (
// SecureConfig defines the config for Secure middleware.
SecureConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// XSSProtection provides protection against cross-site scripting attack (XSS)
// by setting the `X-XSS-Protection` header.
// Optional. Default value "1; mode=block".
XSSProtection string `yaml:"xss_protection"`
// ContentTypeNosniff provides protection against overriding Content-Type
// header by setting the `X-Content-Type-Options` header.
// Optional. Default value "nosniff".
ContentTypeNosniff string `yaml:"content_type_nosniff"`
// XFrameOptions can be used to indicate whether or not a browser should
// be allowed to render a page in a <frame>, <iframe> or <object> .
// Sites can use this to avoid clickjacking attacks, by ensuring that their
// content is not embedded into other sites.provides protection against
// clickjacking.
// Optional. Default value "SAMEORIGIN".
// Possible values:
// - "SAMEORIGIN" - The page can only be displayed in a frame on the same origin as the page itself.
// - "DENY" - The page cannot be displayed in a frame, regardless of the site attempting to do so.
// - "ALLOW-FROM uri" - The page can only be displayed in a frame on the specified origin.
XFrameOptions string `yaml:"x_frame_options"`
// HSTSMaxAge sets the `Strict-Transport-Security` header to indicate how
// long (in seconds) browsers should remember that this site is only to
// be accessed using HTTPS. This reduces your exposure to some SSL-stripping
// man-in-the-middle (MITM) attacks.
// Optional. Default value 0.
HSTSMaxAge int `yaml:"hsts_max_age"`
// HSTSExcludeSubdomains won't include subdomains tag in the `Strict Transport Security`
// header, excluding all subdomains from security policy. It has no effect
// unless HSTSMaxAge is set to a non-zero value.
// Optional. Default value false.
HSTSExcludeSubdomains bool `yaml:"hsts_exclude_subdomains"`
// ContentSecurityPolicy sets the `Content-Security-Policy` header providing
// security against cross-site scripting (XSS), clickjacking and other code
// injection attacks resulting from execution of malicious content in the
// trusted web page context.
// Optional. Default value "".
ContentSecurityPolicy string `yaml:"content_security_policy"`
// CSPReportOnly would use the `Content-Security-Policy-Report-Only` header instead
// of the `Content-Security-Policy` header. This allows iterative updates of the
// content security policy by only reporting the violations that would
// have occurred instead of blocking the resource.
// Optional. Default value false.
CSPReportOnly bool `yaml:"csp_report_only"`
// HSTSPreloadEnabled will add the preload tag in the `Strict Transport Security`
// header, which enables the domain to be included in the HSTS preload list
// maintained by Chrome (and used by Firefox and Safari): https://hstspreload.org/
// Optional. Default value false.
HSTSPreloadEnabled bool `yaml:"hsts_preload_enabled"`
}
)
var (
// DefaultSecureConfig is the default Secure middleware config.
DefaultSecureConfig = SecureConfig{
Skipper: DefaultSkipper,
XSSProtection: "1; mode=block",
ContentTypeNosniff: "nosniff",
XFrameOptions: "SAMEORIGIN",
HSTSPreloadEnabled: false,
}
)
// Secure returns a Secure middleware.
// Secure middleware provides protection against cross-site scripting (XSS) attack,
// content type sniffing, clickjacking, insecure connection and other code injection
// attacks.
func Secure() echo.MiddlewareFunc {
return SecureWithConfig(DefaultSecureConfig)
}
// SecureWithConfig returns a Secure middleware with config.
// See: `Secure()`.
func SecureWithConfig(config SecureConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultSecureConfig.Skipper
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
res := c.Response()
if config.XSSProtection != "" {
res.Header().Set(echo.HeaderXXSSProtection, config.XSSProtection)
}
if config.ContentTypeNosniff != "" {
res.Header().Set(echo.HeaderXContentTypeOptions, config.ContentTypeNosniff)
}
if config.XFrameOptions != "" {
res.Header().Set(echo.HeaderXFrameOptions, config.XFrameOptions)
}
if (c.IsTLS() || (req.Header.Get(echo.HeaderXForwardedProto) == "https")) && config.HSTSMaxAge != 0 {
subdomains := ""
if !config.HSTSExcludeSubdomains {
subdomains = "; includeSubdomains"
}
if config.HSTSPreloadEnabled {
subdomains = fmt.Sprintf("%s; preload", subdomains)
}
res.Header().Set(echo.HeaderStrictTransportSecurity, fmt.Sprintf("max-age=%d%s", config.HSTSMaxAge, subdomains))
}
if config.ContentSecurityPolicy != "" {
if config.CSPReportOnly {
res.Header().Set(echo.HeaderContentSecurityPolicyReportOnly, config.ContentSecurityPolicy)
} else {
res.Header().Set(echo.HeaderContentSecurityPolicy, config.ContentSecurityPolicy)
}
}
return next(c)
}
}
}

View File

@ -1,121 +0,0 @@
package middleware
import (
"strings"
"github.com/labstack/echo/v4"
)
type (
// TrailingSlashConfig defines the config for TrailingSlash middleware.
TrailingSlashConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Status code to be used when redirecting the request.
// Optional, but when provided the request is redirected using this code.
RedirectCode int `yaml:"redirect_code"`
}
)
var (
// DefaultTrailingSlashConfig is the default TrailingSlash middleware config.
DefaultTrailingSlashConfig = TrailingSlashConfig{
Skipper: DefaultSkipper,
}
)
// AddTrailingSlash returns a root level (before router) middleware which adds a
// trailing slash to the request `URL#Path`.
//
// Usage `Echo#Pre(AddTrailingSlash())`
func AddTrailingSlash() echo.MiddlewareFunc {
return AddTrailingSlashWithConfig(DefaultTrailingSlashConfig)
}
// AddTrailingSlashWithConfig returns a AddTrailingSlash middleware with config.
// See `AddTrailingSlash()`.
func AddTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultTrailingSlashConfig.Skipper
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
url := req.URL
path := url.Path
qs := c.QueryString()
if !strings.HasSuffix(path, "/") {
path += "/"
uri := path
if qs != "" {
uri += "?" + qs
}
// Redirect
if config.RedirectCode != 0 {
return c.Redirect(config.RedirectCode, uri)
}
// Forward
req.RequestURI = uri
url.Path = path
}
return next(c)
}
}
}
// RemoveTrailingSlash returns a root level (before router) middleware which removes
// a trailing slash from the request URI.
//
// Usage `Echo#Pre(RemoveTrailingSlash())`
func RemoveTrailingSlash() echo.MiddlewareFunc {
return RemoveTrailingSlashWithConfig(TrailingSlashConfig{})
}
// RemoveTrailingSlashWithConfig returns a RemoveTrailingSlash middleware with config.
// See `RemoveTrailingSlash()`.
func RemoveTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultTrailingSlashConfig.Skipper
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}
req := c.Request()
url := req.URL
path := url.Path
qs := c.QueryString()
l := len(path) - 1
if l > 0 && strings.HasSuffix(path, "/") {
path = path[:l]
uri := path
if qs != "" {
uri += "?" + qs
}
// Redirect
if config.RedirectCode != 0 {
return c.Redirect(config.RedirectCode, uri)
}
// Forward
req.RequestURI = uri
url.Path = path
}
return next(c)
}
}
}

View File

@ -1,229 +0,0 @@
package middleware
import (
"fmt"
"html/template"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"github.com/labstack/echo/v4"
"github.com/labstack/gommon/bytes"
)
type (
// StaticConfig defines the config for Static middleware.
StaticConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Root directory from where the static content is served.
// Required.
Root string `yaml:"root"`
// Index file for serving a directory.
// Optional. Default value "index.html".
Index string `yaml:"index"`
// Enable HTML5 mode by forwarding all not-found requests to root so that
// SPA (single-page application) can handle the routing.
// Optional. Default value false.
HTML5 bool `yaml:"html5"`
// Enable directory browsing.
// Optional. Default value false.
Browse bool `yaml:"browse"`
}
)
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{{ .Name }}</title>
<style>
body {
font-family: Menlo, Consolas, monospace;
padding: 48px;
}
header {
padding: 4px 16px;
font-size: 24px;
}
ul {
list-style-type: none;
margin: 0;
padding: 20px 0 0 0;
display: flex;
flex-wrap: wrap;
}
li {
width: 300px;
padding: 16px;
}
li a {
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-decoration: none;
transition: opacity 0.25s;
}
li span {
color: #707070;
font-size: 12px;
}
li a:hover {
opacity: 0.50;
}
.dir {
color: #E91E63;
}
.file {
color: #673AB7;
}
</style>
</head>
<body>
<header>
{{ .Name }}
</header>
<ul>
{{ range .Files }}
<li>
{{ if .Dir }}
{{ $name := print .Name "/" }}
<a class="dir" href="{{ $name }}">{{ $name }}</a>
{{ else }}
<a class="file" href="{{ .Name }}">{{ .Name }}</a>
<span>{{ .Size }}</span>
{{ end }}
</li>
{{ end }}
</ul>
</body>
</html>
`
var (
// DefaultStaticConfig is the default Static middleware config.
DefaultStaticConfig = StaticConfig{
Skipper: DefaultSkipper,
Index: "index.html",
}
)
// Static returns a Static middleware to serves static content from the provided
// root directory.
func Static(root string) echo.MiddlewareFunc {
c := DefaultStaticConfig
c.Root = root
return StaticWithConfig(c)
}
// StaticWithConfig returns a Static middleware with config.
// See `Static()`.
func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
// Defaults
if config.Root == "" {
config.Root = "." // For security we want to restrict to CWD.
}
if config.Skipper == nil {
config.Skipper = DefaultStaticConfig.Skipper
}
if config.Index == "" {
config.Index = DefaultStaticConfig.Index
}
// Index template
t, err := template.New("index").Parse(html)
if err != nil {
panic(fmt.Sprintf("echo: %v", err))
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) (err error) {
if config.Skipper(c) {
return next(c)
}
p := c.Request().URL.Path
if strings.HasSuffix(c.Path(), "*") { // When serving from a group, e.g. `/static*`.
p = c.Param("*")
}
p, err = url.PathUnescape(p)
if err != nil {
return
}
name := filepath.Join(config.Root, path.Clean("/"+p)) // "/"+ for security
fi, err := os.Stat(name)
if err != nil {
if os.IsNotExist(err) {
if err = next(c); err != nil {
if he, ok := err.(*echo.HTTPError); ok {
if config.HTML5 && he.Code == http.StatusNotFound {
return c.File(filepath.Join(config.Root, config.Index))
}
}
return
}
}
return
}
if fi.IsDir() {
index := filepath.Join(name, config.Index)
fi, err = os.Stat(index)
if err != nil {
if config.Browse {
return listDir(t, name, c.Response())
}
if os.IsNotExist(err) {
return next(c)
}
return
}
return c.File(index)
}
return c.File(name)
}
}
}
func listDir(t *template.Template, name string, res *echo.Response) (err error) {
file, err := os.Open(name)
if err != nil {
return
}
files, err := file.Readdir(-1)
if err != nil {
return
}
// Create directory index
res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
data := struct {
Name string
Files []interface{}
}{
Name: name,
}
for _, f := range files {
data.Files = append(data.Files, struct {
Name string
Dir bool
Size string
}{f.Name(), f.IsDir(), bytes.Format(f.Size())})
}
return t.Execute(res, data)
}

View File

@ -1,54 +0,0 @@
package middleware
import (
"strings"
)
func matchScheme(domain, pattern string) bool {
didx := strings.Index(domain, ":")
pidx := strings.Index(pattern, ":")
return didx != -1 && pidx != -1 && domain[:didx] == pattern[:pidx]
}
// matchSubdomain compares authority with wildcard
func matchSubdomain(domain, pattern string) bool {
if !matchScheme(domain, pattern) {
return false
}
didx := strings.Index(domain, "://")
pidx := strings.Index(pattern, "://")
if didx == -1 || pidx == -1 {
return false
}
domAuth := domain[didx+3:]
// to avoid long loop by invalid long domain
if len(domAuth) > 253 {
return false
}
patAuth := pattern[pidx+3:]
domComp := strings.Split(domAuth, ".")
patComp := strings.Split(patAuth, ".")
for i := len(domComp)/2 - 1; i >= 0; i-- {
opp := len(domComp) - 1 - i
domComp[i], domComp[opp] = domComp[opp], domComp[i]
}
for i := len(patComp)/2 - 1; i >= 0; i-- {
opp := len(patComp) - 1 - i
patComp[i], patComp[opp] = patComp[opp], patComp[i]
}
for i, v := range domComp {
if len(patComp) <= i {
return false
}
p := patComp[i]
if p == "*" {
return true
}
if p != v {
return false
}
}
return false
}

View File

@ -1,101 +0,0 @@
package echo
import (
"bufio"
"net"
"net/http"
)
type (
// Response wraps an http.ResponseWriter and implements its interface to be used
// by an HTTP handler to construct an HTTP response.
// See: https://golang.org/pkg/net/http/#ResponseWriter
Response struct {
echo *Echo
beforeFuncs []func()
afterFuncs []func()
Writer http.ResponseWriter
Status int
Size int64
Committed bool
}
)
// NewResponse creates a new instance of Response.
func NewResponse(w http.ResponseWriter, e *Echo) (r *Response) {
return &Response{Writer: w, echo: e}
}
// Header returns the header map for the writer that will be sent by
// WriteHeader. Changing the header after a call to WriteHeader (or Write) has
// no effect unless the modified headers were declared as trailers by setting
// the "Trailer" header before the call to WriteHeader (see example)
// To suppress implicit response headers, set their value to nil.
// Example: https://golang.org/pkg/net/http/#example_ResponseWriter_trailers
func (r *Response) Header() http.Header {
return r.Writer.Header()
}
// Before registers a function which is called just before the response is written.
func (r *Response) Before(fn func()) {
r.beforeFuncs = append(r.beforeFuncs, fn)
}
// After registers a function which is called just after the response is written.
// If the `Content-Length` is unknown, none of the after function is executed.
func (r *Response) After(fn func()) {
r.afterFuncs = append(r.afterFuncs, fn)
}
// WriteHeader sends an HTTP response header with status code. If WriteHeader is
// not called explicitly, the first call to Write will trigger an implicit
// WriteHeader(http.StatusOK). Thus explicit calls to WriteHeader are mainly
// used to send error codes.
func (r *Response) WriteHeader(code int) {
if r.Committed {
r.echo.Logger.Warn("response already committed")
return
}
for _, fn := range r.beforeFuncs {
fn()
}
r.Status = code
r.Writer.WriteHeader(code)
r.Committed = true
}
// Write writes the data to the connection as part of an HTTP reply.
func (r *Response) Write(b []byte) (n int, err error) {
if !r.Committed {
r.WriteHeader(http.StatusOK)
}
n, err = r.Writer.Write(b)
r.Size += int64(n)
for _, fn := range r.afterFuncs {
fn()
}
return
}
// Flush implements the http.Flusher interface to allow an HTTP handler to flush
// buffered data to the client.
// See [http.Flusher](https://golang.org/pkg/net/http/#Flusher)
func (r *Response) Flush() {
r.Writer.(http.Flusher).Flush()
}
// Hijack implements the http.Hijacker interface to allow an HTTP handler to
// take over the connection.
// See [http.Hijacker](https://golang.org/pkg/net/http/#Hijacker)
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return r.Writer.(http.Hijacker).Hijack()
}
func (r *Response) reset(w http.ResponseWriter) {
r.beforeFuncs = nil
r.afterFuncs = nil
r.Writer = w
r.Size = 0
r.Status = http.StatusOK
r.Committed = false
}

View File

@ -1,435 +0,0 @@
package echo
import "net/http"
type (
// Router is the registry of all registered routes for an `Echo` instance for
// request matching and URL path parameter parsing.
Router struct {
tree *node
routes map[string]*Route
echo *Echo
}
node struct {
kind kind
label byte
prefix string
parent *node
children children
ppath string
pnames []string
methodHandler *methodHandler
}
kind uint8
children []*node
methodHandler struct {
connect HandlerFunc
delete HandlerFunc
get HandlerFunc
head HandlerFunc
options HandlerFunc
patch HandlerFunc
post HandlerFunc
propfind HandlerFunc
put HandlerFunc
trace HandlerFunc
}
)
const (
skind kind = iota
pkind
akind
)
// NewRouter returns a new Router instance.
func NewRouter(e *Echo) *Router {
return &Router{
tree: &node{
methodHandler: new(methodHandler),
},
routes: map[string]*Route{},
echo: e,
}
}
// Add registers a new route for method and path with matching handler.
func (r *Router) Add(method, path string, h HandlerFunc) {
// Validate path
if path == "" {
panic("echo: path cannot be empty")
}
if path[0] != '/' {
path = "/" + path
}
pnames := []string{} // Param names
ppath := path // Pristine path
for i, l := 0, len(path); i < l; i++ {
if path[i] == ':' {
j := i + 1
r.insert(method, path[:i], nil, skind, "", nil)
for ; i < l && path[i] != '/'; i++ {
}
pnames = append(pnames, path[j:i])
path = path[:j] + path[i:]
i, l = j, len(path)
if i == l {
r.insert(method, path[:i], h, pkind, ppath, pnames)
return
}
r.insert(method, path[:i], nil, pkind, "", nil)
} else if path[i] == '*' {
r.insert(method, path[:i], nil, skind, "", nil)
pnames = append(pnames, "*")
r.insert(method, path[:i+1], h, akind, ppath, pnames)
return
}
}
r.insert(method, path, h, skind, ppath, pnames)
}
func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string, pnames []string) {
// Adjust max param
l := len(pnames)
if *r.echo.maxParam < l {
*r.echo.maxParam = l
}
cn := r.tree // Current node as root
if cn == nil {
panic("echo: invalid method")
}
search := path
for {
sl := len(search)
pl := len(cn.prefix)
l := 0
// LCP
max := pl
if sl < max {
max = sl
}
for ; l < max && search[l] == cn.prefix[l]; l++ {
}
if l == 0 {
// At root node
cn.label = search[0]
cn.prefix = search
if h != nil {
cn.kind = t
cn.addHandler(method, h)
cn.ppath = ppath
cn.pnames = pnames
}
} else if l < pl {
// Split node
n := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.ppath, cn.pnames)
// Reset parent node
cn.kind = skind
cn.label = cn.prefix[0]
cn.prefix = cn.prefix[:l]
cn.children = nil
cn.methodHandler = new(methodHandler)
cn.ppath = ""
cn.pnames = nil
cn.addChild(n)
if l == sl {
// At parent node
cn.kind = t
cn.addHandler(method, h)
cn.ppath = ppath
cn.pnames = pnames
} else {
// Create child node
n = newNode(t, search[l:], cn, nil, new(methodHandler), ppath, pnames)
n.addHandler(method, h)
cn.addChild(n)
}
} else if l < sl {
search = search[l:]
c := cn.findChildWithLabel(search[0])
if c != nil {
// Go deeper
cn = c
continue
}
// Create child node
n := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames)
n.addHandler(method, h)
cn.addChild(n)
} else {
// Node already exists
if h != nil {
cn.addHandler(method, h)
cn.ppath = ppath
if len(cn.pnames) == 0 { // Issue #729
cn.pnames = pnames
}
}
}
return
}
}
func newNode(t kind, pre string, p *node, c children, mh *methodHandler, ppath string, pnames []string) *node {
return &node{
kind: t,
label: pre[0],
prefix: pre,
parent: p,
children: c,
ppath: ppath,
pnames: pnames,
methodHandler: mh,
}
}
func (n *node) addChild(c *node) {
n.children = append(n.children, c)
}
func (n *node) findChild(l byte, t kind) *node {
for _, c := range n.children {
if c.label == l && c.kind == t {
return c
}
}
return nil
}
func (n *node) findChildWithLabel(l byte) *node {
for _, c := range n.children {
if c.label == l {
return c
}
}
return nil
}
func (n *node) findChildByKind(t kind) *node {
for _, c := range n.children {
if c.kind == t {
return c
}
}
return nil
}
func (n *node) addHandler(method string, h HandlerFunc) {
switch method {
case http.MethodConnect:
n.methodHandler.connect = h
case http.MethodDelete:
n.methodHandler.delete = h
case http.MethodGet:
n.methodHandler.get = h
case http.MethodHead:
n.methodHandler.head = h
case http.MethodOptions:
n.methodHandler.options = h
case http.MethodPatch:
n.methodHandler.patch = h
case http.MethodPost:
n.methodHandler.post = h
case PROPFIND:
n.methodHandler.propfind = h
case http.MethodPut:
n.methodHandler.put = h
case http.MethodTrace:
n.methodHandler.trace = h
}
}
func (n *node) findHandler(method string) HandlerFunc {
switch method {
case http.MethodConnect:
return n.methodHandler.connect
case http.MethodDelete:
return n.methodHandler.delete
case http.MethodGet:
return n.methodHandler.get
case http.MethodHead:
return n.methodHandler.head
case http.MethodOptions:
return n.methodHandler.options
case http.MethodPatch:
return n.methodHandler.patch
case http.MethodPost:
return n.methodHandler.post
case PROPFIND:
return n.methodHandler.propfind
case http.MethodPut:
return n.methodHandler.put
case http.MethodTrace:
return n.methodHandler.trace
default:
return nil
}
}
func (n *node) checkMethodNotAllowed() HandlerFunc {
for _, m := range methods {
if h := n.findHandler(m); h != nil {
return MethodNotAllowedHandler
}
}
return NotFoundHandler
}
// Find lookup a handler registered for method and path. It also parses URL for path
// parameters and load them into context.
//
// For performance:
//
// - Get context from `Echo#AcquireContext()`
// - Reset it `Context#Reset()`
// - Return it `Echo#ReleaseContext()`.
func (r *Router) Find(method, path string, c Context) {
ctx := c.(*context)
ctx.path = path
cn := r.tree // Current node as root
var (
search = path
child *node // Child node
n int // Param counter
nk kind // Next kind
nn *node // Next node
ns string // Next search
pvalues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice
)
// Search order static > param > any
for {
if search == "" {
break
}
pl := 0 // Prefix length
l := 0 // LCP length
if cn.label != ':' {
sl := len(search)
pl = len(cn.prefix)
// LCP
max := pl
if sl < max {
max = sl
}
for ; l < max && search[l] == cn.prefix[l]; l++ {
}
}
if l == pl {
// Continue search
search = search[l:]
} else {
cn = nn
search = ns
if nk == pkind {
goto Param
} else if nk == akind {
goto Any
}
// Not found
return
}
if search == "" {
break
}
// Static node
if child = cn.findChild(search[0], skind); child != nil {
// Save next
if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623
nk = pkind
nn = cn
ns = search
}
cn = child
continue
}
// Param node
Param:
if child = cn.findChildByKind(pkind); child != nil {
// Issue #378
if len(pvalues) == n {
continue
}
// Save next
if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623
nk = akind
nn = cn
ns = search
}
cn = child
i, l := 0, len(search)
for ; i < l && search[i] != '/'; i++ {
}
pvalues[n] = search[:i]
n++
search = search[i:]
continue
}
// Any node
Any:
if cn = cn.findChildByKind(akind); cn == nil {
if nn != nil {
cn = nn
nn = cn.parent // Next (Issue #954)
search = ns
if nk == pkind {
goto Param
} else if nk == akind {
goto Any
}
}
// Not found
return
}
pvalues[len(cn.pnames)-1] = search
break
}
ctx.handler = cn.findHandler(method)
ctx.path = cn.ppath
ctx.pnames = cn.pnames
// NOTE: Slow zone...
if ctx.handler == nil {
ctx.handler = cn.checkMethodNotAllowed()
// Dig further for any, might have an empty value for *, e.g.
// serving a directory. Issue #207.
if cn = cn.findChildByKind(akind); cn == nil {
return
}
if h := cn.findHandler(method); h != nil {
ctx.handler = h
} else {
ctx.handler = cn.checkMethodNotAllowed()
}
ctx.path = cn.ppath
ctx.pnames = cn.pnames
pvalues[len(cn.pnames)-1] = ""
}
return
}

View File

@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2018 labstack
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.

View File

@ -1,29 +0,0 @@
# Bytes
- Format bytes integer to human readable bytes string.
- Parse human readable bytes string to bytes integer.
## Installation
```go
go get github.com/labstack/gommon/bytes
```
## [Usage](https://github.com/labstack/gommon/blob/master/bytes/bytes_test.go)
### Format
```go
println(bytes.Format(13231323))
```
`12.62MB`
### Parse
```go
b, _ = Parse("2M")
println(b)
```
`2097152`

View File

@ -1,108 +0,0 @@
package bytes
import (
"fmt"
"regexp"
"strconv"
)
type (
// Bytes struct
Bytes struct{}
)
const (
_ = 1.0 << (10 * iota) // ignore first value by assigning to blank identifier
KB
MB
GB
TB
PB
EB
)
var (
pattern = regexp.MustCompile(`(?i)^(-?\d+(?:\.\d+)?)([KMGTPE]B?|B?)$`)
global = New()
)
// New creates a Bytes instance.
func New() *Bytes {
return &Bytes{}
}
// Format formats bytes integer to human readable string.
// For example, 31323 bytes will return 30.59KB.
func (*Bytes) Format(b int64) string {
multiple := ""
value := float64(b)
switch {
case b >= EB:
value /= EB
multiple = "EB"
case b >= PB:
value /= PB
multiple = "PB"
case b >= TB:
value /= TB
multiple = "TB"
case b >= GB:
value /= GB
multiple = "GB"
case b >= MB:
value /= MB
multiple = "MB"
case b >= KB:
value /= KB
multiple = "KB"
case b == 0:
return "0"
default:
return strconv.FormatInt(b, 10) + "B"
}
return fmt.Sprintf("%.2f%s", value, multiple)
}
// Parse parses human readable bytes string to bytes integer.
// For example, 6GB (6G is also valid) will return 6442450944.
func (*Bytes) Parse(value string) (i int64, err error) {
parts := pattern.FindStringSubmatch(value)
if len(parts) < 3 {
return 0, fmt.Errorf("error parsing value=%s", value)
}
bytesString := parts[1]
multiple := parts[2]
bytes, err := strconv.ParseFloat(bytesString, 64)
if err != nil {
return
}
switch multiple {
default:
return int64(bytes), nil
case "K", "KB":
return int64(bytes * KB), nil
case "M", "MB":
return int64(bytes * MB), nil
case "G", "GB":
return int64(bytes * GB), nil
case "T", "TB":
return int64(bytes * TB), nil
case "P", "PB":
return int64(bytes * PB), nil
case "E", "EB":
return int64(bytes * EB), nil
}
}
// Format wraps global Bytes's Format function.
func Format(b int64) string {
return global.Format(b)
}
// Parse wraps global Bytes's Parse function.
func Parse(val string) (int64, error) {
return global.Parse(val)
}

View File

@ -1,86 +0,0 @@
# Color
Style terminal text.
## Installation
```sh
go get github.com/labstack/gommon/color
```
## Windows?
Try [cmder](http://bliker.github.io/cmder) or https://github.com/mattn/go-colorable
## [Usage](https://github.com/labstack/gommon/blob/master/color/color_test.go)
```sh
import github.com/labstack/gommon/color
```
### Colored text
```go
color.Println(color.Black("black"))
color.Println(color.Red("red"))
color.Println(color.Green("green"))
color.Println(color.Yellow("yellow"))
color.Println(color.Blue("blue"))
color.Println(color.Magenta("magenta"))
color.Println(color.Cyan("cyan"))
color.Println(color.White("white"))
color.Println(color.Grey("grey"))
```
![Colored Text](http://i.imgur.com/8RtY1QR.png)
### Colored background
```go
color.Println(color.BlackBg("black background", color.Wht))
color.Println(color.RedBg("red background"))
color.Println(color.GreenBg("green background"))
color.Println(color.YellowBg("yellow background"))
color.Println(color.BlueBg("blue background"))
color.Println(color.MagentaBg("magenta background"))
color.Println(color.CyanBg("cyan background"))
color.Println(color.WhiteBg("white background"))
```
![Colored Background](http://i.imgur.com/SrrS6lw.png)
### Emphasis
```go
color.Println(color.Bold("bold"))
color.Println(color.Dim("dim"))
color.Println(color.Italic("italic"))
color.Println(color.Underline("underline"))
color.Println(color.Inverse("inverse"))
color.Println(color.Hidden("hidden"))
color.Println(color.Strikeout("strikeout"))
```
![Emphasis](http://i.imgur.com/3RSJBbc.png)
### Mix and match
```go
color.Println(color.Green("bold green with white background", color.B, color.WhtBg))
color.Println(color.Red("underline red", color.U))
color.Println(color.Yellow("dim yellow", color.D))
color.Println(color.Cyan("inverse cyan", color.In))
color.Println(color.Blue("bold underline dim blue", color.B, color.U, color.D))
```
![Mix and match](http://i.imgur.com/jWGq9Ca.png)
### Enable/Disable the package
```go
color.Disable()
color.Enable()
```
### New instance
```go
c := New()
c.Green("green")
```

View File

@ -1,407 +0,0 @@
package color
import (
"bytes"
"fmt"
"io"
"os"
"github.com/mattn/go-colorable"
"github.com/mattn/go-isatty"
)
type (
inner func(interface{}, []string, *Color) string
)
// Color styles
const (
// Blk Black text style
Blk = "30"
// Rd red text style
Rd = "31"
// Grn green text style
Grn = "32"
// Yel yellow text style
Yel = "33"
// Blu blue text style
Blu = "34"
// Mgn magenta text style
Mgn = "35"
// Cyn cyan text style
Cyn = "36"
// Wht white text style
Wht = "37"
// Gry grey text style
Gry = "90"
// BlkBg black background style
BlkBg = "40"
// RdBg red background style
RdBg = "41"
// GrnBg green background style
GrnBg = "42"
// YelBg yellow background style
YelBg = "43"
// BluBg blue background style
BluBg = "44"
// MgnBg magenta background style
MgnBg = "45"
// CynBg cyan background style
CynBg = "46"
// WhtBg white background style
WhtBg = "47"
// R reset emphasis style
R = "0"
// B bold emphasis style
B = "1"
// D dim emphasis style
D = "2"
// I italic emphasis style
I = "3"
// U underline emphasis style
U = "4"
// In inverse emphasis style
In = "7"
// H hidden emphasis style
H = "8"
// S strikeout emphasis style
S = "9"
)
var (
black = outer(Blk)
red = outer(Rd)
green = outer(Grn)
yellow = outer(Yel)
blue = outer(Blu)
magenta = outer(Mgn)
cyan = outer(Cyn)
white = outer(Wht)
grey = outer(Gry)
blackBg = outer(BlkBg)
redBg = outer(RdBg)
greenBg = outer(GrnBg)
yellowBg = outer(YelBg)
blueBg = outer(BluBg)
magentaBg = outer(MgnBg)
cyanBg = outer(CynBg)
whiteBg = outer(WhtBg)
reset = outer(R)
bold = outer(B)
dim = outer(D)
italic = outer(I)
underline = outer(U)
inverse = outer(In)
hidden = outer(H)
strikeout = outer(S)
global = New()
)
func outer(n string) inner {
return func(msg interface{}, styles []string, c *Color) string {
// TODO: Drop fmt to boost performance?
if c.disabled {
return fmt.Sprintf("%v", msg)
}
b := new(bytes.Buffer)
b.WriteString("\x1b[")
b.WriteString(n)
for _, s := range styles {
b.WriteString(";")
b.WriteString(s)
}
b.WriteString("m")
return fmt.Sprintf("%s%v\x1b[0m", b.String(), msg)
}
}
type (
Color struct {
output io.Writer
disabled bool
}
)
// New creates a Color instance.
func New() (c *Color) {
c = new(Color)
c.SetOutput(colorable.NewColorableStdout())
return
}
// Output returns the output.
func (c *Color) Output() io.Writer {
return c.output
}
// SetOutput sets the output.
func (c *Color) SetOutput(w io.Writer) {
c.output = w
if w, ok := w.(*os.File); !ok || !isatty.IsTerminal(w.Fd()) {
c.disabled = true
}
}
// Disable disables the colors and styles.
func (c *Color) Disable() {
c.disabled = true
}
// Enable enables the colors and styles.
func (c *Color) Enable() {
c.disabled = false
}
// Print is analogous to `fmt.Print` with termial detection.
func (c *Color) Print(args ...interface{}) {
fmt.Fprint(c.output, args...)
}
// Println is analogous to `fmt.Println` with termial detection.
func (c *Color) Println(args ...interface{}) {
fmt.Fprintln(c.output, args...)
}
// Printf is analogous to `fmt.Printf` with termial detection.
func (c *Color) Printf(format string, args ...interface{}) {
fmt.Fprintf(c.output, format, args...)
}
func (c *Color) Black(msg interface{}, styles ...string) string {
return black(msg, styles, c)
}
func (c *Color) Red(msg interface{}, styles ...string) string {
return red(msg, styles, c)
}
func (c *Color) Green(msg interface{}, styles ...string) string {
return green(msg, styles, c)
}
func (c *Color) Yellow(msg interface{}, styles ...string) string {
return yellow(msg, styles, c)
}
func (c *Color) Blue(msg interface{}, styles ...string) string {
return blue(msg, styles, c)
}
func (c *Color) Magenta(msg interface{}, styles ...string) string {
return magenta(msg, styles, c)
}
func (c *Color) Cyan(msg interface{}, styles ...string) string {
return cyan(msg, styles, c)
}
func (c *Color) White(msg interface{}, styles ...string) string {
return white(msg, styles, c)
}
func (c *Color) Grey(msg interface{}, styles ...string) string {
return grey(msg, styles, c)
}
func (c *Color) BlackBg(msg interface{}, styles ...string) string {
return blackBg(msg, styles, c)
}
func (c *Color) RedBg(msg interface{}, styles ...string) string {
return redBg(msg, styles, c)
}
func (c *Color) GreenBg(msg interface{}, styles ...string) string {
return greenBg(msg, styles, c)
}
func (c *Color) YellowBg(msg interface{}, styles ...string) string {
return yellowBg(msg, styles, c)
}
func (c *Color) BlueBg(msg interface{}, styles ...string) string {
return blueBg(msg, styles, c)
}
func (c *Color) MagentaBg(msg interface{}, styles ...string) string {
return magentaBg(msg, styles, c)
}
func (c *Color) CyanBg(msg interface{}, styles ...string) string {
return cyanBg(msg, styles, c)
}
func (c *Color) WhiteBg(msg interface{}, styles ...string) string {
return whiteBg(msg, styles, c)
}
func (c *Color) Reset(msg interface{}, styles ...string) string {
return reset(msg, styles, c)
}
func (c *Color) Bold(msg interface{}, styles ...string) string {
return bold(msg, styles, c)
}
func (c *Color) Dim(msg interface{}, styles ...string) string {
return dim(msg, styles, c)
}
func (c *Color) Italic(msg interface{}, styles ...string) string {
return italic(msg, styles, c)
}
func (c *Color) Underline(msg interface{}, styles ...string) string {
return underline(msg, styles, c)
}
func (c *Color) Inverse(msg interface{}, styles ...string) string {
return inverse(msg, styles, c)
}
func (c *Color) Hidden(msg interface{}, styles ...string) string {
return hidden(msg, styles, c)
}
func (c *Color) Strikeout(msg interface{}, styles ...string) string {
return strikeout(msg, styles, c)
}
// Output returns the output.
func Output() io.Writer {
return global.output
}
// SetOutput sets the output.
func SetOutput(w io.Writer) {
global.SetOutput(w)
}
func Disable() {
global.Disable()
}
func Enable() {
global.Enable()
}
// Print is analogous to `fmt.Print` with termial detection.
func Print(args ...interface{}) {
global.Print(args...)
}
// Println is analogous to `fmt.Println` with termial detection.
func Println(args ...interface{}) {
global.Println(args...)
}
// Printf is analogous to `fmt.Printf` with termial detection.
func Printf(format string, args ...interface{}) {
global.Printf(format, args...)
}
func Black(msg interface{}, styles ...string) string {
return global.Black(msg, styles...)
}
func Red(msg interface{}, styles ...string) string {
return global.Red(msg, styles...)
}
func Green(msg interface{}, styles ...string) string {
return global.Green(msg, styles...)
}
func Yellow(msg interface{}, styles ...string) string {
return global.Yellow(msg, styles...)
}
func Blue(msg interface{}, styles ...string) string {
return global.Blue(msg, styles...)
}
func Magenta(msg interface{}, styles ...string) string {
return global.Magenta(msg, styles...)
}
func Cyan(msg interface{}, styles ...string) string {
return global.Cyan(msg, styles...)
}
func White(msg interface{}, styles ...string) string {
return global.White(msg, styles...)
}
func Grey(msg interface{}, styles ...string) string {
return global.Grey(msg, styles...)
}
func BlackBg(msg interface{}, styles ...string) string {
return global.BlackBg(msg, styles...)
}
func RedBg(msg interface{}, styles ...string) string {
return global.RedBg(msg, styles...)
}
func GreenBg(msg interface{}, styles ...string) string {
return global.GreenBg(msg, styles...)
}
func YellowBg(msg interface{}, styles ...string) string {
return global.YellowBg(msg, styles...)
}
func BlueBg(msg interface{}, styles ...string) string {
return global.BlueBg(msg, styles...)
}
func MagentaBg(msg interface{}, styles ...string) string {
return global.MagentaBg(msg, styles...)
}
func CyanBg(msg interface{}, styles ...string) string {
return global.CyanBg(msg, styles...)
}
func WhiteBg(msg interface{}, styles ...string) string {
return global.WhiteBg(msg, styles...)
}
func Reset(msg interface{}, styles ...string) string {
return global.Reset(msg, styles...)
}
func Bold(msg interface{}, styles ...string) string {
return global.Bold(msg, styles...)
}
func Dim(msg interface{}, styles ...string) string {
return global.Dim(msg, styles...)
}
func Italic(msg interface{}, styles ...string) string {
return global.Italic(msg, styles...)
}
func Underline(msg interface{}, styles ...string) string {
return global.Underline(msg, styles...)
}
func Inverse(msg interface{}, styles ...string) string {
return global.Inverse(msg, styles...)
}
func Hidden(msg interface{}, styles ...string) string {
return global.Hidden(msg, styles...)
}
func Strikeout(msg interface{}, styles ...string) string {
return global.Strikeout(msg, styles...)
}

View File

@ -1,5 +0,0 @@
## WORK IN PROGRESS
### Usage
[log_test.go](log_test.go)

Some files were not shown because too many files have changed in this diff Show More