pax_global_header00006660000000000000000000000064141531421470014514gustar00rootroot0000000000000052 comment=8e3e76222e5ae9559f578fe2d1e06e4812504766 go-restful-3.7.3/000077500000000000000000000000001415314214700136155ustar00rootroot00000000000000go-restful-3.7.3/.gitignore000066400000000000000000000016201415314214700156040ustar00rootroot00000000000000# 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 .idea go-restful-3.7.3/.goconvey000066400000000000000000000000061415314214700154430ustar00rootroot00000000000000ignorego-restful-3.7.3/.travis.yml000066400000000000000000000002751415314214700157320ustar00rootroot00000000000000language: go go: - 1.x before_install: - go test -v script: - go test -race -coverprofile=coverage.txt -covermode=atomic after_success: - bash <(curl -s https://codecov.io/bash)go-restful-3.7.3/CHANGES.md000066400000000000000000000220661415314214700152150ustar00rootroot00000000000000# Change history of go-restful ## [v3.7.2] - 2021-11-24 - restored FilterChain (#482 by SVilgelm) ## [v3.7.1] - 2021-10-04 - fix problem with contentEncodingEnabled setting (#479) ## [v3.7.0] - 2021-09-24 - feat(parameter): adds additional openapi mappings (#478) ## [v3.6.0] - 2021-09-18 - add support for vendor extensions (#477 thx erraggy) ## [v3.5.2] - 2021-07-14 - fix removing absent route from webservice (#472) ## [v3.5.1] - 2021-04-12 - fix handling no match access selected path - remove obsolete field ## [v3.5.0] - 2021-04-10 - add check for wildcard (#463) in CORS - add access to Route from Request, issue #459 (#462) ## [v3.4.0] - 2020-11-10 - Added OPTIONS to WebService ## [v3.3.2] - 2020-01-23 - Fixed duplicate compression in dispatch. #449 ## [v3.3.1] - 2020-08-31 - Added check on writer to prevent compression of response twice. #447 ## [v3.3.0] - 2020-08-19 - Enable content encoding on Handle and ServeHTTP (#446) - List available representations in 406 body (#437) - Convert to string using rune() (#443) ## [v3.2.0] - 2020-06-21 - 405 Method Not Allowed must have Allow header (#436) (thx Bracken ) - add field allowedMethodsWithoutContentType (#424) ## [v3.1.0] - support describing response headers (#426) - fix openapi examples (#425) v3.0.0 - fix: use request/response resulting from filter chain - add Go module Module consumer should use github.com/emicklei/go-restful/v3 as import path v2.10.0 - support for Custom Verbs (thanks Vinci Xu <277040271@qq.com>) - fixed static example (thanks Arthur ) - simplify code (thanks Christian Muehlhaeuser ) - added JWT HMAC with SHA-512 authentication code example (thanks Amim Knabben ) v2.9.6 - small optimization in filter code v2.11.1 - fix WriteError return value (#415) v2.11.0 - allow prefix and suffix in path variable expression (#414) v2.9.6 - support google custome verb (#413) v2.9.5 - fix panic in Response.WriteError if err == nil v2.9.4 - fix issue #400 , parsing mime type quality - Route Builder added option for contentEncodingEnabled (#398) v2.9.3 - Avoid return of 415 Unsupported Media Type when request body is empty (#396) v2.9.2 - Reduce allocations in per-request methods to improve performance (#395) v2.9.1 - Fix issue with default responses and invalid status code 0. (#393) v2.9.0 - add per Route content encoding setting (overrides container setting) v2.8.0 - add Request.QueryParameters() - add json-iterator (via build tag) - disable vgo module (until log is moved) v2.7.1 - add vgo module v2.6.1 - add JSONNewDecoderFunc to allow custom JSON Decoder usage (go 1.10+) v2.6.0 - Make JSR 311 routing and path param processing consistent - Adding description to RouteBuilder.Reads() - Update example for Swagger12 and OpenAPI 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 go-restful-3.7.3/LICENSE000066400000000000000000000020631415314214700146230ustar00rootroot00000000000000Copyright (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.go-restful-3.7.3/Makefile000066400000000000000000000002031415314214700152500ustar00rootroot00000000000000all: test test: go vet . go test -cover -v . ex: find ./examples -type f -name "*.go" | xargs -I {} go build -o /tmp/ignore {}go-restful-3.7.3/README.md000066400000000000000000000117261415314214700151030ustar00rootroot00000000000000go-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://pkg.go.dev/github.com/emicklei/go-restful) [![codecov](https://codecov.io/gh/emicklei/go-restful/branch/master/graph/badge.svg)](https://codecov.io/gh/emicklei/go-restful) - [Code examples use v3](https://github.com/emicklei/go-restful/tree/v3/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 ### Usage #### Without Go Modules All versions up to `v2.*.*` (on the master) are not supporting Go modules. ``` import ( restful "github.com/emicklei/go-restful" ) ``` #### Using Go Modules As of version `v3.0.0` (on the v3 branch), this package supports Go modules. ``` import ( restful "github.com/emicklei/go-restful/v3" ) ``` ### 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/blob/v3/examples/user-resource/restful-user-resource.go) ### Features - Routes for request → function mapping with path parameter (e.g. {id} but also prefix_{var} and {var}_suffix) support - Configurable router: - (default) Fast routing algorithm that allows static elements, [google custom method](https://cloud.google.com/apis/design/custom_methods), regular expressions and dynamic parameters in the URL path (e.g. /resource/name:customVerb, /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 accessing 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 ## How to customize There are several hooks to customize the behavior of the go-restful package. - Router algorithm - Panic recovery - JSON decoder - Trace logging - Compression - Encoders for other serializers - Use [jsoniter](https://github.com/json-iterator/go) by build this package using a tag, e.g. `go build -tags=jsoniter .` TODO: write examples of these. ## 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 - 2021, http://ernestmicklei.com. MIT License. Contributions are welcome. go-restful-3.7.3/Srcfile000066400000000000000000000000331415314214700151230ustar00rootroot00000000000000{"SkipDirs": ["examples"]} go-restful-3.7.3/bench_curly_test.go000066400000000000000000000024171415314214700175040ustar00rootroot00000000000000package 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(rune(i+97)), string(rune(i+97))) ws := new(WebService).Path(root) for j := 0; j < rtCount; j++ { sub := fmt.Sprintf("/%s2/{%s2}", string(rune(j+97)), string(rune(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) } go-restful-3.7.3/bench_test.go000066400000000000000000000015271415314214700162670ustar00rootroot00000000000000package 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(rune(i+97)), string(rune(i+97))) ws := new(WebService).Path(root) for j := 0; j < rtCount; j++ { sub := fmt.Sprintf("/%s2/{%s2}", string(rune(j+97)), string(rune(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) } } } go-restful-3.7.3/bench_test.sh000066400000000000000000000005411415314214700162670ustar00rootroot00000000000000#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 go-restful-3.7.3/compress.go000066400000000000000000000072771415314214700160140ustar00rootroot00000000000000package 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 } go-restful-3.7.3/compress_test.go000066400000000000000000000060431415314214700170410ustar00rootroot00000000000000package 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) } } go-restful-3.7.3/compressor_cache.go000066400000000000000000000061751415314214700174740ustar00rootroot00000000000000package 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 } } go-restful-3.7.3/compressor_pools.go000066400000000000000000000045401415314214700175570ustar00rootroot00000000000000package 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 } go-restful-3.7.3/compressors.go000066400000000000000000000030451415314214700165250ustar00rootroot00000000000000package 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 } go-restful-3.7.3/constants.go000066400000000000000000000027221415314214700161630ustar00rootroot00000000000000package 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" ) go-restful-3.7.3/container.go000066400000000000000000000361661415314214700161420ustar00rootroot00000000000000package 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/v3/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("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("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("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) { for header, values := range err.Header { for _, value := range values { resp.Header().Add(header, value) } } 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) { // so we can assign a compressing one later 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 } }() } // 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 (may be compressed) 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 } // Unless httpWriter is already an CompressingResponseWriter see if we need to install one if _, isCompressing := httpWriter.(*CompressingResponseWriter); !isCompressing { // Detect if compression is needed // assume without compression, test for override contentEncodingEnabled := c.contentEncodingEnabled if route != nil && route.contentEncodingEnabled != nil { contentEncodingEnabled = *route.contentEncodingEnabled } if contentEncodingEnabled { doCompress, encoding := wantsCompressedResponse(httpRequest) if doCompress { var err error writer, err = NewCompressingResponseWriter(httpWriter, encoding) if err != nil { log.Print("unable to install compressor: ", err) httpWriter.WriteHeader(http.StatusInternalServerError) return } } } } pathProcessor, routerProcessesPath := c.router.(PathProcessor) if !routerProcessesPath { pathProcessor = defaultPathProcessor{} } pathParams := pathProcessor.ExtractParameters(route, webService, httpRequest.URL.Path) wrappedRequest, wrappedResponse := route.wrapRequestResponse(writer, httpRequest, pathParams) // pass through filters (if any) if size := len(c.containerFilters) + len(webService.filters) + len(route.Filters); size > 0 { // compose filter chain allFilters := make([]FilterFunction, 0, size) allFilters = append(allFilters, c.containerFilters...) allFilters = append(allFilters, webService.filters...) allFilters = append(allFilters, route.Filters...) chain := FilterChain{ Filters: allFilters, Target: route.Function, ParameterDocs: route.ParameterDocs, Operation: route.Operation, } 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) { // Skip, if content encoding is disabled if !c.contentEncodingEnabled { c.ServeMux.ServeHTTP(httpWriter, httpRequest) return } // content encoding is enabled // Skip, if httpWriter is already an CompressingResponseWriter if _, ok := httpWriter.(*CompressingResponseWriter); ok { c.ServeMux.ServeHTTP(httpWriter, httpRequest) return } writer := httpWriter // CompressingResponseWriter should be closed after all operations are done defer func() { if compressWriter, ok := writer.(*CompressingResponseWriter); ok { compressWriter.Close() } }() doCompress, encoding := wantsCompressedResponse(httpRequest) if doCompress { var err error writer, err = NewCompressingResponseWriter(httpWriter, encoding) if err != nil { log.Print("unable to install compressor: ", err) httpWriter.WriteHeader(http.StatusInternalServerError) return } } c.ServeMux.ServeHTTP(writer, 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, http.HandlerFunc(func(httpWriter http.ResponseWriter, httpRequest *http.Request) { // Skip, if httpWriter is already an CompressingResponseWriter if _, ok := httpWriter.(*CompressingResponseWriter); ok { handler.ServeHTTP(httpWriter, httpRequest) return } writer := httpWriter // CompressingResponseWriter should be closed after all operations are done defer func() { if compressWriter, ok := writer.(*CompressingResponseWriter); ok { compressWriter.Close() } }() if c.contentEncodingEnabled { doCompress, encoding := wantsCompressedResponse(httpRequest) if doCompress { var err error writer, err = NewCompressingResponseWriter(httpWriter, encoding) if err != nil { log.Print("unable to install compressor: ", err) httpWriter.WriteHeader(http.StatusInternalServerError) return } } } handler.ServeHTTP(writer, httpRequest) })) } // 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(resp, req.Request) }} 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 } go-restful-3.7.3/container_test.go000066400000000000000000000110661415314214700171710ustar00rootroot00000000000000package restful import ( "context" "encoding/hex" "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 contextAvailable := false wc := NewContainer() wc.Filter(func(request *Request, response *Response, chain *FilterChain) { prefilterCalled = true request.Request = request.Request.WithContext(context.WithValue(request.Request.Context(), "prefilterContextSet", "true")) chain.ProcessFilter(request, response) }) wc.HandleWithFilter("/", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { httpHandlerCalled = true _, ok1 := req.Context().Value("prefilterContextSet").(string) _, ok2 := req.Context().Value("postfilterContextSet").(string) if ok1 && ok2 { contextAvailable = true } w.Write([]byte("ok")) })) wc.Filter(func(request *Request, response *Response, chain *FilterChain) { postfilterCalled = true request.Request = request.Request.WithContext(context.WithValue(request.Request.Context(), "postfilterContextSet", "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") } if !contextAvailable { t.Errorf("Context not available in http handler") } } 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") } } func TestContainerCompressResponse(t *testing.T) { wc := NewContainer() ws := new(WebService).Path("/") ws.Route(ws.GET("/").To(dummy)) wc.Add(ws) // no accept header, encoding disabled { 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() != "dummy" { t.Errorf("unexpected body %s", recorder.Body.String()) } } // with gzip accept header, encoding disabled { recorder := httptest.NewRecorder() request, _ := http.NewRequest("GET", "/", nil) request.Header.Set("accept-encoding", "gzip") wc.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("unexpected code %d", recorder.Code) } if recorder.Body.String() != "dummy" { t.Errorf("unexpected body %s", recorder.Body.String()) } } // no accept header, encoding enabled { wc.EnableContentEncoding(true) 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() != "dummy" { t.Errorf("unexpected body %s", recorder.Body.String()) } } // with accept gzip header, encoding enabled { wc.EnableContentEncoding(true) recorder := httptest.NewRecorder() request, _ := http.NewRequest("GET", "/", nil) request.Header.Set("accept-encoding", "gzip") wc.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("unexpected code %d", recorder.Code) } if hex.EncodeToString(recorder.Body.Bytes()) == hex.EncodeToString(gzippedDummy()) { t.Errorf("unexpected body %v", recorder.Body.Bytes()) } } } go-restful-3.7.3/cors_filter.go000066400000000000000000000137061415314214700164660ustar00rootroot00000000000000package 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 } if each == "*" { 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 } go-restful-3.7.3/cors_filter_test.go000066400000000000000000000101201415314214700175100ustar00rootroot00000000000000package 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") } } } go-restful-3.7.3/coverage.sh000066400000000000000000000001031415314214700157360ustar00rootroot00000000000000go test -coverprofile=coverage.out go tool cover -html=coverage.outgo-restful-3.7.3/curly.go000066400000000000000000000133731415314214700153110ustar00rootroot00000000000000package 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 := make(sortableCurlyRoutes, 0, 8) for _, each := range ws.routes { matches, paramCount, staticCount := c.matchesRouteByPathTokens(each.pathParts, requestTokens, each.hasCustomVerb) if matches { candidates.add(curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers? } } sort.Sort(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, routeHasCustomVerb bool) (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 routeHasCustomVerb && hasCustomVerb(routeToken){ if !isMatchCustomVerb(routeToken, requestToken) { return false, 0, 0 } staticCount++ requestToken = removeCustomVerb(requestToken) routeToken = removeCustomVerb(routeToken) } 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 } go-restful-3.7.3/curly_route.go000066400000000000000000000023511415314214700165210ustar00rootroot00000000000000package 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 } // sortableCurlyRoutes orders by most parameters and path elements first. type sortableCurlyRoutes []curlyRoute func (s *sortableCurlyRoutes) add(route curlyRoute) { *s = append(*s, route) } func (s sortableCurlyRoutes) routes() (routes []Route) { routes = make([]Route, 0, len(s)) 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 { a := s[j] b := s[i] // primary key if a.staticCount < b.staticCount { return true } if a.staticCount > b.staticCount { return false } // secundary key if a.paramCount < b.paramCount { return true } if a.paramCount > b.paramCount { return false } return a.route.Path < b.route.Path } go-restful-3.7.3/curly_test.go000066400000000000000000000171361415314214700163510ustar00rootroot00000000000000package 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 hasCustomVerb bool }{ // route, request-path {"/a", "/a", true, 0, 1, false}, {"/a", "/b", false, 0, 0, false}, {"/a", "/b", false, 0, 0, false}, {"/a/{b}/c/", "/a/2/c", true, 1, 2, false}, {"/{a}/{b}/{c}/", "/a/b", false, 0, 0, false}, {"/{x:*}", "/", false, 0, 0, false}, {"/{x:*}", "/a", true, 1, 0, false}, {"/{x:*}", "/a/b", true, 1, 0, false}, {"/a/{x:*}", "/a/b", true, 1, 1, false}, {"/a/{x:[A-Z][A-Z]}", "/a/ZX", true, 1, 1, false}, {"/basepath/{resource:*}", "/basepath/some/other/location/test.xml", true, 1, 1, false}, {"/resources:run", "/resources:run", true, 0, 2, true}, {"/resources:run", "/user:run", false, 0, 0, true}, {"/resources:run", "/resources", false, 0, 0, true}, {"/users/{userId:^prefix-}:start", "/users/prefix-}:startUserId", false, 0, 0, true}, {"/users/{userId:^prefix-}:start", "/users/prefix-userId:start", true, 1, 2, true}, } // 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, each.hasCustomVerb) 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"]) } } func TestExtractParameters_Wildcard4(t *testing.T) { params := doExtractParams("/static/{var:*}/sub", 3, "/static/test/sub", t) if params["var"] != "test/sub" { 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") } go-restful-3.7.3/custom_verb.go000066400000000000000000000011221415314214700164700ustar00rootroot00000000000000package restful import ( "fmt" "regexp" ) var ( customVerbReg = regexp.MustCompile(":([A-Za-z]+)$") ) func hasCustomVerb(routeToken string) bool { return customVerbReg.MatchString(routeToken) } func isMatchCustomVerb(routeToken string, pathToken string) bool { rs := customVerbReg.FindStringSubmatch(routeToken) if len(rs) < 2 { return false } customVerb := rs[1] specificVerbReg := regexp.MustCompile(fmt.Sprintf(":%s$", customVerb)) return specificVerbReg.MatchString(pathToken) } func removeCustomVerb(str string) string { return customVerbReg.ReplaceAllString(str, "") } go-restful-3.7.3/custom_verb_test.go000066400000000000000000000027121415314214700175350ustar00rootroot00000000000000package restful import "testing" func TestHasCustomVerb(t *testing.T) { testCase := []struct { path string has bool }{ {"/{userId}:init", true}, {"/{userId:init}", false}, {"/users/{id:init}:init", true}, {"/users/{id}", false}, } for _, v := range testCase { rs := hasCustomVerb(v.path) if rs != v.has { t.Errorf("path: %v should has no custom verb", v.path) } } } func TestRemoveCustomVerb(t *testing.T) { testCase := []struct { path string expectedPath string }{ {"/{userId}:init", "/{userId}"}, {"/{userId:init}", "/{userId:init}"}, {"/users/{id:init}:init", "/users/{id:init}"}, {"/users/{id}", "/users/{id}"}, {"/init/users/{id:init}:init", "/init/users/{id:init}"}, } for _, v := range testCase { rs := removeCustomVerb(v.path) if rs != v.expectedPath { t.Errorf("expected value: %v, actual: %v", v.expectedPath, rs) } } } func TestMatchCustomVerb(t *testing.T) { testCase := []struct { routeToken string pathToken string expected bool }{ {"{userId:regex}:init", "creator-123456789gWe:init", true}, {"{userId:regex}:init", "creator-123456789gWe", false}, {"{userId:regex}", "creator-123456789gWe:init", false}, {"users:init", "users:init", true}, {"users:init", "tokens:init", true}, } for idx, v := range testCase { rs := isMatchCustomVerb(v.routeToken, v.pathToken) if rs != v.expected { t.Errorf("expected value: %v, actual: %v, index: [%v]", v.expected, rs, idx) } } } go-restful-3.7.3/doc.go000066400000000000000000000165421415314214700147210ustar00rootroot00000000000000/* 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/v3/examples/user-resource/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/v3/examples/filters/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/v3/examples/encoding/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 go-restful-3.7.3/doc_examples_test.go000066400000000000000000000024701415314214700176510ustar00rootroot00000000000000package 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)) } go-restful-3.7.3/entity_accessors.go000066400000000000000000000120611415314214700175250ustar00rootroot00000000000000package 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/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 := 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 := 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 NewEncoder(resp).Encode(v) } go-restful-3.7.3/entity_accessors_test.go000066400000000000000000000032071415314214700205660ustar00rootroot00000000000000package 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") } } go-restful-3.7.3/examples/000077500000000000000000000000001415314214700154335ustar00rootroot00000000000000go-restful-3.7.3/examples/.goconvey000066400000000000000000000000061415314214700172610ustar00rootroot00000000000000ignorego-restful-3.7.3/examples/README.md000066400000000000000000000000411415314214700167050ustar00rootroot00000000000000## examples use v3 of the packagego-restful-3.7.3/examples/basicauth/000077500000000000000000000000001415314214700173765ustar00rootroot00000000000000go-restful-3.7.3/examples/basicauth/go.mod000066400000000000000000000002001415314214700204740ustar00rootroot00000000000000module github.com/emicklei/go-restful/examples/basicauth go 1.14 require github.com/emicklei/go-restful/v3 v3.3.3 // indirect go-restful-3.7.3/examples/basicauth/go.sum000066400000000000000000000005631415314214700205350ustar00rootroot00000000000000github.com/emicklei/go-restful v1.1.3 h1:KOKLkEASmIa2roa2xEV6WkADqyWrok5dt3TOMMHF1fE= github.com/emicklei/go-restful v2.14.3+incompatible h1:i59XyRHAxKCVBw3vHzQlpP/+pi89wH1v1HL+RKyVgxk= github.com/emicklei/go-restful/v3 v3.3.3 h1:2WxjFb4b+Eik8vJ1saz3jbhKnt4xaFVn3N9uU3WXfLE= github.com/emicklei/go-restful/v3 v3.3.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= go-restful-3.7.3/examples/basicauth/restful-basic-authentication.go000066400000000000000000000016361415314214700255130ustar00rootroot00000000000000package main import ( "io" "log" "net/http" restful "github.com/emicklei/go-restful/v3" ) // 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") } go-restful-3.7.3/examples/cors/000077500000000000000000000000001415314214700164015ustar00rootroot00000000000000go-restful-3.7.3/examples/cors/go.mod000066400000000000000000000001731415314214700175100ustar00rootroot00000000000000module github.com/emicklei/go-restful/examples/cors go 1.14 require github.com/emicklei/go-restful/v3 v3.3.3 // indirect go-restful-3.7.3/examples/cors/go.sum000066400000000000000000000005631415314214700175400ustar00rootroot00000000000000github.com/emicklei/go-restful v1.1.3 h1:KOKLkEASmIa2roa2xEV6WkADqyWrok5dt3TOMMHF1fE= github.com/emicklei/go-restful v2.14.3+incompatible h1:i59XyRHAxKCVBw3vHzQlpP/+pi89wH1v1HL+RKyVgxk= github.com/emicklei/go-restful/v3 v3.3.3 h1:2WxjFb4b+Eik8vJ1saz3jbhKnt4xaFVn3N9uU3WXfLE= github.com/emicklei/go-restful/v3 v3.3.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= go-restful-3.7.3/examples/cors/restful-CORS-filter.go000066400000000000000000000034421415314214700224460ustar00rootroot00000000000000package main import ( "io" "log" "net/http" restful "github.com/emicklei/go-restful/v3" ) // 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()) } go-restful-3.7.3/examples/cpuprof/000077500000000000000000000000001415314214700171115ustar00rootroot00000000000000go-restful-3.7.3/examples/cpuprof/go.mod000066400000000000000000000001761415314214700202230ustar00rootroot00000000000000module github.com/emicklei/go-restful/examples/cpuprof go 1.14 require github.com/emicklei/go-restful/v3 v3.3.3 // indirect go-restful-3.7.3/examples/cpuprof/go.sum000066400000000000000000000005631415314214700202500ustar00rootroot00000000000000github.com/emicklei/go-restful v1.1.3 h1:KOKLkEASmIa2roa2xEV6WkADqyWrok5dt3TOMMHF1fE= github.com/emicklei/go-restful v2.14.3+incompatible h1:i59XyRHAxKCVBw3vHzQlpP/+pi89wH1v1HL+RKyVgxk= github.com/emicklei/go-restful/v3 v3.3.3 h1:2WxjFb4b+Eik8vJ1saz3jbhKnt4xaFVn3N9uU3WXfLE= github.com/emicklei/go-restful/v3 v3.3.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= go-restful-3.7.3/examples/cpuprof/restful-cpuprofiler-service.go000066400000000000000000000040171415314214700251140ustar00rootroot00000000000000package main import ( "io" "log" "os" "runtime/pprof" restful "github.com/emicklei/go-restful/v3" ) // 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 go-restful-3.7.3/examples/encoding/000077500000000000000000000000001415314214700172215ustar00rootroot00000000000000go-restful-3.7.3/examples/encoding/go.mod000066400000000000000000000001771415314214700203340ustar00rootroot00000000000000module github.com/emicklei/go-restful/examples/encoding go 1.14 require github.com/emicklei/go-restful/v3 v3.3.3 // indirect go-restful-3.7.3/examples/encoding/go.sum000066400000000000000000000005631415314214700203600ustar00rootroot00000000000000github.com/emicklei/go-restful v1.1.3 h1:KOKLkEASmIa2roa2xEV6WkADqyWrok5dt3TOMMHF1fE= github.com/emicklei/go-restful v2.14.3+incompatible h1:i59XyRHAxKCVBw3vHzQlpP/+pi89wH1v1HL+RKyVgxk= github.com/emicklei/go-restful/v3 v3.3.3 h1:2WxjFb4b+Eik8vJ1saz3jbhKnt4xaFVn3N9uU3WXfLE= github.com/emicklei/go-restful/v3 v3.3.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= go-restful-3.7.3/examples/encoding/restful-encoding-filter.go000066400000000000000000000032451415314214700243070ustar00rootroot00000000000000package main import ( "log" "net/http" restful "github.com/emicklei/go-restful/v3" ) 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"}) } go-restful-3.7.3/examples/filters/000077500000000000000000000000001415314214700171035ustar00rootroot00000000000000go-restful-3.7.3/examples/filters/go.mod000066400000000000000000000001761415314214700202150ustar00rootroot00000000000000module github.com/emicklei/go-restful/examples/filters go 1.14 require github.com/emicklei/go-restful/v3 v3.3.3 // indirect go-restful-3.7.3/examples/filters/go.sum000066400000000000000000000005631415314214700202420ustar00rootroot00000000000000github.com/emicklei/go-restful v1.1.3 h1:KOKLkEASmIa2roa2xEV6WkADqyWrok5dt3TOMMHF1fE= github.com/emicklei/go-restful v2.14.3+incompatible h1:i59XyRHAxKCVBw3vHzQlpP/+pi89wH1v1HL+RKyVgxk= github.com/emicklei/go-restful/v3 v3.3.3 h1:2WxjFb4b+Eik8vJ1saz3jbhKnt4xaFVn3N9uU3WXfLE= github.com/emicklei/go-restful/v3 v3.3.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= go-restful-3.7.3/examples/filters/restful-filters.go000066400000000000000000000066731415314214700226000ustar00rootroot00000000000000package main import ( "log" "net/http" "time" restful "github.com/emicklei/go-restful/v3" ) 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"}) } go-restful-3.7.3/examples/form/000077500000000000000000000000001415314214700163765ustar00rootroot00000000000000go-restful-3.7.3/examples/form/go.mod000066400000000000000000000002561415314214700175070ustar00rootroot00000000000000module github.com/emicklei/go-restful/examples/form go 1.14 require ( github.com/emicklei/go-restful/v3 v3.3.3 // indirect github.com/gorilla/schema v1.2.0 // indirect ) go-restful-3.7.3/examples/form/go.sum000066400000000000000000000010341415314214700175270ustar00rootroot00000000000000github.com/emicklei/go-restful v1.1.3 h1:KOKLkEASmIa2roa2xEV6WkADqyWrok5dt3TOMMHF1fE= github.com/emicklei/go-restful v2.14.3+incompatible h1:i59XyRHAxKCVBw3vHzQlpP/+pi89wH1v1HL+RKyVgxk= github.com/emicklei/go-restful/v3 v3.3.3 h1:2WxjFb4b+Eik8vJ1saz3jbhKnt4xaFVn3N9uU3WXfLE= github.com/emicklei/go-restful/v3 v3.3.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= go-restful-3.7.3/examples/form/restful-form-handling.go000066400000000000000000000030251415314214700231340ustar00rootroot00000000000000package main import ( "fmt" "io" "log" "net/http" restful "github.com/emicklei/go-restful/v3" "github.com/gorilla/schema" ) // 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("Name=%s, Age=%d", p.Name, p.Age)) } func addresssForm(req *restful.Request, resp *restful.Response) { io.WriteString(resp.ResponseWriter, `

