mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-11-25 07:40:19 +00:00
Updated vednor files
This commit is contained in:
parent
18a4ce4439
commit
1f1e8cea37
5
vendor/github.com/PuerkitoBio/purell/.gitignore
generated
vendored
5
vendor/github.com/PuerkitoBio/purell/.gitignore
generated
vendored
@ -1,5 +0,0 @@
|
|||||||
*.sublime-*
|
|
||||||
.DS_Store
|
|
||||||
*.swp
|
|
||||||
*.swo
|
|
||||||
tags
|
|
7
vendor/github.com/PuerkitoBio/purell/.travis.yml
generated
vendored
7
vendor/github.com/PuerkitoBio/purell/.travis.yml
generated
vendored
@ -1,7 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.4
|
|
||||||
- 1.5
|
|
||||||
- 1.6
|
|
||||||
- tip
|
|
12
vendor/github.com/PuerkitoBio/purell/LICENSE
generated
vendored
12
vendor/github.com/PuerkitoBio/purell/LICENSE
generated
vendored
@ -1,12 +0,0 @@
|
|||||||
Copyright (c) 2012, Martin Angers
|
|
||||||
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 the author 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 HOLDER 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.
|
|
187
vendor/github.com/PuerkitoBio/purell/README.md
generated
vendored
187
vendor/github.com/PuerkitoBio/purell/README.md
generated
vendored
@ -1,187 +0,0 @@
|
|||||||
# Purell
|
|
||||||
|
|
||||||
Purell is a tiny Go library to normalize URLs. It returns a pure URL. Pure-ell. Sanitizer and all. Yeah, I know...
|
|
||||||
|
|
||||||
Based on the [wikipedia paper][wiki] and the [RFC 3986 document][rfc].
|
|
||||||
|
|
||||||
[![build status](https://secure.travis-ci.org/PuerkitoBio/purell.png)](http://travis-ci.org/PuerkitoBio/purell)
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
`go get github.com/PuerkitoBio/purell`
|
|
||||||
|
|
||||||
## Changelog
|
|
||||||
|
|
||||||
* **2016-11-14 (v1.1.0)** : IDN: Conform to RFC 5895: Fold character width (thanks to @beeker1121).
|
|
||||||
* **2016-07-27 (v1.0.0)** : Normalize IDN to ASCII (thanks to @zenovich).
|
|
||||||
* **2015-02-08** : Add fix for relative paths issue ([PR #5][pr5]) and add fix for unnecessary encoding of reserved characters ([see issue #7][iss7]).
|
|
||||||
* **v0.2.0** : Add benchmarks, Attempt IDN support.
|
|
||||||
* **v0.1.0** : Initial release.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
From `example_test.go` (note that in your code, you would import "github.com/PuerkitoBio/purell", and would prefix references to its methods and constants with "purell."):
|
|
||||||
|
|
||||||
```go
|
|
||||||
package purell
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleNormalizeURLString() {
|
|
||||||
if normalized, err := NormalizeURLString("hTTp://someWEBsite.com:80/Amazing%3f/url/",
|
|
||||||
FlagLowercaseScheme|FlagLowercaseHost|FlagUppercaseEscapes); err != nil {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
fmt.Print(normalized)
|
|
||||||
}
|
|
||||||
// Output: http://somewebsite.com:80/Amazing%3F/url/
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleMustNormalizeURLString() {
|
|
||||||
normalized := MustNormalizeURLString("hTTpS://someWEBsite.com:443/Amazing%fa/url/",
|
|
||||||
FlagsUnsafeGreedy)
|
|
||||||
fmt.Print(normalized)
|
|
||||||
|
|
||||||
// Output: http://somewebsite.com/Amazing%FA/url
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleNormalizeURL() {
|
|
||||||
if u, err := url.Parse("Http://SomeUrl.com:8080/a/b/.././c///g?c=3&a=1&b=9&c=0#target"); err != nil {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
normalized := NormalizeURL(u, FlagsUsuallySafeGreedy|FlagRemoveDuplicateSlashes|FlagRemoveFragment)
|
|
||||||
fmt.Print(normalized)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output: http://someurl.com:8080/a/c/g?c=3&a=1&b=9&c=0
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
As seen in the examples above, purell offers three methods, `NormalizeURLString(string, NormalizationFlags) (string, error)`, `MustNormalizeURLString(string, NormalizationFlags) (string)` and `NormalizeURL(*url.URL, NormalizationFlags) (string)`. They all normalize the provided URL based on the specified flags. Here are the available flags:
|
|
||||||
|
|
||||||
```go
|
|
||||||
const (
|
|
||||||
// Safe normalizations
|
|
||||||
FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1
|
|
||||||
FlagLowercaseHost // http://HOST -> http://host
|
|
||||||
FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF
|
|
||||||
FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA
|
|
||||||
FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$
|
|
||||||
FlagRemoveDefaultPort // http://host:80 -> http://host
|
|
||||||
FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path
|
|
||||||
|
|
||||||
// Usually safe normalizations
|
|
||||||
FlagRemoveTrailingSlash // http://host/path/ -> http://host/path
|
|
||||||
FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags)
|
|
||||||
FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c
|
|
||||||
|
|
||||||
// Unsafe normalizations
|
|
||||||
FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/
|
|
||||||
FlagRemoveFragment // http://host/path#fragment -> http://host/path
|
|
||||||
FlagForceHTTP // https://host -> http://host
|
|
||||||
FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b
|
|
||||||
FlagRemoveWWW // http://www.host/ -> http://host/
|
|
||||||
FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags)
|
|
||||||
FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3
|
|
||||||
|
|
||||||
// Normalizations not in the wikipedia article, required to cover tests cases
|
|
||||||
// submitted by jehiah
|
|
||||||
FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147
|
|
||||||
FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147
|
|
||||||
FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147
|
|
||||||
FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path
|
|
||||||
FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path
|
|
||||||
|
|
||||||
// Convenience set of safe normalizations
|
|
||||||
FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator
|
|
||||||
|
|
||||||
// For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags,
|
|
||||||
// while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix".
|
|
||||||
|
|
||||||
// Convenience set of usually safe normalizations (includes FlagsSafe)
|
|
||||||
FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments
|
|
||||||
FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments
|
|
||||||
|
|
||||||
// Convenience set of unsafe normalizations (includes FlagsUsuallySafe)
|
|
||||||
FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery
|
|
||||||
FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery
|
|
||||||
|
|
||||||
// Convenience set of all available flags
|
|
||||||
FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
|
|
||||||
FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
For convenience, the set of flags `FlagsSafe`, `FlagsUsuallySafe[Greedy|NonGreedy]`, `FlagsUnsafe[Greedy|NonGreedy]` and `FlagsAll[Greedy|NonGreedy]` are provided for the similarly grouped normalizations on [wikipedia's URL normalization page][wiki]. You can add (using the bitwise OR `|` operator) or remove (using the bitwise AND NOT `&^` operator) individual flags from the sets if required, to build your own custom set.
|
|
||||||
|
|
||||||
The [full godoc reference is available on gopkgdoc][godoc].
|
|
||||||
|
|
||||||
Some things to note:
|
|
||||||
|
|
||||||
* `FlagDecodeUnnecessaryEscapes`, `FlagEncodeNecessaryEscapes`, `FlagUppercaseEscapes` and `FlagRemoveEmptyQuerySeparator` are always implicitly set, because internally, the URL string is parsed as an URL object, which automatically decodes unnecessary escapes, uppercases and encodes necessary ones, and removes empty query separators (an unnecessary `?` at the end of the url). So this operation cannot **not** be done. For this reason, `FlagRemoveEmptyQuerySeparator` (as well as the other three) has been included in the `FlagsSafe` convenience set, instead of `FlagsUnsafe`, where Wikipedia puts it.
|
|
||||||
|
|
||||||
* The `FlagDecodeUnnecessaryEscapes` decodes the following escapes (*from -> to*):
|
|
||||||
- %24 -> $
|
|
||||||
- %26 -> &
|
|
||||||
- %2B-%3B -> +,-./0123456789:;
|
|
||||||
- %3D -> =
|
|
||||||
- %40-%5A -> @ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
||||||
- %5F -> _
|
|
||||||
- %61-%7A -> abcdefghijklmnopqrstuvwxyz
|
|
||||||
- %7E -> ~
|
|
||||||
|
|
||||||
|
|
||||||
* When the `NormalizeURL` function is used (passing an URL object), this source URL object is modified (that is, after the call, the URL object will be modified to reflect the normalization).
|
|
||||||
|
|
||||||
* The *replace IP with domain name* normalization (`http://208.77.188.166/ → http://www.example.com/`) is obviously not possible for a library without making some network requests. This is not implemented in purell.
|
|
||||||
|
|
||||||
* The *remove unused query string parameters* and *remove default query parameters* are also not implemented, since this is a very case-specific normalization, and it is quite trivial to do with an URL object.
|
|
||||||
|
|
||||||
### Safe vs Usually Safe vs Unsafe
|
|
||||||
|
|
||||||
Purell allows you to control the level of risk you take while normalizing an URL. You can aggressively normalize, play it totally safe, or anything in between.
|
|
||||||
|
|
||||||
Consider the following URL:
|
|
||||||
|
|
||||||
`HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid`
|
|
||||||
|
|
||||||
Normalizing with the `FlagsSafe` gives:
|
|
||||||
|
|
||||||
`https://www.root.com/toto/tE%1F///a/./b/../c/?z=3&w=2&a=4&w=1#invalid`
|
|
||||||
|
|
||||||
With the `FlagsUsuallySafeGreedy`:
|
|
||||||
|
|
||||||
`https://www.root.com/toto/tE%1F///a/c?z=3&w=2&a=4&w=1#invalid`
|
|
||||||
|
|
||||||
And with `FlagsUnsafeGreedy`:
|
|
||||||
|
|
||||||
`http://root.com/toto/tE%1F/a/c?a=4&w=1&w=2&z=3`
|
|
||||||
|
|
||||||
## TODOs
|
|
||||||
|
|
||||||
* Add a class/default instance to allow specifying custom directory index names? At the moment, removing directory index removes `(^|/)((?:default|index)\.\w{1,4})$`.
|
|
||||||
|
|
||||||
## Thanks / Contributions
|
|
||||||
|
|
||||||
@rogpeppe
|
|
||||||
@jehiah
|
|
||||||
@opennota
|
|
||||||
@pchristopher1275
|
|
||||||
@zenovich
|
|
||||||
@beeker1121
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
The [BSD 3-Clause license][bsd].
|
|
||||||
|
|
||||||
[bsd]: http://opensource.org/licenses/BSD-3-Clause
|
|
||||||
[wiki]: http://en.wikipedia.org/wiki/URL_normalization
|
|
||||||
[rfc]: http://tools.ietf.org/html/rfc3986#section-6
|
|
||||||
[godoc]: http://go.pkgdoc.org/github.com/PuerkitoBio/purell
|
|
||||||
[pr5]: https://github.com/PuerkitoBio/purell/pull/5
|
|
||||||
[iss7]: https://github.com/PuerkitoBio/purell/issues/7
|
|
57
vendor/github.com/PuerkitoBio/purell/bench_test.go
generated
vendored
57
vendor/github.com/PuerkitoBio/purell/bench_test.go
generated
vendored
@ -1,57 +0,0 @@
|
|||||||
package purell
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
safeUrl = "HttPS://..iaMHost..Test:443/paTh^A%ef//./%41PaTH/..//?"
|
|
||||||
usuallySafeUrl = "HttPS://..iaMHost..Test:443/paTh^A%ef//./%41PaTH/../final/"
|
|
||||||
unsafeUrl = "HttPS://..www.iaMHost..Test:443/paTh^A%ef//./%41PaTH/../final/index.html?t=val1&a=val4&z=val5&a=val1#fragment"
|
|
||||||
allDWORDUrl = "HttPS://1113982867:/paTh^A%ef//./%41PaTH/../final/index.html?t=val1&a=val4&z=val5&a=val1#fragment"
|
|
||||||
allOctalUrl = "HttPS://0102.0146.07.0223:/paTh^A%ef//./%41PaTH/../final/index.html?t=val1&a=val4&z=val5&a=val1#fragment"
|
|
||||||
allHexUrl = "HttPS://0x42660793:/paTh^A%ef//./%41PaTH/../final/index.html?t=val1&a=val4&z=val5&a=val1#fragment"
|
|
||||||
allCombinedUrl = "HttPS://..0x42660793.:/paTh^A%ef//./%41PaTH/../final/index.html?t=val1&a=val4&z=val5&a=val1#fragment"
|
|
||||||
)
|
|
||||||
|
|
||||||
func BenchmarkSafe(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
NormalizeURLString(safeUrl, FlagsSafe)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkUsuallySafe(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
NormalizeURLString(usuallySafeUrl, FlagsUsuallySafeGreedy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkUnsafe(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
NormalizeURLString(unsafeUrl, FlagsUnsafeGreedy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkAllDWORD(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
NormalizeURLString(allDWORDUrl, FlagsAllGreedy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkAllOctal(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
NormalizeURLString(allOctalUrl, FlagsAllGreedy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkAllHex(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
NormalizeURLString(allHexUrl, FlagsAllGreedy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkAllCombined(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
NormalizeURLString(allCombinedUrl, FlagsAllGreedy)
|
|
||||||
}
|
|
||||||
}
|
|
9
vendor/github.com/PuerkitoBio/purell/benchmarks/v0.1.0
generated
vendored
9
vendor/github.com/PuerkitoBio/purell/benchmarks/v0.1.0
generated
vendored
@ -1,9 +0,0 @@
|
|||||||
PASS
|
|
||||||
BenchmarkSafe 500000 6131 ns/op
|
|
||||||
BenchmarkUsuallySafe 200000 7864 ns/op
|
|
||||||
BenchmarkUnsafe 100000 28560 ns/op
|
|
||||||
BenchmarkAllDWORD 50000 38722 ns/op
|
|
||||||
BenchmarkAllOctal 50000 40941 ns/op
|
|
||||||
BenchmarkAllHex 50000 44063 ns/op
|
|
||||||
BenchmarkAllCombined 50000 33613 ns/op
|
|
||||||
ok github.com/PuerkitoBio/purell 17.404s
|
|
35
vendor/github.com/PuerkitoBio/purell/example_test.go
generated
vendored
35
vendor/github.com/PuerkitoBio/purell/example_test.go
generated
vendored
@ -1,35 +0,0 @@
|
|||||||
package purell
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleNormalizeURLString() {
|
|
||||||
if normalized, err := NormalizeURLString("hTTp://someWEBsite.com:80/Amazing%3f/url/",
|
|
||||||
FlagLowercaseScheme|FlagLowercaseHost|FlagUppercaseEscapes); err != nil {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
fmt.Print(normalized)
|
|
||||||
}
|
|
||||||
// Output: http://somewebsite.com:80/Amazing%3F/url/
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleMustNormalizeURLString() {
|
|
||||||
normalized := MustNormalizeURLString("hTTpS://someWEBsite.com:443/Amazing%fa/url/",
|
|
||||||
FlagsUnsafeGreedy)
|
|
||||||
fmt.Print(normalized)
|
|
||||||
|
|
||||||
// Output: http://somewebsite.com/Amazing%FA/url
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleNormalizeURL() {
|
|
||||||
if u, err := url.Parse("Http://SomeUrl.com:8080/a/b/.././c///g?c=3&a=1&b=9&c=0#target"); err != nil {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
normalized := NormalizeURL(u, FlagsUsuallySafeGreedy|FlagRemoveDuplicateSlashes|FlagRemoveFragment)
|
|
||||||
fmt.Print(normalized)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output: http://someurl.com:8080/a/c/g?c=3&a=1&b=9&c=0
|
|
||||||
}
|
|
379
vendor/github.com/PuerkitoBio/purell/purell.go
generated
vendored
379
vendor/github.com/PuerkitoBio/purell/purell.go
generated
vendored
@ -1,379 +0,0 @@
|
|||||||
/*
|
|
||||||
Package purell offers URL normalization as described on the wikipedia page:
|
|
||||||
http://en.wikipedia.org/wiki/URL_normalization
|
|
||||||
*/
|
|
||||||
package purell
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/PuerkitoBio/urlesc"
|
|
||||||
"golang.org/x/net/idna"
|
|
||||||
"golang.org/x/text/unicode/norm"
|
|
||||||
"golang.org/x/text/width"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A set of normalization flags determines how a URL will
|
|
||||||
// be normalized.
|
|
||||||
type NormalizationFlags uint
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Safe normalizations
|
|
||||||
FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1
|
|
||||||
FlagLowercaseHost // http://HOST -> http://host
|
|
||||||
FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF
|
|
||||||
FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA
|
|
||||||
FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$
|
|
||||||
FlagRemoveDefaultPort // http://host:80 -> http://host
|
|
||||||
FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path
|
|
||||||
|
|
||||||
// Usually safe normalizations
|
|
||||||
FlagRemoveTrailingSlash // http://host/path/ -> http://host/path
|
|
||||||
FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags)
|
|
||||||
FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c
|
|
||||||
|
|
||||||
// Unsafe normalizations
|
|
||||||
FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/
|
|
||||||
FlagRemoveFragment // http://host/path#fragment -> http://host/path
|
|
||||||
FlagForceHTTP // https://host -> http://host
|
|
||||||
FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b
|
|
||||||
FlagRemoveWWW // http://www.host/ -> http://host/
|
|
||||||
FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags)
|
|
||||||
FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3
|
|
||||||
|
|
||||||
// Normalizations not in the wikipedia article, required to cover tests cases
|
|
||||||
// submitted by jehiah
|
|
||||||
FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147
|
|
||||||
FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147
|
|
||||||
FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147
|
|
||||||
FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path
|
|
||||||
FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path
|
|
||||||
|
|
||||||
// Convenience set of safe normalizations
|
|
||||||
FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator
|
|
||||||
|
|
||||||
// For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags,
|
|
||||||
// while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix".
|
|
||||||
|
|
||||||
// Convenience set of usually safe normalizations (includes FlagsSafe)
|
|
||||||
FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments
|
|
||||||
FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments
|
|
||||||
|
|
||||||
// Convenience set of unsafe normalizations (includes FlagsUsuallySafe)
|
|
||||||
FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery
|
|
||||||
FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery
|
|
||||||
|
|
||||||
// Convenience set of all available flags
|
|
||||||
FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
|
|
||||||
FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultHttpPort = ":80"
|
|
||||||
defaultHttpsPort = ":443"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Regular expressions used by the normalizations
|
|
||||||
var rxPort = regexp.MustCompile(`(:\d+)/?$`)
|
|
||||||
var rxDirIndex = regexp.MustCompile(`(^|/)((?:default|index)\.\w{1,4})$`)
|
|
||||||
var rxDupSlashes = regexp.MustCompile(`/{2,}`)
|
|
||||||
var rxDWORDHost = regexp.MustCompile(`^(\d+)((?:\.+)?(?:\:\d*)?)$`)
|
|
||||||
var rxOctalHost = regexp.MustCompile(`^(0\d*)\.(0\d*)\.(0\d*)\.(0\d*)((?:\.+)?(?:\:\d*)?)$`)
|
|
||||||
var rxHexHost = regexp.MustCompile(`^0x([0-9A-Fa-f]+)((?:\.+)?(?:\:\d*)?)$`)
|
|
||||||
var rxHostDots = regexp.MustCompile(`^(.+?)(:\d+)?$`)
|
|
||||||
var rxEmptyPort = regexp.MustCompile(`:+$`)
|
|
||||||
|
|
||||||
// Map of flags to implementation function.
|
|
||||||
// FlagDecodeUnnecessaryEscapes has no action, since it is done automatically
|
|
||||||
// by parsing the string as an URL. Same for FlagUppercaseEscapes and FlagRemoveEmptyQuerySeparator.
|
|
||||||
|
|
||||||
// Since maps have undefined traversing order, make a slice of ordered keys
|
|
||||||
var flagsOrder = []NormalizationFlags{
|
|
||||||
FlagLowercaseScheme,
|
|
||||||
FlagLowercaseHost,
|
|
||||||
FlagRemoveDefaultPort,
|
|
||||||
FlagRemoveDirectoryIndex,
|
|
||||||
FlagRemoveDotSegments,
|
|
||||||
FlagRemoveFragment,
|
|
||||||
FlagForceHTTP, // Must be after remove default port (because https=443/http=80)
|
|
||||||
FlagRemoveDuplicateSlashes,
|
|
||||||
FlagRemoveWWW,
|
|
||||||
FlagAddWWW,
|
|
||||||
FlagSortQuery,
|
|
||||||
FlagDecodeDWORDHost,
|
|
||||||
FlagDecodeOctalHost,
|
|
||||||
FlagDecodeHexHost,
|
|
||||||
FlagRemoveUnnecessaryHostDots,
|
|
||||||
FlagRemoveEmptyPortSeparator,
|
|
||||||
FlagRemoveTrailingSlash, // These two (add/remove trailing slash) must be last
|
|
||||||
FlagAddTrailingSlash,
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... and then the map, where order is unimportant
|
|
||||||
var flags = map[NormalizationFlags]func(*url.URL){
|
|
||||||
FlagLowercaseScheme: lowercaseScheme,
|
|
||||||
FlagLowercaseHost: lowercaseHost,
|
|
||||||
FlagRemoveDefaultPort: removeDefaultPort,
|
|
||||||
FlagRemoveDirectoryIndex: removeDirectoryIndex,
|
|
||||||
FlagRemoveDotSegments: removeDotSegments,
|
|
||||||
FlagRemoveFragment: removeFragment,
|
|
||||||
FlagForceHTTP: forceHTTP,
|
|
||||||
FlagRemoveDuplicateSlashes: removeDuplicateSlashes,
|
|
||||||
FlagRemoveWWW: removeWWW,
|
|
||||||
FlagAddWWW: addWWW,
|
|
||||||
FlagSortQuery: sortQuery,
|
|
||||||
FlagDecodeDWORDHost: decodeDWORDHost,
|
|
||||||
FlagDecodeOctalHost: decodeOctalHost,
|
|
||||||
FlagDecodeHexHost: decodeHexHost,
|
|
||||||
FlagRemoveUnnecessaryHostDots: removeUnncessaryHostDots,
|
|
||||||
FlagRemoveEmptyPortSeparator: removeEmptyPortSeparator,
|
|
||||||
FlagRemoveTrailingSlash: removeTrailingSlash,
|
|
||||||
FlagAddTrailingSlash: addTrailingSlash,
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustNormalizeURLString returns the normalized string, and panics if an error occurs.
|
|
||||||
// It takes an URL string as input, as well as the normalization flags.
|
|
||||||
func MustNormalizeURLString(u string, f NormalizationFlags) string {
|
|
||||||
result, e := NormalizeURLString(u, f)
|
|
||||||
if e != nil {
|
|
||||||
panic(e)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// NormalizeURLString returns the normalized string, or an error if it can't be parsed into an URL object.
|
|
||||||
// It takes an URL string as input, as well as the normalization flags.
|
|
||||||
func NormalizeURLString(u string, f NormalizationFlags) (string, error) {
|
|
||||||
parsed, err := url.Parse(u)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if f&FlagLowercaseHost == FlagLowercaseHost {
|
|
||||||
parsed.Host = strings.ToLower(parsed.Host)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The idna package doesn't fully conform to RFC 5895
|
|
||||||
// (https://tools.ietf.org/html/rfc5895), so we do it here.
|
|
||||||
// Taken from Go 1.8 cycle source, courtesy of bradfitz.
|
|
||||||
// TODO: Remove when (if?) idna package conforms to RFC 5895.
|
|
||||||
parsed.Host = width.Fold.String(parsed.Host)
|
|
||||||
parsed.Host = norm.NFC.String(parsed.Host)
|
|
||||||
if parsed.Host, err = idna.ToASCII(parsed.Host); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return NormalizeURL(parsed, f), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NormalizeURL returns the normalized string.
|
|
||||||
// It takes a parsed URL object as input, as well as the normalization flags.
|
|
||||||
func NormalizeURL(u *url.URL, f NormalizationFlags) string {
|
|
||||||
for _, k := range flagsOrder {
|
|
||||||
if f&k == k {
|
|
||||||
flags[k](u)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return urlesc.Escape(u)
|
|
||||||
}
|
|
||||||
|
|
||||||
func lowercaseScheme(u *url.URL) {
|
|
||||||
if len(u.Scheme) > 0 {
|
|
||||||
u.Scheme = strings.ToLower(u.Scheme)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func lowercaseHost(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 {
|
|
||||||
u.Host = strings.ToLower(u.Host)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeDefaultPort(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 {
|
|
||||||
scheme := strings.ToLower(u.Scheme)
|
|
||||||
u.Host = rxPort.ReplaceAllStringFunc(u.Host, func(val string) string {
|
|
||||||
if (scheme == "http" && val == defaultHttpPort) || (scheme == "https" && val == defaultHttpsPort) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeTrailingSlash(u *url.URL) {
|
|
||||||
if l := len(u.Path); l > 0 {
|
|
||||||
if strings.HasSuffix(u.Path, "/") {
|
|
||||||
u.Path = u.Path[:l-1]
|
|
||||||
}
|
|
||||||
} else if l = len(u.Host); l > 0 {
|
|
||||||
if strings.HasSuffix(u.Host, "/") {
|
|
||||||
u.Host = u.Host[:l-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addTrailingSlash(u *url.URL) {
|
|
||||||
if l := len(u.Path); l > 0 {
|
|
||||||
if !strings.HasSuffix(u.Path, "/") {
|
|
||||||
u.Path += "/"
|
|
||||||
}
|
|
||||||
} else if l = len(u.Host); l > 0 {
|
|
||||||
if !strings.HasSuffix(u.Host, "/") {
|
|
||||||
u.Host += "/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeDotSegments(u *url.URL) {
|
|
||||||
if len(u.Path) > 0 {
|
|
||||||
var dotFree []string
|
|
||||||
var lastIsDot bool
|
|
||||||
|
|
||||||
sections := strings.Split(u.Path, "/")
|
|
||||||
for _, s := range sections {
|
|
||||||
if s == ".." {
|
|
||||||
if len(dotFree) > 0 {
|
|
||||||
dotFree = dotFree[:len(dotFree)-1]
|
|
||||||
}
|
|
||||||
} else if s != "." {
|
|
||||||
dotFree = append(dotFree, s)
|
|
||||||
}
|
|
||||||
lastIsDot = (s == "." || s == "..")
|
|
||||||
}
|
|
||||||
// Special case if host does not end with / and new path does not begin with /
|
|
||||||
u.Path = strings.Join(dotFree, "/")
|
|
||||||
if u.Host != "" && !strings.HasSuffix(u.Host, "/") && !strings.HasPrefix(u.Path, "/") {
|
|
||||||
u.Path = "/" + u.Path
|
|
||||||
}
|
|
||||||
// Special case if the last segment was a dot, make sure the path ends with a slash
|
|
||||||
if lastIsDot && !strings.HasSuffix(u.Path, "/") {
|
|
||||||
u.Path += "/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeDirectoryIndex(u *url.URL) {
|
|
||||||
if len(u.Path) > 0 {
|
|
||||||
u.Path = rxDirIndex.ReplaceAllString(u.Path, "$1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeFragment(u *url.URL) {
|
|
||||||
u.Fragment = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func forceHTTP(u *url.URL) {
|
|
||||||
if strings.ToLower(u.Scheme) == "https" {
|
|
||||||
u.Scheme = "http"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeDuplicateSlashes(u *url.URL) {
|
|
||||||
if len(u.Path) > 0 {
|
|
||||||
u.Path = rxDupSlashes.ReplaceAllString(u.Path, "/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeWWW(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 && strings.HasPrefix(strings.ToLower(u.Host), "www.") {
|
|
||||||
u.Host = u.Host[4:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addWWW(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 && !strings.HasPrefix(strings.ToLower(u.Host), "www.") {
|
|
||||||
u.Host = "www." + u.Host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sortQuery(u *url.URL) {
|
|
||||||
q := u.Query()
|
|
||||||
|
|
||||||
if len(q) > 0 {
|
|
||||||
arKeys := make([]string, len(q))
|
|
||||||
i := 0
|
|
||||||
for k, _ := range q {
|
|
||||||
arKeys[i] = k
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
sort.Strings(arKeys)
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
for _, k := range arKeys {
|
|
||||||
sort.Strings(q[k])
|
|
||||||
for _, v := range q[k] {
|
|
||||||
if buf.Len() > 0 {
|
|
||||||
buf.WriteRune('&')
|
|
||||||
}
|
|
||||||
buf.WriteString(fmt.Sprintf("%s=%s", k, urlesc.QueryEscape(v)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rebuild the raw query string
|
|
||||||
u.RawQuery = buf.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeDWORDHost(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 {
|
|
||||||
if matches := rxDWORDHost.FindStringSubmatch(u.Host); len(matches) > 2 {
|
|
||||||
var parts [4]int64
|
|
||||||
|
|
||||||
dword, _ := strconv.ParseInt(matches[1], 10, 0)
|
|
||||||
for i, shift := range []uint{24, 16, 8, 0} {
|
|
||||||
parts[i] = dword >> shift & 0xFF
|
|
||||||
}
|
|
||||||
u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeOctalHost(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 {
|
|
||||||
if matches := rxOctalHost.FindStringSubmatch(u.Host); len(matches) > 5 {
|
|
||||||
var parts [4]int64
|
|
||||||
|
|
||||||
for i := 1; i <= 4; i++ {
|
|
||||||
parts[i-1], _ = strconv.ParseInt(matches[i], 8, 0)
|
|
||||||
}
|
|
||||||
u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[5])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeHexHost(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 {
|
|
||||||
if matches := rxHexHost.FindStringSubmatch(u.Host); len(matches) > 2 {
|
|
||||||
// Conversion is safe because of regex validation
|
|
||||||
parsed, _ := strconv.ParseInt(matches[1], 16, 0)
|
|
||||||
// Set host as DWORD (base 10) encoded host
|
|
||||||
u.Host = fmt.Sprintf("%d%s", parsed, matches[2])
|
|
||||||
// The rest is the same as decoding a DWORD host
|
|
||||||
decodeDWORDHost(u)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeUnncessaryHostDots(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 {
|
|
||||||
if matches := rxHostDots.FindStringSubmatch(u.Host); len(matches) > 1 {
|
|
||||||
// Trim the leading and trailing dots
|
|
||||||
u.Host = strings.Trim(matches[1], ".")
|
|
||||||
if len(matches) > 2 {
|
|
||||||
u.Host += matches[2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeEmptyPortSeparator(u *url.URL) {
|
|
||||||
if len(u.Host) > 0 {
|
|
||||||
u.Host = rxEmptyPort.ReplaceAllString(u.Host, "")
|
|
||||||
}
|
|
||||||
}
|
|
768
vendor/github.com/PuerkitoBio/purell/purell_test.go
generated
vendored
768
vendor/github.com/PuerkitoBio/purell/purell_test.go
generated
vendored
@ -1,768 +0,0 @@
|
|||||||
package purell
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testCase struct {
|
|
||||||
nm string
|
|
||||||
src string
|
|
||||||
flgs NormalizationFlags
|
|
||||||
res string
|
|
||||||
parsed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
cases = [...]*testCase{
|
|
||||||
&testCase{
|
|
||||||
"LowerScheme",
|
|
||||||
"HTTP://www.SRC.ca",
|
|
||||||
FlagLowercaseScheme,
|
|
||||||
"http://www.SRC.ca",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"LowerScheme2",
|
|
||||||
"http://www.SRC.ca",
|
|
||||||
FlagLowercaseScheme,
|
|
||||||
"http://www.SRC.ca",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"LowerHost",
|
|
||||||
"HTTP://www.SRC.ca/",
|
|
||||||
FlagLowercaseHost,
|
|
||||||
"http://www.src.ca/", // Since Go1.1, scheme is automatically lowercased
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"UpperEscapes",
|
|
||||||
`http://www.whatever.com/Some%aa%20Special%8Ecases/`,
|
|
||||||
FlagUppercaseEscapes,
|
|
||||||
"http://www.whatever.com/Some%AA%20Special%8Ecases/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"UnnecessaryEscapes",
|
|
||||||
`http://www.toto.com/%41%42%2E%44/%32%33%52%2D/%5f%7E`,
|
|
||||||
FlagDecodeUnnecessaryEscapes,
|
|
||||||
"http://www.toto.com/AB.D/23R-/_~",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"RemoveDefaultPort",
|
|
||||||
"HTTP://www.SRC.ca:80/",
|
|
||||||
FlagRemoveDefaultPort,
|
|
||||||
"http://www.SRC.ca/", // Since Go1.1, scheme is automatically lowercased
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"RemoveDefaultPort2",
|
|
||||||
"HTTP://www.SRC.ca:80",
|
|
||||||
FlagRemoveDefaultPort,
|
|
||||||
"http://www.SRC.ca", // Since Go1.1, scheme is automatically lowercased
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"RemoveDefaultPort3",
|
|
||||||
"HTTP://www.SRC.ca:8080",
|
|
||||||
FlagRemoveDefaultPort,
|
|
||||||
"http://www.SRC.ca:8080", // Since Go1.1, scheme is automatically lowercased
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Safe",
|
|
||||||
"HTTP://www.SRC.ca:80/to%1ato%8b%ee/OKnow%41%42%43%7e",
|
|
||||||
FlagsSafe,
|
|
||||||
"http://www.src.ca/to%1Ato%8B%EE/OKnowABC~",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"BothLower",
|
|
||||||
"HTTP://www.SRC.ca:80/to%1ato%8b%ee/OKnow%41%42%43%7e",
|
|
||||||
FlagLowercaseHost | FlagLowercaseScheme,
|
|
||||||
"http://www.src.ca:80/to%1Ato%8B%EE/OKnowABC~",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"RemoveTrailingSlash",
|
|
||||||
"HTTP://www.SRC.ca:80/",
|
|
||||||
FlagRemoveTrailingSlash,
|
|
||||||
"http://www.SRC.ca:80", // Since Go1.1, scheme is automatically lowercased
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"RemoveTrailingSlash2",
|
|
||||||
"HTTP://www.SRC.ca:80/toto/titi/",
|
|
||||||
FlagRemoveTrailingSlash,
|
|
||||||
"http://www.SRC.ca:80/toto/titi", // Since Go1.1, scheme is automatically lowercased
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"RemoveTrailingSlash3",
|
|
||||||
"HTTP://www.SRC.ca:80/toto/titi/fin/?a=1",
|
|
||||||
FlagRemoveTrailingSlash,
|
|
||||||
"http://www.SRC.ca:80/toto/titi/fin?a=1", // Since Go1.1, scheme is automatically lowercased
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"AddTrailingSlash",
|
|
||||||
"HTTP://www.SRC.ca:80",
|
|
||||||
FlagAddTrailingSlash,
|
|
||||||
"http://www.SRC.ca:80/", // Since Go1.1, scheme is automatically lowercased
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"AddTrailingSlash2",
|
|
||||||
"HTTP://www.SRC.ca:80/toto/titi.html",
|
|
||||||
FlagAddTrailingSlash,
|
|
||||||
"http://www.SRC.ca:80/toto/titi.html/", // Since Go1.1, scheme is automatically lowercased
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"AddTrailingSlash3",
|
|
||||||
"HTTP://www.SRC.ca:80/toto/titi/fin?a=1",
|
|
||||||
FlagAddTrailingSlash,
|
|
||||||
"http://www.SRC.ca:80/toto/titi/fin/?a=1", // Since Go1.1, scheme is automatically lowercased
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"RemoveDotSegments",
|
|
||||||
"HTTP://root/a/b/./../../c/",
|
|
||||||
FlagRemoveDotSegments,
|
|
||||||
"http://root/c/", // Since Go1.1, scheme is automatically lowercased
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"RemoveDotSegments2",
|
|
||||||
"HTTP://root/../a/b/./../c/../d",
|
|
||||||
FlagRemoveDotSegments,
|
|
||||||
"http://root/a/d", // Since Go1.1, scheme is automatically lowercased
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"UsuallySafe",
|
|
||||||
"HTTP://www.SRC.ca:80/to%1ato%8b%ee/./c/d/../OKnow%41%42%43%7e/?a=b#test",
|
|
||||||
FlagsUsuallySafeGreedy,
|
|
||||||
"http://www.src.ca/to%1Ato%8B%EE/c/OKnowABC~?a=b#test",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"RemoveDirectoryIndex",
|
|
||||||
"HTTP://root/a/b/c/default.aspx",
|
|
||||||
FlagRemoveDirectoryIndex,
|
|
||||||
"http://root/a/b/c/", // Since Go1.1, scheme is automatically lowercased
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"RemoveDirectoryIndex2",
|
|
||||||
"HTTP://root/a/b/c/default#a=b",
|
|
||||||
FlagRemoveDirectoryIndex,
|
|
||||||
"http://root/a/b/c/default#a=b", // Since Go1.1, scheme is automatically lowercased
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"RemoveFragment",
|
|
||||||
"HTTP://root/a/b/c/default#toto=tata",
|
|
||||||
FlagRemoveFragment,
|
|
||||||
"http://root/a/b/c/default", // Since Go1.1, scheme is automatically lowercased
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"ForceHTTP",
|
|
||||||
"https://root/a/b/c/default#toto=tata",
|
|
||||||
FlagForceHTTP,
|
|
||||||
"http://root/a/b/c/default#toto=tata",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"RemoveDuplicateSlashes",
|
|
||||||
"https://root/a//b///c////default#toto=tata",
|
|
||||||
FlagRemoveDuplicateSlashes,
|
|
||||||
"https://root/a/b/c/default#toto=tata",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"RemoveDuplicateSlashes2",
|
|
||||||
"https://root//a//b///c////default#toto=tata",
|
|
||||||
FlagRemoveDuplicateSlashes,
|
|
||||||
"https://root/a/b/c/default#toto=tata",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"RemoveWWW",
|
|
||||||
"https://www.root/a/b/c/",
|
|
||||||
FlagRemoveWWW,
|
|
||||||
"https://root/a/b/c/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"RemoveWWW2",
|
|
||||||
"https://WwW.Root/a/b/c/",
|
|
||||||
FlagRemoveWWW,
|
|
||||||
"https://Root/a/b/c/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"AddWWW",
|
|
||||||
"https://Root/a/b/c/",
|
|
||||||
FlagAddWWW,
|
|
||||||
"https://www.Root/a/b/c/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"SortQuery",
|
|
||||||
"http://root/toto/?b=4&a=1&c=3&b=2&a=5",
|
|
||||||
FlagSortQuery,
|
|
||||||
"http://root/toto/?a=1&a=5&b=2&b=4&c=3",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"RemoveEmptyQuerySeparator",
|
|
||||||
"http://root/toto/?",
|
|
||||||
FlagRemoveEmptyQuerySeparator,
|
|
||||||
"http://root/toto/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Unsafe",
|
|
||||||
"HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid",
|
|
||||||
FlagsUnsafeGreedy,
|
|
||||||
"http://root.com/toto/tE%1F/a/c?a=4&w=1&w=2&z=3",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Safe2",
|
|
||||||
"HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid",
|
|
||||||
FlagsSafe,
|
|
||||||
"https://www.root.com/toto/tE%1F///a/./b/../c/?z=3&w=2&a=4&w=1#invalid",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"UsuallySafe2",
|
|
||||||
"HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid",
|
|
||||||
FlagsUsuallySafeGreedy,
|
|
||||||
"https://www.root.com/toto/tE%1F///a/c?z=3&w=2&a=4&w=1#invalid",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"AddTrailingSlashBug",
|
|
||||||
"http://src.ca/",
|
|
||||||
FlagsAllNonGreedy,
|
|
||||||
"http://www.src.ca/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"SourceModified",
|
|
||||||
"HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid",
|
|
||||||
FlagsUnsafeGreedy,
|
|
||||||
"http://root.com/toto/tE%1F/a/c?a=4&w=1&w=2&z=3",
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"IPv6-1",
|
|
||||||
"http://[2001:db8:1f70::999:de8:7648:6e8]/test",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments,
|
|
||||||
"http://[2001:db8:1f70::999:de8:7648:6e8]/test",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"IPv6-2",
|
|
||||||
"http://[::ffff:192.168.1.1]/test",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments,
|
|
||||||
"http://[::ffff:192.168.1.1]/test",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"IPv6-3",
|
|
||||||
"http://[::ffff:192.168.1.1]:80/test",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments,
|
|
||||||
"http://[::ffff:192.168.1.1]/test",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"IPv6-4",
|
|
||||||
"htTps://[::fFff:192.168.1.1]:443/test",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments,
|
|
||||||
"https://[::ffff:192.168.1.1]/test",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"FTP",
|
|
||||||
"ftp://user:pass@ftp.foo.net/foo/bar",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments,
|
|
||||||
"ftp://user:pass@ftp.foo.net/foo/bar",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Standard-1",
|
|
||||||
"http://www.foo.com:80/foo",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments,
|
|
||||||
"http://www.foo.com/foo",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Standard-2",
|
|
||||||
"http://www.foo.com:8000/foo",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments,
|
|
||||||
"http://www.foo.com:8000/foo",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Standard-3",
|
|
||||||
"http://www.foo.com/%7ebar",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments,
|
|
||||||
"http://www.foo.com/~bar",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Standard-4",
|
|
||||||
"http://www.foo.com/%7Ebar",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments,
|
|
||||||
"http://www.foo.com/~bar",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Standard-5",
|
|
||||||
"http://USER:pass@www.Example.COM/foo/bar",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments,
|
|
||||||
"http://USER:pass@www.example.com/foo/bar",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Standard-6",
|
|
||||||
"http://test.example/?a=%26&b=1",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments,
|
|
||||||
"http://test.example/?a=%26&b=1",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Standard-7",
|
|
||||||
"http://test.example/%25/?p=%20val%20%25",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments,
|
|
||||||
"http://test.example/%25/?p=%20val%20%25",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Standard-8",
|
|
||||||
"http://test.example/path/with a%20space+/",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments,
|
|
||||||
"http://test.example/path/with%20a%20space+/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Standard-9",
|
|
||||||
"http://test.example/?",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments,
|
|
||||||
"http://test.example/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Standard-10",
|
|
||||||
"http://a.COM/path/?b&a",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments,
|
|
||||||
"http://a.com/path/?b&a",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"StandardCasesAddTrailingSlash",
|
|
||||||
"http://test.example?",
|
|
||||||
FlagsSafe | FlagAddTrailingSlash,
|
|
||||||
"http://test.example/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"OctalIP-1",
|
|
||||||
"http://0123.011.0.4/",
|
|
||||||
FlagsSafe | FlagDecodeOctalHost,
|
|
||||||
"http://0123.011.0.4/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"OctalIP-2",
|
|
||||||
"http://0102.0146.07.0223/",
|
|
||||||
FlagsSafe | FlagDecodeOctalHost,
|
|
||||||
"http://66.102.7.147/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"OctalIP-3",
|
|
||||||
"http://0102.0146.07.0223.:23/",
|
|
||||||
FlagsSafe | FlagDecodeOctalHost,
|
|
||||||
"http://66.102.7.147.:23/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"OctalIP-4",
|
|
||||||
"http://USER:pass@0102.0146.07.0223../",
|
|
||||||
FlagsSafe | FlagDecodeOctalHost,
|
|
||||||
"http://USER:pass@66.102.7.147../",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"DWORDIP-1",
|
|
||||||
"http://123.1113982867/",
|
|
||||||
FlagsSafe | FlagDecodeDWORDHost,
|
|
||||||
"http://123.1113982867/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"DWORDIP-2",
|
|
||||||
"http://1113982867/",
|
|
||||||
FlagsSafe | FlagDecodeDWORDHost,
|
|
||||||
"http://66.102.7.147/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"DWORDIP-3",
|
|
||||||
"http://1113982867.:23/",
|
|
||||||
FlagsSafe | FlagDecodeDWORDHost,
|
|
||||||
"http://66.102.7.147.:23/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"DWORDIP-4",
|
|
||||||
"http://USER:pass@1113982867../",
|
|
||||||
FlagsSafe | FlagDecodeDWORDHost,
|
|
||||||
"http://USER:pass@66.102.7.147../",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"HexIP-1",
|
|
||||||
"http://0x123.1113982867/",
|
|
||||||
FlagsSafe | FlagDecodeHexHost,
|
|
||||||
"http://0x123.1113982867/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"HexIP-2",
|
|
||||||
"http://0x42660793/",
|
|
||||||
FlagsSafe | FlagDecodeHexHost,
|
|
||||||
"http://66.102.7.147/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"HexIP-3",
|
|
||||||
"http://0x42660793.:23/",
|
|
||||||
FlagsSafe | FlagDecodeHexHost,
|
|
||||||
"http://66.102.7.147.:23/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"HexIP-4",
|
|
||||||
"http://USER:pass@0x42660793../",
|
|
||||||
FlagsSafe | FlagDecodeHexHost,
|
|
||||||
"http://USER:pass@66.102.7.147../",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"UnnecessaryHostDots-1",
|
|
||||||
"http://.www.foo.com../foo/bar.html",
|
|
||||||
FlagsSafe | FlagRemoveUnnecessaryHostDots,
|
|
||||||
"http://www.foo.com/foo/bar.html",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"UnnecessaryHostDots-2",
|
|
||||||
"http://www.foo.com./foo/bar.html",
|
|
||||||
FlagsSafe | FlagRemoveUnnecessaryHostDots,
|
|
||||||
"http://www.foo.com/foo/bar.html",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"UnnecessaryHostDots-3",
|
|
||||||
"http://www.foo.com.:81/foo",
|
|
||||||
FlagsSafe | FlagRemoveUnnecessaryHostDots,
|
|
||||||
"http://www.foo.com:81/foo",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"UnnecessaryHostDots-4",
|
|
||||||
"http://www.example.com./",
|
|
||||||
FlagsSafe | FlagRemoveUnnecessaryHostDots,
|
|
||||||
"http://www.example.com/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"EmptyPort-1",
|
|
||||||
"http://www.thedraymin.co.uk:/main/?p=308",
|
|
||||||
FlagsSafe | FlagRemoveEmptyPortSeparator,
|
|
||||||
"http://www.thedraymin.co.uk/main/?p=308",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"EmptyPort-2",
|
|
||||||
"http://www.src.ca:",
|
|
||||||
FlagsSafe | FlagRemoveEmptyPortSeparator,
|
|
||||||
"http://www.src.ca",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-1",
|
|
||||||
"http://test.example/foo/bar/.",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/foo/bar/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-2",
|
|
||||||
"http://test.example/foo/bar/./",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/foo/bar/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-3",
|
|
||||||
"http://test.example/foo/bar/..",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/foo/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-4",
|
|
||||||
"http://test.example/foo/bar/../",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/foo/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-5",
|
|
||||||
"http://test.example/foo/bar/../baz",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/foo/baz",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-6",
|
|
||||||
"http://test.example/foo/bar/../..",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-7",
|
|
||||||
"http://test.example/foo/bar/../../",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-8",
|
|
||||||
"http://test.example/foo/bar/../../baz",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/baz",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-9",
|
|
||||||
"http://test.example/foo/bar/../../../baz",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/baz",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-10",
|
|
||||||
"http://test.example/foo/bar/../../../../baz",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/baz",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-11",
|
|
||||||
"http://test.example/./foo",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/foo",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-12",
|
|
||||||
"http://test.example/../foo",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/foo",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-13",
|
|
||||||
"http://test.example/foo.",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/foo.",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-14",
|
|
||||||
"http://test.example/.foo",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/.foo",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-15",
|
|
||||||
"http://test.example/foo..",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/foo..",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-16",
|
|
||||||
"http://test.example/..foo",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/..foo",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-17",
|
|
||||||
"http://test.example/./../foo",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/foo",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-18",
|
|
||||||
"http://test.example/./foo/.",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/foo/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-19",
|
|
||||||
"http://test.example/foo/./bar",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/foo/bar",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-20",
|
|
||||||
"http://test.example/foo/../bar",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/bar",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-21",
|
|
||||||
"http://test.example/foo//",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/foo/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Slashes-22",
|
|
||||||
"http://test.example/foo///bar//",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"http://test.example/foo/bar/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Relative",
|
|
||||||
"foo/bar",
|
|
||||||
FlagsAllGreedy,
|
|
||||||
"foo/bar",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Relative-1",
|
|
||||||
"./../foo",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"foo",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Relative-2",
|
|
||||||
"./foo/bar/../baz/../bang/..",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"foo/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Relative-3",
|
|
||||||
"foo///bar//",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments | FlagRemoveDuplicateSlashes,
|
|
||||||
"foo/bar/",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"Relative-4",
|
|
||||||
"www.youtube.com",
|
|
||||||
FlagsUsuallySafeGreedy,
|
|
||||||
"www.youtube.com",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
/*&testCase{
|
|
||||||
"UrlNorm-5",
|
|
||||||
"http://ja.wikipedia.org/wiki/%E3%82%AD%E3%83%A3%E3%82%BF%E3%83%94%E3%83%A9%E3%83%BC%E3%82%B8%E3%83%A3%E3%83%91%E3%83%B3",
|
|
||||||
FlagsSafe | FlagRemoveDotSegments,
|
|
||||||
"http://ja.wikipedia.org/wiki/\xe3\x82\xad\xe3\x83\xa3\xe3\x82\xbf\xe3\x83\x94\xe3\x83\xa9\xe3\x83\xbc\xe3\x82\xb8\xe3\x83\xa3\xe3\x83\x91\xe3\x83\xb3",
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
&testCase{
|
|
||||||
"UrlNorm-1",
|
|
||||||
"http://test.example/?a=%e3%82%82%26",
|
|
||||||
FlagsAllGreedy,
|
|
||||||
"http://test.example/?a=\xe3\x82\x82%26",
|
|
||||||
false,
|
|
||||||
},*/
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRunner(t *testing.T) {
|
|
||||||
for _, tc := range cases {
|
|
||||||
runCase(tc, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runCase(tc *testCase, t *testing.T) {
|
|
||||||
t.Logf("running %s...", tc.nm)
|
|
||||||
if tc.parsed {
|
|
||||||
u, e := url.Parse(tc.src)
|
|
||||||
if e != nil {
|
|
||||||
t.Errorf("%s - FAIL : %s", tc.nm, e)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
NormalizeURL(u, tc.flgs)
|
|
||||||
if s := u.String(); s != tc.res {
|
|
||||||
t.Errorf("%s - FAIL expected '%s', got '%s'", tc.nm, tc.res, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if s, e := NormalizeURLString(tc.src, tc.flgs); e != nil {
|
|
||||||
t.Errorf("%s - FAIL : %s", tc.nm, e)
|
|
||||||
} else if s != tc.res {
|
|
||||||
t.Errorf("%s - FAIL expected '%s', got '%s'", tc.nm, tc.res, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecodeUnnecessaryEscapesAll(t *testing.T) {
|
|
||||||
var url = "http://host/"
|
|
||||||
|
|
||||||
for i := 0; i < 256; i++ {
|
|
||||||
url += fmt.Sprintf("%%%02x", i)
|
|
||||||
}
|
|
||||||
if s, e := NormalizeURLString(url, FlagDecodeUnnecessaryEscapes); e != nil {
|
|
||||||
t.Fatalf("Got error %s", e.Error())
|
|
||||||
} else {
|
|
||||||
const want = "http://host/%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20!%22%23$%25&'()*+,-./0123456789:;%3C=%3E%3F@ABCDEFGHIJKLMNOPQRSTUVWXYZ[%5C]%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF"
|
|
||||||
if s != want {
|
|
||||||
t.Errorf("DecodeUnnecessaryEscapesAll:\nwant\n%s\ngot\n%s", want, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeNecessaryEscapesAll(t *testing.T) {
|
|
||||||
var url = "http://host/"
|
|
||||||
|
|
||||||
for i := 0; i < 256; i++ {
|
|
||||||
if i != 0x25 {
|
|
||||||
url += string(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if s, e := NormalizeURLString(url, FlagEncodeNecessaryEscapes); e != nil {
|
|
||||||
t.Fatalf("Got error %s", e.Error())
|
|
||||||
} else {
|
|
||||||
const want = "http://host/%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20!%22#$&'()*+,-./0123456789:;%3C=%3E?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[%5C]%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%C2%80%C2%81%C2%82%C2%83%C2%84%C2%85%C2%86%C2%87%C2%88%C2%89%C2%8A%C2%8B%C2%8C%C2%8D%C2%8E%C2%8F%C2%90%C2%91%C2%92%C2%93%C2%94%C2%95%C2%96%C2%97%C2%98%C2%99%C2%9A%C2%9B%C2%9C%C2%9D%C2%9E%C2%9F%C2%A0%C2%A1%C2%A2%C2%A3%C2%A4%C2%A5%C2%A6%C2%A7%C2%A8%C2%A9%C2%AA%C2%AB%C2%AC%C2%AD%C2%AE%C2%AF%C2%B0%C2%B1%C2%B2%C2%B3%C2%B4%C2%B5%C2%B6%C2%B7%C2%B8%C2%B9%C2%BA%C2%BB%C2%BC%C2%BD%C2%BE%C2%BF%C3%80%C3%81%C3%82%C3%83%C3%84%C3%85%C3%86%C3%87%C3%88%C3%89%C3%8A%C3%8B%C3%8C%C3%8D%C3%8E%C3%8F%C3%90%C3%91%C3%92%C3%93%C3%94%C3%95%C3%96%C3%97%C3%98%C3%99%C3%9A%C3%9B%C3%9C%C3%9D%C3%9E%C3%9F%C3%A0%C3%A1%C3%A2%C3%A3%C3%A4%C3%A5%C3%A6%C3%A7%C3%A8%C3%A9%C3%AA%C3%AB%C3%AC%C3%AD%C3%AE%C3%AF%C3%B0%C3%B1%C3%B2%C3%B3%C3%B4%C3%B5%C3%B6%C3%B7%C3%B8%C3%B9%C3%BA%C3%BB%C3%BC%C3%BD%C3%BE%C3%BF"
|
|
||||||
if s != want {
|
|
||||||
t.Errorf("EncodeNecessaryEscapesAll:\nwant\n%s\ngot\n%s", want, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
53
vendor/github.com/PuerkitoBio/purell/urlnorm_test.go
generated
vendored
53
vendor/github.com/PuerkitoBio/purell/urlnorm_test.go
generated
vendored
@ -1,53 +0,0 @@
|
|||||||
package purell
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test cases merged from PR #1
|
|
||||||
// Originally from https://github.com/jehiah/urlnorm/blob/master/test_urlnorm.py
|
|
||||||
|
|
||||||
func assertMap(t *testing.T, cases map[string]string, f NormalizationFlags) {
|
|
||||||
for bad, good := range cases {
|
|
||||||
s, e := NormalizeURLString(bad, f)
|
|
||||||
if e != nil {
|
|
||||||
t.Errorf("%s normalizing %v to %v", e.Error(), bad, good)
|
|
||||||
} else {
|
|
||||||
if s != good {
|
|
||||||
t.Errorf("source: %v expected: %v got: %v", bad, good, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This tests normalization to a unicode representation
|
|
||||||
// precent escapes for unreserved values are unescaped to their unicode value
|
|
||||||
// tests normalization to idna domains
|
|
||||||
// test ip word handling, ipv6 address handling, and trailing domain periods
|
|
||||||
// in general, this matches google chromes unescaping for things in the address bar.
|
|
||||||
// spaces are converted to '+' (perhaphs controversial)
|
|
||||||
// http://code.google.com/p/google-url/ probably is another good reference for this approach
|
|
||||||
func TestUrlnorm(t *testing.T) {
|
|
||||||
testcases := map[string]string{
|
|
||||||
"http://test.example/?a=%e3%82%82%26": "http://test.example/?a=%e3%82%82%26",
|
|
||||||
//"http://test.example/?a=%e3%82%82%26": "http://test.example/?a=\xe3\x82\x82%26", //should return a unicode character
|
|
||||||
"http://s.xn--q-bga.DE/": "http://s.xn--q-bga.de/", //should be in idna format
|
|
||||||
"http://XBLA\u306eXbox.com": "http://xn--xblaxbox-jf4g.com", //test utf8 and unicode
|
|
||||||
"http://президент.рф": "http://xn--d1abbgf6aiiy.xn--p1ai",
|
|
||||||
"http://ПРЕЗИДЕНТ.РФ": "http://xn--d1abbgf6aiiy.xn--p1ai",
|
|
||||||
"http://ab¥ヲ₩○.com": "http://xn--ab-ida8983azmfnvs.com", //test width folding
|
|
||||||
"http://\u00e9.com": "http://xn--9ca.com",
|
|
||||||
"http://e\u0301.com": "http://xn--9ca.com",
|
|
||||||
"http://ja.wikipedia.org/wiki/%E3%82%AD%E3%83%A3%E3%82%BF%E3%83%94%E3%83%A9%E3%83%BC%E3%82%B8%E3%83%A3%E3%83%91%E3%83%B3": "http://ja.wikipedia.org/wiki/%E3%82%AD%E3%83%A3%E3%82%BF%E3%83%94%E3%83%A9%E3%83%BC%E3%82%B8%E3%83%A3%E3%83%91%E3%83%B3",
|
|
||||||
//"http://ja.wikipedia.org/wiki/%E3%82%AD%E3%83%A3%E3%82%BF%E3%83%94%E3%83%A9%E3%83%BC%E3%82%B8%E3%83%A3%E3%83%91%E3%83%B3": "http://ja.wikipedia.org/wiki/\xe3\x82\xad\xe3\x83\xa3\xe3\x82\xbf\xe3\x83\x94\xe3\x83\xa9\xe3\x83\xbc\xe3\x82\xb8\xe3\x83\xa3\xe3\x83\x91\xe3\x83\xb3",
|
|
||||||
|
|
||||||
"http://test.example/\xe3\x82\xad": "http://test.example/%E3%82%AD",
|
|
||||||
//"http://test.example/\xe3\x82\xad": "http://test.example/\xe3\x82\xad",
|
|
||||||
"http://test.example/?p=%23val#test-%23-val%25": "http://test.example/?p=%23val#test-%23-val%25", //check that %23 (#) is not escaped where it shouldn't be
|
|
||||||
|
|
||||||
"http://test.domain/I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%EF%BF%BDliz%C3%A6ti%C3%B8n": "http://test.domain/I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%EF%BF%BDliz%C3%A6ti%C3%B8n",
|
|
||||||
//"http://test.domain/I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%EF%BF%BDliz%C3%A6ti%C3%B8n": "http://test.domain/I\xc3\xb1t\xc3\xabrn\xc3\xa2ti\xc3\xb4n\xef\xbf\xbdliz\xc3\xa6ti\xc3\xb8n",
|
|
||||||
}
|
|
||||||
|
|
||||||
assertMap(t, testcases, FlagsSafe|FlagRemoveDotSegments)
|
|
||||||
}
|
|
15
vendor/github.com/PuerkitoBio/urlesc/.travis.yml
generated
vendored
15
vendor/github.com/PuerkitoBio/urlesc/.travis.yml
generated
vendored
@ -1,15 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.4.x
|
|
||||||
- 1.5.x
|
|
||||||
- 1.6.x
|
|
||||||
- 1.7.x
|
|
||||||
- 1.8.x
|
|
||||||
- tip
|
|
||||||
|
|
||||||
install:
|
|
||||||
- go build .
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go test -v
|
|
27
vendor/github.com/PuerkitoBio/urlesc/LICENSE
generated
vendored
27
vendor/github.com/PuerkitoBio/urlesc/LICENSE
generated
vendored
@ -1,27 +0,0 @@
|
|||||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
16
vendor/github.com/PuerkitoBio/urlesc/README.md
generated
vendored
16
vendor/github.com/PuerkitoBio/urlesc/README.md
generated
vendored
@ -1,16 +0,0 @@
|
|||||||
urlesc [![Build Status](https://travis-ci.org/PuerkitoBio/urlesc.svg?branch=master)](https://travis-ci.org/PuerkitoBio/urlesc) [![GoDoc](http://godoc.org/github.com/PuerkitoBio/urlesc?status.svg)](http://godoc.org/github.com/PuerkitoBio/urlesc)
|
|
||||||
======
|
|
||||||
|
|
||||||
Package urlesc implements query escaping as per RFC 3986.
|
|
||||||
|
|
||||||
It contains some parts of the net/url package, modified so as to allow
|
|
||||||
some reserved characters incorrectly escaped by net/url (see [issue 5684](https://github.com/golang/go/issues/5684)).
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
go get github.com/PuerkitoBio/urlesc
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Go license (BSD-3-Clause)
|
|
||||||
|
|
180
vendor/github.com/PuerkitoBio/urlesc/urlesc.go
generated
vendored
180
vendor/github.com/PuerkitoBio/urlesc/urlesc.go
generated
vendored
@ -1,180 +0,0 @@
|
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package urlesc implements query escaping as per RFC 3986.
|
|
||||||
// It contains some parts of the net/url package, modified so as to allow
|
|
||||||
// some reserved characters incorrectly escaped by net/url.
|
|
||||||
// See https://github.com/golang/go/issues/5684
|
|
||||||
package urlesc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type encoding int
|
|
||||||
|
|
||||||
const (
|
|
||||||
encodePath encoding = 1 + iota
|
|
||||||
encodeUserPassword
|
|
||||||
encodeQueryComponent
|
|
||||||
encodeFragment
|
|
||||||
)
|
|
||||||
|
|
||||||
// Return true if the specified character should be escaped when
|
|
||||||
// appearing in a URL string, according to RFC 3986.
|
|
||||||
func shouldEscape(c byte, mode encoding) bool {
|
|
||||||
// §2.3 Unreserved characters (alphanum)
|
|
||||||
if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch c {
|
|
||||||
case '-', '.', '_', '~': // §2.3 Unreserved characters (mark)
|
|
||||||
return false
|
|
||||||
|
|
||||||
// §2.2 Reserved characters (reserved)
|
|
||||||
case ':', '/', '?', '#', '[', ']', '@', // gen-delims
|
|
||||||
'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=': // sub-delims
|
|
||||||
// Different sections of the URL allow a few of
|
|
||||||
// the reserved characters to appear unescaped.
|
|
||||||
switch mode {
|
|
||||||
case encodePath: // §3.3
|
|
||||||
// The RFC allows sub-delims and : @.
|
|
||||||
// '/', '[' and ']' can be used to assign meaning to individual path
|
|
||||||
// segments. This package only manipulates the path as a whole,
|
|
||||||
// so we allow those as well. That leaves only ? and # to escape.
|
|
||||||
return c == '?' || c == '#'
|
|
||||||
|
|
||||||
case encodeUserPassword: // §3.2.1
|
|
||||||
// The RFC allows : and sub-delims in
|
|
||||||
// userinfo. The parsing of userinfo treats ':' as special so we must escape
|
|
||||||
// all the gen-delims.
|
|
||||||
return c == ':' || c == '/' || c == '?' || c == '#' || c == '[' || c == ']' || c == '@'
|
|
||||||
|
|
||||||
case encodeQueryComponent: // §3.4
|
|
||||||
// The RFC allows / and ?.
|
|
||||||
return c != '/' && c != '?'
|
|
||||||
|
|
||||||
case encodeFragment: // §4.1
|
|
||||||
// The RFC text is silent but the grammar allows
|
|
||||||
// everything, so escape nothing but #
|
|
||||||
return c == '#'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Everything else must be escaped.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryEscape escapes the string so it can be safely placed
|
|
||||||
// inside a URL query.
|
|
||||||
func QueryEscape(s string) string {
|
|
||||||
return escape(s, encodeQueryComponent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func escape(s string, mode encoding) string {
|
|
||||||
spaceCount, hexCount := 0, 0
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
c := s[i]
|
|
||||||
if shouldEscape(c, mode) {
|
|
||||||
if c == ' ' && mode == encodeQueryComponent {
|
|
||||||
spaceCount++
|
|
||||||
} else {
|
|
||||||
hexCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if spaceCount == 0 && hexCount == 0 {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
t := make([]byte, len(s)+2*hexCount)
|
|
||||||
j := 0
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
switch c := s[i]; {
|
|
||||||
case c == ' ' && mode == encodeQueryComponent:
|
|
||||||
t[j] = '+'
|
|
||||||
j++
|
|
||||||
case shouldEscape(c, mode):
|
|
||||||
t[j] = '%'
|
|
||||||
t[j+1] = "0123456789ABCDEF"[c>>4]
|
|
||||||
t[j+2] = "0123456789ABCDEF"[c&15]
|
|
||||||
j += 3
|
|
||||||
default:
|
|
||||||
t[j] = s[i]
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
var uiReplacer = strings.NewReplacer(
|
|
||||||
"%21", "!",
|
|
||||||
"%27", "'",
|
|
||||||
"%28", "(",
|
|
||||||
"%29", ")",
|
|
||||||
"%2A", "*",
|
|
||||||
)
|
|
||||||
|
|
||||||
// unescapeUserinfo unescapes some characters that need not to be escaped as per RFC3986.
|
|
||||||
func unescapeUserinfo(s string) string {
|
|
||||||
return uiReplacer.Replace(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Escape reassembles the URL into a valid URL string.
|
|
||||||
// The general form of the result is one of:
|
|
||||||
//
|
|
||||||
// scheme:opaque
|
|
||||||
// scheme://userinfo@host/path?query#fragment
|
|
||||||
//
|
|
||||||
// If u.Opaque is non-empty, String uses the first form;
|
|
||||||
// otherwise it uses the second form.
|
|
||||||
//
|
|
||||||
// In the second form, the following rules apply:
|
|
||||||
// - if u.Scheme is empty, scheme: is omitted.
|
|
||||||
// - if u.User is nil, userinfo@ is omitted.
|
|
||||||
// - if u.Host is empty, host/ is omitted.
|
|
||||||
// - if u.Scheme and u.Host are empty and u.User is nil,
|
|
||||||
// the entire scheme://userinfo@host/ is omitted.
|
|
||||||
// - if u.Host is non-empty and u.Path begins with a /,
|
|
||||||
// the form host/path does not add its own /.
|
|
||||||
// - if u.RawQuery is empty, ?query is omitted.
|
|
||||||
// - if u.Fragment is empty, #fragment is omitted.
|
|
||||||
func Escape(u *url.URL) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if u.Scheme != "" {
|
|
||||||
buf.WriteString(u.Scheme)
|
|
||||||
buf.WriteByte(':')
|
|
||||||
}
|
|
||||||
if u.Opaque != "" {
|
|
||||||
buf.WriteString(u.Opaque)
|
|
||||||
} else {
|
|
||||||
if u.Scheme != "" || u.Host != "" || u.User != nil {
|
|
||||||
buf.WriteString("//")
|
|
||||||
if ui := u.User; ui != nil {
|
|
||||||
buf.WriteString(unescapeUserinfo(ui.String()))
|
|
||||||
buf.WriteByte('@')
|
|
||||||
}
|
|
||||||
if h := u.Host; h != "" {
|
|
||||||
buf.WriteString(h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if u.Path != "" && u.Path[0] != '/' && u.Host != "" {
|
|
||||||
buf.WriteByte('/')
|
|
||||||
}
|
|
||||||
buf.WriteString(escape(u.Path, encodePath))
|
|
||||||
}
|
|
||||||
if u.RawQuery != "" {
|
|
||||||
buf.WriteByte('?')
|
|
||||||
buf.WriteString(u.RawQuery)
|
|
||||||
}
|
|
||||||
if u.Fragment != "" {
|
|
||||||
buf.WriteByte('#')
|
|
||||||
buf.WriteString(escape(u.Fragment, encodeFragment))
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
641
vendor/github.com/PuerkitoBio/urlesc/urlesc_test.go
generated
vendored
641
vendor/github.com/PuerkitoBio/urlesc/urlesc_test.go
generated
vendored
@ -1,641 +0,0 @@
|
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package urlesc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/url"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type URLTest struct {
|
|
||||||
in string
|
|
||||||
out *url.URL
|
|
||||||
roundtrip string // expected result of reserializing the URL; empty means same as "in".
|
|
||||||
}
|
|
||||||
|
|
||||||
var urltests = []URLTest{
|
|
||||||
// no path
|
|
||||||
{
|
|
||||||
"http://www.google.com",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "www.google.com",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
// path
|
|
||||||
{
|
|
||||||
"http://www.google.com/",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "www.google.com",
|
|
||||||
Path: "/",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
// path with hex escaping
|
|
||||||
{
|
|
||||||
"http://www.google.com/file%20one%26two",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "www.google.com",
|
|
||||||
Path: "/file one&two",
|
|
||||||
},
|
|
||||||
"http://www.google.com/file%20one&two",
|
|
||||||
},
|
|
||||||
// user
|
|
||||||
{
|
|
||||||
"ftp://webmaster@www.google.com/",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "ftp",
|
|
||||||
User: url.User("webmaster"),
|
|
||||||
Host: "www.google.com",
|
|
||||||
Path: "/",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
// escape sequence in username
|
|
||||||
{
|
|
||||||
"ftp://john%20doe@www.google.com/",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "ftp",
|
|
||||||
User: url.User("john doe"),
|
|
||||||
Host: "www.google.com",
|
|
||||||
Path: "/",
|
|
||||||
},
|
|
||||||
"ftp://john%20doe@www.google.com/",
|
|
||||||
},
|
|
||||||
// query
|
|
||||||
{
|
|
||||||
"http://www.google.com/?q=go+language",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "www.google.com",
|
|
||||||
Path: "/",
|
|
||||||
RawQuery: "q=go+language",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
// query with hex escaping: NOT parsed
|
|
||||||
{
|
|
||||||
"http://www.google.com/?q=go%20language",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "www.google.com",
|
|
||||||
Path: "/",
|
|
||||||
RawQuery: "q=go%20language",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
// %20 outside query
|
|
||||||
{
|
|
||||||
"http://www.google.com/a%20b?q=c+d",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "www.google.com",
|
|
||||||
Path: "/a b",
|
|
||||||
RawQuery: "q=c+d",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
// path without leading /, so no parsing
|
|
||||||
{
|
|
||||||
"http:www.google.com/?q=go+language",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Opaque: "www.google.com/",
|
|
||||||
RawQuery: "q=go+language",
|
|
||||||
},
|
|
||||||
"http:www.google.com/?q=go+language",
|
|
||||||
},
|
|
||||||
// path without leading /, so no parsing
|
|
||||||
{
|
|
||||||
"http:%2f%2fwww.google.com/?q=go+language",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Opaque: "%2f%2fwww.google.com/",
|
|
||||||
RawQuery: "q=go+language",
|
|
||||||
},
|
|
||||||
"http:%2f%2fwww.google.com/?q=go+language",
|
|
||||||
},
|
|
||||||
// non-authority with path
|
|
||||||
{
|
|
||||||
"mailto:/webmaster@golang.org",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "mailto",
|
|
||||||
Path: "/webmaster@golang.org",
|
|
||||||
},
|
|
||||||
"mailto:///webmaster@golang.org", // unfortunate compromise
|
|
||||||
},
|
|
||||||
// non-authority
|
|
||||||
{
|
|
||||||
"mailto:webmaster@golang.org",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "mailto",
|
|
||||||
Opaque: "webmaster@golang.org",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
// unescaped :// in query should not create a scheme
|
|
||||||
{
|
|
||||||
"/foo?query=http://bad",
|
|
||||||
&url.URL{
|
|
||||||
Path: "/foo",
|
|
||||||
RawQuery: "query=http://bad",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
// leading // without scheme should create an authority
|
|
||||||
{
|
|
||||||
"//foo",
|
|
||||||
&url.URL{
|
|
||||||
Host: "foo",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
// leading // without scheme, with userinfo, path, and query
|
|
||||||
{
|
|
||||||
"//user@foo/path?a=b",
|
|
||||||
&url.URL{
|
|
||||||
User: url.User("user"),
|
|
||||||
Host: "foo",
|
|
||||||
Path: "/path",
|
|
||||||
RawQuery: "a=b",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
// Three leading slashes isn't an authority, but doesn't return an error.
|
|
||||||
// (We can't return an error, as this code is also used via
|
|
||||||
// ServeHTTP -> ReadRequest -> Parse, which is arguably a
|
|
||||||
// different URL parsing context, but currently shares the
|
|
||||||
// same codepath)
|
|
||||||
{
|
|
||||||
"///threeslashes",
|
|
||||||
&url.URL{
|
|
||||||
Path: "///threeslashes",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"http://user:password@google.com",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
User: url.UserPassword("user", "password"),
|
|
||||||
Host: "google.com",
|
|
||||||
},
|
|
||||||
"http://user:password@google.com",
|
|
||||||
},
|
|
||||||
// unescaped @ in username should not confuse host
|
|
||||||
{
|
|
||||||
"http://j@ne:password@google.com",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
User: url.UserPassword("j@ne", "password"),
|
|
||||||
Host: "google.com",
|
|
||||||
},
|
|
||||||
"http://j%40ne:password@google.com",
|
|
||||||
},
|
|
||||||
// unescaped @ in password should not confuse host
|
|
||||||
{
|
|
||||||
"http://jane:p@ssword@google.com",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
User: url.UserPassword("jane", "p@ssword"),
|
|
||||||
Host: "google.com",
|
|
||||||
},
|
|
||||||
"http://jane:p%40ssword@google.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"http://j@ne:password@google.com/p@th?q=@go",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
User: url.UserPassword("j@ne", "password"),
|
|
||||||
Host: "google.com",
|
|
||||||
Path: "/p@th",
|
|
||||||
RawQuery: "q=@go",
|
|
||||||
},
|
|
||||||
"http://j%40ne:password@google.com/p@th?q=@go",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"http://www.google.com/?q=go+language#foo",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "www.google.com",
|
|
||||||
Path: "/",
|
|
||||||
RawQuery: "q=go+language",
|
|
||||||
Fragment: "foo",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"http://www.google.com/?q=go+language#foo%26bar",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "www.google.com",
|
|
||||||
Path: "/",
|
|
||||||
RawQuery: "q=go+language",
|
|
||||||
Fragment: "foo&bar",
|
|
||||||
},
|
|
||||||
"http://www.google.com/?q=go+language#foo&bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"file:///home/adg/rabbits",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "file",
|
|
||||||
Host: "",
|
|
||||||
Path: "/home/adg/rabbits",
|
|
||||||
},
|
|
||||||
"file:///home/adg/rabbits",
|
|
||||||
},
|
|
||||||
// "Windows" paths are no exception to the rule.
|
|
||||||
// See golang.org/issue/6027, especially comment #9.
|
|
||||||
{
|
|
||||||
"file:///C:/FooBar/Baz.txt",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "file",
|
|
||||||
Host: "",
|
|
||||||
Path: "/C:/FooBar/Baz.txt",
|
|
||||||
},
|
|
||||||
"file:///C:/FooBar/Baz.txt",
|
|
||||||
},
|
|
||||||
// case-insensitive scheme
|
|
||||||
{
|
|
||||||
"MaIlTo:webmaster@golang.org",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "mailto",
|
|
||||||
Opaque: "webmaster@golang.org",
|
|
||||||
},
|
|
||||||
"mailto:webmaster@golang.org",
|
|
||||||
},
|
|
||||||
// Relative path
|
|
||||||
{
|
|
||||||
"a/b/c",
|
|
||||||
&url.URL{
|
|
||||||
Path: "a/b/c",
|
|
||||||
},
|
|
||||||
"a/b/c",
|
|
||||||
},
|
|
||||||
// escaped '?' in username and password
|
|
||||||
{
|
|
||||||
"http://%3Fam:pa%3Fsword@google.com",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
User: url.UserPassword("?am", "pa?sword"),
|
|
||||||
Host: "google.com",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
// escaped '?' and '#' in path
|
|
||||||
{
|
|
||||||
"http://example.com/%3F%23",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "example.com",
|
|
||||||
Path: "?#",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
// unescaped [ ] ! ' ( ) * in path
|
|
||||||
{
|
|
||||||
"http://example.com/[]!'()*",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "example.com",
|
|
||||||
Path: "[]!'()*",
|
|
||||||
},
|
|
||||||
"http://example.com/[]!'()*",
|
|
||||||
},
|
|
||||||
// escaped : / ? # [ ] @ in username and password
|
|
||||||
{
|
|
||||||
"http://%3A%2F%3F:%23%5B%5D%40@example.com",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
User: url.UserPassword(":/?", "#[]@"),
|
|
||||||
Host: "example.com",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
// unescaped ! $ & ' ( ) * + , ; = in username and password
|
|
||||||
{
|
|
||||||
"http://!$&'():*+,;=@example.com",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
User: url.UserPassword("!$&'()", "*+,;="),
|
|
||||||
Host: "example.com",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
// unescaped = : / . ? = in query component
|
|
||||||
{
|
|
||||||
"http://example.com/?q=http://google.com/?q=",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "example.com",
|
|
||||||
Path: "/",
|
|
||||||
RawQuery: "q=http://google.com/?q=",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
// unescaped : / ? [ ] @ ! $ & ' ( ) * + , ; = in fragment
|
|
||||||
{
|
|
||||||
"http://example.com/#:/?%23[]@!$&'()*+,;=",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "example.com",
|
|
||||||
Path: "/",
|
|
||||||
Fragment: ":/?#[]@!$&'()*+,;=",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func DoTestString(t *testing.T, parse func(string) (*url.URL, error), name string, tests []URLTest) {
|
|
||||||
for _, tt := range tests {
|
|
||||||
u, err := parse(tt.in)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s(%q) returned error %s", name, tt.in, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
expected := tt.in
|
|
||||||
if len(tt.roundtrip) > 0 {
|
|
||||||
expected = tt.roundtrip
|
|
||||||
}
|
|
||||||
s := Escape(u)
|
|
||||||
if s != expected {
|
|
||||||
t.Errorf("Escape(%s(%q)) == %q (expected %q)", name, tt.in, s, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestURLString(t *testing.T) {
|
|
||||||
DoTestString(t, url.Parse, "Parse", urltests)
|
|
||||||
|
|
||||||
// no leading slash on path should prepend
|
|
||||||
// slash on String() call
|
|
||||||
noslash := URLTest{
|
|
||||||
"http://www.google.com/search",
|
|
||||||
&url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: "www.google.com",
|
|
||||||
Path: "search",
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
}
|
|
||||||
s := Escape(noslash.out)
|
|
||||||
if s != noslash.in {
|
|
||||||
t.Errorf("Expected %s; go %s", noslash.in, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type EscapeTest struct {
|
|
||||||
in string
|
|
||||||
out string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
var escapeTests = []EscapeTest{
|
|
||||||
{
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abc",
|
|
||||||
"abc",
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"one two",
|
|
||||||
"one+two",
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"10%",
|
|
||||||
"10%25",
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
" ?&=#+%!<>#\"{}|\\^[]`☺\t:/@$'()*,;",
|
|
||||||
"+?%26%3D%23%2B%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09%3A/%40%24%27%28%29%2A%2C%3B",
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEscape(t *testing.T) {
|
|
||||||
for _, tt := range escapeTests {
|
|
||||||
actual := QueryEscape(tt.in)
|
|
||||||
if tt.out != actual {
|
|
||||||
t.Errorf("QueryEscape(%q) = %q, want %q", tt.in, actual, tt.out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for bonus points, verify that escape:unescape is an identity.
|
|
||||||
roundtrip, err := url.QueryUnescape(actual)
|
|
||||||
if roundtrip != tt.in || err != nil {
|
|
||||||
t.Errorf("QueryUnescape(%q) = %q, %s; want %q, %s", actual, roundtrip, err, tt.in, "[no error]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var resolveReferenceTests = []struct {
|
|
||||||
base, rel, expected string
|
|
||||||
}{
|
|
||||||
// Absolute URL references
|
|
||||||
{"http://foo.com?a=b", "https://bar.com/", "https://bar.com/"},
|
|
||||||
{"http://foo.com/", "https://bar.com/?a=b", "https://bar.com/?a=b"},
|
|
||||||
{"http://foo.com/bar", "mailto:foo@example.com", "mailto:foo@example.com"},
|
|
||||||
|
|
||||||
// Path-absolute references
|
|
||||||
{"http://foo.com/bar", "/baz", "http://foo.com/baz"},
|
|
||||||
{"http://foo.com/bar?a=b#f", "/baz", "http://foo.com/baz"},
|
|
||||||
{"http://foo.com/bar?a=b", "/baz?c=d", "http://foo.com/baz?c=d"},
|
|
||||||
|
|
||||||
// Scheme-relative
|
|
||||||
{"https://foo.com/bar?a=b", "//bar.com/quux", "https://bar.com/quux"},
|
|
||||||
|
|
||||||
// Path-relative references:
|
|
||||||
|
|
||||||
// ... current directory
|
|
||||||
{"http://foo.com", ".", "http://foo.com/"},
|
|
||||||
{"http://foo.com/bar", ".", "http://foo.com/"},
|
|
||||||
{"http://foo.com/bar/", ".", "http://foo.com/bar/"},
|
|
||||||
|
|
||||||
// ... going down
|
|
||||||
{"http://foo.com", "bar", "http://foo.com/bar"},
|
|
||||||
{"http://foo.com/", "bar", "http://foo.com/bar"},
|
|
||||||
{"http://foo.com/bar/baz", "quux", "http://foo.com/bar/quux"},
|
|
||||||
|
|
||||||
// ... going up
|
|
||||||
{"http://foo.com/bar/baz", "../quux", "http://foo.com/quux"},
|
|
||||||
{"http://foo.com/bar/baz", "../../../../../quux", "http://foo.com/quux"},
|
|
||||||
{"http://foo.com/bar", "..", "http://foo.com/"},
|
|
||||||
{"http://foo.com/bar/baz", "./..", "http://foo.com/"},
|
|
||||||
// ".." in the middle (issue 3560)
|
|
||||||
{"http://foo.com/bar/baz", "quux/dotdot/../tail", "http://foo.com/bar/quux/tail"},
|
|
||||||
{"http://foo.com/bar/baz", "quux/./dotdot/../tail", "http://foo.com/bar/quux/tail"},
|
|
||||||
{"http://foo.com/bar/baz", "quux/./dotdot/.././tail", "http://foo.com/bar/quux/tail"},
|
|
||||||
{"http://foo.com/bar/baz", "quux/./dotdot/./../tail", "http://foo.com/bar/quux/tail"},
|
|
||||||
{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/././../../tail", "http://foo.com/bar/quux/tail"},
|
|
||||||
{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/./.././../tail", "http://foo.com/bar/quux/tail"},
|
|
||||||
{"http://foo.com/bar/baz", "quux/./dotdot/dotdot/dotdot/./../../.././././tail", "http://foo.com/bar/quux/tail"},
|
|
||||||
{"http://foo.com/bar/baz", "quux/./dotdot/../dotdot/../dot/./tail/..", "http://foo.com/bar/quux/dot/"},
|
|
||||||
|
|
||||||
// Remove any dot-segments prior to forming the target URI.
|
|
||||||
// http://tools.ietf.org/html/rfc3986#section-5.2.4
|
|
||||||
{"http://foo.com/dot/./dotdot/../foo/bar", "../baz", "http://foo.com/dot/baz"},
|
|
||||||
|
|
||||||
// Triple dot isn't special
|
|
||||||
{"http://foo.com/bar", "...", "http://foo.com/..."},
|
|
||||||
|
|
||||||
// Fragment
|
|
||||||
{"http://foo.com/bar", ".#frag", "http://foo.com/#frag"},
|
|
||||||
|
|
||||||
// RFC 3986: Normal Examples
|
|
||||||
// http://tools.ietf.org/html/rfc3986#section-5.4.1
|
|
||||||
{"http://a/b/c/d;p?q", "g:h", "g:h"},
|
|
||||||
{"http://a/b/c/d;p?q", "g", "http://a/b/c/g"},
|
|
||||||
{"http://a/b/c/d;p?q", "./g", "http://a/b/c/g"},
|
|
||||||
{"http://a/b/c/d;p?q", "g/", "http://a/b/c/g/"},
|
|
||||||
{"http://a/b/c/d;p?q", "/g", "http://a/g"},
|
|
||||||
{"http://a/b/c/d;p?q", "//g", "http://g"},
|
|
||||||
{"http://a/b/c/d;p?q", "?y", "http://a/b/c/d;p?y"},
|
|
||||||
{"http://a/b/c/d;p?q", "g?y", "http://a/b/c/g?y"},
|
|
||||||
{"http://a/b/c/d;p?q", "#s", "http://a/b/c/d;p?q#s"},
|
|
||||||
{"http://a/b/c/d;p?q", "g#s", "http://a/b/c/g#s"},
|
|
||||||
{"http://a/b/c/d;p?q", "g?y#s", "http://a/b/c/g?y#s"},
|
|
||||||
{"http://a/b/c/d;p?q", ";x", "http://a/b/c/;x"},
|
|
||||||
{"http://a/b/c/d;p?q", "g;x", "http://a/b/c/g;x"},
|
|
||||||
{"http://a/b/c/d;p?q", "g;x?y#s", "http://a/b/c/g;x?y#s"},
|
|
||||||
{"http://a/b/c/d;p?q", "", "http://a/b/c/d;p?q"},
|
|
||||||
{"http://a/b/c/d;p?q", ".", "http://a/b/c/"},
|
|
||||||
{"http://a/b/c/d;p?q", "./", "http://a/b/c/"},
|
|
||||||
{"http://a/b/c/d;p?q", "..", "http://a/b/"},
|
|
||||||
{"http://a/b/c/d;p?q", "../", "http://a/b/"},
|
|
||||||
{"http://a/b/c/d;p?q", "../g", "http://a/b/g"},
|
|
||||||
{"http://a/b/c/d;p?q", "../..", "http://a/"},
|
|
||||||
{"http://a/b/c/d;p?q", "../../", "http://a/"},
|
|
||||||
{"http://a/b/c/d;p?q", "../../g", "http://a/g"},
|
|
||||||
|
|
||||||
// RFC 3986: Abnormal Examples
|
|
||||||
// http://tools.ietf.org/html/rfc3986#section-5.4.2
|
|
||||||
{"http://a/b/c/d;p?q", "../../../g", "http://a/g"},
|
|
||||||
{"http://a/b/c/d;p?q", "../../../../g", "http://a/g"},
|
|
||||||
{"http://a/b/c/d;p?q", "/./g", "http://a/g"},
|
|
||||||
{"http://a/b/c/d;p?q", "/../g", "http://a/g"},
|
|
||||||
{"http://a/b/c/d;p?q", "g.", "http://a/b/c/g."},
|
|
||||||
{"http://a/b/c/d;p?q", ".g", "http://a/b/c/.g"},
|
|
||||||
{"http://a/b/c/d;p?q", "g..", "http://a/b/c/g.."},
|
|
||||||
{"http://a/b/c/d;p?q", "..g", "http://a/b/c/..g"},
|
|
||||||
{"http://a/b/c/d;p?q", "./../g", "http://a/b/g"},
|
|
||||||
{"http://a/b/c/d;p?q", "./g/.", "http://a/b/c/g/"},
|
|
||||||
{"http://a/b/c/d;p?q", "g/./h", "http://a/b/c/g/h"},
|
|
||||||
{"http://a/b/c/d;p?q", "g/../h", "http://a/b/c/h"},
|
|
||||||
{"http://a/b/c/d;p?q", "g;x=1/./y", "http://a/b/c/g;x=1/y"},
|
|
||||||
{"http://a/b/c/d;p?q", "g;x=1/../y", "http://a/b/c/y"},
|
|
||||||
{"http://a/b/c/d;p?q", "g?y/./x", "http://a/b/c/g?y/./x"},
|
|
||||||
{"http://a/b/c/d;p?q", "g?y/../x", "http://a/b/c/g?y/../x"},
|
|
||||||
{"http://a/b/c/d;p?q", "g#s/./x", "http://a/b/c/g#s/./x"},
|
|
||||||
{"http://a/b/c/d;p?q", "g#s/../x", "http://a/b/c/g#s/../x"},
|
|
||||||
|
|
||||||
// Extras.
|
|
||||||
{"https://a/b/c/d;p?q", "//g?q", "https://g?q"},
|
|
||||||
{"https://a/b/c/d;p?q", "//g#s", "https://g#s"},
|
|
||||||
{"https://a/b/c/d;p?q", "//g/d/e/f?y#s", "https://g/d/e/f?y#s"},
|
|
||||||
{"https://a/b/c/d;p#s", "?y", "https://a/b/c/d;p?y"},
|
|
||||||
{"https://a/b/c/d;p?q#s", "?y", "https://a/b/c/d;p?y"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResolveReference(t *testing.T) {
|
|
||||||
mustParse := func(url_ string) *url.URL {
|
|
||||||
u, err := url.Parse(url_)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected URL to parse: %q, got error: %v", url_, err)
|
|
||||||
}
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
opaque := &url.URL{Scheme: "scheme", Opaque: "opaque"}
|
|
||||||
for _, test := range resolveReferenceTests {
|
|
||||||
base := mustParse(test.base)
|
|
||||||
rel := mustParse(test.rel)
|
|
||||||
url := base.ResolveReference(rel)
|
|
||||||
if Escape(url) != test.expected {
|
|
||||||
t.Errorf("URL(%q).ResolveReference(%q) == %q, got %q", test.base, test.rel, test.expected, Escape(url))
|
|
||||||
}
|
|
||||||
// Ensure that new instances are returned.
|
|
||||||
if base == url {
|
|
||||||
t.Errorf("Expected URL.ResolveReference to return new URL instance.")
|
|
||||||
}
|
|
||||||
// Test the convenience wrapper too.
|
|
||||||
url, err := base.Parse(test.rel)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("URL(%q).Parse(%q) failed: %v", test.base, test.rel, err)
|
|
||||||
} else if Escape(url) != test.expected {
|
|
||||||
t.Errorf("URL(%q).Parse(%q) == %q, got %q", test.base, test.rel, test.expected, Escape(url))
|
|
||||||
} else if base == url {
|
|
||||||
// Ensure that new instances are returned for the wrapper too.
|
|
||||||
t.Errorf("Expected URL.Parse to return new URL instance.")
|
|
||||||
}
|
|
||||||
// Ensure Opaque resets the URL.
|
|
||||||
url = base.ResolveReference(opaque)
|
|
||||||
if *url != *opaque {
|
|
||||||
t.Errorf("ResolveReference failed to resolve opaque URL: want %#v, got %#v", url, opaque)
|
|
||||||
}
|
|
||||||
// Test the convenience wrapper with an opaque URL too.
|
|
||||||
url, err = base.Parse("scheme:opaque")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf(`URL(%q).Parse("scheme:opaque") failed: %v`, test.base, err)
|
|
||||||
} else if *url != *opaque {
|
|
||||||
t.Errorf("Parse failed to resolve opaque URL: want %#v, got %#v", url, opaque)
|
|
||||||
} else if base == url {
|
|
||||||
// Ensure that new instances are returned, again.
|
|
||||||
t.Errorf("Expected URL.Parse to return new URL instance.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type shouldEscapeTest struct {
|
|
||||||
in byte
|
|
||||||
mode encoding
|
|
||||||
escape bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var shouldEscapeTests = []shouldEscapeTest{
|
|
||||||
// Unreserved characters (§2.3)
|
|
||||||
{'a', encodePath, false},
|
|
||||||
{'a', encodeUserPassword, false},
|
|
||||||
{'a', encodeQueryComponent, false},
|
|
||||||
{'a', encodeFragment, false},
|
|
||||||
{'z', encodePath, false},
|
|
||||||
{'A', encodePath, false},
|
|
||||||
{'Z', encodePath, false},
|
|
||||||
{'0', encodePath, false},
|
|
||||||
{'9', encodePath, false},
|
|
||||||
{'-', encodePath, false},
|
|
||||||
{'-', encodeUserPassword, false},
|
|
||||||
{'-', encodeQueryComponent, false},
|
|
||||||
{'-', encodeFragment, false},
|
|
||||||
{'.', encodePath, false},
|
|
||||||
{'_', encodePath, false},
|
|
||||||
{'~', encodePath, false},
|
|
||||||
|
|
||||||
// User information (§3.2.1)
|
|
||||||
{':', encodeUserPassword, true},
|
|
||||||
{'/', encodeUserPassword, true},
|
|
||||||
{'?', encodeUserPassword, true},
|
|
||||||
{'@', encodeUserPassword, true},
|
|
||||||
{'$', encodeUserPassword, false},
|
|
||||||
{'&', encodeUserPassword, false},
|
|
||||||
{'+', encodeUserPassword, false},
|
|
||||||
{',', encodeUserPassword, false},
|
|
||||||
{';', encodeUserPassword, false},
|
|
||||||
{'=', encodeUserPassword, false},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShouldEscape(t *testing.T) {
|
|
||||||
for _, tt := range shouldEscapeTests {
|
|
||||||
if shouldEscape(tt.in, tt.mode) != tt.escape {
|
|
||||||
t.Errorf("shouldEscape(%q, %v) returned %v; expected %v", tt.in, tt.mode, !tt.escape, tt.escape)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
96
vendor/github.com/container-storage-interface/spec/csi.proto
generated
vendored
96
vendor/github.com/container-storage-interface/spec/csi.proto
generated
vendored
@ -47,8 +47,8 @@ service Node {
|
|||||||
rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest)
|
rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest)
|
||||||
returns (NodeUnpublishVolumeResponse) {}
|
returns (NodeUnpublishVolumeResponse) {}
|
||||||
|
|
||||||
rpc GetNodeID (GetNodeIDRequest)
|
rpc NodeGetId (NodeGetIdRequest)
|
||||||
returns (GetNodeIDResponse) {}
|
returns (NodeGetIdResponse) {}
|
||||||
|
|
||||||
rpc NodeProbe (NodeProbeRequest)
|
rpc NodeProbe (NodeProbeRequest)
|
||||||
returns (NodeProbeResponse) {}
|
returns (NodeProbeResponse) {}
|
||||||
@ -70,9 +70,12 @@ message GetSupportedVersionsResponse {
|
|||||||
// Specifies a version in Semantic Version 2.0 format.
|
// Specifies a version in Semantic Version 2.0 format.
|
||||||
// (http://semver.org/spec/v2.0.0.html)
|
// (http://semver.org/spec/v2.0.0.html)
|
||||||
message Version {
|
message Version {
|
||||||
uint32 major = 1; // This field is REQUIRED.
|
// The value of this field MUST NOT be negative.
|
||||||
uint32 minor = 2; // This field is REQUIRED.
|
int32 major = 1; // This field is REQUIRED.
|
||||||
uint32 patch = 3; // This field is REQUIRED.
|
// The value of this field MUST NOT be negative.
|
||||||
|
int32 minor = 2; // This field is REQUIRED.
|
||||||
|
// The value of this field MUST NOT be negative.
|
||||||
|
int32 patch = 3; // This field is REQUIRED.
|
||||||
}
|
}
|
||||||
////////
|
////////
|
||||||
////////
|
////////
|
||||||
@ -140,25 +143,25 @@ message CreateVolumeRequest {
|
|||||||
// validating these parameters. COs will treat these as opaque.
|
// validating these parameters. COs will treat these as opaque.
|
||||||
map<string, string> parameters = 5;
|
map<string, string> parameters = 5;
|
||||||
|
|
||||||
// End user credentials used to authenticate/authorize volume creation
|
// Credentials used by Controller plugin to authenticate/authorize
|
||||||
// request.
|
// volume creation request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
map<string, string> user_credentials = 6;
|
map<string, string> controller_create_credentials = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateVolumeResponse {
|
message CreateVolumeResponse {
|
||||||
// Contains all attributes of the newly created volume that are
|
// Contains all attributes of the newly created volume that are
|
||||||
// relevant to the CO along with information required by the Plugin
|
// relevant to the CO along with information required by the Plugin
|
||||||
// to uniquely identify the volume. This field is REQUIRED.
|
// to uniquely identify the volume. This field is REQUIRED.
|
||||||
VolumeInfo volume_info = 1;
|
Volume volume = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify a capability of a volume.
|
// Specify a capability of a volume.
|
||||||
@ -228,19 +231,22 @@ message VolumeCapability {
|
|||||||
message CapacityRange {
|
message CapacityRange {
|
||||||
// Volume must be at least this big. This field is OPTIONAL.
|
// Volume must be at least this big. This field is OPTIONAL.
|
||||||
// A value of 0 is equal to an unspecified field value.
|
// A value of 0 is equal to an unspecified field value.
|
||||||
uint64 required_bytes = 1;
|
// The value of this field MUST NOT be negative.
|
||||||
|
int64 required_bytes = 1;
|
||||||
|
|
||||||
// Volume must not be bigger than this. This field is OPTIONAL.
|
// Volume must not be bigger than this. This field is OPTIONAL.
|
||||||
// A value of 0 is equal to an unspecified field value.
|
// A value of 0 is equal to an unspecified field value.
|
||||||
uint64 limit_bytes = 2;
|
// The value of this field MUST NOT be negative.
|
||||||
|
int64 limit_bytes = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The information about a provisioned volume.
|
// The information about a provisioned volume.
|
||||||
message VolumeInfo {
|
message Volume {
|
||||||
// The capacity of the volume in bytes. This field is OPTIONAL. If not
|
// The capacity of the volume in bytes. This field is OPTIONAL. If not
|
||||||
// set (value of 0), it indicates that the capacity of the volume is
|
// set (value of 0), it indicates that the capacity of the volume is
|
||||||
// unknown (e.g., NFS share).
|
// unknown (e.g., NFS share).
|
||||||
uint64 capacity_bytes = 1;
|
// The value of this field MUST NOT be negative.
|
||||||
|
int64 capacity_bytes = 1;
|
||||||
|
|
||||||
// Contains identity information for the created volume. This field is
|
// Contains identity information for the created volume. This field is
|
||||||
// REQUIRED. The identity information will be used by the CO in
|
// REQUIRED. The identity information will be used by the CO in
|
||||||
@ -267,18 +273,18 @@ message DeleteVolumeRequest {
|
|||||||
// This field is REQUIRED.
|
// This field is REQUIRED.
|
||||||
string volume_id = 2;
|
string volume_id = 2;
|
||||||
|
|
||||||
// End user credentials used to authenticate/authorize volume deletion
|
// Credentials used by Controller plugin to authenticate/authorize
|
||||||
// request.
|
// volume deletion request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
map<string, string> user_credentials = 3;
|
map<string, string> controller_delete_credentials = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DeleteVolumeResponse {}
|
message DeleteVolumeResponse {}
|
||||||
@ -293,7 +299,7 @@ message ControllerPublishVolumeRequest {
|
|||||||
string volume_id = 2;
|
string volume_id = 2;
|
||||||
|
|
||||||
// The ID of the node. This field is REQUIRED. The CO SHALL set this
|
// The ID of the node. This field is REQUIRED. The CO SHALL set this
|
||||||
// field to match the node ID returned by `GetNodeID`.
|
// field to match the node ID returned by `NodeGetId`.
|
||||||
string node_id = 3;
|
string node_id = 3;
|
||||||
|
|
||||||
// The capability of the volume the CO expects the volume to have.
|
// The capability of the volume the CO expects the volume to have.
|
||||||
@ -304,21 +310,21 @@ message ControllerPublishVolumeRequest {
|
|||||||
// REQUIRED.
|
// REQUIRED.
|
||||||
bool readonly = 5;
|
bool readonly = 5;
|
||||||
|
|
||||||
// End user credentials used to authenticate/authorize controller
|
// Credentials used by Controller plugin to authenticate/authorize
|
||||||
// publish request.
|
// controller publish request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
map<string, string> user_credentials = 6;
|
map<string, string> controller_publish_credentials = 6;
|
||||||
|
|
||||||
// Attributes of the volume to be used on a node. This field is
|
// Attributes of the volume to be used on a node. This field is
|
||||||
// OPTIONAL and MUST match the attributes of the VolumeInfo identified
|
// OPTIONAL and MUST match the attributes of the Volume identified
|
||||||
// by `volume_id`.
|
// by `volume_id`.
|
||||||
map<string,string> volume_attributes = 7;
|
map<string,string> volume_attributes = 7;
|
||||||
}
|
}
|
||||||
@ -327,7 +333,7 @@ message ControllerPublishVolumeResponse {
|
|||||||
// The SP specific information that will be passed to the Plugin in
|
// The SP specific information that will be passed to the Plugin in
|
||||||
// the subsequent `NodePublishVolume` call for the given volume.
|
// the subsequent `NodePublishVolume` call for the given volume.
|
||||||
// This information is opaque to the CO. This field is OPTIONAL.
|
// This information is opaque to the CO. This field is OPTIONAL.
|
||||||
map<string, string> publish_volume_info = 1;
|
map<string, string> publish_info = 1;
|
||||||
}
|
}
|
||||||
////////
|
////////
|
||||||
////////
|
////////
|
||||||
@ -339,24 +345,24 @@ message ControllerUnpublishVolumeRequest {
|
|||||||
string volume_id = 2;
|
string volume_id = 2;
|
||||||
|
|
||||||
// The ID of the node. This field is OPTIONAL. The CO SHOULD set this
|
// The ID of the node. This field is OPTIONAL. The CO SHOULD set this
|
||||||
// field to match the node ID returned by `GetNodeID` or leave it
|
// field to match the node ID returned by `NodeGetId` or leave it
|
||||||
// unset. If the value is set, the SP MUST unpublish the volume from
|
// unset. If the value is set, the SP MUST unpublish the volume from
|
||||||
// the specified node. If the value is unset, the SP MUST unpublish
|
// the specified node. If the value is unset, the SP MUST unpublish
|
||||||
// the volume from all nodes it is published to.
|
// the volume from all nodes it is published to.
|
||||||
string node_id = 3;
|
string node_id = 3;
|
||||||
|
|
||||||
// End user credentials used to authenticate/authorize controller
|
// Credentials used by Controller plugin to authenticate/authorize
|
||||||
// unpublish request.
|
// controller unpublish request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
map<string, string> user_credentials = 4;
|
map<string, string> controller_unpublish_credentials = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ControllerUnpublishVolumeResponse {}
|
message ControllerUnpublishVolumeResponse {}
|
||||||
@ -375,7 +381,7 @@ message ValidateVolumeCapabilitiesRequest {
|
|||||||
repeated VolumeCapability volume_capabilities = 3;
|
repeated VolumeCapability volume_capabilities = 3;
|
||||||
|
|
||||||
// Attributes of the volume to check. This field is OPTIONAL and MUST
|
// Attributes of the volume to check. This field is OPTIONAL and MUST
|
||||||
// match the attributes of the VolumeInfo identified by `volume_id`.
|
// match the attributes of the Volume identified by `volume_id`.
|
||||||
map<string,string> volume_attributes = 4;
|
map<string,string> volume_attributes = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -402,7 +408,8 @@ message ListVolumesRequest {
|
|||||||
// in the subsequent `ListVolumes` call. This field is OPTIONAL. If
|
// in the subsequent `ListVolumes` call. This field is OPTIONAL. If
|
||||||
// not specified (zero value), it means there is no restriction on the
|
// not specified (zero value), it means there is no restriction on the
|
||||||
// number of entries that can be returned.
|
// number of entries that can be returned.
|
||||||
uint32 max_entries = 2;
|
// The value of this field MUST NOT be negative.
|
||||||
|
int32 max_entries = 2;
|
||||||
|
|
||||||
// A token to specify where to start paginating. Set this field to
|
// A token to specify where to start paginating. Set this field to
|
||||||
// `next_token` returned by a previous `ListVolumes` call to get the
|
// `next_token` returned by a previous `ListVolumes` call to get the
|
||||||
@ -413,7 +420,7 @@ message ListVolumesRequest {
|
|||||||
|
|
||||||
message ListVolumesResponse {
|
message ListVolumesResponse {
|
||||||
message Entry {
|
message Entry {
|
||||||
VolumeInfo volume_info = 1;
|
Volume volume = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
repeated Entry entries = 1;
|
repeated Entry entries = 1;
|
||||||
@ -452,7 +459,8 @@ message GetCapacityResponse {
|
|||||||
// specified in the request, the Plugin SHALL take those into
|
// specified in the request, the Plugin SHALL take those into
|
||||||
// consideration when calculating the available capacity of the
|
// consideration when calculating the available capacity of the
|
||||||
// storage. This field is REQUIRED.
|
// storage. This field is REQUIRED.
|
||||||
uint64 available_capacity = 1;
|
// The value of this field MUST NOT be negative.
|
||||||
|
int64 available_capacity = 1;
|
||||||
}
|
}
|
||||||
////////
|
////////
|
||||||
////////
|
////////
|
||||||
@ -508,7 +516,7 @@ message NodePublishVolumeRequest {
|
|||||||
// has `PUBLISH_UNPUBLISH_VOLUME` controller capability, and SHALL be
|
// has `PUBLISH_UNPUBLISH_VOLUME` controller capability, and SHALL be
|
||||||
// left unset if the corresponding Controller Plugin does not have
|
// left unset if the corresponding Controller Plugin does not have
|
||||||
// this capability. This is an OPTIONAL field.
|
// this capability. This is an OPTIONAL field.
|
||||||
map<string, string> publish_volume_info = 3;
|
map<string, string> publish_info = 3;
|
||||||
|
|
||||||
// The path to which the volume will be published. It MUST be an
|
// The path to which the volume will be published. It MUST be an
|
||||||
// absolute path in the root filesystem of the process serving this
|
// absolute path in the root filesystem of the process serving this
|
||||||
@ -526,7 +534,7 @@ message NodePublishVolumeRequest {
|
|||||||
// REQUIRED.
|
// REQUIRED.
|
||||||
bool readonly = 6;
|
bool readonly = 6;
|
||||||
|
|
||||||
// End user credentials used to authenticate/authorize node
|
// Credentials used by Node plugin to authenticate/authorize node
|
||||||
// publish request.
|
// publish request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
@ -534,13 +542,13 @@ message NodePublishVolumeRequest {
|
|||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
map<string, string> user_credentials = 7;
|
map<string, string> node_publish_credentials = 7;
|
||||||
|
|
||||||
// Attributes of the volume to publish. This field is OPTIONAL and
|
// Attributes of the volume to publish. This field is OPTIONAL and
|
||||||
// MUST match the attributes of the VolumeInfo identified by
|
// MUST match the attributes of the Volume identified by
|
||||||
// `volume_id`.
|
// `volume_id`.
|
||||||
map<string,string> volume_attributes = 8;
|
map<string,string> volume_attributes = 8;
|
||||||
}
|
}
|
||||||
@ -560,7 +568,7 @@ message NodeUnpublishVolumeRequest {
|
|||||||
// This is a REQUIRED field.
|
// This is a REQUIRED field.
|
||||||
string target_path = 3;
|
string target_path = 3;
|
||||||
|
|
||||||
// End user credentials used to authenticate/authorize node
|
// Credentials used by Node plugin to authenticate/authorize node
|
||||||
// unpublish request.
|
// unpublish request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
@ -568,21 +576,21 @@ message NodeUnpublishVolumeRequest {
|
|||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
map<string, string> user_credentials = 4;
|
map<string, string> node_unpublish_credentials = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message NodeUnpublishVolumeResponse {}
|
message NodeUnpublishVolumeResponse {}
|
||||||
////////
|
////////
|
||||||
////////
|
////////
|
||||||
message GetNodeIDRequest {
|
message NodeGetIdRequest {
|
||||||
// The API version assumed by the CO. This is a REQUIRED field.
|
// The API version assumed by the CO. This is a REQUIRED field.
|
||||||
Version version = 1;
|
Version version = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetNodeIDResponse {
|
message NodeGetIdResponse {
|
||||||
// The ID of the node as understood by the SP which SHALL be used by
|
// The ID of the node as understood by the SP which SHALL be used by
|
||||||
// CO in subsequent `ControllerPublishVolume`.
|
// CO in subsequent `ControllerPublishVolume`.
|
||||||
// This is a REQUIRED field.
|
// This is a REQUIRED field.
|
||||||
|
2
vendor/github.com/container-storage-interface/spec/lib/go/Makefile
generated
vendored
2
vendor/github.com/container-storage-interface/spec/lib/go/Makefile
generated
vendored
@ -21,7 +21,7 @@ export GOPATH
|
|||||||
|
|
||||||
# Only set PROTOC_VER if it has an empty value.
|
# Only set PROTOC_VER if it has an empty value.
|
||||||
ifeq (,$(strip $(PROTOC_VER)))
|
ifeq (,$(strip $(PROTOC_VER)))
|
||||||
PROTOC_VER := 3.3.0
|
PROTOC_VER := 3.5.1
|
||||||
endif
|
endif
|
||||||
|
|
||||||
PROTOC_OS := $(shell uname -s)
|
PROTOC_OS := $(shell uname -s)
|
||||||
|
492
vendor/github.com/container-storage-interface/spec/lib/go/csi/csi.pb.go
generated
vendored
492
vendor/github.com/container-storage-interface/spec/lib/go/csi/csi.pb.go
generated
vendored
@ -17,7 +17,7 @@ It has these top-level messages:
|
|||||||
CreateVolumeResponse
|
CreateVolumeResponse
|
||||||
VolumeCapability
|
VolumeCapability
|
||||||
CapacityRange
|
CapacityRange
|
||||||
VolumeInfo
|
Volume
|
||||||
DeleteVolumeRequest
|
DeleteVolumeRequest
|
||||||
DeleteVolumeResponse
|
DeleteVolumeResponse
|
||||||
ControllerPublishVolumeRequest
|
ControllerPublishVolumeRequest
|
||||||
@ -39,8 +39,8 @@ It has these top-level messages:
|
|||||||
NodePublishVolumeResponse
|
NodePublishVolumeResponse
|
||||||
NodeUnpublishVolumeRequest
|
NodeUnpublishVolumeRequest
|
||||||
NodeUnpublishVolumeResponse
|
NodeUnpublishVolumeResponse
|
||||||
GetNodeIDRequest
|
NodeGetIdRequest
|
||||||
GetNodeIDResponse
|
NodeGetIdResponse
|
||||||
NodeProbeRequest
|
NodeProbeRequest
|
||||||
NodeProbeResponse
|
NodeProbeResponse
|
||||||
NodeGetCapabilitiesRequest
|
NodeGetCapabilitiesRequest
|
||||||
@ -73,9 +73,11 @@ type VolumeCapability_AccessMode_Mode int32
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
VolumeCapability_AccessMode_UNKNOWN VolumeCapability_AccessMode_Mode = 0
|
VolumeCapability_AccessMode_UNKNOWN VolumeCapability_AccessMode_Mode = 0
|
||||||
// Can be published as read/write at one node at a time.
|
// Can only be published once as read/write on a single node, at
|
||||||
|
// any given time.
|
||||||
VolumeCapability_AccessMode_SINGLE_NODE_WRITER VolumeCapability_AccessMode_Mode = 1
|
VolumeCapability_AccessMode_SINGLE_NODE_WRITER VolumeCapability_AccessMode_Mode = 1
|
||||||
// Can be published as readonly at one node at a time.
|
// Can only be published once as readonly on a single node, at
|
||||||
|
// any given time.
|
||||||
VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY VolumeCapability_AccessMode_Mode = 2
|
VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY VolumeCapability_AccessMode_Mode = 2
|
||||||
// Can be published as readonly at multiple nodes simultaneously.
|
// Can be published as readonly at multiple nodes simultaneously.
|
||||||
VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY VolumeCapability_AccessMode_Mode = 3
|
VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY VolumeCapability_AccessMode_Mode = 3
|
||||||
@ -194,9 +196,12 @@ func (m *GetSupportedVersionsResponse) GetSupportedVersions() []*Version {
|
|||||||
// Specifies a version in Semantic Version 2.0 format.
|
// Specifies a version in Semantic Version 2.0 format.
|
||||||
// (http://semver.org/spec/v2.0.0.html)
|
// (http://semver.org/spec/v2.0.0.html)
|
||||||
type Version struct {
|
type Version struct {
|
||||||
Major uint32 `protobuf:"varint,1,opt,name=major" json:"major,omitempty"`
|
// The value of this field MUST NOT be negative.
|
||||||
Minor uint32 `protobuf:"varint,2,opt,name=minor" json:"minor,omitempty"`
|
Major int32 `protobuf:"varint,1,opt,name=major" json:"major,omitempty"`
|
||||||
Patch uint32 `protobuf:"varint,3,opt,name=patch" json:"patch,omitempty"`
|
// The value of this field MUST NOT be negative.
|
||||||
|
Minor int32 `protobuf:"varint,2,opt,name=minor" json:"minor,omitempty"`
|
||||||
|
// The value of this field MUST NOT be negative.
|
||||||
|
Patch int32 `protobuf:"varint,3,opt,name=patch" json:"patch,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Version) Reset() { *m = Version{} }
|
func (m *Version) Reset() { *m = Version{} }
|
||||||
@ -204,21 +209,21 @@ func (m *Version) String() string { return proto.CompactTextString(m)
|
|||||||
func (*Version) ProtoMessage() {}
|
func (*Version) ProtoMessage() {}
|
||||||
func (*Version) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
|
func (*Version) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
|
||||||
|
|
||||||
func (m *Version) GetMajor() uint32 {
|
func (m *Version) GetMajor() int32 {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Major
|
return m.Major
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Version) GetMinor() uint32 {
|
func (m *Version) GetMinor() int32 {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Minor
|
return m.Minor
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Version) GetPatch() uint32 {
|
func (m *Version) GetPatch() int32 {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Patch
|
return m.Patch
|
||||||
}
|
}
|
||||||
@ -323,18 +328,18 @@ type CreateVolumeRequest struct {
|
|||||||
// This field is OPTIONAL. The Plugin is responsible for parsing and
|
// This field is OPTIONAL. The Plugin is responsible for parsing and
|
||||||
// validating these parameters. COs will treat these as opaque.
|
// validating these parameters. COs will treat these as opaque.
|
||||||
Parameters map[string]string `protobuf:"bytes,5,rep,name=parameters" json:"parameters,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
Parameters map[string]string `protobuf:"bytes,5,rep,name=parameters" json:"parameters,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
// End user credentials used to authenticate/authorize volume creation
|
// Credentials used by Controller plugin to authenticate/authorize
|
||||||
// request.
|
// volume creation request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
UserCredentials map[string]string `protobuf:"bytes,6,rep,name=user_credentials,json=userCredentials" json:"user_credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
ControllerCreateCredentials map[string]string `protobuf:"bytes,6,rep,name=controller_create_credentials,json=controllerCreateCredentials" json:"controller_create_credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *CreateVolumeRequest) Reset() { *m = CreateVolumeRequest{} }
|
func (m *CreateVolumeRequest) Reset() { *m = CreateVolumeRequest{} }
|
||||||
@ -377,9 +382,9 @@ func (m *CreateVolumeRequest) GetParameters() map[string]string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *CreateVolumeRequest) GetUserCredentials() map[string]string {
|
func (m *CreateVolumeRequest) GetControllerCreateCredentials() map[string]string {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.UserCredentials
|
return m.ControllerCreateCredentials
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -388,7 +393,7 @@ type CreateVolumeResponse struct {
|
|||||||
// Contains all attributes of the newly created volume that are
|
// Contains all attributes of the newly created volume that are
|
||||||
// relevant to the CO along with information required by the Plugin
|
// relevant to the CO along with information required by the Plugin
|
||||||
// to uniquely identify the volume. This field is REQUIRED.
|
// to uniquely identify the volume. This field is REQUIRED.
|
||||||
VolumeInfo *VolumeInfo `protobuf:"bytes,1,opt,name=volume_info,json=volumeInfo" json:"volume_info,omitempty"`
|
Volume *Volume `protobuf:"bytes,1,opt,name=volume" json:"volume,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *CreateVolumeResponse) Reset() { *m = CreateVolumeResponse{} }
|
func (m *CreateVolumeResponse) Reset() { *m = CreateVolumeResponse{} }
|
||||||
@ -396,9 +401,9 @@ func (m *CreateVolumeResponse) String() string { return proto.Compact
|
|||||||
func (*CreateVolumeResponse) ProtoMessage() {}
|
func (*CreateVolumeResponse) ProtoMessage() {}
|
||||||
func (*CreateVolumeResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
|
func (*CreateVolumeResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
|
||||||
|
|
||||||
func (m *CreateVolumeResponse) GetVolumeInfo() *VolumeInfo {
|
func (m *CreateVolumeResponse) GetVolume() *Volume {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.VolumeInfo
|
return m.Volume
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -602,10 +607,12 @@ func (m *VolumeCapability_AccessMode) GetMode() VolumeCapability_AccessMode_Mode
|
|||||||
type CapacityRange struct {
|
type CapacityRange struct {
|
||||||
// Volume must be at least this big. This field is OPTIONAL.
|
// Volume must be at least this big. This field is OPTIONAL.
|
||||||
// A value of 0 is equal to an unspecified field value.
|
// A value of 0 is equal to an unspecified field value.
|
||||||
RequiredBytes uint64 `protobuf:"varint,1,opt,name=required_bytes,json=requiredBytes" json:"required_bytes,omitempty"`
|
// The value of this field MUST NOT be negative.
|
||||||
|
RequiredBytes int64 `protobuf:"varint,1,opt,name=required_bytes,json=requiredBytes" json:"required_bytes,omitempty"`
|
||||||
// Volume must not be bigger than this. This field is OPTIONAL.
|
// Volume must not be bigger than this. This field is OPTIONAL.
|
||||||
// A value of 0 is equal to an unspecified field value.
|
// A value of 0 is equal to an unspecified field value.
|
||||||
LimitBytes uint64 `protobuf:"varint,2,opt,name=limit_bytes,json=limitBytes" json:"limit_bytes,omitempty"`
|
// The value of this field MUST NOT be negative.
|
||||||
|
LimitBytes int64 `protobuf:"varint,2,opt,name=limit_bytes,json=limitBytes" json:"limit_bytes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *CapacityRange) Reset() { *m = CapacityRange{} }
|
func (m *CapacityRange) Reset() { *m = CapacityRange{} }
|
||||||
@ -613,14 +620,14 @@ func (m *CapacityRange) String() string { return proto.CompactTextStr
|
|||||||
func (*CapacityRange) ProtoMessage() {}
|
func (*CapacityRange) ProtoMessage() {}
|
||||||
func (*CapacityRange) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} }
|
func (*CapacityRange) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} }
|
||||||
|
|
||||||
func (m *CapacityRange) GetRequiredBytes() uint64 {
|
func (m *CapacityRange) GetRequiredBytes() int64 {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.RequiredBytes
|
return m.RequiredBytes
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *CapacityRange) GetLimitBytes() uint64 {
|
func (m *CapacityRange) GetLimitBytes() int64 {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.LimitBytes
|
return m.LimitBytes
|
||||||
}
|
}
|
||||||
@ -628,11 +635,12 @@ func (m *CapacityRange) GetLimitBytes() uint64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The information about a provisioned volume.
|
// The information about a provisioned volume.
|
||||||
type VolumeInfo struct {
|
type Volume struct {
|
||||||
// The capacity of the volume in bytes. This field is OPTIONAL. If not
|
// The capacity of the volume in bytes. This field is OPTIONAL. If not
|
||||||
// set (value of 0), it indicates that the capacity of the volume is
|
// set (value of 0), it indicates that the capacity of the volume is
|
||||||
// unknown (e.g., NFS share).
|
// unknown (e.g., NFS share).
|
||||||
CapacityBytes uint64 `protobuf:"varint,1,opt,name=capacity_bytes,json=capacityBytes" json:"capacity_bytes,omitempty"`
|
// The value of this field MUST NOT be negative.
|
||||||
|
CapacityBytes int64 `protobuf:"varint,1,opt,name=capacity_bytes,json=capacityBytes" json:"capacity_bytes,omitempty"`
|
||||||
// Contains identity information for the created volume. This field is
|
// Contains identity information for the created volume. This field is
|
||||||
// REQUIRED. The identity information will be used by the CO in
|
// REQUIRED. The identity information will be used by the CO in
|
||||||
// subsequent calls to refer to the provisioned volume.
|
// subsequent calls to refer to the provisioned volume.
|
||||||
@ -648,26 +656,26 @@ type VolumeInfo struct {
|
|||||||
Attributes map[string]string `protobuf:"bytes,3,rep,name=attributes" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
Attributes map[string]string `protobuf:"bytes,3,rep,name=attributes" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *VolumeInfo) Reset() { *m = VolumeInfo{} }
|
func (m *Volume) Reset() { *m = Volume{} }
|
||||||
func (m *VolumeInfo) String() string { return proto.CompactTextString(m) }
|
func (m *Volume) String() string { return proto.CompactTextString(m) }
|
||||||
func (*VolumeInfo) ProtoMessage() {}
|
func (*Volume) ProtoMessage() {}
|
||||||
func (*VolumeInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} }
|
func (*Volume) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} }
|
||||||
|
|
||||||
func (m *VolumeInfo) GetCapacityBytes() uint64 {
|
func (m *Volume) GetCapacityBytes() int64 {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.CapacityBytes
|
return m.CapacityBytes
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *VolumeInfo) GetId() string {
|
func (m *Volume) GetId() string {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Id
|
return m.Id
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *VolumeInfo) GetAttributes() map[string]string {
|
func (m *Volume) GetAttributes() map[string]string {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Attributes
|
return m.Attributes
|
||||||
}
|
}
|
||||||
@ -682,18 +690,18 @@ type DeleteVolumeRequest struct {
|
|||||||
// The ID of the volume to be deprovisioned.
|
// The ID of the volume to be deprovisioned.
|
||||||
// This field is REQUIRED.
|
// This field is REQUIRED.
|
||||||
VolumeId string `protobuf:"bytes,2,opt,name=volume_id,json=volumeId" json:"volume_id,omitempty"`
|
VolumeId string `protobuf:"bytes,2,opt,name=volume_id,json=volumeId" json:"volume_id,omitempty"`
|
||||||
// End user credentials used to authenticate/authorize volume deletion
|
// Credentials used by Controller plugin to authenticate/authorize
|
||||||
// request.
|
// volume deletion request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
UserCredentials map[string]string `protobuf:"bytes,3,rep,name=user_credentials,json=userCredentials" json:"user_credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
ControllerDeleteCredentials map[string]string `protobuf:"bytes,3,rep,name=controller_delete_credentials,json=controllerDeleteCredentials" json:"controller_delete_credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DeleteVolumeRequest) Reset() { *m = DeleteVolumeRequest{} }
|
func (m *DeleteVolumeRequest) Reset() { *m = DeleteVolumeRequest{} }
|
||||||
@ -715,9 +723,9 @@ func (m *DeleteVolumeRequest) GetVolumeId() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DeleteVolumeRequest) GetUserCredentials() map[string]string {
|
func (m *DeleteVolumeRequest) GetControllerDeleteCredentials() map[string]string {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.UserCredentials
|
return m.ControllerDeleteCredentials
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -739,7 +747,7 @@ type ControllerPublishVolumeRequest struct {
|
|||||||
// This field is REQUIRED.
|
// This field is REQUIRED.
|
||||||
VolumeId string `protobuf:"bytes,2,opt,name=volume_id,json=volumeId" json:"volume_id,omitempty"`
|
VolumeId string `protobuf:"bytes,2,opt,name=volume_id,json=volumeId" json:"volume_id,omitempty"`
|
||||||
// The ID of the node. This field is REQUIRED. The CO SHALL set this
|
// The ID of the node. This field is REQUIRED. The CO SHALL set this
|
||||||
// field to match the node ID returned by `GetNodeID`.
|
// field to match the node ID returned by `NodeGetId`.
|
||||||
NodeId string `protobuf:"bytes,3,opt,name=node_id,json=nodeId" json:"node_id,omitempty"`
|
NodeId string `protobuf:"bytes,3,opt,name=node_id,json=nodeId" json:"node_id,omitempty"`
|
||||||
// The capability of the volume the CO expects the volume to have.
|
// The capability of the volume the CO expects the volume to have.
|
||||||
// This is a REQUIRED field.
|
// This is a REQUIRED field.
|
||||||
@ -747,20 +755,20 @@ type ControllerPublishVolumeRequest struct {
|
|||||||
// Whether to publish the volume in readonly mode. This field is
|
// Whether to publish the volume in readonly mode. This field is
|
||||||
// REQUIRED.
|
// REQUIRED.
|
||||||
Readonly bool `protobuf:"varint,5,opt,name=readonly" json:"readonly,omitempty"`
|
Readonly bool `protobuf:"varint,5,opt,name=readonly" json:"readonly,omitempty"`
|
||||||
// End user credentials used to authenticate/authorize controller
|
// Credentials used by Controller plugin to authenticate/authorize
|
||||||
// publish request.
|
// controller publish request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
UserCredentials map[string]string `protobuf:"bytes,6,rep,name=user_credentials,json=userCredentials" json:"user_credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
ControllerPublishCredentials map[string]string `protobuf:"bytes,6,rep,name=controller_publish_credentials,json=controllerPublishCredentials" json:"controller_publish_credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
// Attributes of the volume to be used on a node. This field is
|
// Attributes of the volume to be used on a node. This field is
|
||||||
// OPTIONAL and MUST match the attributes of the VolumeInfo identified
|
// OPTIONAL and MUST match the attributes of the Volume identified
|
||||||
// by `volume_id`.
|
// by `volume_id`.
|
||||||
VolumeAttributes map[string]string `protobuf:"bytes,7,rep,name=volume_attributes,json=volumeAttributes" json:"volume_attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
VolumeAttributes map[string]string `protobuf:"bytes,7,rep,name=volume_attributes,json=volumeAttributes" json:"volume_attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
}
|
}
|
||||||
@ -805,9 +813,9 @@ func (m *ControllerPublishVolumeRequest) GetReadonly() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ControllerPublishVolumeRequest) GetUserCredentials() map[string]string {
|
func (m *ControllerPublishVolumeRequest) GetControllerPublishCredentials() map[string]string {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.UserCredentials
|
return m.ControllerPublishCredentials
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -823,7 +831,7 @@ type ControllerPublishVolumeResponse struct {
|
|||||||
// The SP specific information that will be passed to the Plugin in
|
// The SP specific information that will be passed to the Plugin in
|
||||||
// the subsequent `NodePublishVolume` call for the given volume.
|
// the subsequent `NodePublishVolume` call for the given volume.
|
||||||
// This information is opaque to the CO. This field is OPTIONAL.
|
// This information is opaque to the CO. This field is OPTIONAL.
|
||||||
PublishVolumeInfo map[string]string `protobuf:"bytes,1,rep,name=publish_volume_info,json=publishVolumeInfo" json:"publish_volume_info,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
PublishInfo map[string]string `protobuf:"bytes,1,rep,name=publish_info,json=publishInfo" json:"publish_info,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ControllerPublishVolumeResponse) Reset() { *m = ControllerPublishVolumeResponse{} }
|
func (m *ControllerPublishVolumeResponse) Reset() { *m = ControllerPublishVolumeResponse{} }
|
||||||
@ -833,9 +841,9 @@ func (*ControllerPublishVolumeResponse) Descriptor() ([]byte, []int) {
|
|||||||
return fileDescriptor0, []int{13}
|
return fileDescriptor0, []int{13}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ControllerPublishVolumeResponse) GetPublishVolumeInfo() map[string]string {
|
func (m *ControllerPublishVolumeResponse) GetPublishInfo() map[string]string {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.PublishVolumeInfo
|
return m.PublishInfo
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -848,23 +856,23 @@ type ControllerUnpublishVolumeRequest struct {
|
|||||||
// The ID of the volume. This field is REQUIRED.
|
// The ID of the volume. This field is REQUIRED.
|
||||||
VolumeId string `protobuf:"bytes,2,opt,name=volume_id,json=volumeId" json:"volume_id,omitempty"`
|
VolumeId string `protobuf:"bytes,2,opt,name=volume_id,json=volumeId" json:"volume_id,omitempty"`
|
||||||
// The ID of the node. This field is OPTIONAL. The CO SHOULD set this
|
// The ID of the node. This field is OPTIONAL. The CO SHOULD set this
|
||||||
// field to match the node ID returned by `GetNodeID` or leave it
|
// field to match the node ID returned by `NodeGetId` or leave it
|
||||||
// unset. If the value is set, the SP MUST unpublish the volume from
|
// unset. If the value is set, the SP MUST unpublish the volume from
|
||||||
// the specified node. If the value is unset, the SP MUST unpublish
|
// the specified node. If the value is unset, the SP MUST unpublish
|
||||||
// the volume from all nodes it is published to.
|
// the volume from all nodes it is published to.
|
||||||
NodeId string `protobuf:"bytes,3,opt,name=node_id,json=nodeId" json:"node_id,omitempty"`
|
NodeId string `protobuf:"bytes,3,opt,name=node_id,json=nodeId" json:"node_id,omitempty"`
|
||||||
// End user credentials used to authenticate/authorize controller
|
// Credentials used by Controller plugin to authenticate/authorize
|
||||||
// unpublish request.
|
// controller unpublish request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
UserCredentials map[string]string `protobuf:"bytes,4,rep,name=user_credentials,json=userCredentials" json:"user_credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
ControllerUnpublishCredentials map[string]string `protobuf:"bytes,4,rep,name=controller_unpublish_credentials,json=controllerUnpublishCredentials" json:"controller_unpublish_credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ControllerUnpublishVolumeRequest) Reset() { *m = ControllerUnpublishVolumeRequest{} }
|
func (m *ControllerUnpublishVolumeRequest) Reset() { *m = ControllerUnpublishVolumeRequest{} }
|
||||||
@ -895,9 +903,9 @@ func (m *ControllerUnpublishVolumeRequest) GetNodeId() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ControllerUnpublishVolumeRequest) GetUserCredentials() map[string]string {
|
func (m *ControllerUnpublishVolumeRequest) GetControllerUnpublishCredentials() map[string]string {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.UserCredentials
|
return m.ControllerUnpublishCredentials
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -924,7 +932,7 @@ type ValidateVolumeCapabilitiesRequest struct {
|
|||||||
// specified below are supported. This field is REQUIRED.
|
// specified below are supported. This field is REQUIRED.
|
||||||
VolumeCapabilities []*VolumeCapability `protobuf:"bytes,3,rep,name=volume_capabilities,json=volumeCapabilities" json:"volume_capabilities,omitempty"`
|
VolumeCapabilities []*VolumeCapability `protobuf:"bytes,3,rep,name=volume_capabilities,json=volumeCapabilities" json:"volume_capabilities,omitempty"`
|
||||||
// Attributes of the volume to check. This field is OPTIONAL and MUST
|
// Attributes of the volume to check. This field is OPTIONAL and MUST
|
||||||
// match the attributes of the VolumeInfo identified by `volume_id`.
|
// match the attributes of the Volume identified by `volume_id`.
|
||||||
VolumeAttributes map[string]string `protobuf:"bytes,4,rep,name=volume_attributes,json=volumeAttributes" json:"volume_attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
VolumeAttributes map[string]string `protobuf:"bytes,4,rep,name=volume_attributes,json=volumeAttributes" json:"volume_attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1006,7 +1014,8 @@ type ListVolumesRequest struct {
|
|||||||
// in the subsequent `ListVolumes` call. This field is OPTIONAL. If
|
// in the subsequent `ListVolumes` call. This field is OPTIONAL. If
|
||||||
// not specified (zero value), it means there is no restriction on the
|
// not specified (zero value), it means there is no restriction on the
|
||||||
// number of entries that can be returned.
|
// number of entries that can be returned.
|
||||||
MaxEntries uint32 `protobuf:"varint,2,opt,name=max_entries,json=maxEntries" json:"max_entries,omitempty"`
|
// The value of this field MUST NOT be negative.
|
||||||
|
MaxEntries int32 `protobuf:"varint,2,opt,name=max_entries,json=maxEntries" json:"max_entries,omitempty"`
|
||||||
// A token to specify where to start paginating. Set this field to
|
// A token to specify where to start paginating. Set this field to
|
||||||
// `next_token` returned by a previous `ListVolumes` call to get the
|
// `next_token` returned by a previous `ListVolumes` call to get the
|
||||||
// next page of entries. This field is OPTIONAL.
|
// next page of entries. This field is OPTIONAL.
|
||||||
@ -1026,7 +1035,7 @@ func (m *ListVolumesRequest) GetVersion() *Version {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ListVolumesRequest) GetMaxEntries() uint32 {
|
func (m *ListVolumesRequest) GetMaxEntries() int32 {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.MaxEntries
|
return m.MaxEntries
|
||||||
}
|
}
|
||||||
@ -1071,7 +1080,7 @@ func (m *ListVolumesResponse) GetNextToken() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ListVolumesResponse_Entry struct {
|
type ListVolumesResponse_Entry struct {
|
||||||
VolumeInfo *VolumeInfo `protobuf:"bytes,1,opt,name=volume_info,json=volumeInfo" json:"volume_info,omitempty"`
|
Volume *Volume `protobuf:"bytes,1,opt,name=volume" json:"volume,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ListVolumesResponse_Entry) Reset() { *m = ListVolumesResponse_Entry{} }
|
func (m *ListVolumesResponse_Entry) Reset() { *m = ListVolumesResponse_Entry{} }
|
||||||
@ -1079,9 +1088,9 @@ func (m *ListVolumesResponse_Entry) String() string { return proto.Co
|
|||||||
func (*ListVolumesResponse_Entry) ProtoMessage() {}
|
func (*ListVolumesResponse_Entry) ProtoMessage() {}
|
||||||
func (*ListVolumesResponse_Entry) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{19, 0} }
|
func (*ListVolumesResponse_Entry) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{19, 0} }
|
||||||
|
|
||||||
func (m *ListVolumesResponse_Entry) GetVolumeInfo() *VolumeInfo {
|
func (m *ListVolumesResponse_Entry) GetVolume() *Volume {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.VolumeInfo
|
return m.Volume
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1136,7 +1145,8 @@ type GetCapacityResponse struct {
|
|||||||
// specified in the request, the Plugin SHALL take those into
|
// specified in the request, the Plugin SHALL take those into
|
||||||
// consideration when calculating the available capacity of the
|
// consideration when calculating the available capacity of the
|
||||||
// storage. This field is REQUIRED.
|
// storage. This field is REQUIRED.
|
||||||
AvailableCapacity uint64 `protobuf:"varint,1,opt,name=available_capacity,json=availableCapacity" json:"available_capacity,omitempty"`
|
// The value of this field MUST NOT be negative.
|
||||||
|
AvailableCapacity int64 `protobuf:"varint,1,opt,name=available_capacity,json=availableCapacity" json:"available_capacity,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *GetCapacityResponse) Reset() { *m = GetCapacityResponse{} }
|
func (m *GetCapacityResponse) Reset() { *m = GetCapacityResponse{} }
|
||||||
@ -1144,7 +1154,7 @@ func (m *GetCapacityResponse) String() string { return proto.CompactT
|
|||||||
func (*GetCapacityResponse) ProtoMessage() {}
|
func (*GetCapacityResponse) ProtoMessage() {}
|
||||||
func (*GetCapacityResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{21} }
|
func (*GetCapacityResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{21} }
|
||||||
|
|
||||||
func (m *GetCapacityResponse) GetAvailableCapacity() uint64 {
|
func (m *GetCapacityResponse) GetAvailableCapacity() int64 {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.AvailableCapacity
|
return m.AvailableCapacity
|
||||||
}
|
}
|
||||||
@ -1340,10 +1350,12 @@ type NodePublishVolumeRequest struct {
|
|||||||
// has `PUBLISH_UNPUBLISH_VOLUME` controller capability, and SHALL be
|
// has `PUBLISH_UNPUBLISH_VOLUME` controller capability, and SHALL be
|
||||||
// left unset if the corresponding Controller Plugin does not have
|
// left unset if the corresponding Controller Plugin does not have
|
||||||
// this capability. This is an OPTIONAL field.
|
// this capability. This is an OPTIONAL field.
|
||||||
PublishVolumeInfo map[string]string `protobuf:"bytes,3,rep,name=publish_volume_info,json=publishVolumeInfo" json:"publish_volume_info,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
PublishInfo map[string]string `protobuf:"bytes,3,rep,name=publish_info,json=publishInfo" json:"publish_info,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
// The path to which the volume will be published. It MUST be an
|
// The path to which the volume will be published. It MUST be an
|
||||||
// absolute path in the root filesystem of the process serving this
|
// absolute path in the root filesystem of the process serving this
|
||||||
// request. The CO SHALL ensure uniqueness of target_path per volume.
|
// request. The CO SHALL ensure uniqueness of target_path per volume.
|
||||||
|
// The CO SHALL ensure that the path exists, and that the process
|
||||||
|
// serving the request has `read` and `write` permissions to the path.
|
||||||
// This is a REQUIRED field.
|
// This is a REQUIRED field.
|
||||||
TargetPath string `protobuf:"bytes,4,opt,name=target_path,json=targetPath" json:"target_path,omitempty"`
|
TargetPath string `protobuf:"bytes,4,opt,name=target_path,json=targetPath" json:"target_path,omitempty"`
|
||||||
// The capability of the volume the CO expects the volume to have.
|
// The capability of the volume the CO expects the volume to have.
|
||||||
@ -1352,7 +1364,7 @@ type NodePublishVolumeRequest struct {
|
|||||||
// Whether to publish the volume in readonly mode. This field is
|
// Whether to publish the volume in readonly mode. This field is
|
||||||
// REQUIRED.
|
// REQUIRED.
|
||||||
Readonly bool `protobuf:"varint,6,opt,name=readonly" json:"readonly,omitempty"`
|
Readonly bool `protobuf:"varint,6,opt,name=readonly" json:"readonly,omitempty"`
|
||||||
// End user credentials used to authenticate/authorize node
|
// Credentials used by Node plugin to authenticate/authorize node
|
||||||
// publish request.
|
// publish request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
@ -1360,12 +1372,12 @@ type NodePublishVolumeRequest struct {
|
|||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
UserCredentials map[string]string `protobuf:"bytes,7,rep,name=user_credentials,json=userCredentials" json:"user_credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
NodePublishCredentials map[string]string `protobuf:"bytes,7,rep,name=node_publish_credentials,json=nodePublishCredentials" json:"node_publish_credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
// Attributes of the volume to publish. This field is OPTIONAL and
|
// Attributes of the volume to publish. This field is OPTIONAL and
|
||||||
// MUST match the attributes of the VolumeInfo identified by
|
// MUST match the attributes of the Volume identified by
|
||||||
// `volume_id`.
|
// `volume_id`.
|
||||||
VolumeAttributes map[string]string `protobuf:"bytes,8,rep,name=volume_attributes,json=volumeAttributes" json:"volume_attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
VolumeAttributes map[string]string `protobuf:"bytes,8,rep,name=volume_attributes,json=volumeAttributes" json:"volume_attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
}
|
}
|
||||||
@ -1389,9 +1401,9 @@ func (m *NodePublishVolumeRequest) GetVolumeId() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *NodePublishVolumeRequest) GetPublishVolumeInfo() map[string]string {
|
func (m *NodePublishVolumeRequest) GetPublishInfo() map[string]string {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.PublishVolumeInfo
|
return m.PublishInfo
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1417,9 +1429,9 @@ func (m *NodePublishVolumeRequest) GetReadonly() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *NodePublishVolumeRequest) GetUserCredentials() map[string]string {
|
func (m *NodePublishVolumeRequest) GetNodePublishCredentials() map[string]string {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.UserCredentials
|
return m.NodePublishCredentials
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1450,7 +1462,7 @@ type NodeUnpublishVolumeRequest struct {
|
|||||||
// path in the root filesystem of the process serving this request.
|
// path in the root filesystem of the process serving this request.
|
||||||
// This is a REQUIRED field.
|
// This is a REQUIRED field.
|
||||||
TargetPath string `protobuf:"bytes,3,opt,name=target_path,json=targetPath" json:"target_path,omitempty"`
|
TargetPath string `protobuf:"bytes,3,opt,name=target_path,json=targetPath" json:"target_path,omitempty"`
|
||||||
// End user credentials used to authenticate/authorize node
|
// Credentials used by Node plugin to authenticate/authorize node
|
||||||
// unpublish request.
|
// unpublish request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
@ -1458,14 +1470,10 @@ type NodeUnpublishVolumeRequest struct {
|
|||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
UserCredentials map[string]string `protobuf:"bytes,4,rep,name=user_credentials,json=userCredentials" json:"user_credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
NodeUnpublishCredentials map[string]string `protobuf:"bytes,4,rep,name=node_unpublish_credentials,json=nodeUnpublishCredentials" json:"node_unpublish_credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||||
// Attributes of the volume to publish. This field is OPTIONAL and
|
|
||||||
// MUST match the attributes of the VolumeInfo identified by
|
|
||||||
// `volume_id`.
|
|
||||||
VolumeAttributes map[string]string `protobuf:"bytes,5,rep,name=volume_attributes,json=volumeAttributes" json:"volume_attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *NodeUnpublishVolumeRequest) Reset() { *m = NodeUnpublishVolumeRequest{} }
|
func (m *NodeUnpublishVolumeRequest) Reset() { *m = NodeUnpublishVolumeRequest{} }
|
||||||
@ -1494,16 +1502,9 @@ func (m *NodeUnpublishVolumeRequest) GetTargetPath() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *NodeUnpublishVolumeRequest) GetUserCredentials() map[string]string {
|
func (m *NodeUnpublishVolumeRequest) GetNodeUnpublishCredentials() map[string]string {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.UserCredentials
|
return m.NodeUnpublishCredentials
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *NodeUnpublishVolumeRequest) GetVolumeAttributes() map[string]string {
|
|
||||||
if m != nil {
|
|
||||||
return m.VolumeAttributes
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1518,36 +1519,36 @@ func (*NodeUnpublishVolumeResponse) Descriptor() ([]byte, []int) { return fileDe
|
|||||||
|
|
||||||
// //////
|
// //////
|
||||||
// //////
|
// //////
|
||||||
type GetNodeIDRequest struct {
|
type NodeGetIdRequest struct {
|
||||||
// The API version assumed by the CO. This is a REQUIRED field.
|
// The API version assumed by the CO. This is a REQUIRED field.
|
||||||
Version *Version `protobuf:"bytes,1,opt,name=version" json:"version,omitempty"`
|
Version *Version `protobuf:"bytes,1,opt,name=version" json:"version,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *GetNodeIDRequest) Reset() { *m = GetNodeIDRequest{} }
|
func (m *NodeGetIdRequest) Reset() { *m = NodeGetIdRequest{} }
|
||||||
func (m *GetNodeIDRequest) String() string { return proto.CompactTextString(m) }
|
func (m *NodeGetIdRequest) String() string { return proto.CompactTextString(m) }
|
||||||
func (*GetNodeIDRequest) ProtoMessage() {}
|
func (*NodeGetIdRequest) ProtoMessage() {}
|
||||||
func (*GetNodeIDRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{31} }
|
func (*NodeGetIdRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{31} }
|
||||||
|
|
||||||
func (m *GetNodeIDRequest) GetVersion() *Version {
|
func (m *NodeGetIdRequest) GetVersion() *Version {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Version
|
return m.Version
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetNodeIDResponse struct {
|
type NodeGetIdResponse struct {
|
||||||
// The ID of the node as understood by the SP which SHALL be used by
|
// The ID of the node as understood by the SP which SHALL be used by
|
||||||
// CO in subsequent `ControllerPublishVolume`.
|
// CO in subsequent `ControllerPublishVolume`.
|
||||||
// This is a REQUIRED field.
|
// This is a REQUIRED field.
|
||||||
NodeId string `protobuf:"bytes,1,opt,name=node_id,json=nodeId" json:"node_id,omitempty"`
|
NodeId string `protobuf:"bytes,1,opt,name=node_id,json=nodeId" json:"node_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *GetNodeIDResponse) Reset() { *m = GetNodeIDResponse{} }
|
func (m *NodeGetIdResponse) Reset() { *m = NodeGetIdResponse{} }
|
||||||
func (m *GetNodeIDResponse) String() string { return proto.CompactTextString(m) }
|
func (m *NodeGetIdResponse) String() string { return proto.CompactTextString(m) }
|
||||||
func (*GetNodeIDResponse) ProtoMessage() {}
|
func (*NodeGetIdResponse) ProtoMessage() {}
|
||||||
func (*GetNodeIDResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{32} }
|
func (*NodeGetIdResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{32} }
|
||||||
|
|
||||||
func (m *GetNodeIDResponse) GetNodeId() string {
|
func (m *NodeGetIdResponse) GetNodeId() string {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.NodeId
|
return m.NodeId
|
||||||
}
|
}
|
||||||
@ -1738,7 +1739,7 @@ func init() {
|
|||||||
proto.RegisterType((*VolumeCapability_MountVolume)(nil), "csi.VolumeCapability.MountVolume")
|
proto.RegisterType((*VolumeCapability_MountVolume)(nil), "csi.VolumeCapability.MountVolume")
|
||||||
proto.RegisterType((*VolumeCapability_AccessMode)(nil), "csi.VolumeCapability.AccessMode")
|
proto.RegisterType((*VolumeCapability_AccessMode)(nil), "csi.VolumeCapability.AccessMode")
|
||||||
proto.RegisterType((*CapacityRange)(nil), "csi.CapacityRange")
|
proto.RegisterType((*CapacityRange)(nil), "csi.CapacityRange")
|
||||||
proto.RegisterType((*VolumeInfo)(nil), "csi.VolumeInfo")
|
proto.RegisterType((*Volume)(nil), "csi.Volume")
|
||||||
proto.RegisterType((*DeleteVolumeRequest)(nil), "csi.DeleteVolumeRequest")
|
proto.RegisterType((*DeleteVolumeRequest)(nil), "csi.DeleteVolumeRequest")
|
||||||
proto.RegisterType((*DeleteVolumeResponse)(nil), "csi.DeleteVolumeResponse")
|
proto.RegisterType((*DeleteVolumeResponse)(nil), "csi.DeleteVolumeResponse")
|
||||||
proto.RegisterType((*ControllerPublishVolumeRequest)(nil), "csi.ControllerPublishVolumeRequest")
|
proto.RegisterType((*ControllerPublishVolumeRequest)(nil), "csi.ControllerPublishVolumeRequest")
|
||||||
@ -1762,8 +1763,8 @@ func init() {
|
|||||||
proto.RegisterType((*NodePublishVolumeResponse)(nil), "csi.NodePublishVolumeResponse")
|
proto.RegisterType((*NodePublishVolumeResponse)(nil), "csi.NodePublishVolumeResponse")
|
||||||
proto.RegisterType((*NodeUnpublishVolumeRequest)(nil), "csi.NodeUnpublishVolumeRequest")
|
proto.RegisterType((*NodeUnpublishVolumeRequest)(nil), "csi.NodeUnpublishVolumeRequest")
|
||||||
proto.RegisterType((*NodeUnpublishVolumeResponse)(nil), "csi.NodeUnpublishVolumeResponse")
|
proto.RegisterType((*NodeUnpublishVolumeResponse)(nil), "csi.NodeUnpublishVolumeResponse")
|
||||||
proto.RegisterType((*GetNodeIDRequest)(nil), "csi.GetNodeIDRequest")
|
proto.RegisterType((*NodeGetIdRequest)(nil), "csi.NodeGetIdRequest")
|
||||||
proto.RegisterType((*GetNodeIDResponse)(nil), "csi.GetNodeIDResponse")
|
proto.RegisterType((*NodeGetIdResponse)(nil), "csi.NodeGetIdResponse")
|
||||||
proto.RegisterType((*NodeProbeRequest)(nil), "csi.NodeProbeRequest")
|
proto.RegisterType((*NodeProbeRequest)(nil), "csi.NodeProbeRequest")
|
||||||
proto.RegisterType((*NodeProbeResponse)(nil), "csi.NodeProbeResponse")
|
proto.RegisterType((*NodeProbeResponse)(nil), "csi.NodeProbeResponse")
|
||||||
proto.RegisterType((*NodeGetCapabilitiesRequest)(nil), "csi.NodeGetCapabilitiesRequest")
|
proto.RegisterType((*NodeGetCapabilitiesRequest)(nil), "csi.NodeGetCapabilitiesRequest")
|
||||||
@ -2213,7 +2214,7 @@ var _Controller_serviceDesc = grpc.ServiceDesc{
|
|||||||
type NodeClient interface {
|
type NodeClient interface {
|
||||||
NodePublishVolume(ctx context.Context, in *NodePublishVolumeRequest, opts ...grpc.CallOption) (*NodePublishVolumeResponse, error)
|
NodePublishVolume(ctx context.Context, in *NodePublishVolumeRequest, opts ...grpc.CallOption) (*NodePublishVolumeResponse, error)
|
||||||
NodeUnpublishVolume(ctx context.Context, in *NodeUnpublishVolumeRequest, opts ...grpc.CallOption) (*NodeUnpublishVolumeResponse, error)
|
NodeUnpublishVolume(ctx context.Context, in *NodeUnpublishVolumeRequest, opts ...grpc.CallOption) (*NodeUnpublishVolumeResponse, error)
|
||||||
GetNodeID(ctx context.Context, in *GetNodeIDRequest, opts ...grpc.CallOption) (*GetNodeIDResponse, error)
|
NodeGetId(ctx context.Context, in *NodeGetIdRequest, opts ...grpc.CallOption) (*NodeGetIdResponse, error)
|
||||||
NodeProbe(ctx context.Context, in *NodeProbeRequest, opts ...grpc.CallOption) (*NodeProbeResponse, error)
|
NodeProbe(ctx context.Context, in *NodeProbeRequest, opts ...grpc.CallOption) (*NodeProbeResponse, error)
|
||||||
NodeGetCapabilities(ctx context.Context, in *NodeGetCapabilitiesRequest, opts ...grpc.CallOption) (*NodeGetCapabilitiesResponse, error)
|
NodeGetCapabilities(ctx context.Context, in *NodeGetCapabilitiesRequest, opts ...grpc.CallOption) (*NodeGetCapabilitiesResponse, error)
|
||||||
}
|
}
|
||||||
@ -2244,9 +2245,9 @@ func (c *nodeClient) NodeUnpublishVolume(ctx context.Context, in *NodeUnpublishV
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *nodeClient) GetNodeID(ctx context.Context, in *GetNodeIDRequest, opts ...grpc.CallOption) (*GetNodeIDResponse, error) {
|
func (c *nodeClient) NodeGetId(ctx context.Context, in *NodeGetIdRequest, opts ...grpc.CallOption) (*NodeGetIdResponse, error) {
|
||||||
out := new(GetNodeIDResponse)
|
out := new(NodeGetIdResponse)
|
||||||
err := grpc.Invoke(ctx, "/csi.Node/GetNodeID", in, out, c.cc, opts...)
|
err := grpc.Invoke(ctx, "/csi.Node/NodeGetId", in, out, c.cc, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -2276,7 +2277,7 @@ func (c *nodeClient) NodeGetCapabilities(ctx context.Context, in *NodeGetCapabil
|
|||||||
type NodeServer interface {
|
type NodeServer interface {
|
||||||
NodePublishVolume(context.Context, *NodePublishVolumeRequest) (*NodePublishVolumeResponse, error)
|
NodePublishVolume(context.Context, *NodePublishVolumeRequest) (*NodePublishVolumeResponse, error)
|
||||||
NodeUnpublishVolume(context.Context, *NodeUnpublishVolumeRequest) (*NodeUnpublishVolumeResponse, error)
|
NodeUnpublishVolume(context.Context, *NodeUnpublishVolumeRequest) (*NodeUnpublishVolumeResponse, error)
|
||||||
GetNodeID(context.Context, *GetNodeIDRequest) (*GetNodeIDResponse, error)
|
NodeGetId(context.Context, *NodeGetIdRequest) (*NodeGetIdResponse, error)
|
||||||
NodeProbe(context.Context, *NodeProbeRequest) (*NodeProbeResponse, error)
|
NodeProbe(context.Context, *NodeProbeRequest) (*NodeProbeResponse, error)
|
||||||
NodeGetCapabilities(context.Context, *NodeGetCapabilitiesRequest) (*NodeGetCapabilitiesResponse, error)
|
NodeGetCapabilities(context.Context, *NodeGetCapabilitiesRequest) (*NodeGetCapabilitiesResponse, error)
|
||||||
}
|
}
|
||||||
@ -2321,20 +2322,20 @@ func _Node_NodeUnpublishVolume_Handler(srv interface{}, ctx context.Context, dec
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _Node_GetNodeID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _Node_NodeGetId_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(GetNodeIDRequest)
|
in := new(NodeGetIdRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if interceptor == nil {
|
if interceptor == nil {
|
||||||
return srv.(NodeServer).GetNodeID(ctx, in)
|
return srv.(NodeServer).NodeGetId(ctx, in)
|
||||||
}
|
}
|
||||||
info := &grpc.UnaryServerInfo{
|
info := &grpc.UnaryServerInfo{
|
||||||
Server: srv,
|
Server: srv,
|
||||||
FullMethod: "/csi.Node/GetNodeID",
|
FullMethod: "/csi.Node/NodeGetId",
|
||||||
}
|
}
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
return srv.(NodeServer).GetNodeID(ctx, req.(*GetNodeIDRequest))
|
return srv.(NodeServer).NodeGetId(ctx, req.(*NodeGetIdRequest))
|
||||||
}
|
}
|
||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
@ -2388,8 +2389,8 @@ var _Node_serviceDesc = grpc.ServiceDesc{
|
|||||||
Handler: _Node_NodeUnpublishVolume_Handler,
|
Handler: _Node_NodeUnpublishVolume_Handler,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
MethodName: "GetNodeID",
|
MethodName: "NodeGetId",
|
||||||
Handler: _Node_GetNodeID_Handler,
|
Handler: _Node_NodeGetId_Handler,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
MethodName: "NodeProbe",
|
MethodName: "NodeProbe",
|
||||||
@ -2407,130 +2408,135 @@ var _Node_serviceDesc = grpc.ServiceDesc{
|
|||||||
func init() { proto.RegisterFile("csi.proto", fileDescriptor0) }
|
func init() { proto.RegisterFile("csi.proto", fileDescriptor0) }
|
||||||
|
|
||||||
var fileDescriptor0 = []byte{
|
var fileDescriptor0 = []byte{
|
||||||
// 1993 bytes of a gzipped FileDescriptorProto
|
// 2071 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x59, 0x4b, 0x73, 0xe3, 0x58,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x19, 0x49, 0x73, 0xdb, 0xd6,
|
||||||
0x15, 0xb6, 0x6c, 0xe7, 0xe1, 0xe3, 0x38, 0xed, 0x5c, 0xe7, 0xa1, 0x28, 0xfd, 0x70, 0xab, 0xa7,
|
0x59, 0xe0, 0xa2, 0xe5, 0xa3, 0xa8, 0xd0, 0x4f, 0x1b, 0x04, 0x4a, 0x32, 0x0d, 0xc5, 0x8e, 0x3a,
|
||||||
0x7b, 0x42, 0x15, 0xe3, 0xa2, 0x3c, 0x54, 0xd1, 0xe9, 0x9e, 0x19, 0x48, 0x6c, 0x4f, 0x62, 0x26,
|
0x93, 0xf2, 0xa0, 0x4c, 0xa7, 0x76, 0x1c, 0x7b, 0x2a, 0x51, 0x8c, 0xc4, 0x5a, 0xa2, 0x14, 0x88,
|
||||||
0x71, 0xa7, 0x14, 0xa7, 0x9b, 0x01, 0xa6, 0x84, 0x62, 0xdf, 0xa4, 0x45, 0xcb, 0x92, 0x47, 0x92,
|
0xb2, 0x9b, 0xb6, 0x19, 0x14, 0x22, 0x9f, 0x64, 0xd4, 0x24, 0xc0, 0x00, 0xa0, 0xc6, 0x9c, 0x4e,
|
||||||
0x5d, 0xed, 0x3d, 0x4b, 0x16, 0xec, 0xd8, 0xc1, 0x72, 0xa0, 0x58, 0x02, 0xbf, 0x80, 0xbf, 0x00,
|
0x6f, 0xbd, 0xb5, 0x87, 0xce, 0xf4, 0xd0, 0x4b, 0xa7, 0x3d, 0xb6, 0x9d, 0xe9, 0xad, 0xd3, 0x53,
|
||||||
0xac, 0xd9, 0xf2, 0x0f, 0xa8, 0x62, 0x43, 0xdd, 0x87, 0x64, 0x49, 0x96, 0x1c, 0x3b, 0x93, 0x1e,
|
0xfe, 0x42, 0xaf, 0xbd, 0xf4, 0xd4, 0x6b, 0xfa, 0x2b, 0x32, 0x6f, 0x01, 0xf8, 0x00, 0x02, 0x5c,
|
||||||
0x66, 0x27, 0x9d, 0xc7, 0x77, 0xcf, 0xe3, 0xde, 0x73, 0xce, 0x95, 0x20, 0xd7, 0x71, 0xf4, 0x4a,
|
0xec, 0x38, 0x27, 0x12, 0xdf, 0xbe, 0xbc, 0xf7, 0x2d, 0x00, 0x2c, 0x34, 0x5d, 0xb3, 0xdc, 0x75,
|
||||||
0xdf, 0xb6, 0x5c, 0x0b, 0x65, 0x3a, 0x8e, 0x2e, 0xdf, 0x83, 0x9d, 0x43, 0xec, 0x9e, 0x0d, 0xfa,
|
0x6c, 0xcf, 0x46, 0xe9, 0xa6, 0x6b, 0xaa, 0x5b, 0x50, 0x3c, 0xc2, 0xde, 0x45, 0xaf, 0xdb, 0xb5,
|
||||||
0x7d, 0xcb, 0x76, 0x71, 0xf7, 0x25, 0xb6, 0x1d, 0xdd, 0x32, 0x1d, 0x05, 0x7f, 0x39, 0xc0, 0x8e,
|
0x1d, 0x0f, 0xb7, 0x9e, 0x63, 0xc7, 0x35, 0x6d, 0xcb, 0xd5, 0xf0, 0x97, 0x3d, 0xec, 0x7a, 0xea,
|
||||||
0x2b, 0xff, 0x0c, 0xee, 0xc6, 0xb3, 0x9d, 0xbe, 0x65, 0x3a, 0x18, 0x3d, 0x07, 0xe4, 0x78, 0x4c,
|
0xcf, 0x60, 0x33, 0x1e, 0xed, 0x76, 0x6d, 0xcb, 0xc5, 0xe8, 0x31, 0x20, 0xd7, 0x47, 0xea, 0xb7,
|
||||||
0x75, 0xc8, 0xb9, 0xa2, 0x50, 0xce, 0xec, 0xe6, 0xab, 0x2b, 0x15, 0xb2, 0x16, 0x57, 0x51, 0xd6,
|
0x1c, 0x2b, 0x4b, 0xa5, 0xf4, 0x6e, 0x6e, 0x6f, 0xb1, 0x4c, 0x74, 0x71, 0x16, 0xed, 0x8e, 0x1b,
|
||||||
0x9c, 0x28, 0x88, 0xfc, 0x19, 0x2c, 0xf1, 0x67, 0xb4, 0x0e, 0x0b, 0x3d, 0xed, 0x97, 0x96, 0x2d,
|
0x15, 0xa2, 0x3e, 0x83, 0x39, 0xfe, 0x1f, 0xad, 0x40, 0xb6, 0x63, 0xfc, 0xd2, 0x76, 0x64, 0xa9,
|
||||||
0x0a, 0x65, 0x61, 0xb7, 0xa0, 0xb0, 0x17, 0x4a, 0xd5, 0x4d, 0xcb, 0x16, 0xd3, 0x9c, 0x4a, 0x5e,
|
0x24, 0xed, 0x66, 0x35, 0xf6, 0x40, 0xa1, 0xa6, 0x65, 0x3b, 0x72, 0x8a, 0x43, 0xc9, 0x03, 0x81,
|
||||||
0x08, 0xb5, 0xaf, 0xb9, 0x9d, 0xd7, 0x62, 0x86, 0x51, 0xe9, 0x8b, 0xfc, 0x09, 0xac, 0x1f, 0x62,
|
0x76, 0x0d, 0xaf, 0xf9, 0x52, 0x4e, 0x33, 0x28, 0x7d, 0x50, 0x9f, 0xc2, 0xca, 0x11, 0xf6, 0xce,
|
||||||
0xf7, 0xd4, 0x18, 0x5c, 0xe9, 0x66, 0xd3, 0xbc, 0xb4, 0xb8, 0x07, 0xe8, 0x09, 0x2c, 0x71, 0xbb,
|
0xdb, 0xbd, 0x1b, 0xd3, 0xaa, 0x59, 0xd7, 0x36, 0xf7, 0x00, 0x3d, 0x80, 0x39, 0x6e, 0x17, 0x95,
|
||||||
0x28, 0x76, 0xd4, 0x2c, 0x8f, 0x29, 0xff, 0x43, 0x80, 0x8d, 0x08, 0x00, 0xf7, 0x11, 0x41, 0xd6,
|
0x1d, 0x35, 0xcb, 0x47, 0xaa, 0xff, 0x91, 0x60, 0x35, 0x22, 0x80, 0xfb, 0x88, 0x20, 0x63, 0x19,
|
||||||
0xd4, 0x7a, 0x98, 0xaa, 0xe7, 0x14, 0xfa, 0x8c, 0x1e, 0xc3, 0xea, 0x10, 0x9b, 0x5d, 0xcb, 0xf6,
|
0x1d, 0x4c, 0xd9, 0x17, 0x34, 0xfa, 0x1f, 0xdd, 0x87, 0xa5, 0x5b, 0x6c, 0xb5, 0x6c, 0xc7, 0x77,
|
||||||
0x9c, 0xa6, 0x26, 0xe6, 0x94, 0x02, 0xa3, 0x7a, 0x6e, 0xd5, 0x61, 0xb9, 0xa7, 0x99, 0xfa, 0x25,
|
0x9a, 0x9a, 0xb8, 0xa0, 0xe5, 0x19, 0xd4, 0x77, 0xeb, 0x10, 0xe6, 0x3b, 0x86, 0x65, 0x5e, 0x63,
|
||||||
0x76, 0x5c, 0x31, 0x43, 0x83, 0xb2, 0x4b, 0x57, 0x8f, 0x5d, 0xa8, 0x72, 0xc2, 0x45, 0x1b, 0xa6,
|
0xd7, 0x93, 0xd3, 0x34, 0x28, 0xbb, 0x54, 0x7b, 0xac, 0xa2, 0xf2, 0x29, 0x27, 0xad, 0x5a, 0x9e,
|
||||||
0x6b, 0x8f, 0x14, 0x5f, 0x53, 0x7a, 0x0e, 0x85, 0x10, 0x0b, 0x15, 0x21, 0xf3, 0x06, 0x8f, 0xb8,
|
0xd3, 0xd7, 0x02, 0x4e, 0xe5, 0x31, 0xe4, 0x43, 0x28, 0x54, 0x80, 0xf4, 0x2b, 0xdc, 0xe7, 0x06,
|
||||||
0x41, 0xe4, 0x91, 0xc4, 0x64, 0xa8, 0x19, 0x03, 0xcc, 0xcd, 0x60, 0x2f, 0xcf, 0xd2, 0x4f, 0x05,
|
0x91, 0xbf, 0x24, 0x26, 0xb7, 0x46, 0xbb, 0x87, 0xb9, 0x19, 0xec, 0xe1, 0xe3, 0xd4, 0x43, 0x49,
|
||||||
0xf9, 0xbf, 0x19, 0x28, 0xd5, 0x6c, 0xac, 0xb9, 0xf8, 0xa5, 0x65, 0x0c, 0x7a, 0x78, 0xce, 0xb8,
|
0xfd, 0x47, 0x06, 0x96, 0x2b, 0x0e, 0x36, 0x3c, 0xfc, 0xdc, 0x6e, 0xf7, 0x3a, 0x78, 0xca, 0xb8,
|
||||||
0xf8, 0xde, 0xa7, 0x03, 0xde, 0xef, 0xc1, 0x6a, 0x47, 0xeb, 0x6b, 0x1d, 0xdd, 0x1d, 0xa9, 0xb6,
|
0x04, 0xde, 0xa7, 0x04, 0xef, 0x1f, 0xc1, 0x52, 0xd3, 0xe8, 0x1a, 0x4d, 0xd3, 0xeb, 0xeb, 0x8e,
|
||||||
0x66, 0x5e, 0x61, 0x9a, 0x8a, 0x7c, 0x15, 0x51, 0x88, 0x1a, 0x67, 0x29, 0x84, 0xa3, 0x14, 0x3a,
|
0x61, 0xdd, 0x60, 0x9a, 0x8a, 0xdc, 0x1e, 0xa2, 0x22, 0x2a, 0x1c, 0xa5, 0x11, 0x8c, 0x96, 0x6f,
|
||||||
0xc1, 0x57, 0xf4, 0x29, 0x94, 0x86, 0xd4, 0x0e, 0x95, 0xd0, 0x2f, 0x74, 0x43, 0x77, 0x75, 0xec,
|
0x8a, 0x8f, 0xe8, 0x53, 0x58, 0xbe, 0xa5, 0x76, 0xe8, 0x04, 0x7e, 0x65, 0xb6, 0x4d, 0xcf, 0xc4,
|
||||||
0x88, 0x59, 0x1a, 0x9c, 0x0d, 0x66, 0x02, 0xe5, 0xd7, 0x3c, 0xf6, 0x48, 0x41, 0xc3, 0x30, 0x45,
|
0xae, 0x9c, 0xa1, 0xc1, 0x59, 0x65, 0x26, 0x50, 0x7c, 0xc5, 0x47, 0xf7, 0x35, 0x74, 0x1b, 0x86,
|
||||||
0xc7, 0x0e, 0x3a, 0x02, 0xe8, 0x6b, 0xb6, 0xd6, 0xc3, 0x2e, 0xb6, 0x1d, 0x71, 0x21, 0x10, 0xdb,
|
0x98, 0xd8, 0x45, 0xc7, 0x00, 0x5d, 0xc3, 0x31, 0x3a, 0xd8, 0xc3, 0x8e, 0x2b, 0x67, 0x85, 0xd8,
|
||||||
0x18, 0x67, 0x2b, 0xa7, 0xbe, 0x28, 0x8b, 0x6d, 0x40, 0x17, 0xfd, 0x04, 0x8a, 0x03, 0x07, 0xdb,
|
0xc6, 0x38, 0x5b, 0x3e, 0x0f, 0x48, 0x59, 0x6c, 0x05, 0x5e, 0xf4, 0x6b, 0xd8, 0x6a, 0xda, 0x96,
|
||||||
0x6a, 0xc7, 0xc6, 0x5d, 0x6c, 0xba, 0xba, 0x66, 0x38, 0xe2, 0x22, 0xc5, 0xfb, 0x20, 0x11, 0xef,
|
0xe7, 0xd8, 0xed, 0x36, 0x76, 0xf4, 0x26, 0xe5, 0x26, 0x3f, 0x2d, 0x6c, 0x79, 0xa6, 0xd1, 0x76,
|
||||||
0xdc, 0xc1, 0x76, 0x6d, 0x2c, 0xcf, 0x40, 0xef, 0x0c, 0xc2, 0x54, 0xe9, 0x63, 0xb8, 0x13, 0x59,
|
0xe5, 0x59, 0x2a, 0xfc, 0x51, 0xa2, 0xf0, 0x4a, 0xc0, 0xcd, 0xb0, 0x95, 0x01, 0x2f, 0xd3, 0x56,
|
||||||
0x78, 0x9e, 0xcc, 0x49, 0x07, 0xb0, 0x1e, 0xb7, 0xce, 0x5c, 0xd9, 0x3f, 0x82, 0xf5, 0xb0, 0xfd,
|
0x6c, 0x26, 0x53, 0x28, 0x4f, 0xe0, 0xbd, 0x88, 0x75, 0xd3, 0xa4, 0x57, 0xa9, 0x43, 0x69, 0x9c,
|
||||||
0x7c, 0x4f, 0x7f, 0x0f, 0xf2, 0x3c, 0x0d, 0xba, 0x79, 0x69, 0xf1, 0x1d, 0x70, 0x27, 0x10, 0x7e,
|
0xfe, 0xa9, 0x8e, 0xcb, 0x63, 0x58, 0x09, 0xfb, 0xc8, 0x2f, 0xc1, 0x0e, 0xcc, 0xb2, 0x2c, 0xf0,
|
||||||
0xba, 0x31, 0x61, 0xe8, 0x3f, 0xcb, 0xbf, 0xcb, 0x42, 0x31, 0x9a, 0x19, 0xb4, 0x07, 0x0b, 0x17,
|
0xd3, 0x92, 0x13, 0x52, 0xa5, 0x71, 0x94, 0xfa, 0xe7, 0x0c, 0x14, 0xa2, 0xd9, 0x43, 0x8f, 0x20,
|
||||||
0x86, 0xd5, 0x79, 0xc3, 0x01, 0x1e, 0xc6, 0xe6, 0xaf, 0x72, 0x40, 0x44, 0x18, 0xf5, 0x28, 0xa5,
|
0x7b, 0xd5, 0xb6, 0x9b, 0xaf, 0x38, 0xe3, 0xbd, 0xd8, 0x1c, 0x97, 0x0f, 0x08, 0x09, 0x83, 0x1e,
|
||||||
0x30, 0x0d, 0xa2, 0xda, 0xb3, 0x06, 0xa6, 0x4b, 0x6d, 0x4e, 0x54, 0x3d, 0x21, 0x22, 0x63, 0x55,
|
0xcf, 0x68, 0x8c, 0x83, 0xb0, 0x76, 0xec, 0x9e, 0xe5, 0x51, 0x33, 0x13, 0x59, 0x4f, 0x09, 0xc9,
|
||||||
0xaa, 0x81, 0xf6, 0x21, 0xaf, 0x75, 0x3a, 0xd8, 0x71, 0xd4, 0x9e, 0xd5, 0xf5, 0xf6, 0x5e, 0x39,
|
0x80, 0x95, 0x72, 0xa0, 0x7d, 0xc8, 0x19, 0xcd, 0x26, 0x76, 0x5d, 0xbd, 0x63, 0xb7, 0xfc, 0xf3,
|
||||||
0x1e, 0x60, 0x9f, 0x0a, 0x9e, 0x58, 0x5d, 0xac, 0x80, 0xe6, 0x3f, 0x4b, 0x05, 0xc8, 0x07, 0xac,
|
0x59, 0x8a, 0x17, 0xb0, 0x4f, 0x09, 0x4f, 0xed, 0x16, 0xd6, 0xc0, 0x08, 0xfe, 0x2b, 0x79, 0xc8,
|
||||||
0x92, 0x0e, 0x21, 0x1f, 0x58, 0x09, 0x6d, 0xc1, 0xd2, 0xa5, 0xa3, 0xba, 0xa3, 0xbe, 0x77, 0xe8,
|
0x09, 0x56, 0x29, 0x47, 0x90, 0x13, 0x34, 0xa1, 0x75, 0x98, 0xbb, 0x76, 0x75, 0xaf, 0xdf, 0xf5,
|
||||||
0x17, 0x2f, 0x9d, 0xf6, 0xa8, 0x8f, 0xd1, 0x03, 0xc8, 0x53, 0x13, 0xd4, 0x4b, 0x43, 0xbb, 0x72,
|
0x0b, 0xc3, 0xec, 0xb5, 0xdb, 0xe8, 0x77, 0x31, 0xba, 0x0b, 0x39, 0x6a, 0x82, 0x7e, 0xdd, 0x36,
|
||||||
0xc4, 0x74, 0x39, 0xb3, 0x9b, 0x53, 0x80, 0x92, 0x3e, 0x25, 0x14, 0xe9, 0xdf, 0x02, 0xc0, 0x78,
|
0x6e, 0x5c, 0x39, 0x55, 0x4a, 0xef, 0x2e, 0x68, 0x40, 0x41, 0x9f, 0x12, 0x88, 0xf2, 0xb5, 0x04,
|
||||||
0x49, 0xb4, 0x07, 0x59, 0x6a, 0x22, 0x41, 0x59, 0xad, 0x3e, 0xbe, 0xce, 0xc4, 0x0a, 0xb5, 0x93,
|
0x30, 0x50, 0x89, 0x1e, 0x41, 0x86, 0x9a, 0x48, 0xa4, 0x2c, 0xed, 0xdd, 0x1f, 0x67, 0x62, 0x99,
|
||||||
0xaa, 0xc8, 0xbf, 0x17, 0x20, 0x4b, 0x31, 0xf2, 0xb0, 0x74, 0xde, 0xfa, 0xac, 0xf5, 0xe2, 0x55,
|
0xda, 0x49, 0x59, 0xd4, 0xbf, 0x48, 0x90, 0xa1, 0x32, 0x72, 0x30, 0x77, 0x59, 0x7f, 0x56, 0x3f,
|
||||||
0xab, 0x98, 0x42, 0x9b, 0x80, 0xce, 0x9a, 0xad, 0xc3, 0xe3, 0x86, 0xda, 0x7a, 0x51, 0x6f, 0xa8,
|
0x7b, 0x51, 0x2f, 0xcc, 0xa0, 0x35, 0x40, 0x17, 0xb5, 0xfa, 0xd1, 0x49, 0x55, 0xaf, 0x9f, 0x1d,
|
||||||
0xaf, 0x94, 0x66, 0xbb, 0xa1, 0x14, 0x05, 0xb4, 0x03, 0x5b, 0x41, 0xba, 0xd2, 0xd8, 0xaf, 0x37,
|
0x56, 0xf5, 0x17, 0x5a, 0xad, 0x51, 0xd5, 0x0a, 0x12, 0x2a, 0xc2, 0xba, 0x08, 0xd7, 0xaa, 0xfb,
|
||||||
0x14, 0xf5, 0x45, 0xeb, 0xf8, 0xf3, 0x62, 0x1a, 0x49, 0xb0, 0x79, 0x72, 0x7e, 0xdc, 0x6e, 0x4e,
|
0x87, 0x55, 0x4d, 0x3f, 0xab, 0x9f, 0x7c, 0x5e, 0x48, 0x21, 0x05, 0xd6, 0x4e, 0x2f, 0x4f, 0x1a,
|
||||||
0xf2, 0x32, 0xe8, 0x2e, 0x88, 0x01, 0x1e, 0xc7, 0xe0, 0xb0, 0x59, 0x02, 0x1b, 0xe0, 0xb2, 0x47,
|
0xb5, 0x61, 0x5c, 0x1a, 0x6d, 0x82, 0x2c, 0xe0, 0xb8, 0x0c, 0x2e, 0x36, 0x43, 0xc4, 0x0a, 0x58,
|
||||||
0xce, 0x5c, 0x38, 0x28, 0xf8, 0x69, 0x20, 0x91, 0x92, 0x5f, 0x41, 0x21, 0x74, 0xf2, 0x49, 0x8d,
|
0xf6, 0x97, 0x23, 0xb3, 0x07, 0xf9, 0x20, 0x0d, 0x24, 0x52, 0xea, 0x0b, 0xc8, 0x87, 0xaa, 0x03,
|
||||||
0xb4, 0xf1, 0x97, 0x03, 0xdd, 0xc6, 0x5d, 0xf5, 0x62, 0xe4, 0x62, 0x87, 0x86, 0x21, 0xab, 0x14,
|
0xa9, 0xa3, 0x0e, 0xfe, 0xb2, 0x67, 0x3a, 0xb8, 0xa5, 0x5f, 0xf5, 0x3d, 0xec, 0xd2, 0x30, 0xa4,
|
||||||
0x3c, 0xea, 0x01, 0x21, 0x92, 0x98, 0x1a, 0x7a, 0x4f, 0x77, 0xb9, 0x4c, 0x9a, 0xca, 0x00, 0x25,
|
0xb5, 0xbc, 0x0f, 0x3d, 0x20, 0x40, 0x12, 0xd3, 0xb6, 0xd9, 0x31, 0x3d, 0x4e, 0x93, 0xa2, 0x34,
|
||||||
0x51, 0x01, 0xf9, 0x6f, 0x02, 0xc0, 0x78, 0x53, 0x12, 0x58, 0xbf, 0xf8, 0x84, 0x60, 0x3d, 0x2a,
|
0x40, 0x41, 0x94, 0x40, 0xfd, 0x4a, 0x82, 0x59, 0x9e, 0x98, 0xfb, 0x42, 0x71, 0x0a, 0x89, 0xf4,
|
||||||
0x83, 0x5d, 0x85, 0xb4, 0xde, 0xe5, 0x07, 0x22, 0xad, 0x77, 0xd1, 0x0f, 0x01, 0x34, 0xd7, 0xb5,
|
0xa1, 0x4c, 0xe4, 0x12, 0xa4, 0xcc, 0x16, 0x3f, 0xff, 0x29, 0xb3, 0x85, 0x1e, 0x03, 0x18, 0x9e,
|
||||||
0xf5, 0x8b, 0x01, 0x51, 0x61, 0xc5, 0xf8, 0x41, 0x64, 0xc3, 0x57, 0xf6, 0x7d, 0x09, 0x5e, 0x27,
|
0xe7, 0x98, 0x57, 0x3d, 0xc2, 0xc2, 0x8a, 0x75, 0x51, 0x48, 0x46, 0x79, 0x3f, 0xc0, 0xf2, 0x1a,
|
||||||
0xc6, 0x2a, 0xe4, 0x34, 0x47, 0xd8, 0x73, 0x9d, 0xc4, 0xff, 0x08, 0x50, 0xaa, 0x63, 0x03, 0xdf,
|
0x32, 0x20, 0x27, 0x97, 0x38, 0x82, 0x9e, 0xea, 0xd2, 0xfd, 0x3d, 0x05, 0xcb, 0x87, 0xb8, 0x8d,
|
||||||
0xb4, 0x0e, 0xef, 0x40, 0xce, 0x3b, 0xb1, 0x9e, 0x5b, 0xcb, 0xfc, 0x78, 0x76, 0x63, 0x6b, 0x58,
|
0xdf, 0xb4, 0x46, 0x17, 0x61, 0x81, 0x17, 0xd5, 0xc0, 0xa5, 0x79, 0x06, 0xa8, 0xb5, 0x22, 0xf5,
|
||||||
0x26, 0x50, 0xc3, 0x62, 0x16, 0x9e, 0xb1, 0x86, 0xdd, 0x46, 0x11, 0xda, 0x84, 0xf5, 0xb0, 0x01,
|
0xad, 0x45, 0xd5, 0x84, 0xea, 0x5b, 0x5a, 0xa8, 0x6f, 0x31, 0x56, 0x08, 0xf5, 0x8d, 0x61, 0x47,
|
||||||
0xac, 0x08, 0xc9, 0x7f, 0xc9, 0xc2, 0xfd, 0x9a, 0x65, 0xba, 0xb6, 0x65, 0x18, 0xd8, 0x3e, 0x1d,
|
0xd5, 0xb7, 0x21, 0x8a, 0x70, 0x81, 0x8a, 0x17, 0x30, 0x55, 0xac, 0xd6, 0x60, 0x25, 0x6c, 0x24,
|
||||||
0x5c, 0x18, 0xba, 0xf3, 0xfa, 0x1d, 0x44, 0x67, 0x0b, 0x96, 0x4c, 0xab, 0x4b, 0x59, 0x19, 0x76,
|
0x2b, 0x50, 0xea, 0xff, 0x33, 0xb0, 0x3d, 0x50, 0x74, 0xde, 0xbb, 0x6a, 0x9b, 0xee, 0xcb, 0x77,
|
||||||
0x9c, 0xc9, 0x6b, 0xb3, 0x8b, 0x0e, 0x60, 0x2d, 0xda, 0x8c, 0x46, 0x62, 0x96, 0xae, 0x93, 0xd0,
|
0x10, 0xce, 0x75, 0x98, 0xb3, 0xec, 0x16, 0x45, 0xa5, 0xd9, 0xbd, 0x27, 0x8f, 0xb5, 0x16, 0x3a,
|
||||||
0x8a, 0x8a, 0xc3, 0x68, 0x09, 0x94, 0x60, 0xd9, 0xc6, 0x5a, 0xd7, 0x32, 0x8d, 0x91, 0xb8, 0x50,
|
0x80, 0x3b, 0xd1, 0xce, 0xd6, 0x97, 0x33, 0x54, 0x4f, 0x42, 0x5f, 0x2b, 0xdc, 0x46, 0x6b, 0xa5,
|
||||||
0x16, 0x76, 0x97, 0x15, 0xff, 0x1d, 0x75, 0x12, 0x5b, 0xcb, 0x53, 0xd6, 0x5a, 0xa6, 0x3a, 0x3f,
|
0x02, 0xf3, 0x0e, 0x36, 0x5a, 0xb6, 0xd5, 0xee, 0xcb, 0xd9, 0x92, 0xb4, 0x3b, 0xaf, 0x05, 0xcf,
|
||||||
0x5b, 0x86, 0xd0, 0xa5, 0xef, 0x44, 0x60, 0x7f, 0x2f, 0xd1, 0x55, 0xf6, 0x66, 0x59, 0x85, 0xbd,
|
0xe8, 0xb7, 0x12, 0x6c, 0x0b, 0x89, 0xec, 0x32, 0x0f, 0x63, 0x3a, 0x55, 0x95, 0x75, 0xaa, 0x91,
|
||||||
0x45, 0x77, 0x3e, 0x77, 0x74, 0x4c, 0xbe, 0x8d, 0x9d, 0x20, 0xd5, 0x60, 0x23, 0x76, 0xb9, 0xb9,
|
0xb1, 0x18, 0x46, 0x0f, 0x65, 0x75, 0xb3, 0x39, 0x82, 0x04, 0x5d, 0x07, 0xde, 0x0a, 0xb7, 0x66,
|
||||||
0xb6, 0xd3, 0xdf, 0x05, 0x78, 0x90, 0xe8, 0x13, 0xef, 0x6f, 0x6f, 0xa0, 0xd4, 0x67, 0x0c, 0x35,
|
0x4e, 0xec, 0x94, 0xa3, 0xf5, 0xb3, 0xa7, 0xe8, 0x9d, 0xe2, 0x11, 0x19, 0x80, 0x95, 0x33, 0xb8,
|
||||||
0xdc, 0xe7, 0x48, 0x58, 0x9e, 0x4f, 0x0f, 0x0b, 0x9f, 0xc6, 0x42, 0x54, 0x52, 0x1d, 0x58, 0x60,
|
0x37, 0xd6, 0xd4, 0xa9, 0x1a, 0x66, 0x05, 0x56, 0x63, 0x75, 0x4f, 0x75, 0x08, 0xbf, 0x92, 0xe0,
|
||||||
0xd6, 0xfa, 0x51, 0xba, 0x54, 0x87, 0xcd, 0x78, 0xe1, 0xb9, 0xdc, 0xfa, 0x53, 0x1a, 0xca, 0x63,
|
0x6e, 0xa2, 0x83, 0xbc, 0x63, 0xfe, 0x04, 0x16, 0xfd, 0x1c, 0x99, 0xd6, 0xb5, 0xcd, 0x87, 0xe2,
|
||||||
0x9b, 0xce, 0xcd, 0xfe, 0x37, 0x7f, 0x1e, 0x70, 0xcc, 0x7e, 0x65, 0x93, 0xd9, 0xb3, 0x48, 0xc8,
|
0x1f, 0x8c, 0x0e, 0x0e, 0x9f, 0x04, 0x39, 0x94, 0x4c, 0x87, 0x2c, 0x30, 0xb9, 0xee, 0x00, 0xa2,
|
||||||
0xe2, 0xcd, 0xfb, 0x06, 0x6b, 0xca, 0x23, 0x78, 0x38, 0xc5, 0x1a, 0x5e, 0x60, 0xfe, 0x95, 0x86,
|
0x3c, 0x85, 0x42, 0x94, 0x60, 0x2a, 0xeb, 0xbf, 0x4e, 0x89, 0x77, 0xf2, 0xd2, 0xea, 0x7e, 0xf7,
|
||||||
0x87, 0x2f, 0x35, 0x43, 0xef, 0xfa, 0x03, 0x50, 0x70, 0x86, 0xbc, 0xd5, 0x98, 0x26, 0xcc, 0xb5,
|
0x97, 0xe5, 0x0f, 0x12, 0x94, 0x84, 0xc3, 0xdc, 0xb3, 0xe2, 0x8e, 0x33, 0x1b, 0x0a, 0x6b, 0x91,
|
||||||
0x99, 0x79, 0xe7, 0x5a, 0x3d, 0xee, 0x34, 0xb3, 0x1c, 0x7c, 0xc4, 0x50, 0xae, 0xf3, 0x67, 0xe6,
|
0x88, 0xc5, 0xdb, 0x1b, 0x47, 0x30, 0x74, 0xa4, 0x85, 0xfb, 0x13, 0x47, 0xa4, 0x7c, 0x06, 0x3b,
|
||||||
0x03, 0x7d, 0x2b, 0x87, 0xf1, 0xe7, 0x20, 0x4f, 0xb3, 0x88, 0x1f, 0xc7, 0xbb, 0x90, 0xf3, 0xaf,
|
0x13, 0x88, 0x99, 0x2a, 0xd6, 0x3b, 0xe2, 0xf9, 0x1d, 0x32, 0x9d, 0xd7, 0xae, 0xff, 0xa5, 0xe0,
|
||||||
0x7f, 0x14, 0x77, 0x59, 0x19, 0x13, 0x90, 0x08, 0x4b, 0x3d, 0xec, 0x38, 0xda, 0x95, 0x87, 0xef,
|
0xde, 0x73, 0xa3, 0x6d, 0xb6, 0x82, 0xb9, 0x4b, 0x9c, 0x75, 0xbf, 0xd5, 0x8c, 0x24, 0xcc, 0xdf,
|
||||||
0xbd, 0xca, 0xbf, 0x12, 0x00, 0x1d, 0xeb, 0x0e, 0x9f, 0xcb, 0xe6, 0xce, 0x18, 0x19, 0xd7, 0xb4,
|
0xe9, 0x69, 0xe7, 0x6f, 0x33, 0xee, 0xfe, 0xb3, 0x84, 0x7d, 0xc2, 0xa4, 0x8c, 0xf3, 0x67, 0xe2,
|
||||||
0xb7, 0x2a, 0x36, 0x5d, 0x5b, 0xe7, 0xa3, 0x45, 0x41, 0x81, 0x9e, 0xf6, 0xb6, 0xc1, 0x28, 0x64,
|
0x12, 0xf0, 0xad, 0xdc, 0xd8, 0x9f, 0x83, 0x3a, 0xca, 0x22, 0x7e, 0x67, 0x37, 0x61, 0x21, 0x58,
|
||||||
0x96, 0x70, 0x5c, 0xcd, 0x76, 0x75, 0xf3, 0x4a, 0x75, 0xad, 0x37, 0xd8, 0xe4, 0x07, 0xa2, 0xe0,
|
0x53, 0xa9, 0xdc, 0x79, 0x6d, 0x00, 0x40, 0x32, 0xcc, 0x75, 0xb0, 0xeb, 0x1a, 0x37, 0xbe, 0x7c,
|
||||||
0x51, 0xdb, 0x84, 0x28, 0xff, 0x51, 0x80, 0x52, 0xc8, 0x0c, 0xee, 0xd6, 0x53, 0x58, 0xf2, 0xb0,
|
0xff, 0x51, 0xfd, 0x8d, 0x04, 0xe8, 0xc4, 0x74, 0xf9, 0x6c, 0x38, 0x75, 0xc6, 0xc8, 0xc8, 0x68,
|
||||||
0x59, 0x65, 0xb9, 0x4f, 0xed, 0x88, 0x11, 0xad, 0xb0, 0x24, 0x78, 0xe2, 0xe8, 0x1e, 0x80, 0x89,
|
0xbc, 0xd6, 0xb1, 0xe5, 0x39, 0x26, 0x1f, 0x6f, 0xb2, 0x1a, 0x74, 0x8c, 0xd7, 0x55, 0x06, 0x21,
|
||||||
0xdf, 0xba, 0x7c, 0x51, 0xe6, 0x75, 0x8e, 0x50, 0xe8, 0x82, 0xd2, 0x1e, 0x2c, 0xb0, 0x54, 0xcc,
|
0x33, 0x8d, 0xeb, 0x19, 0x8e, 0x67, 0x5a, 0x37, 0xba, 0x67, 0xbf, 0xc2, 0x16, 0xbf, 0x4e, 0x79,
|
||||||
0x3f, 0xa7, 0xff, 0x3a, 0x0d, 0xe8, 0x10, 0xbb, 0xfe, 0x28, 0x36, 0x67, 0xc8, 0x12, 0xf6, 0x71,
|
0x1f, 0xda, 0x20, 0x40, 0xf5, 0x4f, 0x12, 0x2c, 0x87, 0xcc, 0xe0, 0x6e, 0x3d, 0x84, 0x39, 0x5f,
|
||||||
0x7a, 0xde, 0x7d, 0x7c, 0x18, 0xba, 0x9f, 0xb1, 0x63, 0xf0, 0xbe, 0x77, 0xf7, 0x8d, 0x18, 0x37,
|
0x36, 0xab, 0x42, 0xdb, 0xd4, 0x8e, 0x18, 0xd2, 0x32, 0x4b, 0x82, 0x4f, 0x8e, 0xb6, 0x00, 0x2c,
|
||||||
0xed, 0x7a, 0xf6, 0x35, 0x2f, 0x51, 0x72, 0x1d, 0x4a, 0xa1, 0x05, 0x79, 0xe6, 0x3e, 0x00, 0xa4,
|
0xfc, 0xda, 0xe3, 0x4a, 0x99, 0xd7, 0x0b, 0x04, 0x42, 0x15, 0x2a, 0x1f, 0x42, 0x96, 0xa5, 0x62,
|
||||||
0x0d, 0x35, 0xdd, 0xd0, 0x2e, 0x0c, 0xe6, 0x29, 0xe1, 0xf2, 0x41, 0x72, 0xcd, 0xe7, 0x78, 0x6a,
|
0xa2, 0xf5, 0xe0, 0x77, 0x29, 0x40, 0x47, 0xd8, 0x0b, 0x26, 0xc0, 0x29, 0xa3, 0x94, 0x70, 0x74,
|
||||||
0xf2, 0x8f, 0x60, 0x33, 0xd0, 0x2e, 0x6c, 0xeb, 0x62, 0xde, 0x82, 0x2c, 0x6f, 0xc3, 0xd6, 0x04,
|
0x53, 0xd3, 0x1e, 0xdd, 0xa3, 0xd0, 0xea, 0xc8, 0x4e, 0xfe, 0x07, 0xfe, 0x5a, 0x1e, 0x31, 0x6e,
|
||||||
0x02, 0xaf, 0x52, 0x3f, 0x0e, 0xd6, 0x7d, 0x6e, 0xec, 0x0d, 0x6b, 0x94, 0xac, 0x07, 0xcb, 0xe2,
|
0xd4, 0xe6, 0xf8, 0x96, 0xab, 0x9b, 0x7a, 0x08, 0xcb, 0x21, 0x85, 0x3c, 0x59, 0xdf, 0x07, 0x64,
|
||||||
0x04, 0x16, 0x77, 0xbe, 0x0e, 0x2b, 0x31, 0xc9, 0x2d, 0x47, 0x4a, 0xfc, 0x19, 0xb6, 0x87, 0x7a,
|
0xdc, 0x1a, 0x66, 0xdb, 0xb8, 0x6a, 0x33, 0x4f, 0x09, 0x96, 0xcf, 0xb0, 0x77, 0x02, 0x8c, 0xcf,
|
||||||
0x27, 0x98, 0xe7, 0x90, 0x96, 0xfc, 0xdb, 0x34, 0xec, 0x4c, 0x91, 0x46, 0x4f, 0x21, 0x63, 0xf7,
|
0xa6, 0xfe, 0x08, 0xd6, 0x84, 0x6e, 0xe2, 0xd8, 0x57, 0xd3, 0x56, 0x70, 0x75, 0x03, 0xd6, 0x87,
|
||||||
0x3b, 0xdc, 0xdc, 0xf7, 0xae, 0x03, 0xaf, 0x28, 0xa7, 0xb5, 0xa3, 0x94, 0x42, 0x54, 0xa4, 0xbf,
|
0x24, 0xf0, 0xc2, 0xf4, 0x63, 0xb1, 0x51, 0x70, 0x63, 0xdf, 0xb0, 0x2c, 0xa9, 0xa6, 0x58, 0x09,
|
||||||
0x0a, 0x90, 0x51, 0x4e, 0x6b, 0xe8, 0x63, 0xc8, 0xfa, 0x77, 0xb0, 0xd5, 0xea, 0x77, 0x66, 0x81,
|
0x87, 0x64, 0x71, 0xe7, 0x0f, 0x61, 0x31, 0x26, 0xb9, 0xa5, 0x48, 0x0b, 0xb8, 0xc0, 0xce, 0xad,
|
||||||
0xa8, 0x90, 0x6b, 0x9a, 0x42, 0xd5, 0x64, 0x0b, 0xb2, 0xf4, 0xd2, 0x16, 0xba, 0x40, 0x89, 0xb0,
|
0xd9, 0x14, 0xf3, 0x1c, 0xe2, 0x52, 0xff, 0x98, 0x82, 0xe2, 0x08, 0x6a, 0xf4, 0x10, 0xd2, 0x4e,
|
||||||
0x5e, 0x53, 0x1a, 0xfb, 0xed, 0x86, 0x5a, 0x6f, 0x1c, 0x37, 0xda, 0x0d, 0xf5, 0xe5, 0x8b, 0xe3,
|
0xb7, 0xc9, 0xcd, 0x7d, 0x7f, 0x9c, 0xf0, 0xb2, 0x76, 0x5e, 0x39, 0x9e, 0xd1, 0x08, 0x8b, 0xf2,
|
||||||
0xf3, 0x93, 0x46, 0x51, 0x20, 0x37, 0xa1, 0xd3, 0xf3, 0x83, 0xe3, 0xe6, 0xd9, 0x91, 0x7a, 0xde,
|
0x2f, 0x09, 0xd2, 0xda, 0x79, 0x05, 0x3d, 0x81, 0x4c, 0xb0, 0xfa, 0x2d, 0xed, 0x7d, 0x6f, 0x12,
|
||||||
0xf2, 0x9e, 0x38, 0x37, 0x8d, 0x8a, 0xb0, 0x72, 0xdc, 0x3c, 0x6b, 0x73, 0xc2, 0x59, 0x31, 0x43,
|
0x11, 0x65, 0xb2, 0x1d, 0x6a, 0x94, 0x4d, 0xb5, 0x21, 0x43, 0x77, 0xc5, 0xd0, 0xde, 0x26, 0xc3,
|
||||||
0x28, 0x87, 0x8d, 0xb6, 0x5a, 0xdb, 0x3f, 0xdd, 0xaf, 0x35, 0xdb, 0x9f, 0x17, 0xb3, 0x07, 0x8b,
|
0x4a, 0x45, 0xab, 0xee, 0x37, 0xaa, 0xfa, 0x61, 0xf5, 0xa4, 0xda, 0xa8, 0xea, 0xcf, 0xcf, 0x4e,
|
||||||
0xcc, 0x5e, 0xf9, 0x9f, 0x0b, 0x20, 0xb6, 0xac, 0x2e, 0x7e, 0x77, 0x13, 0x6d, 0x37, 0x7e, 0xbc,
|
0x2e, 0x4f, 0xab, 0x05, 0x89, 0x2c, 0x60, 0xe7, 0x97, 0x07, 0x27, 0xb5, 0x8b, 0x63, 0xfd, 0xb2,
|
||||||
0x61, 0xc7, 0xec, 0xfb, 0x14, 0x30, 0xc9, 0x80, 0xd9, 0xe7, 0x1a, 0x52, 0x3e, 0x5d, 0xcd, 0xbe,
|
0xee, 0xff, 0xe3, 0xd8, 0x14, 0x2a, 0xc0, 0xe2, 0x49, 0xed, 0xa2, 0xc1, 0x01, 0x17, 0x85, 0x34,
|
||||||
0xc2, 0xae, 0xda, 0xd7, 0xdc, 0xd7, 0x74, 0x30, 0xce, 0x29, 0xc0, 0x48, 0xa7, 0x9a, 0xfb, 0x3a,
|
0x81, 0x1c, 0x55, 0x1b, 0x7a, 0x65, 0xff, 0x7c, 0xbf, 0x52, 0x6b, 0x7c, 0x5e, 0xc8, 0x1c, 0xcc,
|
||||||
0x7e, 0x7e, 0x5e, 0xb8, 0xf9, 0xfc, 0xbc, 0x18, 0x99, 0x9f, 0xbf, 0x88, 0x99, 0x47, 0xd8, 0x64,
|
0x32, 0x7b, 0xd5, 0xff, 0x66, 0x41, 0xae, 0xdb, 0x2d, 0xfc, 0xee, 0xe6, 0xe3, 0xcf, 0x22, 0x63,
|
||||||
0x5b, 0x9d, 0xee, 0xe3, 0x6c, 0x93, 0xf3, 0x2f, 0xe2, 0x7a, 0xed, 0x32, 0xc5, 0xff, 0x70, 0x3a,
|
0x0f, 0xbb, 0x5f, 0x65, 0x2a, 0x29, 0x49, 0xf3, 0xe8, 0x79, 0x87, 0x94, 0x47, 0xcf, 0x70, 0x6e,
|
||||||
0xfe, 0xac, 0x2d, 0xf6, 0x56, 0x26, 0xc3, 0x6f, 0xcf, 0xe4, 0xbd, 0x03, 0xdb, 0x31, 0x21, 0xe1,
|
0xb0, 0xa7, 0x77, 0x0d, 0xef, 0x25, 0x9d, 0xa9, 0x17, 0x34, 0x60, 0xa0, 0x73, 0xc3, 0x7b, 0x19,
|
||||||
0x65, 0xec, 0xab, 0x34, 0x48, 0x84, 0xfb, 0x2e, 0x27, 0xd7, 0xc8, 0x8e, 0xcc, 0x4c, 0xec, 0x48,
|
0x3f, 0x7a, 0x67, 0xdf, 0x7c, 0xf4, 0x9e, 0x8d, 0x8c, 0xde, 0x2e, 0xc8, 0x74, 0x8c, 0x89, 0x1b,
|
||||||
0x35, 0x71, 0x82, 0x1d, 0x9f, 0x8a, 0xff, 0xfb, 0xec, 0x7a, 0x0f, 0x76, 0x62, 0xed, 0xe0, 0x81,
|
0x52, 0xc4, 0x99, 0x37, 0xd1, 0x3f, 0x01, 0x31, 0x34, 0x94, 0xac, 0x59, 0xb1, 0x48, 0xf4, 0x8b,
|
||||||
0x7c, 0x06, 0xc5, 0x43, 0xec, 0x12, 0x89, 0x66, 0x7d, 0xde, 0xfa, 0xff, 0x5d, 0x58, 0x0b, 0xe8,
|
0xb8, 0x0e, 0x3b, 0x4f, 0xb5, 0x7d, 0x34, 0x5a, 0xdb, 0xa4, 0x8d, 0xf5, 0x2d, 0xe7, 0x48, 0xa5,
|
||||||
0xf2, 0x7a, 0x1f, 0x98, 0xf7, 0x85, 0xe0, 0xbc, 0x4f, 0x56, 0xa2, 0xf9, 0xbc, 0x49, 0x43, 0x2b,
|
0x06, 0xc5, 0x11, 0x8e, 0x7d, 0xf7, 0x53, 0x79, 0x11, 0x36, 0x62, 0x62, 0xc2, 0x4b, 0xd9, 0xbf,
|
||||||
0xc1, 0x5a, 0x40, 0x97, 0x9b, 0x5e, 0x67, 0x5b, 0xe0, 0x6b, 0x36, 0xb1, 0x2f, 0x58, 0x7c, 0x92,
|
0x53, 0xa0, 0x10, 0xec, 0xbb, 0x1c, 0x77, 0x23, 0x07, 0x35, 0x3d, 0x74, 0x50, 0x7f, 0x05, 0x0a,
|
||||||
0xda, 0xd7, 0x27, 0x91, 0xf6, 0xc5, 0x46, 0x2f, 0xc9, 0xcf, 0xef, 0x75, 0x8d, 0xeb, 0x0f, 0x02,
|
0x3d, 0x48, 0xa3, 0xe6, 0xdd, 0x27, 0x41, 0x72, 0x13, 0x26, 0xdd, 0x10, 0x6a, 0xe8, 0x38, 0xd1,
|
||||||
0x6c, 0xc4, 0xca, 0xa1, 0x6a, 0xb0, 0x65, 0xdd, 0x4f, 0x06, 0x0c, 0x36, 0xab, 0x33, 0xd6, 0xab,
|
0x93, 0x1a, 0x3b, 0xdd, 0x3e, 0x83, 0xad, 0x91, 0xac, 0x53, 0xc5, 0x7a, 0x8b, 0xe5, 0x3e, 0x69,
|
||||||
0x7e, 0x10, 0xea, 0x55, 0x8f, 0xa6, 0xeb, 0x06, 0xbb, 0x54, 0x29, 0xa6, 0x4b, 0x79, 0x9d, 0xa4,
|
0xa2, 0xfd, 0x18, 0x0a, 0x04, 0x7d, 0x84, 0xbd, 0x5a, 0x6b, 0xda, 0x46, 0xf1, 0x21, 0xdc, 0x11,
|
||||||
0xfa, 0x67, 0x01, 0x96, 0x9b, 0x74, 0xa3, 0xb9, 0xa4, 0x1a, 0xae, 0xc7, 0xfd, 0x8b, 0x41, 0x65,
|
0x78, 0x79, 0x63, 0x10, 0x36, 0x09, 0x49, 0xdc, 0x24, 0x7c, 0x4d, 0x6f, 0xd4, 0xf9, 0x96, 0x99,
|
||||||
0x6f, 0xac, 0x4a, 0xfa, 0x8b, 0x23, 0x3d, 0x9c, 0x22, 0xc1, 0x33, 0x97, 0x42, 0x47, 0x50, 0x08,
|
0xa6, 0x70, 0xcf, 0x3b, 0x64, 0xe7, 0xe4, 0x2d, 0xbb, 0xdd, 0x17, 0x2c, 0x3e, 0x49, 0x7d, 0xee,
|
||||||
0xfd, 0x96, 0x40, 0xdb, 0x71, 0xbf, 0x2a, 0x18, 0xa0, 0x94, 0xfc, 0x17, 0x43, 0x4e, 0x55, 0xbf,
|
0x69, 0xa4, 0xcf, 0xb1, 0xb1, 0x4c, 0x09, 0x52, 0x3f, 0xae, 0xc3, 0xfd, 0x4d, 0x82, 0xd5, 0x58,
|
||||||
0x5a, 0x04, 0x18, 0xf7, 0x69, 0xd4, 0x80, 0x95, 0xe0, 0x37, 0x68, 0x24, 0x26, 0x7d, 0x56, 0x97,
|
0x3a, 0xb4, 0x27, 0xf6, 0xb6, 0xed, 0x64, 0x81, 0x62, 0x57, 0xbb, 0x60, 0x4d, 0xed, 0x87, 0xa1,
|
||||||
0xb6, 0x63, 0x38, 0xbe, 0x7d, 0x0d, 0x58, 0x09, 0x7e, 0x45, 0xe2, 0x30, 0x31, 0x5f, 0xb6, 0x38,
|
0xa6, 0xb6, 0x33, 0x9a, 0x57, 0x6c, 0x67, 0xcb, 0x31, 0xed, 0xcc, 0x6f, 0x39, 0x7b, 0xff, 0x94,
|
||||||
0x4c, 0xec, 0x27, 0xa7, 0x14, 0xba, 0x0c, 0x0d, 0x62, 0xc1, 0x03, 0x88, 0x1e, 0xcd, 0xf0, 0xb9,
|
0x60, 0xbe, 0x46, 0x0f, 0x9a, 0xd7, 0x47, 0x5f, 0xd0, 0xaf, 0x34, 0x43, 0xdf, 0x93, 0x50, 0xc9,
|
||||||
0x44, 0x7a, 0x6f, 0x96, 0x8f, 0x07, 0x72, 0x0a, 0x19, 0xb0, 0x9d, 0x78, 0x41, 0x45, 0x8f, 0x67,
|
0x9f, 0xbf, 0x92, 0xbe, 0x44, 0x29, 0xf7, 0x46, 0x50, 0xf0, 0xcc, 0xcd, 0xa0, 0x63, 0xc8, 0x87,
|
||||||
0xba, 0x4e, 0x4b, 0x4f, 0xae, 0x13, 0xf3, 0x57, 0xb3, 0x40, 0x4a, 0xbe, 0x86, 0xa1, 0x27, 0xb3,
|
0x3e, 0xad, 0xa0, 0x8d, 0xb8, 0xcf, 0x2d, 0x4c, 0xa0, 0x92, 0xfc, 0x25, 0x46, 0x9d, 0xd9, 0xfb,
|
||||||
0xdd, 0x1c, 0xa5, 0xf7, 0xaf, 0x95, 0xf3, 0x17, 0x3c, 0x80, 0x7c, 0xe0, 0x9a, 0x83, 0xb6, 0x26,
|
0xeb, 0x2c, 0xc0, 0xa0, 0xa1, 0xa3, 0x2a, 0x2c, 0x8a, 0xaf, 0xc5, 0x91, 0x9c, 0xf4, 0x35, 0x40,
|
||||||
0x2f, 0x3e, 0x0c, 0x52, 0x4c, 0xba, 0x11, 0x31, 0x8c, 0xc0, 0x6c, 0xce, 0x31, 0x26, 0xaf, 0x07,
|
0xd9, 0x88, 0xc1, 0x04, 0xf6, 0x55, 0x61, 0x51, 0x7c, 0x79, 0xc5, 0xc5, 0xc4, 0xbc, 0x74, 0xe3,
|
||||||
0x1c, 0x23, 0x66, 0x8c, 0x97, 0x53, 0xa8, 0x05, 0x77, 0x22, 0x73, 0x35, 0xda, 0x89, 0x66, 0x28,
|
0x62, 0x62, 0xdf, 0x74, 0xcd, 0xa0, 0xeb, 0xd0, 0xc4, 0x26, 0x5e, 0x40, 0xb4, 0x33, 0xc1, 0xcb,
|
||||||
0x50, 0xde, 0xa4, 0xbb, 0xf1, 0xcc, 0xf8, 0xb4, 0x45, 0x2a, 0xd0, 0x44, 0xda, 0xe2, 0xeb, 0xdc,
|
0x17, 0xe5, 0xfd, 0x49, 0x5e, 0x42, 0xa8, 0x33, 0xa8, 0x0d, 0x1b, 0x89, 0xcb, 0x2b, 0xba, 0x3f,
|
||||||
0x44, 0xda, 0x12, 0x0a, 0x99, 0x9c, 0xaa, 0xfe, 0x26, 0x03, 0x59, 0x52, 0x25, 0x50, 0x9b, 0x57,
|
0xd1, 0x5e, 0xae, 0x3c, 0x18, 0x47, 0x16, 0x68, 0xb3, 0x41, 0x49, 0x5e, 0xd1, 0xd0, 0x83, 0xc9,
|
||||||
0xd3, 0xd0, 0x2e, 0xb9, 0x37, 0x75, 0x08, 0x91, 0xee, 0x27, 0xb1, 0x7d, 0x67, 0x7e, 0x0a, 0xa5,
|
0xb6, 0x4a, 0xe5, 0x83, 0xb1, 0x74, 0x81, 0xc2, 0x03, 0xc8, 0x09, 0x2b, 0x10, 0x5a, 0x1f, 0x5e,
|
||||||
0x98, 0x46, 0x83, 0x1e, 0x5c, 0xd3, 0x0a, 0xa5, 0x72, 0xb2, 0x80, 0x8f, 0xfd, 0x11, 0xe4, 0xfc,
|
0x8a, 0x98, 0x48, 0x39, 0x69, 0x5b, 0x62, 0x32, 0x84, 0x21, 0x9e, 0xcb, 0x18, 0xde, 0x23, 0xb8,
|
||||||
0x4e, 0x83, 0x36, 0xbc, 0x0c, 0x85, 0xba, 0x96, 0xb4, 0x19, 0x25, 0x07, 0xb5, 0xfd, 0xee, 0xc1,
|
0x8c, 0x98, 0x79, 0x5f, 0x9d, 0x41, 0x75, 0x78, 0x2f, 0x32, 0x80, 0xa3, 0x62, 0x34, 0x43, 0x42,
|
||||||
0xb5, 0xa3, 0x9d, 0x88, 0x6b, 0x4f, 0x36, 0x19, 0xdf, 0xaf, 0x68, 0x7a, 0xc6, 0x7e, 0x25, 0x24,
|
0x79, 0x53, 0x36, 0xe3, 0x91, 0xf1, 0x69, 0x8b, 0x54, 0xa0, 0xa1, 0xb4, 0xc5, 0xd7, 0xb9, 0xa1,
|
||||||
0xa6, 0x9c, 0x2c, 0xe0, 0x61, 0x5f, 0x2c, 0xd2, 0x9f, 0xe3, 0x1f, 0xfe, 0x2f, 0x00, 0x00, 0xff,
|
0xb4, 0x25, 0x14, 0x32, 0x75, 0x66, 0xef, 0xf7, 0x69, 0xc8, 0x90, 0x2a, 0x81, 0x1a, 0xbc, 0x9a,
|
||||||
0xff, 0x17, 0x25, 0x65, 0xbe, 0x29, 0x1f, 0x00, 0x00,
|
0x86, 0x4e, 0xc9, 0xd6, 0xc8, 0x51, 0x45, 0xd9, 0x4e, 0x42, 0x07, 0xce, 0xfc, 0x14, 0x96, 0x63,
|
||||||
|
0x1a, 0x0d, 0xba, 0x3b, 0xa6, 0x4b, 0x2a, 0xa5, 0x64, 0x82, 0x40, 0xf6, 0x27, 0xb0, 0x10, 0x74,
|
||||||
|
0x1a, 0xb4, 0x1a, 0x30, 0x88, 0x5d, 0x4b, 0x59, 0x8b, 0x82, 0xa3, 0xdc, 0x2c, 0x61, 0x03, 0xee,
|
||||||
|
0x50, 0xaa, 0xd6, 0xa2, 0xe0, 0xa8, 0x5f, 0xd1, 0xf4, 0xdc, 0x15, 0xd5, 0xc5, 0x25, 0xa6, 0x94,
|
||||||
|
0x4c, 0xe0, 0xcb, 0xbe, 0x9a, 0xa5, 0x1f, 0xf8, 0x3f, 0xfa, 0x26, 0x00, 0x00, 0xff, 0xff, 0x9b,
|
||||||
|
0x61, 0xfa, 0x6c, 0xed, 0x1f, 0x00, 0x00,
|
||||||
}
|
}
|
||||||
|
106
vendor/github.com/container-storage-interface/spec/spec.md
generated
vendored
106
vendor/github.com/container-storage-interface/spec/spec.md
generated
vendored
@ -279,8 +279,8 @@ service Node {
|
|||||||
rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest)
|
rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest)
|
||||||
returns (NodeUnpublishVolumeResponse) {}
|
returns (NodeUnpublishVolumeResponse) {}
|
||||||
|
|
||||||
rpc GetNodeID (GetNodeIDRequest)
|
rpc NodeGetId (NodeGetIdRequest)
|
||||||
returns (GetNodeIDResponse) {}
|
returns (NodeGetIdResponse) {}
|
||||||
|
|
||||||
rpc NodeProbe (NodeProbeRequest)
|
rpc NodeProbe (NodeProbeRequest)
|
||||||
returns (NodeProbeResponse) {}
|
returns (NodeProbeResponse) {}
|
||||||
@ -386,9 +386,12 @@ message GetSupportedVersionsResponse {
|
|||||||
// Specifies a version in Semantic Version 2.0 format.
|
// Specifies a version in Semantic Version 2.0 format.
|
||||||
// (http://semver.org/spec/v2.0.0.html)
|
// (http://semver.org/spec/v2.0.0.html)
|
||||||
message Version {
|
message Version {
|
||||||
uint32 major = 1; // This field is REQUIRED.
|
// The value of this field MUST NOT be negative.
|
||||||
uint32 minor = 2; // This field is REQUIRED.
|
int32 major = 1; // This field is REQUIRED.
|
||||||
uint32 patch = 3; // This field is REQUIRED.
|
// The value of this field MUST NOT be negative.
|
||||||
|
int32 minor = 2; // This field is REQUIRED.
|
||||||
|
// The value of this field MUST NOT be negative.
|
||||||
|
int32 patch = 3; // This field is REQUIRED.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -478,25 +481,25 @@ message CreateVolumeRequest {
|
|||||||
// validating these parameters. COs will treat these as opaque.
|
// validating these parameters. COs will treat these as opaque.
|
||||||
map<string, string> parameters = 5;
|
map<string, string> parameters = 5;
|
||||||
|
|
||||||
// End user credentials used to authenticate/authorize volume creation
|
// Credentials used by Controller plugin to authenticate/authorize
|
||||||
// request.
|
// volume creation request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
map<string, string> user_credentials = 6;
|
map<string, string> controller_create_credentials = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateVolumeResponse {
|
message CreateVolumeResponse {
|
||||||
// Contains all attributes of the newly created volume that are
|
// Contains all attributes of the newly created volume that are
|
||||||
// relevant to the CO along with information required by the Plugin
|
// relevant to the CO along with information required by the Plugin
|
||||||
// to uniquely identify the volume. This field is REQUIRED.
|
// to uniquely identify the volume. This field is REQUIRED.
|
||||||
VolumeInfo volume_info = 1;
|
Volume volume = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify a capability of a volume.
|
// Specify a capability of a volume.
|
||||||
@ -566,19 +569,22 @@ message VolumeCapability {
|
|||||||
message CapacityRange {
|
message CapacityRange {
|
||||||
// Volume must be at least this big. This field is OPTIONAL.
|
// Volume must be at least this big. This field is OPTIONAL.
|
||||||
// A value of 0 is equal to an unspecified field value.
|
// A value of 0 is equal to an unspecified field value.
|
||||||
uint64 required_bytes = 1;
|
// The value of this field MUST NOT be negative.
|
||||||
|
int64 required_bytes = 1;
|
||||||
|
|
||||||
// Volume must not be bigger than this. This field is OPTIONAL.
|
// Volume must not be bigger than this. This field is OPTIONAL.
|
||||||
// A value of 0 is equal to an unspecified field value.
|
// A value of 0 is equal to an unspecified field value.
|
||||||
uint64 limit_bytes = 2;
|
// The value of this field MUST NOT be negative.
|
||||||
|
int64 limit_bytes = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The information about a provisioned volume.
|
// The information about a provisioned volume.
|
||||||
message VolumeInfo {
|
message Volume {
|
||||||
// The capacity of the volume in bytes. This field is OPTIONAL. If not
|
// The capacity of the volume in bytes. This field is OPTIONAL. If not
|
||||||
// set (value of 0), it indicates that the capacity of the volume is
|
// set (value of 0), it indicates that the capacity of the volume is
|
||||||
// unknown (e.g., NFS share).
|
// unknown (e.g., NFS share).
|
||||||
uint64 capacity_bytes = 1;
|
// The value of this field MUST NOT be negative.
|
||||||
|
int64 capacity_bytes = 1;
|
||||||
|
|
||||||
// Contains identity information for the created volume. This field is
|
// Contains identity information for the created volume. This field is
|
||||||
// REQUIRED. The identity information will be used by the CO in
|
// REQUIRED. The identity information will be used by the CO in
|
||||||
@ -629,18 +635,18 @@ message DeleteVolumeRequest {
|
|||||||
// This field is REQUIRED.
|
// This field is REQUIRED.
|
||||||
string volume_id = 2;
|
string volume_id = 2;
|
||||||
|
|
||||||
// End user credentials used to authenticate/authorize volume deletion
|
// Credentials used by Controller plugin to authenticate/authorize
|
||||||
// request.
|
// volume deletion request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
map<string, string> user_credentials = 3;
|
map<string, string> controller_delete_credentials = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DeleteVolumeResponse {}
|
message DeleteVolumeResponse {}
|
||||||
@ -683,7 +689,7 @@ message ControllerPublishVolumeRequest {
|
|||||||
string volume_id = 2;
|
string volume_id = 2;
|
||||||
|
|
||||||
// The ID of the node. This field is REQUIRED. The CO SHALL set this
|
// The ID of the node. This field is REQUIRED. The CO SHALL set this
|
||||||
// field to match the node ID returned by `GetNodeID`.
|
// field to match the node ID returned by `NodeGetId`.
|
||||||
string node_id = 3;
|
string node_id = 3;
|
||||||
|
|
||||||
// The capability of the volume the CO expects the volume to have.
|
// The capability of the volume the CO expects the volume to have.
|
||||||
@ -694,21 +700,21 @@ message ControllerPublishVolumeRequest {
|
|||||||
// REQUIRED.
|
// REQUIRED.
|
||||||
bool readonly = 5;
|
bool readonly = 5;
|
||||||
|
|
||||||
// End user credentials used to authenticate/authorize controller
|
// Credentials used by Controller plugin to authenticate/authorize
|
||||||
// publish request.
|
// controller publish request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
map<string, string> user_credentials = 6;
|
map<string, string> controller_publish_credentials = 6;
|
||||||
|
|
||||||
// Attributes of the volume to be used on a node. This field is
|
// Attributes of the volume to be used on a node. This field is
|
||||||
// OPTIONAL and MUST match the attributes of the VolumeInfo identified
|
// OPTIONAL and MUST match the attributes of the Volume identified
|
||||||
// by `volume_id`.
|
// by `volume_id`.
|
||||||
map<string,string> volume_attributes = 7;
|
map<string,string> volume_attributes = 7;
|
||||||
}
|
}
|
||||||
@ -717,7 +723,7 @@ message ControllerPublishVolumeResponse {
|
|||||||
// The SP specific information that will be passed to the Plugin in
|
// The SP specific information that will be passed to the Plugin in
|
||||||
// the subsequent `NodePublishVolume` call for the given volume.
|
// the subsequent `NodePublishVolume` call for the given volume.
|
||||||
// This information is opaque to the CO. This field is OPTIONAL.
|
// This information is opaque to the CO. This field is OPTIONAL.
|
||||||
map<string, string> publish_volume_info = 1;
|
map<string, string> publish_info = 1;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -760,24 +766,24 @@ message ControllerUnpublishVolumeRequest {
|
|||||||
string volume_id = 2;
|
string volume_id = 2;
|
||||||
|
|
||||||
// The ID of the node. This field is OPTIONAL. The CO SHOULD set this
|
// The ID of the node. This field is OPTIONAL. The CO SHOULD set this
|
||||||
// field to match the node ID returned by `GetNodeID` or leave it
|
// field to match the node ID returned by `NodeGetId` or leave it
|
||||||
// unset. If the value is set, the SP MUST unpublish the volume from
|
// unset. If the value is set, the SP MUST unpublish the volume from
|
||||||
// the specified node. If the value is unset, the SP MUST unpublish
|
// the specified node. If the value is unset, the SP MUST unpublish
|
||||||
// the volume from all nodes it is published to.
|
// the volume from all nodes it is published to.
|
||||||
string node_id = 3;
|
string node_id = 3;
|
||||||
|
|
||||||
// End user credentials used to authenticate/authorize controller
|
// Credentials used by Controller plugin to authenticate/authorize
|
||||||
// unpublish request.
|
// controller unpublish request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
// '_' or '.'. Each value MUST contain a valid string. An SP MAY
|
||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
map<string, string> user_credentials = 4;
|
map<string, string> controller_unpublish_credentials = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ControllerUnpublishVolumeResponse {}
|
message ControllerUnpublishVolumeResponse {}
|
||||||
@ -818,7 +824,7 @@ message ValidateVolumeCapabilitiesRequest {
|
|||||||
repeated VolumeCapability volume_capabilities = 3;
|
repeated VolumeCapability volume_capabilities = 3;
|
||||||
|
|
||||||
// Attributes of the volume to check. This field is OPTIONAL and MUST
|
// Attributes of the volume to check. This field is OPTIONAL and MUST
|
||||||
// match the attributes of the VolumeInfo identified by `volume_id`.
|
// match the attributes of the Volume identified by `volume_id`.
|
||||||
map<string,string> volume_attributes = 4;
|
map<string,string> volume_attributes = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -862,7 +868,8 @@ message ListVolumesRequest {
|
|||||||
// in the subsequent `ListVolumes` call. This field is OPTIONAL. If
|
// in the subsequent `ListVolumes` call. This field is OPTIONAL. If
|
||||||
// not specified (zero value), it means there is no restriction on the
|
// not specified (zero value), it means there is no restriction on the
|
||||||
// number of entries that can be returned.
|
// number of entries that can be returned.
|
||||||
uint32 max_entries = 2;
|
// The value of this field MUST NOT be negative.
|
||||||
|
int32 max_entries = 2;
|
||||||
|
|
||||||
// A token to specify where to start paginating. Set this field to
|
// A token to specify where to start paginating. Set this field to
|
||||||
// `next_token` returned by a previous `ListVolumes` call to get the
|
// `next_token` returned by a previous `ListVolumes` call to get the
|
||||||
@ -873,7 +880,7 @@ message ListVolumesRequest {
|
|||||||
|
|
||||||
message ListVolumesResponse {
|
message ListVolumesResponse {
|
||||||
message Entry {
|
message Entry {
|
||||||
VolumeInfo volume_info = 1;
|
Volume volume = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
repeated Entry entries = 1;
|
repeated Entry entries = 1;
|
||||||
@ -929,7 +936,8 @@ message GetCapacityResponse {
|
|||||||
// specified in the request, the Plugin SHALL take those into
|
// specified in the request, the Plugin SHALL take those into
|
||||||
// consideration when calculating the available capacity of the
|
// consideration when calculating the available capacity of the
|
||||||
// storage. This field is REQUIRED.
|
// storage. This field is REQUIRED.
|
||||||
uint64 available_capacity = 1;
|
// The value of this field MUST NOT be negative.
|
||||||
|
int64 available_capacity = 1;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -1041,7 +1049,7 @@ The following table shows what the Plugin SHOULD return when receiving a second
|
|||||||
| MULTI_NODE | OK (idempotent) | ALREADY_EXISTS | OK | OK |
|
| MULTI_NODE | OK (idempotent) | ALREADY_EXISTS | OK | OK |
|
||||||
| Non MULTI_NODE | OK (idempotent) | ALREADY_EXISTS | FAILED_PRECONDITION | FAILED_PRECONDITION|
|
| Non MULTI_NODE | OK (idempotent) | ALREADY_EXISTS | FAILED_PRECONDITION | FAILED_PRECONDITION|
|
||||||
|
|
||||||
(`Tn`: target path of the n-th `NodePublishVolume`, `Pn`: other arguments of the n-th `NodePublishVolume` except `user_credentials`)
|
(`Tn`: target path of the n-th `NodePublishVolume`, `Pn`: other arguments of the n-th `NodePublishVolume` except `node_credentials`)
|
||||||
|
|
||||||
```protobuf
|
```protobuf
|
||||||
message NodePublishVolumeRequest {
|
message NodePublishVolumeRequest {
|
||||||
@ -1056,7 +1064,7 @@ message NodePublishVolumeRequest {
|
|||||||
// has `PUBLISH_UNPUBLISH_VOLUME` controller capability, and SHALL be
|
// has `PUBLISH_UNPUBLISH_VOLUME` controller capability, and SHALL be
|
||||||
// left unset if the corresponding Controller Plugin does not have
|
// left unset if the corresponding Controller Plugin does not have
|
||||||
// this capability. This is an OPTIONAL field.
|
// this capability. This is an OPTIONAL field.
|
||||||
map<string, string> publish_volume_info = 3;
|
map<string, string> publish_info = 3;
|
||||||
|
|
||||||
// The path to which the volume will be published. It MUST be an
|
// The path to which the volume will be published. It MUST be an
|
||||||
// absolute path in the root filesystem of the process serving this
|
// absolute path in the root filesystem of the process serving this
|
||||||
@ -1074,7 +1082,7 @@ message NodePublishVolumeRequest {
|
|||||||
// REQUIRED.
|
// REQUIRED.
|
||||||
bool readonly = 6;
|
bool readonly = 6;
|
||||||
|
|
||||||
// End user credentials used to authenticate/authorize node
|
// Credentials used by Node plugin to authenticate/authorize node
|
||||||
// publish request.
|
// publish request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
@ -1082,13 +1090,13 @@ message NodePublishVolumeRequest {
|
|||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
map<string, string> user_credentials = 7;
|
map<string, string> node_publish_credentials = 7;
|
||||||
|
|
||||||
// Attributes of the volume to publish. This field is OPTIONAL and
|
// Attributes of the volume to publish. This field is OPTIONAL and
|
||||||
// MUST match the attributes of the VolumeInfo identified by
|
// MUST match the attributes of the Volume identified by
|
||||||
// `volume_id`.
|
// `volume_id`.
|
||||||
map<string,string> volume_attributes = 8;
|
map<string,string> volume_attributes = 8;
|
||||||
}
|
}
|
||||||
@ -1137,7 +1145,7 @@ message NodeUnpublishVolumeRequest {
|
|||||||
// This is a REQUIRED field.
|
// This is a REQUIRED field.
|
||||||
string target_path = 3;
|
string target_path = 3;
|
||||||
|
|
||||||
// End user credentials used to authenticate/authorize node
|
// Credentials used by Node plugin to authenticate/authorize node
|
||||||
// unpublish request.
|
// unpublish request.
|
||||||
// This field contains credential data, for example username and
|
// This field contains credential data, for example username and
|
||||||
// password. Each key must consist of alphanumeric characters, '-',
|
// password. Each key must consist of alphanumeric characters, '-',
|
||||||
@ -1145,10 +1153,10 @@ message NodeUnpublishVolumeRequest {
|
|||||||
// choose to accept binary (non-string) data by using a binary-to-text
|
// choose to accept binary (non-string) data by using a binary-to-text
|
||||||
// encoding scheme, like base64. An SP SHALL advertise the
|
// encoding scheme, like base64. An SP SHALL advertise the
|
||||||
// requirements for credentials in documentation. COs SHALL permit
|
// requirements for credentials in documentation. COs SHALL permit
|
||||||
// users to pass through the required credentials. This information is
|
// passing through the required credentials. This information is
|
||||||
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
// sensitive and MUST be treated as such (not logged, etc.) by the CO.
|
||||||
// This field is OPTIONAL.
|
// This field is OPTIONAL.
|
||||||
map<string, string> user_credentials = 4;
|
map<string, string> node_unpublish_credentials = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message NodeUnpublishVolumeResponse {}
|
message NodeUnpublishVolumeResponse {}
|
||||||
@ -1166,7 +1174,7 @@ The CO MUST implement the specified error recovery behavior when it encounters t
|
|||||||
| Operation pending for volume | 10 ABORTED | Indicates that there is a already an operation pending for the specified volume. In general the Cluster Orchestrator (CO) is responsible for ensuring that there is no more than one call "in-flight" per volume at a given time. However, in some circumstances, the CO MAY lose state (for example when the CO crashes and restarts), and MAY issue multiple calls simultaneously for the same volume. The Plugin, SHOULD handle this as gracefully as possible, and MAY return this error code to reject secondary calls. | Caller SHOULD ensure that there are no other calls pending for the specified volume, and then retry with exponential back off. |
|
| Operation pending for volume | 10 ABORTED | Indicates that there is a already an operation pending for the specified volume. In general the Cluster Orchestrator (CO) is responsible for ensuring that there is no more than one call "in-flight" per volume at a given time. However, in some circumstances, the CO MAY lose state (for example when the CO crashes and restarts), and MAY issue multiple calls simultaneously for the same volume. The Plugin, SHOULD handle this as gracefully as possible, and MAY return this error code to reject secondary calls. | Caller SHOULD ensure that there are no other calls pending for the specified volume, and then retry with exponential back off. |
|
||||||
|
|
||||||
|
|
||||||
#### `GetNodeID`
|
#### `NodeGetId`
|
||||||
|
|
||||||
A Node Plugin MUST implement this RPC call if the plugin has `PUBLISH_UNPUBLISH_VOLUME` controller capability.
|
A Node Plugin MUST implement this RPC call if the plugin has `PUBLISH_UNPUBLISH_VOLUME` controller capability.
|
||||||
The Plugin SHALL assume that this RPC will be executed on the node where the volume will be used.
|
The Plugin SHALL assume that this RPC will be executed on the node where the volume will be used.
|
||||||
@ -1174,12 +1182,12 @@ The CO SHOULD call this RPC for the node at which it wants to place the workload
|
|||||||
The result of this call will be used by CO in `ControllerPublishVolume`.
|
The result of this call will be used by CO in `ControllerPublishVolume`.
|
||||||
|
|
||||||
```protobuf
|
```protobuf
|
||||||
message GetNodeIDRequest {
|
message NodeGetIdRequest {
|
||||||
// The API version assumed by the CO. This is a REQUIRED field.
|
// The API version assumed by the CO. This is a REQUIRED field.
|
||||||
Version version = 1;
|
Version version = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetNodeIDResponse {
|
message NodeGetIdResponse {
|
||||||
// The ID of the node as understood by the SP which SHALL be used by
|
// The ID of the node as understood by the SP which SHALL be used by
|
||||||
// CO in subsequent `ControllerPublishVolume`.
|
// CO in subsequent `ControllerPublishVolume`.
|
||||||
// This is a REQUIRED field.
|
// This is a REQUIRED field.
|
||||||
@ -1187,15 +1195,15 @@ message GetNodeIDResponse {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
##### GetNodeID Errors
|
##### NodeGetId Errors
|
||||||
|
|
||||||
If the plugin is unable to complete the GetNodeID call successfully, it MUST return a non-ok gRPC code in the gRPC status.
|
If the plugin is unable to complete the NodeGetId call successfully, it MUST return a non-ok gRPC code in the gRPC status.
|
||||||
If the conditions defined below are encountered, the plugin MUST return the specified gRPC error code.
|
If the conditions defined below are encountered, the plugin MUST return the specified gRPC error code.
|
||||||
The CO MUST implement the specified error recovery behavior when it encounters the gRPC error code.
|
The CO MUST implement the specified error recovery behavior when it encounters the gRPC error code.
|
||||||
|
|
||||||
Condition | gRPC Code | Description | Recovery Behavior
|
Condition | gRPC Code | Description | Recovery Behavior
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| Call not implemented | 12 UNIMPLEMENTED | GetNodeID call is not implemented by the plugin or disabled in the Plugin's current mode of operation. | Caller MUST NOT retry. Caller MAY call `ControllerGetCapabilities` or `NodeGetCapabilities` to discover Plugin capabilities. |
|
| Call not implemented | 12 UNIMPLEMENTED | NodeGetId call is not implemented by the plugin or disabled in the Plugin's current mode of operation. | Caller MUST NOT retry. Caller MAY call `ControllerGetCapabilities` or `NodeGetCapabilities` to discover Plugin capabilities. |
|
||||||
|
|
||||||
#### `NodeProbe`
|
#### `NodeProbe`
|
||||||
|
|
||||||
|
70
vendor/github.com/emicklei/go-restful/.gitignore
generated
vendored
70
vendor/github.com/emicklei/go-restful/.gitignore
generated
vendored
@ -1,70 +0,0 @@
|
|||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
|
||||||
|
|
||||||
restful.html
|
|
||||||
|
|
||||||
*.out
|
|
||||||
|
|
||||||
tmp.prof
|
|
||||||
|
|
||||||
go-restful.test
|
|
||||||
|
|
||||||
examples/restful-basic-authentication
|
|
||||||
|
|
||||||
examples/restful-encoding-filter
|
|
||||||
|
|
||||||
examples/restful-filters
|
|
||||||
|
|
||||||
examples/restful-hello-world
|
|
||||||
|
|
||||||
examples/restful-resource-functions
|
|
||||||
|
|
||||||
examples/restful-serve-static
|
|
||||||
|
|
||||||
examples/restful-user-service
|
|
||||||
|
|
||||||
*.DS_Store
|
|
||||||
examples/restful-user-resource
|
|
||||||
|
|
||||||
examples/restful-multi-containers
|
|
||||||
|
|
||||||
examples/restful-form-handling
|
|
||||||
|
|
||||||
examples/restful-CORS-filter
|
|
||||||
|
|
||||||
examples/restful-options-filter
|
|
||||||
|
|
||||||
examples/restful-curly-router
|
|
||||||
|
|
||||||
examples/restful-cpuprofiler-service
|
|
||||||
|
|
||||||
examples/restful-pre-post-filters
|
|
||||||
|
|
||||||
curly.prof
|
|
||||||
|
|
||||||
examples/restful-NCSA-logging
|
|
||||||
|
|
||||||
examples/restful-html-template
|
|
||||||
|
|
||||||
s.html
|
|
||||||
restful-path-tail
|
|
6
vendor/github.com/emicklei/go-restful/.travis.yml
generated
vendored
6
vendor/github.com/emicklei/go-restful/.travis.yml
generated
vendored
@ -1,6 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.x
|
|
||||||
|
|
||||||
script: go test -v
|
|
226
vendor/github.com/emicklei/go-restful/CHANGES.md
generated
vendored
226
vendor/github.com/emicklei/go-restful/CHANGES.md
generated
vendored
@ -1,226 +0,0 @@
|
|||||||
Change history of go-restful
|
|
||||||
=
|
|
||||||
2017-09-13
|
|
||||||
- added route condition functions using `.If(func)` in route building.
|
|
||||||
|
|
||||||
2017-02-16
|
|
||||||
- solved issue #304, make operation names unique
|
|
||||||
|
|
||||||
2017-01-30
|
|
||||||
|
|
||||||
[IMPORTANT] For swagger users, change your import statement to:
|
|
||||||
swagger "github.com/emicklei/go-restful-swagger12"
|
|
||||||
|
|
||||||
- moved swagger 1.2 code to go-restful-swagger12
|
|
||||||
- created TAG 2.0.0
|
|
||||||
|
|
||||||
2017-01-27
|
|
||||||
|
|
||||||
- remove defer request body close
|
|
||||||
- expose Dispatch for testing filters and Routefunctions
|
|
||||||
- swagger response model cannot be array
|
|
||||||
- created TAG 1.0.0
|
|
||||||
|
|
||||||
2016-12-22
|
|
||||||
|
|
||||||
- (API change) Remove code related to caching request content. Removes SetCacheReadEntity(doCache bool)
|
|
||||||
|
|
||||||
2016-11-26
|
|
||||||
|
|
||||||
- Default change! now use CurlyRouter (was RouterJSR311)
|
|
||||||
- Default change! no more caching of request content
|
|
||||||
- Default change! do not recover from panics
|
|
||||||
|
|
||||||
2016-09-22
|
|
||||||
|
|
||||||
- fix the DefaultRequestContentType feature
|
|
||||||
|
|
||||||
2016-02-14
|
|
||||||
|
|
||||||
- take the qualify factor of the Accept header mediatype into account when deciding the contentype of the response
|
|
||||||
- add constructors for custom entity accessors for xml and json
|
|
||||||
|
|
||||||
2015-09-27
|
|
||||||
|
|
||||||
- rename new WriteStatusAnd... to WriteHeaderAnd... for consistency
|
|
||||||
|
|
||||||
2015-09-25
|
|
||||||
|
|
||||||
- fixed problem with changing Header after WriteHeader (issue 235)
|
|
||||||
|
|
||||||
2015-09-14
|
|
||||||
|
|
||||||
- changed behavior of WriteHeader (immediate write) and WriteEntity (no status write)
|
|
||||||
- added support for custom EntityReaderWriters.
|
|
||||||
|
|
||||||
2015-08-06
|
|
||||||
|
|
||||||
- add support for reading entities from compressed request content
|
|
||||||
- use sync.Pool for compressors of http response and request body
|
|
||||||
- add Description to Parameter for documentation in Swagger UI
|
|
||||||
|
|
||||||
2015-03-20
|
|
||||||
|
|
||||||
- add configurable logging
|
|
||||||
|
|
||||||
2015-03-18
|
|
||||||
|
|
||||||
- if not specified, the Operation is derived from the Route function
|
|
||||||
|
|
||||||
2015-03-17
|
|
||||||
|
|
||||||
- expose Parameter creation functions
|
|
||||||
- make trace logger an interface
|
|
||||||
- fix OPTIONSFilter
|
|
||||||
- customize rendering of ServiceError
|
|
||||||
- JSR311 router now handles wildcards
|
|
||||||
- add Notes to Route
|
|
||||||
|
|
||||||
2014-11-27
|
|
||||||
|
|
||||||
- (api add) PrettyPrint per response. (as proposed in #167)
|
|
||||||
|
|
||||||
2014-11-12
|
|
||||||
|
|
||||||
- (api add) ApiVersion(.) for documentation in Swagger UI
|
|
||||||
|
|
||||||
2014-11-10
|
|
||||||
|
|
||||||
- (api change) struct fields tagged with "description" show up in Swagger UI
|
|
||||||
|
|
||||||
2014-10-31
|
|
||||||
|
|
||||||
- (api change) ReturnsError -> Returns
|
|
||||||
- (api add) RouteBuilder.Do(aBuilder) for DRY use of RouteBuilder
|
|
||||||
- fix swagger nested structs
|
|
||||||
- sort Swagger response messages by code
|
|
||||||
|
|
||||||
2014-10-23
|
|
||||||
|
|
||||||
- (api add) ReturnsError allows you to document Http codes in swagger
|
|
||||||
- fixed problem with greedy CurlyRouter
|
|
||||||
- (api add) Access-Control-Max-Age in CORS
|
|
||||||
- add tracing functionality (injectable) for debugging purposes
|
|
||||||
- support JSON parse 64bit int
|
|
||||||
- fix empty parameters for swagger
|
|
||||||
- WebServicesUrl is now optional for swagger
|
|
||||||
- fixed duplicate AccessControlAllowOrigin in CORS
|
|
||||||
- (api change) expose ServeMux in container
|
|
||||||
- (api add) added AllowedDomains in CORS
|
|
||||||
- (api add) ParameterNamed for detailed documentation
|
|
||||||
|
|
||||||
2014-04-16
|
|
||||||
|
|
||||||
- (api add) expose constructor of Request for testing.
|
|
||||||
|
|
||||||
2014-06-27
|
|
||||||
|
|
||||||
- (api add) ParameterNamed gives access to a Parameter definition and its data (for further specification).
|
|
||||||
- (api add) SetCacheReadEntity allow scontrol over whether or not the request body is being cached (default true for compatibility reasons).
|
|
||||||
|
|
||||||
2014-07-03
|
|
||||||
|
|
||||||
- (api add) CORS can be configured with a list of allowed domains
|
|
||||||
|
|
||||||
2014-03-12
|
|
||||||
|
|
||||||
- (api add) Route path parameters can use wildcard or regular expressions. (requires CurlyRouter)
|
|
||||||
|
|
||||||
2014-02-26
|
|
||||||
|
|
||||||
- (api add) Request now provides information about the matched Route, see method SelectedRoutePath
|
|
||||||
|
|
||||||
2014-02-17
|
|
||||||
|
|
||||||
- (api change) renamed parameter constants (go-lint checks)
|
|
||||||
|
|
||||||
2014-01-10
|
|
||||||
|
|
||||||
- (api add) support for CloseNotify, see http://golang.org/pkg/net/http/#CloseNotifier
|
|
||||||
|
|
||||||
2014-01-07
|
|
||||||
|
|
||||||
- (api change) Write* methods in Response now return the error or nil.
|
|
||||||
- added example of serving HTML from a Go template.
|
|
||||||
- fixed comparing Allowed headers in CORS (is now case-insensitive)
|
|
||||||
|
|
||||||
2013-11-13
|
|
||||||
|
|
||||||
- (api add) Response knows how many bytes are written to the response body.
|
|
||||||
|
|
||||||
2013-10-29
|
|
||||||
|
|
||||||
- (api add) RecoverHandler(handler RecoverHandleFunction) to change how panic recovery is handled. Default behavior is to log and return a stacktrace. This may be a security issue as it exposes sourcecode information.
|
|
||||||
|
|
||||||
2013-10-04
|
|
||||||
|
|
||||||
- (api add) Response knows what HTTP status has been written
|
|
||||||
- (api add) Request can have attributes (map of string->interface, also called request-scoped variables
|
|
||||||
|
|
||||||
2013-09-12
|
|
||||||
|
|
||||||
- (api change) Router interface simplified
|
|
||||||
- Implemented CurlyRouter, a Router that does not use|allow regular expressions in paths
|
|
||||||
|
|
||||||
2013-08-05
|
|
||||||
- add OPTIONS support
|
|
||||||
- add CORS support
|
|
||||||
|
|
||||||
2013-08-27
|
|
||||||
|
|
||||||
- fixed some reported issues (see github)
|
|
||||||
- (api change) deprecated use of WriteError; use WriteErrorString instead
|
|
||||||
|
|
||||||
2014-04-15
|
|
||||||
|
|
||||||
- (fix) v1.0.1 tag: fix Issue 111: WriteErrorString
|
|
||||||
|
|
||||||
2013-08-08
|
|
||||||
|
|
||||||
- (api add) Added implementation Container: a WebServices collection with its own http.ServeMux allowing multiple endpoints per program. Existing uses of go-restful will register their services to the DefaultContainer.
|
|
||||||
- (api add) the swagger package has be extended to have a UI per container.
|
|
||||||
- if panic is detected then a small stack trace is printed (thanks to runner-mei)
|
|
||||||
- (api add) WriteErrorString to Response
|
|
||||||
|
|
||||||
Important API changes:
|
|
||||||
|
|
||||||
- (api remove) package variable DoNotRecover no longer works ; use restful.DefaultContainer.DoNotRecover(true) instead.
|
|
||||||
- (api remove) package variable EnableContentEncoding no longer works ; use restful.DefaultContainer.EnableContentEncoding(true) instead.
|
|
||||||
|
|
||||||
|
|
||||||
2013-07-06
|
|
||||||
|
|
||||||
- (api add) Added support for response encoding (gzip and deflate(zlib)). This feature is disabled on default (for backwards compatibility). Use restful.EnableContentEncoding = true in your initialization to enable this feature.
|
|
||||||
|
|
||||||
2013-06-19
|
|
||||||
|
|
||||||
- (improve) DoNotRecover option, moved request body closer, improved ReadEntity
|
|
||||||
|
|
||||||
2013-06-03
|
|
||||||
|
|
||||||
- (api change) removed Dispatcher interface, hide PathExpression
|
|
||||||
- changed receiver names of type functions to be more idiomatic Go
|
|
||||||
|
|
||||||
2013-06-02
|
|
||||||
|
|
||||||
- (optimize) Cache the RegExp compilation of Paths.
|
|
||||||
|
|
||||||
2013-05-22
|
|
||||||
|
|
||||||
- (api add) Added support for request/response filter functions
|
|
||||||
|
|
||||||
2013-05-18
|
|
||||||
|
|
||||||
|
|
||||||
- (api add) Added feature to change the default Http Request Dispatch function (travis cline)
|
|
||||||
- (api change) Moved Swagger Webservice to swagger package (see example restful-user)
|
|
||||||
|
|
||||||
[2012-11-14 .. 2013-05-18>
|
|
||||||
|
|
||||||
- See https://github.com/emicklei/go-restful/commits
|
|
||||||
|
|
||||||
2012-11-14
|
|
||||||
|
|
||||||
- Initial commit
|
|
||||||
|
|
||||||
|
|
22
vendor/github.com/emicklei/go-restful/LICENSE
generated
vendored
22
vendor/github.com/emicklei/go-restful/LICENSE
generated
vendored
@ -1,22 +0,0 @@
|
|||||||
Copyright (c) 2012,2013 Ernest Micklei
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
7
vendor/github.com/emicklei/go-restful/Makefile
generated
vendored
7
vendor/github.com/emicklei/go-restful/Makefile
generated
vendored
@ -1,7 +0,0 @@
|
|||||||
all: test
|
|
||||||
|
|
||||||
test:
|
|
||||||
go test -v .
|
|
||||||
|
|
||||||
ex:
|
|
||||||
cd examples && ls *.go | xargs go build -o /tmp/ignore
|
|
75
vendor/github.com/emicklei/go-restful/README.md
generated
vendored
75
vendor/github.com/emicklei/go-restful/README.md
generated
vendored
@ -1,75 +0,0 @@
|
|||||||
go-restful
|
|
||||||
==========
|
|
||||||
package for building REST-style Web Services using Google Go
|
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/emicklei/go-restful.png)](https://travis-ci.org/emicklei/go-restful)
|
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/emicklei/go-restful)](https://goreportcard.com/report/github.com/emicklei/go-restful)
|
|
||||||
[![GoDoc](https://godoc.org/github.com/emicklei/go-restful?status.svg)](https://godoc.org/github.com/emicklei/go-restful)
|
|
||||||
|
|
||||||
- [Code examples](https://github.com/emicklei/go-restful/tree/master/examples)
|
|
||||||
|
|
||||||
REST asks developers to use HTTP methods explicitly and in a way that's consistent with the protocol definition. This basic REST design principle establishes a one-to-one mapping between create, read, update, and delete (CRUD) operations and HTTP methods. According to this mapping:
|
|
||||||
|
|
||||||
- GET = Retrieve a representation of a resource
|
|
||||||
- POST = Create if you are sending content to the server to create a subordinate of the specified resource collection, using some server-side algorithm.
|
|
||||||
- PUT = Create if you are sending the full content of the specified resource (URI).
|
|
||||||
- PUT = Update if you are updating the full content of the specified resource.
|
|
||||||
- DELETE = Delete if you are requesting the server to delete the resource
|
|
||||||
- PATCH = Update partial content of a resource
|
|
||||||
- OPTIONS = Get information about the communication options for the request URI
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```Go
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.
|
|
||||||
Path("/users").
|
|
||||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
|
||||||
Produces(restful.MIME_JSON, restful.MIME_XML)
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/{user-id}").To(u.findUser).
|
|
||||||
Doc("get a user").
|
|
||||||
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
|
|
||||||
Writes(User{}))
|
|
||||||
...
|
|
||||||
|
|
||||||
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
|
|
||||||
id := request.PathParameter("user-id")
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[Full API of a UserResource](https://github.com/emicklei/go-restful/tree/master/examples/restful-user-resource.go)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Routes for request → function mapping with path parameter (e.g. {id}) support
|
|
||||||
- Configurable router:
|
|
||||||
- (default) Fast routing algorithm that allows static elements, regular expressions and dynamic parameters in the URL path (e.g. /meetings/{id} or /static/{subpath:*}
|
|
||||||
- Routing algorithm after [JSR311](http://jsr311.java.net/nonav/releases/1.1/spec/spec.html) that is implemented using (but does **not** accept) regular expressions
|
|
||||||
- Request API for reading structs from JSON/XML and accesing parameters (path,query,header)
|
|
||||||
- Response API for writing structs to JSON/XML and setting headers
|
|
||||||
- Customizable encoding using EntityReaderWriter registration
|
|
||||||
- Filters for intercepting the request → response flow on Service or Route level
|
|
||||||
- Request-scoped variables using attributes
|
|
||||||
- Containers for WebServices on different HTTP endpoints
|
|
||||||
- Content encoding (gzip,deflate) of request and response payloads
|
|
||||||
- Automatic responses on OPTIONS (using a filter)
|
|
||||||
- Automatic CORS request handling (using a filter)
|
|
||||||
- API declaration for Swagger UI ([go-restful-openapi](https://github.com/emicklei/go-restful-openapi), see [go-restful-swagger12](https://github.com/emicklei/go-restful-swagger12))
|
|
||||||
- Panic recovery to produce HTTP 500, customizable using RecoverHandler(...)
|
|
||||||
- Route errors produce HTTP 404/405/406/415 errors, customizable using ServiceErrorHandler(...)
|
|
||||||
- Configurable (trace) logging
|
|
||||||
- Customizable gzip/deflate readers and writers using CompressorProvider registration
|
|
||||||
|
|
||||||
### Resources
|
|
||||||
|
|
||||||
- [Example posted on blog](http://ernestmicklei.com/2012/11/go-restful-first-working-example/)
|
|
||||||
- [Design explained on blog](http://ernestmicklei.com/2012/11/go-restful-api-design/)
|
|
||||||
- [sourcegraph](https://sourcegraph.com/github.com/emicklei/go-restful)
|
|
||||||
- [showcase: Zazkia - tcp proxy for testing resiliency](https://github.com/emicklei/zazkia)
|
|
||||||
- [showcase: Mora - MongoDB REST Api server](https://github.com/emicklei/mora)
|
|
||||||
|
|
||||||
Type ```git shortlog -s``` for a full list of contributors.
|
|
||||||
|
|
||||||
© 2012 - 2017, http://ernestmicklei.com. MIT License. Contributions are welcome.
|
|
1
vendor/github.com/emicklei/go-restful/Srcfile
generated
vendored
1
vendor/github.com/emicklei/go-restful/Srcfile
generated
vendored
@ -1 +0,0 @@
|
|||||||
{"SkipDirs": ["examples"]}
|
|
51
vendor/github.com/emicklei/go-restful/bench_curly_test.go
generated
vendored
51
vendor/github.com/emicklei/go-restful/bench_curly_test.go
generated
vendored
@ -1,51 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setupCurly(container *Container) []string {
|
|
||||||
wsCount := 26
|
|
||||||
rtCount := 26
|
|
||||||
urisCurly := []string{}
|
|
||||||
|
|
||||||
container.Router(CurlyRouter{})
|
|
||||||
for i := 0; i < wsCount; i++ {
|
|
||||||
root := fmt.Sprintf("/%s/{%s}/", string(i+97), string(i+97))
|
|
||||||
ws := new(WebService).Path(root)
|
|
||||||
for j := 0; j < rtCount; j++ {
|
|
||||||
sub := fmt.Sprintf("/%s2/{%s2}", string(j+97), string(j+97))
|
|
||||||
ws.Route(ws.GET(sub).Consumes("application/xml").Produces("application/xml").To(echoCurly))
|
|
||||||
}
|
|
||||||
container.Add(ws)
|
|
||||||
for _, each := range ws.Routes() {
|
|
||||||
urisCurly = append(urisCurly, "http://bench.com"+each.Path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return urisCurly
|
|
||||||
}
|
|
||||||
|
|
||||||
func echoCurly(req *Request, resp *Response) {}
|
|
||||||
|
|
||||||
func BenchmarkManyCurly(b *testing.B) {
|
|
||||||
container := NewContainer()
|
|
||||||
urisCurly := setupCurly(container)
|
|
||||||
b.ResetTimer()
|
|
||||||
for t := 0; t < b.N; t++ {
|
|
||||||
for r := 0; r < 1000; r++ {
|
|
||||||
for _, each := range urisCurly {
|
|
||||||
sendNoReturnTo(each, container, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendNoReturnTo(address string, container *Container, t int) {
|
|
||||||
httpRequest, _ := http.NewRequest("GET", address, nil)
|
|
||||||
httpRequest.Header.Set("Accept", "application/xml")
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
container.dispatch(httpWriter, httpRequest)
|
|
||||||
}
|
|
43
vendor/github.com/emicklei/go-restful/bench_test.go
generated
vendored
43
vendor/github.com/emicklei/go-restful/bench_test.go
generated
vendored
@ -1,43 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var uris = []string{}
|
|
||||||
|
|
||||||
func setup(container *Container) {
|
|
||||||
wsCount := 26
|
|
||||||
rtCount := 26
|
|
||||||
|
|
||||||
for i := 0; i < wsCount; i++ {
|
|
||||||
root := fmt.Sprintf("/%s/{%s}/", string(i+97), string(i+97))
|
|
||||||
ws := new(WebService).Path(root)
|
|
||||||
for j := 0; j < rtCount; j++ {
|
|
||||||
sub := fmt.Sprintf("/%s2/{%s2}", string(j+97), string(j+97))
|
|
||||||
ws.Route(ws.GET(sub).To(echo))
|
|
||||||
}
|
|
||||||
container.Add(ws)
|
|
||||||
for _, each := range ws.Routes() {
|
|
||||||
uris = append(uris, "http://bench.com"+each.Path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func echo(req *Request, resp *Response) {
|
|
||||||
io.WriteString(resp.ResponseWriter, "echo")
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkMany(b *testing.B) {
|
|
||||||
container := NewContainer()
|
|
||||||
setup(container)
|
|
||||||
b.ResetTimer()
|
|
||||||
for t := 0; t < b.N; t++ {
|
|
||||||
for _, each := range uris {
|
|
||||||
// println(each)
|
|
||||||
sendItTo(each, container)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
10
vendor/github.com/emicklei/go-restful/bench_test.sh
generated
vendored
10
vendor/github.com/emicklei/go-restful/bench_test.sh
generated
vendored
@ -1,10 +0,0 @@
|
|||||||
#go test -run=none -file bench_test.go -test.bench . -cpuprofile=bench_test.out
|
|
||||||
|
|
||||||
go test -c
|
|
||||||
./go-restful.test -test.run=none -test.cpuprofile=tmp.prof -test.bench=BenchmarkMany
|
|
||||||
./go-restful.test -test.run=none -test.cpuprofile=curly.prof -test.bench=BenchmarkManyCurly
|
|
||||||
|
|
||||||
#go tool pprof go-restful.test tmp.prof
|
|
||||||
go tool pprof go-restful.test curly.prof
|
|
||||||
|
|
||||||
|
|
123
vendor/github.com/emicklei/go-restful/compress.go
generated
vendored
123
vendor/github.com/emicklei/go-restful/compress.go
generated
vendored
@ -1,123 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"compress/gzip"
|
|
||||||
"compress/zlib"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OBSOLETE : use restful.DefaultContainer.EnableContentEncoding(true) to change this setting.
|
|
||||||
var EnableContentEncoding = false
|
|
||||||
|
|
||||||
// CompressingResponseWriter is a http.ResponseWriter that can perform content encoding (gzip and zlib)
|
|
||||||
type CompressingResponseWriter struct {
|
|
||||||
writer http.ResponseWriter
|
|
||||||
compressor io.WriteCloser
|
|
||||||
encoding string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header is part of http.ResponseWriter interface
|
|
||||||
func (c *CompressingResponseWriter) Header() http.Header {
|
|
||||||
return c.writer.Header()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeader is part of http.ResponseWriter interface
|
|
||||||
func (c *CompressingResponseWriter) WriteHeader(status int) {
|
|
||||||
c.writer.WriteHeader(status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write is part of http.ResponseWriter interface
|
|
||||||
// It is passed through the compressor
|
|
||||||
func (c *CompressingResponseWriter) Write(bytes []byte) (int, error) {
|
|
||||||
if c.isCompressorClosed() {
|
|
||||||
return -1, errors.New("Compressing error: tried to write data using closed compressor")
|
|
||||||
}
|
|
||||||
return c.compressor.Write(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseNotify is part of http.CloseNotifier interface
|
|
||||||
func (c *CompressingResponseWriter) CloseNotify() <-chan bool {
|
|
||||||
return c.writer.(http.CloseNotifier).CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the underlying compressor
|
|
||||||
func (c *CompressingResponseWriter) Close() error {
|
|
||||||
if c.isCompressorClosed() {
|
|
||||||
return errors.New("Compressing error: tried to close already closed compressor")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.compressor.Close()
|
|
||||||
if ENCODING_GZIP == c.encoding {
|
|
||||||
currentCompressorProvider.ReleaseGzipWriter(c.compressor.(*gzip.Writer))
|
|
||||||
}
|
|
||||||
if ENCODING_DEFLATE == c.encoding {
|
|
||||||
currentCompressorProvider.ReleaseZlibWriter(c.compressor.(*zlib.Writer))
|
|
||||||
}
|
|
||||||
// gc hint needed?
|
|
||||||
c.compressor = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CompressingResponseWriter) isCompressorClosed() bool {
|
|
||||||
return nil == c.compressor
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hijack implements the Hijacker interface
|
|
||||||
// This is especially useful when combining Container.EnabledContentEncoding
|
|
||||||
// in combination with websockets (for instance gorilla/websocket)
|
|
||||||
func (c *CompressingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
||||||
hijacker, ok := c.writer.(http.Hijacker)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, errors.New("ResponseWriter doesn't support Hijacker interface")
|
|
||||||
}
|
|
||||||
return hijacker.Hijack()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WantsCompressedResponse reads the Accept-Encoding header to see if and which encoding is requested.
|
|
||||||
func wantsCompressedResponse(httpRequest *http.Request) (bool, string) {
|
|
||||||
header := httpRequest.Header.Get(HEADER_AcceptEncoding)
|
|
||||||
gi := strings.Index(header, ENCODING_GZIP)
|
|
||||||
zi := strings.Index(header, ENCODING_DEFLATE)
|
|
||||||
// use in order of appearance
|
|
||||||
if gi == -1 {
|
|
||||||
return zi != -1, ENCODING_DEFLATE
|
|
||||||
} else if zi == -1 {
|
|
||||||
return gi != -1, ENCODING_GZIP
|
|
||||||
} else {
|
|
||||||
if gi < zi {
|
|
||||||
return true, ENCODING_GZIP
|
|
||||||
}
|
|
||||||
return true, ENCODING_DEFLATE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCompressingResponseWriter create a CompressingResponseWriter for a known encoding = {gzip,deflate}
|
|
||||||
func NewCompressingResponseWriter(httpWriter http.ResponseWriter, encoding string) (*CompressingResponseWriter, error) {
|
|
||||||
httpWriter.Header().Set(HEADER_ContentEncoding, encoding)
|
|
||||||
c := new(CompressingResponseWriter)
|
|
||||||
c.writer = httpWriter
|
|
||||||
var err error
|
|
||||||
if ENCODING_GZIP == encoding {
|
|
||||||
w := currentCompressorProvider.AcquireGzipWriter()
|
|
||||||
w.Reset(httpWriter)
|
|
||||||
c.compressor = w
|
|
||||||
c.encoding = ENCODING_GZIP
|
|
||||||
} else if ENCODING_DEFLATE == encoding {
|
|
||||||
w := currentCompressorProvider.AcquireZlibWriter()
|
|
||||||
w.Reset(httpWriter)
|
|
||||||
c.compressor = w
|
|
||||||
c.encoding = ENCODING_DEFLATE
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("Unknown encoding:" + encoding)
|
|
||||||
}
|
|
||||||
return c, err
|
|
||||||
}
|
|
125
vendor/github.com/emicklei/go-restful/compress_test.go
generated
vendored
125
vendor/github.com/emicklei/go-restful/compress_test.go
generated
vendored
@ -1,125 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"compress/zlib"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// go test -v -test.run TestGzip ...restful
|
|
||||||
func TestGzip(t *testing.T) {
|
|
||||||
EnableContentEncoding = true
|
|
||||||
httpRequest, _ := http.NewRequest("GET", "/test", nil)
|
|
||||||
httpRequest.Header.Set("Accept-Encoding", "gzip,deflate")
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
wanted, encoding := wantsCompressedResponse(httpRequest)
|
|
||||||
if !wanted {
|
|
||||||
t.Fatal("should accept gzip")
|
|
||||||
}
|
|
||||||
if encoding != "gzip" {
|
|
||||||
t.Fatal("expected gzip")
|
|
||||||
}
|
|
||||||
c, err := NewCompressingResponseWriter(httpWriter, encoding)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
c.Write([]byte("Hello World"))
|
|
||||||
c.Close()
|
|
||||||
if httpWriter.Header().Get("Content-Encoding") != "gzip" {
|
|
||||||
t.Fatal("Missing gzip header")
|
|
||||||
}
|
|
||||||
reader, err := gzip.NewReader(httpWriter.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
data, err := ioutil.ReadAll(reader)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
if got, want := string(data), "Hello World"; got != want {
|
|
||||||
t.Errorf("got %v want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeflate(t *testing.T) {
|
|
||||||
EnableContentEncoding = true
|
|
||||||
httpRequest, _ := http.NewRequest("GET", "/test", nil)
|
|
||||||
httpRequest.Header.Set("Accept-Encoding", "deflate,gzip")
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
wanted, encoding := wantsCompressedResponse(httpRequest)
|
|
||||||
if !wanted {
|
|
||||||
t.Fatal("should accept deflate")
|
|
||||||
}
|
|
||||||
if encoding != "deflate" {
|
|
||||||
t.Fatal("expected deflate")
|
|
||||||
}
|
|
||||||
c, err := NewCompressingResponseWriter(httpWriter, encoding)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
c.Write([]byte("Hello World"))
|
|
||||||
c.Close()
|
|
||||||
if httpWriter.Header().Get("Content-Encoding") != "deflate" {
|
|
||||||
t.Fatal("Missing deflate header")
|
|
||||||
}
|
|
||||||
reader, err := zlib.NewReader(httpWriter.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
data, err := ioutil.ReadAll(reader)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
if got, want := string(data), "Hello World"; got != want {
|
|
||||||
t.Errorf("got %v want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGzipDecompressRequestBody(t *testing.T) {
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
w := newGzipWriter()
|
|
||||||
w.Reset(b)
|
|
||||||
io.WriteString(w, `{"msg":"hi"}`)
|
|
||||||
w.Flush()
|
|
||||||
w.Close()
|
|
||||||
|
|
||||||
req := new(Request)
|
|
||||||
httpRequest, _ := http.NewRequest("GET", "/", bytes.NewReader(b.Bytes()))
|
|
||||||
httpRequest.Header.Set("Content-Type", "application/json")
|
|
||||||
httpRequest.Header.Set("Content-Encoding", "gzip")
|
|
||||||
req.Request = httpRequest
|
|
||||||
|
|
||||||
doc := make(map[string]interface{})
|
|
||||||
req.ReadEntity(&doc)
|
|
||||||
|
|
||||||
if got, want := doc["msg"], "hi"; got != want {
|
|
||||||
t.Errorf("got %v want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestZlibDecompressRequestBody(t *testing.T) {
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
w := newZlibWriter()
|
|
||||||
w.Reset(b)
|
|
||||||
io.WriteString(w, `{"msg":"hi"}`)
|
|
||||||
w.Flush()
|
|
||||||
w.Close()
|
|
||||||
|
|
||||||
req := new(Request)
|
|
||||||
httpRequest, _ := http.NewRequest("GET", "/", bytes.NewReader(b.Bytes()))
|
|
||||||
httpRequest.Header.Set("Content-Type", "application/json")
|
|
||||||
httpRequest.Header.Set("Content-Encoding", "deflate")
|
|
||||||
req.Request = httpRequest
|
|
||||||
|
|
||||||
doc := make(map[string]interface{})
|
|
||||||
req.ReadEntity(&doc)
|
|
||||||
|
|
||||||
if got, want := doc["msg"], "hi"; got != want {
|
|
||||||
t.Errorf("got %v want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
103
vendor/github.com/emicklei/go-restful/compressor_cache.go
generated
vendored
103
vendor/github.com/emicklei/go-restful/compressor_cache.go
generated
vendored
@ -1,103 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"compress/gzip"
|
|
||||||
"compress/zlib"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BoundedCachedCompressors is a CompressorProvider that uses a cache with a fixed amount
|
|
||||||
// of writers and readers (resources).
|
|
||||||
// If a new resource is acquired and all are in use, it will return a new unmanaged resource.
|
|
||||||
type BoundedCachedCompressors struct {
|
|
||||||
gzipWriters chan *gzip.Writer
|
|
||||||
gzipReaders chan *gzip.Reader
|
|
||||||
zlibWriters chan *zlib.Writer
|
|
||||||
writersCapacity int
|
|
||||||
readersCapacity int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBoundedCachedCompressors returns a new, with filled cache, BoundedCachedCompressors.
|
|
||||||
func NewBoundedCachedCompressors(writersCapacity, readersCapacity int) *BoundedCachedCompressors {
|
|
||||||
b := &BoundedCachedCompressors{
|
|
||||||
gzipWriters: make(chan *gzip.Writer, writersCapacity),
|
|
||||||
gzipReaders: make(chan *gzip.Reader, readersCapacity),
|
|
||||||
zlibWriters: make(chan *zlib.Writer, writersCapacity),
|
|
||||||
writersCapacity: writersCapacity,
|
|
||||||
readersCapacity: readersCapacity,
|
|
||||||
}
|
|
||||||
for ix := 0; ix < writersCapacity; ix++ {
|
|
||||||
b.gzipWriters <- newGzipWriter()
|
|
||||||
b.zlibWriters <- newZlibWriter()
|
|
||||||
}
|
|
||||||
for ix := 0; ix < readersCapacity; ix++ {
|
|
||||||
b.gzipReaders <- newGzipReader()
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcquireGzipWriter returns an resettable *gzip.Writer. Needs to be released.
|
|
||||||
func (b *BoundedCachedCompressors) AcquireGzipWriter() *gzip.Writer {
|
|
||||||
var writer *gzip.Writer
|
|
||||||
select {
|
|
||||||
case writer, _ = <-b.gzipWriters:
|
|
||||||
default:
|
|
||||||
// return a new unmanaged one
|
|
||||||
writer = newGzipWriter()
|
|
||||||
}
|
|
||||||
return writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReleaseGzipWriter accepts a writer (does not have to be one that was cached)
|
|
||||||
// only when the cache has room for it. It will ignore it otherwise.
|
|
||||||
func (b *BoundedCachedCompressors) ReleaseGzipWriter(w *gzip.Writer) {
|
|
||||||
// forget the unmanaged ones
|
|
||||||
if len(b.gzipWriters) < b.writersCapacity {
|
|
||||||
b.gzipWriters <- w
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcquireGzipReader returns a *gzip.Reader. Needs to be released.
|
|
||||||
func (b *BoundedCachedCompressors) AcquireGzipReader() *gzip.Reader {
|
|
||||||
var reader *gzip.Reader
|
|
||||||
select {
|
|
||||||
case reader, _ = <-b.gzipReaders:
|
|
||||||
default:
|
|
||||||
// return a new unmanaged one
|
|
||||||
reader = newGzipReader()
|
|
||||||
}
|
|
||||||
return reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReleaseGzipReader accepts a reader (does not have to be one that was cached)
|
|
||||||
// only when the cache has room for it. It will ignore it otherwise.
|
|
||||||
func (b *BoundedCachedCompressors) ReleaseGzipReader(r *gzip.Reader) {
|
|
||||||
// forget the unmanaged ones
|
|
||||||
if len(b.gzipReaders) < b.readersCapacity {
|
|
||||||
b.gzipReaders <- r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcquireZlibWriter returns an resettable *zlib.Writer. Needs to be released.
|
|
||||||
func (b *BoundedCachedCompressors) AcquireZlibWriter() *zlib.Writer {
|
|
||||||
var writer *zlib.Writer
|
|
||||||
select {
|
|
||||||
case writer, _ = <-b.zlibWriters:
|
|
||||||
default:
|
|
||||||
// return a new unmanaged one
|
|
||||||
writer = newZlibWriter()
|
|
||||||
}
|
|
||||||
return writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReleaseZlibWriter accepts a writer (does not have to be one that was cached)
|
|
||||||
// only when the cache has room for it. It will ignore it otherwise.
|
|
||||||
func (b *BoundedCachedCompressors) ReleaseZlibWriter(w *zlib.Writer) {
|
|
||||||
// forget the unmanaged ones
|
|
||||||
if len(b.zlibWriters) < b.writersCapacity {
|
|
||||||
b.zlibWriters <- w
|
|
||||||
}
|
|
||||||
}
|
|
91
vendor/github.com/emicklei/go-restful/compressor_pools.go
generated
vendored
91
vendor/github.com/emicklei/go-restful/compressor_pools.go
generated
vendored
@ -1,91 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"compress/zlib"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SyncPoolCompessors is a CompressorProvider that use the standard sync.Pool.
|
|
||||||
type SyncPoolCompessors struct {
|
|
||||||
GzipWriterPool *sync.Pool
|
|
||||||
GzipReaderPool *sync.Pool
|
|
||||||
ZlibWriterPool *sync.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSyncPoolCompessors returns a new ("empty") SyncPoolCompessors.
|
|
||||||
func NewSyncPoolCompessors() *SyncPoolCompessors {
|
|
||||||
return &SyncPoolCompessors{
|
|
||||||
GzipWriterPool: &sync.Pool{
|
|
||||||
New: func() interface{} { return newGzipWriter() },
|
|
||||||
},
|
|
||||||
GzipReaderPool: &sync.Pool{
|
|
||||||
New: func() interface{} { return newGzipReader() },
|
|
||||||
},
|
|
||||||
ZlibWriterPool: &sync.Pool{
|
|
||||||
New: func() interface{} { return newZlibWriter() },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) AcquireGzipWriter() *gzip.Writer {
|
|
||||||
return s.GzipWriterPool.Get().(*gzip.Writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) ReleaseGzipWriter(w *gzip.Writer) {
|
|
||||||
s.GzipWriterPool.Put(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) AcquireGzipReader() *gzip.Reader {
|
|
||||||
return s.GzipReaderPool.Get().(*gzip.Reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) ReleaseGzipReader(r *gzip.Reader) {
|
|
||||||
s.GzipReaderPool.Put(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) AcquireZlibWriter() *zlib.Writer {
|
|
||||||
return s.ZlibWriterPool.Get().(*zlib.Writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SyncPoolCompessors) ReleaseZlibWriter(w *zlib.Writer) {
|
|
||||||
s.ZlibWriterPool.Put(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGzipWriter() *gzip.Writer {
|
|
||||||
// create with an empty bytes writer; it will be replaced before using the gzipWriter
|
|
||||||
writer, err := gzip.NewWriterLevel(new(bytes.Buffer), gzip.BestSpeed)
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGzipReader() *gzip.Reader {
|
|
||||||
// create with an empty reader (but with GZIP header); it will be replaced before using the gzipReader
|
|
||||||
// we can safely use currentCompressProvider because it is set on package initialization.
|
|
||||||
w := currentCompressorProvider.AcquireGzipWriter()
|
|
||||||
defer currentCompressorProvider.ReleaseGzipWriter(w)
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
w.Reset(b)
|
|
||||||
w.Flush()
|
|
||||||
w.Close()
|
|
||||||
reader, err := gzip.NewReader(bytes.NewReader(b.Bytes()))
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func newZlibWriter() *zlib.Writer {
|
|
||||||
writer, err := zlib.NewWriterLevel(new(bytes.Buffer), gzip.BestSpeed)
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return writer
|
|
||||||
}
|
|
54
vendor/github.com/emicklei/go-restful/compressors.go
generated
vendored
54
vendor/github.com/emicklei/go-restful/compressors.go
generated
vendored
@ -1,54 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"compress/gzip"
|
|
||||||
"compress/zlib"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CompressorProvider describes a component that can provider compressors for the std methods.
|
|
||||||
type CompressorProvider interface {
|
|
||||||
// Returns a *gzip.Writer which needs to be released later.
|
|
||||||
// Before using it, call Reset().
|
|
||||||
AcquireGzipWriter() *gzip.Writer
|
|
||||||
|
|
||||||
// Releases an acquired *gzip.Writer.
|
|
||||||
ReleaseGzipWriter(w *gzip.Writer)
|
|
||||||
|
|
||||||
// Returns a *gzip.Reader which needs to be released later.
|
|
||||||
AcquireGzipReader() *gzip.Reader
|
|
||||||
|
|
||||||
// Releases an acquired *gzip.Reader.
|
|
||||||
ReleaseGzipReader(w *gzip.Reader)
|
|
||||||
|
|
||||||
// Returns a *zlib.Writer which needs to be released later.
|
|
||||||
// Before using it, call Reset().
|
|
||||||
AcquireZlibWriter() *zlib.Writer
|
|
||||||
|
|
||||||
// Releases an acquired *zlib.Writer.
|
|
||||||
ReleaseZlibWriter(w *zlib.Writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultCompressorProvider is the actual provider of compressors (zlib or gzip).
|
|
||||||
var currentCompressorProvider CompressorProvider
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
currentCompressorProvider = NewSyncPoolCompessors()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurrentCompressorProvider returns the current CompressorProvider.
|
|
||||||
// It is initialized using a SyncPoolCompessors.
|
|
||||||
func CurrentCompressorProvider() CompressorProvider {
|
|
||||||
return currentCompressorProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCompressorProvider sets the actual provider of compressors (zlib or gzip).
|
|
||||||
func SetCompressorProvider(p CompressorProvider) {
|
|
||||||
if p == nil {
|
|
||||||
panic("cannot set compressor provider to nil")
|
|
||||||
}
|
|
||||||
currentCompressorProvider = p
|
|
||||||
}
|
|
30
vendor/github.com/emicklei/go-restful/constants.go
generated
vendored
30
vendor/github.com/emicklei/go-restful/constants.go
generated
vendored
@ -1,30 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
const (
|
|
||||||
MIME_XML = "application/xml" // Accept or Content-Type used in Consumes() and/or Produces()
|
|
||||||
MIME_JSON = "application/json" // Accept or Content-Type used in Consumes() and/or Produces()
|
|
||||||
MIME_OCTET = "application/octet-stream" // If Content-Type is not present in request, use the default
|
|
||||||
|
|
||||||
HEADER_Allow = "Allow"
|
|
||||||
HEADER_Accept = "Accept"
|
|
||||||
HEADER_Origin = "Origin"
|
|
||||||
HEADER_ContentType = "Content-Type"
|
|
||||||
HEADER_LastModified = "Last-Modified"
|
|
||||||
HEADER_AcceptEncoding = "Accept-Encoding"
|
|
||||||
HEADER_ContentEncoding = "Content-Encoding"
|
|
||||||
HEADER_AccessControlExposeHeaders = "Access-Control-Expose-Headers"
|
|
||||||
HEADER_AccessControlRequestMethod = "Access-Control-Request-Method"
|
|
||||||
HEADER_AccessControlRequestHeaders = "Access-Control-Request-Headers"
|
|
||||||
HEADER_AccessControlAllowMethods = "Access-Control-Allow-Methods"
|
|
||||||
HEADER_AccessControlAllowOrigin = "Access-Control-Allow-Origin"
|
|
||||||
HEADER_AccessControlAllowCredentials = "Access-Control-Allow-Credentials"
|
|
||||||
HEADER_AccessControlAllowHeaders = "Access-Control-Allow-Headers"
|
|
||||||
HEADER_AccessControlMaxAge = "Access-Control-Max-Age"
|
|
||||||
|
|
||||||
ENCODING_GZIP = "gzip"
|
|
||||||
ENCODING_DEFLATE = "deflate"
|
|
||||||
)
|
|
366
vendor/github.com/emicklei/go-restful/container.go
generated
vendored
366
vendor/github.com/emicklei/go-restful/container.go
generated
vendored
@ -1,366 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Container holds a collection of WebServices and a http.ServeMux to dispatch http requests.
|
|
||||||
// The requests are further dispatched to routes of WebServices using a RouteSelector
|
|
||||||
type Container struct {
|
|
||||||
webServicesLock sync.RWMutex
|
|
||||||
webServices []*WebService
|
|
||||||
ServeMux *http.ServeMux
|
|
||||||
isRegisteredOnRoot bool
|
|
||||||
containerFilters []FilterFunction
|
|
||||||
doNotRecover bool // default is true
|
|
||||||
recoverHandleFunc RecoverHandleFunction
|
|
||||||
serviceErrorHandleFunc ServiceErrorHandleFunction
|
|
||||||
router RouteSelector // default is a CurlyRouter (RouterJSR311 is a slower alternative)
|
|
||||||
contentEncodingEnabled bool // default is false
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewContainer creates a new Container using a new ServeMux and default router (CurlyRouter)
|
|
||||||
func NewContainer() *Container {
|
|
||||||
return &Container{
|
|
||||||
webServices: []*WebService{},
|
|
||||||
ServeMux: http.NewServeMux(),
|
|
||||||
isRegisteredOnRoot: false,
|
|
||||||
containerFilters: []FilterFunction{},
|
|
||||||
doNotRecover: true,
|
|
||||||
recoverHandleFunc: logStackOnRecover,
|
|
||||||
serviceErrorHandleFunc: writeServiceError,
|
|
||||||
router: CurlyRouter{},
|
|
||||||
contentEncodingEnabled: false}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecoverHandleFunction declares functions that can be used to handle a panic situation.
|
|
||||||
// The first argument is what recover() returns. The second must be used to communicate an error response.
|
|
||||||
type RecoverHandleFunction func(interface{}, http.ResponseWriter)
|
|
||||||
|
|
||||||
// RecoverHandler changes the default function (logStackOnRecover) to be called
|
|
||||||
// when a panic is detected. DoNotRecover must be have its default value (=false).
|
|
||||||
func (c *Container) RecoverHandler(handler RecoverHandleFunction) {
|
|
||||||
c.recoverHandleFunc = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceErrorHandleFunction declares functions that can be used to handle a service error situation.
|
|
||||||
// The first argument is the service error, the second is the request that resulted in the error and
|
|
||||||
// the third must be used to communicate an error response.
|
|
||||||
type ServiceErrorHandleFunction func(ServiceError, *Request, *Response)
|
|
||||||
|
|
||||||
// ServiceErrorHandler changes the default function (writeServiceError) to be called
|
|
||||||
// when a ServiceError is detected.
|
|
||||||
func (c *Container) ServiceErrorHandler(handler ServiceErrorHandleFunction) {
|
|
||||||
c.serviceErrorHandleFunc = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// DoNotRecover controls whether panics will be caught to return HTTP 500.
|
|
||||||
// If set to true, Route functions are responsible for handling any error situation.
|
|
||||||
// Default value is true.
|
|
||||||
func (c *Container) DoNotRecover(doNot bool) {
|
|
||||||
c.doNotRecover = doNot
|
|
||||||
}
|
|
||||||
|
|
||||||
// Router changes the default Router (currently CurlyRouter)
|
|
||||||
func (c *Container) Router(aRouter RouteSelector) {
|
|
||||||
c.router = aRouter
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnableContentEncoding (default=false) allows for GZIP or DEFLATE encoding of responses.
|
|
||||||
func (c *Container) EnableContentEncoding(enabled bool) {
|
|
||||||
c.contentEncodingEnabled = enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a WebService to the Container. It will detect duplicate root paths and exit in that case.
|
|
||||||
func (c *Container) Add(service *WebService) *Container {
|
|
||||||
c.webServicesLock.Lock()
|
|
||||||
defer c.webServicesLock.Unlock()
|
|
||||||
|
|
||||||
// if rootPath was not set then lazy initialize it
|
|
||||||
if len(service.rootPath) == 0 {
|
|
||||||
service.Path("/")
|
|
||||||
}
|
|
||||||
|
|
||||||
// cannot have duplicate root paths
|
|
||||||
for _, each := range c.webServices {
|
|
||||||
if each.RootPath() == service.RootPath() {
|
|
||||||
log.Printf("[restful] WebService with duplicate root path detected:['%v']", each)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not registered on root then add specific mapping
|
|
||||||
if !c.isRegisteredOnRoot {
|
|
||||||
c.isRegisteredOnRoot = c.addHandler(service, c.ServeMux)
|
|
||||||
}
|
|
||||||
c.webServices = append(c.webServices, service)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// addHandler may set a new HandleFunc for the serveMux
|
|
||||||
// this function must run inside the critical region protected by the webServicesLock.
|
|
||||||
// returns true if the function was registered on root ("/")
|
|
||||||
func (c *Container) addHandler(service *WebService, serveMux *http.ServeMux) bool {
|
|
||||||
pattern := fixedPrefixPath(service.RootPath())
|
|
||||||
// check if root path registration is needed
|
|
||||||
if "/" == pattern || "" == pattern {
|
|
||||||
serveMux.HandleFunc("/", c.dispatch)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// detect if registration already exists
|
|
||||||
alreadyMapped := false
|
|
||||||
for _, each := range c.webServices {
|
|
||||||
if each.RootPath() == service.RootPath() {
|
|
||||||
alreadyMapped = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !alreadyMapped {
|
|
||||||
serveMux.HandleFunc(pattern, c.dispatch)
|
|
||||||
if !strings.HasSuffix(pattern, "/") {
|
|
||||||
serveMux.HandleFunc(pattern+"/", c.dispatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Remove(ws *WebService) error {
|
|
||||||
if c.ServeMux == http.DefaultServeMux {
|
|
||||||
errMsg := fmt.Sprintf("[restful] cannot remove a WebService from a Container using the DefaultServeMux: ['%v']", ws)
|
|
||||||
log.Print(errMsg)
|
|
||||||
return errors.New(errMsg)
|
|
||||||
}
|
|
||||||
c.webServicesLock.Lock()
|
|
||||||
defer c.webServicesLock.Unlock()
|
|
||||||
// build a new ServeMux and re-register all WebServices
|
|
||||||
newServeMux := http.NewServeMux()
|
|
||||||
newServices := []*WebService{}
|
|
||||||
newIsRegisteredOnRoot := false
|
|
||||||
for _, each := range c.webServices {
|
|
||||||
if each.rootPath != ws.rootPath {
|
|
||||||
// If not registered on root then add specific mapping
|
|
||||||
if !newIsRegisteredOnRoot {
|
|
||||||
newIsRegisteredOnRoot = c.addHandler(each, newServeMux)
|
|
||||||
}
|
|
||||||
newServices = append(newServices, each)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.webServices, c.ServeMux, c.isRegisteredOnRoot = newServices, newServeMux, newIsRegisteredOnRoot
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// logStackOnRecover is the default RecoverHandleFunction and is called
|
|
||||||
// when DoNotRecover is false and the recoverHandleFunc is not set for the container.
|
|
||||||
// Default implementation logs the stacktrace and writes the stacktrace on the response.
|
|
||||||
// This may be a security issue as it exposes sourcecode information.
|
|
||||||
func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
buffer.WriteString(fmt.Sprintf("[restful] recover from panic situation: - %v\r\n", panicReason))
|
|
||||||
for i := 2; ; i += 1 {
|
|
||||||
_, file, line, ok := runtime.Caller(i)
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line))
|
|
||||||
}
|
|
||||||
log.Print(buffer.String())
|
|
||||||
httpWriter.WriteHeader(http.StatusInternalServerError)
|
|
||||||
httpWriter.Write(buffer.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeServiceError is the default ServiceErrorHandleFunction and is called
|
|
||||||
// when a ServiceError is returned during route selection. Default implementation
|
|
||||||
// calls resp.WriteErrorString(err.Code, err.Message)
|
|
||||||
func writeServiceError(err ServiceError, req *Request, resp *Response) {
|
|
||||||
resp.WriteErrorString(err.Code, err.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch the incoming Http Request to a matching WebService.
|
|
||||||
func (c *Container) Dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
|
|
||||||
if httpWriter == nil {
|
|
||||||
panic("httpWriter cannot be nil")
|
|
||||||
}
|
|
||||||
if httpRequest == nil {
|
|
||||||
panic("httpRequest cannot be nil")
|
|
||||||
}
|
|
||||||
c.dispatch(httpWriter, httpRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch the incoming Http Request to a matching WebService.
|
|
||||||
func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
|
|
||||||
writer := httpWriter
|
|
||||||
|
|
||||||
// CompressingResponseWriter should be closed after all operations are done
|
|
||||||
defer func() {
|
|
||||||
if compressWriter, ok := writer.(*CompressingResponseWriter); ok {
|
|
||||||
compressWriter.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Instal panic recovery unless told otherwise
|
|
||||||
if !c.doNotRecover { // catch all for 500 response
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
c.recoverHandleFunc(r, writer)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect if compression is needed
|
|
||||||
// assume without compression, test for override
|
|
||||||
if c.contentEncodingEnabled {
|
|
||||||
doCompress, encoding := wantsCompressedResponse(httpRequest)
|
|
||||||
if doCompress {
|
|
||||||
var err error
|
|
||||||
writer, err = NewCompressingResponseWriter(httpWriter, encoding)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("[restful] unable to install compressor: ", err)
|
|
||||||
httpWriter.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Find best match Route ; err is non nil if no match was found
|
|
||||||
var webService *WebService
|
|
||||||
var route *Route
|
|
||||||
var err error
|
|
||||||
func() {
|
|
||||||
c.webServicesLock.RLock()
|
|
||||||
defer c.webServicesLock.RUnlock()
|
|
||||||
webService, route, err = c.router.SelectRoute(
|
|
||||||
c.webServices,
|
|
||||||
httpRequest)
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
// a non-200 response has already been written
|
|
||||||
// run container filters anyway ; they should not touch the response...
|
|
||||||
chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
|
|
||||||
switch err.(type) {
|
|
||||||
case ServiceError:
|
|
||||||
ser := err.(ServiceError)
|
|
||||||
c.serviceErrorHandleFunc(ser, req, resp)
|
|
||||||
}
|
|
||||||
// TODO
|
|
||||||
}}
|
|
||||||
chain.ProcessFilter(NewRequest(httpRequest), NewResponse(writer))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
wrappedRequest, wrappedResponse := route.wrapRequestResponse(writer, httpRequest)
|
|
||||||
// pass through filters (if any)
|
|
||||||
if len(c.containerFilters)+len(webService.filters)+len(route.Filters) > 0 {
|
|
||||||
// compose filter chain
|
|
||||||
allFilters := []FilterFunction{}
|
|
||||||
allFilters = append(allFilters, c.containerFilters...)
|
|
||||||
allFilters = append(allFilters, webService.filters...)
|
|
||||||
allFilters = append(allFilters, route.Filters...)
|
|
||||||
chain := FilterChain{Filters: allFilters, Target: func(req *Request, resp *Response) {
|
|
||||||
// handle request by route after passing all filters
|
|
||||||
route.Function(wrappedRequest, wrappedResponse)
|
|
||||||
}}
|
|
||||||
chain.ProcessFilter(wrappedRequest, wrappedResponse)
|
|
||||||
} else {
|
|
||||||
// no filters, handle request by route
|
|
||||||
route.Function(wrappedRequest, wrappedResponse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixedPrefixPath returns the fixed part of the partspec ; it may include template vars {}
|
|
||||||
func fixedPrefixPath(pathspec string) string {
|
|
||||||
varBegin := strings.Index(pathspec, "{")
|
|
||||||
if -1 == varBegin {
|
|
||||||
return pathspec
|
|
||||||
}
|
|
||||||
return pathspec[:varBegin]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP implements net/http.Handler therefore a Container can be a Handler in a http.Server
|
|
||||||
func (c *Container) ServeHTTP(httpwriter http.ResponseWriter, httpRequest *http.Request) {
|
|
||||||
c.ServeMux.ServeHTTP(httpwriter, httpRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics.
|
|
||||||
func (c *Container) Handle(pattern string, handler http.Handler) {
|
|
||||||
c.ServeMux.Handle(pattern, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleWithFilter registers the handler for the given pattern.
|
|
||||||
// Container's filter chain is applied for handler.
|
|
||||||
// If a handler already exists for pattern, HandleWithFilter panics.
|
|
||||||
func (c *Container) HandleWithFilter(pattern string, handler http.Handler) {
|
|
||||||
f := func(httpResponse http.ResponseWriter, httpRequest *http.Request) {
|
|
||||||
if len(c.containerFilters) == 0 {
|
|
||||||
handler.ServeHTTP(httpResponse, httpRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
|
|
||||||
handler.ServeHTTP(httpResponse, httpRequest)
|
|
||||||
}}
|
|
||||||
chain.ProcessFilter(NewRequest(httpRequest), NewResponse(httpResponse))
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Handle(pattern, http.HandlerFunc(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter appends a container FilterFunction. These are called before dispatching
|
|
||||||
// a http.Request to a WebService from the container
|
|
||||||
func (c *Container) Filter(filter FilterFunction) {
|
|
||||||
c.containerFilters = append(c.containerFilters, filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisteredWebServices returns the collections of added WebServices
|
|
||||||
func (c *Container) RegisteredWebServices() []*WebService {
|
|
||||||
c.webServicesLock.RLock()
|
|
||||||
defer c.webServicesLock.RUnlock()
|
|
||||||
result := make([]*WebService, len(c.webServices))
|
|
||||||
for ix := range c.webServices {
|
|
||||||
result[ix] = c.webServices[ix]
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// computeAllowedMethods returns a list of HTTP methods that are valid for a Request
|
|
||||||
func (c *Container) computeAllowedMethods(req *Request) []string {
|
|
||||||
// Go through all RegisteredWebServices() and all its Routes to collect the options
|
|
||||||
methods := []string{}
|
|
||||||
requestPath := req.Request.URL.Path
|
|
||||||
for _, ws := range c.RegisteredWebServices() {
|
|
||||||
matches := ws.pathExpr.Matcher.FindStringSubmatch(requestPath)
|
|
||||||
if matches != nil {
|
|
||||||
finalMatch := matches[len(matches)-1]
|
|
||||||
for _, rt := range ws.Routes() {
|
|
||||||
matches := rt.pathExpr.Matcher.FindStringSubmatch(finalMatch)
|
|
||||||
if matches != nil {
|
|
||||||
lastMatch := matches[len(matches)-1]
|
|
||||||
if lastMatch == "" || lastMatch == "/" { // do not include if value is neither empty nor ‘/’.
|
|
||||||
methods = append(methods, rt.Method)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// methods = append(methods, "OPTIONS") not sure about this
|
|
||||||
return methods
|
|
||||||
}
|
|
||||||
|
|
||||||
// newBasicRequestResponse creates a pair of Request,Response from its http versions.
|
|
||||||
// It is basic because no parameter or (produces) content-type information is given.
|
|
||||||
func newBasicRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request) (*Request, *Response) {
|
|
||||||
resp := NewResponse(httpWriter)
|
|
||||||
resp.requestAccept = httpRequest.Header.Get(HEADER_Accept)
|
|
||||||
return NewRequest(httpRequest), resp
|
|
||||||
}
|
|
83
vendor/github.com/emicklei/go-restful/container_test.go
generated
vendored
83
vendor/github.com/emicklei/go-restful/container_test.go
generated
vendored
@ -1,83 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// go test -v -test.run TestContainer_computeAllowedMethods ...restful
|
|
||||||
func TestContainer_computeAllowedMethods(t *testing.T) {
|
|
||||||
wc := NewContainer()
|
|
||||||
ws1 := new(WebService).Path("/users")
|
|
||||||
ws1.Route(ws1.GET("{i}").To(dummy))
|
|
||||||
ws1.Route(ws1.POST("{i}").To(dummy))
|
|
||||||
wc.Add(ws1)
|
|
||||||
httpRequest, _ := http.NewRequest("GET", "http://api.his.com/users/1", nil)
|
|
||||||
rreq := Request{Request: httpRequest}
|
|
||||||
m := wc.computeAllowedMethods(&rreq)
|
|
||||||
if len(m) != 2 {
|
|
||||||
t.Errorf("got %d expected 2 methods, %v", len(m), m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContainer_HandleWithFilter(t *testing.T) {
|
|
||||||
prefilterCalled := false
|
|
||||||
postfilterCalled := false
|
|
||||||
httpHandlerCalled := false
|
|
||||||
|
|
||||||
wc := NewContainer()
|
|
||||||
wc.Filter(func(request *Request, response *Response, chain *FilterChain) {
|
|
||||||
prefilterCalled = true
|
|
||||||
chain.ProcessFilter(request, response)
|
|
||||||
})
|
|
||||||
wc.HandleWithFilter("/", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
||||||
httpHandlerCalled = true
|
|
||||||
w.Write([]byte("ok"))
|
|
||||||
}))
|
|
||||||
wc.Filter(func(request *Request, response *Response, chain *FilterChain) {
|
|
||||||
postfilterCalled = true
|
|
||||||
chain.ProcessFilter(request, response)
|
|
||||||
})
|
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
request, _ := http.NewRequest("GET", "/", nil)
|
|
||||||
wc.ServeHTTP(recorder, request)
|
|
||||||
if recorder.Code != http.StatusOK {
|
|
||||||
t.Errorf("unexpected code %d", recorder.Code)
|
|
||||||
}
|
|
||||||
if recorder.Body.String() != "ok" {
|
|
||||||
t.Errorf("unexpected body %s", recorder.Body.String())
|
|
||||||
}
|
|
||||||
if !prefilterCalled {
|
|
||||||
t.Errorf("filter added before calling HandleWithFilter wasn't called")
|
|
||||||
}
|
|
||||||
if !postfilterCalled {
|
|
||||||
t.Errorf("filter added after calling HandleWithFilter wasn't called")
|
|
||||||
}
|
|
||||||
if !httpHandlerCalled {
|
|
||||||
t.Errorf("handler added by calling HandleWithFilter wasn't called")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContainerAddAndRemove(t *testing.T) {
|
|
||||||
ws1 := new(WebService).Path("/")
|
|
||||||
ws2 := new(WebService).Path("/users")
|
|
||||||
wc := NewContainer()
|
|
||||||
wc.Add(ws1)
|
|
||||||
wc.Add(ws2)
|
|
||||||
wc.Remove(ws2)
|
|
||||||
if len(wc.webServices) != 1 {
|
|
||||||
t.Errorf("expected one webservices")
|
|
||||||
}
|
|
||||||
if !wc.isRegisteredOnRoot {
|
|
||||||
t.Errorf("expected on root registered")
|
|
||||||
}
|
|
||||||
wc.Remove(ws1)
|
|
||||||
if len(wc.webServices) > 0 {
|
|
||||||
t.Errorf("expected zero webservices")
|
|
||||||
}
|
|
||||||
if wc.isRegisteredOnRoot {
|
|
||||||
t.Errorf("expected not on root registered")
|
|
||||||
}
|
|
||||||
}
|
|
202
vendor/github.com/emicklei/go-restful/cors_filter.go
generated
vendored
202
vendor/github.com/emicklei/go-restful/cors_filter.go
generated
vendored
@ -1,202 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CrossOriginResourceSharing is used to create a Container Filter that implements CORS.
|
|
||||||
// Cross-origin resource sharing (CORS) is a mechanism that allows JavaScript on a web page
|
|
||||||
// to make XMLHttpRequests to another domain, not the domain the JavaScript originated from.
|
|
||||||
//
|
|
||||||
// http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
|
|
||||||
// http://enable-cors.org/server.html
|
|
||||||
// http://www.html5rocks.com/en/tutorials/cors/#toc-handling-a-not-so-simple-request
|
|
||||||
type CrossOriginResourceSharing struct {
|
|
||||||
ExposeHeaders []string // list of Header names
|
|
||||||
AllowedHeaders []string // list of Header names
|
|
||||||
AllowedDomains []string // list of allowed values for Http Origin. An allowed value can be a regular expression to support subdomain matching. If empty all are allowed.
|
|
||||||
AllowedMethods []string
|
|
||||||
MaxAge int // number of seconds before requiring new Options request
|
|
||||||
CookiesAllowed bool
|
|
||||||
Container *Container
|
|
||||||
|
|
||||||
allowedOriginPatterns []*regexp.Regexp // internal field for origin regexp check.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter is a filter function that implements the CORS flow as documented on http://enable-cors.org/server.html
|
|
||||||
// and http://www.html5rocks.com/static/images/cors_server_flowchart.png
|
|
||||||
func (c CrossOriginResourceSharing) Filter(req *Request, resp *Response, chain *FilterChain) {
|
|
||||||
origin := req.Request.Header.Get(HEADER_Origin)
|
|
||||||
if len(origin) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Print("no Http header Origin set")
|
|
||||||
}
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !c.isOriginAllowed(origin) { // check whether this origin is allowed
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("HTTP Origin:%s is not part of %v, neither matches any part of %v", origin, c.AllowedDomains, c.allowedOriginPatterns)
|
|
||||||
}
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if req.Request.Method != "OPTIONS" {
|
|
||||||
c.doActualRequest(req, resp)
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if acrm := req.Request.Header.Get(HEADER_AccessControlRequestMethod); acrm != "" {
|
|
||||||
c.doPreflightRequest(req, resp)
|
|
||||||
} else {
|
|
||||||
c.doActualRequest(req, resp)
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) doActualRequest(req *Request, resp *Response) {
|
|
||||||
c.setOptionsHeaders(req, resp)
|
|
||||||
// continue processing the response
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CrossOriginResourceSharing) doPreflightRequest(req *Request, resp *Response) {
|
|
||||||
if len(c.AllowedMethods) == 0 {
|
|
||||||
if c.Container == nil {
|
|
||||||
c.AllowedMethods = DefaultContainer.computeAllowedMethods(req)
|
|
||||||
} else {
|
|
||||||
c.AllowedMethods = c.Container.computeAllowedMethods(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
acrm := req.Request.Header.Get(HEADER_AccessControlRequestMethod)
|
|
||||||
if !c.isValidAccessControlRequestMethod(acrm, c.AllowedMethods) {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("Http header %s:%s is not in %v",
|
|
||||||
HEADER_AccessControlRequestMethod,
|
|
||||||
acrm,
|
|
||||||
c.AllowedMethods)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
acrhs := req.Request.Header.Get(HEADER_AccessControlRequestHeaders)
|
|
||||||
if len(acrhs) > 0 {
|
|
||||||
for _, each := range strings.Split(acrhs, ",") {
|
|
||||||
if !c.isValidAccessControlRequestHeader(strings.Trim(each, " ")) {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("Http header %s:%s is not in %v",
|
|
||||||
HEADER_AccessControlRequestHeaders,
|
|
||||||
acrhs,
|
|
||||||
c.AllowedHeaders)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowMethods, strings.Join(c.AllowedMethods, ","))
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowHeaders, acrhs)
|
|
||||||
c.setOptionsHeaders(req, resp)
|
|
||||||
|
|
||||||
// return http 200 response, no body
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) setOptionsHeaders(req *Request, resp *Response) {
|
|
||||||
c.checkAndSetExposeHeaders(resp)
|
|
||||||
c.setAllowOriginHeader(req, resp)
|
|
||||||
c.checkAndSetAllowCredentials(resp)
|
|
||||||
if c.MaxAge > 0 {
|
|
||||||
resp.AddHeader(HEADER_AccessControlMaxAge, strconv.Itoa(c.MaxAge))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) isOriginAllowed(origin string) bool {
|
|
||||||
if len(origin) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(c.AllowedDomains) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
allowed := false
|
|
||||||
for _, domain := range c.AllowedDomains {
|
|
||||||
if domain == origin {
|
|
||||||
allowed = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !allowed {
|
|
||||||
if len(c.allowedOriginPatterns) == 0 {
|
|
||||||
// compile allowed domains to allowed origin patterns
|
|
||||||
allowedOriginRegexps, err := compileRegexps(c.AllowedDomains)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
c.allowedOriginPatterns = allowedOriginRegexps
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pattern := range c.allowedOriginPatterns {
|
|
||||||
if allowed = pattern.MatchString(origin); allowed {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return allowed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) setAllowOriginHeader(req *Request, resp *Response) {
|
|
||||||
origin := req.Request.Header.Get(HEADER_Origin)
|
|
||||||
if c.isOriginAllowed(origin) {
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowOrigin, origin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) checkAndSetExposeHeaders(resp *Response) {
|
|
||||||
if len(c.ExposeHeaders) > 0 {
|
|
||||||
resp.AddHeader(HEADER_AccessControlExposeHeaders, strings.Join(c.ExposeHeaders, ","))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) checkAndSetAllowCredentials(resp *Response) {
|
|
||||||
if c.CookiesAllowed {
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowCredentials, "true")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) isValidAccessControlRequestMethod(method string, allowedMethods []string) bool {
|
|
||||||
for _, each := range allowedMethods {
|
|
||||||
if each == method {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CrossOriginResourceSharing) isValidAccessControlRequestHeader(header string) bool {
|
|
||||||
for _, each := range c.AllowedHeaders {
|
|
||||||
if strings.ToLower(each) == strings.ToLower(header) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take a list of strings and compile them into a list of regular expressions.
|
|
||||||
func compileRegexps(regexpStrings []string) ([]*regexp.Regexp, error) {
|
|
||||||
regexps := []*regexp.Regexp{}
|
|
||||||
for _, regexpStr := range regexpStrings {
|
|
||||||
r, err := regexp.Compile(regexpStr)
|
|
||||||
if err != nil {
|
|
||||||
return regexps, err
|
|
||||||
}
|
|
||||||
regexps = append(regexps, r)
|
|
||||||
}
|
|
||||||
return regexps, nil
|
|
||||||
}
|
|
129
vendor/github.com/emicklei/go-restful/cors_filter_test.go
generated
vendored
129
vendor/github.com/emicklei/go-restful/cors_filter_test.go
generated
vendored
@ -1,129 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// go test -v -test.run TestCORSFilter_Preflight ...restful
|
|
||||||
// http://www.html5rocks.com/en/tutorials/cors/#toc-handling-a-not-so-simple-request
|
|
||||||
func TestCORSFilter_Preflight(t *testing.T) {
|
|
||||||
tearDown()
|
|
||||||
ws := new(WebService)
|
|
||||||
ws.Route(ws.PUT("/cors").To(dummy))
|
|
||||||
Add(ws)
|
|
||||||
|
|
||||||
cors := CrossOriginResourceSharing{
|
|
||||||
ExposeHeaders: []string{"X-Custom-Header"},
|
|
||||||
AllowedHeaders: []string{"X-Custom-Header", "X-Additional-Header"},
|
|
||||||
CookiesAllowed: true,
|
|
||||||
Container: DefaultContainer}
|
|
||||||
Filter(cors.Filter)
|
|
||||||
|
|
||||||
// Preflight
|
|
||||||
httpRequest, _ := http.NewRequest("OPTIONS", "http://api.alice.com/cors", nil)
|
|
||||||
httpRequest.Method = "OPTIONS"
|
|
||||||
httpRequest.Header.Set(HEADER_Origin, "http://api.bob.com")
|
|
||||||
httpRequest.Header.Set(HEADER_AccessControlRequestMethod, "PUT")
|
|
||||||
httpRequest.Header.Set(HEADER_AccessControlRequestHeaders, "X-Custom-Header, X-Additional-Header")
|
|
||||||
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
DefaultContainer.Dispatch(httpWriter, httpRequest)
|
|
||||||
|
|
||||||
actual := httpWriter.Header().Get(HEADER_AccessControlAllowOrigin)
|
|
||||||
if "http://api.bob.com" != actual {
|
|
||||||
t.Fatal("expected: http://api.bob.com but got:" + actual)
|
|
||||||
}
|
|
||||||
actual = httpWriter.Header().Get(HEADER_AccessControlAllowMethods)
|
|
||||||
if "PUT" != actual {
|
|
||||||
t.Fatal("expected: PUT but got:" + actual)
|
|
||||||
}
|
|
||||||
actual = httpWriter.Header().Get(HEADER_AccessControlAllowHeaders)
|
|
||||||
if "X-Custom-Header, X-Additional-Header" != actual {
|
|
||||||
t.Fatal("expected: X-Custom-Header, X-Additional-Header but got:" + actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cors.isOriginAllowed("somewhere") {
|
|
||||||
t.Fatal("origin expected to be allowed")
|
|
||||||
}
|
|
||||||
cors.AllowedDomains = []string{"overthere.com"}
|
|
||||||
if cors.isOriginAllowed("somewhere") {
|
|
||||||
t.Fatal("origin [somewhere] expected NOT to be allowed")
|
|
||||||
}
|
|
||||||
if !cors.isOriginAllowed("overthere.com") {
|
|
||||||
t.Fatal("origin [overthere] expected to be allowed")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestCORSFilter_Actual ...restful
|
|
||||||
// http://www.html5rocks.com/en/tutorials/cors/#toc-handling-a-not-so-simple-request
|
|
||||||
func TestCORSFilter_Actual(t *testing.T) {
|
|
||||||
tearDown()
|
|
||||||
ws := new(WebService)
|
|
||||||
ws.Route(ws.PUT("/cors").To(dummy))
|
|
||||||
Add(ws)
|
|
||||||
|
|
||||||
cors := CrossOriginResourceSharing{
|
|
||||||
ExposeHeaders: []string{"X-Custom-Header"},
|
|
||||||
AllowedHeaders: []string{"X-Custom-Header", "X-Additional-Header"},
|
|
||||||
CookiesAllowed: true,
|
|
||||||
Container: DefaultContainer}
|
|
||||||
Filter(cors.Filter)
|
|
||||||
|
|
||||||
// Actual
|
|
||||||
httpRequest, _ := http.NewRequest("PUT", "http://api.alice.com/cors", nil)
|
|
||||||
httpRequest.Header.Set(HEADER_Origin, "http://api.bob.com")
|
|
||||||
httpRequest.Header.Set("X-Custom-Header", "value")
|
|
||||||
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
DefaultContainer.Dispatch(httpWriter, httpRequest)
|
|
||||||
actual := httpWriter.Header().Get(HEADER_AccessControlAllowOrigin)
|
|
||||||
if "http://api.bob.com" != actual {
|
|
||||||
t.Fatal("expected: http://api.bob.com but got:" + actual)
|
|
||||||
}
|
|
||||||
if httpWriter.Body.String() != "dummy" {
|
|
||||||
t.Fatal("expected: dummy but got:" + httpWriter.Body.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var allowedDomainInput = []struct {
|
|
||||||
domains []string
|
|
||||||
origin string
|
|
||||||
allowed bool
|
|
||||||
}{
|
|
||||||
{[]string{}, "http://anything.com", true},
|
|
||||||
{[]string{"example.com"}, "example.com", true},
|
|
||||||
{[]string{"example.com"}, "not-allowed", false},
|
|
||||||
{[]string{"not-matching.com", "example.com"}, "example.com", true},
|
|
||||||
{[]string{".*"}, "example.com", true},
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestCORSFilter_AllowedDomains ...restful
|
|
||||||
func TestCORSFilter_AllowedDomains(t *testing.T) {
|
|
||||||
for _, each := range allowedDomainInput {
|
|
||||||
tearDown()
|
|
||||||
ws := new(WebService)
|
|
||||||
ws.Route(ws.PUT("/cors").To(dummy))
|
|
||||||
Add(ws)
|
|
||||||
|
|
||||||
cors := CrossOriginResourceSharing{
|
|
||||||
AllowedDomains: each.domains,
|
|
||||||
CookiesAllowed: true,
|
|
||||||
Container: DefaultContainer}
|
|
||||||
Filter(cors.Filter)
|
|
||||||
|
|
||||||
httpRequest, _ := http.NewRequest("PUT", "http://api.his.com/cors", nil)
|
|
||||||
httpRequest.Header.Set(HEADER_Origin, each.origin)
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
DefaultContainer.Dispatch(httpWriter, httpRequest)
|
|
||||||
actual := httpWriter.Header().Get(HEADER_AccessControlAllowOrigin)
|
|
||||||
if actual != each.origin && each.allowed {
|
|
||||||
t.Fatal("expected to be accepted")
|
|
||||||
}
|
|
||||||
if actual == each.origin && !each.allowed {
|
|
||||||
t.Fatal("did not expect to be accepted")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
2
vendor/github.com/emicklei/go-restful/coverage.sh
generated
vendored
2
vendor/github.com/emicklei/go-restful/coverage.sh
generated
vendored
@ -1,2 +0,0 @@
|
|||||||
go test -coverprofile=coverage.out
|
|
||||||
go tool cover -html=coverage.out
|
|
164
vendor/github.com/emicklei/go-restful/curly.go
generated
vendored
164
vendor/github.com/emicklei/go-restful/curly.go
generated
vendored
@ -1,164 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CurlyRouter expects Routes with paths that contain zero or more parameters in curly brackets.
|
|
||||||
type CurlyRouter struct{}
|
|
||||||
|
|
||||||
// SelectRoute is part of the Router interface and returns the best match
|
|
||||||
// for the WebService and its Route for the given Request.
|
|
||||||
func (c CurlyRouter) SelectRoute(
|
|
||||||
webServices []*WebService,
|
|
||||||
httpRequest *http.Request) (selectedService *WebService, selected *Route, err error) {
|
|
||||||
|
|
||||||
requestTokens := tokenizePath(httpRequest.URL.Path)
|
|
||||||
|
|
||||||
detectedService := c.detectWebService(requestTokens, webServices)
|
|
||||||
if detectedService == nil {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no WebService was found to match URL path:%s\n", httpRequest.URL.Path)
|
|
||||||
}
|
|
||||||
return nil, nil, NewError(http.StatusNotFound, "404: Page Not Found")
|
|
||||||
}
|
|
||||||
candidateRoutes := c.selectRoutes(detectedService, requestTokens)
|
|
||||||
if len(candidateRoutes) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no Route in WebService with path %s was found to match URL path:%s\n", detectedService.rootPath, httpRequest.URL.Path)
|
|
||||||
}
|
|
||||||
return detectedService, nil, NewError(http.StatusNotFound, "404: Page Not Found")
|
|
||||||
}
|
|
||||||
selectedRoute, err := c.detectRoute(candidateRoutes, httpRequest)
|
|
||||||
if selectedRoute == nil {
|
|
||||||
return detectedService, nil, err
|
|
||||||
}
|
|
||||||
return detectedService, selectedRoute, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// selectRoutes return a collection of Route from a WebService that matches the path tokens from the request.
|
|
||||||
func (c CurlyRouter) selectRoutes(ws *WebService, requestTokens []string) sortableCurlyRoutes {
|
|
||||||
candidates := sortableCurlyRoutes{}
|
|
||||||
for _, each := range ws.routes {
|
|
||||||
matches, paramCount, staticCount := c.matchesRouteByPathTokens(each.pathParts, requestTokens)
|
|
||||||
if matches {
|
|
||||||
candidates.add(curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Sort(sort.Reverse(candidates))
|
|
||||||
return candidates
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchesRouteByPathTokens computes whether it matches, howmany parameters do match and what the number of static path elements are.
|
|
||||||
func (c CurlyRouter) matchesRouteByPathTokens(routeTokens, requestTokens []string) (matches bool, paramCount int, staticCount int) {
|
|
||||||
if len(routeTokens) < len(requestTokens) {
|
|
||||||
// proceed in matching only if last routeToken is wildcard
|
|
||||||
count := len(routeTokens)
|
|
||||||
if count == 0 || !strings.HasSuffix(routeTokens[count-1], "*}") {
|
|
||||||
return false, 0, 0
|
|
||||||
}
|
|
||||||
// proceed
|
|
||||||
}
|
|
||||||
for i, routeToken := range routeTokens {
|
|
||||||
if i == len(requestTokens) {
|
|
||||||
// reached end of request path
|
|
||||||
return false, 0, 0
|
|
||||||
}
|
|
||||||
requestToken := requestTokens[i]
|
|
||||||
if strings.HasPrefix(routeToken, "{") {
|
|
||||||
paramCount++
|
|
||||||
if colon := strings.Index(routeToken, ":"); colon != -1 {
|
|
||||||
// match by regex
|
|
||||||
matchesToken, matchesRemainder := c.regularMatchesPathToken(routeToken, colon, requestToken)
|
|
||||||
if !matchesToken {
|
|
||||||
return false, 0, 0
|
|
||||||
}
|
|
||||||
if matchesRemainder {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else { // no { prefix
|
|
||||||
if requestToken != routeToken {
|
|
||||||
return false, 0, 0
|
|
||||||
}
|
|
||||||
staticCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, paramCount, staticCount
|
|
||||||
}
|
|
||||||
|
|
||||||
// regularMatchesPathToken tests whether the regular expression part of routeToken matches the requestToken or all remaining tokens
|
|
||||||
// format routeToken is {someVar:someExpression}, e.g. {zipcode:[\d][\d][\d][\d][A-Z][A-Z]}
|
|
||||||
func (c CurlyRouter) regularMatchesPathToken(routeToken string, colon int, requestToken string) (matchesToken bool, matchesRemainder bool) {
|
|
||||||
regPart := routeToken[colon+1 : len(routeToken)-1]
|
|
||||||
if regPart == "*" {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("wildcard parameter detected in route token %s that matches %s\n", routeToken, requestToken)
|
|
||||||
}
|
|
||||||
return true, true
|
|
||||||
}
|
|
||||||
matched, err := regexp.MatchString(regPart, requestToken)
|
|
||||||
return (matched && err == nil), false
|
|
||||||
}
|
|
||||||
|
|
||||||
var jsr311Router = RouterJSR311{}
|
|
||||||
|
|
||||||
// detectRoute selectes from a list of Route the first match by inspecting both the Accept and Content-Type
|
|
||||||
// headers of the Request. See also RouterJSR311 in jsr311.go
|
|
||||||
func (c CurlyRouter) detectRoute(candidateRoutes sortableCurlyRoutes, httpRequest *http.Request) (*Route, error) {
|
|
||||||
// tracing is done inside detectRoute
|
|
||||||
return jsr311Router.detectRoute(candidateRoutes.routes(), httpRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// detectWebService returns the best matching webService given the list of path tokens.
|
|
||||||
// see also computeWebserviceScore
|
|
||||||
func (c CurlyRouter) detectWebService(requestTokens []string, webServices []*WebService) *WebService {
|
|
||||||
var best *WebService
|
|
||||||
score := -1
|
|
||||||
for _, each := range webServices {
|
|
||||||
matches, eachScore := c.computeWebserviceScore(requestTokens, each.pathExpr.tokens)
|
|
||||||
if matches && (eachScore > score) {
|
|
||||||
best = each
|
|
||||||
score = eachScore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return best
|
|
||||||
}
|
|
||||||
|
|
||||||
// computeWebserviceScore returns whether tokens match and
|
|
||||||
// the weighted score of the longest matching consecutive tokens from the beginning.
|
|
||||||
func (c CurlyRouter) computeWebserviceScore(requestTokens []string, tokens []string) (bool, int) {
|
|
||||||
if len(tokens) > len(requestTokens) {
|
|
||||||
return false, 0
|
|
||||||
}
|
|
||||||
score := 0
|
|
||||||
for i := 0; i < len(tokens); i++ {
|
|
||||||
each := requestTokens[i]
|
|
||||||
other := tokens[i]
|
|
||||||
if len(each) == 0 && len(other) == 0 {
|
|
||||||
score++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(other) > 0 && strings.HasPrefix(other, "{") {
|
|
||||||
// no empty match
|
|
||||||
if len(each) == 0 {
|
|
||||||
return false, score
|
|
||||||
}
|
|
||||||
score += 1
|
|
||||||
} else {
|
|
||||||
// not a parameter
|
|
||||||
if each != other {
|
|
||||||
return false, score
|
|
||||||
}
|
|
||||||
score += (len(tokens) - i) * 10 //fuzzy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, score
|
|
||||||
}
|
|
52
vendor/github.com/emicklei/go-restful/curly_route.go
generated
vendored
52
vendor/github.com/emicklei/go-restful/curly_route.go
generated
vendored
@ -1,52 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// curlyRoute exits for sorting Routes by the CurlyRouter based on number of parameters and number of static path elements.
|
|
||||||
type curlyRoute struct {
|
|
||||||
route Route
|
|
||||||
paramCount int
|
|
||||||
staticCount int
|
|
||||||
}
|
|
||||||
|
|
||||||
type sortableCurlyRoutes []curlyRoute
|
|
||||||
|
|
||||||
func (s *sortableCurlyRoutes) add(route curlyRoute) {
|
|
||||||
*s = append(*s, route)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s sortableCurlyRoutes) routes() (routes []Route) {
|
|
||||||
for _, each := range s {
|
|
||||||
routes = append(routes, each.route) // TODO change return type
|
|
||||||
}
|
|
||||||
return routes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s sortableCurlyRoutes) Len() int {
|
|
||||||
return len(s)
|
|
||||||
}
|
|
||||||
func (s sortableCurlyRoutes) Swap(i, j int) {
|
|
||||||
s[i], s[j] = s[j], s[i]
|
|
||||||
}
|
|
||||||
func (s sortableCurlyRoutes) Less(i, j int) bool {
|
|
||||||
ci := s[i]
|
|
||||||
cj := s[j]
|
|
||||||
|
|
||||||
// primary key
|
|
||||||
if ci.staticCount < cj.staticCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.staticCount > cj.staticCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// secundary key
|
|
||||||
if ci.paramCount < cj.paramCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.paramCount > cj.paramCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return ci.route.Path < cj.route.Path
|
|
||||||
}
|
|
231
vendor/github.com/emicklei/go-restful/curly_test.go
generated
vendored
231
vendor/github.com/emicklei/go-restful/curly_test.go
generated
vendored
@ -1,231 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var requestPaths = []struct {
|
|
||||||
// url with path (1) is handled by service with root (2) and remainder has value final (3)
|
|
||||||
path, root string
|
|
||||||
}{
|
|
||||||
{"/", "/"},
|
|
||||||
{"/p", "/p"},
|
|
||||||
{"/p/x", "/p/{q}"},
|
|
||||||
{"/q/x", "/q"},
|
|
||||||
{"/p/x/", "/p/{q}"},
|
|
||||||
{"/p/x/y", "/p/{q}"},
|
|
||||||
{"/q/x/y", "/q"},
|
|
||||||
{"/z/q", "/{p}/q"},
|
|
||||||
{"/a/b/c/q", "/"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestCurlyDetectWebService ...restful
|
|
||||||
func TestCurlyDetectWebService(t *testing.T) {
|
|
||||||
ws1 := new(WebService).Path("/")
|
|
||||||
ws2 := new(WebService).Path("/p")
|
|
||||||
ws3 := new(WebService).Path("/q")
|
|
||||||
ws4 := new(WebService).Path("/p/q")
|
|
||||||
ws5 := new(WebService).Path("/p/{q}")
|
|
||||||
ws7 := new(WebService).Path("/{p}/q")
|
|
||||||
var wss = []*WebService{ws1, ws2, ws3, ws4, ws5, ws7}
|
|
||||||
|
|
||||||
for _, each := range wss {
|
|
||||||
t.Logf("path=%s,toks=%v\n", each.pathExpr.Source, each.pathExpr.tokens)
|
|
||||||
}
|
|
||||||
|
|
||||||
router := CurlyRouter{}
|
|
||||||
|
|
||||||
ok := true
|
|
||||||
for i, fixture := range requestPaths {
|
|
||||||
requestTokens := tokenizePath(fixture.path)
|
|
||||||
who := router.detectWebService(requestTokens, wss)
|
|
||||||
if who != nil && who.RootPath() != fixture.root {
|
|
||||||
t.Logf("[line:%v] Unexpected dispatcher, expected:%v, actual:%v", i, fixture.root, who.RootPath())
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var serviceDetects = []struct {
|
|
||||||
path string
|
|
||||||
found bool
|
|
||||||
root string
|
|
||||||
}{
|
|
||||||
{"/a/b", true, "/{p}/{q}/{r}"},
|
|
||||||
{"/p/q", true, "/p/q"},
|
|
||||||
{"/q/p", true, "/q"},
|
|
||||||
{"/", true, "/"},
|
|
||||||
{"/p/q/r", true, "/p/q"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run Test_detectWebService ...restful
|
|
||||||
func Test_detectWebService(t *testing.T) {
|
|
||||||
router := CurlyRouter{}
|
|
||||||
ws1 := new(WebService).Path("/")
|
|
||||||
ws2 := new(WebService).Path("/p")
|
|
||||||
ws3 := new(WebService).Path("/q")
|
|
||||||
ws4 := new(WebService).Path("/p/q")
|
|
||||||
ws5 := new(WebService).Path("/p/{q}")
|
|
||||||
ws6 := new(WebService).Path("/p/{q}/")
|
|
||||||
ws7 := new(WebService).Path("/{p}/q")
|
|
||||||
ws8 := new(WebService).Path("/{p}/{q}/{r}")
|
|
||||||
var wss = []*WebService{ws8, ws7, ws6, ws5, ws4, ws3, ws2, ws1}
|
|
||||||
for _, fix := range serviceDetects {
|
|
||||||
requestPath := fix.path
|
|
||||||
requestTokens := tokenizePath(requestPath)
|
|
||||||
for _, ws := range wss {
|
|
||||||
serviceTokens := ws.pathExpr.tokens
|
|
||||||
matches, score := router.computeWebserviceScore(requestTokens, serviceTokens)
|
|
||||||
t.Logf("req=%s,toks:%v,ws=%s,toks:%v,score=%d,matches=%v", requestPath, requestTokens, ws.RootPath(), serviceTokens, score, matches)
|
|
||||||
}
|
|
||||||
best := router.detectWebService(requestTokens, wss)
|
|
||||||
if best != nil {
|
|
||||||
if fix.found {
|
|
||||||
t.Logf("best=%s", best.RootPath())
|
|
||||||
} else {
|
|
||||||
t.Fatalf("should have found:%s", fix.root)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var routeMatchers = []struct {
|
|
||||||
route string
|
|
||||||
path string
|
|
||||||
matches bool
|
|
||||||
paramCount int
|
|
||||||
staticCount int
|
|
||||||
}{
|
|
||||||
// route, request-path
|
|
||||||
{"/a", "/a", true, 0, 1},
|
|
||||||
{"/a", "/b", false, 0, 0},
|
|
||||||
{"/a", "/b", false, 0, 0},
|
|
||||||
{"/a/{b}/c/", "/a/2/c", true, 1, 2},
|
|
||||||
{"/{a}/{b}/{c}/", "/a/b", false, 0, 0},
|
|
||||||
{"/{x:*}", "/", false, 0, 0},
|
|
||||||
{"/{x:*}", "/a", true, 1, 0},
|
|
||||||
{"/{x:*}", "/a/b", true, 1, 0},
|
|
||||||
{"/a/{x:*}", "/a/b", true, 1, 1},
|
|
||||||
{"/a/{x:[A-Z][A-Z]}", "/a/ZX", true, 1, 1},
|
|
||||||
{"/basepath/{resource:*}", "/basepath/some/other/location/test.xml", true, 1, 1},
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear && go test -v -test.run Test_matchesRouteByPathTokens ...restful
|
|
||||||
func Test_matchesRouteByPathTokens(t *testing.T) {
|
|
||||||
router := CurlyRouter{}
|
|
||||||
for i, each := range routeMatchers {
|
|
||||||
routeToks := tokenizePath(each.route)
|
|
||||||
reqToks := tokenizePath(each.path)
|
|
||||||
matches, pCount, sCount := router.matchesRouteByPathTokens(routeToks, reqToks)
|
|
||||||
if matches != each.matches {
|
|
||||||
t.Fatalf("[%d] unexpected matches outcome route:%s, path:%s, matches:%v", i, each.route, each.path, matches)
|
|
||||||
}
|
|
||||||
if pCount != each.paramCount {
|
|
||||||
t.Fatalf("[%d] unexpected paramCount got:%d want:%d ", i, pCount, each.paramCount)
|
|
||||||
}
|
|
||||||
if sCount != each.staticCount {
|
|
||||||
t.Fatalf("[%d] unexpected staticCount got:%d want:%d ", i, sCount, each.staticCount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear && go test -v -test.run TestExtractParameters_Wildcard1 ...restful
|
|
||||||
func TestExtractParameters_Wildcard1(t *testing.T) {
|
|
||||||
params := doExtractParams("/fixed/{var:*}", 2, "/fixed/remainder", t)
|
|
||||||
if params["var"] != "remainder" {
|
|
||||||
t.Errorf("parameter mismatch var: %s", params["var"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear && go test -v -test.run TestExtractParameters_Wildcard2 ...restful
|
|
||||||
func TestExtractParameters_Wildcard2(t *testing.T) {
|
|
||||||
params := doExtractParams("/fixed/{var:*}", 2, "/fixed/remain/der", t)
|
|
||||||
if params["var"] != "remain/der" {
|
|
||||||
t.Errorf("parameter mismatch var: %s", params["var"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear && go test -v -test.run TestExtractParameters_Wildcard3 ...restful
|
|
||||||
func TestExtractParameters_Wildcard3(t *testing.T) {
|
|
||||||
params := doExtractParams("/static/{var:*}", 2, "/static/test/sub/hi.html", t)
|
|
||||||
if params["var"] != "test/sub/hi.html" {
|
|
||||||
t.Errorf("parameter mismatch var: %s", params["var"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear && go test -v -test.run TestCurly_ISSUE_34 ...restful
|
|
||||||
func TestCurly_ISSUE_34(t *testing.T) {
|
|
||||||
ws1 := new(WebService).Path("/")
|
|
||||||
ws1.Route(ws1.GET("/{type}/{id}").To(curlyDummy))
|
|
||||||
ws1.Route(ws1.GET("/network/{id}").To(curlyDummy))
|
|
||||||
croutes := CurlyRouter{}.selectRoutes(ws1, tokenizePath("/network/12"))
|
|
||||||
if len(croutes) != 2 {
|
|
||||||
t.Fatal("expected 2 routes")
|
|
||||||
}
|
|
||||||
if got, want := croutes[0].route.Path, "/network/{id}"; got != want {
|
|
||||||
t.Errorf("got %v want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear && go test -v -test.run TestCurly_ISSUE_34_2 ...restful
|
|
||||||
func TestCurly_ISSUE_34_2(t *testing.T) {
|
|
||||||
ws1 := new(WebService)
|
|
||||||
ws1.Route(ws1.GET("/network/{id}").To(curlyDummy))
|
|
||||||
ws1.Route(ws1.GET("/{type}/{id}").To(curlyDummy))
|
|
||||||
croutes := CurlyRouter{}.selectRoutes(ws1, tokenizePath("/network/12"))
|
|
||||||
if len(croutes) != 2 {
|
|
||||||
t.Fatal("expected 2 routes")
|
|
||||||
}
|
|
||||||
if got, want := croutes[0].route.Path, "/network/{id}"; got != want {
|
|
||||||
t.Errorf("got %v want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear && go test -v -test.run TestCurly_JsonHtml ...restful
|
|
||||||
func TestCurly_JsonHtml(t *testing.T) {
|
|
||||||
ws1 := new(WebService)
|
|
||||||
ws1.Path("/")
|
|
||||||
ws1.Route(ws1.GET("/some.html").To(curlyDummy).Consumes("*/*").Produces("text/html"))
|
|
||||||
req, _ := http.NewRequest("GET", "/some.html", nil)
|
|
||||||
req.Header.Set("Accept", "application/json")
|
|
||||||
_, route, err := CurlyRouter{}.SelectRoute([]*WebService{ws1}, req)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("error expected")
|
|
||||||
}
|
|
||||||
if route != nil {
|
|
||||||
t.Error("no route expected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestCurly_ISSUE_137 ...restful
|
|
||||||
func TestCurly_ISSUE_137(t *testing.T) {
|
|
||||||
ws1 := new(WebService)
|
|
||||||
ws1.Route(ws1.GET("/hello").To(curlyDummy))
|
|
||||||
ws1.Path("/")
|
|
||||||
req, _ := http.NewRequest("GET", "/", nil)
|
|
||||||
_, route, _ := CurlyRouter{}.SelectRoute([]*WebService{ws1}, req)
|
|
||||||
t.Log(route)
|
|
||||||
if route != nil {
|
|
||||||
t.Error("no route expected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestCurly_ISSUE_137_2 ...restful
|
|
||||||
func TestCurly_ISSUE_137_2(t *testing.T) {
|
|
||||||
ws1 := new(WebService)
|
|
||||||
ws1.Route(ws1.GET("/hello").To(curlyDummy))
|
|
||||||
ws1.Path("/")
|
|
||||||
req, _ := http.NewRequest("GET", "/hello/bob", nil)
|
|
||||||
_, route, _ := CurlyRouter{}.SelectRoute([]*WebService{ws1}, req)
|
|
||||||
t.Log(route)
|
|
||||||
if route != nil {
|
|
||||||
t.Errorf("no route expected, got %v", route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func curlyDummy(req *Request, resp *Response) { io.WriteString(resp.ResponseWriter, "curlyDummy") }
|
|
185
vendor/github.com/emicklei/go-restful/doc.go
generated
vendored
185
vendor/github.com/emicklei/go-restful/doc.go
generated
vendored
@ -1,185 +0,0 @@
|
|||||||
/*
|
|
||||||
Package restful , a lean package for creating REST-style WebServices without magic.
|
|
||||||
|
|
||||||
WebServices and Routes
|
|
||||||
|
|
||||||
A WebService has a collection of Route objects that dispatch incoming Http Requests to a function calls.
|
|
||||||
Typically, a WebService has a root path (e.g. /users) and defines common MIME types for its routes.
|
|
||||||
WebServices must be added to a container (see below) in order to handler Http requests from a server.
|
|
||||||
|
|
||||||
A Route is defined by a HTTP method, an URL path and (optionally) the MIME types it consumes (Content-Type) and produces (Accept).
|
|
||||||
This package has the logic to find the best matching Route and if found, call its Function.
|
|
||||||
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.
|
|
||||||
Path("/users").
|
|
||||||
Consumes(restful.MIME_JSON, restful.MIME_XML).
|
|
||||||
Produces(restful.MIME_JSON, restful.MIME_XML)
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/{user-id}").To(u.findUser)) // u is a UserResource
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
// GET http://localhost:8080/users/1
|
|
||||||
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
|
|
||||||
id := request.PathParameter("user-id")
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
The (*Request, *Response) arguments provide functions for reading information from the request and writing information back to the response.
|
|
||||||
|
|
||||||
See the example https://github.com/emicklei/go-restful/blob/master/examples/restful-user-resource.go with a full implementation.
|
|
||||||
|
|
||||||
Regular expression matching Routes
|
|
||||||
|
|
||||||
A Route parameter can be specified using the format "uri/{var[:regexp]}" or the special version "uri/{var:*}" for matching the tail of the path.
|
|
||||||
For example, /persons/{name:[A-Z][A-Z]} can be used to restrict values for the parameter "name" to only contain capital alphabetic characters.
|
|
||||||
Regular expressions must use the standard Go syntax as described in the regexp package. (https://code.google.com/p/re2/wiki/Syntax)
|
|
||||||
This feature requires the use of a CurlyRouter.
|
|
||||||
|
|
||||||
Containers
|
|
||||||
|
|
||||||
A Container holds a collection of WebServices, Filters and a http.ServeMux for multiplexing http requests.
|
|
||||||
Using the statements "restful.Add(...) and restful.Filter(...)" will register WebServices and Filters to the Default Container.
|
|
||||||
The Default container of go-restful uses the http.DefaultServeMux.
|
|
||||||
You can create your own Container and create a new http.Server for that particular container.
|
|
||||||
|
|
||||||
container := restful.NewContainer()
|
|
||||||
server := &http.Server{Addr: ":8081", Handler: container}
|
|
||||||
|
|
||||||
Filters
|
|
||||||
|
|
||||||
A filter dynamically intercepts requests and responses to transform or use the information contained in the requests or responses.
|
|
||||||
You can use filters to perform generic logging, measurement, authentication, redirect, set response headers etc.
|
|
||||||
In the restful package there are three hooks into the request,response flow where filters can be added.
|
|
||||||
Each filter must define a FilterFunction:
|
|
||||||
|
|
||||||
func (req *restful.Request, resp *restful.Response, chain *restful.FilterChain)
|
|
||||||
|
|
||||||
Use the following statement to pass the request,response pair to the next filter or RouteFunction
|
|
||||||
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
|
|
||||||
Container Filters
|
|
||||||
|
|
||||||
These are processed before any registered WebService.
|
|
||||||
|
|
||||||
// install a (global) filter for the default container (processed before any webservice)
|
|
||||||
restful.Filter(globalLogging)
|
|
||||||
|
|
||||||
WebService Filters
|
|
||||||
|
|
||||||
These are processed before any Route of a WebService.
|
|
||||||
|
|
||||||
// install a webservice filter (processed before any route)
|
|
||||||
ws.Filter(webserviceLogging).Filter(measureTime)
|
|
||||||
|
|
||||||
|
|
||||||
Route Filters
|
|
||||||
|
|
||||||
These are processed before calling the function associated with the Route.
|
|
||||||
|
|
||||||
// install 2 chained route filters (processed before calling findUser)
|
|
||||||
ws.Route(ws.GET("/{user-id}").Filter(routeLogging).Filter(NewCountFilter().routeCounter).To(findUser))
|
|
||||||
|
|
||||||
See the example https://github.com/emicklei/go-restful/blob/master/examples/restful-filters.go with full implementations.
|
|
||||||
|
|
||||||
Response Encoding
|
|
||||||
|
|
||||||
Two encodings are supported: gzip and deflate. To enable this for all responses:
|
|
||||||
|
|
||||||
restful.DefaultContainer.EnableContentEncoding(true)
|
|
||||||
|
|
||||||
If a Http request includes the Accept-Encoding header then the response content will be compressed using the specified encoding.
|
|
||||||
Alternatively, you can create a Filter that performs the encoding and install it per WebService or Route.
|
|
||||||
|
|
||||||
See the example https://github.com/emicklei/go-restful/blob/master/examples/restful-encoding-filter.go
|
|
||||||
|
|
||||||
OPTIONS support
|
|
||||||
|
|
||||||
By installing a pre-defined container filter, your Webservice(s) can respond to the OPTIONS Http request.
|
|
||||||
|
|
||||||
Filter(OPTIONSFilter())
|
|
||||||
|
|
||||||
CORS
|
|
||||||
|
|
||||||
By installing the filter of a CrossOriginResourceSharing (CORS), your WebService(s) can handle CORS requests.
|
|
||||||
|
|
||||||
cors := CrossOriginResourceSharing{ExposeHeaders: []string{"X-My-Header"}, CookiesAllowed: false, Container: DefaultContainer}
|
|
||||||
Filter(cors.Filter)
|
|
||||||
|
|
||||||
Error Handling
|
|
||||||
|
|
||||||
Unexpected things happen. If a request cannot be processed because of a failure, your service needs to tell via the response what happened and why.
|
|
||||||
For this reason HTTP status codes exist and it is important to use the correct code in every exceptional situation.
|
|
||||||
|
|
||||||
400: Bad Request
|
|
||||||
|
|
||||||
If path or query parameters are not valid (content or type) then use http.StatusBadRequest.
|
|
||||||
|
|
||||||
404: Not Found
|
|
||||||
|
|
||||||
Despite a valid URI, the resource requested may not be available
|
|
||||||
|
|
||||||
500: Internal Server Error
|
|
||||||
|
|
||||||
If the application logic could not process the request (or write the response) then use http.StatusInternalServerError.
|
|
||||||
|
|
||||||
405: Method Not Allowed
|
|
||||||
|
|
||||||
The request has a valid URL but the method (GET,PUT,POST,...) is not allowed.
|
|
||||||
|
|
||||||
406: Not Acceptable
|
|
||||||
|
|
||||||
The request does not have or has an unknown Accept Header set for this operation.
|
|
||||||
|
|
||||||
415: Unsupported Media Type
|
|
||||||
|
|
||||||
The request does not have or has an unknown Content-Type Header set for this operation.
|
|
||||||
|
|
||||||
ServiceError
|
|
||||||
|
|
||||||
In addition to setting the correct (error) Http status code, you can choose to write a ServiceError message on the response.
|
|
||||||
|
|
||||||
Performance options
|
|
||||||
|
|
||||||
This package has several options that affect the performance of your service. It is important to understand them and how you can change it.
|
|
||||||
|
|
||||||
restful.DefaultContainer.DoNotRecover(false)
|
|
||||||
|
|
||||||
DoNotRecover controls whether panics will be caught to return HTTP 500.
|
|
||||||
If set to false, the container will recover from panics.
|
|
||||||
Default value is true
|
|
||||||
|
|
||||||
restful.SetCompressorProvider(NewBoundedCachedCompressors(20, 20))
|
|
||||||
|
|
||||||
If content encoding is enabled then the default strategy for getting new gzip/zlib writers and readers is to use a sync.Pool.
|
|
||||||
Because writers are expensive structures, performance is even more improved when using a preloaded cache. You can also inject your own implementation.
|
|
||||||
|
|
||||||
Trouble shooting
|
|
||||||
|
|
||||||
This package has the means to produce detail logging of the complete Http request matching process and filter invocation.
|
|
||||||
Enabling this feature requires you to set an implementation of restful.StdLogger (e.g. log.Logger) instance such as:
|
|
||||||
|
|
||||||
restful.TraceLogger(log.New(os.Stdout, "[restful] ", log.LstdFlags|log.Lshortfile))
|
|
||||||
|
|
||||||
Logging
|
|
||||||
|
|
||||||
The restful.SetLogger() method allows you to override the logger used by the package. By default restful
|
|
||||||
uses the standard library `log` package and logs to stdout. Different logging packages are supported as
|
|
||||||
long as they conform to `StdLogger` interface defined in the `log` sub-package, writing an adapter for your
|
|
||||||
preferred package is simple.
|
|
||||||
|
|
||||||
Resources
|
|
||||||
|
|
||||||
[project]: https://github.com/emicklei/go-restful
|
|
||||||
|
|
||||||
[examples]: https://github.com/emicklei/go-restful/blob/master/examples
|
|
||||||
|
|
||||||
[design]: http://ernestmicklei.com/2012/11/11/go-restful-api-design/
|
|
||||||
|
|
||||||
[showcases]: https://github.com/emicklei/mora, https://github.com/emicklei/landskape
|
|
||||||
|
|
||||||
(c) 2012-2015, http://ernestmicklei.com. MIT License
|
|
||||||
*/
|
|
||||||
package restful
|
|
41
vendor/github.com/emicklei/go-restful/doc_examples_test.go
generated
vendored
41
vendor/github.com/emicklei/go-restful/doc_examples_test.go
generated
vendored
@ -1,41 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
func ExampleOPTIONSFilter() {
|
|
||||||
// Install the OPTIONS filter on the default Container
|
|
||||||
Filter(OPTIONSFilter())
|
|
||||||
}
|
|
||||||
func ExampleContainer_OPTIONSFilter() {
|
|
||||||
// Install the OPTIONS filter on a Container
|
|
||||||
myContainer := new(Container)
|
|
||||||
myContainer.Filter(myContainer.OPTIONSFilter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleContainer() {
|
|
||||||
// The Default container of go-restful uses the http.DefaultServeMux.
|
|
||||||
// You can create your own Container using restful.NewContainer() and create a new http.Server for that particular container
|
|
||||||
|
|
||||||
ws := new(WebService)
|
|
||||||
wsContainer := NewContainer()
|
|
||||||
wsContainer.Add(ws)
|
|
||||||
server := &http.Server{Addr: ":8080", Handler: wsContainer}
|
|
||||||
server.ListenAndServe()
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleCrossOriginResourceSharing() {
|
|
||||||
// To install this filter on the Default Container use:
|
|
||||||
cors := CrossOriginResourceSharing{ExposeHeaders: []string{"X-My-Header"}, CookiesAllowed: false, Container: DefaultContainer}
|
|
||||||
Filter(cors.Filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleServiceError() {
|
|
||||||
resp := new(Response)
|
|
||||||
resp.WriteEntity(NewError(http.StatusBadRequest, "Non-integer {id} path parameter"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleBoundedCachedCompressors() {
|
|
||||||
// Register a compressor provider (gzip/deflate read/write) that uses
|
|
||||||
// a bounded cache with a maximum of 20 writers and 20 readers.
|
|
||||||
SetCompressorProvider(NewBoundedCachedCompressors(20, 20))
|
|
||||||
}
|
|
163
vendor/github.com/emicklei/go-restful/entity_accessors.go
generated
vendored
163
vendor/github.com/emicklei/go-restful/entity_accessors.go
generated
vendored
@ -1,163 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2015 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/xml"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EntityReaderWriter can read and write values using an encoding such as JSON,XML.
|
|
||||||
type EntityReaderWriter interface {
|
|
||||||
// Read a serialized version of the value from the request.
|
|
||||||
// The Request may have a decompressing reader. Depends on Content-Encoding.
|
|
||||||
Read(req *Request, v interface{}) error
|
|
||||||
|
|
||||||
// Write a serialized version of the value on the response.
|
|
||||||
// The Response may have a compressing writer. Depends on Accept-Encoding.
|
|
||||||
// status should be a valid Http Status code
|
|
||||||
Write(resp *Response, status int, v interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// entityAccessRegistry is a singleton
|
|
||||||
var entityAccessRegistry = &entityReaderWriters{
|
|
||||||
protection: new(sync.RWMutex),
|
|
||||||
accessors: map[string]EntityReaderWriter{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// entityReaderWriters associates MIME to an EntityReaderWriter
|
|
||||||
type entityReaderWriters struct {
|
|
||||||
protection *sync.RWMutex
|
|
||||||
accessors map[string]EntityReaderWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RegisterEntityAccessor(MIME_JSON, NewEntityAccessorJSON(MIME_JSON))
|
|
||||||
RegisterEntityAccessor(MIME_XML, NewEntityAccessorXML(MIME_XML))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterEntityAccessor add/overrides the ReaderWriter for encoding content with this MIME type.
|
|
||||||
func RegisterEntityAccessor(mime string, erw EntityReaderWriter) {
|
|
||||||
entityAccessRegistry.protection.Lock()
|
|
||||||
defer entityAccessRegistry.protection.Unlock()
|
|
||||||
entityAccessRegistry.accessors[mime] = erw
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEntityAccessorJSON returns a new EntityReaderWriter for accessing JSON content.
|
|
||||||
// This package is already initialized with such an accessor using the MIME_JSON contentType.
|
|
||||||
func NewEntityAccessorJSON(contentType string) EntityReaderWriter {
|
|
||||||
return entityJSONAccess{ContentType: contentType}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEntityAccessorXML returns a new EntityReaderWriter for accessing XML content.
|
|
||||||
// This package is already initialized with such an accessor using the MIME_XML contentType.
|
|
||||||
func NewEntityAccessorXML(contentType string) EntityReaderWriter {
|
|
||||||
return entityXMLAccess{ContentType: contentType}
|
|
||||||
}
|
|
||||||
|
|
||||||
// accessorAt returns the registered ReaderWriter for this MIME type.
|
|
||||||
func (r *entityReaderWriters) accessorAt(mime string) (EntityReaderWriter, bool) {
|
|
||||||
r.protection.RLock()
|
|
||||||
defer r.protection.RUnlock()
|
|
||||||
er, ok := r.accessors[mime]
|
|
||||||
if !ok {
|
|
||||||
// retry with reverse lookup
|
|
||||||
// more expensive but we are in an exceptional situation anyway
|
|
||||||
for k, v := range r.accessors {
|
|
||||||
if strings.Contains(mime, k) {
|
|
||||||
return v, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return er, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// entityXMLAccess is a EntityReaderWriter for XML encoding
|
|
||||||
type entityXMLAccess struct {
|
|
||||||
// This is used for setting the Content-Type header when writing
|
|
||||||
ContentType string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read unmarshalls the value from XML
|
|
||||||
func (e entityXMLAccess) Read(req *Request, v interface{}) error {
|
|
||||||
return xml.NewDecoder(req.Request.Body).Decode(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write marshalls the value to JSON and set the Content-Type Header.
|
|
||||||
func (e entityXMLAccess) Write(resp *Response, status int, v interface{}) error {
|
|
||||||
return writeXML(resp, status, e.ContentType, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeXML marshalls the value to JSON and set the Content-Type Header.
|
|
||||||
func writeXML(resp *Response, status int, contentType string, v interface{}) error {
|
|
||||||
if v == nil {
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
// do not write a nil representation
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if resp.prettyPrint {
|
|
||||||
// pretty output must be created and written explicitly
|
|
||||||
output, err := xml.MarshalIndent(v, " ", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Header().Set(HEADER_ContentType, contentType)
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
_, err = resp.Write([]byte(xml.Header))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = resp.Write(output)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// not-so-pretty
|
|
||||||
resp.Header().Set(HEADER_ContentType, contentType)
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
return xml.NewEncoder(resp).Encode(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// entityJSONAccess is a EntityReaderWriter for JSON encoding
|
|
||||||
type entityJSONAccess struct {
|
|
||||||
// This is used for setting the Content-Type header when writing
|
|
||||||
ContentType string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read unmarshalls the value from JSON
|
|
||||||
func (e entityJSONAccess) Read(req *Request, v interface{}) error {
|
|
||||||
decoder := json.NewDecoder(req.Request.Body)
|
|
||||||
decoder.UseNumber()
|
|
||||||
return decoder.Decode(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write marshalls the value to JSON and set the Content-Type Header.
|
|
||||||
func (e entityJSONAccess) Write(resp *Response, status int, v interface{}) error {
|
|
||||||
return writeJSON(resp, status, e.ContentType, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// write marshalls the value to JSON and set the Content-Type Header.
|
|
||||||
func writeJSON(resp *Response, status int, contentType string, v interface{}) error {
|
|
||||||
if v == nil {
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
// do not write a nil representation
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if resp.prettyPrint {
|
|
||||||
// pretty output must be created and written explicitly
|
|
||||||
output, err := json.MarshalIndent(v, " ", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp.Header().Set(HEADER_ContentType, contentType)
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
_, err = resp.Write(output)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// not-so-pretty
|
|
||||||
resp.Header().Set(HEADER_ContentType, contentType)
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
return json.NewEncoder(resp).Encode(v)
|
|
||||||
}
|
|
69
vendor/github.com/emicklei/go-restful/entity_accessors_test.go
generated
vendored
69
vendor/github.com/emicklei/go-restful/entity_accessors_test.go
generated
vendored
@ -1,69 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type keyvalue struct {
|
|
||||||
readCalled bool
|
|
||||||
writeCalled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kv *keyvalue) Read(req *Request, v interface{}) error {
|
|
||||||
//t := reflect.TypeOf(v)
|
|
||||||
//rv := reflect.ValueOf(v)
|
|
||||||
kv.readCalled = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kv *keyvalue) Write(resp *Response, status int, v interface{}) error {
|
|
||||||
t := reflect.TypeOf(v)
|
|
||||||
rv := reflect.ValueOf(v)
|
|
||||||
for ix := 0; ix < t.NumField(); ix++ {
|
|
||||||
sf := t.Field(ix)
|
|
||||||
io.WriteString(resp, sf.Name)
|
|
||||||
io.WriteString(resp, "=")
|
|
||||||
io.WriteString(resp, fmt.Sprintf("%v\n", rv.Field(ix).Interface()))
|
|
||||||
}
|
|
||||||
kv.writeCalled = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestKeyValueEncoding ...restful
|
|
||||||
func TestKeyValueEncoding(t *testing.T) {
|
|
||||||
type Book struct {
|
|
||||||
Title string
|
|
||||||
Author string
|
|
||||||
PublishedYear int
|
|
||||||
}
|
|
||||||
kv := new(keyvalue)
|
|
||||||
RegisterEntityAccessor("application/kv", kv)
|
|
||||||
b := Book{"Singing for Dummies", "john doe", 2015}
|
|
||||||
|
|
||||||
// Write
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
// Accept Produces
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: "application/kv,*/*;q=0.8", routeProduces: []string{"application/kv"}, prettyPrint: true}
|
|
||||||
resp.WriteEntity(b)
|
|
||||||
t.Log(string(httpWriter.Body.Bytes()))
|
|
||||||
if !kv.writeCalled {
|
|
||||||
t.Error("Write never called")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read
|
|
||||||
bodyReader := bytes.NewReader(httpWriter.Body.Bytes())
|
|
||||||
httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
|
|
||||||
httpRequest.Header.Set("Content-Type", "application/kv; charset=UTF-8")
|
|
||||||
request := NewRequest(httpRequest)
|
|
||||||
var bb Book
|
|
||||||
request.ReadEntity(&bb)
|
|
||||||
if !kv.readCalled {
|
|
||||||
t.Error("Read never called")
|
|
||||||
}
|
|
||||||
}
|
|
1
vendor/github.com/emicklei/go-restful/examples/.goconvey
generated
vendored
1
vendor/github.com/emicklei/go-restful/examples/.goconvey
generated
vendored
@ -1 +0,0 @@
|
|||||||
ignore
|
|
1
vendor/github.com/emicklei/go-restful/examples/google_app_engine/.goconvey
generated
vendored
1
vendor/github.com/emicklei/go-restful/examples/google_app_engine/.goconvey
generated
vendored
@ -1 +0,0 @@
|
|||||||
ignore
|
|
20
vendor/github.com/emicklei/go-restful/examples/google_app_engine/app.yaml
generated
vendored
20
vendor/github.com/emicklei/go-restful/examples/google_app_engine/app.yaml
generated
vendored
@ -1,20 +0,0 @@
|
|||||||
#
|
|
||||||
# Include your application ID here
|
|
||||||
#
|
|
||||||
application: <your_app_id>
|
|
||||||
version: 1
|
|
||||||
runtime: go
|
|
||||||
api_version: go1
|
|
||||||
|
|
||||||
handlers:
|
|
||||||
#
|
|
||||||
# Regex for all swagger files to make as static content.
|
|
||||||
# You should create the folder static/swagger and copy
|
|
||||||
# swagger-ui into it.
|
|
||||||
#
|
|
||||||
- url: /apidocs/(.*?)/(.*\.(js|html|css))
|
|
||||||
static_files: static/swagger/\1/\2
|
|
||||||
upload: static/swagger/(.*?)/(.*\.(js|html|css))
|
|
||||||
|
|
||||||
- url: /.*
|
|
||||||
script: _go_app
|
|
1
vendor/github.com/emicklei/go-restful/examples/google_app_engine/datastore/.goconvey
generated
vendored
1
vendor/github.com/emicklei/go-restful/examples/google_app_engine/datastore/.goconvey
generated
vendored
@ -1 +0,0 @@
|
|||||||
ignore
|
|
18
vendor/github.com/emicklei/go-restful/examples/google_app_engine/datastore/app.yaml
generated
vendored
18
vendor/github.com/emicklei/go-restful/examples/google_app_engine/datastore/app.yaml
generated
vendored
@ -1,18 +0,0 @@
|
|||||||
application: <your_app_id>
|
|
||||||
version: 1
|
|
||||||
runtime: go
|
|
||||||
api_version: go1
|
|
||||||
|
|
||||||
handlers:
|
|
||||||
# Regex for all swagger files to make as static content.
|
|
||||||
# You should create the folder static/swagger and copy
|
|
||||||
# swagger-ui into it.
|
|
||||||
#
|
|
||||||
- url: /apidocs/(.*?)/(.*\.(js|html|css))
|
|
||||||
static_files: static/swagger/\1/\2
|
|
||||||
upload: static/swagger/(.*?)/(.*\.(js|html|css))
|
|
||||||
|
|
||||||
# Catch all.
|
|
||||||
- url: /.*
|
|
||||||
script: _go_app
|
|
||||||
login: required
|
|
267
vendor/github.com/emicklei/go-restful/examples/google_app_engine/datastore/main.go
generated
vendored
267
vendor/github.com/emicklei/go-restful/examples/google_app_engine/datastore/main.go
generated
vendored
@ -1,267 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"github.com/emicklei/go-restful-swagger12"
|
|
||||||
"google.golang.org/appengine"
|
|
||||||
"google.golang.org/appengine/datastore"
|
|
||||||
"google.golang.org/appengine/user"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example demonstrates a reasonably complete suite of RESTful operations backed
|
|
||||||
// by DataStore on Google App Engine.
|
|
||||||
|
|
||||||
// Our simple example struct.
|
|
||||||
type Profile struct {
|
|
||||||
LastModified time.Time `json:"-" xml:"-"`
|
|
||||||
Email string `json:"-" xml:"-"`
|
|
||||||
FirstName string `json:"first_name" xml:"first-name"`
|
|
||||||
NickName string `json:"nick_name" xml:"nick-name"`
|
|
||||||
LastName string `json:"last_name" xml:"last-name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProfileApi struct {
|
|
||||||
Path string
|
|
||||||
}
|
|
||||||
|
|
||||||
func gaeUrl() string {
|
|
||||||
if appengine.IsDevAppServer() {
|
|
||||||
return "http://localhost:8080"
|
|
||||||
} else {
|
|
||||||
// Include your URL on App Engine here.
|
|
||||||
// I found no way to get AppID without appengine.Context and this always
|
|
||||||
// based on a http.Request.
|
|
||||||
return "http://federatedservices.appspot.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
u := ProfileApi{Path: "/profiles"}
|
|
||||||
u.register()
|
|
||||||
|
|
||||||
// Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API
|
|
||||||
// You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
|
|
||||||
// Open <your_app_id>.appspot.com/apidocs and enter
|
|
||||||
// Place the Swagger UI files into a folder called static/swagger if you wish to use Swagger
|
|
||||||
// http://<your_app_id>.appspot.com/apidocs.json in the api input field.
|
|
||||||
// For testing, you can use http://localhost:8080/apidocs.json
|
|
||||||
config := swagger.Config{
|
|
||||||
// You control what services are visible
|
|
||||||
WebServices: restful.RegisteredWebServices(),
|
|
||||||
WebServicesUrl: gaeUrl(),
|
|
||||||
ApiPath: "/apidocs.json",
|
|
||||||
|
|
||||||
// Optionally, specify where the UI is located
|
|
||||||
SwaggerPath: "/apidocs/",
|
|
||||||
|
|
||||||
// GAE support static content which is configured in your app.yaml.
|
|
||||||
// This example expect the swagger-ui in static/swagger so you should place it there :)
|
|
||||||
SwaggerFilePath: "static/swagger"}
|
|
||||||
swagger.InstallSwaggerService(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u ProfileApi) register() {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
|
|
||||||
ws.
|
|
||||||
Path(u.Path).
|
|
||||||
// You can specify consumes and produces per route as well.
|
|
||||||
Consumes(restful.MIME_JSON, restful.MIME_XML).
|
|
||||||
Produces(restful.MIME_JSON, restful.MIME_XML)
|
|
||||||
|
|
||||||
ws.Route(ws.POST("").To(u.insert).
|
|
||||||
// Swagger documentation.
|
|
||||||
Doc("insert a new profile").
|
|
||||||
Param(ws.BodyParameter("Profile", "representation of a profile").DataType("main.Profile")).
|
|
||||||
Reads(Profile{}))
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/{profile-id}").To(u.read).
|
|
||||||
// Swagger documentation.
|
|
||||||
Doc("read a profile").
|
|
||||||
Param(ws.PathParameter("profile-id", "identifier for a profile").DataType("string")).
|
|
||||||
Writes(Profile{}))
|
|
||||||
|
|
||||||
ws.Route(ws.PUT("/{profile-id}").To(u.update).
|
|
||||||
// Swagger documentation.
|
|
||||||
Doc("update an existing profile").
|
|
||||||
Param(ws.PathParameter("profile-id", "identifier for a profile").DataType("string")).
|
|
||||||
Param(ws.BodyParameter("Profile", "representation of a profile").DataType("main.Profile")).
|
|
||||||
Reads(Profile{}))
|
|
||||||
|
|
||||||
ws.Route(ws.DELETE("/{profile-id}").To(u.remove).
|
|
||||||
// Swagger documentation.
|
|
||||||
Doc("remove a profile").
|
|
||||||
Param(ws.PathParameter("profile-id", "identifier for a profile").DataType("string")))
|
|
||||||
|
|
||||||
restful.Add(ws)
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST http://localhost:8080/profiles
|
|
||||||
// {"first_name": "Ivan", "nick_name": "Socks", "last_name": "Hawkes"}
|
|
||||||
//
|
|
||||||
func (u *ProfileApi) insert(r *restful.Request, w *restful.Response) {
|
|
||||||
c := appengine.NewContext(r.Request)
|
|
||||||
|
|
||||||
// Marshall the entity from the request into a struct.
|
|
||||||
p := new(Profile)
|
|
||||||
err := r.ReadEntity(&p)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteError(http.StatusNotAcceptable, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure we start with a sensible value for this field.
|
|
||||||
p.LastModified = time.Now()
|
|
||||||
|
|
||||||
// The profile belongs to this user.
|
|
||||||
p.Email = user.Current(c).String()
|
|
||||||
|
|
||||||
k, err := datastore.Put(c, datastore.NewIncompleteKey(c, "profiles", nil), p)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let them know the location of the newly created resource.
|
|
||||||
// TODO: Use a safe Url path append function.
|
|
||||||
w.AddHeader("Location", u.Path+"/"+k.Encode())
|
|
||||||
|
|
||||||
// Return the resultant entity.
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
|
||||||
w.WriteEntity(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET http://localhost:8080/profiles/ahdkZXZ-ZmVkZXJhdGlvbi1zZXJ2aWNlc3IVCxIIcHJvZmlsZXMYgICAgICAgAoM
|
|
||||||
//
|
|
||||||
func (u ProfileApi) read(r *restful.Request, w *restful.Response) {
|
|
||||||
c := appengine.NewContext(r.Request)
|
|
||||||
|
|
||||||
// Decode the request parameter to determine the key for the entity.
|
|
||||||
k, err := datastore.DecodeKey(r.PathParameter("profile-id"))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the entity from the datastore.
|
|
||||||
p := Profile{}
|
|
||||||
if err := datastore.Get(c, k, &p); err != nil {
|
|
||||||
if err.Error() == "datastore: no such entity" {
|
|
||||||
http.Error(w, err.Error(), http.StatusNotFound)
|
|
||||||
} else {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check we own the profile before allowing them to view it.
|
|
||||||
// Optionally, return a 404 instead to help prevent guessing ids.
|
|
||||||
// TODO: Allow admins access.
|
|
||||||
if p.Email != user.Current(c).String() {
|
|
||||||
http.Error(w, "You do not have access to this resource", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteEntity(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PUT http://localhost:8080/profiles/ahdkZXZ-ZmVkZXJhdGlvbi1zZXJ2aWNlc3IVCxIIcHJvZmlsZXMYgICAgICAgAoM
|
|
||||||
// {"first_name": "Ivan", "nick_name": "Socks", "last_name": "Hawkes"}
|
|
||||||
//
|
|
||||||
func (u *ProfileApi) update(r *restful.Request, w *restful.Response) {
|
|
||||||
c := appengine.NewContext(r.Request)
|
|
||||||
|
|
||||||
// Decode the request parameter to determine the key for the entity.
|
|
||||||
k, err := datastore.DecodeKey(r.PathParameter("profile-id"))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshall the entity from the request into a struct.
|
|
||||||
p := new(Profile)
|
|
||||||
err = r.ReadEntity(&p)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteError(http.StatusNotAcceptable, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the old entity from the datastore.
|
|
||||||
old := Profile{}
|
|
||||||
if err := datastore.Get(c, k, &old); err != nil {
|
|
||||||
if err.Error() == "datastore: no such entity" {
|
|
||||||
http.Error(w, err.Error(), http.StatusNotFound)
|
|
||||||
} else {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check we own the profile before allowing them to update it.
|
|
||||||
// Optionally, return a 404 instead to help prevent guessing ids.
|
|
||||||
// TODO: Allow admins access.
|
|
||||||
if old.Email != user.Current(c).String() {
|
|
||||||
http.Error(w, "You do not have access to this resource", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since the whole entity is re-written, we need to assign any invariant fields again
|
|
||||||
// e.g. the owner of the entity.
|
|
||||||
p.Email = user.Current(c).String()
|
|
||||||
|
|
||||||
// Keep track of the last modification date.
|
|
||||||
p.LastModified = time.Now()
|
|
||||||
|
|
||||||
// Attempt to overwrite the old entity.
|
|
||||||
_, err = datastore.Put(c, k, p)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let them know it succeeded.
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE http://localhost:8080/profiles/ahdkZXZ-ZmVkZXJhdGlvbi1zZXJ2aWNlc3IVCxIIcHJvZmlsZXMYgICAgICAgAoM
|
|
||||||
//
|
|
||||||
func (u *ProfileApi) remove(r *restful.Request, w *restful.Response) {
|
|
||||||
c := appengine.NewContext(r.Request)
|
|
||||||
|
|
||||||
// Decode the request parameter to determine the key for the entity.
|
|
||||||
k, err := datastore.DecodeKey(r.PathParameter("profile-id"))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the old entity from the datastore.
|
|
||||||
old := Profile{}
|
|
||||||
if err := datastore.Get(c, k, &old); err != nil {
|
|
||||||
if err.Error() == "datastore: no such entity" {
|
|
||||||
http.Error(w, err.Error(), http.StatusNotFound)
|
|
||||||
} else {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check we own the profile before allowing them to delete it.
|
|
||||||
// Optionally, return a 404 instead to help prevent guessing ids.
|
|
||||||
// TODO: Allow admins access.
|
|
||||||
if old.Email != user.Current(c).String() {
|
|
||||||
http.Error(w, "You do not have access to this resource", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the entity.
|
|
||||||
if err := datastore.Delete(c, k); err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success notification.
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mjibson/appstats"
|
|
||||||
)
|
|
||||||
|
|
||||||
func stats(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
||||||
c := appstats.NewContext(req.Request)
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
c.Stats.Status = resp.StatusCode()
|
|
||||||
c.Save()
|
|
||||||
}
|
|
162
vendor/github.com/emicklei/go-restful/examples/google_app_engine/restful-user-service.go
generated
vendored
162
vendor/github.com/emicklei/go-restful/examples/google_app_engine/restful-user-service.go
generated
vendored
@ -1,162 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"github.com/emicklei/go-restful-swagger12"
|
|
||||||
"google.golang.org/appengine"
|
|
||||||
"google.golang.org/appengine/memcache"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example is functionally the same as ../restful-user-service.go
|
|
||||||
// but it`s supposed to run on Goole App Engine (GAE)
|
|
||||||
//
|
|
||||||
// contributed by ivanhawkes
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Id, Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserService struct {
|
|
||||||
// normally one would use DAO (data access object)
|
|
||||||
// but in this example we simple use memcache.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u UserService) Register() {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
|
|
||||||
ws.
|
|
||||||
Path("/users").
|
|
||||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
|
||||||
Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/{user-id}").To(u.findUser).
|
|
||||||
// docs
|
|
||||||
Doc("get a user").
|
|
||||||
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
|
|
||||||
Writes(User{})) // on the response
|
|
||||||
|
|
||||||
ws.Route(ws.PATCH("").To(u.updateUser).
|
|
||||||
// docs
|
|
||||||
Doc("update a user").
|
|
||||||
Reads(User{})) // from the request
|
|
||||||
|
|
||||||
ws.Route(ws.PUT("/{user-id}").To(u.createUser).
|
|
||||||
// docs
|
|
||||||
Doc("create a user").
|
|
||||||
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
|
|
||||||
Reads(User{})) // from the request
|
|
||||||
|
|
||||||
ws.Route(ws.DELETE("/{user-id}").To(u.removeUser).
|
|
||||||
// docs
|
|
||||||
Doc("delete a user").
|
|
||||||
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")))
|
|
||||||
|
|
||||||
restful.Add(ws)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET http://localhost:8080/users/1
|
|
||||||
//
|
|
||||||
func (u UserService) findUser(request *restful.Request, response *restful.Response) {
|
|
||||||
c := appengine.NewContext(request.Request)
|
|
||||||
id := request.PathParameter("user-id")
|
|
||||||
usr := new(User)
|
|
||||||
_, err := memcache.Gob.Get(c, id, &usr)
|
|
||||||
if err != nil || len(usr.Id) == 0 {
|
|
||||||
response.WriteErrorString(http.StatusNotFound, "User could not be found.")
|
|
||||||
} else {
|
|
||||||
response.WriteEntity(usr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PATCH http://localhost:8080/users
|
|
||||||
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
|
|
||||||
//
|
|
||||||
func (u *UserService) updateUser(request *restful.Request, response *restful.Response) {
|
|
||||||
c := appengine.NewContext(request.Request)
|
|
||||||
usr := new(User)
|
|
||||||
err := request.ReadEntity(&usr)
|
|
||||||
if err == nil {
|
|
||||||
item := &memcache.Item{
|
|
||||||
Key: usr.Id,
|
|
||||||
Object: &usr,
|
|
||||||
}
|
|
||||||
err = memcache.Gob.Set(c, item)
|
|
||||||
if err != nil {
|
|
||||||
response.WriteError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
response.WriteEntity(usr)
|
|
||||||
} else {
|
|
||||||
response.WriteError(http.StatusInternalServerError, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PUT http://localhost:8080/users/1
|
|
||||||
// <User><Id>1</Id><Name>Melissa</Name></User>
|
|
||||||
//
|
|
||||||
func (u *UserService) createUser(request *restful.Request, response *restful.Response) {
|
|
||||||
c := appengine.NewContext(request.Request)
|
|
||||||
usr := User{Id: request.PathParameter("user-id")}
|
|
||||||
err := request.ReadEntity(&usr)
|
|
||||||
if err == nil {
|
|
||||||
item := &memcache.Item{
|
|
||||||
Key: usr.Id,
|
|
||||||
Object: &usr,
|
|
||||||
}
|
|
||||||
err = memcache.Gob.Add(c, item)
|
|
||||||
if err != nil {
|
|
||||||
response.WriteError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
response.WriteHeader(http.StatusCreated)
|
|
||||||
response.WriteEntity(usr)
|
|
||||||
} else {
|
|
||||||
response.WriteError(http.StatusInternalServerError, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE http://localhost:8080/users/1
|
|
||||||
//
|
|
||||||
func (u *UserService) removeUser(request *restful.Request, response *restful.Response) {
|
|
||||||
c := appengine.NewContext(request.Request)
|
|
||||||
id := request.PathParameter("user-id")
|
|
||||||
err := memcache.Delete(c, id)
|
|
||||||
if err != nil {
|
|
||||||
response.WriteError(http.StatusInternalServerError, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getGaeURL() string {
|
|
||||||
if appengine.IsDevAppServer() {
|
|
||||||
return "http://localhost:8080"
|
|
||||||
} else {
|
|
||||||
/**
|
|
||||||
* Include your URL on App Engine here.
|
|
||||||
* I found no way to get AppID without appengine.Context and this always
|
|
||||||
* based on a http.Request.
|
|
||||||
*/
|
|
||||||
return "http://<your_app_id>.appspot.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
u := UserService{}
|
|
||||||
u.Register()
|
|
||||||
|
|
||||||
// Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API
|
|
||||||
// You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
|
|
||||||
// Open <your_app_id>.appspot.com/apidocs and enter http://<your_app_id>.appspot.com/apidocs.json in the api input field.
|
|
||||||
config := swagger.Config{
|
|
||||||
WebServices: restful.RegisteredWebServices(), // you control what services are visible
|
|
||||||
WebServicesUrl: getGaeURL(),
|
|
||||||
ApiPath: "/apidocs.json",
|
|
||||||
|
|
||||||
// Optionally, specify where the UI is located
|
|
||||||
SwaggerPath: "/apidocs/",
|
|
||||||
// GAE support static content which is configured in your app.yaml.
|
|
||||||
// This example expect the swagger-ui in static/swagger so you should place it there :)
|
|
||||||
SwaggerFilePath: "static/swagger"}
|
|
||||||
swagger.InstallSwaggerService(config)
|
|
||||||
}
|
|
7
vendor/github.com/emicklei/go-restful/examples/home.html
generated
vendored
7
vendor/github.com/emicklei/go-restful/examples/home.html
generated
vendored
@ -1,7 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h1>{{.Text}}</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
34
vendor/github.com/emicklei/go-restful/examples/msgpack/msgpack_entity.go
generated
vendored
34
vendor/github.com/emicklei/go-restful/examples/msgpack/msgpack_entity.go
generated
vendored
@ -1,34 +0,0 @@
|
|||||||
package restPack
|
|
||||||
|
|
||||||
import (
|
|
||||||
restful "github.com/emicklei/go-restful"
|
|
||||||
"gopkg.in/vmihailenco/msgpack.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const MIME_MSGPACK = "application/x-msgpack" // Accept or Content-Type used in Consumes() and/or Produces()
|
|
||||||
|
|
||||||
// NewEntityAccessorMPack returns a new EntityReaderWriter for accessing MessagePack content.
|
|
||||||
// This package is not initialized with such an accessor using the MIME_MSGPACK contentType.
|
|
||||||
func NewEntityAccessorMsgPack() restful.EntityReaderWriter {
|
|
||||||
return entityMsgPackAccess{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// entityOctetAccess is a EntityReaderWriter for Octet encoding
|
|
||||||
type entityMsgPackAccess struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read unmarshalls the value from byte slice and using msgpack to unmarshal
|
|
||||||
func (e entityMsgPackAccess) Read(req *restful.Request, v interface{}) error {
|
|
||||||
return msgpack.NewDecoder(req.Request.Body).Decode(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write marshals the value to byte slice and set the Content-Type Header.
|
|
||||||
func (e entityMsgPackAccess) Write(resp *restful.Response, status int, v interface{}) error {
|
|
||||||
if v == nil {
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
// do not write a nil representation
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
resp.WriteHeader(status)
|
|
||||||
return msgpack.NewEncoder(resp).Encode(v)
|
|
||||||
}
|
|
160
vendor/github.com/emicklei/go-restful/examples/msgpack/msgpack_entity_test.go
generated
vendored
160
vendor/github.com/emicklei/go-restful/examples/msgpack/msgpack_entity_test.go
generated
vendored
@ -1,160 +0,0 @@
|
|||||||
package restPack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
restful "github.com/emicklei/go-restful"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMsgPack(t *testing.T) {
|
|
||||||
|
|
||||||
// register msg pack entity
|
|
||||||
restful.RegisterEntityAccessor(MIME_MSGPACK, NewEntityAccessorMsgPack())
|
|
||||||
type Tool struct {
|
|
||||||
Name string
|
|
||||||
Vendor string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
mpack := &Tool{Name: "json", Vendor: "apple"}
|
|
||||||
resp := restful.NewResponse(httpWriter)
|
|
||||||
resp.SetRequestAccepts("application/x-msgpack,*/*;q=0.8")
|
|
||||||
|
|
||||||
err := resp.WriteEntity(mpack)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("err %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read
|
|
||||||
bodyReader := bytes.NewReader(httpWriter.Body.Bytes())
|
|
||||||
httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
|
|
||||||
httpRequest.Header.Set("Content-Type", MIME_MSGPACK)
|
|
||||||
request := restful.NewRequest(httpRequest)
|
|
||||||
readMsgPack := new(Tool)
|
|
||||||
err = request.ReadEntity(&readMsgPack)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("err %v", err)
|
|
||||||
}
|
|
||||||
if equal := reflect.DeepEqual(mpack, readMsgPack); !equal {
|
|
||||||
t.Fatalf("should not be error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithWebService(t *testing.T) {
|
|
||||||
serverURL := "http://127.0.0.1:8090"
|
|
||||||
go func() {
|
|
||||||
runRestfulMsgPackRouterServer()
|
|
||||||
}()
|
|
||||||
if err := waitForServerUp(serverURL); err != nil {
|
|
||||||
t.Errorf("%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// send a post request
|
|
||||||
userData := user{Id: "0001", Name: "Tony"}
|
|
||||||
msgPackData, err := msgpack.Marshal(userData)
|
|
||||||
req, err := http.NewRequest("POST", serverURL+"/test/msgpack", bytes.NewBuffer(msgPackData))
|
|
||||||
req.Header.Set("Content-Type", MIME_MSGPACK)
|
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error in sending req: %v", err)
|
|
||||||
}
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
t.Errorf("unexpected response: %v, expected: %v", resp.StatusCode, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
ur := &userResponse{}
|
|
||||||
expectMsgPackDocument(t, resp, ur)
|
|
||||||
if ur.Status != statusActive {
|
|
||||||
t.Fatalf("should not error")
|
|
||||||
}
|
|
||||||
log.Printf("user response:%v", ur)
|
|
||||||
}
|
|
||||||
|
|
||||||
func expectMsgPackDocument(t *testing.T, r *http.Response, doc interface{}) {
|
|
||||||
data, err := ioutil.ReadAll(r.Body)
|
|
||||||
defer r.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("ExpectMsgPackDocument: unable to read response body :%v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// put the body back for re-reads
|
|
||||||
r.Body = ioutil.NopCloser(bytes.NewReader(data))
|
|
||||||
|
|
||||||
err = msgpack.Unmarshal(data, doc)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("ExpectMsgPackDocument: unable to unmarshal MsgPack:%v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runRestfulMsgPackRouterServer() {
|
|
||||||
|
|
||||||
container := restful.NewContainer()
|
|
||||||
register(container)
|
|
||||||
|
|
||||||
log.Print("start listening on localhost:8090")
|
|
||||||
server := &http.Server{Addr: ":8090", Handler: container}
|
|
||||||
log.Fatal(server.ListenAndServe())
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForServerUp(serverURL string) error {
|
|
||||||
for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(5 * time.Second) {
|
|
||||||
_, err := http.Get(serverURL + "/")
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.New("waiting for server timed out")
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
statusActive = "active"
|
|
||||||
)
|
|
||||||
|
|
||||||
type user struct {
|
|
||||||
Id, Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
type userResponse struct {
|
|
||||||
Status string
|
|
||||||
}
|
|
||||||
|
|
||||||
func register(container *restful.Container) {
|
|
||||||
restful.RegisterEntityAccessor(MIME_MSGPACK, NewEntityAccessorMsgPack())
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.
|
|
||||||
Path("/test").
|
|
||||||
Consumes(restful.MIME_JSON, MIME_MSGPACK).
|
|
||||||
Produces(restful.MIME_JSON, MIME_MSGPACK)
|
|
||||||
// route user api
|
|
||||||
ws.Route(ws.POST("/msgpack").
|
|
||||||
To(do).
|
|
||||||
Reads(user{}).
|
|
||||||
Writes(userResponse{}))
|
|
||||||
container.Add(ws)
|
|
||||||
}
|
|
||||||
|
|
||||||
func do(request *restful.Request, response *restful.Response) {
|
|
||||||
u := &user{}
|
|
||||||
err := request.ReadEntity(u)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("should be no error, got:%v", err)
|
|
||||||
}
|
|
||||||
log.Printf("got:%v", u)
|
|
||||||
|
|
||||||
ur := &userResponse{Status: statusActive}
|
|
||||||
|
|
||||||
response.SetRequestAccepts(MIME_MSGPACK)
|
|
||||||
response.WriteEntity(ur)
|
|
||||||
}
|
|
68
vendor/github.com/emicklei/go-restful/examples/restful-CORS-filter.go
generated
vendored
68
vendor/github.com/emicklei/go-restful/examples/restful-CORS-filter.go
generated
vendored
@ -1,68 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Cross-origin resource sharing (CORS) is a mechanism that allows JavaScript on a web page
|
|
||||||
// to make XMLHttpRequests to another domain, not the domain the JavaScript originated from.
|
|
||||||
//
|
|
||||||
// http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
|
|
||||||
// http://enable-cors.org/server.html
|
|
||||||
//
|
|
||||||
// GET http://localhost:8080/users
|
|
||||||
//
|
|
||||||
// GET http://localhost:8080/users/1
|
|
||||||
//
|
|
||||||
// PUT http://localhost:8080/users/1
|
|
||||||
//
|
|
||||||
// DELETE http://localhost:8080/users/1
|
|
||||||
//
|
|
||||||
// OPTIONS http://localhost:8080/users/1 with Header "Origin" set to some domain and
|
|
||||||
|
|
||||||
type UserResource struct{}
|
|
||||||
|
|
||||||
func (u UserResource) RegisterTo(container *restful.Container) {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.
|
|
||||||
Path("/users").
|
|
||||||
Consumes("*/*").
|
|
||||||
Produces("*/*")
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/{user-id}").To(u.nop))
|
|
||||||
ws.Route(ws.POST("").To(u.nop))
|
|
||||||
ws.Route(ws.PUT("/{user-id}").To(u.nop))
|
|
||||||
ws.Route(ws.DELETE("/{user-id}").To(u.nop))
|
|
||||||
|
|
||||||
container.Add(ws)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u UserResource) nop(request *restful.Request, response *restful.Response) {
|
|
||||||
io.WriteString(response.ResponseWriter, "this would be a normal response")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
wsContainer := restful.NewContainer()
|
|
||||||
u := UserResource{}
|
|
||||||
u.RegisterTo(wsContainer)
|
|
||||||
|
|
||||||
// Add container filter to enable CORS
|
|
||||||
cors := restful.CrossOriginResourceSharing{
|
|
||||||
ExposeHeaders: []string{"X-My-Header"},
|
|
||||||
AllowedHeaders: []string{"Content-Type", "Accept"},
|
|
||||||
AllowedMethods: []string{"GET", "POST"},
|
|
||||||
CookiesAllowed: false,
|
|
||||||
Container: wsContainer}
|
|
||||||
wsContainer.Filter(cors.Filter)
|
|
||||||
|
|
||||||
// Add container filter to respond to OPTIONS
|
|
||||||
wsContainer.Filter(wsContainer.OPTIONSFilter)
|
|
||||||
|
|
||||||
log.Print("start listening on localhost:8080")
|
|
||||||
server := &http.Server{Addr: ":8080", Handler: wsContainer}
|
|
||||||
log.Fatal(server.ListenAndServe())
|
|
||||||
}
|
|
54
vendor/github.com/emicklei/go-restful/examples/restful-NCSA-logging.go
generated
vendored
54
vendor/github.com/emicklei/go-restful/examples/restful-NCSA-logging.go
generated
vendored
@ -1,54 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example shows how to create a filter that produces log lines
|
|
||||||
// according to the Common Log Format, also known as the NCSA standard.
|
|
||||||
//
|
|
||||||
// kindly contributed by leehambley
|
|
||||||
//
|
|
||||||
// GET http://localhost:8080/ping
|
|
||||||
|
|
||||||
var logger *log.Logger = log.New(os.Stdout, "", 0)
|
|
||||||
|
|
||||||
func NCSACommonLogFormatLogger() restful.FilterFunction {
|
|
||||||
return func(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
||||||
var username = "-"
|
|
||||||
if req.Request.URL.User != nil {
|
|
||||||
if name := req.Request.URL.User.Username(); name != "" {
|
|
||||||
username = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
logger.Printf("%s - %s [%s] \"%s %s %s\" %d %d",
|
|
||||||
strings.Split(req.Request.RemoteAddr, ":")[0],
|
|
||||||
username,
|
|
||||||
time.Now().Format("02/Jan/2006:15:04:05 -0700"),
|
|
||||||
req.Request.Method,
|
|
||||||
req.Request.URL.RequestURI(),
|
|
||||||
req.Request.Proto,
|
|
||||||
resp.StatusCode(),
|
|
||||||
resp.ContentLength(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Filter(NCSACommonLogFormatLogger())
|
|
||||||
ws.Route(ws.GET("/ping").To(hello))
|
|
||||||
restful.Add(ws)
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func hello(req *restful.Request, resp *restful.Response) {
|
|
||||||
io.WriteString(resp, "pong")
|
|
||||||
}
|
|
35
vendor/github.com/emicklei/go-restful/examples/restful-basic-authentication.go
generated
vendored
35
vendor/github.com/emicklei/go-restful/examples/restful-basic-authentication.go
generated
vendored
@ -1,35 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example shows how to create a (Route) Filter that performs Basic Authentication on the Http request.
|
|
||||||
//
|
|
||||||
// GET http://localhost:8080/secret
|
|
||||||
// and use admin,admin for the credentials
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Route(ws.GET("/secret").Filter(basicAuthenticate).To(secret))
|
|
||||||
restful.Add(ws)
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func basicAuthenticate(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
||||||
// usr/pwd = admin/admin
|
|
||||||
u, p, ok := req.Request.BasicAuth()
|
|
||||||
if !ok || u != "admin" || p != "admin" {
|
|
||||||
resp.AddHeader("WWW-Authenticate", "Basic realm=Protected Area")
|
|
||||||
resp.WriteErrorString(401, "401: Not Authorized")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func secret(req *restful.Request, resp *restful.Response) {
|
|
||||||
io.WriteString(resp, "42")
|
|
||||||
}
|
|
65
vendor/github.com/emicklei/go-restful/examples/restful-cpuprofiler-service.go
generated
vendored
65
vendor/github.com/emicklei/go-restful/examples/restful-cpuprofiler-service.go
generated
vendored
@ -1,65 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"runtime/pprof"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ProfilingService is a WebService that can start/stop a CPU profile and write results to a file
|
|
||||||
// GET /{rootPath}/start will activate CPU profiling
|
|
||||||
// GET /{rootPath}/stop will stop profiling
|
|
||||||
//
|
|
||||||
// NewProfileService("/profiler", "ace.prof").AddWebServiceTo(restful.DefaultContainer)
|
|
||||||
//
|
|
||||||
type ProfilingService struct {
|
|
||||||
rootPath string // the base (root) of the service, e.g. /profiler
|
|
||||||
cpuprofile string // the output filename to write profile results, e.g. myservice.prof
|
|
||||||
cpufile *os.File // if not nil, then profiling is active
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProfileService(rootPath string, outputFilename string) *ProfilingService {
|
|
||||||
ps := new(ProfilingService)
|
|
||||||
ps.rootPath = rootPath
|
|
||||||
ps.cpuprofile = outputFilename
|
|
||||||
return ps
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add this ProfileService to a restful Container
|
|
||||||
func (p ProfilingService) AddWebServiceTo(container *restful.Container) {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Path(p.rootPath).Consumes("*/*").Produces(restful.MIME_JSON)
|
|
||||||
ws.Route(ws.GET("/start").To(p.startProfiler))
|
|
||||||
ws.Route(ws.GET("/stop").To(p.stopProfiler))
|
|
||||||
container.Add(ws)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ProfilingService) startProfiler(req *restful.Request, resp *restful.Response) {
|
|
||||||
if p.cpufile != nil {
|
|
||||||
io.WriteString(resp.ResponseWriter, "[restful] CPU profiling already running")
|
|
||||||
return // error?
|
|
||||||
}
|
|
||||||
cpufile, err := os.Create(p.cpuprofile)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// remember for close
|
|
||||||
p.cpufile = cpufile
|
|
||||||
pprof.StartCPUProfile(cpufile)
|
|
||||||
io.WriteString(resp.ResponseWriter, "[restful] CPU profiling started, writing on:"+p.cpuprofile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ProfilingService) stopProfiler(req *restful.Request, resp *restful.Response) {
|
|
||||||
if p.cpufile == nil {
|
|
||||||
io.WriteString(resp.ResponseWriter, "[restful] CPU profiling not active")
|
|
||||||
return // error?
|
|
||||||
}
|
|
||||||
pprof.StopCPUProfile()
|
|
||||||
p.cpufile.Close()
|
|
||||||
p.cpufile = nil
|
|
||||||
io.WriteString(resp.ResponseWriter, "[restful] CPU profiling stopped, closing:"+p.cpuprofile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {} // exists for example compilation only
|
|
107
vendor/github.com/emicklei/go-restful/examples/restful-curly-router.go
generated
vendored
107
vendor/github.com/emicklei/go-restful/examples/restful-curly-router.go
generated
vendored
@ -1,107 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example has the same service definition as restful-user-resource
|
|
||||||
// but uses a different router (CurlyRouter) that does not use regular expressions
|
|
||||||
//
|
|
||||||
// POST http://localhost:8080/users
|
|
||||||
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
|
|
||||||
//
|
|
||||||
// GET http://localhost:8080/users/1
|
|
||||||
//
|
|
||||||
// PUT http://localhost:8080/users/1
|
|
||||||
// <User><Id>1</Id><Name>Melissa</Name></User>
|
|
||||||
//
|
|
||||||
// DELETE http://localhost:8080/users/1
|
|
||||||
//
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Id, Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserResource struct {
|
|
||||||
// normally one would use DAO (data access object)
|
|
||||||
users map[string]User
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u UserResource) Register(container *restful.Container) {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.
|
|
||||||
Path("/users").
|
|
||||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
|
||||||
Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/{user-id}").To(u.findUser))
|
|
||||||
ws.Route(ws.POST("").To(u.updateUser))
|
|
||||||
ws.Route(ws.PUT("/{user-id}").To(u.createUser))
|
|
||||||
ws.Route(ws.DELETE("/{user-id}").To(u.removeUser))
|
|
||||||
|
|
||||||
container.Add(ws)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET http://localhost:8080/users/1
|
|
||||||
//
|
|
||||||
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
|
|
||||||
id := request.PathParameter("user-id")
|
|
||||||
usr := u.users[id]
|
|
||||||
if len(usr.Id) == 0 {
|
|
||||||
response.AddHeader("Content-Type", "text/plain")
|
|
||||||
response.WriteErrorString(http.StatusNotFound, "User could not be found.")
|
|
||||||
} else {
|
|
||||||
response.WriteEntity(usr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST http://localhost:8080/users
|
|
||||||
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
|
|
||||||
//
|
|
||||||
func (u *UserResource) updateUser(request *restful.Request, response *restful.Response) {
|
|
||||||
usr := new(User)
|
|
||||||
err := request.ReadEntity(&usr)
|
|
||||||
if err == nil {
|
|
||||||
u.users[usr.Id] = *usr
|
|
||||||
response.WriteEntity(usr)
|
|
||||||
} else {
|
|
||||||
response.AddHeader("Content-Type", "text/plain")
|
|
||||||
response.WriteErrorString(http.StatusInternalServerError, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PUT http://localhost:8080/users/1
|
|
||||||
// <User><Id>1</Id><Name>Melissa</Name></User>
|
|
||||||
//
|
|
||||||
func (u *UserResource) createUser(request *restful.Request, response *restful.Response) {
|
|
||||||
usr := User{Id: request.PathParameter("user-id")}
|
|
||||||
err := request.ReadEntity(&usr)
|
|
||||||
if err == nil {
|
|
||||||
u.users[usr.Id] = usr
|
|
||||||
response.WriteHeaderAndEntity(http.StatusCreated, usr)
|
|
||||||
} else {
|
|
||||||
response.AddHeader("Content-Type", "text/plain")
|
|
||||||
response.WriteErrorString(http.StatusInternalServerError, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE http://localhost:8080/users/1
|
|
||||||
//
|
|
||||||
func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) {
|
|
||||||
id := request.PathParameter("user-id")
|
|
||||||
delete(u.users, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
wsContainer := restful.NewContainer()
|
|
||||||
wsContainer.Router(restful.CurlyRouter{})
|
|
||||||
u := UserResource{map[string]User{}}
|
|
||||||
u.Register(wsContainer)
|
|
||||||
|
|
||||||
log.Print("start listening on localhost:8080")
|
|
||||||
server := &http.Server{Addr: ":8080", Handler: wsContainer}
|
|
||||||
log.Fatal(server.ListenAndServe())
|
|
||||||
}
|
|
149
vendor/github.com/emicklei/go-restful/examples/restful-curly-router_test.go
generated
vendored
149
vendor/github.com/emicklei/go-restful/examples/restful-curly-router_test.go
generated
vendored
@ -1,149 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
)
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Id, Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserResource struct {
|
|
||||||
users map[string]User
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u UserResource) Register(container *restful.Container) {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.
|
|
||||||
Path("/users").
|
|
||||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
|
||||||
Produces(restful.MIME_JSON, restful.MIME_XML)
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/{user-id}").To(u.findUser))
|
|
||||||
ws.Route(ws.POST("").To(u.updateUser))
|
|
||||||
ws.Route(ws.PUT("/{user-id}").To(u.createUser))
|
|
||||||
ws.Route(ws.DELETE("/{user-id}").To(u.removeUser))
|
|
||||||
|
|
||||||
container.Add(ws)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET http://localhost:8090/users/1
|
|
||||||
//
|
|
||||||
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
|
|
||||||
id := request.PathParameter("user-id")
|
|
||||||
usr := u.users[id]
|
|
||||||
if len(usr.Id) == 0 {
|
|
||||||
response.AddHeader("Content-Type", "text/plain")
|
|
||||||
response.WriteErrorString(http.StatusNotFound, "User could not be found.")
|
|
||||||
} else {
|
|
||||||
response.WriteEntity(usr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST http://localhost:8090/users
|
|
||||||
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
|
|
||||||
//
|
|
||||||
func (u *UserResource) updateUser(request *restful.Request, response *restful.Response) {
|
|
||||||
usr := new(User)
|
|
||||||
err := request.ReadEntity(&usr)
|
|
||||||
if err == nil {
|
|
||||||
u.users[usr.Id] = *usr
|
|
||||||
response.WriteEntity(usr)
|
|
||||||
} else {
|
|
||||||
response.AddHeader("Content-Type", "text/plain")
|
|
||||||
response.WriteErrorString(http.StatusInternalServerError, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PUT http://localhost:8090/users/1
|
|
||||||
// <User><Id>1</Id><Name>Melissa</Name></User>
|
|
||||||
//
|
|
||||||
func (u *UserResource) createUser(request *restful.Request, response *restful.Response) {
|
|
||||||
usr := User{Id: request.PathParameter("user-id")}
|
|
||||||
err := request.ReadEntity(&usr)
|
|
||||||
if err == nil {
|
|
||||||
u.users[usr.Id] = usr
|
|
||||||
response.WriteHeader(http.StatusCreated)
|
|
||||||
response.WriteEntity(usr)
|
|
||||||
} else {
|
|
||||||
response.AddHeader("Content-Type", "text/plain")
|
|
||||||
response.WriteErrorString(http.StatusInternalServerError, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE http://localhost:8090/users/1
|
|
||||||
//
|
|
||||||
func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) {
|
|
||||||
id := request.PathParameter("user-id")
|
|
||||||
delete(u.users, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunRestfulCurlyRouterServer() {
|
|
||||||
wsContainer := restful.NewContainer()
|
|
||||||
wsContainer.Router(restful.CurlyRouter{})
|
|
||||||
u := UserResource{map[string]User{}}
|
|
||||||
u.Register(wsContainer)
|
|
||||||
|
|
||||||
log.Print("start listening on localhost:8090")
|
|
||||||
server := &http.Server{Addr: ":8090", Handler: wsContainer}
|
|
||||||
log.Fatal(server.ListenAndServe())
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForServerUp(serverURL string) error {
|
|
||||||
for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(5 * time.Second) {
|
|
||||||
_, err := http.Get(serverURL + "/")
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.New("waiting for server timed out")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServer(t *testing.T) {
|
|
||||||
serverURL := "http://localhost:8090"
|
|
||||||
go func() {
|
|
||||||
RunRestfulCurlyRouterServer()
|
|
||||||
}()
|
|
||||||
if err := waitForServerUp(serverURL); err != nil {
|
|
||||||
t.Errorf("%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET should give a 405
|
|
||||||
resp, err := http.Get(serverURL + "/users/")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error in GET /users/: %v", err)
|
|
||||||
}
|
|
||||||
if resp.StatusCode != http.StatusMethodNotAllowed {
|
|
||||||
t.Errorf("unexpected response: %v, expected: %v", resp.StatusCode, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a POST request.
|
|
||||||
var jsonStr = []byte(`{"id":"1","name":"user1"}`)
|
|
||||||
req, err := http.NewRequest("POST", serverURL+"/users/", bytes.NewBuffer(jsonStr))
|
|
||||||
req.Header.Set("Content-Type", restful.MIME_JSON)
|
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
resp, err = client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error in sending req: %v", err)
|
|
||||||
}
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
t.Errorf("unexpected response: %v, expected: %v", resp.StatusCode, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that GET works.
|
|
||||||
resp, err = http.Get(serverURL + "/users/1")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error in GET /users/1: %v", err)
|
|
||||||
}
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
t.Errorf("unexpected response: %v, expected: %v", resp.StatusCode, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
|
61
vendor/github.com/emicklei/go-restful/examples/restful-encoding-filter.go
generated
vendored
61
vendor/github.com/emicklei/go-restful/examples/restful-encoding-filter.go
generated
vendored
@ -1,61 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Id, Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserList struct {
|
|
||||||
Users []User
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// This example shows how to use the CompressingResponseWriter by a Filter
|
|
||||||
// such that encoding can be enabled per WebService or per Route (instead of per container)
|
|
||||||
// Using restful.DefaultContainer.EnableContentEncoding(true) will encode all responses served by WebServices in the DefaultContainer.
|
|
||||||
//
|
|
||||||
// Set Accept-Encoding to gzip or deflate
|
|
||||||
// GET http://localhost:8080/users/42
|
|
||||||
// and look at the response headers
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
restful.Add(NewUserService())
|
|
||||||
log.Print("start listening on localhost:8080")
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUserService() *restful.WebService {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.
|
|
||||||
Path("/users").
|
|
||||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
|
||||||
Produces(restful.MIME_JSON, restful.MIME_XML)
|
|
||||||
|
|
||||||
// install a response encoding filter
|
|
||||||
ws.Route(ws.GET("/{user-id}").Filter(encodingFilter).To(findUser))
|
|
||||||
return ws
|
|
||||||
}
|
|
||||||
|
|
||||||
// Route Filter (defines FilterFunction)
|
|
||||||
func encodingFilter(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
||||||
log.Printf("[encoding-filter] %s,%s\n", req.Request.Method, req.Request.URL)
|
|
||||||
// wrap responseWriter into a compressing one
|
|
||||||
compress, _ := restful.NewCompressingResponseWriter(resp.ResponseWriter, restful.ENCODING_GZIP)
|
|
||||||
resp.ResponseWriter = compress
|
|
||||||
defer func() {
|
|
||||||
compress.Close()
|
|
||||||
}()
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET http://localhost:8080/users/42
|
|
||||||
//
|
|
||||||
func findUser(request *restful.Request, response *restful.Response) {
|
|
||||||
log.Print("findUser")
|
|
||||||
response.WriteEntity(User{"42", "Gandalf"})
|
|
||||||
}
|
|
114
vendor/github.com/emicklei/go-restful/examples/restful-filters.go
generated
vendored
114
vendor/github.com/emicklei/go-restful/examples/restful-filters.go
generated
vendored
@ -1,114 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Id, Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserList struct {
|
|
||||||
Users []User
|
|
||||||
}
|
|
||||||
|
|
||||||
// This example show how to create and use the three different Filters (Container,WebService and Route)
|
|
||||||
// When applied to the restful.DefaultContainer, we refer to them as a global filter.
|
|
||||||
//
|
|
||||||
// GET http://localhost:8080/users/42
|
|
||||||
// and see the logging per filter (try repeating this request)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// install a global (=DefaultContainer) filter (processed before any webservice in the DefaultContainer)
|
|
||||||
restful.Filter(globalLogging)
|
|
||||||
|
|
||||||
restful.Add(NewUserService())
|
|
||||||
log.Print("start listening on localhost:8080")
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUserService() *restful.WebService {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.
|
|
||||||
Path("/users").
|
|
||||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
|
||||||
Produces(restful.MIME_JSON, restful.MIME_XML)
|
|
||||||
|
|
||||||
// install a webservice filter (processed before any route)
|
|
||||||
ws.Filter(webserviceLogging).Filter(measureTime)
|
|
||||||
|
|
||||||
// install a counter filter
|
|
||||||
ws.Route(ws.GET("").Filter(NewCountFilter().routeCounter).To(getAllUsers))
|
|
||||||
|
|
||||||
// install 2 chained route filters (processed before calling findUser)
|
|
||||||
ws.Route(ws.GET("/{user-id}").Filter(routeLogging).Filter(NewCountFilter().routeCounter).To(findUser))
|
|
||||||
return ws
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global Filter
|
|
||||||
func globalLogging(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
||||||
log.Printf("[global-filter (logger)] %s,%s\n", req.Request.Method, req.Request.URL)
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebService Filter
|
|
||||||
func webserviceLogging(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
||||||
log.Printf("[webservice-filter (logger)] %s,%s\n", req.Request.Method, req.Request.URL)
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebService (post-process) Filter (as a struct that defines a FilterFunction)
|
|
||||||
func measureTime(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
||||||
now := time.Now()
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
log.Printf("[webservice-filter (timer)] %v\n", time.Now().Sub(now))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Route Filter (defines FilterFunction)
|
|
||||||
func routeLogging(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
||||||
log.Printf("[route-filter (logger)] %s,%s\n", req.Request.Method, req.Request.URL)
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Route Filter (as a struct that defines a FilterFunction)
|
|
||||||
// CountFilter implements a FilterFunction for counting requests.
|
|
||||||
type CountFilter struct {
|
|
||||||
count int
|
|
||||||
counter chan int // for go-routine safe count increments
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCountFilter creates and initializes a new CountFilter.
|
|
||||||
func NewCountFilter() *CountFilter {
|
|
||||||
c := new(CountFilter)
|
|
||||||
c.counter = make(chan int)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
c.count += <-c.counter
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// routeCounter increments the count of the filter (through a channel)
|
|
||||||
func (c *CountFilter) routeCounter(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
||||||
c.counter <- 1
|
|
||||||
log.Printf("[route-filter (counter)] count:%d", c.count)
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET http://localhost:8080/users
|
|
||||||
//
|
|
||||||
func getAllUsers(request *restful.Request, response *restful.Response) {
|
|
||||||
log.Print("getAllUsers")
|
|
||||||
response.WriteEntity(UserList{[]User{{"42", "Gandalf"}, {"3.14", "Pi"}}})
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET http://localhost:8080/users/42
|
|
||||||
//
|
|
||||||
func findUser(request *restful.Request, response *restful.Response) {
|
|
||||||
log.Print("findUser")
|
|
||||||
response.WriteEntity(User{"42", "Gandalf"})
|
|
||||||
}
|
|
63
vendor/github.com/emicklei/go-restful/examples/restful-form-handling.go
generated
vendored
63
vendor/github.com/emicklei/go-restful/examples/restful-form-handling.go
generated
vendored
@ -1,63 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"github.com/gorilla/schema"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example shows how to handle a POST of a HTML form that uses the standard x-www-form-urlencoded content-type.
|
|
||||||
// It uses the gorilla web tool kit schema package to decode the form data into a struct.
|
|
||||||
//
|
|
||||||
// GET http://localhost:8080/profiles
|
|
||||||
//
|
|
||||||
|
|
||||||
type Profile struct {
|
|
||||||
Name string
|
|
||||||
Age int
|
|
||||||
}
|
|
||||||
|
|
||||||
var decoder *schema.Decoder
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
decoder = schema.NewDecoder()
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Route(ws.POST("/profiles").Consumes("application/x-www-form-urlencoded").To(postAdddress))
|
|
||||||
ws.Route(ws.GET("/profiles").To(addresssForm))
|
|
||||||
restful.Add(ws)
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func postAdddress(req *restful.Request, resp *restful.Response) {
|
|
||||||
err := req.Request.ParseForm()
|
|
||||||
if err != nil {
|
|
||||||
resp.WriteErrorString(http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p := new(Profile)
|
|
||||||
err = decoder.Decode(p, req.Request.PostForm)
|
|
||||||
if err != nil {
|
|
||||||
resp.WriteErrorString(http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
io.WriteString(resp.ResponseWriter, fmt.Sprintf("<html><body>Name=%s, Age=%d</body></html>", p.Name, p.Age))
|
|
||||||
}
|
|
||||||
|
|
||||||
func addresssForm(req *restful.Request, resp *restful.Response) {
|
|
||||||
io.WriteString(resp.ResponseWriter,
|
|
||||||
`<html>
|
|
||||||
<body>
|
|
||||||
<h1>Enter Profile</h1>
|
|
||||||
<form method="post">
|
|
||||||
<label>Name:</label>
|
|
||||||
<input type="text" name="Name"/>
|
|
||||||
<label>Age:</label>
|
|
||||||
<input type="text" name="Age"/>
|
|
||||||
<input type="Submit" />
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
</html>`)
|
|
||||||
}
|
|
23
vendor/github.com/emicklei/go-restful/examples/restful-hello-world.go
generated
vendored
23
vendor/github.com/emicklei/go-restful/examples/restful-hello-world.go
generated
vendored
@ -1,23 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example shows the minimal code needed to get a restful.WebService working.
|
|
||||||
//
|
|
||||||
// GET http://localhost:8080/hello
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Route(ws.GET("/hello").To(hello))
|
|
||||||
restful.Add(ws)
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func hello(req *restful.Request, resp *restful.Response) {
|
|
||||||
io.WriteString(resp, "world")
|
|
||||||
}
|
|
35
vendor/github.com/emicklei/go-restful/examples/restful-html-template.go
generated
vendored
35
vendor/github.com/emicklei/go-restful/examples/restful-html-template.go
generated
vendored
@ -1,35 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example shows how to serve a HTML page using the standard Go template engine.
|
|
||||||
//
|
|
||||||
// GET http://localhost:8080/
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Route(ws.GET("/").To(home))
|
|
||||||
restful.Add(ws)
|
|
||||||
print("open browser on http://localhost:8080/\n")
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Message struct {
|
|
||||||
Text string
|
|
||||||
}
|
|
||||||
|
|
||||||
func home(req *restful.Request, resp *restful.Response) {
|
|
||||||
p := &Message{"restful-html-template demo"}
|
|
||||||
// you might want to cache compiled templates
|
|
||||||
t, err := template.ParseFiles("home.html")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Template gave: %s", err)
|
|
||||||
}
|
|
||||||
t.Execute(resp.ResponseWriter, p)
|
|
||||||
}
|
|
43
vendor/github.com/emicklei/go-restful/examples/restful-multi-containers.go
generated
vendored
43
vendor/github.com/emicklei/go-restful/examples/restful-multi-containers.go
generated
vendored
@ -1,43 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example shows how to have a program with 2 WebServices containers
|
|
||||||
// each having a http server listening on its own port.
|
|
||||||
//
|
|
||||||
// The first "hello" is added to the restful.DefaultContainer (and uses DefaultServeMux)
|
|
||||||
// For the second "hello", a new container and ServeMux is created
|
|
||||||
// and requires a new http.Server with the container being the Handler.
|
|
||||||
// This first server is spawn in its own go-routine such that the program proceeds to create the second.
|
|
||||||
//
|
|
||||||
// GET http://localhost:8080/hello
|
|
||||||
// GET http://localhost:8081/hello
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Route(ws.GET("/hello").To(hello))
|
|
||||||
restful.Add(ws)
|
|
||||||
go func() {
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}()
|
|
||||||
|
|
||||||
container2 := restful.NewContainer()
|
|
||||||
ws2 := new(restful.WebService)
|
|
||||||
ws2.Route(ws2.GET("/hello").To(hello2))
|
|
||||||
container2.Add(ws2)
|
|
||||||
server := &http.Server{Addr: ":8081", Handler: container2}
|
|
||||||
log.Fatal(server.ListenAndServe())
|
|
||||||
}
|
|
||||||
|
|
||||||
func hello(req *restful.Request, resp *restful.Response) {
|
|
||||||
io.WriteString(resp, "default world")
|
|
||||||
}
|
|
||||||
|
|
||||||
func hello2(req *restful.Request, resp *restful.Response) {
|
|
||||||
io.WriteString(resp, "second world")
|
|
||||||
}
|
|
25
vendor/github.com/emicklei/go-restful/examples/restful-no-cache-filter.go
generated
vendored
25
vendor/github.com/emicklei/go-restful/examples/restful-no-cache-filter.go
generated
vendored
@ -1,25 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example shows how to use a WebService filter that passed the Http headers to disable browser cacheing.
|
|
||||||
//
|
|
||||||
// GET http://localhost:8080/hello
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Filter(restful.NoBrowserCacheFilter)
|
|
||||||
ws.Route(ws.GET("/hello").To(hello))
|
|
||||||
restful.Add(ws)
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func hello(req *restful.Request, resp *restful.Response) {
|
|
||||||
io.WriteString(resp, "world")
|
|
||||||
}
|
|
51
vendor/github.com/emicklei/go-restful/examples/restful-options-filter.go
generated
vendored
51
vendor/github.com/emicklei/go-restful/examples/restful-options-filter.go
generated
vendored
@ -1,51 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example shows how to use the OPTIONSFilter on a Container
|
|
||||||
//
|
|
||||||
// OPTIONS http://localhost:8080/users
|
|
||||||
//
|
|
||||||
// OPTIONS http://localhost:8080/users/1
|
|
||||||
|
|
||||||
type UserResource struct{}
|
|
||||||
|
|
||||||
func (u UserResource) RegisterTo(container *restful.Container) {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.
|
|
||||||
Path("/users").
|
|
||||||
Consumes("*/*").
|
|
||||||
Produces("*/*")
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/{user-id}").To(u.nop))
|
|
||||||
ws.Route(ws.POST("").To(u.nop))
|
|
||||||
ws.Route(ws.PUT("/{user-id}").To(u.nop))
|
|
||||||
ws.Route(ws.DELETE("/{user-id}").To(u.nop))
|
|
||||||
|
|
||||||
container.Add(ws)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u UserResource) nop(request *restful.Request, response *restful.Response) {
|
|
||||||
io.WriteString(response.ResponseWriter, "this would be a normal response")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
wsContainer := restful.NewContainer()
|
|
||||||
u := UserResource{}
|
|
||||||
u.RegisterTo(wsContainer)
|
|
||||||
|
|
||||||
// Add container filter to respond to OPTIONS
|
|
||||||
wsContainer.Filter(wsContainer.OPTIONSFilter)
|
|
||||||
|
|
||||||
// For use on the default container, you can write
|
|
||||||
// restful.Filter(restful.OPTIONSFilter())
|
|
||||||
|
|
||||||
log.Print("start listening on localhost:8080")
|
|
||||||
server := &http.Server{Addr: ":8080", Handler: wsContainer}
|
|
||||||
log.Fatal(server.ListenAndServe())
|
|
||||||
}
|
|
27
vendor/github.com/emicklei/go-restful/examples/restful-path-tail.go
generated
vendored
27
vendor/github.com/emicklei/go-restful/examples/restful-path-tail.go
generated
vendored
@ -1,27 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/emicklei/go-restful"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example shows how to create a Route matching the "tail" of a path.
|
|
||||||
// Requires the use of a CurlyRouter and the star "*" path parameter pattern.
|
|
||||||
//
|
|
||||||
// GET http://localhost:8080/basepath/some/other/location/test.xml
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
DefaultContainer.Router(CurlyRouter{})
|
|
||||||
ws := new(WebService)
|
|
||||||
ws.Route(ws.GET("/basepath/{resource:*}").To(staticFromPathParam))
|
|
||||||
Add(ws)
|
|
||||||
|
|
||||||
println("[go-restful] serve path tails from http://localhost:8080/basepath")
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func staticFromPathParam(req *Request, resp *Response) {
|
|
||||||
io.WriteString(resp, "Tail="+req.PathParameter("resource"))
|
|
||||||
}
|
|
98
vendor/github.com/emicklei/go-restful/examples/restful-pre-post-filters.go
generated
vendored
98
vendor/github.com/emicklei/go-restful/examples/restful-pre-post-filters.go
generated
vendored
@ -1,98 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example shows how the different types of filters are called in the request-response flow.
|
|
||||||
// The call chain is logged on the console when sending an http request.
|
|
||||||
//
|
|
||||||
// GET http://localhost:8080/1
|
|
||||||
// GET http://localhost:8080/2
|
|
||||||
|
|
||||||
var indentLevel int
|
|
||||||
|
|
||||||
func container_filter_A(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
||||||
log.Printf("url path:%v\n", req.Request.URL)
|
|
||||||
trace("container_filter_A: before", 1)
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
trace("container_filter_A: after", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func container_filter_B(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
||||||
trace("container_filter_B: before", 1)
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
trace("container_filter_B: after", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func service_filter_A(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
||||||
trace("service_filter_A: before", 1)
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
trace("service_filter_A: after", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func service_filter_B(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
||||||
trace("service_filter_B: before", 1)
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
trace("service_filter_B: after", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func route_filter_A(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
||||||
trace("route_filter_A: before", 1)
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
trace("route_filter_A: after", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func route_filter_B(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
||||||
trace("route_filter_B: before", 1)
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
trace("route_filter_B: after", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func trace(what string, delta int) {
|
|
||||||
indented := what
|
|
||||||
if delta < 0 {
|
|
||||||
indentLevel += delta
|
|
||||||
}
|
|
||||||
for t := 0; t < indentLevel; t++ {
|
|
||||||
indented = "." + indented
|
|
||||||
}
|
|
||||||
log.Printf("%s", indented)
|
|
||||||
if delta > 0 {
|
|
||||||
indentLevel += delta
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
restful.Filter(container_filter_A)
|
|
||||||
restful.Filter(container_filter_B)
|
|
||||||
|
|
||||||
ws1 := new(restful.WebService)
|
|
||||||
ws1.Path("/1")
|
|
||||||
ws1.Filter(service_filter_A)
|
|
||||||
ws1.Filter(service_filter_B)
|
|
||||||
ws1.Route(ws1.GET("").To(doit1).Filter(route_filter_A).Filter(route_filter_B))
|
|
||||||
|
|
||||||
ws2 := new(restful.WebService)
|
|
||||||
ws2.Path("/2")
|
|
||||||
ws2.Filter(service_filter_A)
|
|
||||||
ws2.Filter(service_filter_B)
|
|
||||||
ws2.Route(ws2.GET("").To(doit2).Filter(route_filter_A).Filter(route_filter_B))
|
|
||||||
|
|
||||||
restful.Add(ws1)
|
|
||||||
restful.Add(ws2)
|
|
||||||
|
|
||||||
log.Print("go-restful example listing on http://localhost:8080/1 and http://localhost:8080/2")
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func doit1(req *restful.Request, resp *restful.Response) {
|
|
||||||
io.WriteString(resp, "nothing to see in 1")
|
|
||||||
}
|
|
||||||
|
|
||||||
func doit2(req *restful.Request, resp *restful.Response) {
|
|
||||||
io.WriteString(resp, "nothing to see in 2")
|
|
||||||
}
|
|
63
vendor/github.com/emicklei/go-restful/examples/restful-resource-functions.go
generated
vendored
63
vendor/github.com/emicklei/go-restful/examples/restful-resource-functions.go
generated
vendored
@ -1,63 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example shows how to use methods as RouteFunctions for WebServices.
|
|
||||||
// The ProductResource has a Register() method that creates and initializes
|
|
||||||
// a WebService to expose its methods as REST operations.
|
|
||||||
// The WebService is added to the restful.DefaultContainer.
|
|
||||||
// A ProductResource is typically created using some data access object.
|
|
||||||
//
|
|
||||||
// GET http://localhost:8080/products/1
|
|
||||||
// POST http://localhost:8080/products
|
|
||||||
// <Product><Id>1</Id><Title>The First</Title></Product>
|
|
||||||
|
|
||||||
type Product struct {
|
|
||||||
Id, Title string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProductResource struct {
|
|
||||||
// typically reference a DAO (data-access-object)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ProductResource) getOne(req *restful.Request, resp *restful.Response) {
|
|
||||||
id := req.PathParameter("id")
|
|
||||||
log.Println("getting product with id:" + id)
|
|
||||||
resp.WriteEntity(Product{Id: id, Title: "test"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ProductResource) postOne(req *restful.Request, resp *restful.Response) {
|
|
||||||
updatedProduct := new(Product)
|
|
||||||
err := req.ReadEntity(updatedProduct)
|
|
||||||
if err != nil { // bad request
|
|
||||||
resp.WriteErrorString(http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Println("updating product with id:" + updatedProduct.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ProductResource) Register() {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Path("/products")
|
|
||||||
ws.Consumes(restful.MIME_XML)
|
|
||||||
ws.Produces(restful.MIME_XML)
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/{id}").To(p.getOne).
|
|
||||||
Doc("get the product by its id").
|
|
||||||
Param(ws.PathParameter("id", "identifier of the product").DataType("string")))
|
|
||||||
|
|
||||||
ws.Route(ws.POST("").To(p.postOne).
|
|
||||||
Doc("update or create a product").
|
|
||||||
Param(ws.BodyParameter("Product", "a Product (XML)").DataType("main.Product")))
|
|
||||||
|
|
||||||
restful.Add(ws)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ProductResource{}.Register()
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
39
vendor/github.com/emicklei/go-restful/examples/restful-route_test.go
generated
vendored
39
vendor/github.com/emicklei/go-restful/examples/restful-route_test.go
generated
vendored
@ -1,39 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
Result string
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRouteExtractParameter(t *testing.T) {
|
|
||||||
// setup service
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Consumes(restful.MIME_XML)
|
|
||||||
ws.Route(ws.GET("/test/{param}").To(DummyHandler))
|
|
||||||
restful.Add(ws)
|
|
||||||
|
|
||||||
// setup request + writer
|
|
||||||
bodyReader := strings.NewReader("<Sample><Value>42</Value></Sample>")
|
|
||||||
httpRequest, _ := http.NewRequest("GET", "/test/THIS", bodyReader)
|
|
||||||
httpRequest.Header.Set("Content-Type", restful.MIME_XML)
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
|
|
||||||
// run
|
|
||||||
restful.DefaultContainer.ServeHTTP(httpWriter, httpRequest)
|
|
||||||
|
|
||||||
if Result != "THIS" {
|
|
||||||
t.Fatalf("Result is actually: %s", Result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DummyHandler(rq *restful.Request, rp *restful.Response) {
|
|
||||||
Result = rq.PathParameter("param")
|
|
||||||
}
|
|
29
vendor/github.com/emicklei/go-restful/examples/restful-routefunction_test.go
generated
vendored
29
vendor/github.com/emicklei/go-restful/examples/restful-routefunction_test.go
generated
vendored
@ -1,29 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example show how to test one particular RouteFunction (getIt)
|
|
||||||
// It uses the httptest.ResponseRecorder to capture output
|
|
||||||
|
|
||||||
func getIt(req *restful.Request, resp *restful.Response) {
|
|
||||||
resp.WriteHeader(204)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCallFunction(t *testing.T) {
|
|
||||||
httpReq, _ := http.NewRequest("GET", "/", nil)
|
|
||||||
req := restful.NewRequest(httpReq)
|
|
||||||
|
|
||||||
recorder := new(httptest.ResponseRecorder)
|
|
||||||
resp := restful.NewResponse(recorder)
|
|
||||||
|
|
||||||
getIt(req, resp)
|
|
||||||
if recorder.Code != 204 {
|
|
||||||
t.Fatalf("Missing or wrong status code:%d", recorder.Code)
|
|
||||||
}
|
|
||||||
}
|
|
47
vendor/github.com/emicklei/go-restful/examples/restful-serve-static.go
generated
vendored
47
vendor/github.com/emicklei/go-restful/examples/restful-serve-static.go
generated
vendored
@ -1,47 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example shows how to define methods that serve static files
|
|
||||||
// It uses the standard http.ServeFile method
|
|
||||||
//
|
|
||||||
// GET http://localhost:8080/static/test.xml
|
|
||||||
// GET http://localhost:8080/static/
|
|
||||||
//
|
|
||||||
// GET http://localhost:8080/static?resource=subdir/test.xml
|
|
||||||
|
|
||||||
var rootdir = "/tmp"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
restful.DefaultContainer.Router(restful.CurlyRouter{})
|
|
||||||
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Route(ws.GET("/static/{subpath:*}").To(staticFromPathParam))
|
|
||||||
ws.Route(ws.GET("/static").To(staticFromQueryParam))
|
|
||||||
restful.Add(ws)
|
|
||||||
|
|
||||||
println("[go-restful] serving files on http://localhost:8080/static from local /tmp")
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func staticFromPathParam(req *restful.Request, resp *restful.Response) {
|
|
||||||
actual := path.Join(rootdir, req.PathParameter("subpath"))
|
|
||||||
fmt.Printf("serving %s ... (from %s)\n", actual, req.PathParameter("subpath"))
|
|
||||||
http.ServeFile(
|
|
||||||
resp.ResponseWriter,
|
|
||||||
req.Request,
|
|
||||||
actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func staticFromQueryParam(req *restful.Request, resp *restful.Response) {
|
|
||||||
http.ServeFile(
|
|
||||||
resp.ResponseWriter,
|
|
||||||
req.Request,
|
|
||||||
path.Join(rootdir, req.QueryParameter("resource")))
|
|
||||||
}
|
|
61
vendor/github.com/emicklei/go-restful/examples/restful-swagger.go
generated
vendored
61
vendor/github.com/emicklei/go-restful/examples/restful-swagger.go
generated
vendored
@ -1,61 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
"github.com/emicklei/go-restful-swagger12"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Book struct {
|
|
||||||
Title string
|
|
||||||
Author string
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.Path("/books")
|
|
||||||
ws.Consumes(restful.MIME_JSON, restful.MIME_XML)
|
|
||||||
ws.Produces(restful.MIME_JSON, restful.MIME_XML)
|
|
||||||
restful.Add(ws)
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/{medium}").To(noop).
|
|
||||||
Doc("Search all books").
|
|
||||||
Param(ws.PathParameter("medium", "digital or paperback").DataType("string")).
|
|
||||||
Param(ws.QueryParameter("language", "en,nl,de").DataType("string")).
|
|
||||||
Param(ws.HeaderParameter("If-Modified-Since", "last known timestamp").DataType("datetime")).
|
|
||||||
Do(returns200, returns500))
|
|
||||||
|
|
||||||
ws.Route(ws.PUT("/{medium}").To(noop).
|
|
||||||
Doc("Add a new book").
|
|
||||||
Param(ws.PathParameter("medium", "digital or paperback").DataType("string")).
|
|
||||||
Reads(Book{}))
|
|
||||||
|
|
||||||
// You can install the Swagger Service which provides a nice Web UI on your REST API
|
|
||||||
// You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
|
|
||||||
// Open http://localhost:8080/apidocs and enter http://localhost:8080/apidocs.json in the api input field.
|
|
||||||
config := swagger.Config{
|
|
||||||
WebServices: restful.DefaultContainer.RegisteredWebServices(), // you control what services are visible
|
|
||||||
WebServicesUrl: "http://localhost:8080",
|
|
||||||
ApiPath: "/apidocs.json",
|
|
||||||
|
|
||||||
// Optionally, specify where the UI is located
|
|
||||||
SwaggerPath: "/apidocs/",
|
|
||||||
SwaggerFilePath: "/Users/emicklei/xProjects/swagger-ui/dist"}
|
|
||||||
swagger.RegisterSwaggerService(config, restful.DefaultContainer)
|
|
||||||
|
|
||||||
log.Print("start listening on localhost:8080")
|
|
||||||
server := &http.Server{Addr: ":8080", Handler: restful.DefaultContainer}
|
|
||||||
log.Fatal(server.ListenAndServe())
|
|
||||||
}
|
|
||||||
|
|
||||||
func noop(req *restful.Request, resp *restful.Response) {}
|
|
||||||
|
|
||||||
func returns200(b *restful.RouteBuilder) {
|
|
||||||
b.Returns(http.StatusOK, "OK", Book{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func returns500(b *restful.RouteBuilder) {
|
|
||||||
b.Returns(http.StatusInternalServerError, "Bummer, something went wrong", nil)
|
|
||||||
}
|
|
170
vendor/github.com/emicklei/go-restful/examples/restful-user-resource.go
generated
vendored
170
vendor/github.com/emicklei/go-restful/examples/restful-user-resource.go
generated
vendored
@ -1,170 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
|
||||||
restfulspec "github.com/emicklei/go-restful-openapi"
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UserResource is the REST layer to the User domain
|
|
||||||
type UserResource struct {
|
|
||||||
// normally one would use DAO (data access object)
|
|
||||||
users map[string]User
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebService creates a new service that can handle REST requests for User resources.
|
|
||||||
func (u UserResource) WebService() *restful.WebService {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.
|
|
||||||
Path("/users").
|
|
||||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
|
||||||
Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well
|
|
||||||
|
|
||||||
tags := []string{"users"}
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/").To(u.findAllUsers).
|
|
||||||
// docs
|
|
||||||
Doc("get all users").
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
|
||||||
Writes([]User{}).
|
|
||||||
Returns(200, "OK", []User{}))
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/{user-id}").To(u.findUser).
|
|
||||||
// docs
|
|
||||||
Doc("get a user").
|
|
||||||
Param(ws.PathParameter("user-id", "identifier of the user").DataType("integer").DefaultValue("1")).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
|
||||||
Writes(User{}). // on the response
|
|
||||||
Returns(200, "OK", User{}).
|
|
||||||
Returns(404, "Not Found", nil))
|
|
||||||
|
|
||||||
ws.Route(ws.PUT("/{user-id}").To(u.updateUser).
|
|
||||||
// docs
|
|
||||||
Doc("update a user").
|
|
||||||
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
|
||||||
Reads(User{})) // from the request
|
|
||||||
|
|
||||||
ws.Route(ws.PUT("").To(u.createUser).
|
|
||||||
// docs
|
|
||||||
Doc("create a user").
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
|
||||||
Reads(User{})) // from the request
|
|
||||||
|
|
||||||
ws.Route(ws.DELETE("/{user-id}").To(u.removeUser).
|
|
||||||
// docs
|
|
||||||
Doc("delete a user").
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
|
||||||
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")))
|
|
||||||
|
|
||||||
return ws
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET http://localhost:8080/users
|
|
||||||
//
|
|
||||||
func (u UserResource) findAllUsers(request *restful.Request, response *restful.Response) {
|
|
||||||
list := []User{}
|
|
||||||
for _, each := range u.users {
|
|
||||||
list = append(list, each)
|
|
||||||
}
|
|
||||||
response.WriteEntity(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET http://localhost:8080/users/1
|
|
||||||
//
|
|
||||||
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
|
|
||||||
id := request.PathParameter("user-id")
|
|
||||||
usr := u.users[id]
|
|
||||||
if len(usr.ID) == 0 {
|
|
||||||
response.WriteErrorString(http.StatusNotFound, "User could not be found.")
|
|
||||||
} else {
|
|
||||||
response.WriteEntity(usr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PUT http://localhost:8080/users/1
|
|
||||||
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
|
|
||||||
//
|
|
||||||
func (u *UserResource) updateUser(request *restful.Request, response *restful.Response) {
|
|
||||||
usr := new(User)
|
|
||||||
err := request.ReadEntity(&usr)
|
|
||||||
if err == nil {
|
|
||||||
u.users[usr.ID] = *usr
|
|
||||||
response.WriteEntity(usr)
|
|
||||||
} else {
|
|
||||||
response.WriteError(http.StatusInternalServerError, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PUT http://localhost:8080/users/1
|
|
||||||
// <User><Id>1</Id><Name>Melissa</Name></User>
|
|
||||||
//
|
|
||||||
func (u *UserResource) createUser(request *restful.Request, response *restful.Response) {
|
|
||||||
usr := User{ID: request.PathParameter("user-id")}
|
|
||||||
err := request.ReadEntity(&usr)
|
|
||||||
if err == nil {
|
|
||||||
u.users[usr.ID] = usr
|
|
||||||
response.WriteHeaderAndEntity(http.StatusCreated, usr)
|
|
||||||
} else {
|
|
||||||
response.WriteError(http.StatusInternalServerError, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE http://localhost:8080/users/1
|
|
||||||
//
|
|
||||||
func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) {
|
|
||||||
id := request.PathParameter("user-id")
|
|
||||||
delete(u.users, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
u := UserResource{map[string]User{}}
|
|
||||||
restful.DefaultContainer.Add(u.WebService())
|
|
||||||
|
|
||||||
config := restfulspec.Config{
|
|
||||||
WebServices: restful.RegisteredWebServices(), // you control what services are visible
|
|
||||||
WebServicesURL: "http://localhost:8080",
|
|
||||||
APIPath: "/apidocs.json",
|
|
||||||
PostBuildSwaggerObjectHandler: enrichSwaggerObject}
|
|
||||||
restful.DefaultContainer.Add(restfulspec.NewOpenAPIService(config))
|
|
||||||
|
|
||||||
// Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API
|
|
||||||
// You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
|
|
||||||
// Open http://localhost:8080/apidocs/?url=http://localhost:8080/apidocs.json
|
|
||||||
http.Handle("/apidocs/", http.StripPrefix("/apidocs/", http.FileServer(http.Dir("/Users/emicklei/Projects/swagger-ui/dist"))))
|
|
||||||
|
|
||||||
log.Printf("start listening on localhost:8080")
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func enrichSwaggerObject(swo *spec.Swagger) {
|
|
||||||
swo.Info = &spec.Info{
|
|
||||||
InfoProps: spec.InfoProps{
|
|
||||||
Title: "UserService",
|
|
||||||
Description: "Resource for managing Users",
|
|
||||||
Contact: &spec.ContactInfo{
|
|
||||||
Name: "john",
|
|
||||||
Email: "john@doe.rp",
|
|
||||||
URL: "http://johndoe.org",
|
|
||||||
},
|
|
||||||
License: &spec.License{
|
|
||||||
Name: "MIT",
|
|
||||||
URL: "http://mit.org",
|
|
||||||
},
|
|
||||||
Version: "1.0.0",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
swo.Tags = []spec.Tag{spec.Tag{TagProps: spec.TagProps{
|
|
||||||
Name: "users",
|
|
||||||
Description: "Managing users"}}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// User is just a sample type
|
|
||||||
type User struct {
|
|
||||||
ID string `json:"id" description:"identifier of the user"`
|
|
||||||
Name string `json:"name" description:"name of the user" default:"john"`
|
|
||||||
Age int `json:"age" description:"age of the user" default:"21"`
|
|
||||||
}
|
|
35
vendor/github.com/emicklei/go-restful/filter.go
generated
vendored
35
vendor/github.com/emicklei/go-restful/filter.go
generated
vendored
@ -1,35 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// FilterChain is a request scoped object to process one or more filters before calling the target RouteFunction.
|
|
||||||
type FilterChain struct {
|
|
||||||
Filters []FilterFunction // ordered list of FilterFunction
|
|
||||||
Index int // index into filters that is currently in progress
|
|
||||||
Target RouteFunction // function to call after passing all filters
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessFilter passes the request,response pair through the next of Filters.
|
|
||||||
// Each filter can decide to proceed to the next Filter or handle the Response itself.
|
|
||||||
func (f *FilterChain) ProcessFilter(request *Request, response *Response) {
|
|
||||||
if f.Index < len(f.Filters) {
|
|
||||||
f.Index++
|
|
||||||
f.Filters[f.Index-1](request, response, f)
|
|
||||||
} else {
|
|
||||||
f.Target(request, response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterFunction definitions must call ProcessFilter on the FilterChain to pass on the control and eventually call the RouteFunction
|
|
||||||
type FilterFunction func(*Request, *Response, *FilterChain)
|
|
||||||
|
|
||||||
// NoBrowserCacheFilter is a filter function to set HTTP headers that disable browser caching
|
|
||||||
// See examples/restful-no-cache-filter.go for usage
|
|
||||||
func NoBrowserCacheFilter(req *Request, resp *Response, chain *FilterChain) {
|
|
||||||
resp.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") // HTTP 1.1.
|
|
||||||
resp.Header().Set("Pragma", "no-cache") // HTTP 1.0.
|
|
||||||
resp.Header().Set("Expires", "0") // Proxies.
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
}
|
|
141
vendor/github.com/emicklei/go-restful/filter_test.go
generated
vendored
141
vendor/github.com/emicklei/go-restful/filter_test.go
generated
vendored
@ -1,141 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setupServices(addGlobalFilter bool, addServiceFilter bool, addRouteFilter bool) {
|
|
||||||
if addGlobalFilter {
|
|
||||||
Filter(globalFilter)
|
|
||||||
}
|
|
||||||
Add(newTestService(addServiceFilter, addRouteFilter))
|
|
||||||
}
|
|
||||||
|
|
||||||
func tearDown() {
|
|
||||||
DefaultContainer.webServices = []*WebService{}
|
|
||||||
DefaultContainer.isRegisteredOnRoot = true // this allows for setupServices multiple times
|
|
||||||
DefaultContainer.containerFilters = []FilterFunction{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestService(addServiceFilter bool, addRouteFilter bool) *WebService {
|
|
||||||
ws := new(WebService).Path("")
|
|
||||||
if addServiceFilter {
|
|
||||||
ws.Filter(serviceFilter)
|
|
||||||
}
|
|
||||||
rb := ws.GET("/foo").To(foo)
|
|
||||||
if addRouteFilter {
|
|
||||||
rb.Filter(routeFilter)
|
|
||||||
}
|
|
||||||
ws.Route(rb)
|
|
||||||
ws.Route(ws.GET("/bar").To(bar))
|
|
||||||
return ws
|
|
||||||
}
|
|
||||||
|
|
||||||
func foo(req *Request, resp *Response) {
|
|
||||||
io.WriteString(resp.ResponseWriter, "foo")
|
|
||||||
}
|
|
||||||
|
|
||||||
func bar(req *Request, resp *Response) {
|
|
||||||
io.WriteString(resp.ResponseWriter, "bar")
|
|
||||||
}
|
|
||||||
|
|
||||||
func fail(req *Request, resp *Response) {
|
|
||||||
http.Error(resp.ResponseWriter, "something failed", http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
func globalFilter(req *Request, resp *Response, chain *FilterChain) {
|
|
||||||
io.WriteString(resp.ResponseWriter, "global-")
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func serviceFilter(req *Request, resp *Response, chain *FilterChain) {
|
|
||||||
io.WriteString(resp.ResponseWriter, "service-")
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func routeFilter(req *Request, resp *Response, chain *FilterChain) {
|
|
||||||
io.WriteString(resp.ResponseWriter, "route-")
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoFilter(t *testing.T) {
|
|
||||||
tearDown()
|
|
||||||
setupServices(false, false, false)
|
|
||||||
actual := sendIt("http://example.com/foo")
|
|
||||||
if "foo" != actual {
|
|
||||||
t.Fatal("expected: foo but got:" + actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGlobalFilter(t *testing.T) {
|
|
||||||
tearDown()
|
|
||||||
setupServices(true, false, false)
|
|
||||||
actual := sendIt("http://example.com/foo")
|
|
||||||
if "global-foo" != actual {
|
|
||||||
t.Fatal("expected: global-foo but got:" + actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWebServiceFilter(t *testing.T) {
|
|
||||||
tearDown()
|
|
||||||
setupServices(true, true, false)
|
|
||||||
actual := sendIt("http://example.com/foo")
|
|
||||||
if "global-service-foo" != actual {
|
|
||||||
t.Fatal("expected: global-service-foo but got:" + actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRouteFilter(t *testing.T) {
|
|
||||||
tearDown()
|
|
||||||
setupServices(true, true, true)
|
|
||||||
actual := sendIt("http://example.com/foo")
|
|
||||||
if "global-service-route-foo" != actual {
|
|
||||||
t.Fatal("expected: global-service-route-foo but got:" + actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRouteFilterOnly(t *testing.T) {
|
|
||||||
tearDown()
|
|
||||||
setupServices(false, false, true)
|
|
||||||
actual := sendIt("http://example.com/foo")
|
|
||||||
if "route-foo" != actual {
|
|
||||||
t.Fatal("expected: route-foo but got:" + actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBar(t *testing.T) {
|
|
||||||
tearDown()
|
|
||||||
setupServices(false, true, false)
|
|
||||||
actual := sendIt("http://example.com/bar")
|
|
||||||
if "service-bar" != actual {
|
|
||||||
t.Fatal("expected: service-bar but got:" + actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAllFiltersBar(t *testing.T) {
|
|
||||||
tearDown()
|
|
||||||
setupServices(true, true, true)
|
|
||||||
actual := sendIt("http://example.com/bar")
|
|
||||||
if "global-service-bar" != actual {
|
|
||||||
t.Fatal("expected: global-service-bar but got:" + actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendIt(address string) string {
|
|
||||||
httpRequest, _ := http.NewRequest("GET", address, nil)
|
|
||||||
httpRequest.Header.Set("Accept", "*/*")
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
DefaultContainer.dispatch(httpWriter, httpRequest)
|
|
||||||
return httpWriter.Body.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendItTo(address string, container *Container) string {
|
|
||||||
httpRequest, _ := http.NewRequest("GET", address, nil)
|
|
||||||
httpRequest.Header.Set("Accept", "*/*")
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
container.dispatch(httpWriter, httpRequest)
|
|
||||||
return httpWriter.Body.String()
|
|
||||||
}
|
|
268
vendor/github.com/emicklei/go-restful/jsr311.go
generated
vendored
268
vendor/github.com/emicklei/go-restful/jsr311.go
generated
vendored
@ -1,268 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RouterJSR311 implements the flow for matching Requests to Routes (and consequently Resource Functions)
|
|
||||||
// as specified by the JSR311 http://jsr311.java.net/nonav/releases/1.1/spec/spec.html.
|
|
||||||
// RouterJSR311 implements the Router interface.
|
|
||||||
// Concept of locators is not implemented.
|
|
||||||
type RouterJSR311 struct{}
|
|
||||||
|
|
||||||
// SelectRoute is part of the Router interface and returns the best match
|
|
||||||
// for the WebService and its Route for the given Request.
|
|
||||||
func (r RouterJSR311) SelectRoute(
|
|
||||||
webServices []*WebService,
|
|
||||||
httpRequest *http.Request) (selectedService *WebService, selectedRoute *Route, err error) {
|
|
||||||
|
|
||||||
// Identify the root resource class (WebService)
|
|
||||||
dispatcher, finalMatch, err := r.detectDispatcher(httpRequest.URL.Path, webServices)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, NewError(http.StatusNotFound, "")
|
|
||||||
}
|
|
||||||
// Obtain the set of candidate methods (Routes)
|
|
||||||
routes := r.selectRoutes(dispatcher, finalMatch)
|
|
||||||
if len(routes) == 0 {
|
|
||||||
return dispatcher, nil, NewError(http.StatusNotFound, "404: Page Not Found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Identify the method (Route) that will handle the request
|
|
||||||
route, ok := r.detectRoute(routes, httpRequest)
|
|
||||||
return dispatcher, route, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2
|
|
||||||
func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*Route, error) {
|
|
||||||
ifOk := []Route{}
|
|
||||||
for _, each := range routes {
|
|
||||||
ok := true
|
|
||||||
for _, fn := range each.If {
|
|
||||||
if !fn(httpRequest) {
|
|
||||||
ok = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
ifOk = append(ifOk, each)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(ifOk) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no Route found (from %d) that passes conditional checks", len(routes))
|
|
||||||
}
|
|
||||||
return nil, NewError(http.StatusNotFound, "404: Not Found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// http method
|
|
||||||
methodOk := []Route{}
|
|
||||||
for _, each := range ifOk {
|
|
||||||
if httpRequest.Method == each.Method {
|
|
||||||
methodOk = append(methodOk, each)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(methodOk) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no Route found (in %d routes) that matches HTTP method %s\n", len(routes), httpRequest.Method)
|
|
||||||
}
|
|
||||||
return nil, NewError(http.StatusMethodNotAllowed, "405: Method Not Allowed")
|
|
||||||
}
|
|
||||||
inputMediaOk := methodOk
|
|
||||||
|
|
||||||
// content-type
|
|
||||||
contentType := httpRequest.Header.Get(HEADER_ContentType)
|
|
||||||
inputMediaOk = []Route{}
|
|
||||||
for _, each := range methodOk {
|
|
||||||
if each.matchesContentType(contentType) {
|
|
||||||
inputMediaOk = append(inputMediaOk, each)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(inputMediaOk) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no Route found (from %d) that matches HTTP Content-Type: %s\n", len(methodOk), contentType)
|
|
||||||
}
|
|
||||||
return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type")
|
|
||||||
}
|
|
||||||
|
|
||||||
// accept
|
|
||||||
outputMediaOk := []Route{}
|
|
||||||
accept := httpRequest.Header.Get(HEADER_Accept)
|
|
||||||
if len(accept) == 0 {
|
|
||||||
accept = "*/*"
|
|
||||||
}
|
|
||||||
for _, each := range inputMediaOk {
|
|
||||||
if each.matchesAccept(accept) {
|
|
||||||
outputMediaOk = append(outputMediaOk, each)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(outputMediaOk) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no Route found (from %d) that matches HTTP Accept: %s\n", len(inputMediaOk), accept)
|
|
||||||
}
|
|
||||||
return nil, NewError(http.StatusNotAcceptable, "406: Not Acceptable")
|
|
||||||
}
|
|
||||||
// return r.bestMatchByMedia(outputMediaOk, contentType, accept), nil
|
|
||||||
return &outputMediaOk[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2
|
|
||||||
// n/m > n/* > */*
|
|
||||||
func (r RouterJSR311) bestMatchByMedia(routes []Route, contentType string, accept string) *Route {
|
|
||||||
// TODO
|
|
||||||
return &routes[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 2)
|
|
||||||
func (r RouterJSR311) selectRoutes(dispatcher *WebService, pathRemainder string) []Route {
|
|
||||||
filtered := &sortableRouteCandidates{}
|
|
||||||
for _, each := range dispatcher.Routes() {
|
|
||||||
pathExpr := each.pathExpr
|
|
||||||
matches := pathExpr.Matcher.FindStringSubmatch(pathRemainder)
|
|
||||||
if matches != nil {
|
|
||||||
lastMatch := matches[len(matches)-1]
|
|
||||||
if len(lastMatch) == 0 || lastMatch == "/" { // do not include if value is neither empty nor ‘/’.
|
|
||||||
filtered.candidates = append(filtered.candidates,
|
|
||||||
routeCandidate{each, len(matches) - 1, pathExpr.LiteralCount, pathExpr.VarCount})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(filtered.candidates) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("WebService on path %s has no routes that match URL path remainder:%s\n", dispatcher.rootPath, pathRemainder)
|
|
||||||
}
|
|
||||||
return []Route{}
|
|
||||||
}
|
|
||||||
sort.Sort(sort.Reverse(filtered))
|
|
||||||
|
|
||||||
// select other routes from candidates whoes expression matches rmatch
|
|
||||||
matchingRoutes := []Route{filtered.candidates[0].route}
|
|
||||||
for c := 1; c < len(filtered.candidates); c++ {
|
|
||||||
each := filtered.candidates[c]
|
|
||||||
if each.route.pathExpr.Matcher.MatchString(pathRemainder) {
|
|
||||||
matchingRoutes = append(matchingRoutes, each.route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matchingRoutes
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 1)
|
|
||||||
func (r RouterJSR311) detectDispatcher(requestPath string, dispatchers []*WebService) (*WebService, string, error) {
|
|
||||||
filtered := &sortableDispatcherCandidates{}
|
|
||||||
for _, each := range dispatchers {
|
|
||||||
matches := each.pathExpr.Matcher.FindStringSubmatch(requestPath)
|
|
||||||
if matches != nil {
|
|
||||||
filtered.candidates = append(filtered.candidates,
|
|
||||||
dispatcherCandidate{each, matches[len(matches)-1], len(matches), each.pathExpr.LiteralCount, each.pathExpr.VarCount})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(filtered.candidates) == 0 {
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no WebService was found to match URL path:%s\n", requestPath)
|
|
||||||
}
|
|
||||||
return nil, "", errors.New("not found")
|
|
||||||
}
|
|
||||||
sort.Sort(sort.Reverse(filtered))
|
|
||||||
return filtered.candidates[0].dispatcher, filtered.candidates[0].finalMatch, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Types and functions to support the sorting of Routes
|
|
||||||
|
|
||||||
type routeCandidate struct {
|
|
||||||
route Route
|
|
||||||
matchesCount int // the number of capturing groups
|
|
||||||
literalCount int // the number of literal characters (means those not resulting from template variable substitution)
|
|
||||||
nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^ /]+?)’)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r routeCandidate) expressionToMatch() string {
|
|
||||||
return r.route.pathExpr.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r routeCandidate) String() string {
|
|
||||||
return fmt.Sprintf("(m=%d,l=%d,n=%d)", r.matchesCount, r.literalCount, r.nonDefaultCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
type sortableRouteCandidates struct {
|
|
||||||
candidates []routeCandidate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rcs *sortableRouteCandidates) Len() int {
|
|
||||||
return len(rcs.candidates)
|
|
||||||
}
|
|
||||||
func (rcs *sortableRouteCandidates) Swap(i, j int) {
|
|
||||||
rcs.candidates[i], rcs.candidates[j] = rcs.candidates[j], rcs.candidates[i]
|
|
||||||
}
|
|
||||||
func (rcs *sortableRouteCandidates) Less(i, j int) bool {
|
|
||||||
ci := rcs.candidates[i]
|
|
||||||
cj := rcs.candidates[j]
|
|
||||||
// primary key
|
|
||||||
if ci.literalCount < cj.literalCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.literalCount > cj.literalCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// secundary key
|
|
||||||
if ci.matchesCount < cj.matchesCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.matchesCount > cj.matchesCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// tertiary key
|
|
||||||
if ci.nonDefaultCount < cj.nonDefaultCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.nonDefaultCount > cj.nonDefaultCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// quaternary key ("source" is interpreted as Path)
|
|
||||||
return ci.route.Path < cj.route.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
// Types and functions to support the sorting of Dispatchers
|
|
||||||
|
|
||||||
type dispatcherCandidate struct {
|
|
||||||
dispatcher *WebService
|
|
||||||
finalMatch string
|
|
||||||
matchesCount int // the number of capturing groups
|
|
||||||
literalCount int // the number of literal characters (means those not resulting from template variable substitution)
|
|
||||||
nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^ /]+?)’)
|
|
||||||
}
|
|
||||||
type sortableDispatcherCandidates struct {
|
|
||||||
candidates []dispatcherCandidate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *sortableDispatcherCandidates) Len() int {
|
|
||||||
return len(dc.candidates)
|
|
||||||
}
|
|
||||||
func (dc *sortableDispatcherCandidates) Swap(i, j int) {
|
|
||||||
dc.candidates[i], dc.candidates[j] = dc.candidates[j], dc.candidates[i]
|
|
||||||
}
|
|
||||||
func (dc *sortableDispatcherCandidates) Less(i, j int) bool {
|
|
||||||
ci := dc.candidates[i]
|
|
||||||
cj := dc.candidates[j]
|
|
||||||
// primary key
|
|
||||||
if ci.matchesCount < cj.matchesCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.matchesCount > cj.matchesCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// secundary key
|
|
||||||
if ci.literalCount < cj.literalCount {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ci.literalCount > cj.literalCount {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// tertiary key
|
|
||||||
return ci.nonDefaultCount < cj.nonDefaultCount
|
|
||||||
}
|
|
251
vendor/github.com/emicklei/go-restful/jsr311_test.go
generated
vendored
251
vendor/github.com/emicklei/go-restful/jsr311_test.go
generated
vendored
@ -1,251 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
//
|
|
||||||
// Step 1 tests
|
|
||||||
//
|
|
||||||
var paths = []struct {
|
|
||||||
// url with path (1) is handled by service with root (2) and last capturing group has value final (3)
|
|
||||||
path, root, final string
|
|
||||||
}{
|
|
||||||
{"/", "/", "/"},
|
|
||||||
{"/p", "/p", ""},
|
|
||||||
{"/p/x", "/p/{q}", ""},
|
|
||||||
{"/q/x", "/q", "/x"},
|
|
||||||
{"/p/x/", "/p/{q}", "/"},
|
|
||||||
{"/p/x/y", "/p/{q}", "/y"},
|
|
||||||
{"/q/x/y", "/q", "/x/y"},
|
|
||||||
{"/z/q", "/{p}/q", ""},
|
|
||||||
{"/a/b/c/q", "/", "/a/b/c/q"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDetectDispatcher(t *testing.T) {
|
|
||||||
ws1 := new(WebService).Path("/")
|
|
||||||
ws2 := new(WebService).Path("/p")
|
|
||||||
ws3 := new(WebService).Path("/q")
|
|
||||||
ws4 := new(WebService).Path("/p/q")
|
|
||||||
ws5 := new(WebService).Path("/p/{q}")
|
|
||||||
ws6 := new(WebService).Path("/p/{q}/")
|
|
||||||
ws7 := new(WebService).Path("/{p}/q")
|
|
||||||
var dispatchers = []*WebService{ws1, ws2, ws3, ws4, ws5, ws6, ws7}
|
|
||||||
|
|
||||||
wc := NewContainer()
|
|
||||||
for _, each := range dispatchers {
|
|
||||||
wc.Add(each)
|
|
||||||
}
|
|
||||||
|
|
||||||
router := RouterJSR311{}
|
|
||||||
|
|
||||||
ok := true
|
|
||||||
for i, fixture := range paths {
|
|
||||||
who, final, err := router.detectDispatcher(fixture.path, dispatchers)
|
|
||||||
if err != nil {
|
|
||||||
t.Logf("error in detection:%v", err)
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
if who.RootPath() != fixture.root {
|
|
||||||
t.Logf("[line:%v] Unexpected dispatcher, expected:%v, actual:%v", i, fixture.root, who.RootPath())
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
if final != fixture.final {
|
|
||||||
t.Logf("[line:%v] Unexpected final, expected:%v, actual:%v", i, fixture.final, final)
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Step 2 tests
|
|
||||||
//
|
|
||||||
|
|
||||||
// go test -v -test.run TestISSUE_179 ...restful
|
|
||||||
func TestISSUE_179(t *testing.T) {
|
|
||||||
ws1 := new(WebService)
|
|
||||||
ws1.Route(ws1.GET("/v1/category/{param:*}").To(dummy))
|
|
||||||
routes := RouterJSR311{}.selectRoutes(ws1, "/v1/category/sub/sub")
|
|
||||||
t.Logf("%v", routes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestISSUE_30 ...restful
|
|
||||||
func TestISSUE_30(t *testing.T) {
|
|
||||||
ws1 := new(WebService).Path("/users")
|
|
||||||
ws1.Route(ws1.GET("/{id}").To(dummy))
|
|
||||||
ws1.Route(ws1.POST("/login").To(dummy))
|
|
||||||
routes := RouterJSR311{}.selectRoutes(ws1, "/login")
|
|
||||||
if len(routes) != 2 {
|
|
||||||
t.Fatal("expected 2 routes")
|
|
||||||
}
|
|
||||||
if routes[0].Path != "/users/login" {
|
|
||||||
t.Error("first is", routes[0].Path)
|
|
||||||
t.Logf("routes:%v", routes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestISSUE_34 ...restful
|
|
||||||
func TestISSUE_34(t *testing.T) {
|
|
||||||
ws1 := new(WebService).Path("/")
|
|
||||||
ws1.Route(ws1.GET("/{type}/{id}").To(dummy))
|
|
||||||
ws1.Route(ws1.GET("/network/{id}").To(dummy))
|
|
||||||
routes := RouterJSR311{}.selectRoutes(ws1, "/network/12")
|
|
||||||
if len(routes) != 2 {
|
|
||||||
t.Fatal("expected 2 routes")
|
|
||||||
}
|
|
||||||
if routes[0].Path != "/network/{id}" {
|
|
||||||
t.Error("first is", routes[0].Path)
|
|
||||||
t.Logf("routes:%v", routes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestISSUE_34_2 ...restful
|
|
||||||
func TestISSUE_34_2(t *testing.T) {
|
|
||||||
ws1 := new(WebService).Path("/")
|
|
||||||
// change the registration order
|
|
||||||
ws1.Route(ws1.GET("/network/{id}").To(dummy))
|
|
||||||
ws1.Route(ws1.GET("/{type}/{id}").To(dummy))
|
|
||||||
routes := RouterJSR311{}.selectRoutes(ws1, "/network/12")
|
|
||||||
if len(routes) != 2 {
|
|
||||||
t.Fatal("expected 2 routes")
|
|
||||||
}
|
|
||||||
if routes[0].Path != "/network/{id}" {
|
|
||||||
t.Error("first is", routes[0].Path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestISSUE_137 ...restful
|
|
||||||
func TestISSUE_137(t *testing.T) {
|
|
||||||
ws1 := new(WebService)
|
|
||||||
ws1.Route(ws1.GET("/hello").To(dummy))
|
|
||||||
routes := RouterJSR311{}.selectRoutes(ws1, "/")
|
|
||||||
t.Log(routes)
|
|
||||||
if len(routes) > 0 {
|
|
||||||
t.Error("no route expected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSelectRoutesSlash(t *testing.T) {
|
|
||||||
ws1 := new(WebService).Path("/")
|
|
||||||
ws1.Route(ws1.GET("").To(dummy))
|
|
||||||
ws1.Route(ws1.GET("/").To(dummy))
|
|
||||||
ws1.Route(ws1.GET("/u").To(dummy))
|
|
||||||
ws1.Route(ws1.POST("/u").To(dummy))
|
|
||||||
ws1.Route(ws1.POST("/u/v").To(dummy))
|
|
||||||
ws1.Route(ws1.POST("/u/{w}").To(dummy))
|
|
||||||
ws1.Route(ws1.POST("/u/{w}/z").To(dummy))
|
|
||||||
routes := RouterJSR311{}.selectRoutes(ws1, "/u")
|
|
||||||
checkRoutesContains(routes, "/u", t)
|
|
||||||
checkRoutesContainsNo(routes, "/u/v", t)
|
|
||||||
checkRoutesContainsNo(routes, "/", t)
|
|
||||||
checkRoutesContainsNo(routes, "/u/{w}/z", t)
|
|
||||||
}
|
|
||||||
func TestSelectRoutesU(t *testing.T) {
|
|
||||||
ws1 := new(WebService).Path("/u")
|
|
||||||
ws1.Route(ws1.GET("").To(dummy))
|
|
||||||
ws1.Route(ws1.GET("/").To(dummy))
|
|
||||||
ws1.Route(ws1.GET("/v").To(dummy))
|
|
||||||
ws1.Route(ws1.POST("/{w}").To(dummy))
|
|
||||||
ws1.Route(ws1.POST("/{w}/z").To(dummy)) // so full path = /u/{w}/z
|
|
||||||
routes := RouterJSR311{}.selectRoutes(ws1, "/v") // test against /u/v
|
|
||||||
checkRoutesContains(routes, "/u/{w}", t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSelectRoutesUsers1(t *testing.T) {
|
|
||||||
ws1 := new(WebService).Path("/users")
|
|
||||||
ws1.Route(ws1.POST("").To(dummy))
|
|
||||||
ws1.Route(ws1.POST("/").To(dummy))
|
|
||||||
ws1.Route(ws1.PUT("/{id}").To(dummy))
|
|
||||||
routes := RouterJSR311{}.selectRoutes(ws1, "/1")
|
|
||||||
checkRoutesContains(routes, "/users/{id}", t)
|
|
||||||
}
|
|
||||||
func checkRoutesContains(routes []Route, path string, t *testing.T) {
|
|
||||||
if !containsRoutePath(routes, path, t) {
|
|
||||||
for _, r := range routes {
|
|
||||||
t.Logf("route %v %v", r.Method, r.Path)
|
|
||||||
}
|
|
||||||
t.Fatalf("routes should include [%v]:", path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func checkRoutesContainsNo(routes []Route, path string, t *testing.T) {
|
|
||||||
if containsRoutePath(routes, path, t) {
|
|
||||||
for _, r := range routes {
|
|
||||||
t.Logf("route %v %v", r.Method, r.Path)
|
|
||||||
}
|
|
||||||
t.Fatalf("routes should not include [%v]:", path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func containsRoutePath(routes []Route, path string, t *testing.T) bool {
|
|
||||||
for _, each := range routes {
|
|
||||||
if each.Path == path {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestSortableRouteCandidates ...restful
|
|
||||||
func TestSortableRouteCandidates(t *testing.T) {
|
|
||||||
fixture := &sortableRouteCandidates{}
|
|
||||||
r1 := routeCandidate{matchesCount: 0, literalCount: 0, nonDefaultCount: 0}
|
|
||||||
r2 := routeCandidate{matchesCount: 0, literalCount: 0, nonDefaultCount: 1}
|
|
||||||
r3 := routeCandidate{matchesCount: 0, literalCount: 1, nonDefaultCount: 1}
|
|
||||||
r4 := routeCandidate{matchesCount: 1, literalCount: 1, nonDefaultCount: 0}
|
|
||||||
r5 := routeCandidate{matchesCount: 1, literalCount: 0, nonDefaultCount: 0}
|
|
||||||
fixture.candidates = append(fixture.candidates, r5, r4, r3, r2, r1)
|
|
||||||
sort.Sort(sort.Reverse(fixture))
|
|
||||||
first := fixture.candidates[0]
|
|
||||||
if first.matchesCount != 1 && first.literalCount != 1 && first.nonDefaultCount != 0 {
|
|
||||||
t.Fatal("expected r4")
|
|
||||||
}
|
|
||||||
last := fixture.candidates[len(fixture.candidates)-1]
|
|
||||||
if last.matchesCount != 0 && last.literalCount != 0 && last.nonDefaultCount != 0 {
|
|
||||||
t.Fatal("expected r1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDetectRouteReturns404IfNoRoutePassesConditions(t *testing.T) {
|
|
||||||
called := false
|
|
||||||
shouldNotBeCalledButWas := false
|
|
||||||
|
|
||||||
routes := []Route{
|
|
||||||
new(RouteBuilder).To(dummy).
|
|
||||||
If(func(req *http.Request) bool { return false }).
|
|
||||||
Build(),
|
|
||||||
|
|
||||||
// check that condition functions are called in order
|
|
||||||
new(RouteBuilder).
|
|
||||||
To(dummy).
|
|
||||||
If(func(req *http.Request) bool { return true }).
|
|
||||||
If(func(req *http.Request) bool { called = true; return false }).
|
|
||||||
Build(),
|
|
||||||
|
|
||||||
// check that condition functions short circuit
|
|
||||||
new(RouteBuilder).
|
|
||||||
To(dummy).
|
|
||||||
If(func(req *http.Request) bool { return false }).
|
|
||||||
If(func(req *http.Request) bool { shouldNotBeCalledButWas = true; return false }).
|
|
||||||
Build(),
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := RouterJSR311{}.detectRoute(routes, (*http.Request)(nil))
|
|
||||||
if se := err.(ServiceError); se.Code != 404 {
|
|
||||||
t.Fatalf("expected 404, got %d", se.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !called {
|
|
||||||
t.Fatal("expected condition function to get called, but it wasn't")
|
|
||||||
}
|
|
||||||
|
|
||||||
if shouldNotBeCalledButWas {
|
|
||||||
t.Fatal("expected condition function to not be called, but it was")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func dummy(req *Request, resp *Response) { io.WriteString(resp.ResponseWriter, "dummy") }
|
|
34
vendor/github.com/emicklei/go-restful/log/log.go
generated
vendored
34
vendor/github.com/emicklei/go-restful/log/log.go
generated
vendored
@ -1,34 +0,0 @@
|
|||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
stdlog "log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StdLogger corresponds to a minimal subset of the interface satisfied by stdlib log.Logger
|
|
||||||
type StdLogger interface {
|
|
||||||
Print(v ...interface{})
|
|
||||||
Printf(format string, v ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
var Logger StdLogger
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// default Logger
|
|
||||||
SetLogger(stdlog.New(os.Stderr, "[restful] ", stdlog.LstdFlags|stdlog.Lshortfile))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLogger sets the logger for this package
|
|
||||||
func SetLogger(customLogger StdLogger) {
|
|
||||||
Logger = customLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print delegates to the Logger
|
|
||||||
func Print(v ...interface{}) {
|
|
||||||
Logger.Print(v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf delegates to the Logger
|
|
||||||
func Printf(format string, v ...interface{}) {
|
|
||||||
Logger.Printf(format, v...)
|
|
||||||
}
|
|
32
vendor/github.com/emicklei/go-restful/logger.go
generated
vendored
32
vendor/github.com/emicklei/go-restful/logger.go
generated
vendored
@ -1,32 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2014 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
import (
|
|
||||||
"github.com/emicklei/go-restful/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var trace bool = false
|
|
||||||
var traceLogger log.StdLogger
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
traceLogger = log.Logger // use the package logger by default
|
|
||||||
}
|
|
||||||
|
|
||||||
// TraceLogger enables detailed logging of Http request matching and filter invocation. Default no logger is set.
|
|
||||||
// You may call EnableTracing() directly to enable trace logging to the package-wide logger.
|
|
||||||
func TraceLogger(logger log.StdLogger) {
|
|
||||||
traceLogger = logger
|
|
||||||
EnableTracing(logger != nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLogger exposes the setter for the global logger on the top-level package
|
|
||||||
func SetLogger(customLogger log.StdLogger) {
|
|
||||||
log.SetLogger(customLogger)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnableTracing can be used to Trace logging on and off.
|
|
||||||
func EnableTracing(enabled bool) {
|
|
||||||
trace = enabled
|
|
||||||
}
|
|
45
vendor/github.com/emicklei/go-restful/mime.go
generated
vendored
45
vendor/github.com/emicklei/go-restful/mime.go
generated
vendored
@ -1,45 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mime struct {
|
|
||||||
media string
|
|
||||||
quality float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// insertMime adds a mime to a list and keeps it sorted by quality.
|
|
||||||
func insertMime(l []mime, e mime) []mime {
|
|
||||||
for i, each := range l {
|
|
||||||
// if current mime has lower quality then insert before
|
|
||||||
if e.quality > each.quality {
|
|
||||||
left := append([]mime{}, l[0:i]...)
|
|
||||||
return append(append(left, e), l[i:]...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return append(l, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sortedMimes returns a list of mime sorted (desc) by its specified quality.
|
|
||||||
func sortedMimes(accept string) (sorted []mime) {
|
|
||||||
for _, each := range strings.Split(accept, ",") {
|
|
||||||
typeAndQuality := strings.Split(strings.Trim(each, " "), ";")
|
|
||||||
if len(typeAndQuality) == 1 {
|
|
||||||
sorted = insertMime(sorted, mime{typeAndQuality[0], 1.0})
|
|
||||||
} else {
|
|
||||||
// take factor
|
|
||||||
parts := strings.Split(typeAndQuality[1], "=")
|
|
||||||
if len(parts) == 2 {
|
|
||||||
f, err := strconv.ParseFloat(parts[1], 64)
|
|
||||||
if err != nil {
|
|
||||||
traceLogger.Printf("unable to parse quality in %s, %v", each, err)
|
|
||||||
} else {
|
|
||||||
sorted = insertMime(sorted, mime{typeAndQuality[0], f})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
17
vendor/github.com/emicklei/go-restful/mime_test.go
generated
vendored
17
vendor/github.com/emicklei/go-restful/mime_test.go
generated
vendored
@ -1,17 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// go test -v -test.run TestSortMimes ...restful
|
|
||||||
func TestSortMimes(t *testing.T) {
|
|
||||||
accept := "text/html; q=0.8, text/plain, image/gif, */*; q=0.01, image/jpeg"
|
|
||||||
result := sortedMimes(accept)
|
|
||||||
got := fmt.Sprintf("%v", result)
|
|
||||||
want := "[{text/plain 1} {image/gif 1} {image/jpeg 1} {text/html 0.8} {*/* 0.01}]"
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("bad sort order of mime types:%s", got)
|
|
||||||
}
|
|
||||||
}
|
|
34
vendor/github.com/emicklei/go-restful/options_filter.go
generated
vendored
34
vendor/github.com/emicklei/go-restful/options_filter.go
generated
vendored
@ -1,34 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// OPTIONSFilter is a filter function that inspects the Http Request for the OPTIONS method
|
|
||||||
// and provides the response with a set of allowed methods for the request URL Path.
|
|
||||||
// As for any filter, you can also install it for a particular WebService within a Container.
|
|
||||||
// Note: this filter is not needed when using CrossOriginResourceSharing (for CORS).
|
|
||||||
func (c *Container) OPTIONSFilter(req *Request, resp *Response, chain *FilterChain) {
|
|
||||||
if "OPTIONS" != req.Request.Method {
|
|
||||||
chain.ProcessFilter(req, resp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
archs := req.Request.Header.Get(HEADER_AccessControlRequestHeaders)
|
|
||||||
methods := strings.Join(c.computeAllowedMethods(req), ",")
|
|
||||||
origin := req.Request.Header.Get(HEADER_Origin)
|
|
||||||
|
|
||||||
resp.AddHeader(HEADER_Allow, methods)
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowOrigin, origin)
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowHeaders, archs)
|
|
||||||
resp.AddHeader(HEADER_AccessControlAllowMethods, methods)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OPTIONSFilter is a filter function that inspects the Http Request for the OPTIONS method
|
|
||||||
// and provides the response with a set of allowed methods for the request URL Path.
|
|
||||||
// Note: this filter is not needed when using CrossOriginResourceSharing (for CORS).
|
|
||||||
func OPTIONSFilter() FilterFunction {
|
|
||||||
return DefaultContainer.OPTIONSFilter
|
|
||||||
}
|
|
34
vendor/github.com/emicklei/go-restful/options_filter_test.go
generated
vendored
34
vendor/github.com/emicklei/go-restful/options_filter_test.go
generated
vendored
@ -1,34 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// go test -v -test.run TestOptionsFilter ...restful
|
|
||||||
func TestOptionsFilter(t *testing.T) {
|
|
||||||
tearDown()
|
|
||||||
ws := new(WebService)
|
|
||||||
ws.Route(ws.GET("/candy/{kind}").To(dummy))
|
|
||||||
ws.Route(ws.DELETE("/candy/{kind}").To(dummy))
|
|
||||||
ws.Route(ws.POST("/candies").To(dummy))
|
|
||||||
Add(ws)
|
|
||||||
Filter(OPTIONSFilter())
|
|
||||||
|
|
||||||
httpRequest, _ := http.NewRequest("OPTIONS", "http://here.io/candy/gum", nil)
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
DefaultContainer.dispatch(httpWriter, httpRequest)
|
|
||||||
actual := httpWriter.Header().Get(HEADER_Allow)
|
|
||||||
if "GET,DELETE" != actual {
|
|
||||||
t.Fatal("expected: GET,DELETE but got:" + actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
httpRequest, _ = http.NewRequest("OPTIONS", "http://here.io/candies", nil)
|
|
||||||
httpWriter = httptest.NewRecorder()
|
|
||||||
DefaultContainer.dispatch(httpWriter, httpRequest)
|
|
||||||
actual = httpWriter.Header().Get(HEADER_Allow)
|
|
||||||
if "POST" != actual {
|
|
||||||
t.Fatal("expected: POST but got:" + actual)
|
|
||||||
}
|
|
||||||
}
|
|
114
vendor/github.com/emicklei/go-restful/parameter.go
generated
vendored
114
vendor/github.com/emicklei/go-restful/parameter.go
generated
vendored
@ -1,114 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
const (
|
|
||||||
// PathParameterKind = indicator of Request parameter type "path"
|
|
||||||
PathParameterKind = iota
|
|
||||||
|
|
||||||
// QueryParameterKind = indicator of Request parameter type "query"
|
|
||||||
QueryParameterKind
|
|
||||||
|
|
||||||
// BodyParameterKind = indicator of Request parameter type "body"
|
|
||||||
BodyParameterKind
|
|
||||||
|
|
||||||
// HeaderParameterKind = indicator of Request parameter type "header"
|
|
||||||
HeaderParameterKind
|
|
||||||
|
|
||||||
// FormParameterKind = indicator of Request parameter type "form"
|
|
||||||
FormParameterKind
|
|
||||||
)
|
|
||||||
|
|
||||||
// Parameter is for documententing the parameter used in a Http Request
|
|
||||||
// ParameterData kinds are Path,Query and Body
|
|
||||||
type Parameter struct {
|
|
||||||
data *ParameterData
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParameterData represents the state of a Parameter.
|
|
||||||
// It is made public to make it accessible to e.g. the Swagger package.
|
|
||||||
type ParameterData struct {
|
|
||||||
Name, Description, DataType, DataFormat string
|
|
||||||
Kind int
|
|
||||||
Required bool
|
|
||||||
AllowableValues map[string]string
|
|
||||||
AllowMultiple bool
|
|
||||||
DefaultValue string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data returns the state of the Parameter
|
|
||||||
func (p *Parameter) Data() ParameterData {
|
|
||||||
return *p.data
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kind returns the parameter type indicator (see const for valid values)
|
|
||||||
func (p *Parameter) Kind() int {
|
|
||||||
return p.data.Kind
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parameter) bePath() *Parameter {
|
|
||||||
p.data.Kind = PathParameterKind
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
func (p *Parameter) beQuery() *Parameter {
|
|
||||||
p.data.Kind = QueryParameterKind
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
func (p *Parameter) beBody() *Parameter {
|
|
||||||
p.data.Kind = BodyParameterKind
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parameter) beHeader() *Parameter {
|
|
||||||
p.data.Kind = HeaderParameterKind
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Parameter) beForm() *Parameter {
|
|
||||||
p.data.Kind = FormParameterKind
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required sets the required field and returns the receiver
|
|
||||||
func (p *Parameter) Required(required bool) *Parameter {
|
|
||||||
p.data.Required = required
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowMultiple sets the allowMultiple field and returns the receiver
|
|
||||||
func (p *Parameter) AllowMultiple(multiple bool) *Parameter {
|
|
||||||
p.data.AllowMultiple = multiple
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllowableValues sets the allowableValues field and returns the receiver
|
|
||||||
func (p *Parameter) AllowableValues(values map[string]string) *Parameter {
|
|
||||||
p.data.AllowableValues = values
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// DataType sets the dataType field and returns the receiver
|
|
||||||
func (p *Parameter) DataType(typeName string) *Parameter {
|
|
||||||
p.data.DataType = typeName
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// DataFormat sets the dataFormat field for Swagger UI
|
|
||||||
func (p *Parameter) DataFormat(formatName string) *Parameter {
|
|
||||||
p.data.DataFormat = formatName
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultValue sets the default value field and returns the receiver
|
|
||||||
func (p *Parameter) DefaultValue(stringRepresentation string) *Parameter {
|
|
||||||
p.data.DefaultValue = stringRepresentation
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Description sets the description value field and returns the receiver
|
|
||||||
func (p *Parameter) Description(doc string) *Parameter {
|
|
||||||
p.data.Description = doc
|
|
||||||
return p
|
|
||||||
}
|
|
69
vendor/github.com/emicklei/go-restful/path_expression.go
generated
vendored
69
vendor/github.com/emicklei/go-restful/path_expression.go
generated
vendored
@ -1,69 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PathExpression holds a compiled path expression (RegExp) needed to match against
|
|
||||||
// Http request paths and to extract path parameter values.
|
|
||||||
type pathExpression struct {
|
|
||||||
LiteralCount int // the number of literal characters (means those not resulting from template variable substitution)
|
|
||||||
VarCount int // the number of named parameters (enclosed by {}) in the path
|
|
||||||
Matcher *regexp.Regexp
|
|
||||||
Source string // Path as defined by the RouteBuilder
|
|
||||||
tokens []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPathExpression creates a PathExpression from the input URL path.
|
|
||||||
// Returns an error if the path is invalid.
|
|
||||||
func newPathExpression(path string) (*pathExpression, error) {
|
|
||||||
expression, literalCount, varCount, tokens := templateToRegularExpression(path)
|
|
||||||
compiled, err := regexp.Compile(expression)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &pathExpression{literalCount, varCount, compiled, expression, tokens}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-370003.7.3
|
|
||||||
func templateToRegularExpression(template string) (expression string, literalCount int, varCount int, tokens []string) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
buffer.WriteString("^")
|
|
||||||
//tokens = strings.Split(template, "/")
|
|
||||||
tokens = tokenizePath(template)
|
|
||||||
for _, each := range tokens {
|
|
||||||
if each == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
buffer.WriteString("/")
|
|
||||||
if strings.HasPrefix(each, "{") {
|
|
||||||
// check for regular expression in variable
|
|
||||||
colon := strings.Index(each, ":")
|
|
||||||
if colon != -1 {
|
|
||||||
// extract expression
|
|
||||||
paramExpr := strings.TrimSpace(each[colon+1 : len(each)-1])
|
|
||||||
if paramExpr == "*" { // special case
|
|
||||||
buffer.WriteString("(.*)")
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(fmt.Sprintf("(%s)", paramExpr)) // between colon and closing moustache
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// plain var
|
|
||||||
buffer.WriteString("([^/]+?)")
|
|
||||||
}
|
|
||||||
varCount += 1
|
|
||||||
} else {
|
|
||||||
literalCount += len(each)
|
|
||||||
encoded := each // TODO URI encode
|
|
||||||
buffer.WriteString(regexp.QuoteMeta(encoded))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.TrimRight(buffer.String(), "/") + "(/.*)?$", literalCount, varCount, tokens
|
|
||||||
}
|
|
37
vendor/github.com/emicklei/go-restful/path_expression_test.go
generated
vendored
37
vendor/github.com/emicklei/go-restful/path_expression_test.go
generated
vendored
@ -1,37 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
var tempregexs = []struct {
|
|
||||||
template, regex string
|
|
||||||
literalCount, varCount int
|
|
||||||
}{
|
|
||||||
{"", "^(/.*)?$", 0, 0},
|
|
||||||
{"/a/{b}/c/", "^/a/([^/]+?)/c(/.*)?$", 2, 1},
|
|
||||||
{"/{a}/{b}/{c-d-e}/", "^/([^/]+?)/([^/]+?)/([^/]+?)(/.*)?$", 0, 3},
|
|
||||||
{"/{p}/abcde", "^/([^/]+?)/abcde(/.*)?$", 5, 1},
|
|
||||||
{"/a/{b:*}", "^/a/(.*)(/.*)?$", 1, 1},
|
|
||||||
{"/a/{b:[a-z]+}", "^/a/([a-z]+)(/.*)?$", 1, 1},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTemplateToRegularExpression(t *testing.T) {
|
|
||||||
ok := true
|
|
||||||
for i, fixture := range tempregexs {
|
|
||||||
actual, lCount, vCount, _ := templateToRegularExpression(fixture.template)
|
|
||||||
if actual != fixture.regex {
|
|
||||||
t.Logf("regex mismatch, expected:%v , actual:%v, line:%v\n", fixture.regex, actual, i) // 11 = where the data starts
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
if lCount != fixture.literalCount {
|
|
||||||
t.Logf("literal count mismatch, expected:%v , actual:%v, line:%v\n", fixture.literalCount, lCount, i)
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
if vCount != fixture.varCount {
|
|
||||||
t.Logf("variable count mismatch, expected:%v , actual:%v, line:%v\n", fixture.varCount, vCount, i)
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("one or more expression did not match")
|
|
||||||
}
|
|
||||||
}
|
|
113
vendor/github.com/emicklei/go-restful/request.go
generated
vendored
113
vendor/github.com/emicklei/go-restful/request.go
generated
vendored
@ -1,113 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"compress/zlib"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultRequestContentType string
|
|
||||||
|
|
||||||
// Request is a wrapper for a http Request that provides convenience methods
|
|
||||||
type Request struct {
|
|
||||||
Request *http.Request
|
|
||||||
pathParameters map[string]string
|
|
||||||
attributes map[string]interface{} // for storing request-scoped values
|
|
||||||
selectedRoutePath string // root path + route path that matched the request, e.g. /meetings/{id}/attendees
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRequest(httpRequest *http.Request) *Request {
|
|
||||||
return &Request{
|
|
||||||
Request: httpRequest,
|
|
||||||
pathParameters: map[string]string{},
|
|
||||||
attributes: map[string]interface{}{},
|
|
||||||
} // empty parameters, attributes
|
|
||||||
}
|
|
||||||
|
|
||||||
// If ContentType is missing or */* is given then fall back to this type, otherwise
|
|
||||||
// a "Unable to unmarshal content of type:" response is returned.
|
|
||||||
// Valid values are restful.MIME_JSON and restful.MIME_XML
|
|
||||||
// Example:
|
|
||||||
// restful.DefaultRequestContentType(restful.MIME_JSON)
|
|
||||||
func DefaultRequestContentType(mime string) {
|
|
||||||
defaultRequestContentType = mime
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathParameter accesses the Path parameter value by its name
|
|
||||||
func (r *Request) PathParameter(name string) string {
|
|
||||||
return r.pathParameters[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathParameters accesses the Path parameter values
|
|
||||||
func (r *Request) PathParameters() map[string]string {
|
|
||||||
return r.pathParameters
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryParameter returns the (first) Query parameter value by its name
|
|
||||||
func (r *Request) QueryParameter(name string) string {
|
|
||||||
return r.Request.FormValue(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BodyParameter parses the body of the request (once for typically a POST or a PUT) and returns the value of the given name or an error.
|
|
||||||
func (r *Request) BodyParameter(name string) (string, error) {
|
|
||||||
err := r.Request.ParseForm()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return r.Request.PostFormValue(name), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderParameter returns the HTTP Header value of a Header name or empty if missing
|
|
||||||
func (r *Request) HeaderParameter(name string) string {
|
|
||||||
return r.Request.Header.Get(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadEntity checks the Accept header and reads the content into the entityPointer.
|
|
||||||
func (r *Request) ReadEntity(entityPointer interface{}) (err error) {
|
|
||||||
contentType := r.Request.Header.Get(HEADER_ContentType)
|
|
||||||
contentEncoding := r.Request.Header.Get(HEADER_ContentEncoding)
|
|
||||||
|
|
||||||
// check if the request body needs decompression
|
|
||||||
if ENCODING_GZIP == contentEncoding {
|
|
||||||
gzipReader := currentCompressorProvider.AcquireGzipReader()
|
|
||||||
defer currentCompressorProvider.ReleaseGzipReader(gzipReader)
|
|
||||||
gzipReader.Reset(r.Request.Body)
|
|
||||||
r.Request.Body = gzipReader
|
|
||||||
} else if ENCODING_DEFLATE == contentEncoding {
|
|
||||||
zlibReader, err := zlib.NewReader(r.Request.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Request.Body = zlibReader
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookup the EntityReader, use defaultRequestContentType if needed and provided
|
|
||||||
entityReader, ok := entityAccessRegistry.accessorAt(contentType)
|
|
||||||
if !ok {
|
|
||||||
if len(defaultRequestContentType) != 0 {
|
|
||||||
entityReader, ok = entityAccessRegistry.accessorAt(defaultRequestContentType)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return NewError(http.StatusBadRequest, "Unable to unmarshal content of type:"+contentType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entityReader.Read(r, entityPointer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAttribute adds or replaces the attribute with the given value.
|
|
||||||
func (r *Request) SetAttribute(name string, value interface{}) {
|
|
||||||
r.attributes[name] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attribute returns the value associated to the given name. Returns nil if absent.
|
|
||||||
func (r Request) Attribute(name string) interface{} {
|
|
||||||
return r.attributes[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// SelectedRoutePath root path + route path that matched the request, e.g. /meetings/{id}/attendees
|
|
||||||
func (r Request) SelectedRoutePath() string {
|
|
||||||
return r.selectedRoutePath
|
|
||||||
}
|
|
141
vendor/github.com/emicklei/go-restful/request_test.go
generated
vendored
141
vendor/github.com/emicklei/go-restful/request_test.go
generated
vendored
@ -1,141 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestQueryParameter(t *testing.T) {
|
|
||||||
hreq := http.Request{Method: "GET"}
|
|
||||||
hreq.URL, _ = url.Parse("http://www.google.com/search?q=foo&q=bar")
|
|
||||||
rreq := Request{Request: &hreq}
|
|
||||||
if rreq.QueryParameter("q") != "foo" {
|
|
||||||
t.Errorf("q!=foo %#v", rreq)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Anything map[string]interface{}
|
|
||||||
|
|
||||||
type Number struct {
|
|
||||||
ValueFloat float64
|
|
||||||
ValueInt int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type Sample struct {
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadEntityJson(t *testing.T) {
|
|
||||||
bodyReader := strings.NewReader(`{"Value" : "42"}`)
|
|
||||||
httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
|
|
||||||
httpRequest.Header.Set("Content-Type", "application/json")
|
|
||||||
request := &Request{Request: httpRequest}
|
|
||||||
sam := new(Sample)
|
|
||||||
request.ReadEntity(sam)
|
|
||||||
if sam.Value != "42" {
|
|
||||||
t.Fatal("read failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadEntityJsonCharset(t *testing.T) {
|
|
||||||
bodyReader := strings.NewReader(`{"Value" : "42"}`)
|
|
||||||
httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
|
|
||||||
httpRequest.Header.Set("Content-Type", "application/json; charset=UTF-8")
|
|
||||||
request := NewRequest(httpRequest)
|
|
||||||
sam := new(Sample)
|
|
||||||
request.ReadEntity(sam)
|
|
||||||
if sam.Value != "42" {
|
|
||||||
t.Fatal("read failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadEntityJsonNumber(t *testing.T) {
|
|
||||||
bodyReader := strings.NewReader(`{"Value" : 4899710515899924123}`)
|
|
||||||
httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
|
|
||||||
httpRequest.Header.Set("Content-Type", "application/json")
|
|
||||||
request := &Request{Request: httpRequest}
|
|
||||||
any := make(Anything)
|
|
||||||
request.ReadEntity(&any)
|
|
||||||
number, ok := any["Value"].(json.Number)
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("read failed")
|
|
||||||
}
|
|
||||||
vint, err := number.Int64()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("convert failed")
|
|
||||||
}
|
|
||||||
if vint != 4899710515899924123 {
|
|
||||||
t.Fatal("read failed")
|
|
||||||
}
|
|
||||||
vfloat, err := number.Float64()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("convert failed")
|
|
||||||
}
|
|
||||||
// match the default behaviour
|
|
||||||
vstring := strconv.FormatFloat(vfloat, 'e', 15, 64)
|
|
||||||
if vstring != "4.899710515899924e+18" {
|
|
||||||
t.Fatal("convert float64 failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadEntityJsonLong(t *testing.T) {
|
|
||||||
bodyReader := strings.NewReader(`{"ValueFloat" : 4899710515899924123, "ValueInt": 4899710515899924123}`)
|
|
||||||
httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
|
|
||||||
httpRequest.Header.Set("Content-Type", "application/json")
|
|
||||||
request := &Request{Request: httpRequest}
|
|
||||||
number := new(Number)
|
|
||||||
request.ReadEntity(&number)
|
|
||||||
if number.ValueInt != 4899710515899924123 {
|
|
||||||
t.Fatal("read failed")
|
|
||||||
}
|
|
||||||
// match the default behaviour
|
|
||||||
vstring := strconv.FormatFloat(number.ValueFloat, 'e', 15, 64)
|
|
||||||
if vstring != "4.899710515899924e+18" {
|
|
||||||
t.Fatal("convert float64 failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBodyParameter(t *testing.T) {
|
|
||||||
bodyReader := strings.NewReader(`value1=42&value2=43`)
|
|
||||||
httpRequest, _ := http.NewRequest("POST", "/test?value1=44", bodyReader) // POST and PUT body parameters take precedence over URL query string
|
|
||||||
httpRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
|
|
||||||
request := NewRequest(httpRequest)
|
|
||||||
v1, err := request.BodyParameter("value1")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
v2, err := request.BodyParameter("value2")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
if v1 != "42" || v2 != "43" {
|
|
||||||
t.Fatal("read failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadEntityUnkown(t *testing.T) {
|
|
||||||
bodyReader := strings.NewReader("?")
|
|
||||||
httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
|
|
||||||
httpRequest.Header.Set("Content-Type", "application/rubbish")
|
|
||||||
request := NewRequest(httpRequest)
|
|
||||||
sam := new(Sample)
|
|
||||||
err := request.ReadEntity(sam)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("read should be in error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetAttribute(t *testing.T) {
|
|
||||||
bodyReader := strings.NewReader("?")
|
|
||||||
httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
|
|
||||||
request := NewRequest(httpRequest)
|
|
||||||
request.SetAttribute("go", "there")
|
|
||||||
there := request.Attribute("go")
|
|
||||||
if there != "there" {
|
|
||||||
t.Fatalf("missing request attribute:%v", there)
|
|
||||||
}
|
|
||||||
}
|
|
250
vendor/github.com/emicklei/go-restful/response.go
generated
vendored
250
vendor/github.com/emicklei/go-restful/response.go
generated
vendored
@ -1,250 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultResponseMimeType is DEPRECATED, use DefaultResponseContentType(mime)
|
|
||||||
var DefaultResponseMimeType string
|
|
||||||
|
|
||||||
//PrettyPrintResponses controls the indentation feature of XML and JSON serialization
|
|
||||||
var PrettyPrintResponses = true
|
|
||||||
|
|
||||||
// Response is a wrapper on the actual http ResponseWriter
|
|
||||||
// It provides several convenience methods to prepare and write response content.
|
|
||||||
type Response struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
requestAccept string // mime-type what the Http Request says it wants to receive
|
|
||||||
routeProduces []string // mime-types what the Route says it can produce
|
|
||||||
statusCode int // HTTP status code that has been written explicitly (if zero then net/http has written 200)
|
|
||||||
contentLength int // number of bytes written for the response body
|
|
||||||
prettyPrint bool // controls the indentation feature of XML and JSON serialization. It is initialized using var PrettyPrintResponses.
|
|
||||||
err error // err property is kept when WriteError is called
|
|
||||||
hijacker http.Hijacker // if underlying ResponseWriter supports it
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewResponse creates a new response based on a http ResponseWriter.
|
|
||||||
func NewResponse(httpWriter http.ResponseWriter) *Response {
|
|
||||||
hijacker, _ := httpWriter.(http.Hijacker)
|
|
||||||
return &Response{ResponseWriter: httpWriter, routeProduces: []string{}, statusCode: http.StatusOK, prettyPrint: PrettyPrintResponses, hijacker: hijacker}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultResponseContentType set a default.
|
|
||||||
// If Accept header matching fails, fall back to this type.
|
|
||||||
// Valid values are restful.MIME_JSON and restful.MIME_XML
|
|
||||||
// Example:
|
|
||||||
// restful.DefaultResponseContentType(restful.MIME_JSON)
|
|
||||||
func DefaultResponseContentType(mime string) {
|
|
||||||
DefaultResponseMimeType = mime
|
|
||||||
}
|
|
||||||
|
|
||||||
// InternalServerError writes the StatusInternalServerError header.
|
|
||||||
// DEPRECATED, use WriteErrorString(http.StatusInternalServerError,reason)
|
|
||||||
func (r Response) InternalServerError() Response {
|
|
||||||
r.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hijack implements the http.Hijacker interface. This expands
|
|
||||||
// the Response to fulfill http.Hijacker if the underlying
|
|
||||||
// http.ResponseWriter supports it.
|
|
||||||
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
||||||
if r.hijacker == nil {
|
|
||||||
return nil, nil, errors.New("http.Hijacker not implemented by underlying http.ResponseWriter")
|
|
||||||
}
|
|
||||||
return r.hijacker.Hijack()
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrettyPrint changes whether this response must produce pretty (line-by-line, indented) JSON or XML output.
|
|
||||||
func (r *Response) PrettyPrint(bePretty bool) {
|
|
||||||
r.prettyPrint = bePretty
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddHeader is a shortcut for .Header().Add(header,value)
|
|
||||||
func (r Response) AddHeader(header string, value string) Response {
|
|
||||||
r.Header().Add(header, value)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRequestAccepts tells the response what Mime-type(s) the HTTP request said it wants to accept. Exposed for testing.
|
|
||||||
func (r *Response) SetRequestAccepts(mime string) {
|
|
||||||
r.requestAccept = mime
|
|
||||||
}
|
|
||||||
|
|
||||||
// EntityWriter returns the registered EntityWriter that the entity (requested resource)
|
|
||||||
// can write according to what the request wants (Accept) and what the Route can produce or what the restful defaults say.
|
|
||||||
// If called before WriteEntity and WriteHeader then a false return value can be used to write a 406: Not Acceptable.
|
|
||||||
func (r *Response) EntityWriter() (EntityReaderWriter, bool) {
|
|
||||||
sorted := sortedMimes(r.requestAccept)
|
|
||||||
for _, eachAccept := range sorted {
|
|
||||||
for _, eachProduce := range r.routeProduces {
|
|
||||||
if eachProduce == eachAccept.media {
|
|
||||||
if w, ok := entityAccessRegistry.accessorAt(eachAccept.media); ok {
|
|
||||||
return w, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if eachAccept.media == "*/*" {
|
|
||||||
for _, each := range r.routeProduces {
|
|
||||||
if w, ok := entityAccessRegistry.accessorAt(each); ok {
|
|
||||||
return w, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if requestAccept is empty
|
|
||||||
writer, ok := entityAccessRegistry.accessorAt(r.requestAccept)
|
|
||||||
if !ok {
|
|
||||||
// if not registered then fallback to the defaults (if set)
|
|
||||||
if DefaultResponseMimeType == MIME_JSON {
|
|
||||||
return entityAccessRegistry.accessorAt(MIME_JSON)
|
|
||||||
}
|
|
||||||
if DefaultResponseMimeType == MIME_XML {
|
|
||||||
return entityAccessRegistry.accessorAt(MIME_XML)
|
|
||||||
}
|
|
||||||
// Fallback to whatever the route says it can produce.
|
|
||||||
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
|
||||||
for _, each := range r.routeProduces {
|
|
||||||
if w, ok := entityAccessRegistry.accessorAt(each); ok {
|
|
||||||
return w, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if trace {
|
|
||||||
traceLogger.Printf("no registered EntityReaderWriter found for %s", r.requestAccept)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return writer, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteEntity calls WriteHeaderAndEntity with Http Status OK (200)
|
|
||||||
func (r *Response) WriteEntity(value interface{}) error {
|
|
||||||
return r.WriteHeaderAndEntity(http.StatusOK, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeaderAndEntity marshals the value using the representation denoted by the Accept Header and the registered EntityWriters.
|
|
||||||
// If no Accept header is specified (or */*) then respond with the Content-Type as specified by the first in the Route.Produces.
|
|
||||||
// If an Accept header is specified then respond with the Content-Type as specified by the first in the Route.Produces that is matched with the Accept header.
|
|
||||||
// If the value is nil then no response is send except for the Http status. You may want to call WriteHeader(http.StatusNotFound) instead.
|
|
||||||
// If there is no writer available that can represent the value in the requested MIME type then Http Status NotAcceptable is written.
|
|
||||||
// Current implementation ignores any q-parameters in the Accept Header.
|
|
||||||
// Returns an error if the value could not be written on the response.
|
|
||||||
func (r *Response) WriteHeaderAndEntity(status int, value interface{}) error {
|
|
||||||
writer, ok := r.EntityWriter()
|
|
||||||
if !ok {
|
|
||||||
r.WriteHeader(http.StatusNotAcceptable)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return writer.Write(r, status, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteAsXml is a convenience method for writing a value in xml (requires Xml tags on the value)
|
|
||||||
// It uses the standard encoding/xml package for marshalling the value ; not using a registered EntityReaderWriter.
|
|
||||||
func (r *Response) WriteAsXml(value interface{}) error {
|
|
||||||
return writeXML(r, http.StatusOK, MIME_XML, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeaderAndXml is a convenience method for writing a status and value in xml (requires Xml tags on the value)
|
|
||||||
// It uses the standard encoding/xml package for marshalling the value ; not using a registered EntityReaderWriter.
|
|
||||||
func (r *Response) WriteHeaderAndXml(status int, value interface{}) error {
|
|
||||||
return writeXML(r, status, MIME_XML, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteAsJson is a convenience method for writing a value in json.
|
|
||||||
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
|
|
||||||
func (r *Response) WriteAsJson(value interface{}) error {
|
|
||||||
return writeJSON(r, http.StatusOK, MIME_JSON, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteJson is a convenience method for writing a value in Json with a given Content-Type.
|
|
||||||
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
|
|
||||||
func (r *Response) WriteJson(value interface{}, contentType string) error {
|
|
||||||
return writeJSON(r, http.StatusOK, contentType, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeaderAndJson is a convenience method for writing the status and a value in Json with a given Content-Type.
|
|
||||||
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
|
|
||||||
func (r *Response) WriteHeaderAndJson(status int, value interface{}, contentType string) error {
|
|
||||||
return writeJSON(r, status, contentType, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteError write the http status and the error string on the response.
|
|
||||||
func (r *Response) WriteError(httpStatus int, err error) error {
|
|
||||||
r.err = err
|
|
||||||
return r.WriteErrorString(httpStatus, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteServiceError is a convenience method for a responding with a status and a ServiceError
|
|
||||||
func (r *Response) WriteServiceError(httpStatus int, err ServiceError) error {
|
|
||||||
r.err = err
|
|
||||||
return r.WriteHeaderAndEntity(httpStatus, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteErrorString is a convenience method for an error status with the actual error
|
|
||||||
func (r *Response) WriteErrorString(httpStatus int, errorReason string) error {
|
|
||||||
if r.err == nil {
|
|
||||||
// if not called from WriteError
|
|
||||||
r.err = errors.New(errorReason)
|
|
||||||
}
|
|
||||||
r.WriteHeader(httpStatus)
|
|
||||||
if _, err := r.Write([]byte(errorReason)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush implements http.Flusher interface, which sends any buffered data to the client.
|
|
||||||
func (r *Response) Flush() {
|
|
||||||
if f, ok := r.ResponseWriter.(http.Flusher); ok {
|
|
||||||
f.Flush()
|
|
||||||
} else if trace {
|
|
||||||
traceLogger.Printf("ResponseWriter %v doesn't support Flush", r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeader is overridden to remember the Status Code that has been written.
|
|
||||||
// Changes to the Header of the response have no effect after this.
|
|
||||||
func (r *Response) WriteHeader(httpStatus int) {
|
|
||||||
r.statusCode = httpStatus
|
|
||||||
r.ResponseWriter.WriteHeader(httpStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StatusCode returns the code that has been written using WriteHeader.
|
|
||||||
func (r Response) StatusCode() int {
|
|
||||||
if 0 == r.statusCode {
|
|
||||||
// no status code has been written yet; assume OK
|
|
||||||
return http.StatusOK
|
|
||||||
}
|
|
||||||
return r.statusCode
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes the data to the connection as part of an HTTP reply.
|
|
||||||
// Write is part of http.ResponseWriter interface.
|
|
||||||
func (r *Response) Write(bytes []byte) (int, error) {
|
|
||||||
written, err := r.ResponseWriter.Write(bytes)
|
|
||||||
r.contentLength += written
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContentLength returns the number of bytes written for the response content.
|
|
||||||
// Note that this value is only correct if all data is written through the Response using its Write* methods.
|
|
||||||
// Data written directly using the underlying http.ResponseWriter is not accounted for.
|
|
||||||
func (r Response) ContentLength() int {
|
|
||||||
return r.contentLength
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseNotify is part of http.CloseNotifier interface
|
|
||||||
func (r Response) CloseNotify() <-chan bool {
|
|
||||||
return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns the err created by WriteError
|
|
||||||
func (r Response) Error() error {
|
|
||||||
return r.err
|
|
||||||
}
|
|
213
vendor/github.com/emicklei/go-restful/response_test.go
generated
vendored
213
vendor/github.com/emicklei/go-restful/response_test.go
generated
vendored
@ -1,213 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestWriteHeader(t *testing.T) {
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: "*/*", routeProduces: []string{"*/*"}, prettyPrint: true}
|
|
||||||
resp.WriteHeader(123)
|
|
||||||
if resp.StatusCode() != 123 {
|
|
||||||
t.Errorf("Unexpected status code:%d", resp.StatusCode())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoWriteHeader(t *testing.T) {
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: "*/*", routeProduces: []string{"*/*"}, prettyPrint: true}
|
|
||||||
if resp.StatusCode() != http.StatusOK {
|
|
||||||
t.Errorf("Unexpected status code:%d", resp.StatusCode())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type food struct {
|
|
||||||
Kind string
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestMeasureContentLengthXml ...restful
|
|
||||||
func TestMeasureContentLengthXml(t *testing.T) {
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: "*/*", routeProduces: []string{"*/*"}, prettyPrint: true}
|
|
||||||
resp.WriteAsXml(food{"apple"})
|
|
||||||
if resp.ContentLength() != 76 {
|
|
||||||
t.Errorf("Incorrect measured length:%d", resp.ContentLength())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestMeasureContentLengthJson ...restful
|
|
||||||
func TestMeasureContentLengthJson(t *testing.T) {
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: "*/*", routeProduces: []string{"*/*"}, prettyPrint: true}
|
|
||||||
resp.WriteAsJson(food{"apple"})
|
|
||||||
if resp.ContentLength() != 22 {
|
|
||||||
t.Errorf("Incorrect measured length:%d", resp.ContentLength())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestMeasureContentLengthJsonNotPretty ...restful
|
|
||||||
func TestMeasureContentLengthJsonNotPretty(t *testing.T) {
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: "*/*", routeProduces: []string{"*/*"}}
|
|
||||||
resp.WriteAsJson(food{"apple"})
|
|
||||||
if resp.ContentLength() != 17 { // 16+1 using the Encoder directly yields another /n
|
|
||||||
t.Errorf("Incorrect measured length:%d", resp.ContentLength())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestMeasureContentLengthWriteErrorString ...restful
|
|
||||||
func TestMeasureContentLengthWriteErrorString(t *testing.T) {
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: "*/*", routeProduces: []string{"*/*"}, prettyPrint: true}
|
|
||||||
resp.WriteErrorString(404, "Invalid")
|
|
||||||
if resp.ContentLength() != len("Invalid") {
|
|
||||||
t.Errorf("Incorrect measured length:%d", resp.ContentLength())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestStatusIsPassedToResponse ...restful
|
|
||||||
func TestStatusIsPassedToResponse(t *testing.T) {
|
|
||||||
for _, each := range []struct {
|
|
||||||
write, read int
|
|
||||||
}{
|
|
||||||
{write: 204, read: 204},
|
|
||||||
{write: 304, read: 304},
|
|
||||||
{write: 200, read: 200},
|
|
||||||
{write: 400, read: 400},
|
|
||||||
} {
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: "*/*", routeProduces: []string{"*/*"}, prettyPrint: true}
|
|
||||||
resp.WriteHeader(each.write)
|
|
||||||
if got, want := httpWriter.Code, each.read; got != want {
|
|
||||||
t.Errorf("got %v want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestStatusCreatedAndContentTypeJson_Issue54 ...restful
|
|
||||||
func TestStatusCreatedAndContentTypeJson_Issue54(t *testing.T) {
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: "application/json", routeProduces: []string{"application/json"}, prettyPrint: true}
|
|
||||||
resp.WriteHeader(201)
|
|
||||||
resp.WriteAsJson(food{"Juicy"})
|
|
||||||
if httpWriter.HeaderMap.Get("Content-Type") != "application/json" {
|
|
||||||
t.Errorf("Expected content type json but got:%s", httpWriter.HeaderMap.Get("Content-Type"))
|
|
||||||
}
|
|
||||||
if httpWriter.Code != 201 {
|
|
||||||
t.Errorf("Expected status 201 but got:%d", httpWriter.Code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type errorOnWriteRecorder struct {
|
|
||||||
*httptest.ResponseRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e errorOnWriteRecorder) Write(bytes []byte) (int, error) {
|
|
||||||
return 0, errors.New("fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestLastWriteErrorCaught ...restful
|
|
||||||
func TestLastWriteErrorCaught(t *testing.T) {
|
|
||||||
httpWriter := errorOnWriteRecorder{httptest.NewRecorder()}
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: "application/json", routeProduces: []string{"application/json"}, prettyPrint: true}
|
|
||||||
err := resp.WriteAsJson(food{"Juicy"})
|
|
||||||
if err.Error() != "fail" {
|
|
||||||
t.Errorf("Unexpected error message:%v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestAcceptStarStar_Issue83 ...restful
|
|
||||||
func TestAcceptStarStar_Issue83(t *testing.T) {
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
// Accept Produces
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: "application/bogus,*/*;q=0.8", routeProduces: []string{"application/json"}, prettyPrint: true}
|
|
||||||
resp.WriteEntity(food{"Juicy"})
|
|
||||||
ct := httpWriter.Header().Get("Content-Type")
|
|
||||||
if "application/json" != ct {
|
|
||||||
t.Errorf("Unexpected content type:%s", ct)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestAcceptSkipStarStar_Issue83 ...restful
|
|
||||||
func TestAcceptSkipStarStar_Issue83(t *testing.T) {
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
// Accept Produces
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: " application/xml ,*/* ; q=0.8", routeProduces: []string{"application/json", "application/xml"}, prettyPrint: true}
|
|
||||||
resp.WriteEntity(food{"Juicy"})
|
|
||||||
ct := httpWriter.Header().Get("Content-Type")
|
|
||||||
if "application/xml" != ct {
|
|
||||||
t.Errorf("Unexpected content type:%s", ct)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestAcceptXmlBeforeStarStar_Issue83 ...restful
|
|
||||||
func TestAcceptXmlBeforeStarStar_Issue83(t *testing.T) {
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
// Accept Produces
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", routeProduces: []string{"application/json"}, prettyPrint: true}
|
|
||||||
resp.WriteEntity(food{"Juicy"})
|
|
||||||
ct := httpWriter.Header().Get("Content-Type")
|
|
||||||
if "application/json" != ct {
|
|
||||||
t.Errorf("Unexpected content type:%s", ct)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestWriteHeaderNoContent_Issue124 ...restful
|
|
||||||
func TestWriteHeaderNoContent_Issue124(t *testing.T) {
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: "text/plain", routeProduces: []string{"text/plain"}, prettyPrint: true}
|
|
||||||
resp.WriteHeader(http.StatusNoContent)
|
|
||||||
if httpWriter.Code != http.StatusNoContent {
|
|
||||||
t.Errorf("got %d want %d", httpWriter.Code, http.StatusNoContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// go test -v -test.run TestStatusCreatedAndContentTypeJson_Issue163 ...restful
|
|
||||||
func TestStatusCreatedAndContentTypeJson_Issue163(t *testing.T) {
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: "application/json", routeProduces: []string{"application/json"}, prettyPrint: true}
|
|
||||||
resp.WriteHeader(http.StatusNotModified)
|
|
||||||
if httpWriter.Code != http.StatusNotModified {
|
|
||||||
t.Errorf("Got %d want %d", httpWriter.Code, http.StatusNotModified)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteHeaderAndEntity_Issue235(t *testing.T) {
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: "application/json", routeProduces: []string{"application/json"}, prettyPrint: true}
|
|
||||||
var pong = struct {
|
|
||||||
Foo string `json:"foo"`
|
|
||||||
}{Foo: "123"}
|
|
||||||
resp.WriteHeaderAndEntity(404, pong)
|
|
||||||
if httpWriter.Code != http.StatusNotFound {
|
|
||||||
t.Errorf("got %d want %d", httpWriter.Code, http.StatusNoContent)
|
|
||||||
}
|
|
||||||
if got, want := httpWriter.Header().Get("Content-Type"), "application/json"; got != want {
|
|
||||||
t.Errorf("got %v want %v", got, want)
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(httpWriter.Body.String(), "{") {
|
|
||||||
t.Errorf("expected pong struct in json:%s", httpWriter.Body.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteEntityNoAcceptMatchWithProduces(t *testing.T) {
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: "application/bogus", routeProduces: []string{"application/json"}, prettyPrint: true}
|
|
||||||
resp.WriteEntity("done")
|
|
||||||
if httpWriter.Code != http.StatusOK {
|
|
||||||
t.Errorf("got %d want %d", httpWriter.Code, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteEntityNoAcceptMatchNoProduces(t *testing.T) {
|
|
||||||
httpWriter := httptest.NewRecorder()
|
|
||||||
resp := Response{ResponseWriter: httpWriter, requestAccept: "application/bogus", routeProduces: []string{}, prettyPrint: true}
|
|
||||||
resp.WriteEntity("done")
|
|
||||||
if httpWriter.Code != http.StatusNotAcceptable {
|
|
||||||
t.Errorf("got %d want %d", httpWriter.Code, http.StatusNotAcceptable)
|
|
||||||
}
|
|
||||||
}
|
|
192
vendor/github.com/emicklei/go-restful/route.go
generated
vendored
192
vendor/github.com/emicklei/go-restful/route.go
generated
vendored
@ -1,192 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RouteFunction declares the signature of a function that can be bound to a Route.
|
|
||||||
type RouteFunction func(*Request, *Response)
|
|
||||||
|
|
||||||
// RouteSelectionConditionFunction declares the signature of a function that
|
|
||||||
// can be used to add extra conditional logic when selecting whether the route
|
|
||||||
// matches the HTTP request.
|
|
||||||
type RouteSelectionConditionFunction func(httpRequest *http.Request) bool
|
|
||||||
|
|
||||||
// Route binds a HTTP Method,Path,Consumes combination to a RouteFunction.
|
|
||||||
type Route struct {
|
|
||||||
Method string
|
|
||||||
Produces []string
|
|
||||||
Consumes []string
|
|
||||||
Path string // webservice root path + described path
|
|
||||||
Function RouteFunction
|
|
||||||
Filters []FilterFunction
|
|
||||||
If []RouteSelectionConditionFunction
|
|
||||||
|
|
||||||
// cached values for dispatching
|
|
||||||
relativePath string
|
|
||||||
pathParts []string
|
|
||||||
pathExpr *pathExpression // cached compilation of relativePath as RegExp
|
|
||||||
|
|
||||||
// documentation
|
|
||||||
Doc string
|
|
||||||
Notes string
|
|
||||||
Operation string
|
|
||||||
ParameterDocs []*Parameter
|
|
||||||
ResponseErrors map[int]ResponseError
|
|
||||||
ReadSample, WriteSample interface{} // structs that model an example request or response payload
|
|
||||||
|
|
||||||
// Extra information used to store custom information about the route.
|
|
||||||
Metadata map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize for Route
|
|
||||||
func (r *Route) postBuild() {
|
|
||||||
r.pathParts = tokenizePath(r.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Request and Response from their http versions
|
|
||||||
func (r *Route) wrapRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request) (*Request, *Response) {
|
|
||||||
params := r.extractParameters(httpRequest.URL.Path)
|
|
||||||
wrappedRequest := NewRequest(httpRequest)
|
|
||||||
wrappedRequest.pathParameters = params
|
|
||||||
wrappedRequest.selectedRoutePath = r.Path
|
|
||||||
wrappedResponse := NewResponse(httpWriter)
|
|
||||||
wrappedResponse.requestAccept = httpRequest.Header.Get(HEADER_Accept)
|
|
||||||
wrappedResponse.routeProduces = r.Produces
|
|
||||||
return wrappedRequest, wrappedResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
// dispatchWithFilters call the function after passing through its own filters
|
|
||||||
func (r *Route) dispatchWithFilters(wrappedRequest *Request, wrappedResponse *Response) {
|
|
||||||
if len(r.Filters) > 0 {
|
|
||||||
chain := FilterChain{Filters: r.Filters, Target: r.Function}
|
|
||||||
chain.ProcessFilter(wrappedRequest, wrappedResponse)
|
|
||||||
} else {
|
|
||||||
// unfiltered
|
|
||||||
r.Function(wrappedRequest, wrappedResponse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return whether the mimeType matches to what this Route can produce.
|
|
||||||
func (r Route) matchesAccept(mimeTypesWithQuality string) bool {
|
|
||||||
parts := strings.Split(mimeTypesWithQuality, ",")
|
|
||||||
for _, each := range parts {
|
|
||||||
var withoutQuality string
|
|
||||||
if strings.Contains(each, ";") {
|
|
||||||
withoutQuality = strings.Split(each, ";")[0]
|
|
||||||
} else {
|
|
||||||
withoutQuality = each
|
|
||||||
}
|
|
||||||
// trim before compare
|
|
||||||
withoutQuality = strings.Trim(withoutQuality, " ")
|
|
||||||
if withoutQuality == "*/*" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, producibleType := range r.Produces {
|
|
||||||
if producibleType == "*/*" || producibleType == withoutQuality {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return whether this Route can consume content with a type specified by mimeTypes (can be empty).
|
|
||||||
func (r Route) matchesContentType(mimeTypes string) bool {
|
|
||||||
|
|
||||||
if len(r.Consumes) == 0 {
|
|
||||||
// did not specify what it can consume ; any media type (“*/*”) is assumed
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(mimeTypes) == 0 {
|
|
||||||
// idempotent methods with (most-likely or guaranteed) empty content match missing Content-Type
|
|
||||||
m := r.Method
|
|
||||||
if m == "GET" || m == "HEAD" || m == "OPTIONS" || m == "DELETE" || m == "TRACE" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// proceed with default
|
|
||||||
mimeTypes = MIME_OCTET
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.Split(mimeTypes, ",")
|
|
||||||
for _, each := range parts {
|
|
||||||
var contentType string
|
|
||||||
if strings.Contains(each, ";") {
|
|
||||||
contentType = strings.Split(each, ";")[0]
|
|
||||||
} else {
|
|
||||||
contentType = each
|
|
||||||
}
|
|
||||||
// trim before compare
|
|
||||||
contentType = strings.Trim(contentType, " ")
|
|
||||||
for _, consumeableType := range r.Consumes {
|
|
||||||
if consumeableType == "*/*" || consumeableType == contentType {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the parameters from the request url path
|
|
||||||
func (r Route) extractParameters(urlPath string) map[string]string {
|
|
||||||
urlParts := tokenizePath(urlPath)
|
|
||||||
pathParameters := map[string]string{}
|
|
||||||
for i, key := range r.pathParts {
|
|
||||||
var value string
|
|
||||||
if i >= len(urlParts) {
|
|
||||||
value = ""
|
|
||||||
} else {
|
|
||||||
value = urlParts[i]
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(key, "{") { // path-parameter
|
|
||||||
if colon := strings.Index(key, ":"); colon != -1 {
|
|
||||||
// extract by regex
|
|
||||||
regPart := key[colon+1 : len(key)-1]
|
|
||||||
keyPart := key[1:colon]
|
|
||||||
if regPart == "*" {
|
|
||||||
pathParameters[keyPart] = untokenizePath(i, urlParts)
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
pathParameters[keyPart] = value
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// without enclosing {}
|
|
||||||
pathParameters[key[1:len(key)-1]] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pathParameters
|
|
||||||
}
|
|
||||||
|
|
||||||
// Untokenize back into an URL path using the slash separator
|
|
||||||
func untokenizePath(offset int, parts []string) string {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
for p := offset; p < len(parts); p++ {
|
|
||||||
buffer.WriteString(parts[p])
|
|
||||||
// do not end
|
|
||||||
if p < len(parts)-1 {
|
|
||||||
buffer.WriteString("/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buffer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tokenize an URL path using the slash separator ; the result does not have empty tokens
|
|
||||||
func tokenizePath(path string) []string {
|
|
||||||
if "/" == path {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
return strings.Split(strings.Trim(path, "/"), "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
// for debugging
|
|
||||||
func (r Route) String() string {
|
|
||||||
return r.Method + " " + r.Path
|
|
||||||
}
|
|
310
vendor/github.com/emicklei/go-restful/route_builder.go
generated
vendored
310
vendor/github.com/emicklei/go-restful/route_builder.go
generated
vendored
@ -1,310 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
|
||||||
// Use of this source code is governed by a license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RouteBuilder is a helper to construct Routes.
|
|
||||||
type RouteBuilder struct {
|
|
||||||
rootPath string
|
|
||||||
currentPath string
|
|
||||||
produces []string
|
|
||||||
consumes []string
|
|
||||||
httpMethod string // required
|
|
||||||
function RouteFunction // required
|
|
||||||
filters []FilterFunction
|
|
||||||
conditions []RouteSelectionConditionFunction
|
|
||||||
|
|
||||||
typeNameHandleFunc TypeNameHandleFunction // required
|
|
||||||
|
|
||||||
// documentation
|
|
||||||
doc string
|
|
||||||
notes string
|
|
||||||
operation string
|
|
||||||
readSample, writeSample interface{}
|
|
||||||
parameters []*Parameter
|
|
||||||
errorMap map[int]ResponseError
|
|
||||||
metadata map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do evaluates each argument with the RouteBuilder itself.
|
|
||||||
// This allows you to follow DRY principles without breaking the fluent programming style.
|
|
||||||
// Example:
|
|
||||||
// ws.Route(ws.DELETE("/{name}").To(t.deletePerson).Do(Returns200, Returns500))
|
|
||||||
//
|
|
||||||
// func Returns500(b *RouteBuilder) {
|
|
||||||
// b.Returns(500, "Internal Server Error", restful.ServiceError{})
|
|
||||||
// }
|
|
||||||
func (b *RouteBuilder) Do(oneArgBlocks ...func(*RouteBuilder)) *RouteBuilder {
|
|
||||||
for _, each := range oneArgBlocks {
|
|
||||||
each(b)
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// To bind the route to a function.
|
|
||||||
// If this route is matched with the incoming Http Request then call this function with the *Request,*Response pair. Required.
|
|
||||||
func (b *RouteBuilder) To(function RouteFunction) *RouteBuilder {
|
|
||||||
b.function = function
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method specifies what HTTP method to match. Required.
|
|
||||||
func (b *RouteBuilder) Method(method string) *RouteBuilder {
|
|
||||||
b.httpMethod = method
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Produces specifies what MIME types can be produced ; the matched one will appear in the Content-Type Http header.
|
|
||||||
func (b *RouteBuilder) Produces(mimeTypes ...string) *RouteBuilder {
|
|
||||||
b.produces = mimeTypes
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consumes specifies what MIME types can be consumes ; the Accept Http header must matched any of these
|
|
||||||
func (b *RouteBuilder) Consumes(mimeTypes ...string) *RouteBuilder {
|
|
||||||
b.consumes = mimeTypes
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path specifies the relative (w.r.t WebService root path) URL path to match. Default is "/".
|
|
||||||
func (b *RouteBuilder) Path(subPath string) *RouteBuilder {
|
|
||||||
b.currentPath = subPath
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Doc tells what this route is all about. Optional.
|
|
||||||
func (b *RouteBuilder) Doc(documentation string) *RouteBuilder {
|
|
||||||
b.doc = documentation
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notes is a verbose explanation of the operation behavior. Optional.
|
|
||||||
func (b *RouteBuilder) Notes(notes string) *RouteBuilder {
|
|
||||||
b.notes = notes
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reads tells what resource type will be read from the request payload. Optional.
|
|
||||||
// A parameter of type "body" is added ,required is set to true and the dataType is set to the qualified name of the sample's type.
|
|
||||||
func (b *RouteBuilder) Reads(sample interface{}) *RouteBuilder {
|
|
||||||
fn := b.typeNameHandleFunc
|
|
||||||
if fn == nil {
|
|
||||||
fn = reflectTypeName
|
|
||||||
}
|
|
||||||
typeAsName := fn(sample)
|
|
||||||
|
|
||||||
b.readSample = sample
|
|
||||||
bodyParameter := &Parameter{&ParameterData{Name: "body"}}
|
|
||||||
bodyParameter.beBody()
|
|
||||||
bodyParameter.Required(true)
|
|
||||||
bodyParameter.DataType(typeAsName)
|
|
||||||
b.Param(bodyParameter)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParameterNamed returns a Parameter already known to the RouteBuilder. Returns nil if not.
|
|
||||||
// Use this to modify or extend information for the Parameter (through its Data()).
|
|
||||||
func (b RouteBuilder) ParameterNamed(name string) (p *Parameter) {
|
|
||||||
for _, each := range b.parameters {
|
|
||||||
if each.Data().Name == name {
|
|
||||||
return each
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writes tells what resource type will be written as the response payload. Optional.
|
|
||||||
func (b *RouteBuilder) Writes(sample interface{}) *RouteBuilder {
|
|
||||||
b.writeSample = sample
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Param allows you to document the parameters of the Route. It adds a new Parameter (does not check for duplicates).
|
|
||||||
func (b *RouteBuilder) Param(parameter *Parameter) *RouteBuilder {
|
|
||||||
if b.parameters == nil {
|
|
||||||
b.parameters = []*Parameter{}
|
|
||||||
}
|
|
||||||
b.parameters = append(b.parameters, parameter)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Operation allows you to document what the actual method/function call is of the Route.
|
|
||||||
// Unless called, the operation name is derived from the RouteFunction set using To(..).
|
|
||||||
func (b *RouteBuilder) Operation(name string) *RouteBuilder {
|
|
||||||
b.operation = name
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReturnsError is deprecated, use Returns instead.
|
|
||||||
func (b *RouteBuilder) ReturnsError(code int, message string, model interface{}) *RouteBuilder {
|
|
||||||
log.Print("ReturnsError is deprecated, use Returns instead.")
|
|
||||||
return b.Returns(code, message, model)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns allows you to document what responses (errors or regular) can be expected.
|
|
||||||
// The model parameter is optional ; either pass a struct instance or use nil if not applicable.
|
|
||||||
func (b *RouteBuilder) Returns(code int, message string, model interface{}) *RouteBuilder {
|
|
||||||
err := ResponseError{
|
|
||||||
Code: code,
|
|
||||||
Message: message,
|
|
||||||
Model: model,
|
|
||||||
IsDefault: false,
|
|
||||||
}
|
|
||||||
// lazy init because there is no NewRouteBuilder (yet)
|
|
||||||
if b.errorMap == nil {
|
|
||||||
b.errorMap = map[int]ResponseError{}
|
|
||||||
}
|
|
||||||
b.errorMap[code] = err
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultReturns is a special Returns call that sets the default of the response ; the code is zero.
|
|
||||||
func (b *RouteBuilder) DefaultReturns(message string, model interface{}) *RouteBuilder {
|
|
||||||
b.Returns(0, message, model)
|
|
||||||
// Modify the ResponseError just added/updated
|
|
||||||
re := b.errorMap[0]
|
|
||||||
// errorMap is initialized
|
|
||||||
b.errorMap[0] = ResponseError{
|
|
||||||
Code: re.Code,
|
|
||||||
Message: re.Message,
|
|
||||||
Model: re.Model,
|
|
||||||
IsDefault: true,
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata adds or updates a key=value pair to the metadata map.
|
|
||||||
func (b *RouteBuilder) Metadata(key string, value interface{}) *RouteBuilder {
|
|
||||||
if b.metadata == nil {
|
|
||||||
b.metadata = map[string]interface{}{}
|
|
||||||
}
|
|
||||||
b.metadata[key] = value
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResponseError represents a response; not necessarily an error.
|
|
||||||
type ResponseError struct {
|
|
||||||
Code int
|
|
||||||
Message string
|
|
||||||
Model interface{}
|
|
||||||
IsDefault bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *RouteBuilder) servicePath(path string) *RouteBuilder {
|
|
||||||
b.rootPath = path
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter appends a FilterFunction to the end of filters for this Route to build.
|
|
||||||
func (b *RouteBuilder) Filter(filter FilterFunction) *RouteBuilder {
|
|
||||||
b.filters = append(b.filters, filter)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// If sets a condition function that controls matching the Route based on custom logic.
|
|
||||||
// The condition function is provided the HTTP request and should return true if the route
|
|
||||||
// should be considered.
|
|
||||||
//
|
|
||||||
// Efficiency note: the condition function is called before checking the method, produces, and
|
|
||||||
// consumes criteria, so that the correct HTTP status code can be returned.
|
|
||||||
//
|
|
||||||
// Lifecycle note: no filter functions have been called prior to calling the condition function,
|
|
||||||
// so the condition function should not depend on any context that might be set up by container
|
|
||||||
// or route filters.
|
|
||||||
func (b *RouteBuilder) If(condition RouteSelectionConditionFunction) *RouteBuilder {
|
|
||||||
b.conditions = append(b.conditions, condition)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no specific Route path then set to rootPath
|
|
||||||
// If no specific Produces then set to rootProduces
|
|
||||||
// If no specific Consumes then set to rootConsumes
|
|
||||||
func (b *RouteBuilder) copyDefaults(rootProduces, rootConsumes []string) {
|
|
||||||
if len(b.produces) == 0 {
|
|
||||||
b.produces = rootProduces
|
|
||||||
}
|
|
||||||
if len(b.consumes) == 0 {
|
|
||||||
b.consumes = rootConsumes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// typeNameHandler sets the function that will convert types to strings in the parameter
|
|
||||||
// and model definitions.
|
|
||||||
func (b *RouteBuilder) typeNameHandler(handler TypeNameHandleFunction) *RouteBuilder {
|
|
||||||
b.typeNameHandleFunc = handler
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build creates a new Route using the specification details collected by the RouteBuilder
|
|
||||||
func (b *RouteBuilder) Build() Route {
|
|
||||||
pathExpr, err := newPathExpression(b.currentPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[restful] Invalid path:%s because:%v", b.currentPath, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if b.function == nil {
|
|
||||||
log.Printf("[restful] No function specified for route:" + b.currentPath)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
operationName := b.operation
|
|
||||||
if len(operationName) == 0 && b.function != nil {
|
|
||||||
// extract from definition
|
|
||||||
operationName = nameOfFunction(b.function)
|
|
||||||
}
|
|
||||||
route := Route{
|
|
||||||
Method: b.httpMethod,
|
|
||||||
Path: concatPath(b.rootPath, b.currentPath),
|
|
||||||
Produces: b.produces,
|
|
||||||
Consumes: b.consumes,
|
|
||||||
Function: b.function,
|
|
||||||
Filters: b.filters,
|
|
||||||
If: b.conditions,
|
|
||||||
relativePath: b.currentPath,
|
|
||||||
pathExpr: pathExpr,
|
|
||||||
Doc: b.doc,
|
|
||||||
Notes: b.notes,
|
|
||||||
Operation: operationName,
|
|
||||||
ParameterDocs: b.parameters,
|
|
||||||
ResponseErrors: b.errorMap,
|
|
||||||
ReadSample: b.readSample,
|
|
||||||
WriteSample: b.writeSample,
|
|
||||||
Metadata: b.metadata}
|
|
||||||
route.postBuild()
|
|
||||||
return route
|
|
||||||
}
|
|
||||||
|
|
||||||
func concatPath(path1, path2 string) string {
|
|
||||||
return strings.TrimRight(path1, "/") + "/" + strings.TrimLeft(path2, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
var anonymousFuncCount int32
|
|
||||||
|
|
||||||
// nameOfFunction returns the short name of the function f for documentation.
|
|
||||||
// It uses a runtime feature for debugging ; its value may change for later Go versions.
|
|
||||||
func nameOfFunction(f interface{}) string {
|
|
||||||
fun := runtime.FuncForPC(reflect.ValueOf(f).Pointer())
|
|
||||||
tokenized := strings.Split(fun.Name(), ".")
|
|
||||||
last := tokenized[len(tokenized)-1]
|
|
||||||
last = strings.TrimSuffix(last, ")·fm") // < Go 1.5
|
|
||||||
last = strings.TrimSuffix(last, ")-fm") // Go 1.5
|
|
||||||
last = strings.TrimSuffix(last, "·fm") // < Go 1.5
|
|
||||||
last = strings.TrimSuffix(last, "-fm") // Go 1.5
|
|
||||||
if last == "func1" { // this could mean conflicts in API docs
|
|
||||||
val := atomic.AddInt32(&anonymousFuncCount, 1)
|
|
||||||
last = "func" + fmt.Sprintf("%d", val)
|
|
||||||
atomic.StoreInt32(&anonymousFuncCount, val)
|
|
||||||
}
|
|
||||||
return last
|
|
||||||
}
|
|
76
vendor/github.com/emicklei/go-restful/route_builder_test.go
generated
vendored
76
vendor/github.com/emicklei/go-restful/route_builder_test.go
generated
vendored
@ -1,76 +0,0 @@
|
|||||||
package restful
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRouteBuilder_PathParameter(t *testing.T) {
|
|
||||||
p := &Parameter{&ParameterData{Name: "name", Description: "desc"}}
|
|
||||||
p.AllowMultiple(true)
|
|
||||||
p.DataType("int")
|
|
||||||
p.Required(true)
|
|
||||||
values := map[string]string{"a": "b"}
|
|
||||||
p.AllowableValues(values)
|
|
||||||
p.bePath()
|
|
||||||
|
|
||||||
b := new(RouteBuilder)
|
|
||||||
b.function = dummy
|
|
||||||
b.Param(p)
|
|
||||||
r := b.Build()
|
|
||||||
if !r.ParameterDocs[0].Data().AllowMultiple {
|
|
||||||
t.Error("AllowMultiple invalid")
|
|
||||||
}
|
|
||||||
if r.ParameterDocs[0].Data().DataType != "int" {
|
|
||||||
t.Error("dataType invalid")
|
|
||||||
}
|
|
||||||
if !r.ParameterDocs[0].Data().Required {
|
|
||||||
t.Error("required invalid")
|
|
||||||
}
|
|
||||||
if r.ParameterDocs[0].Data().Kind != PathParameterKind {
|
|
||||||
t.Error("kind invalid")
|
|
||||||
}
|
|
||||||
if r.ParameterDocs[0].Data().AllowableValues["a"] != "b" {
|
|
||||||
t.Error("allowableValues invalid")
|
|
||||||
}
|
|
||||||
if b.ParameterNamed("name") == nil {
|
|
||||||
t.Error("access to parameter failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRouteBuilder(t *testing.T) {
|
|
||||||
json := "application/json"
|
|
||||||
b := new(RouteBuilder)
|
|
||||||
b.To(dummy)
|
|
||||||
b.Path("/routes").Method("HEAD").Consumes(json).Produces(json).Metadata("test", "test-value").DefaultReturns("default", time.Now())
|
|
||||||
r := b.Build()
|
|
||||||
if r.Path != "/routes" {
|
|
||||||
t.Error("path invalid")
|
|
||||||
}
|
|
||||||
if r.Produces[0] != json {
|
|
||||||
t.Error("produces invalid")
|
|
||||||
}
|
|
||||||
if r.Consumes[0] != json {
|
|
||||||
t.Error("consumes invalid")
|
|
||||||
}
|
|
||||||
if r.Operation != "dummy" {
|
|
||||||
t.Error("Operation not set")
|
|
||||||
}
|
|
||||||
if r.Metadata["test"] != "test-value" {
|
|
||||||
t.Errorf("Metadata not set")
|
|
||||||
}
|
|
||||||
if _, ok := r.ResponseErrors[0]; !ok {
|
|
||||||
t.Fatal("expected default response")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAnonymousFuncNaming(t *testing.T) {
|
|
||||||
f1 := func() {}
|
|
||||||
f2 := func() {}
|
|
||||||
if got, want := nameOfFunction(f1), "func1"; got != want {
|
|
||||||
t.Errorf("got %v want %v", got, want)
|
|
||||||
}
|
|
||||||
if got, want := nameOfFunction(f2), "func2"; got != want {
|
|
||||||
t.Errorf("got %v want %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user