Enter Profile

`) } go-restful-3.7.3/examples/fulllog/000077500000000000000000000000001415314214700170775ustar00rootroot00000000000000go-restful-3.7.3/examples/fulllog/go.mod000066400000000000000000000001761415314214700202110ustar00rootroot00000000000000module github.com/emicklei/go-restful/examples/fulllog go 1.14 require github.com/emicklei/go-restful/v3 v3.3.3 // indirect go-restful-3.7.3/examples/fulllog/go.sum000066400000000000000000000005631415314214700202360ustar00rootroot00000000000000github.com/emicklei/go-restful v1.1.3 h1:KOKLkEASmIa2roa2xEV6WkADqyWrok5dt3TOMMHF1fE= github.com/emicklei/go-restful v2.14.3+incompatible h1:i59XyRHAxKCVBw3vHzQlpP/+pi89wH1v1HL+RKyVgxk= github.com/emicklei/go-restful/v3 v3.3.3 h1:2WxjFb4b+Eik8vJ1saz3jbhKnt4xaFVn3N9uU3WXfLE= github.com/emicklei/go-restful/v3 v3.3.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= go-restful-3.7.3/examples/fulllog/restful-full-logging-filter.go000066400000000000000000000043541415314214700247670ustar00rootroot00000000000000package main import ( "bytes" "io/ioutil" "log" "net/http" restful "github.com/emicklei/go-restful/v3" ) type User struct { Id int Name string } // // This example shows how to log both the request body and response body 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) ws.Route(ws.POST("/").Filter(bodyLogFilter).To(createUser)) return ws } // Route Filter (defines FilterFunction) func bodyLogFilter(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { inBody, err := ioutil.ReadAll(req.Request.Body) if err != nil { resp.WriteError(400, err) return } req.Request.Body = ioutil.NopCloser(bytes.NewReader(inBody)) c := NewResponseCapture(resp.ResponseWriter) resp.ResponseWriter = c chain.ProcessFilter(req, resp) log.Println("Request body:", string(inBody)) log.Println("Response body:", string(c.Bytes())) } // curl -H "content-type:application/json" http://localhost:8080/users -d '{"Id":42, "Name":"Captain Marvel"}' // func createUser(request *restful.Request, response *restful.Response) { u := new(User) err := request.ReadEntity(u) log.Print("createUser", err, u) response.WriteEntity(u) } type ResponseCapture struct { http.ResponseWriter wroteHeader bool status int body *bytes.Buffer } func NewResponseCapture(w http.ResponseWriter) *ResponseCapture { return &ResponseCapture{ ResponseWriter: w, wroteHeader: false, body: new(bytes.Buffer), } } func (c ResponseCapture) Header() http.Header { return c.ResponseWriter.Header() } func (c ResponseCapture) Write(data []byte) (int, error) { if !c.wroteHeader { c.WriteHeader(http.StatusOK) } c.body.Write(data) return c.ResponseWriter.Write(data) } func (c *ResponseCapture) WriteHeader(statusCode int) { c.status = statusCode c.wroteHeader = true c.ResponseWriter.WriteHeader(statusCode) } func (c ResponseCapture) Bytes() []byte { return c.body.Bytes() } func (c ResponseCapture) StatusCode() int { return c.status } go-restful-3.7.3/examples/google-custom-method/000077500000000000000000000000001415314214700214755ustar00rootroot00000000000000go-restful-3.7.3/examples/google-custom-method/go.mod000066400000000000000000000002131415314214700225770ustar00rootroot00000000000000module github.com/emicklei/go-restful/examples/google-custom-method go 1.14 require github.com/emicklei/go-restful/v3 v3.3.3 // indirect go-restful-3.7.3/examples/google-custom-method/go.sum000066400000000000000000000005631415314214700226340ustar00rootroot00000000000000github.com/emicklei/go-restful v1.1.3 h1:KOKLkEASmIa2roa2xEV6WkADqyWrok5dt3TOMMHF1fE= github.com/emicklei/go-restful v2.14.3+incompatible h1:i59XyRHAxKCVBw3vHzQlpP/+pi89wH1v1HL+RKyVgxk= github.com/emicklei/go-restful/v3 v3.3.3 h1:2WxjFb4b+Eik8vJ1saz3jbhKnt4xaFVn3N9uU3WXfLE= github.com/emicklei/go-restful/v3 v3.3.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= go-restful-3.7.3/examples/google-custom-method/restful-google-custom-method.go000066400000000000000000000023271415314214700275540ustar00rootroot00000000000000package main import ( "io" "log" "net/http" . "github.com/emicklei/go-restful/v3" ) // This example shows how to create a Route with google custom method // Requires the use of a CurlyRouter and path should end with the custom method // // GET http://localhost:8080/resource:validate // POST http://localhost:8080/resource/some-resource-id:init // POST http://localhost:8080/resource/some-resource-id:recycle func main() { DefaultContainer.Router(CurlyRouter{}) ws := new(WebService) ws.Route(ws.GET("/resource:validate").To(validateHandler)) ws.Route(ws.POST("/resource/{resourceId}:init").To(initHandler)) ws.Route(ws.POST("/resource/{resourceId}:recycle").To(recycleHandler)) Add(ws) println("[go-restful] serve path tails from http://localhost:8080/basepath") log.Fatal(http.ListenAndServe(":8080", nil)) } func validateHandler(req *Request, resp *Response) { io.WriteString(resp, "validate resource completed") } func initHandler(req *Request, resp *Response) { io.WriteString(resp, "init resource completed, resourceId: "+req.PathParameter("resourceId")) } func recycleHandler(req *Request, resp *Response) { io.WriteString(resp, "recycle resource completed, resourceId: "+req.PathParameter("resourceId")) } go-restful-3.7.3/examples/hello/000077500000000000000000000000001415314214700165365ustar00rootroot00000000000000go-restful-3.7.3/examples/hello/go.mod000066400000000000000000000001741415314214700176460ustar00rootroot00000000000000module github.com/emicklei/go-restful/examples/hello go 1.14 require github.com/emicklei/go-restful/v3 v3.3.3 // indirect go-restful-3.7.3/examples/hello/go.sum000066400000000000000000000005631415314214700176750ustar00rootroot00000000000000github.com/emicklei/go-restful v1.1.3 h1:KOKLkEASmIa2roa2xEV6WkADqyWrok5dt3TOMMHF1fE= github.com/emicklei/go-restful v2.14.3+incompatible h1:i59XyRHAxKCVBw3vHzQlpP/+pi89wH1v1HL+RKyVgxk= github.com/emicklei/go-restful/v3 v3.3.3 h1:2WxjFb4b+Eik8vJ1saz3jbhKnt4xaFVn3N9uU3WXfLE= github.com/emicklei/go-restful/v3 v3.3.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= go-restful-3.7.3/examples/hello/restful-hello-world.go000066400000000000000000000007141415314214700230010ustar00rootroot00000000000000package main import ( "io" "log" "net/http" restful "github.com/emicklei/go-restful/v3" ) // 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") } go-restful-3.7.3/examples/jwtauth/000077500000000000000000000000001415314214700171215ustar00rootroot00000000000000go-restful-3.7.3/examples/jwtauth/go.mod000066400000000000000000000003001415314214700202200ustar00rootroot00000000000000module github.com/emicklei/go-restful/examples/jwtauth go 1.14 require ( github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect github.com/emicklei/go-restful/v3 v3.3.3 // indirect ) go-restful-3.7.3/examples/jwtauth/go.sum000066400000000000000000000012151415314214700202530ustar00rootroot00000000000000github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/emicklei/go-restful v1.1.3 h1:KOKLkEASmIa2roa2xEV6WkADqyWrok5dt3TOMMHF1fE= github.com/emicklei/go-restful v2.14.3+incompatible h1:i59XyRHAxKCVBw3vHzQlpP/+pi89wH1v1HL+RKyVgxk= github.com/emicklei/go-restful/v3 v3.3.3 h1:2WxjFb4b+Eik8vJ1saz3jbhKnt4xaFVn3N9uU3WXfLE= github.com/emicklei/go-restful/v3 v3.3.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= go-restful-3.7.3/examples/jwtauth/restful-jwt-authentication.go000066400000000000000000000023741415314214700247610ustar00rootroot00000000000000package main import ( "io" "log" "net/http" "strings" "github.com/dgrijalva/jwt-go" restful "github.com/emicklei/go-restful/v3" ) // This example shows how to create a (Route) Filter that performs a JWT HS512 authentication. // // GET http://localhost:8080/secret // and use shared-token as a shared secret. var ( sharedSecret = []byte("shared-token") ) func main() { ws := new(restful.WebService) ws.Route(ws.GET("/secret").Filter(authJWT).To(secretJWT)) restful.Add(ws) log.Fatal(http.ListenAndServe(":8080", nil)) } func secretJWT(req *restful.Request, resp *restful.Response) { io.WriteString(resp, "42") } func validJWT(authHeader string) bool { if !strings.HasPrefix(authHeader, "JWT ") { return false } jwtToken := strings.Split(authHeader, " ") if len(jwtToken) < 2 { return false } parts := strings.Split(jwtToken[1], ".") err := jwt.SigningMethodHS512.Verify(strings.Join(parts[0:2], "."), parts[2], sharedSecret) if err != nil { return false } return true } func authJWT(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { authHeader := req.HeaderParameter("Authorization") if !validJWT(authHeader) { resp.WriteErrorString(401, "401: Not Authorized") return } chain.ProcessFilter(req, resp) } go-restful-3.7.3/examples/msgpack/000077500000000000000000000000001415314214700170605ustar00rootroot00000000000000go-restful-3.7.3/examples/msgpack/msgpack_entity.go000066400000000000000000000022001415314214700224220ustar00rootroot00000000000000package restPack import ( restful "github.com/emicklei/go-restful/v3" "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) } go-restful-3.7.3/examples/msgpack/msgpack_entity_test.go000066400000000000000000000072601415314214700234740ustar00rootroot00000000000000package 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) } go-restful-3.7.3/examples/multi-container/000077500000000000000000000000001415314214700205455ustar00rootroot00000000000000go-restful-3.7.3/examples/multi-container/go.mod000066400000000000000000000002061415314214700216510ustar00rootroot00000000000000module github.com/emicklei/go-restful/examples/multi-container go 1.14 require github.com/emicklei/go-restful/v3 v3.3.3 // indirect go-restful-3.7.3/examples/multi-container/go.sum000066400000000000000000000005631415314214700217040ustar00rootroot00000000000000github.com/emicklei/go-restful v1.1.3 h1:KOKLkEASmIa2roa2xEV6WkADqyWrok5dt3TOMMHF1fE= github.com/emicklei/go-restful v2.14.3+incompatible h1:i59XyRHAxKCVBw3vHzQlpP/+pi89wH1v1HL+RKyVgxk= github.com/emicklei/go-restful/v3 v3.3.3 h1:2WxjFb4b+Eik8vJ1saz3jbhKnt4xaFVn3N9uU3WXfLE= github.com/emicklei/go-restful/v3 v3.3.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= go-restful-3.7.3/examples/multi-container/restful-multi-containers.go000066400000000000000000000023231415314214700260530ustar00rootroot00000000000000package main import ( "io" "log" "net/http" restful "github.com/emicklei/go-restful/v3" ) // 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") } go-restful-3.7.3/examples/ncsa/000077500000000000000000000000001415314214700163575ustar00rootroot00000000000000go-restful-3.7.3/examples/ncsa/go.mod000066400000000000000000000001731415314214700174660ustar00rootroot00000000000000module github.com/emicklei/go-restful/examples/ncsa go 1.14 require github.com/emicklei/go-restful/v3 v3.3.3 // indirect go-restful-3.7.3/examples/ncsa/go.sum000066400000000000000000000005631415314214700175160ustar00rootroot00000000000000github.com/emicklei/go-restful v1.1.3 h1:KOKLkEASmIa2roa2xEV6WkADqyWrok5dt3TOMMHF1fE= github.com/emicklei/go-restful v2.14.3+incompatible h1:i59XyRHAxKCVBw3vHzQlpP/+pi89wH1v1HL+RKyVgxk= github.com/emicklei/go-restful/v3 v3.3.3 h1:2WxjFb4b+Eik8vJ1saz3jbhKnt4xaFVn3N9uU3WXfLE= github.com/emicklei/go-restful/v3 v3.3.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= go-restful-3.7.3/examples/ncsa/restful-NCSA-logging.go000066400000000000000000000024211415314214700225370ustar00rootroot00000000000000package main import ( "io" "log" "net/http" "os" "strings" "time" restful "github.com/emicklei/go-restful/v3" ) // 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") } go-restful-3.7.3/examples/nocache/000077500000000000000000000000001415314214700170335ustar00rootroot00000000000000go-restful-3.7.3/examples/nocache/go.mod000066400000000000000000000001761415314214700201450ustar00rootroot00000000000000module github.com/emicklei/go-restful/examples/nocache go 1.14 require github.com/emicklei/go-restful/v3 v3.3.3 // indirect go-restful-3.7.3/examples/nocache/go.sum000066400000000000000000000005631415314214700201720ustar00rootroot00000000000000github.com/emicklei/go-restful v1.1.3 h1:KOKLkEASmIa2roa2xEV6WkADqyWrok5dt3TOMMHF1fE= github.com/emicklei/go-restful v2.14.3+incompatible h1:i59XyRHAxKCVBw3vHzQlpP/+pi89wH1v1HL+RKyVgxk= github.com/emicklei/go-restful/v3 v3.3.3 h1:2WxjFb4b+Eik8vJ1saz3jbhKnt4xaFVn3N9uU3WXfLE= github.com/emicklei/go-restful/v3 v3.3.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= go-restful-3.7.3/examples/nocache/restful-no-cache-filter.go000066400000000000000000000010211415314214700237760ustar00rootroot00000000000000package main import ( "io" "log" "net/http" restful "github.com/emicklei/go-restful/v3" ) // 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") } go-restful-3.7.3/examples/openapi/000077500000000000000000000000001415314214700170665ustar00rootroot00000000000000go-restful-3.7.3/examples/openapi/go.mod000066400000000000000000000003161415314214700201740ustar00rootroot00000000000000module github.com/emicklei/go-restful/examples/openapi go 1.14 require ( github.com/emicklei/go-restful-openapi/v2 v2.2.1 github.com/emicklei/go-restful/v3 v3.3.1 github.com/go-openapi/spec v0.19.10 ) go-restful-3.7.3/examples/openapi/go.sum000066400000000000000000000134571415314214700202330ustar00rootroot00000000000000github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful-openapi/v2 v2.2.1 h1:9xG+IjR5kUNBNJzksOmV92DzgrBJLKCLD7uY9i5XtYI= github.com/emicklei/go-restful-openapi/v2 v2.2.1/go.mod h1:bs67E3SEVgSmB3qDuRLqpS0NcpheqtsCCMhW2/jml1E= github.com/emicklei/go-restful/v3 v3.0.0-rc2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.3.1 h1:engBLuFVe3n4ck963X94ZFfauROk9nthZOKilyHLgpM= github.com/emicklei/go-restful/v3 v3.3.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg= github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= github.com/go-openapi/spec v0.19.10 h1:pcNevfYytLaOQuTju0wm6OqcqU/E/pRwuSGigrLTI28= github.com/go-openapi/spec v0.19.10/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.6 h1:JSUbVWlaTLMhXeOMyArSUNCdroxZu2j1TcrsOV8Mj7Q= github.com/go-openapi/swag v0.19.6/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= go-restful-3.7.3/examples/openapi/restful-openapi.go000066400000000000000000000130421415314214700225320ustar00rootroot00000000000000package main // Note: this file is copied from https://github.com/emicklei/go-restful-openapi/blob/master/examples/user-resource.go import ( "log" "net/http" restfulspec "github.com/emicklei/go-restful-openapi/v2" restful "github.com/emicklei/go-restful/v3" "github.com/go-openapi/spec" ) type UserResource struct { // normally one would use DAO (data access object) users map[string]User } 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 // 1Melissa Raspberry // 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 // 1Melissa // 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 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")))) // Optionally, you may need to enable CORS for the UI to work. cors := restful.CrossOriginResourceSharing{ AllowedHeaders: []string{"Content-Type", "Accept"}, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"}, CookiesAllowed: false, Container: restful.DefaultContainer} restful.DefaultContainer.Filter(cors.Filter) log.Printf("Get the API using http://localhost:8080/apidocs.json") log.Printf("Open Swagger UI using http://localhost:8080/apidocs/?url=http://localhost:8080/apidocs.json") 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{ ContactInfoProps: spec.ContactInfoProps{ Name: "john", Email: "john@doe.rp", URL: "http://johndoe.org", }, }, License: &spec.License{ LicenseProps: spec.LicenseProps{ 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"` } go-restful-3.7.3/examples/options/000077500000000000000000000000001415314214700171265ustar00rootroot00000000000000go-restful-3.7.3/examples/options/restful-options-filter.go000066400000000000000000000023061415314214700241160ustar00rootroot00000000000000package main import ( "io" "log" "net/http" restful "github.com/emicklei/go-restful/v3" ) // 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()) } go-restful-3.7.3/examples/pathtail/000077500000000000000000000000001415314214700172415ustar00rootroot00000000000000go-restful-3.7.3/examples/pathtail/restful-path-tail.go000066400000000000000000000013151415314214700231350ustar00rootroot00000000000000package main import ( "io" "log" "net/http" . "github.com/emicklei/go-restful/v3" ) // 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")) } go-restful-3.7.3/examples/prepost/000077500000000000000000000000001415314214700171275ustar00rootroot00000000000000go-restful-3.7.3/examples/prepost/restful-pre-post-filters.go000066400000000000000000000053001415314214700243550ustar00rootroot00000000000000package main import ( "io" "log" "net/http" restful "github.com/emicklei/go-restful/v3" ) // 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") } go-restful-3.7.3/examples/resource-func/000077500000000000000000000000001415314214700202135ustar00rootroot00000000000000go-restful-3.7.3/examples/resource-func/restful-resource-functions.go000066400000000000000000000034251415314214700260650ustar00rootroot00000000000000package main import ( "log" "net/http" restful "github.com/emicklei/go-restful/v3" ) // 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 // 1The First 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)) } go-restful-3.7.3/examples/static/000077500000000000000000000000001415314214700167225ustar00rootroot00000000000000go-restful-3.7.3/examples/static/restful-serve-static.go000066400000000000000000000023211415314214700233420ustar00rootroot00000000000000package main import ( "fmt" "log" "net/http" "path" restful "github.com/emicklei/go-restful/v3" ) // 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"))) } go-restful-3.7.3/examples/swagger/000077500000000000000000000000001415314214700170725ustar00rootroot00000000000000go-restful-3.7.3/examples/swagger/restful-swagger.go000066400000000000000000000037461415314214700225540ustar00rootroot00000000000000package 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) } go-restful-3.7.3/examples/template/000077500000000000000000000000001415314214700172465ustar00rootroot00000000000000go-restful-3.7.3/examples/template/go.mod000066400000000000000000000001771415314214700203610ustar00rootroot00000000000000module github.com/emicklei/go-restful/examples/template go 1.14 require github.com/emicklei/go-restful/v3 v3.3.3 // indirect go-restful-3.7.3/examples/template/go.sum000066400000000000000000000005631415314214700204050ustar00rootroot00000000000000github.com/emicklei/go-restful v1.1.3 h1:KOKLkEASmIa2roa2xEV6WkADqyWrok5dt3TOMMHF1fE= github.com/emicklei/go-restful v2.14.3+incompatible h1:i59XyRHAxKCVBw3vHzQlpP/+pi89wH1v1HL+RKyVgxk= github.com/emicklei/go-restful/v3 v3.3.3 h1:2WxjFb4b+Eik8vJ1saz3jbhKnt4xaFVn3N9uU3WXfLE= github.com/emicklei/go-restful/v3 v3.3.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= go-restful-3.7.3/examples/template/home.html000066400000000000000000000001021415314214700210550ustar00rootroot00000000000000

{{.Text}}

go-restful-3.7.3/examples/template/restful-html-template.go000066400000000000000000000013561415314214700240410ustar00rootroot00000000000000package main import ( "log" "net/http" "text/template" restful "github.com/emicklei/go-restful/v3" ) // 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) } go-restful-3.7.3/examples/tests/000077500000000000000000000000001415314214700165755ustar00rootroot00000000000000go-restful-3.7.3/examples/tests/restful-curly-router_test.go000066400000000000000000000076421415314214700243320ustar00rootroot00000000000000package 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 // 1Melissa Raspberry // 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 // 1Melissa // 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) } } go-restful-3.7.3/examples/tests/restful-route_test.go000066400000000000000000000015151415314214700230050ustar00rootroot00000000000000package 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("42") 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") } go-restful-3.7.3/examples/tests/restful-routefunction_test.go000066400000000000000000000011761415314214700245560ustar00rootroot00000000000000package 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) } } go-restful-3.7.3/examples/user-resource/000077500000000000000000000000001415314214700202365ustar00rootroot00000000000000go-restful-3.7.3/examples/user-resource/go.mod000066400000000000000000000003701415314214700213440ustar00rootroot00000000000000module github.com/emicklei/go-restful/examples/user-resource go 1.14 require ( github.com/emicklei/go-restful-openapi/v2 v2.2.1 // indirect github.com/emicklei/go-restful/v3 v3.3.3 // indirect github.com/go-openapi/spec v0.19.10 // indirect ) go-restful-3.7.3/examples/user-resource/go.sum000066400000000000000000000123101415314214700213660ustar00rootroot00000000000000github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful v1.1.3 h1:KOKLkEASmIa2roa2xEV6WkADqyWrok5dt3TOMMHF1fE= github.com/emicklei/go-restful v2.14.3+incompatible h1:i59XyRHAxKCVBw3vHzQlpP/+pi89wH1v1HL+RKyVgxk= github.com/emicklei/go-restful-openapi v1.4.1 h1:SocVTIQWnXyit4dotTrwmncBAjtRaBmfcHjo3XGcCm4= github.com/emicklei/go-restful-openapi/v2 v2.2.1 h1:9xG+IjR5kUNBNJzksOmV92DzgrBJLKCLD7uY9i5XtYI= github.com/emicklei/go-restful-openapi/v2 v2.2.1/go.mod h1:bs67E3SEVgSmB3qDuRLqpS0NcpheqtsCCMhW2/jml1E= github.com/emicklei/go-restful/v3 v3.0.0-rc2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.3.3 h1:2WxjFb4b+Eik8vJ1saz3jbhKnt4xaFVn3N9uU3WXfLE= github.com/emicklei/go-restful/v3 v3.3.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg= github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= github.com/go-openapi/spec v0.19.10 h1:pcNevfYytLaOQuTju0wm6OqcqU/E/pRwuSGigrLTI28= github.com/go-openapi/spec v0.19.10/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.6 h1:JSUbVWlaTLMhXeOMyArSUNCdroxZu2j1TcrsOV8Mj7Q= github.com/go-openapi/swag v0.19.6/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= go-restful-3.7.3/examples/user-resource/restful-user-resource.go000066400000000000000000000121441415314214700250540ustar00rootroot00000000000000package main import ( "log" "net/http" restfulspec "github.com/emicklei/go-restful-openapi/v2" restful "github.com/emicklei/go-restful/v3" "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 // 1Melissa Raspberry // 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 // 1Melissa // 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 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{ ContactInfoProps: spec.ContactInfoProps{ Name: "john", Email: "john@doe.rp", URL: "http://johndoe.org", }, }, License: &spec.License{ LicenseProps: spec.LicenseProps{ 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"` } go-restful-3.7.3/extensions.go000066400000000000000000000013231415314214700163420ustar00rootroot00000000000000package restful // Copyright 2021 Ernest Micklei. All rights reserved. // Use of this source code is governed by a license // that can be found in the LICENSE file. // ExtensionProperties provides storage of vendor extensions for entities type ExtensionProperties struct { // Extensions vendor extensions used to describe extra functionality // (https://swagger.io/docs/specification/2-0/swagger-extensions/) Extensions map[string]interface{} } // AddExtension adds or updates a key=value pair to the extension map. func (ep *ExtensionProperties) AddExtension(key string, value interface{}) { if ep.Extensions == nil { ep.Extensions = map[string]interface{}{key: value} } else { ep.Extensions[key] = value } } go-restful-3.7.3/filter.go000066400000000000000000000033401415314214700154310ustar00rootroot00000000000000package 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 ParameterDocs []*Parameter // the parameter docs for the route Operation string // the name of the operation } // 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) } go-restful-3.7.3/filter_test.go000066400000000000000000000070721415314214700164760ustar00rootroot00000000000000package 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() } go-restful-3.7.3/go.mod000066400000000000000000000000621415314214700147210ustar00rootroot00000000000000module github.com/emicklei/go-restful/v3 go 1.13 go-restful-3.7.3/json.go000066400000000000000000000002541415314214700151160ustar00rootroot00000000000000// +build !jsoniter package restful import "encoding/json" var ( MarshalIndent = json.MarshalIndent NewDecoder = json.NewDecoder NewEncoder = json.NewEncoder ) go-restful-3.7.3/jsoniter.go000066400000000000000000000003671415314214700160070ustar00rootroot00000000000000// +build jsoniter package restful import "github.com/json-iterator/go" var ( json = jsoniter.ConfigCompatibleWithStandardLibrary MarshalIndent = json.MarshalIndent NewDecoder = json.NewDecoder NewEncoder = json.NewEncoder ) go-restful-3.7.3/jsr311.go000066400000000000000000000236531415314214700152000ustar00rootroot00000000000000package 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" "strings" ) // 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 } // ExtractParameters is used to obtain the path parameters from the route using the same matching // engine as the JSR 311 router. func (r RouterJSR311) ExtractParameters(route *Route, webService *WebService, urlPath string) map[string]string { webServiceExpr := webService.pathExpr webServiceMatches := webServiceExpr.Matcher.FindStringSubmatch(urlPath) pathParameters := r.extractParams(webServiceExpr, webServiceMatches) routeExpr := route.pathExpr routeMatches := routeExpr.Matcher.FindStringSubmatch(webServiceMatches[len(webServiceMatches)-1]) routeParams := r.extractParams(routeExpr, routeMatches) for key, value := range routeParams { pathParameters[key] = value } return pathParameters } func (RouterJSR311) extractParams(pathExpr *pathExpression, matches []string) map[string]string { params := map[string]string{} for i := 1; i < len(matches); i++ { if len(pathExpr.VarNames) >= i { params[pathExpr.VarNames[i-1]] = matches[i] } } return params } // 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) { candidates := make([]*Route, 0, 8) for i, each := range routes { ok := true for _, fn := range each.If { if !fn(httpRequest) { ok = false break } } if ok { candidates = append(candidates, &routes[i]) } } if len(candidates) == 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 previous := candidates candidates = candidates[:0] for _, each := range previous { if httpRequest.Method == each.Method { candidates = append(candidates, each) } } if len(candidates) == 0 { if trace { traceLogger.Printf("no Route found (in %d routes) that matches HTTP method %s\n", len(previous), httpRequest.Method) } allowed := []string{} allowedLoop: for _, candidate := range previous { for _, method := range allowed { if method == candidate.Method { continue allowedLoop } } allowed = append(allowed, candidate.Method) } header := http.Header{"Allow": []string{strings.Join(allowed, ", ")}} return nil, NewErrorWithHeader(http.StatusMethodNotAllowed, "405: Method Not Allowed", header) } // content-type contentType := httpRequest.Header.Get(HEADER_ContentType) previous = candidates candidates = candidates[:0] for _, each := range previous { if each.matchesContentType(contentType) { candidates = append(candidates, each) } } if len(candidates) == 0 { if trace { traceLogger.Printf("no Route found (from %d) that matches HTTP Content-Type: %s\n", len(previous), contentType) } if httpRequest.ContentLength > 0 { return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type") } } // accept previous = candidates candidates = candidates[:0] accept := httpRequest.Header.Get(HEADER_Accept) if len(accept) == 0 { accept = "*/*" } for _, each := range previous { if each.matchesAccept(accept) { candidates = append(candidates, each) } } if len(candidates) == 0 { if trace { traceLogger.Printf("no Route found (from %d) that matches HTTP Accept: %s\n", len(previous), accept) } available := []string{} for _, candidate := range previous { available = append(available, candidate.Produces...) } return nil, NewError( http.StatusNotAcceptable, fmt.Sprintf("406: Not Acceptable\n\nAvailable representations: %s", strings.Join(available, ", ")), ) } // return r.bestMatchByMedia(outputMediaOk, contentType, accept), nil return candidates[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 } go-restful-3.7.3/jsr311_test.go000066400000000000000000000263151415314214700162350ustar00rootroot00000000000000package restful import ( "bytes" "compress/gzip" "io" "net/http" "reflect" "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 params map[string]string }{ {"/", "/", "/", map[string]string{}}, {"/p", "/p", "", map[string]string{}}, {"/p/x", "/p/{q}", "", map[string]string{"q": "x"}}, {"/q/x", "/q", "/x", map[string]string{}}, {"/p/x/", "/p/{q}", "/", map[string]string{"q": "x"}}, {"/p/x/y", "/p/{q}", "/y", map[string]string{"q": "x"}}, {"/q/x/y", "/q", "/x/y", map[string]string{}}, {"/z/q", "/{p}/q", "", map[string]string{"p": "z"}}, {"/a/b/c/q", "/", "/a/b/c/q", map[string]string{}}, } 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 { each.Route(each.GET("").To(dummy)) 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 } params := router.ExtractParameters(&who.Routes()[0], who, fixture.path) if !reflect.DeepEqual(params, fixture.params) { t.Logf("[line:%v] Unexpected params, expected:%v, actual:%v", i, fixture.params, params) 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") } } var extractParams = []struct { name string routePath string urlPath string expectedParams map[string]string }{ {"wildcardLastPart", "/fixed/{var:*}", "/fixed/remainder", map[string]string{"var": "remainder"}}, {"wildcardMultipleParts", "/fixed/{var:*}", "/fixed/remain/der", map[string]string{"var": "remain/der"}}, {"wildcardManyParts", "/fixed/{var:*}", "/fixed/test/sub/hi.html", map[string]string{"var": "test/sub/hi.html"}}, {"wildcardInMiddle", "/fixed/{var:*}/morefixed", "/fixed/middle/stuff/morefixed", map[string]string{"var": "middle/stuff"}}, {"wildcardFollowedByVar", "/fixed/{var:*}/morefixed/{otherVar}", "/fixed/middle/stuff/morefixed/end", map[string]string{"var": "middle/stuff", "otherVar": "end"}}, {"singleParam", "/fixed/{var}", "/fixed/remainder", map[string]string{"var": "remainder"}}, {"slash", "/", "/", map[string]string{}}, {"NoVars", "/fixed", "/fixed", map[string]string{}}, {"TwoVars", "/from/{source}/to/{destination}", "/from/LHR/to/AMS", map[string]string{"source": "LHR", "destination": "AMS"}}, {"VarOnFront", "/{what}/from/{source}", "/who/from/SOS", map[string]string{"what": "who", "source": "SOS"}}, } func TestExtractParams(t *testing.T) { for _, testCase := range extractParams { t.Run(testCase.name, func(t *testing.T) { ws1 := new(WebService).Path("/") ws1.Route(ws1.GET(testCase.routePath).To(dummy)) router := RouterJSR311{} req, _ := http.NewRequest(http.MethodGet, testCase.urlPath, nil) params := router.ExtractParameters(&ws1.Routes()[0], ws1, req.URL.Path) if len(params) != len(testCase.expectedParams) { t.Fatalf("Wrong length of params on selected route, expected: %v, got: %v", testCase.expectedParams, params) } for expectedParamKey, expectedParamValue := range testCase.expectedParams { if expectedParamValue != params[expectedParamKey] { t.Errorf("Wrong parameter for key '%v', expected: %v, got: %v", expectedParamKey, expectedParamValue, params[expectedParamKey]) } } }) } } func TestSelectRouteInvalidMethod(t *testing.T) { ws1 := new(WebService).Path("/") ws1.Route(ws1.GET("/simple").To(dummy)) router := RouterJSR311{} req, _ := http.NewRequest(http.MethodPost, "/simple", nil) _, _, err := router.SelectRoute([]*WebService{ws1}, req) if err == nil { t.Fatal("Expected an error as the wrong method is used but was nil") } } func TestParameterInWebService(t *testing.T) { for _, testCase := range extractParams { t.Run(testCase.name, func(t *testing.T) { ws1 := new(WebService).Path("/{wsParam}") ws1.Route(ws1.GET(testCase.routePath).To(dummy)) router := RouterJSR311{} req, _ := http.NewRequest(http.MethodGet, "/wsValue"+testCase.urlPath, nil) params := router.ExtractParameters(&ws1.Routes()[0], ws1, req.URL.Path) expectedParams := map[string]string{"wsParam": "wsValue"} for key, value := range testCase.expectedParams { expectedParams[key] = value } if len(params) != len(expectedParams) { t.Fatalf("Wrong length of params on selected route, expected: %v, got: %v", testCase.expectedParams, params) } for expectedParamKey, expectedParamValue := range testCase.expectedParams { if expectedParamValue != params[expectedParamKey] { t.Errorf("Wrong parameter for key '%v', expected: %v, got: %v", expectedParamKey, expectedParamValue, params[expectedParamKey]) } } }) } } func dummy(req *Request, resp *Response) { io.WriteString(resp.ResponseWriter, "dummy") } func gzippedDummy() []byte { var buf bytes.Buffer zw := gzip.NewWriter(&buf) zw.Write([]byte("dummy")) zw.Flush() zw.Close() return buf.Bytes() } go-restful-3.7.3/log/000077500000000000000000000000001415314214700143765ustar00rootroot00000000000000go-restful-3.7.3/log/log.go000066400000000000000000000012601415314214700155050ustar00rootroot00000000000000package 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...) } go-restful-3.7.3/logger.go000066400000000000000000000016341415314214700154270ustar00rootroot00000000000000package 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/v3/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 } go-restful-3.7.3/mime.go000066400000000000000000000026331415314214700150770ustar00rootroot00000000000000package 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) } const qFactorWeightingKey = "q" // sortedMimes returns a list of mime sorted (desc) by its specified quality. // e.g. text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 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 qAndWeight := strings.Split(typeAndQuality[1], "=") if len(qAndWeight) == 2 && strings.Trim(qAndWeight[0], " ") == qFactorWeightingKey { f, err := strconv.ParseFloat(qAndWeight[1], 64) if err != nil { traceLogger.Printf("unable to parse quality in %s, %v", each, err) } else { sorted = insertMime(sorted, mime{typeAndQuality[0], f}) } } else { sorted = insertMime(sorted, mime{typeAndQuality[0], 1.0}) } } } return } go-restful-3.7.3/mime_test.go000066400000000000000000000013721415314214700161350ustar00rootroot00000000000000package 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) } } func TestNonNumberQualityInAccept_issue400(t *testing.T) { accept := `text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3` result := sortedMimes(accept) if got, want := len(result), 7; got != want { t.Errorf("got %d want %d quality mimes", got, want) } } go-restful-3.7.3/options_filter.go000066400000000000000000000026461415314214700172140ustar00rootroot00000000000000package 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 } go-restful-3.7.3/options_filter_test.go000066400000000000000000000016771415314214700202560ustar00rootroot00000000000000package 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) } } go-restful-3.7.3/parameter.go000066400000000000000000000147571415314214700161420ustar00rootroot00000000000000package restful import "sort" // 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 // CollectionFormatCSV comma separated values `foo,bar` CollectionFormatCSV = CollectionFormat("csv") // CollectionFormatSSV space separated values `foo bar` CollectionFormatSSV = CollectionFormat("ssv") // CollectionFormatTSV tab separated values `foo\tbar` CollectionFormatTSV = CollectionFormat("tsv") // CollectionFormatPipes pipe separated values `foo|bar` CollectionFormatPipes = CollectionFormat("pipes") // CollectionFormatMulti corresponds to multiple parameter instances instead of multiple values for a single // instance `foo=bar&foo=baz`. This is valid only for QueryParameters and FormParameters CollectionFormatMulti = CollectionFormat("multi") ) type CollectionFormat string func (cf CollectionFormat) String() string { return string(cf) } // 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 { ExtensionProperties Name, Description, DataType, DataFormat string Kind int Required bool // AllowableValues is deprecated. Use PossibleValues instead AllowableValues map[string]string PossibleValues []string AllowMultiple bool AllowEmptyValue bool DefaultValue string CollectionFormat string Pattern string Minimum *float64 Maximum *float64 MinLength *int64 MaxLength *int64 MinItems *int64 MaxItems *int64 UniqueItems bool } // 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 } // AddExtension adds or updates a key=value pair to the extension map func (p *Parameter) AddExtension(key string, value interface{}) *Parameter { p.data.AddExtension(key, value) return p } // AllowEmptyValue sets the AllowEmptyValue field and returns the receiver func (p *Parameter) AllowEmptyValue(multiple bool) *Parameter { p.data.AllowEmptyValue = multiple return p } // AllowableValues is deprecated. Use PossibleValues instead. Both will be set. func (p *Parameter) AllowableValues(values map[string]string) *Parameter { p.data.AllowableValues = values allowableSortedKeys := make([]string, 0, len(values)) for k := range values { allowableSortedKeys = append(allowableSortedKeys, k) } sort.Strings(allowableSortedKeys) p.data.PossibleValues = make([]string, 0, len(values)) for _, k := range allowableSortedKeys { p.data.PossibleValues = append(p.data.PossibleValues, values[k]) } return p } // PossibleValues sets the possible values field and returns the receiver func (p *Parameter) PossibleValues(values []string) *Parameter { p.data.PossibleValues = 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 } // CollectionFormat sets the collection format for an array type func (p *Parameter) CollectionFormat(format CollectionFormat) *Parameter { p.data.CollectionFormat = format.String() return p } // Pattern sets the pattern field and returns the receiver func (p *Parameter) Pattern(pattern string) *Parameter { p.data.Pattern = pattern return p } // Minimum sets the minimum field and returns the receiver func (p *Parameter) Minimum(minimum float64) *Parameter { p.data.Minimum = &minimum return p } // Maximum sets the maximum field and returns the receiver func (p *Parameter) Maximum(maximum float64) *Parameter { p.data.Maximum = &maximum return p } // MinLength sets the minLength field and returns the receiver func (p *Parameter) MinLength(minLength int64) *Parameter { p.data.MinLength = &minLength return p } // MaxLength sets the maxLength field and returns the receiver func (p *Parameter) MaxLength(maxLength int64) *Parameter { p.data.MaxLength = &maxLength return p } // MinItems sets the minItems field and returns the receiver func (p *Parameter) MinItems(minItems int64) *Parameter { p.data.MinItems = &minItems return p } // MaxItems sets the maxItems field and returns the receiver func (p *Parameter) MaxItems(maxItems int64) *Parameter { p.data.MaxItems = &maxItems return p } // UniqueItems sets the uniqueItems field and returns the receiver func (p *Parameter) UniqueItems(uniqueItems bool) *Parameter { p.data.UniqueItems = uniqueItems return p } go-restful-3.7.3/path_expression.go000066400000000000000000000050201415314214700173540ustar00rootroot00000000000000package 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) VarNames []string // the names of parameters (enclosed by {}) in the path 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, varNames, varCount, tokens := templateToRegularExpression(path) compiled, err := regexp.Compile(expression) if err != nil { return nil, err } return &pathExpression{literalCount, varNames, 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, varNames []string, 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, ":") var varName string if colon != -1 { // extract expression varName = strings.TrimSpace(each[1:colon]) 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 varName = strings.TrimSpace(each[1 : len(each)-1]) buffer.WriteString("([^/]+?)") } varNames = append(varNames, varName) varCount += 1 } else { literalCount += len(each) encoded := each // TODO URI encode buffer.WriteString(regexp.QuoteMeta(encoded)) } } return strings.TrimRight(buffer.String(), "/") + "(/.*)?$", literalCount, varNames, varCount, tokens } go-restful-3.7.3/path_expression_test.go000066400000000000000000000026661415314214700204300ustar00rootroot00000000000000package restful import ( "reflect" "testing" ) var tempregexs = []struct { template, regex string names []string literalCount, varCount int }{ {"", "^(/.*)?$", nil, 0, 0}, {"/a/{b}/c/", "^/a/([^/]+?)/c(/.*)?$", []string{"b"}, 2, 1}, {"/{a}/{b}/{c-d-e}/", "^/([^/]+?)/([^/]+?)/([^/]+?)(/.*)?$", []string{"a", "b", "c-d-e"}, 0, 3}, {"/{p}/abcde", "^/([^/]+?)/abcde(/.*)?$", []string{"p"}, 5, 1}, {"/a/{b:*}", "^/a/(.*)(/.*)?$", []string{"b"}, 1, 1}, {"/a/{b:[a-z]+}", "^/a/([a-z]+)(/.*)?$", []string{"b"}, 1, 1}, } func TestTemplateToRegularExpression(t *testing.T) { ok := true for i, fixture := range tempregexs { actual, lCount, varNames, 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 !reflect.DeepEqual(fixture.names, varNames) { t.Logf("variable name mismatch, expected:%v , actual:%v, line:%v\n", fixture.names, varNames, i) ok = false } } if !ok { t.Fatal("one or more expression did not match") } } go-restful-3.7.3/path_processor.go000066400000000000000000000041421415314214700172000ustar00rootroot00000000000000package restful import ( "bytes" "strings" ) // Copyright 2018 Ernest Micklei. All rights reserved. // Use of this source code is governed by a license // that can be found in the LICENSE file. // PathProcessor is extra behaviour that a Router can provide to extract path parameters from the path. // If a Router does not implement this interface then the default behaviour will be used. type PathProcessor interface { // ExtractParameters gets the path parameters defined in the route and webService from the urlPath ExtractParameters(route *Route, webService *WebService, urlPath string) map[string]string } type defaultPathProcessor struct{} // Extract the parameters from the request url path func (d defaultPathProcessor) ExtractParameters(r *Route, _ *WebService, 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 r.hasCustomVerb && hasCustomVerb(key) { key = removeCustomVerb(key) value = removeCustomVerb(value) } if strings.Index(key, "{") > -1 { // 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 {} startIndex := strings.Index(key, "{") endKeyIndex := strings.Index(key, "}") suffixLength := len(key) - endKeyIndex - 1 endValueIndex := len(value) - suffixLength pathParameters[key[startIndex+1:endKeyIndex]] = value[startIndex:endValueIndex] } } } 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() } go-restful-3.7.3/path_processor_test.go000066400000000000000000000060041415314214700202360ustar00rootroot00000000000000package restful import ( "testing" ) func TestMatchesPath_OneParam(t *testing.T) { params := doExtractParams("/from/{source}", 2, "/from/here", t) if params["source"] != "here" { t.Errorf("parameter mismatch here") } } func TestMatchesPath_Slash(t *testing.T) { params := doExtractParams("/", 0, "/", t) if len(params) != 0 { t.Errorf("expected empty parameters") } } func TestMatchesPath_SlashNonVar(t *testing.T) { params := doExtractParams("/any", 1, "/any", t) if len(params) != 0 { t.Errorf("expected empty parameters") } } func TestMatchesPath_TwoVars(t *testing.T) { params := doExtractParams("/from/{source}/to/{destination}", 4, "/from/AMS/to/NY", t) if params["source"] != "AMS" { t.Errorf("parameter mismatch AMS") } } func TestMatchesPath_VarOnFront(t *testing.T) { params := doExtractParams("{what}/from/{source}/", 3, "who/from/SOS/", t) if params["source"] != "SOS" { t.Errorf("parameter mismatch SOS") } } func TestExtractParameters_EmptyValue(t *testing.T) { params := doExtractParams("/fixed/{var}", 2, "/fixed/", t) if params["var"] != "" { t.Errorf("parameter mismatch var") } } func TestExtractParameters_Dot(t *testing.T) { params := doExtractParams("/fixed/{var}.foo", 2, "/fixed/barrr.foo", t) if params["var"] != "barrr" { t.Errorf("parameter mismatch var") } } func TestExtractParameters_Prefix(t *testing.T) { params := doExtractParams("/fixed/foo_{var}", 2, "/fixed/foo_barrr", t) if params["var"] != "barrr" { t.Errorf("parameter mismatch var") } } func TestExtractParameters_Suffix(t *testing.T) { params := doExtractParams("/fixed/{var}_foo", 2, "/fixed/barrr_foo", t) if params["var"] != "barrr" { t.Errorf("parameter mismatch var") } } func TestExtractParameters_Mixed(t *testing.T) { params := doExtractParams("/fixed/foo_{var}_bar", 2, "/fixed/foo_barrr_bar", t) if params["var"] != "barrr" { t.Errorf("parameter mismatch var") } } func TestExtractParameters_RegexAndCustomVerb(t *testing.T) { testCase := []struct { route string size int path string checkList map[string]string }{ {"/projects/{projectId}/users/{id:^prefix-}:custom", 4, "/projects/110/users/prefix-userId:custom", map[string]string{ "projectId": "110", "id": "prefix-userId"}}, {"/projects/{projectId}/users/{id:*}", 4, "/projects/110/users/prefix-userId:custom", map[string]string{ "projectId": "110", "id": "prefix-userId:custom"}}, } for idx, v := range testCase { params := doExtractParams(v.route, v.size, v.path, t) for k, val := range v.checkList { if params[k] != val { t.Errorf("[%v] params: %v mismatch, expected: %v, actual: %v", idx, k, v.checkList[k], params[k]) } } } } func doExtractParams(routePath string, size int, urlPath string, t *testing.T) map[string]string { r := Route{Path: routePath} r.postBuild() if len(r.pathParts) != size { t.Fatalf("len not %v %v, but %v", size, r.pathParts, len(r.pathParts)) } pathProcessor := defaultPathProcessor{} return pathProcessor.ExtractParameters(&r, nil, urlPath) } go-restful-3.7.3/request.go000066400000000000000000000103601415314214700156340ustar00rootroot00000000000000package 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 selectedRoute *Route // is nil when no route was matched } 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) } // QueryParameters returns the all the query parameters values by name func (r *Request) QueryParameters(name string) []string { return r.Request.URL.Query()[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 // If no route was matched then return an empty string. func (r Request) SelectedRoutePath() string { if r.selectedRoute == nil { return "" } // skip creating an accessor return r.selectedRoute.Path } // SelectedRoute returns a reader to access the selected Route by the container // Returns nil if no route was matched. func (r Request) SelectedRoute() RouteReader { if r.selectedRoute == nil { return nil } return routeAccessor{route: r.selectedRoute} } go-restful-3.7.3/request_test.go000066400000000000000000000103751415314214700167010ustar00rootroot00000000000000package 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) } } func TestQueryParameters(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} parameters := rreq.QueryParameters("q") if len(parameters) != 2 { t.Fatalf("len(q)!=2 %#v", rreq) } else { if parameters[0] != "foo" || parameters[1] != "bar" { t.Fatalf("invalid content: required [\"foo\" \"bar\", got: %#v", parameters) } } } 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) } } go-restful-3.7.3/response.go000066400000000000000000000241221415314214700160030ustar00rootroot00000000000000package 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 writes the http status and the error string on the response. err can be nil. // Return an error if writing was not successful. func (r *Response) WriteError(httpStatus int, err error) (writeErr error) { r.err = err if err == nil { writeErr = r.WriteErrorString(httpStatus, "") } else { writeErr = r.WriteErrorString(httpStatus, err.Error()) } return writeErr } // 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 } go-restful-3.7.3/response_test.go000066400000000000000000000206071415314214700170460ustar00rootroot00000000000000package 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() != 20 { 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) } } func TestWriteErrorWithNil(t *testing.T) { httpWriter := httptest.NewRecorder() resp := Response{ResponseWriter: httpWriter} resp.WriteError(http.StatusGone, nil) if httpWriter.Code != http.StatusGone { t.Errorf("got %d want %d", httpWriter.Code, http.StatusGone) } } go-restful-3.7.3/route.go000066400000000000000000000124141415314214700153040ustar00rootroot00000000000000package 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" "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 { ExtensionProperties 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 DefaultResponse *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{} // marks a route as deprecated Deprecated bool //Overrides the container.contentEncodingEnabled contentEncodingEnabled *bool // indicate route path has custom verb hasCustomVerb bool // if a request does not include a content-type header then // depending on the method, it may return a 415 Unsupported Media // Must have uppercase HTTP Method names such as GET,HEAD,OPTIONS,... allowedMethodsWithoutContentType []string } // Initialize for Route func (r *Route) postBuild() { r.pathParts = tokenizePath(r.Path) r.hasCustomVerb = hasCustomVerb(r.Path) } // Create Request and Response from their http versions func (r *Route) wrapRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request, pathParams map[string]string) (*Request, *Response) { wrappedRequest := NewRequest(httpRequest) wrappedRequest.pathParameters = pathParams wrappedRequest.selectedRoute = r wrappedResponse := NewResponse(httpWriter) wrappedResponse.requestAccept = httpRequest.Header.Get(HEADER_Accept) wrappedResponse.routeProduces = r.Produces return wrappedRequest, wrappedResponse } func stringTrimSpaceCutset(r rune) bool { return r == ' ' } // Return whether the mimeType matches to what this Route can produce. func (r Route) matchesAccept(mimeTypesWithQuality string) bool { remaining := mimeTypesWithQuality for { var mimeType string if end := strings.Index(remaining, ","); end == -1 { mimeType, remaining = remaining, "" } else { mimeType, remaining = remaining[:end], remaining[end+1:] } if quality := strings.Index(mimeType, ";"); quality != -1 { mimeType = mimeType[:quality] } mimeType = strings.TrimFunc(mimeType, stringTrimSpaceCutset) if mimeType == "*/*" { return true } for _, producibleType := range r.Produces { if producibleType == "*/*" || producibleType == mimeType { return true } } if len(remaining) == 0 { 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 route specifies less or non-idempotent methods then use that if len(r.allowedMethodsWithoutContentType) > 0 { for _, each := range r.allowedMethodsWithoutContentType { if m == each { return true } } } else { if m == "GET" || m == "HEAD" || m == "OPTIONS" || m == "DELETE" || m == "TRACE" { return true } } // proceed with default mimeTypes = MIME_OCTET } remaining := mimeTypes for { var mimeType string if end := strings.Index(remaining, ","); end == -1 { mimeType, remaining = remaining, "" } else { mimeType, remaining = remaining[:end], remaining[end+1:] } if quality := strings.Index(mimeType, ";"); quality != -1 { mimeType = mimeType[:quality] } mimeType = strings.TrimFunc(mimeType, stringTrimSpaceCutset) for _, consumeableType := range r.Consumes { if consumeableType == "*/*" || consumeableType == mimeType { return true } } if len(remaining) == 0 { return false } } } // Tokenize an URL path using the slash separator ; the result does not have empty tokens func tokenizePath(path string) []string { if "/" == path { return nil } return strings.Split(strings.Trim(path, "/"), "/") } // for debugging func (r Route) String() string { return r.Method + " " + r.Path } // EnableContentEncoding (default=false) allows for GZIP or DEFLATE encoding of responses. Overrides the container.contentEncodingEnabled value. func (r *Route) EnableContentEncoding(enabled bool) { r.contentEncodingEnabled = &enabled } go-restful-3.7.3/route_builder.go000066400000000000000000000307411415314214700170150ustar00rootroot00000000000000package 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/v3/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 allowedMethodsWithoutContentType []string // see Route typeNameHandleFunc TypeNameHandleFunction // required // documentation doc string notes string operation string readSample, writeSample interface{} parameters []*Parameter errorMap map[int]ResponseError defaultResponse *ResponseError metadata map[string]interface{} extensions map[string]interface{} deprecated bool contentEncodingEnabled *bool } // 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{}, optionalDescription ...string) *RouteBuilder { fn := b.typeNameHandleFunc if fn == nil { fn = reflectTypeName } typeAsName := fn(sample) description := "" if len(optionalDescription) > 0 { description = optionalDescription[0] } b.readSample = sample bodyParameter := &Parameter{&ParameterData{Name: "body", Description: description}} 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, // this field is deprecated, use default response instead. } // lazy init because there is no NewRouteBuilder (yet) if b.errorMap == nil { b.errorMap = map[int]ResponseError{} } b.errorMap[code] = err return b } // ReturnsWithHeaders is similar to Returns, but can specify response headers func (b *RouteBuilder) ReturnsWithHeaders(code int, message string, model interface{}, headers map[string]Header) *RouteBuilder { b.Returns(code, message, model) err := b.errorMap[code] err.Headers = headers b.errorMap[code] = err return b } // DefaultReturns is a special Returns call that sets the default of the response. func (b *RouteBuilder) DefaultReturns(message string, model interface{}) *RouteBuilder { b.defaultResponse = &ResponseError{ Message: message, Model: model, } 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 } // AddExtension adds or updates a key=value pair to the extensions map. func (b *RouteBuilder) AddExtension(key string, value interface{}) *RouteBuilder { if b.extensions == nil { b.extensions = map[string]interface{}{} } b.extensions[key] = value return b } // Deprecate sets the value of deprecated to true. Deprecated routes have a special UI treatment to warn against use func (b *RouteBuilder) Deprecate() *RouteBuilder { b.deprecated = true return b } // AllowedMethodsWithoutContentType overrides the default list GET,HEAD,OPTIONS,DELETE,TRACE // If a request does not include a content-type header then // depending on the method, it may return a 415 Unsupported Media. // Must have uppercase HTTP Method names such as GET,HEAD,OPTIONS,... func (b *RouteBuilder) AllowedMethodsWithoutContentType(methods []string) *RouteBuilder { b.allowedMethodsWithoutContentType = methods return b } // ResponseError represents a response; not necessarily an error. type ResponseError struct { ExtensionProperties Code int Message string Model interface{} Headers map[string]Header IsDefault bool } // Header describes a header for a response of the API // // For more information: http://goo.gl/8us55a#headerObject type Header struct { *Items Description string } // Items describe swagger simple schemas for headers type Items struct { Type string Format string Items *Items CollectionFormat string Default interface{} } 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 } // ContentEncodingEnabled allows you to override the Containers value for auto-compressing this route response. func (b *RouteBuilder) ContentEncodingEnabled(enabled bool) *RouteBuilder { b.contentEncodingEnabled = &enabled 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("Invalid path:%s because:%v", b.currentPath, err) os.Exit(1) } if b.function == nil { log.Printf("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, DefaultResponse: b.defaultResponse, ReadSample: b.readSample, WriteSample: b.writeSample, Metadata: b.metadata, Deprecated: b.deprecated, contentEncodingEnabled: b.contentEncodingEnabled, allowedMethodsWithoutContentType: b.allowedMethodsWithoutContentType, } route.Extensions = b.extensions 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 } go-restful-3.7.3/route_builder_test.go000066400000000000000000000055171415314214700200570ustar00rootroot00000000000000package 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 := []string{"a"} p.PossibleValues(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().PossibleValues[0] != "a" { t.Error("PossibleValues 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"). AddExtension("x-restful-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 r.Extensions["x-restful-test"] != "test-value" { t.Errorf("Extensions not set") } if r.DefaultResponse == nil { t.Fatal("expected default response") } if r.hasCustomVerb { t.Errorf("hasCustomVerb should not be true") } customVerbRoute := new(RouteBuilder) customVerbRoute.To(dummy) customVerbRoute.Path("/users:init") if !customVerbRoute.Build().hasCustomVerb { t.Errorf("hasCustomVerb should be true") } } 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) } } func TestContentEncodingEnabled(t *testing.T) { b := new(RouteBuilder) b.function = dummy r := b.Build() got := r.contentEncodingEnabled var want *bool // nil if got != want { t.Errorf("got %v want %v (default nil)", got, want) } // true b = new(RouteBuilder) b.function = dummy b.ContentEncodingEnabled(true) r = b.Build() got = r.contentEncodingEnabled if *got != true { t.Errorf("got %v want %v (explicit true)", *got, true) } // true b = new(RouteBuilder) b.function = dummy b.ContentEncodingEnabled(false) r = b.Build() got = r.contentEncodingEnabled if *got != false { t.Errorf("got %v want %v (explicit false)", *got, false) } } go-restful-3.7.3/route_reader.go000066400000000000000000000026261415314214700166320ustar00rootroot00000000000000package restful // Copyright 2021 Ernest Micklei. All rights reserved. // Use of this source code is governed by a license // that can be found in the LICENSE file. type RouteReader interface { Method() string Consumes() []string Path() string Doc() string Notes() string Operation() string ParameterDocs() []*Parameter // Returns a copy Metadata() map[string]interface{} Deprecated() bool } type routeAccessor struct { route *Route } func (r routeAccessor) Method() string { return r.route.Method } func (r routeAccessor) Consumes() []string { return r.route.Consumes[:] } func (r routeAccessor) Path() string { return r.route.Path } func (r routeAccessor) Doc() string { return r.route.Doc } func (r routeAccessor) Notes() string { return r.route.Notes } func (r routeAccessor) Operation() string { return r.route.Operation } func (r routeAccessor) ParameterDocs() []*Parameter { return r.route.ParameterDocs[:] } // Returns a copy func (r routeAccessor) Metadata() map[string]interface{} { return copyMap(r.route.Metadata) } func (r routeAccessor) Deprecated() bool { return r.route.Deprecated } // https://stackoverflow.com/questions/23057785/how-to-copy-a-map func copyMap(m map[string]interface{}) map[string]interface{} { cp := make(map[string]interface{}) for k, v := range m { vm, ok := v.(map[string]interface{}) if ok { cp[k] = copyMap(vm) } else { cp[k] = v } } return cp } go-restful-3.7.3/route_test.go000066400000000000000000000037661415314214700163550ustar00rootroot00000000000000package restful import ( "testing" ) // accept should match produces func TestMatchesAcceptPlainTextWhenProducePlainTextAsLast(t *testing.T) { r := Route{Produces: []string{"application/json", "text/plain"}} if !r.matchesAccept("text/plain") { t.Errorf("accept should match text/plain") } } // accept should match produces func TestMatchesAcceptStar(t *testing.T) { r := Route{Produces: []string{"application/xml"}} if !r.matchesAccept("*/*") { t.Errorf("accept should match star") } } // accept should match produces func TestMatchesAcceptIE(t *testing.T) { r := Route{Produces: []string{"application/xml"}} if !r.matchesAccept("text/html, application/xhtml+xml, */*") { t.Errorf("accept should match star") } } // accept should match produces func TestMatchesAcceptXml(t *testing.T) { r := Route{Produces: []string{"application/xml"}} if r.matchesAccept("application/json") { t.Errorf("accept should not match json") } if !r.matchesAccept("application/xml") { t.Errorf("accept should match xml") } } // accept should match produces func TestMatchesAcceptAny(t *testing.T) { r := Route{Produces: []string{"*/*"}} if !r.matchesAccept("application/json") { t.Errorf("accept should match json") } if !r.matchesAccept("application/xml") { t.Errorf("accept should match xml") } } // content type should match consumes func TestMatchesContentTypeXml(t *testing.T) { r := Route{Consumes: []string{"application/xml"}} if r.matchesContentType("application/json") { t.Errorf("accept should not match json") } if !r.matchesContentType("application/xml") { t.Errorf("accept should match xml") } } // content type should match consumes func TestMatchesContentTypeCharsetInformation(t *testing.T) { r := Route{Consumes: []string{"application/json"}} if !r.matchesContentType("application/json; charset=UTF-8") { t.Errorf("matchesContentType should ignore charset information") } } func TestTokenizePath(t *testing.T) { if len(tokenizePath("/")) != 0 { t.Errorf("not empty path tokens") } } go-restful-3.7.3/router.go000066400000000000000000000013761415314214700154730ustar00rootroot00000000000000package 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" // A RouteSelector finds the best matching Route given the input HTTP Request // RouteSelectors can optionally also implement the PathProcessor interface to also calculate the // path parameters after the route has been selected. type RouteSelector interface { // SelectRoute finds a Route given the input HTTP Request and a list of WebServices. // It returns a selected Route and its containing WebService or an error indicating // a problem. SelectRoute( webServices []*WebService, httpRequest *http.Request) (selectedService *WebService, selected *Route, err error) } go-restful-3.7.3/service_error.go000066400000000000000000000017241415314214700170210ustar00rootroot00000000000000package 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" "net/http" ) // ServiceError is a transport object to pass information about a non-Http error occurred in a WebService while processing a request. type ServiceError struct { Code int Message string Header http.Header } // NewError returns a ServiceError using the code and reason func NewError(code int, message string) ServiceError { return ServiceError{Code: code, Message: message} } // NewErrorWithHeader returns a ServiceError using the code, reason and header func NewErrorWithHeader(code int, message string, header http.Header) ServiceError { return ServiceError{Code: code, Message: message, Header: header} } // Error returns a text representation of the service error func (s ServiceError) Error() string { return fmt.Sprintf("[ServiceError:%v] %v", s.Code, s.Message) } go-restful-3.7.3/tracer_test.go000066400000000000000000000004321415314214700164620ustar00rootroot00000000000000package restful import "testing" // Use like this: // // TraceLogger(testLogger{t}) type testLogger struct { t *testing.T } func (l testLogger) Print(v ...interface{}) { l.t.Log(v...) } func (l testLogger) Printf(format string, v ...interface{}) { l.t.Logf(format, v...) } go-restful-3.7.3/web_service.go000066400000000000000000000243001415314214700164400ustar00rootroot00000000000000package restful import ( "errors" "os" "reflect" "sync" "github.com/emicklei/go-restful/v3/log" ) // 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. // WebService holds a collection of Route values that bind a Http Method + URL Path to a function. type WebService struct { rootPath string pathExpr *pathExpression // cached compilation of rootPath as RegExp routes []Route produces []string consumes []string pathParameters []*Parameter filters []FilterFunction documentation string apiVersion string typeNameHandleFunc TypeNameHandleFunction dynamicRoutes bool // protects 'routes' if dynamic routes are enabled routesLock sync.RWMutex } func (w *WebService) SetDynamicRoutes(enable bool) { w.dynamicRoutes = enable } // TypeNameHandleFunction declares functions that can handle translating the name of a sample object // into the restful documentation for the service. type TypeNameHandleFunction func(sample interface{}) string // TypeNameHandler sets the function that will convert types to strings in the parameter // and model definitions. If not set, the web service will invoke // reflect.TypeOf(object).String(). func (w *WebService) TypeNameHandler(handler TypeNameHandleFunction) *WebService { w.typeNameHandleFunc = handler return w } // reflectTypeName is the default TypeNameHandleFunction and for a given object // returns the name that Go identifies it with (e.g. "string" or "v1.Object") via // the reflection API. func reflectTypeName(sample interface{}) string { return reflect.TypeOf(sample).String() } // compilePathExpression ensures that the path is compiled into a RegEx for those routers that need it. func (w *WebService) compilePathExpression() { compiled, err := newPathExpression(w.rootPath) if err != nil { log.Printf("invalid path:%s because:%v", w.rootPath, err) os.Exit(1) } w.pathExpr = compiled } // ApiVersion sets the API version for documentation purposes. func (w *WebService) ApiVersion(apiVersion string) *WebService { w.apiVersion = apiVersion return w } // Version returns the API version for documentation purposes. func (w *WebService) Version() string { return w.apiVersion } // Path specifies the root URL template path of the WebService. // All Routes will be relative to this path. func (w *WebService) Path(root string) *WebService { w.rootPath = root if len(w.rootPath) == 0 { w.rootPath = "/" } w.compilePathExpression() return w } // Param adds a PathParameter to document parameters used in the root path. func (w *WebService) Param(parameter *Parameter) *WebService { if w.pathParameters == nil { w.pathParameters = []*Parameter{} } w.pathParameters = append(w.pathParameters, parameter) return w } // PathParameter creates a new Parameter of kind Path for documentation purposes. // It is initialized as required with string as its DataType. func (w *WebService) PathParameter(name, description string) *Parameter { return PathParameter(name, description) } // PathParameter creates a new Parameter of kind Path for documentation purposes. // It is initialized as required with string as its DataType. func PathParameter(name, description string) *Parameter { p := &Parameter{&ParameterData{Name: name, Description: description, Required: true, DataType: "string"}} p.bePath() return p } // QueryParameter creates a new Parameter of kind Query for documentation purposes. // It is initialized as not required with string as its DataType. func (w *WebService) QueryParameter(name, description string) *Parameter { return QueryParameter(name, description) } // QueryParameter creates a new Parameter of kind Query for documentation purposes. // It is initialized as not required with string as its DataType. func QueryParameter(name, description string) *Parameter { p := &Parameter{&ParameterData{Name: name, Description: description, Required: false, DataType: "string", CollectionFormat: CollectionFormatCSV.String()}} p.beQuery() return p } // BodyParameter creates a new Parameter of kind Body for documentation purposes. // It is initialized as required without a DataType. func (w *WebService) BodyParameter(name, description string) *Parameter { return BodyParameter(name, description) } // BodyParameter creates a new Parameter of kind Body for documentation purposes. // It is initialized as required without a DataType. func BodyParameter(name, description string) *Parameter { p := &Parameter{&ParameterData{Name: name, Description: description, Required: true}} p.beBody() return p } // HeaderParameter creates a new Parameter of kind (Http) Header for documentation purposes. // It is initialized as not required with string as its DataType. func (w *WebService) HeaderParameter(name, description string) *Parameter { return HeaderParameter(name, description) } // HeaderParameter creates a new Parameter of kind (Http) Header for documentation purposes. // It is initialized as not required with string as its DataType. func HeaderParameter(name, description string) *Parameter { p := &Parameter{&ParameterData{Name: name, Description: description, Required: false, DataType: "string"}} p.beHeader() return p } // FormParameter creates a new Parameter of kind Form (using application/x-www-form-urlencoded) for documentation purposes. // It is initialized as required with string as its DataType. func (w *WebService) FormParameter(name, description string) *Parameter { return FormParameter(name, description) } // FormParameter creates a new Parameter of kind Form (using application/x-www-form-urlencoded) for documentation purposes. // It is initialized as required with string as its DataType. func FormParameter(name, description string) *Parameter { p := &Parameter{&ParameterData{Name: name, Description: description, Required: false, DataType: "string"}} p.beForm() return p } // Route creates a new Route using the RouteBuilder and add to the ordered list of Routes. func (w *WebService) Route(builder *RouteBuilder) *WebService { w.routesLock.Lock() defer w.routesLock.Unlock() builder.copyDefaults(w.produces, w.consumes) w.routes = append(w.routes, builder.Build()) return w } // RemoveRoute removes the specified route, looks for something that matches 'path' and 'method' func (w *WebService) RemoveRoute(path, method string) error { if !w.dynamicRoutes { return errors.New("dynamic routes are not enabled.") } w.routesLock.Lock() defer w.routesLock.Unlock() newRoutes := []Route{} for _, route := range w.routes { if route.Method == method && route.Path == path { continue } newRoutes = append(newRoutes, route) } w.routes = newRoutes return nil } // Method creates a new RouteBuilder and initialize its http method func (w *WebService) Method(httpMethod string) *RouteBuilder { return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method(httpMethod) } // Produces specifies that this WebService can produce one or more MIME types. // Http requests must have one of these values set for the Accept header. func (w *WebService) Produces(contentTypes ...string) *WebService { w.produces = contentTypes return w } // Consumes specifies that this WebService can consume one or more MIME types. // Http requests must have one of these values set for the Content-Type header. func (w *WebService) Consumes(accepts ...string) *WebService { w.consumes = accepts return w } // Routes returns the Routes associated with this WebService func (w *WebService) Routes() []Route { if !w.dynamicRoutes { return w.routes } // Make a copy of the array to prevent concurrency problems w.routesLock.RLock() defer w.routesLock.RUnlock() result := make([]Route, len(w.routes)) for ix := range w.routes { result[ix] = w.routes[ix] } return result } // RootPath returns the RootPath associated with this WebService. Default "/" func (w *WebService) RootPath() string { return w.rootPath } // PathParameters return the path parameter names for (shared among its Routes) func (w *WebService) PathParameters() []*Parameter { return w.pathParameters } // Filter adds a filter function to the chain of filters applicable to all its Routes func (w *WebService) Filter(filter FilterFunction) *WebService { w.filters = append(w.filters, filter) return w } // Doc is used to set the documentation of this service. func (w *WebService) Doc(plainText string) *WebService { w.documentation = plainText return w } // Documentation returns it. func (w *WebService) Documentation() string { return w.documentation } /* Convenience methods */ // HEAD is a shortcut for .Method("HEAD").Path(subPath) func (w *WebService) HEAD(subPath string) *RouteBuilder { return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("HEAD").Path(subPath) } // GET is a shortcut for .Method("GET").Path(subPath) func (w *WebService) GET(subPath string) *RouteBuilder { return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("GET").Path(subPath) } // POST is a shortcut for .Method("POST").Path(subPath) func (w *WebService) POST(subPath string) *RouteBuilder { return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("POST").Path(subPath) } // PUT is a shortcut for .Method("PUT").Path(subPath) func (w *WebService) PUT(subPath string) *RouteBuilder { return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("PUT").Path(subPath) } // PATCH is a shortcut for .Method("PATCH").Path(subPath) func (w *WebService) PATCH(subPath string) *RouteBuilder { return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("PATCH").Path(subPath) } // DELETE is a shortcut for .Method("DELETE").Path(subPath) func (w *WebService) DELETE(subPath string) *RouteBuilder { return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("DELETE").Path(subPath) } // OPTIONS is a shortcut for .Method("OPTIONS").Path(subPath) func (w *WebService) OPTIONS(subPath string) *RouteBuilder { return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("OPTIONS").Path(subPath) } go-restful-3.7.3/web_service_container.go000066400000000000000000000023531415314214700205060ustar00rootroot00000000000000package 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" ) // DefaultContainer is a restful.Container that uses http.DefaultServeMux var DefaultContainer *Container func init() { DefaultContainer = NewContainer() DefaultContainer.ServeMux = http.DefaultServeMux } // If set the true then panics will not be caught to return HTTP 500. // In that case, Route functions are responsible for handling any error situation. // Default value is false = recover from panics. This has performance implications. // OBSOLETE ; use restful.DefaultContainer.DoNotRecover(true) var DoNotRecover = false // Add registers a new WebService add it to the DefaultContainer. func Add(service *WebService) { DefaultContainer.Add(service) } // Filter appends a container FilterFunction from the DefaultContainer. // These are called before dispatching a http.Request to a WebService. func Filter(filter FilterFunction) { DefaultContainer.Filter(filter) } // RegisteredWebServices returns the collections of WebServices from the DefaultContainer func RegisteredWebServices() []*WebService { return DefaultContainer.RegisteredWebServices() } go-restful-3.7.3/web_service_test.go000066400000000000000000000272521415314214700175100ustar00rootroot00000000000000package restful import ( "io/ioutil" "net/http" "net/http/httptest" "testing" ) const ( pathGetFriends = "/get/{userId}/friends" ) func TestParameter(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() ws := new(WebService) ws.Param(p) if ws.pathParameters[0].Data().Name != "name" { t.Error("path parameter (or name) invalid") } } func TestWebService_CanCreateParameterKinds(t *testing.T) { ws := new(WebService) if ws.BodyParameter("b", "b").Kind() != BodyParameterKind { t.Error("body parameter expected") } if ws.PathParameter("p", "p").Kind() != PathParameterKind { t.Error("path parameter expected") } if ws.QueryParameter("q", "q").Kind() != QueryParameterKind { t.Error("query parameter expected") } } func TestCapturePanic(t *testing.T) { tearDown() Add(newPanicingService()) httpRequest, _ := http.NewRequest("GET", "http://here.com/fire", nil) httpRequest.Header.Set("Accept", "*/*") httpWriter := httptest.NewRecorder() // override the default here DefaultContainer.DoNotRecover(false) DefaultContainer.dispatch(httpWriter, httpRequest) if 500 != httpWriter.Code { t.Error("500 expected on fire") } } func TestCapturePanicWithEncoded(t *testing.T) { tearDown() Add(newPanicingService()) DefaultContainer.EnableContentEncoding(true) httpRequest, _ := http.NewRequest("GET", "http://here.com/fire", nil) httpRequest.Header.Set("Accept", "*/*") httpRequest.Header.Set("Accept-Encoding", "gzip") httpWriter := httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if 500 != httpWriter.Code { t.Error("500 expected on fire, got", httpWriter.Code) } } func TestNotFound(t *testing.T) { tearDown() httpRequest, _ := http.NewRequest("GET", "http://here.com/missing", nil) httpRequest.Header.Set("Accept", "*/*") httpWriter := httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if 404 != httpWriter.Code { t.Error("404 expected on missing") } } func TestMethodNotAllowed(t *testing.T) { tearDown() Add(newGetOnlyService()) httpRequest, _ := http.NewRequest("POST", "http://here.com/get", nil) httpRequest.Header.Set("Accept", "*/*") httpWriter := httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if 405 != httpWriter.Code { t.Error("405 expected method not allowed") } } func TestMethodNotAllowed_Issue435(t *testing.T) { tearDown() Add(newPutGetDeleteWithDuplicateService()) httpRequest, _ := http.NewRequest("POST", "http://here/thing", nil) httpRequest.Header.Set("Accept", "*/*") httpWriter := httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if 405 != httpWriter.Code { t.Error("405 expected method not allowed") } if "PUT, GET, DELETE" != httpWriter.Header().Get("Allow") { t.Error("405 expected Allowed header got ", httpWriter.Header()) } } func TestNotAcceptable_Issue434(t *testing.T) { tearDown() Add(newGetPlainTextOrJsonService()) httpRequest, _ := http.NewRequest("GET", "http://here.com/get", nil) httpRequest.Header.Set("Accept", "application/toml") httpWriter := httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if 406 != httpWriter.Code { t.Error("406 expected not acceptable", httpWriter.Code) } expected := `406: Not Acceptable Available representations: text/plain, application/json` body, _ := ioutil.ReadAll(httpWriter.Body) if expected != string(body) { t.Errorf("Expected body:\n%s\ngot:\n%s\n", expected, string(body)) } } func TestSelectedRoutePath_Issue100(t *testing.T) { tearDown() Add(newSelectedRouteTestingService()) httpRequest, _ := http.NewRequest("GET", "http://here.com/get/232452/friends", nil) httpRequest.Header.Set("Accept", "*/*") httpWriter := httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if http.StatusOK != httpWriter.Code { t.Error(http.StatusOK, "expected,", httpWriter.Code, "received.") } } func TestContentType415_Issue170(t *testing.T) { tearDown() Add(newGetOnlyJsonOnlyService()) httpRequest, _ := http.NewRequest("GET", "http://here.com/get", nil) httpWriter := httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if 200 != httpWriter.Code { t.Errorf("Expected 200, got %d", httpWriter.Code) } } func TestNoContentTypePOST(t *testing.T) { tearDown() Add(newPostNoConsumesService()) httpRequest, _ := http.NewRequest("POST", "http://here.com/post", nil) httpWriter := httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if 204 != httpWriter.Code { t.Errorf("Expected 204, got %d", httpWriter.Code) } } func TestContentType415_POST_Issue170(t *testing.T) { tearDown() Add(newPostOnlyJsonOnlyService()) httpRequest, _ := http.NewRequest("POST", "http://here.com/post", nil) httpRequest.Header.Set("Content-Type", "application/json") httpWriter := httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if 200 != httpWriter.Code { t.Errorf("Expected 200, got %d", httpWriter.Code) } } // go test -v -test.run TestContentType406PlainJson ...restful func TestContentType406PlainJson(t *testing.T) { tearDown() TraceLogger(testLogger{t}) Add(newGetPlainTextOrJsonService()) httpRequest, _ := http.NewRequest("GET", "http://here.com/get", nil) httpRequest.Header.Set("Accept", "text/plain") httpWriter := httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if got, want := httpWriter.Code, 200; got != want { t.Errorf("got %v, want %v", got, want) } } func TestRemoveRoute(t *testing.T) { tearDown() TraceLogger(testLogger{t}) ws := newGetPlainTextOrJsonService() Add(ws) httpRequest, _ := http.NewRequest("GET", "http://here.com/get", nil) httpRequest.Header.Set("Accept", "text/plain") httpWriter := httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if got, want := httpWriter.Code, 200; got != want { t.Errorf("got %v, want %v", got, want) } // dynamic apis are disabled, should error and do nothing if err := ws.RemoveRoute("/get", "GET"); err == nil { t.Error("unexpected non-error") } httpWriter = httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if got, want := httpWriter.Code, 200; got != want { t.Errorf("got %v, want %v", got, want) } ws.SetDynamicRoutes(true) if err := ws.RemoveRoute("/get", "GET"); err != nil { t.Errorf("unexpected error %v", err) } httpWriter = httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if got, want := httpWriter.Code, 404; got != want { t.Errorf("got %v, want %v", got, want) } } func TestRemoveLastRoute(t *testing.T) { tearDown() TraceLogger(testLogger{t}) ws := newGetPlainTextOrJsonServiceMultiRoute() Add(ws) httpRequest, _ := http.NewRequest("GET", "http://here.com/get", nil) httpRequest.Header.Set("Accept", "text/plain") httpWriter := httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if got, want := httpWriter.Code, 200; got != want { t.Errorf("got %v, want %v", got, want) } // dynamic apis are disabled, should error and do nothing if err := ws.RemoveRoute("/get", "GET"); err == nil { t.Error("unexpected non-error") } httpWriter = httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if got, want := httpWriter.Code, 200; got != want { t.Errorf("got %v, want %v", got, want) } ws.SetDynamicRoutes(true) if err := ws.RemoveRoute("/get", "GET"); err != nil { t.Errorf("unexpected error %v", err) } httpWriter = httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if got, want := httpWriter.Code, 404; got != want { t.Errorf("got %v, want %v", got, want) } } // go test -v -test.run TestContentTypeOctet_Issue170 ...restful func TestContentTypeOctet_Issue170(t *testing.T) { tearDown() Add(newGetConsumingOctetStreamService()) // with content-type httpRequest, _ := http.NewRequest("GET", "http://here.com/get", nil) httpRequest.Header.Set("Content-Type", MIME_OCTET) httpWriter := httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if 200 != httpWriter.Code { t.Errorf("Expected 200, got %d", httpWriter.Code) } // without content-type httpRequest, _ = http.NewRequest("GET", "http://here.com/get", nil) httpWriter = httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if 200 != httpWriter.Code { t.Errorf("Expected 200, got %d", httpWriter.Code) } } type exampleBody struct{} func TestParameterDataTypeDefaults(t *testing.T) { tearDown() ws := new(WebService) route := ws.POST("/post").Reads(&exampleBody{}, "") if route.parameters[0].data.DataType != "*restful.exampleBody" { t.Errorf("body parameter incorrect name: %#v", route.parameters[0].data) } } func TestParameterDataTypeCustomization(t *testing.T) { tearDown() ws := new(WebService) ws.TypeNameHandler(func(sample interface{}) string { return "my.custom.type.name" }) route := ws.POST("/post").Reads(&exampleBody{}, "") if route.parameters[0].data.DataType != "my.custom.type.name" { t.Errorf("body parameter incorrect name: %#v", route.parameters[0].data) } } func TestOptionsShortcut(t *testing.T) { tearDown() ws := new(WebService).Path("") ws.Route(ws.OPTIONS("/options").To(return200)) Add(ws) httpRequest, _ := http.NewRequest("OPTIONS", "http://here.com/options", nil) httpWriter := httptest.NewRecorder() DefaultContainer.dispatch(httpWriter, httpRequest) if got, want := httpWriter.Code, 200; got != want { t.Errorf("got %v, want %v", got, want) } } func newPanicingService() *WebService { ws := new(WebService).Path("") ws.Route(ws.GET("/fire").To(doPanic)) return ws } func newGetOnlyService() *WebService { ws := new(WebService).Path("") ws.Route(ws.GET("/get").To(doPanic)) return ws } func newPutGetDeleteWithDuplicateService() *WebService { ws := new(WebService).Path("") ws.Route(ws.PUT("/thing").To(doPanic)) ws.Route(ws.GET("/thing").To(doPanic)) ws.Route(ws.DELETE("/thing").To(doPanic)) ws.Route(ws.GET("/thing").To(doPanic)) return ws } func newPostOnlyJsonOnlyService() *WebService { ws := new(WebService).Path("") ws.Consumes("application/json") ws.Route(ws.POST("/post").To(doNothing)) return ws } func newGetOnlyJsonOnlyService() *WebService { ws := new(WebService).Path("") ws.Consumes("application/json") ws.Route(ws.GET("/get").To(doNothing)) return ws } func newGetPlainTextOrJsonService() *WebService { ws := new(WebService).Path("") ws.Produces("text/plain", "application/json") ws.Route(ws.GET("/get").To(doNothing)) return ws } func newGetPlainTextOrJsonServiceMultiRoute() *WebService { ws := new(WebService).Path("") ws.Produces("text/plain", "application/json") ws.Route(ws.GET("/get").To(doNothing)) ws.Route(ws.GET("/status").To(doNothing)) return ws } func newGetConsumingOctetStreamService() *WebService { ws := new(WebService).Path("") ws.Consumes("application/octet-stream") ws.Route(ws.GET("/get").To(doNothing)) return ws } func newPostNoConsumesService() *WebService { ws := new(WebService).Path("") ws.Route(ws.POST("/post").To(return204)) return ws } func newSelectedRouteTestingService() *WebService { ws := new(WebService).Path("") ws.Route(ws.GET(pathGetFriends).To(selectedRouteChecker)) return ws } func selectedRouteChecker(req *Request, resp *Response) { if req.SelectedRoute() == nil { resp.InternalServerError() return } if req.SelectedRoutePath() != pathGetFriends { resp.InternalServerError() } } func doPanic(req *Request, resp *Response) { println("lightning...") panic("fire") } func doNothing(req *Request, resp *Response) { } func return204(req *Request, resp *Response) { resp.WriteHeader(204) } func return200(req *Request, resp *Response) { resp.WriteHeader(200) }