pax_global_header00006660000000000000000000000064124651311050014510gustar00rootroot0000000000000052 comment=89af920d613f1e3f771f6460b2629632e7a36ae9 go-restful-1.1.3/000077500000000000000000000000001246513110500136015ustar00rootroot00000000000000go-restful-1.1.3/.gitignore000066400000000000000000000016121246513110500155710ustar00rootroot00000000000000# 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 go-restful-1.1.3/CHANGES.md000066400000000000000000000107031246513110500151740ustar00rootroot00000000000000Change history of go-restful = 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-1.1.3/LICENSE000066400000000000000000000020631246513110500146070ustar00rootroot00000000000000Copyright (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-1.1.3/README.md000066400000000000000000000075721246513110500150730ustar00rootroot00000000000000go-restful ========== package for building REST-style Web Services using Google Go REST asks developers to use HTTP methods explicitly and in a way that's consistent with the protocol definition. This basic REST design principle establishes a one-to-one mapping between create, read, update, and delete (CRUD) operations and HTTP methods. According to this mapping: - GET = Retrieve a representation of a resource - POST = Create if you are sending content to the server to create a subordinate of the specified resource collection, using some server-side algorithm. - PUT = Create if you are sending the full content of the specified resource (URI). - PUT = Update if you are updating the full content of the specified resource. - DELETE = Delete if you are requesting the server to delete the resource - PATCH = Update partial content of a resource - OPTIONS = Get information about the communication options for the request URI ### Example ```Go ws := new(restful.WebService) ws. Path("/users"). Consumes(restful.MIME_XML, restful.MIME_JSON). Produces(restful.MIME_JSON, restful.MIME_XML) ws.Route(ws.GET("/{user-id}").To(u.findUser). Doc("get a user"). Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")). Writes(User{})) ... func (u UserResource) findUser(request *restful.Request, response *restful.Response) { id := request.PathParameter("user-id") ... } ``` [Full API of a UserResource](https://github.com/emicklei/go-restful/tree/master/examples/restful-user-resource.go) ### Features - Routes for request → function mapping with path parameter (e.g. {id}) support - Configurable router: - Routing algorithm after [JSR311](http://jsr311.java.net/nonav/releases/1.1/spec/spec.html) that is implemented using (but doest **not** accept) regular expressions (See RouterJSR311 which is used by default) - Fast routing algorithm that allows static elements, regular expressions and dynamic parameters in the URL path (e.g. /meetings/{id} or /static/{subpath:*}, See CurlyRouter) - Request API for reading structs from JSON/XML and accesing parameters (path,query,header) - Response API for writing structs to JSON/XML and setting headers - 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 responses - Automatic responses on OPTIONS (using a filter) - Automatic CORS request handling (using a filter) - API declaration for Swagger UI (see swagger package) - Panic recovery to produce HTTP 500, customizable using RecoverHandler(...) ### Resources - [Documentation on godoc.org](http://godoc.org/github.com/emicklei/go-restful) - [Code examples](https://github.com/emicklei/go-restful/tree/master/examples) - [Example posted on blog](http://ernestmicklei.com/2012/11/24/go-restful-first-working-example/) - [Design explained on blog](http://ernestmicklei.com/2012/11/11/go-restful-api-design/) - [sourcegraph](https://sourcegraph.com/github.com/emicklei/go-restful) - [gopkg.in](https://gopkg.in/emicklei/go-restful.v1) - [showcase: Mora - MongoDB REST Api server](https://github.com/emicklei/mora) [![Build Status](https://drone.io/github.com/emicklei/go-restful/status.png)](https://drone.io/github.com/emicklei/go-restful/latest)[![library users](https://sourcegraph.com/api/repos/github.com/emicklei/go-restful/badges/library-users.png)](https://sourcegraph.com/github.com/emicklei/go-restful) [![authors](https://sourcegraph.com/api/repos/github.com/emicklei/go-restful/badges/authors.png)](https://sourcegraph.com/github.com/emicklei/go-restful) [![xrefs](https://sourcegraph.com/api/repos/github.com/emicklei/go-restful/badges/xrefs.png)](https://sourcegraph.com/github.com/emicklei/go-restful) (c) 2012 - 2014, http://ernestmicklei.com. MIT License Type ```git shortlog -s``` for a full list of contributors.go-restful-1.1.3/Srcfile000066400000000000000000000000331246513110500151070ustar00rootroot00000000000000{"SkipDirs": ["examples"]} go-restful-1.1.3/bench_curly_test.go000066400000000000000000000023671246513110500174740ustar00rootroot00000000000000package restful import ( "fmt" "net/http" "net/http/httptest" "testing" ) func setupCurly(container *Container) []string { wsCount := 26 rtCount := 26 urisCurly := []string{} container.Router(CurlyRouter{}) for i := 0; i < wsCount; i++ { root := fmt.Sprintf("/%s/{%s}/", string(i+97), string(i+97)) ws := new(WebService).Path(root) for j := 0; j < rtCount; j++ { sub := fmt.Sprintf("/%s2/{%s2}", string(j+97), string(j+97)) ws.Route(ws.GET(sub).Consumes("application/xml").Produces("application/xml").To(echoCurly)) } container.Add(ws) for _, each := range ws.Routes() { urisCurly = append(urisCurly, "http://bench.com"+each.Path) } } return urisCurly } func echoCurly(req *Request, resp *Response) {} func BenchmarkManyCurly(b *testing.B) { container := NewContainer() urisCurly := setupCurly(container) b.ResetTimer() for t := 0; t < b.N; t++ { for r := 0; r < 1000; r++ { for _, each := range urisCurly { sendNoReturnTo(each, container, t) } } } } func sendNoReturnTo(address string, container *Container, t int) { httpRequest, _ := http.NewRequest("GET", address, nil) httpRequest.Header.Set("Accept", "application/xml") httpWriter := httptest.NewRecorder() container.dispatch(httpWriter, httpRequest) } go-restful-1.1.3/bench_test.go000066400000000000000000000014771246513110500162570ustar00rootroot00000000000000package restful import ( "fmt" "io" "testing" ) var uris = []string{} func setup(container *Container) { wsCount := 26 rtCount := 26 for i := 0; i < wsCount; i++ { root := fmt.Sprintf("/%s/{%s}/", string(i+97), string(i+97)) ws := new(WebService).Path(root) for j := 0; j < rtCount; j++ { sub := fmt.Sprintf("/%s2/{%s2}", string(j+97), string(j+97)) ws.Route(ws.GET(sub).To(echo)) } container.Add(ws) for _, each := range ws.Routes() { uris = append(uris, "http://bench.com"+each.Path) } } } func echo(req *Request, resp *Response) { io.WriteString(resp.ResponseWriter, "echo") } func BenchmarkMany(b *testing.B) { container := NewContainer() setup(container) b.ResetTimer() for t := 0; t < b.N; t++ { for _, each := range uris { // println(each) sendItTo(each, container) } } } go-restful-1.1.3/bench_test.sh000066400000000000000000000005411246513110500162530ustar00rootroot00000000000000#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-1.1.3/compress.go000066400000000000000000000051401246513110500157630ustar00rootroot00000000000000package 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/gzip" "compress/zlib" "errors" "io" "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 } // 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) { 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() { c.compressor.Close() } // 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 { c.compressor, err = gzip.NewWriterLevel(httpWriter, gzip.BestSpeed) if err != nil { return nil, err } } else if ENCODING_DEFLATE == encoding { c.compressor, err = zlib.NewWriterLevel(httpWriter, zlib.BestSpeed) if err != nil { return nil, err } } else { return nil, errors.New("Unknown encoding:" + encoding) } return c, err } go-restful-1.1.3/compress_test.go000066400000000000000000000024661246513110500170320ustar00rootroot00000000000000package restful import ( "net/http" "net/http/httptest" "testing" ) 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") } } 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") } } go-restful-1.1.3/constants.go000066400000000000000000000025311246513110500161450ustar00rootroot00000000000000package 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() 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-1.1.3/container.go000066400000000000000000000235751246513110500161260ustar00rootroot00000000000000package 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" "log" "net/http" "runtime" "strings" ) // 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 { webServices []*WebService ServeMux *http.ServeMux isRegisteredOnRoot bool containerFilters []FilterFunction doNotRecover bool // default is false recoverHandleFunc RecoverHandleFunction router RouteSelector // default is a RouterJSR311, CurlyRouter is the faster alternative contentEncodingEnabled bool // default is false } // NewContainer creates a new Container using a new ServeMux and default router (RouterJSR311) func NewContainer() *Container { return &Container{ webServices: []*WebService{}, ServeMux: http.NewServeMux(), isRegisteredOnRoot: false, containerFilters: []FilterFunction{}, doNotRecover: false, recoverHandleFunc: logStackOnRecover, router: RouterJSR311{}, 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 } // 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 false = recover from panics. This has performance implications. func (c *Container) DoNotRecover(doNot bool) { c.doNotRecover = doNot } // Router changes the default Router (currently RouterJSR311) 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 panic in that case. func (c *Container) Add(service *WebService) *Container { // If registered on root then no additional specific mapping is needed if !c.isRegisteredOnRoot { pattern := c.fixedPrefixPath(service.RootPath()) // check if root path registration is needed if "/" == pattern || "" == pattern { c.ServeMux.HandleFunc("/", c.dispatch) c.isRegisteredOnRoot = true } else { // detect if registration already exists alreadyMapped := false for _, each := range c.webServices { if each.RootPath() == service.RootPath() { alreadyMapped = true break } } if !alreadyMapped { c.ServeMux.HandleFunc(pattern, c.dispatch) if !strings.HasSuffix(pattern, "/") { c.ServeMux.HandleFunc(pattern+"/", c.dispatch) } } } } // cannot have duplicate root paths for _, each := range c.webServices { if each.RootPath() == service.RootPath() { log.Fatalf("[restful] WebService with duplicate root path detected:['%v']", each) } } // if rootPath was not set then lazy initialize it if len(service.rootPath) == 0 { service.Path("/") } c.webServices = append(c.webServices, service) return c } // logStackOnRecover is the default RecoverHandleFunction and is called // when DoNotRecover is false and the recoverHandleFunc is not set for the container. // Default implementation logs the stacktrace and writes the stacktrace on the response. // This may be a security issue as it exposes sourcecode information. func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter) { var buffer bytes.Buffer buffer.WriteString(fmt.Sprintf("[restful] recover from panic situation: - %v\r\n", panicReason)) for i := 2; ; i += 1 { _, file, line, ok := runtime.Caller(i) if !ok { break } buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line)) } log.Println(buffer.String()) httpWriter.WriteHeader(http.StatusInternalServerError) httpWriter.Write(buffer.Bytes()) } // Dispatch the incoming Http Request to a matching WebService. func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) { // Instal panic recovery unless told otherwise if !c.doNotRecover { // catch all for 500 response defer func() { if r := recover(); r != nil { c.recoverHandleFunc(r, httpWriter) return } }() } // Install closing the request body (if any) defer func() { if nil != httpRequest.Body { httpRequest.Body.Close() } }() // Detect if compression is needed // assume without compression, test for override writer := httpWriter if c.contentEncodingEnabled { doCompress, encoding := wantsCompressedResponse(httpRequest) if doCompress { var err error writer, err = NewCompressingResponseWriter(httpWriter, encoding) if err != nil { log.Println("[restful] unable to install compressor:", err) httpWriter.WriteHeader(http.StatusInternalServerError) return } defer func() { writer.(*CompressingResponseWriter).Close() }() } } // Find best match Route ; err is non nil if no match was found webService, route, err := c.router.SelectRoute( c.webServices, httpRequest) if err != nil { // a non-200 response has already been written // run container filters anyway ; they should not touch the response... chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) { switch err.(type) { case ServiceError: ser := err.(ServiceError) resp.WriteErrorString(ser.Code, ser.Message) } // TODO }} chain.ProcessFilter(NewRequest(httpRequest), NewResponse(writer)) return } wrappedRequest, wrappedResponse := route.wrapRequestResponse(writer, httpRequest) // pass through filters (if any) if len(c.containerFilters)+len(webService.filters)+len(route.Filters) > 0 { // compose filter chain allFilters := []FilterFunction{} allFilters = append(allFilters, c.containerFilters...) allFilters = append(allFilters, webService.filters...) allFilters = append(allFilters, route.Filters...) chain := FilterChain{Filters: allFilters, Target: func(req *Request, resp *Response) { // handle request by route after passing all filters route.Function(wrappedRequest, wrappedResponse) }} chain.ProcessFilter(wrappedRequest, wrappedResponse) } else { // no filters, handle request by route route.Function(wrappedRequest, wrappedResponse) } } // fixedPrefixPath returns the fixed part of the partspec ; it may include template vars {} func (c Container) fixedPrefixPath(pathspec string) string { varBegin := strings.Index(pathspec, "{") if -1 == varBegin { return pathspec } return pathspec[:varBegin] } // ServeHTTP implements net/http.Handler therefore a Container can be a Handler in a http.Server func (c Container) ServeHTTP(httpwriter http.ResponseWriter, httpRequest *http.Request) { c.ServeMux.ServeHTTP(httpwriter, httpRequest) } // Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics. func (c Container) Handle(pattern string, handler http.Handler) { c.ServeMux.Handle(pattern, handler) } // HandleWithFilter registers the handler for the given pattern. // Container's filter chain is applied for handler. // If a handler already exists for pattern, HandleWithFilter panics. func (c Container) HandleWithFilter(pattern string, handler http.Handler) { f := func(httpResponse http.ResponseWriter, httpRequest *http.Request) { if len(c.containerFilters) == 0 { handler.ServeHTTP(httpResponse, httpRequest) return } chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) { handler.ServeHTTP(httpResponse, httpRequest) }} chain.ProcessFilter(NewRequest(httpRequest), NewResponse(httpResponse)) } c.Handle(pattern, http.HandlerFunc(f)) } // Filter appends a container FilterFunction. These are called before dispatching // a http.Request to a WebService from the container func (c *Container) Filter(filter FilterFunction) { c.containerFilters = append(c.containerFilters, filter) } // RegisteredWebServices returns the collections of added WebServices func (c Container) RegisteredWebServices() []*WebService { return c.webServices } // 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-1.1.3/cors_filter.go000066400000000000000000000116721246513110500164520ustar00rootroot00000000000000package 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 ( "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. If empty all are allowed. AllowedMethods []string MaxAge int // number of seconds before requiring new Options request CookiesAllowed bool Container *Container } // 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.Println("no Http header Origin set") } chain.ProcessFilter(req, resp) return } if len(c.AllowedDomains) > 0 { // if provided then origin must be included included := false for _, each := range c.AllowedDomains { if each == origin { included = true break } } if !included { if trace { traceLogger.Println("HTTP Origin:%s is not part of %v", origin, c.AllowedDomains) } 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) } } 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 { 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 _, each := range c.AllowedDomains { if each == origin { allowed = true break } } return allowed } func (c CrossOriginResourceSharing) setAllowOriginHeader(req *Request, resp *Response) { origin := req.Request.Header.Get(HEADER_Origin) if c.isOriginAllowed(origin) { resp.AddHeader(HEADER_AccessControlAllowOrigin, origin) } } func (c CrossOriginResourceSharing) checkAndSetExposeHeaders(resp *Response) { if len(c.ExposeHeaders) > 0 { resp.AddHeader(HEADER_AccessControlExposeHeaders, strings.Join(c.ExposeHeaders, ",")) } } func (c CrossOriginResourceSharing) checkAndSetAllowCredentials(resp *Response) { if c.CookiesAllowed { resp.AddHeader(HEADER_AccessControlAllowCredentials, "true") } } func (c CrossOriginResourceSharing) isValidAccessControlRequestMethod(method string, allowedMethods []string) bool { for _, each := range allowedMethods { if each == method { return true } } return false } func (c CrossOriginResourceSharing) isValidAccessControlRequestHeader(header string) bool { for _, each := range c.AllowedHeaders { if strings.ToLower(each) == strings.ToLower(header) { return true } } return false } go-restful-1.1.3/cors_filter_test.go000066400000000000000000000076051246513110500175120ustar00rootroot00000000000000package 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 accepted bool }{ {[]string{}, "http://anything.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.accepted { t.Fatal("expected to be accepted") } if actual == each.origin && !each.accepted { t.Fatal("did not expect to be accepted") } } } go-restful-1.1.3/coverage.sh000066400000000000000000000001031246513110500157220ustar00rootroot00000000000000go test -coverprofile=coverage.out go tool cover -html=coverage.outgo-restful-1.1.3/curly.go000066400000000000000000000126571246513110500153010ustar00rootroot00000000000000package 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) []Route { candidates := &sortableCurlyRoutes{[]*curlyRoute{}} for _, each := range ws.routes { matches, paramCount, staticCount := c.matchesRouteByPathTokens(each.pathParts, requestTokens) if matches { candidates.add(&curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers? } } sort.Sort(sort.Reverse(candidates)) return candidates.routes() } // matchesRouteByPathTokens computes whether it matches, howmany parameters do match and what the number of static path elements are. func (c CurlyRouter) matchesRouteByPathTokens(routeTokens, requestTokens []string) (matches bool, paramCount int, staticCount int) { if len(routeTokens) < len(requestTokens) { // proceed in matching only if last routeToken is wildcard count := len(routeTokens) if count == 0 || !strings.HasSuffix(routeTokens[count-1], "*}") { return false, 0, 0 } // proceed } for i, routeToken := range routeTokens { if i == len(requestTokens) { // reached end of request path return false, 0, 0 } requestToken := requestTokens[i] if strings.HasPrefix(routeToken, "{") { paramCount++ if colon := strings.Index(routeToken, ":"); colon != -1 { // match by regex matchesToken, matchesRemainder := c.regularMatchesPathToken(routeToken, colon, requestToken) if !matchesToken { return false, 0, 0 } if matchesRemainder { break } } } else { // no { prefix if requestToken != routeToken { return false, 0, 0 } staticCount++ } } return true, paramCount, staticCount } // regularMatchesPathToken tests whether the regular expression part of routeToken matches the requestToken or all remaining tokens // format routeToken is {someVar:someExpression}, e.g. {zipcode:[\d][\d][\d][\d][A-Z][A-Z]} func (c CurlyRouter) regularMatchesPathToken(routeToken string, colon int, requestToken string) (matchesToken bool, matchesRemainder bool) { regPart := routeToken[colon+1 : len(routeToken)-1] if regPart == "*" { if trace { traceLogger.Printf("wildcard parameter detected in route token %s that matches %s\n", routeToken, requestToken) } return true, true } matched, err := regexp.MatchString(regPart, requestToken) return (matched && err == nil), false } // 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 []Route, httpRequest *http.Request) (*Route, error) { // tracing is done inside detectRoute return RouterJSR311{}.detectRoute(candidateRoutes, 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-1.1.3/curly_route.go000066400000000000000000000024211246513110500165030ustar00rootroot00000000000000package restful // Copyright 2013 Ernest Micklei. All rights reserved. // Use of this source code is governed by a license // that can be found in the LICENSE file. // curlyRoute exits for sorting Routes by the CurlyRouter based on number of parameters and number of static path elements. type curlyRoute struct { route Route paramCount int staticCount int } type sortableCurlyRoutes struct { candidates []*curlyRoute } func (s *sortableCurlyRoutes) add(route *curlyRoute) { s.candidates = append(s.candidates, route) } func (s *sortableCurlyRoutes) routes() (routes []Route) { for _, each := range s.candidates { routes = append(routes, each.route) // TODO change return type } return routes } func (s *sortableCurlyRoutes) Len() int { return len(s.candidates) } func (s *sortableCurlyRoutes) Swap(i, j int) { s.candidates[i], s.candidates[j] = s.candidates[j], s.candidates[i] } func (s *sortableCurlyRoutes) Less(i, j int) bool { ci := s.candidates[i] cj := s.candidates[j] // primary key if ci.staticCount < cj.staticCount { return true } if ci.staticCount > cj.staticCount { return false } // secundary key if ci.paramCount < cj.paramCount { return true } if ci.paramCount > cj.paramCount { return false } return ci.route.Path < cj.route.Path } go-restful-1.1.3/curly_test.go000066400000000000000000000155611246513110500163350ustar00rootroot00000000000000package restful import ( "io" "net/http" "testing" ) var requestPaths = []struct { // url with path (1) is handled by service with root (2) and remainder has value final (3) path, root string }{ {"/", "/"}, {"/p", "/p"}, {"/p/x", "/p/{q}"}, {"/q/x", "/q"}, {"/p/x/", "/p/{q}"}, {"/p/x/y", "/p/{q}"}, {"/q/x/y", "/q"}, {"/z/q", "/{p}/q"}, {"/a/b/c/q", "/"}, } // go test -v -test.run TestCurlyDetectWebService ...restful func TestCurlyDetectWebService(t *testing.T) { ws1 := new(WebService).Path("/") ws2 := new(WebService).Path("/p") ws3 := new(WebService).Path("/q") ws4 := new(WebService).Path("/p/q") ws5 := new(WebService).Path("/p/{q}") ws7 := new(WebService).Path("/{p}/q") var wss = []*WebService{ws1, ws2, ws3, ws4, ws5, ws7} for _, each := range wss { t.Logf("path=%s,toks=%v\n", each.pathExpr.Source, each.pathExpr.tokens) } router := CurlyRouter{} ok := true for i, fixture := range requestPaths { requestTokens := tokenizePath(fixture.path) who := router.detectWebService(requestTokens, wss) if who != nil && who.RootPath() != fixture.root { t.Logf("[line:%v] Unexpected dispatcher, expected:%v, actual:%v", i, fixture.root, who.RootPath()) ok = false } } if !ok { t.Fail() } } var serviceDetects = []struct { path string found bool root string }{ {"/a/b", true, "/{p}/{q}/{r}"}, {"/p/q", true, "/p/q"}, {"/q/p", true, "/q"}, {"/", true, "/"}, {"/p/q/r", true, "/p/q"}, } // go test -v -test.run Test_detectWebService ...restful func Test_detectWebService(t *testing.T) { router := CurlyRouter{} ws1 := new(WebService).Path("/") ws2 := new(WebService).Path("/p") ws3 := new(WebService).Path("/q") ws4 := new(WebService).Path("/p/q") ws5 := new(WebService).Path("/p/{q}") ws6 := new(WebService).Path("/p/{q}/") ws7 := new(WebService).Path("/{p}/q") ws8 := new(WebService).Path("/{p}/{q}/{r}") var wss = []*WebService{ws8, ws7, ws6, ws5, ws4, ws3, ws2, ws1} for _, fix := range serviceDetects { requestPath := fix.path requestTokens := tokenizePath(requestPath) for _, ws := range wss { serviceTokens := ws.pathExpr.tokens matches, score := router.computeWebserviceScore(requestTokens, serviceTokens) t.Logf("req=%s,toks:%v,ws=%s,toks:%v,score=%d,matches=%v", requestPath, requestTokens, ws.RootPath(), serviceTokens, score, matches) } best := router.detectWebService(requestTokens, wss) if best != nil { if fix.found { t.Logf("best=%s", best.RootPath()) } else { t.Fatalf("should have found:%s", fix.root) } } } } var routeMatchers = []struct { route string path string matches bool paramCount int staticCount int }{ // route, request-path {"/a", "/a", true, 0, 1}, {"/a", "/b", false, 0, 0}, {"/a", "/b", false, 0, 0}, {"/a/{b}/c/", "/a/2/c", true, 1, 2}, {"/{a}/{b}/{c}/", "/a/b", false, 0, 0}, {"/{x:*}", "/", false, 0, 0}, {"/{x:*}", "/a", true, 1, 0}, {"/{x:*}", "/a/b", true, 1, 0}, {"/a/{x:*}", "/a/b", true, 1, 1}, {"/a/{x:[A-Z][A-Z]}", "/a/ZX", true, 1, 1}, {"/basepath/{resource:*}", "/basepath/some/other/location/test.xml", true, 1, 1}, } // clear && go test -v -test.run Test_matchesRouteByPathTokens ...restful func Test_matchesRouteByPathTokens(t *testing.T) { router := CurlyRouter{} for i, each := range routeMatchers { routeToks := tokenizePath(each.route) reqToks := tokenizePath(each.path) matches, pCount, sCount := router.matchesRouteByPathTokens(routeToks, reqToks) if matches != each.matches { t.Fatalf("[%d] unexpected matches outcome route:%s, path:%s, matches:%v", i, each.route, each.path, matches) } if pCount != each.paramCount { t.Fatalf("[%d] unexpected paramCount got:%d want:%d ", i, pCount, each.paramCount) } if sCount != each.staticCount { t.Fatalf("[%d] unexpected staticCount got:%d want:%d ", i, sCount, each.staticCount) } } } // clear && go test -v -test.run TestExtractParameters_Wildcard1 ...restful func TestExtractParameters_Wildcard1(t *testing.T) { params := doExtractParams("/fixed/{var:*}", 2, "/fixed/remainder", t) if params["var"] != "remainder" { t.Errorf("parameter mismatch var: %s", params["var"]) } } // clear && go test -v -test.run TestExtractParameters_Wildcard2 ...restful func TestExtractParameters_Wildcard2(t *testing.T) { params := doExtractParams("/fixed/{var:*}", 2, "/fixed/remain/der", t) if params["var"] != "remain/der" { t.Errorf("parameter mismatch var: %s", params["var"]) } } // clear && go test -v -test.run TestExtractParameters_Wildcard3 ...restful func TestExtractParameters_Wildcard3(t *testing.T) { params := doExtractParams("/static/{var:*}", 2, "/static/test/sub/hi.html", t) if params["var"] != "test/sub/hi.html" { t.Errorf("parameter mismatch var: %s", params["var"]) } } // clear && go test -v -test.run TestCurly_ISSUE_34 ...restful func TestCurly_ISSUE_34(t *testing.T) { ws1 := new(WebService).Path("/") ws1.Route(ws1.GET("/{type}/{id}").To(curlyDummy)) ws1.Route(ws1.GET("/network/{id}").To(curlyDummy)) routes := CurlyRouter{}.selectRoutes(ws1, tokenizePath("/network/12")) if len(routes) != 2 { t.Fatal("expected 2 routes") } if routes[0].Path != "/network/{id}" { t.Error("first is", routes[0].Path) } } // 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)) routes := CurlyRouter{}.selectRoutes(ws1, tokenizePath("/network/12")) if len(routes) != 2 { t.Fatal("expected 2 routes") } if routes[0].Path != "/network/{id}" { t.Error("first is", routes[0].Path) } } // 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-1.1.3/doc.go000066400000000000000000000165771246513110500147150ustar00rootroot00000000000000/* Package restful, a lean package for creating REST-style WebServices without magic. WebServices and Routes A WebService has a collection of Route objects that dispatch incoming Http Requests to a function calls. Typically, a WebService has a root path (e.g. /users) and defines common MIME types for its routes. WebServices must be added to a container (see below) in order to handler Http requests from a server. A Route is defined by a HTTP method, an URL path and (optionally) the MIME types it consumes (Content-Type) and produces (Accept). This package has the logic to find the best matching Route and if found, call its Function. ws := new(restful.WebService) ws. Path("/users"). Consumes(restful.MIME_JSON, restful.MIME_XML). Produces(restful.MIME_JSON, restful.MIME_XML) ws.Route(ws.GET("/{user-id}").To(u.findUser)) // u is a UserResource ... // GET http://localhost:8080/users/1 func (u UserResource) findUser(request *restful.Request, response *restful.Response) { id := request.PathParameter("user-id") ... } The (*Request, *Response) arguments provide functions for reading information from the request and writing information back to the response. See the example https://github.com/emicklei/go-restful/blob/master/examples/restful-user-resource.go with a full implementation. Regular expression matching Routes A Route parameter can be specified using the format "uri/{var[:regexp]}" or the special version "uri/{var:*}" for matching the tail of the path. For example, /persons/{name:[A-Z][A-Z]} can be used to restrict values for the parameter "name" to only contain capital alphabetic characters. Regular expressions must use the standard Go syntax as described in the regexp package. (https://code.google.com/p/re2/wiki/Syntax) This feature requires the use of a CurlyRouter. Containers A Container holds a collection of WebServices, Filters and a http.ServeMux for multiplexing http requests. Using the statements "restful.Add(...) and restful.Filter(...)" will register WebServices and Filters to the Default Container. The Default container of go-restful uses the http.DefaultServeMux. You can create your own Container and create a new http.Server for that particular container. container := restful.NewContainer() server := &http.Server{Addr: ":8081", Handler: container} Filters A filter dynamically intercepts requests and responses to transform or use the information contained in the requests or responses. You can use filters to perform generic logging, measurement, authentication, redirect, set response headers etc. In the restful package there are three hooks into the request,response flow where filters can be added. Each filter must define a FilterFunction: func (req *restful.Request, resp *restful.Response, chain *restful.FilterChain) Use the following statement to pass the request,response pair to the next filter or RouteFunction chain.ProcessFilter(req, resp) Container Filters These are processed before any registered WebService. // install a (global) filter for the default container (processed before any webservice) restful.Filter(globalLogging) WebService Filters These are processed before any Route of a WebService. // install a webservice filter (processed before any route) ws.Filter(webserviceLogging).Filter(measureTime) Route Filters These are processed before calling the function associated with the Route. // install 2 chained route filters (processed before calling findUser) ws.Route(ws.GET("/{user-id}").Filter(routeLogging).Filter(NewCountFilter().routeCounter).To(findUser)) See the example https://github.com/emicklei/go-restful/blob/master/examples/restful-filters.go with full implementations. Response Encoding Two encodings are supported: gzip and deflate. To enable this for all responses: restful.DefaultContainer.EnableContentEncoding(true) If a Http request includes the Accept-Encoding header then the response content will be compressed using the specified encoding. Alternatively, you can create a Filter that performs the encoding and install it per WebService or Route. See the example https://github.com/emicklei/go-restful/blob/master/examples/restful-encoding-filter.go OPTIONS support By installing a pre-defined container filter, your Webservice(s) can respond to the OPTIONS Http request. Filter(OPTIONSFilter()) CORS By installing the filter of a CrossOriginResourceSharing (CORS), your WebService(s) can handle CORS requests. cors := CrossOriginResourceSharing{ExposeHeaders: []string{"X-My-Header"}, CookiesAllowed: false, Container: DefaultContainer} Filter(cors.Filter) Error Handling Unexpected things happen. If a request cannot be processed because of a failure, your service needs to tell via the response what happened and why. For this reason HTTP status codes exist and it is important to use the correct code in every exceptional situation. 400: Bad Request If path or query parameters are not valid (content or type) then use http.StatusBadRequest. 404: Not Found Despite a valid URI, the resource requested may not be available 500: Internal Server Error If the application logic could not process the request (or write the response) then use http.StatusInternalServerError. 405: Method Not Allowed The request has a valid URL but the method (GET,PUT,POST,...) is not allowed. 406: Not Acceptable The request does not have or has an unknown Accept Header set for this operation. 415: Unsupported Media Type The request does not have or has an unknown Content-Type Header set for this operation. ServiceError In addition to setting the correct (error) Http status code, you can choose to write a ServiceError message on the response. Performance options This package has several options that affect the performance of your service. It is important to understand them and how you can change it. restful.DefaultContainer.Router(CurlyRouter{}) The default router is the RouterJSR311 which is an implementation of its spec (http://jsr311.java.net/nonav/releases/1.1/spec/spec.html). However, it uses regular expressions for all its routes which, depending on your usecase, may consume a significant amount of time. The CurlyRouter implementation is more lightweight that also allows you to use wildcards and expressions, but only if needed. restful.DefaultContainer.DoNotRecover(true) 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 false; it will recover from panics. This has performance implications. restful.SetCacheReadEntity(false) SetCacheReadEntity controls whether the response data ([]byte) is cached such that ReadEntity is repeatable. If you expect to read large amounts of payload data, and you do not use this feature, you should set it to false. 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 a log.Logger instance such as: restful.TraceLogger(log.New(os.Stdout, "[restful] ", log.LstdFlags|log.Lshortfile)) 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-2014, http://ernestmicklei.com. MIT License */ package restful go-restful-1.1.3/doc_examples_test.go000066400000000000000000000021101246513110500176240ustar00rootroot00000000000000package 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")) } go-restful-1.1.3/examples/000077500000000000000000000000001246513110500154175ustar00rootroot00000000000000go-restful-1.1.3/examples/.goconvey000066400000000000000000000000061246513110500172450ustar00rootroot00000000000000ignorego-restful-1.1.3/examples/google_app_engine/000077500000000000000000000000001246513110500210605ustar00rootroot00000000000000go-restful-1.1.3/examples/google_app_engine/.goconvey000066400000000000000000000000061246513110500227060ustar00rootroot00000000000000ignorego-restful-1.1.3/examples/google_app_engine/app.yaml000066400000000000000000000006371246513110500225320ustar00rootroot00000000000000# # Include your application ID here # application: version: 1 runtime: go api_version: go1 handlers: # # Regex for all swagger files to make as static content. # You should create the folder static/swagger and copy # swagger-ui into it. # - url: /apidocs/(.*?)/(.*\.(js|html|css)) static_files: static/swagger/\1/\2 upload: static/swagger/(.*?)/(.*\.(js|html|css)) - url: /.* script: _go_app go-restful-1.1.3/examples/google_app_engine/datastore/000077500000000000000000000000001246513110500230465ustar00rootroot00000000000000go-restful-1.1.3/examples/google_app_engine/datastore/.goconvey000066400000000000000000000000061246513110500246740ustar00rootroot00000000000000ignorego-restful-1.1.3/examples/google_app_engine/datastore/app.yaml000066400000000000000000000006311246513110500245120ustar00rootroot00000000000000application: datastore-example version: 1 runtime: go api_version: go1 handlers: # Regex for all swagger files to make as static content. # You should create the folder static/swagger and copy # swagger-ui into it. # - url: /apidocs/(.*?)/(.*\.(js|html|css)) static_files: static/swagger/\1/\2 upload: static/swagger/(.*?)/(.*\.(js|html|css)) # Catch all. - url: /.* script: _go_app login: required go-restful-1.1.3/examples/google_app_engine/datastore/main.go000066400000000000000000000200511246513110500243170ustar00rootroot00000000000000package main import ( "appengine" "appengine/datastore" "appengine/user" "github.com/emicklei/go-restful" "github.com/emicklei/go-restful/swagger" "net/http" "time" ) // This example demonstrates a reasonably complete suite of RESTful operations backed // by DataStore on Google App Engine. // Our simple example struct. type Profile struct { LastModified time.Time `json:"-" xml:"-"` Email string `json:"-" xml:"-"` FirstName string `json:"first_name" xml:"first-name"` NickName string `json:"nick_name" xml:"nick-name"` LastName string `json:"last_name" xml:"last-name"` } type ProfileApi struct { Path string } func gaeUrl() string { if appengine.IsDevAppServer() { return "http://localhost:8080" } else { // Include your URL on App Engine here. // I found no way to get AppID without appengine.Context and this always // based on a http.Request. return "http://federatedservices.appspot.com" } } func init() { u := ProfileApi{Path: "/profiles"} u.register() // Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API // You need to download the Swagger HTML5 assets and change the FilePath location in the config below. // Open .appspot.com/apidocs and enter // Place the Swagger UI files into a folder called static/swagger if you wish to use Swagger // http://.appspot.com/apidocs.json in the api input field. // For testing, you can use http://localhost:8080/apidocs.json config := swagger.Config{ // You control what services are visible WebServices: restful.RegisteredWebServices(), WebServicesUrl: gaeUrl(), ApiPath: "/apidocs.json", // Optionally, specifiy where the UI is located SwaggerPath: "/apidocs/", // GAE support static content which is configured in your app.yaml. // This example expect the swagger-ui in static/swagger so you should place it there :) SwaggerFilePath: "static/swagger"} swagger.InstallSwaggerService(config) } func (u ProfileApi) register() { ws := new(restful.WebService) ws. Path(u.Path). // You can specify consumes and produces per route as well. Consumes(restful.MIME_JSON, restful.MIME_XML). Produces(restful.MIME_JSON, restful.MIME_XML) ws.Route(ws.POST("").To(u.insert). // Swagger documentation. Doc("insert a new profile"). Param(ws.BodyParameter("Profile", "representation of a profile").DataType("main.Profile")). Reads(Profile{})) ws.Route(ws.GET("/{profile-id}").To(u.read). // Swagger documentation. Doc("read a profile"). Param(ws.PathParameter("profile-id", "identifier for a profile").DataType("string")). Writes(Profile{})) ws.Route(ws.PUT("/{profile-id}").To(u.update). // Swagger documentation. Doc("update an existing profile"). Param(ws.PathParameter("profile-id", "identifier for a profile").DataType("string")). Param(ws.BodyParameter("Profile", "representation of a profile").DataType("main.Profile")). Reads(Profile{})) ws.Route(ws.DELETE("/{profile-id}").To(u.remove). // Swagger documentation. Doc("remove a profile"). Param(ws.PathParameter("profile-id", "identifier for a profile").DataType("string"))) restful.Add(ws) } // POST http://localhost:8080/profiles // {"first_name": "Ivan", "nick_name": "Socks", "last_name": "Hawkes"} // func (u *ProfileApi) insert(r *restful.Request, w *restful.Response) { c := appengine.NewContext(r.Request) // Marshall the entity from the request into a struct. p := new(Profile) err := r.ReadEntity(&p) if err != nil { w.WriteError(http.StatusNotAcceptable, err) return } // Ensure we start with a sensible value for this field. p.LastModified = time.Now() // The profile belongs to this user. p.Email = user.Current(c).String() k, err := datastore.Put(c, datastore.NewIncompleteKey(c, "profiles", nil), p) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Let them know the location of the newly created resource. // TODO: Use a safe Url path append function. w.AddHeader("Location", u.Path+"/"+k.Encode()) // Return the resultant entity. w.WriteHeader(http.StatusCreated) w.WriteEntity(p) } // GET http://localhost:8080/profiles/ahdkZXZ-ZmVkZXJhdGlvbi1zZXJ2aWNlc3IVCxIIcHJvZmlsZXMYgICAgICAgAoM // func (u ProfileApi) read(r *restful.Request, w *restful.Response) { c := appengine.NewContext(r.Request) // Decode the request parameter to determine the key for the entity. k, err := datastore.DecodeKey(r.PathParameter("profile-id")) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Retrieve the entity from the datastore. p := Profile{} if err := datastore.Get(c, k, &p); err != nil { if err.Error() == "datastore: no such entity" { http.Error(w, err.Error(), http.StatusNotFound) } else { http.Error(w, err.Error(), http.StatusInternalServerError) } return } // Check we own the profile before allowing them to view it. // Optionally, return a 404 instead to help prevent guessing ids. // TODO: Allow admins access. if p.Email != user.Current(c).String() { http.Error(w, "You do not have access to this resource", http.StatusForbidden) return } w.WriteEntity(p) } // PUT http://localhost:8080/profiles/ahdkZXZ-ZmVkZXJhdGlvbi1zZXJ2aWNlc3IVCxIIcHJvZmlsZXMYgICAgICAgAoM // {"first_name": "Ivan", "nick_name": "Socks", "last_name": "Hawkes"} // func (u *ProfileApi) update(r *restful.Request, w *restful.Response) { c := appengine.NewContext(r.Request) // Decode the request parameter to determine the key for the entity. k, err := datastore.DecodeKey(r.PathParameter("profile-id")) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Marshall the entity from the request into a struct. p := new(Profile) err = r.ReadEntity(&p) if err != nil { w.WriteError(http.StatusNotAcceptable, err) return } // Retrieve the old entity from the datastore. old := Profile{} if err := datastore.Get(c, k, &old); err != nil { if err.Error() == "datastore: no such entity" { http.Error(w, err.Error(), http.StatusNotFound) } else { http.Error(w, err.Error(), http.StatusInternalServerError) } return } // Check we own the profile before allowing them to update it. // Optionally, return a 404 instead to help prevent guessing ids. // TODO: Allow admins access. if old.Email != user.Current(c).String() { http.Error(w, "You do not have access to this resource", http.StatusForbidden) return } // Since the whole entity is re-written, we need to assign any invariant fields again // e.g. the owner of the entity. p.Email = user.Current(c).String() // Keep track of the last modification date. p.LastModified = time.Now() // Attempt to overwrite the old entity. _, err = datastore.Put(c, k, p) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Let them know it succeeded. w.WriteHeader(http.StatusNoContent) } // DELETE http://localhost:8080/profiles/ahdkZXZ-ZmVkZXJhdGlvbi1zZXJ2aWNlc3IVCxIIcHJvZmlsZXMYgICAgICAgAoM // func (u *ProfileApi) remove(r *restful.Request, w *restful.Response) { c := appengine.NewContext(r.Request) // Decode the request parameter to determine the key for the entity. k, err := datastore.DecodeKey(r.PathParameter("profile-id")) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Retrieve the old entity from the datastore. old := Profile{} if err := datastore.Get(c, k, &old); err != nil { if err.Error() == "datastore: no such entity" { http.Error(w, err.Error(), http.StatusNotFound) } else { http.Error(w, err.Error(), http.StatusInternalServerError) } return } // Check we own the profile before allowing them to delete it. // Optionally, return a 404 instead to help prevent guessing ids. // TODO: Allow admins access. if old.Email != user.Current(c).String() { http.Error(w, "You do not have access to this resource", http.StatusForbidden) return } // Delete the entity. if err := datastore.Delete(c, k); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } // Success notification. w.WriteHeader(http.StatusNoContent) } go-restful-1.1.3/examples/google_app_engine/restful-appstats-integration.go000066400000000000000000000004101246513110500272440ustar00rootroot00000000000000package main import ( "github.com/mjibson/appstats" ) func stats(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { c := appstats.NewContext(req.Request) chain.ProcessFilter(req, resp) c.Stats.Status = resp.StatusCode() c.Save() } go-restful-1.1.3/examples/google_app_engine/restful-user-service.go000066400000000000000000000107751246513110500255170ustar00rootroot00000000000000package main import ( "appengine" "appengine/memcache" "github.com/emicklei/go-restful" "github.com/emicklei/go-restful/swagger" "net/http" ) // This example is functionally the same as ../restful-user-service.go // but it`s supposed to run on Goole App Engine (GAE) // // contributed by ivanhawkes type User struct { Id, Name string } type UserService struct { // normally one would use DAO (data access object) // but in this example we simple use memcache. } func (u UserService) Register() { ws := new(restful.WebService) ws. Path("/users"). Consumes(restful.MIME_XML, restful.MIME_JSON). Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well ws.Route(ws.GET("/{user-id}").To(u.findUser). // docs Doc("get a user"). Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")). Writes(User{})) // on the response ws.Route(ws.PATCH("").To(u.updateUser). // docs Doc("update a user"). Reads(User{})) // from the request ws.Route(ws.PUT("/{user-id}").To(u.createUser). // docs Doc("create a user"). Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")). Reads(User{})) // from the request ws.Route(ws.DELETE("/{user-id}").To(u.removeUser). // docs Doc("delete a user"). Param(ws.PathParameter("user-id", "identifier of the user").DataType("string"))) restful.Add(ws) } // GET http://localhost:8080/users/1 // func (u UserService) findUser(request *restful.Request, response *restful.Response) { c := appengine.NewContext(request.Request) id := request.PathParameter("user-id") usr := new(User) _, err := memcache.Gob.Get(c, id, &usr) if err != nil || len(usr.Id) == 0 { response.WriteErrorString(http.StatusNotFound, "User could not be found.") } else { response.WriteEntity(usr) } } // PATCH http://localhost:8080/users // 1Melissa Raspberry // func (u *UserService) updateUser(request *restful.Request, response *restful.Response) { c := appengine.NewContext(request.Request) usr := new(User) err := request.ReadEntity(&usr) if err == nil { item := &memcache.Item{ Key: usr.Id, Object: &usr, } err = memcache.Gob.Set(c, item) if err != nil { response.WriteError(http.StatusInternalServerError, err) return } response.WriteEntity(usr) } else { response.WriteError(http.StatusInternalServerError, err) } } // PUT http://localhost:8080/users/1 // 1Melissa // func (u *UserService) createUser(request *restful.Request, response *restful.Response) { c := appengine.NewContext(request.Request) usr := User{Id: request.PathParameter("user-id")} err := request.ReadEntity(&usr) if err == nil { item := &memcache.Item{ Key: usr.Id, Object: &usr, } err = memcache.Gob.Add(c, item) if err != nil { response.WriteError(http.StatusInternalServerError, err) return } response.WriteHeader(http.StatusCreated) response.WriteEntity(usr) } else { response.WriteError(http.StatusInternalServerError, err) } } // DELETE http://localhost:8080/users/1 // func (u *UserService) removeUser(request *restful.Request, response *restful.Response) { c := appengine.NewContext(request.Request) id := request.PathParameter("user-id") err := memcache.Delete(c, id) if err != nil { response.WriteError(http.StatusInternalServerError, err) } } func getGaeURL() string { if appengine.IsDevAppServer() { return "http://localhost:8080" } else { /** * Include your URL on App Engine here. * I found no way to get AppID without appengine.Context and this always * based on a http.Request. */ return "http://.appspot.com" } } func init() { u := UserService{} u.Register() // Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API // You need to download the Swagger HTML5 assets and change the FilePath location in the config below. // Open .appspot.com/apidocs and enter http://.appspot.com/apidocs.json in the api input field. config := swagger.Config{ WebServices: restful.RegisteredWebServices(), // you control what services are visible WebServicesUrl: getGaeURL(), ApiPath: "/apidocs.json", // Optionally, specifiy where the UI is located SwaggerPath: "/apidocs/", // GAE support static content which is configured in your app.yaml. // This example expect the swagger-ui in static/swagger so you should place it there :) SwaggerFilePath: "static/swagger"} swagger.InstallSwaggerService(config) } go-restful-1.1.3/examples/home.html000066400000000000000000000001021246513110500172260ustar00rootroot00000000000000

{{.Text}}

go-restful-1.1.3/examples/restful-CORS-filter.go000066400000000000000000000033431246513110500214640ustar00rootroot00000000000000package main import ( "io" "log" "net/http" "github.com/emicklei/go-restful" ) // Cross-origin resource sharing (CORS) is a mechanism that allows JavaScript on a web page // to make XMLHttpRequests to another domain, not the domain the JavaScript originated from. // // http://en.wikipedia.org/wiki/Cross-origin_resource_sharing // http://enable-cors.org/server.html // // GET http://localhost:8080/users // // GET http://localhost:8080/users/1 // // PUT http://localhost:8080/users/1 // // DELETE http://localhost:8080/users/1 // // OPTIONS http://localhost:8080/users/1 with Header "Origin" set to some domain and type UserResource struct{} func (u UserResource) RegisterTo(container *restful.Container) { ws := new(restful.WebService) ws. Path("/users"). Consumes("*/*"). Produces("*/*") ws.Route(ws.GET("/{user-id}").To(u.nop)) ws.Route(ws.POST("").To(u.nop)) ws.Route(ws.PUT("/{user-id}").To(u.nop)) ws.Route(ws.DELETE("/{user-id}").To(u.nop)) container.Add(ws) } func (u UserResource) nop(request *restful.Request, response *restful.Response) { io.WriteString(response.ResponseWriter, "this would be a normal response") } func main() { wsContainer := restful.NewContainer() u := UserResource{} u.RegisterTo(wsContainer) // Add container filter to enable CORS cors := restful.CrossOriginResourceSharing{ ExposeHeaders: []string{"X-My-Header"}, AllowedHeaders: []string{"Content-Type"}, CookiesAllowed: false, Container: wsContainer} wsContainer.Filter(cors.Filter) // Add container filter to respond to OPTIONS wsContainer.Filter(wsContainer.OPTIONSFilter) log.Printf("start listening on localhost:8080") server := &http.Server{Addr: ":8080", Handler: wsContainer} log.Fatal(server.ListenAndServe()) } go-restful-1.1.3/examples/restful-NCSA-logging.go000066400000000000000000000023721246513110500216040ustar00rootroot00000000000000package main import ( "github.com/emicklei/go-restful" "io" "log" "net/http" "os" "strings" "time" ) // This example shows how to create a filter that produces log lines // according to the Common Log Format, also known as the NCSA standard. // // kindly contributed by leehambley // // GET http://localhost:8080/ping var logger *log.Logger = log.New(os.Stdout, "", 0) func NCSACommonLogFormatLogger() restful.FilterFunction { return func(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { var username = "-" if req.Request.URL.User != nil { if name := req.Request.URL.User.Username(); name != "" { username = name } } chain.ProcessFilter(req, resp) logger.Printf("%s - %s [%s] \"%s %s %s\" %d %d", strings.Split(req.Request.RemoteAddr, ":")[0], username, time.Now().Format("02/Jan/2006:15:04:05 -0700"), req.Request.Method, req.Request.URL.RequestURI(), req.Request.Proto, resp.StatusCode(), resp.ContentLength(), ) } } func main() { ws := new(restful.WebService) ws.Filter(NCSACommonLogFormatLogger()) ws.Route(ws.GET("/ping").To(hello)) restful.Add(ws) http.ListenAndServe(":8080", nil) } func hello(req *restful.Request, resp *restful.Response) { io.WriteString(resp, "pong") } go-restful-1.1.3/examples/restful-basic-authentication.go000066400000000000000000000017051246513110500235310ustar00rootroot00000000000000package main import ( "github.com/emicklei/go-restful" "io" "net/http" ) // This example shows how to create a (Route) Filter that performs Basic Authentication on the Http request. // // GET http://localhost:8080/secret // and use admin,admin for the credentials func main() { ws := new(restful.WebService) ws.Route(ws.GET("/secret").Filter(basicAuthenticate).To(secret)) restful.Add(ws) http.ListenAndServe(":8080", nil) } func basicAuthenticate(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { encoded := req.Request.Header.Get("Authorization") // usr/pwd = admin/admin // real code does some decoding if len(encoded) == 0 || "Basic YWRtaW46YWRtaW4=" != encoded { 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-1.1.3/examples/restful-cpuprofiler-service.go000066400000000000000000000040031246513110500234150ustar00rootroot00000000000000package main import ( "github.com/emicklei/go-restful" "io" "log" "os" "runtime/pprof" ) // ProfilingService is a WebService that can start/stop a CPU profile and write results to a file // GET /{rootPath}/start will activate CPU profiling // GET /{rootPath}/stop will stop profiling // // NewProfileService("/profiler", "ace.prof").AddWebServiceTo(restful.DefaultContainer) // type ProfilingService struct { rootPath string // the base (root) of the service, e.g. /profiler cpuprofile string // the output filename to write profile results, e.g. myservice.prof cpufile *os.File // if not nil, then profiling is active } func NewProfileService(rootPath string, outputFilename string) *ProfilingService { ps := new(ProfilingService) ps.rootPath = rootPath ps.cpuprofile = outputFilename return ps } // Add this ProfileService to a restful Container func (p ProfilingService) AddWebServiceTo(container *restful.Container) { ws := new(restful.WebService) ws.Path(p.rootPath).Consumes("*/*").Produces(restful.MIME_JSON) ws.Route(ws.GET("/start").To(p.startProfiler)) ws.Route(ws.GET("/stop").To(p.stopProfiler)) container.Add(ws) } func (p *ProfilingService) startProfiler(req *restful.Request, resp *restful.Response) { if p.cpufile != nil { io.WriteString(resp.ResponseWriter, "[restful] CPU profiling already running") return // error? } cpufile, err := os.Create(p.cpuprofile) if err != nil { log.Fatal(err) } // remember for close p.cpufile = cpufile pprof.StartCPUProfile(cpufile) io.WriteString(resp.ResponseWriter, "[restful] CPU profiling started, writing on:"+p.cpuprofile) } func (p *ProfilingService) stopProfiler(req *restful.Request, resp *restful.Response) { if p.cpufile == nil { io.WriteString(resp.ResponseWriter, "[restful] CPU profiling not active") return // error? } pprof.StopCPUProfile() p.cpufile.Close() p.cpufile = nil io.WriteString(resp.ResponseWriter, "[restful] CPU profiling stopped, closing:"+p.cpuprofile) } func main() {} // exists for example compilation only go-restful-1.1.3/examples/restful-curly-router.go000066400000000000000000000056241246513110500221130ustar00rootroot00000000000000package main import ( "github.com/emicklei/go-restful" "log" "net/http" ) // This example has the same service definition as restful-user-resource // but uses a different router (CurlyRouter) that does not use regular expressions // // POST http://localhost:8080/users // 1Melissa Raspberry // // GET http://localhost:8080/users/1 // // PUT http://localhost:8080/users/1 // 1Melissa // // DELETE http://localhost:8080/users/1 // type User struct { Id, Name string } type UserResource struct { // normally one would use DAO (data access object) users map[string]User } func (u UserResource) Register(container *restful.Container) { ws := new(restful.WebService) ws. Path("/users"). Consumes(restful.MIME_XML, restful.MIME_JSON). Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well ws.Route(ws.GET("/{user-id}").To(u.findUser)) ws.Route(ws.POST("").To(u.updateUser)) ws.Route(ws.PUT("/{user-id}").To(u.createUser)) ws.Route(ws.DELETE("/{user-id}").To(u.removeUser)) container.Add(ws) } // GET http://localhost:8080/users/1 // func (u UserResource) findUser(request *restful.Request, response *restful.Response) { id := request.PathParameter("user-id") usr := u.users[id] if len(usr.Id) == 0 { response.AddHeader("Content-Type", "text/plain") response.WriteErrorString(http.StatusNotFound, "User could not be found.") } else { response.WriteEntity(usr) } } // POST http://localhost:8080/users // 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: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.WriteHeader(http.StatusCreated) response.WriteEntity(usr) } else { response.AddHeader("Content-Type", "text/plain") response.WriteErrorString(http.StatusInternalServerError, err.Error()) } } // DELETE http://localhost:8080/users/1 // func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) { id := request.PathParameter("user-id") delete(u.users, id) } func main() { wsContainer := restful.NewContainer() wsContainer.Router(restful.CurlyRouter{}) u := UserResource{map[string]User{}} u.Register(wsContainer) log.Printf("start listening on localhost:8080") server := &http.Server{Addr: ":8080", Handler: wsContainer} log.Fatal(server.ListenAndServe()) } go-restful-1.1.3/examples/restful-encoding-filter.go000066400000000000000000000032331246513110500225020ustar00rootroot00000000000000package main import ( "github.com/emicklei/go-restful" "log" "net/http" ) type User struct { Id, Name string } type UserList struct { Users []User } // // This example shows how to use the CompressingResponseWriter by a Filter // such that encoding can be enabled per WebService or per Route (instead of per container) // Using restful.DefaultContainer.EnableContentEncoding(true) will encode all responses served by WebServices in the DefaultContainer. // // Set Accept-Encoding to gzip or deflate // GET http://localhost:8080/users/42 // and look at the response headers func main() { restful.Add(NewUserService()) log.Printf("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.Printf("findUser") response.WriteEntity(User{"42", "Gandalf"}) } go-restful-1.1.3/examples/restful-filters.go000066400000000000000000000066711246513110500211120ustar00rootroot00000000000000package main import ( "github.com/emicklei/go-restful" "log" "net/http" "time" ) type User struct { Id, Name string } type UserList struct { Users []User } // This example show how to create and use the three different Filters (Container,WebService and Route) // When applied to the restful.DefaultContainer, we refer to them as a global filter. // // GET http://locahost: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.Printf("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.Printf("getAllUsers") response.WriteEntity(UserList{[]User{User{"42", "Gandalf"}, User{"3.14", "Pi"}}}) } // GET http://localhost:8080/users/42 // func findUser(request *restful.Request, response *restful.Response) { log.Printf("findUser") response.WriteEntity(User{"42", "Gandalf"}) } go-restful-1.1.3/examples/restful-form-handling.go000066400000000000000000000027671246513110500221710ustar00rootroot00000000000000package main import ( "fmt" "github.com/emicklei/go-restful" "github.com/gorilla/schema" "io" "net/http" ) // This example shows how to handle a POST of a HTML form that uses the standard x-www-form-urlencoded content-type. // It uses the gorilla web tool kit schema package to decode the form data into a struct. // // GET http://localhost:8080/profiles // type Profile struct { Name string Age int } var decoder *schema.Decoder func main() { decoder = schema.NewDecoder() ws := new(restful.WebService) ws.Route(ws.POST("/profiles").Consumes("application/x-www-form-urlencoded").To(postAdddress)) ws.Route(ws.GET("/profiles").To(addresssForm)) restful.Add(ws) 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-1.1.3/examples/restful-hello-world.go000066400000000000000000000006561246513110500216670ustar00rootroot00000000000000package main import ( "github.com/emicklei/go-restful" "io" "net/http" ) // This example shows the minimal code needed to get a restful.WebService working. // // GET http://localhost:8080/hello func main() { ws := new(restful.WebService) ws.Route(ws.GET("/hello").To(hello)) restful.Add(ws) http.ListenAndServe(":8080", nil) } func hello(req *restful.Request, resp *restful.Response) { io.WriteString(resp, "world") } go-restful-1.1.3/examples/restful-html-template.go000066400000000000000000000013301246513110500222020ustar00rootroot00000000000000package main import ( "log" "net/http" "text/template" "github.com/emicklei/go-restful" ) // This example shows how to serve a HTML page using the standard Go template engine. // // GET http://localhost:8080/ func main() { ws := new(restful.WebService) ws.Route(ws.GET("/").To(home)) restful.Add(ws) print("open browser on http://localhost:8080/\n") 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-1.1.3/examples/restful-multi-containers.go000066400000000000000000000022741246513110500227320ustar00rootroot00000000000000package main import ( "github.com/emicklei/go-restful" "io" "log" "net/http" ) // This example shows how to have a program with 2 WebServices containers // each having a http server listening on its own port. // // The first "hello" is added to the restful.DefaultContainer (and uses DefaultServeMux) // For the second "hello", a new container and ServeMux is created // and requires a new http.Server with the container being the Handler. // This first server is spawn in its own go-routine such that the program proceeds to create the second. // // GET http://localhost:8080/hello // GET http://localhost:8081/hello func main() { ws := new(restful.WebService) ws.Route(ws.GET("/hello").To(hello)) restful.Add(ws) go func() { 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-1.1.3/examples/restful-options-filter.go000066400000000000000000000022731246513110500224120ustar00rootroot00000000000000package main import ( "github.com/emicklei/go-restful" "io" "log" "net/http" ) // This example shows how to use the OPTIONSFilter on a Container // // OPTIONS http://localhost:8080/users // // OPTIONS http://localhost:8080/users/1 type UserResource struct{} func (u UserResource) RegisterTo(container *restful.Container) { ws := new(restful.WebService) ws. Path("/users"). Consumes("*/*"). Produces("*/*") ws.Route(ws.GET("/{user-id}").To(u.nop)) ws.Route(ws.POST("").To(u.nop)) ws.Route(ws.PUT("/{user-id}").To(u.nop)) ws.Route(ws.DELETE("/{user-id}").To(u.nop)) container.Add(ws) } func (u UserResource) nop(request *restful.Request, response *restful.Response) { io.WriteString(response.ResponseWriter, "this would be a normal response") } func main() { wsContainer := restful.NewContainer() u := UserResource{} u.RegisterTo(wsContainer) // Add container filter to respond to OPTIONS wsContainer.Filter(wsContainer.OPTIONSFilter) // For use on the default container, you can write // restful.Filter(restful.OPTIONSFilter()) log.Printf("start listening on localhost:8080") server := &http.Server{Addr: ":8080", Handler: wsContainer} log.Fatal(server.ListenAndServe()) } go-restful-1.1.3/examples/restful-path-tail.go000066400000000000000000000012641246513110500213160ustar00rootroot00000000000000package main import ( "io" "net/http" . "github.com/emicklei/go-restful" ) // This example shows how to a Route that matches 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") http.ListenAndServe(":8080", nil) } func staticFromPathParam(req *Request, resp *Response) { io.WriteString(resp, "Tail="+req.PathParameter("resource")) } go-restful-1.1.3/examples/restful-pre-post-filters.go000066400000000000000000000052641246513110500226560ustar00rootroot00000000000000package main import ( "github.com/emicklei/go-restful" "io" "log" "net/http" ) // This example shows how the different types of filters are called in the request-response flow. // The call chain is logged on the console when sending an http request. // // GET http://localhost:8080/1 // GET http://localhost:8080/2 var indentLevel int func container_filter_A(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { log.Printf("url path:%v\n", req.Request.URL) trace("container_filter_A: before", 1) chain.ProcessFilter(req, resp) trace("container_filter_A: after", -1) } func container_filter_B(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { trace("container_filter_B: before", 1) chain.ProcessFilter(req, resp) trace("container_filter_B: after", -1) } func service_filter_A(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { trace("service_filter_A: before", 1) chain.ProcessFilter(req, resp) trace("service_filter_A: after", -1) } func service_filter_B(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { trace("service_filter_B: before", 1) chain.ProcessFilter(req, resp) trace("service_filter_B: after", -1) } func route_filter_A(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { trace("route_filter_A: before", 1) chain.ProcessFilter(req, resp) trace("route_filter_A: after", -1) } func route_filter_B(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { trace("route_filter_B: before", 1) chain.ProcessFilter(req, resp) trace("route_filter_B: after", -1) } func trace(what string, delta int) { indented := what if delta < 0 { indentLevel += delta } for t := 0; t < indentLevel; t++ { indented = "." + indented } log.Printf("%s", indented) if delta > 0 { indentLevel += delta } } func main() { restful.Filter(container_filter_A) restful.Filter(container_filter_B) ws1 := new(restful.WebService) ws1.Path("/1") ws1.Filter(service_filter_A) ws1.Filter(service_filter_B) ws1.Route(ws1.GET("").To(doit1).Filter(route_filter_A).Filter(route_filter_B)) ws2 := new(restful.WebService) ws2.Path("/2") ws2.Filter(service_filter_A) ws2.Filter(service_filter_B) ws2.Route(ws2.GET("").To(doit2).Filter(route_filter_A).Filter(route_filter_B)) restful.Add(ws1) restful.Add(ws2) log.Print("go-restful example listing on http://localhost:8080/1 and http://localhost:8080/2") log.Fatal(http.ListenAndServe(":8080", nil)) } func doit1(req *restful.Request, resp *restful.Response) { io.WriteString(resp, "nothing to see in 1") } func doit2(req *restful.Request, resp *restful.Response) { io.WriteString(resp, "nothing to see in 2") } go-restful-1.1.3/examples/restful-resource-functions.go000066400000000000000000000033761246513110500232760ustar00rootroot00000000000000package main import ( "github.com/emicklei/go-restful" "log" "net/http" ) // This example shows how to use methods as RouteFunctions for WebServices. // The ProductResource has a Register() method that creates and initializes // a WebService to expose its methods as REST operations. // The WebService is added to the restful.DefaultContainer. // A ProductResource is typically created using some data access object. // // GET http://localhost:8080/products/1 // POST http://localhost:8080/products // 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() http.ListenAndServe(":8080", nil) } go-restful-1.1.3/examples/restful-route_test.go000066400000000000000000000015151246513110500216270ustar00rootroot00000000000000package 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-1.1.3/examples/restful-routefunction_test.go000066400000000000000000000011731246513110500233750ustar00rootroot00000000000000package 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(404) } func TestCallFunction(t *testing.T) { httpReq, _ := http.NewRequest("GET", "/", nil) req := restful.NewRequest(httpReq) recorder := new(httptest.ResponseRecorder) resp := restful.NewResponse(recoder) getIt(req, resp) if recorder.Code != 404 { t.Logf("Missing or wrong status code:%d", recorder.Code) } } go-restful-1.1.3/examples/restful-serve-static.go000066400000000000000000000022641246513110500220450ustar00rootroot00000000000000package main import ( "fmt" "net/http" "path" "github.com/emicklei/go-restful" ) // This example shows how to define methods that serve static files // It uses the standard http.ServeFile method // // GET http://localhost:8080/static/test.xml // GET http://localhost:8080/static/ // // GET http://localhost:8080/static?resource=subdir/test.xml var rootdir = "/tmp" func main() { restful.DefaultContainer.Router(restful.CurlyRouter{}) ws := new(restful.WebService) ws.Route(ws.GET("/static/{subpath:*}").To(staticFromPathParam)) ws.Route(ws.GET("/static").To(staticFromQueryParam)) restful.Add(ws) println("[go-restful] serving files on http://localhost:8080/static from local /tmp") 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-1.1.3/examples/restful-user-resource.go000066400000000000000000000112641246513110500222370ustar00rootroot00000000000000package main import ( "log" "net/http" "strconv" "github.com/emicklei/go-restful" "github.com/emicklei/go-restful/swagger" ) // This example show a complete (GET,PUT,POST,DELETE) conventional example of // a REST Resource including documentation to be served by e.g. a Swagger UI // It is recommended to create a Resource struct (UserResource) that can encapsulate // an object that provide domain access (a DAO) // It has a Register method including the complete Route mapping to methods together // with all the appropriate documentation // // POST http://localhost:8080/users // 1Melissa Raspberry // // GET http://localhost:8080/users/1 // // PUT http://localhost:8080/users/1 // 1Melissa // // DELETE http://localhost:8080/users/1 // type User struct { Id, Name string } type UserResource struct { // normally one would use DAO (data access object) users map[string]User } func (u UserResource) Register(container *restful.Container) { ws := new(restful.WebService) ws. Path("/users"). Doc("Manage Users"). Consumes(restful.MIME_XML, restful.MIME_JSON). Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well ws.Route(ws.GET("/{user-id}").To(u.findUser). // docs Doc("get a user"). Operation("findUser"). Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")). Writes(User{})) // on the response ws.Route(ws.PUT("/{user-id}").To(u.updateUser). // docs Doc("update a user"). Operation("updateUser"). Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")). ReturnsError(409, "duplicate user-id", nil). Reads(User{})) // from the request ws.Route(ws.POST("").To(u.createUser). // docs Doc("create a user"). Operation("createUser"). Reads(User{})) // from the request ws.Route(ws.DELETE("/{user-id}").To(u.removeUser). // docs Doc("delete a user"). Operation("removeUser"). Param(ws.PathParameter("user-id", "identifier of the user").DataType("string"))) container.Add(ws) } // GET http://localhost:8080/users/1 // func (u UserResource) findUser(request *restful.Request, response *restful.Response) { id := request.PathParameter("user-id") usr := u.users[id] if len(usr.Id) == 0 { response.AddHeader("Content-Type", "text/plain") response.WriteErrorString(http.StatusNotFound, "404: User could not be found.") return } response.WriteEntity(usr) } // POST http://localhost:8080/users // Melissa // func (u *UserResource) createUser(request *restful.Request, response *restful.Response) { usr := new(User) err := request.ReadEntity(usr) if err != nil { response.AddHeader("Content-Type", "text/plain") response.WriteErrorString(http.StatusInternalServerError, err.Error()) return } usr.Id = strconv.Itoa(len(u.users) + 1) // simple id generation u.users[usr.Id] = *usr response.WriteHeader(http.StatusCreated) 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 { response.AddHeader("Content-Type", "text/plain") response.WriteErrorString(http.StatusInternalServerError, err.Error()) return } u.users[usr.Id] = *usr response.WriteEntity(usr) } // 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() { // to see what happens in the package, uncomment the following //restful.TraceLogger(log.New(os.Stdout, "[restful] ", log.LstdFlags|log.Lshortfile)) wsContainer := restful.NewContainer() u := UserResource{map[string]User{}} u.Register(wsContainer) // 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 and enter http://localhost:8080/apidocs.json in the api input field. config := swagger.Config{ WebServices: wsContainer.RegisteredWebServices(), // you control what services are visible WebServicesUrl: "http://localhost:8080", ApiPath: "/apidocs.json", // Optionally, specifiy where the UI is located SwaggerPath: "/apidocs/", SwaggerFilePath: "/Users/emicklei/xProjects/swagger-ui/dist"} swagger.RegisterSwaggerService(config, wsContainer) log.Printf("start listening on localhost:8080") server := &http.Server{Addr: ":8080", Handler: wsContainer} log.Fatal(server.ListenAndServe()) } go-restful-1.1.3/examples/restful-user-service.go000066400000000000000000000076111246513110500220510ustar00rootroot00000000000000package main import ( "log" "net/http" "github.com/emicklei/go-restful" "github.com/emicklei/go-restful/swagger" ) // This example is functionally the same as the example in restful-user-resource.go // with the only difference that is served using the restful.DefaultContainer type User struct { Id, Name string } type UserService struct { // normally one would use DAO (data access object) users map[string]User } func (u UserService) Register() { ws := new(restful.WebService) ws. Path("/users"). Consumes(restful.MIME_XML, restful.MIME_JSON). Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well ws.Route(ws.GET("/").To(u.findAllUsers). // docs Doc("get all users"). Operation("findAllUsers"). Returns(200, "OK", []User{})) ws.Route(ws.GET("/{user-id}").To(u.findUser). // docs Doc("get a user"). Operation("findUser"). Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")). Writes(User{})) // on the response ws.Route(ws.PUT("/{user-id}").To(u.updateUser). // docs Doc("update a user"). Operation("updateUser"). Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")). Reads(User{})) // from the request ws.Route(ws.PUT("").To(u.createUser). // docs Doc("create a user"). Operation("createUser"). Reads(User{})) // from the request ws.Route(ws.DELETE("/{user-id}").To(u.removeUser). // docs Doc("delete a user"). Operation("removeUser"). Param(ws.PathParameter("user-id", "identifier of the user").DataType("string"))) restful.Add(ws) } // GET http://localhost:8080/users // func (u UserService) findAllUsers(request *restful.Request, response *restful.Response) { response.WriteEntity(u.users) } // GET http://localhost:8080/users/1 // func (u UserService) 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 *UserService) 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 *UserService) 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.WriteError(http.StatusInternalServerError, err) } } // DELETE http://localhost:8080/users/1 // func (u *UserService) removeUser(request *restful.Request, response *restful.Response) { id := request.PathParameter("user-id") delete(u.users, id) } func main() { u := UserService{map[string]User{}} u.Register() // Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API // You need to download the Swagger HTML5 assets and change the FilePath location in the config below. // Open http://localhost:8080/apidocs and enter http://localhost:8080/apidocs.json in the api input field. config := swagger.Config{ WebServices: restful.RegisteredWebServices(), // you control what services are visible WebServicesUrl: "http://localhost:8080", ApiPath: "/apidocs.json", // Optionally, specifiy where the UI is located SwaggerPath: "/apidocs/", SwaggerFilePath: "/Users/emicklei/Projects/swagger-ui/dist"} swagger.InstallSwaggerService(config) log.Printf("start listening on localhost:8080") log.Fatal(http.ListenAndServe(":8080", nil)) } go-restful-1.1.3/filter.go000066400000000000000000000021021246513110500154100ustar00rootroot00000000000000package restful // Copyright 2013 Ernest Micklei. All rights reserved. // Use of this source code is governed by a license // that can be found in the LICENSE file. // FilterChain is a request scoped object to process one or more filters before calling the target RouteFunction. type FilterChain struct { Filters []FilterFunction // ordered list of FilterFunction Index int // index into filters that is currently in progress Target RouteFunction // function to call after passing all filters } // ProcessFilter passes the request,response pair through the next of Filters. // Each filter can decide to proceed to the next Filter or handle the Response itself. func (f *FilterChain) ProcessFilter(request *Request, response *Response) { if f.Index < len(f.Filters) { f.Index++ f.Filters[f.Index-1](request, response, f) } else { f.Target(request, response) } } // FilterFunction definitions must call ProcessFilter on the FilterChain to pass on the control and eventually call the RouteFunction type FilterFunction func(*Request, *Response, *FilterChain) go-restful-1.1.3/filter_test.go000066400000000000000000000070721246513110500164620ustar00rootroot00000000000000package 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-1.1.3/install.sh000066400000000000000000000003241246513110500156020ustar00rootroot00000000000000cd examples ls *.go | xargs -I {} go build {} cd .. go fmt ...swagger && \ go test -test.v ...swagger && \ go install ...swagger && \ go fmt ...restful && \ go test -test.v ...restful && \ go install ...restfulgo-restful-1.1.3/jsr311.go000066400000000000000000000176631246513110500151700ustar00rootroot00000000000000package restful // Copyright 2013 Ernest Micklei. All rights reserved. // Use of this source code is governed by a license // that can be found in the LICENSE file. import ( "errors" "fmt" "net/http" "sort" ) // RouterJSR311 implements the flow for matching Requests to Routes (and consequently Resource Functions) // as specified by the JSR311 http://jsr311.java.net/nonav/releases/1.1/spec/spec.html. // RouterJSR311 implements the Router interface. // Concept of locators is not implemented. type RouterJSR311 struct{} // SelectRoute is part of the Router interface and returns the best match // for the WebService and its Route for the given Request. func (r RouterJSR311) SelectRoute( webServices []*WebService, httpRequest *http.Request) (selectedService *WebService, selectedRoute *Route, err error) { // Identify the root resource class (WebService) dispatcher, finalMatch, err := r.detectDispatcher(httpRequest.URL.Path, webServices) if err != nil { return nil, nil, NewError(http.StatusNotFound, "") } // Obtain the set of candidate methods (Routes) routes := r.selectRoutes(dispatcher, finalMatch) if len(routes) == 0 { return dispatcher, nil, NewError(http.StatusNotFound, "404: Page Not Found") } // Identify the method (Route) that will handle the request route, ok := r.detectRoute(routes, httpRequest) return dispatcher, route, ok } // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*Route, error) { // http method methodOk := []Route{} for _, each := range routes { if httpRequest.Method == each.Method { methodOk = append(methodOk, each) } } if len(methodOk) == 0 { if trace { traceLogger.Printf("no Route found (in %d routes) that matches HTTP method %s\n", len(routes), httpRequest.Method) } return nil, NewError(http.StatusMethodNotAllowed, "405: Method Not Allowed") } inputMediaOk := methodOk // content-type contentType := httpRequest.Header.Get(HEADER_ContentType) if httpRequest.ContentLength > 0 { inputMediaOk = []Route{} for _, each := range methodOk { if each.matchesContentType(contentType) { inputMediaOk = append(inputMediaOk, each) } } if len(inputMediaOk) == 0 { if trace { traceLogger.Printf("no Route found (from %d) that matches HTTP Content-Type: %s\n", len(methodOk), contentType) } return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type") } } // accept outputMediaOk := []Route{} accept := httpRequest.Header.Get(HEADER_Accept) if accept == "" { accept = "*/*" } for _, each := range inputMediaOk { if each.matchesAccept(accept) { outputMediaOk = append(outputMediaOk, each) } } if len(outputMediaOk) == 0 { if trace { traceLogger.Printf("no Route found (from %d) that matches HTTP Accept: %s\n", len(inputMediaOk), accept) } return nil, NewError(http.StatusNotAcceptable, "406: Not Acceptable") } return r.bestMatchByMedia(outputMediaOk, contentType, accept), nil } // 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-1.1.3/jsr311_test.go000066400000000000000000000141771246513110500162240ustar00rootroot00000000000000package restful import ( "io" "sort" "testing" ) // // Step 1 tests // var paths = []struct { // url with path (1) is handled by service with root (2) and last capturing group has value final (3) path, root, final string }{ {"/", "/", "/"}, {"/p", "/p", ""}, {"/p/x", "/p/{q}", ""}, {"/q/x", "/q", "/x"}, {"/p/x/", "/p/{q}", "/"}, {"/p/x/y", "/p/{q}", "/y"}, {"/q/x/y", "/q", "/x/y"}, {"/z/q", "/{p}/q", ""}, {"/a/b/c/q", "/", "/a/b/c/q"}, } func TestDetectDispatcher(t *testing.T) { ws1 := new(WebService).Path("/") ws2 := new(WebService).Path("/p") ws3 := new(WebService).Path("/q") ws4 := new(WebService).Path("/p/q") ws5 := new(WebService).Path("/p/{q}") ws6 := new(WebService).Path("/p/{q}/") ws7 := new(WebService).Path("/{p}/q") var dispatchers = []*WebService{ws1, ws2, ws3, ws4, ws5, ws6, ws7} wc := NewContainer() for _, each := range dispatchers { wc.Add(each) } router := RouterJSR311{} ok := true for i, fixture := range paths { who, final, err := router.detectDispatcher(fixture.path, dispatchers) if err != nil { t.Logf("error in detection:%v", err) ok = false } if who.RootPath() != fixture.root { t.Logf("[line:%v] Unexpected dispatcher, expected:%v, actual:%v", i, fixture.root, who.RootPath()) ok = false } if final != fixture.final { t.Logf("[line:%v] Unexpected final, expected:%v, actual:%v", i, fixture.final, final) ok = false } } if !ok { t.Fail() } } // // Step 2 tests // // go test -v -test.run TestISSUE_179 ...restful func TestISSUE_179(t *testing.T) { ws1 := new(WebService) ws1.Route(ws1.GET("/v1/category/{param:*}").To(dummy)) routes := RouterJSR311{}.selectRoutes(ws1, "/v1/category/sub/sub") t.Logf("%v", routes) } // go test -v -test.run TestISSUE_30 ...restful func TestISSUE_30(t *testing.T) { ws1 := new(WebService).Path("/users") ws1.Route(ws1.GET("/{id}").To(dummy)) ws1.Route(ws1.POST("/login").To(dummy)) routes := RouterJSR311{}.selectRoutes(ws1, "/login") if len(routes) != 2 { t.Fatal("expected 2 routes") } if routes[0].Path != "/users/login" { t.Error("first is", routes[0].Path) t.Logf("routes:%v", routes) } } // go test -v -test.run TestISSUE_34 ...restful func TestISSUE_34(t *testing.T) { ws1 := new(WebService).Path("/") ws1.Route(ws1.GET("/{type}/{id}").To(dummy)) ws1.Route(ws1.GET("/network/{id}").To(dummy)) routes := RouterJSR311{}.selectRoutes(ws1, "/network/12") if len(routes) != 2 { t.Fatal("expected 2 routes") } if routes[0].Path != "/network/{id}" { t.Error("first is", routes[0].Path) t.Logf("routes:%v", routes) } } // go test -v -test.run TestISSUE_34_2 ...restful func TestISSUE_34_2(t *testing.T) { ws1 := new(WebService).Path("/") // change the registration order ws1.Route(ws1.GET("/network/{id}").To(dummy)) ws1.Route(ws1.GET("/{type}/{id}").To(dummy)) routes := RouterJSR311{}.selectRoutes(ws1, "/network/12") if len(routes) != 2 { t.Fatal("expected 2 routes") } if routes[0].Path != "/network/{id}" { t.Error("first is", routes[0].Path) } } // go test -v -test.run TestISSUE_137 ...restful func TestISSUE_137(t *testing.T) { ws1 := new(WebService) ws1.Route(ws1.GET("/hello").To(dummy)) routes := RouterJSR311{}.selectRoutes(ws1, "/") t.Log(routes) if len(routes) > 0 { t.Error("no route expected") } } func TestSelectRoutesSlash(t *testing.T) { ws1 := new(WebService).Path("/") ws1.Route(ws1.GET("").To(dummy)) ws1.Route(ws1.GET("/").To(dummy)) ws1.Route(ws1.GET("/u").To(dummy)) ws1.Route(ws1.POST("/u").To(dummy)) ws1.Route(ws1.POST("/u/v").To(dummy)) ws1.Route(ws1.POST("/u/{w}").To(dummy)) ws1.Route(ws1.POST("/u/{w}/z").To(dummy)) routes := RouterJSR311{}.selectRoutes(ws1, "/u") checkRoutesContains(routes, "/u", t) checkRoutesContainsNo(routes, "/u/v", t) checkRoutesContainsNo(routes, "/", t) checkRoutesContainsNo(routes, "/u/{w}/z", t) } func TestSelectRoutesU(t *testing.T) { ws1 := new(WebService).Path("/u") ws1.Route(ws1.GET("").To(dummy)) ws1.Route(ws1.GET("/").To(dummy)) ws1.Route(ws1.GET("/v").To(dummy)) ws1.Route(ws1.POST("/{w}").To(dummy)) ws1.Route(ws1.POST("/{w}/z").To(dummy)) // so full path = /u/{w}/z routes := RouterJSR311{}.selectRoutes(ws1, "/v") // test against /u/v checkRoutesContains(routes, "/u/{w}", t) } func TestSelectRoutesUsers1(t *testing.T) { ws1 := new(WebService).Path("/users") ws1.Route(ws1.POST("").To(dummy)) ws1.Route(ws1.POST("/").To(dummy)) ws1.Route(ws1.PUT("/{id}").To(dummy)) routes := RouterJSR311{}.selectRoutes(ws1, "/1") checkRoutesContains(routes, "/users/{id}", t) } func checkRoutesContains(routes []Route, path string, t *testing.T) { if !containsRoutePath(routes, path, t) { for _, r := range routes { t.Logf("route %v %v", r.Method, r.Path) } t.Fatalf("routes should include [%v]:", path) } } func checkRoutesContainsNo(routes []Route, path string, t *testing.T) { if containsRoutePath(routes, path, t) { for _, r := range routes { t.Logf("route %v %v", r.Method, r.Path) } t.Fatalf("routes should not include [%v]:", path) } } func containsRoutePath(routes []Route, path string, t *testing.T) bool { for _, each := range routes { if each.Path == path { return true } } return false } // go test -v -test.run TestSortableRouteCandidates ...restful func TestSortableRouteCandidates(t *testing.T) { fixture := &sortableRouteCandidates{} r1 := routeCandidate{matchesCount: 0, literalCount: 0, nonDefaultCount: 0} r2 := routeCandidate{matchesCount: 0, literalCount: 0, nonDefaultCount: 1} r3 := routeCandidate{matchesCount: 0, literalCount: 1, nonDefaultCount: 1} r4 := routeCandidate{matchesCount: 1, literalCount: 1, nonDefaultCount: 0} r5 := routeCandidate{matchesCount: 1, literalCount: 0, nonDefaultCount: 0} fixture.candidates = append(fixture.candidates, r5, r4, r3, r2, r1) sort.Sort(sort.Reverse(fixture)) first := fixture.candidates[0] if first.matchesCount != 1 && first.literalCount != 1 && first.nonDefaultCount != 0 { t.Fatal("expected r4") } last := fixture.candidates[len(fixture.candidates)-1] if last.matchesCount != 0 && last.literalCount != 0 && last.nonDefaultCount != 0 { t.Fatal("expected r1") } } func dummy(req *Request, resp *Response) { io.WriteString(resp.ResponseWriter, "dummy") } go-restful-1.1.3/logger.go000066400000000000000000000006611246513110500154120ustar00rootroot00000000000000package restful import "log" // 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. var trace bool = false var traceLogger *log.Logger // TraceLogger enables detailed logging of Http request matching and filter invocation. Default no logger is set. func TraceLogger(logger *log.Logger) { traceLogger = logger trace = logger != nil } go-restful-1.1.3/options_filter.go000066400000000000000000000017011246513110500171670ustar00rootroot00000000000000package 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 func (c Container) OPTIONSFilter(req *Request, resp *Response, chain *FilterChain) { if "OPTIONS" != req.Request.Method { chain.ProcessFilter(req, resp) return } resp.AddHeader(HEADER_Allow, strings.Join(c.computeAllowedMethods(req), ",")) } // 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. func OPTIONSFilter() FilterFunction { return DefaultContainer.OPTIONSFilter } go-restful-1.1.3/options_filter_test.go000066400000000000000000000016771246513110500202420ustar00rootroot00000000000000package 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-1.1.3/parameter.go000066400000000000000000000047111246513110500161130ustar00rootroot00000000000000package restful // Copyright 2013 Ernest Micklei. All rights reserved. // Use of this source code is governed by a license // that can be found in the LICENSE file. const ( // PathParameterKind = indicator of Request parameter type "path" PathParameterKind = iota // QueryParameterKind = indicator of Request parameter type "query" QueryParameterKind // BodyParameterKind = indicator of Request parameter type "body" BodyParameterKind // HeaderParameterKind = indicator of Request parameter type "header" HeaderParameterKind // FormParameterKind = indicator of Request parameter type "form" FormParameterKind ) // Parameter is for documententing the parameter used in a Http Request // ParameterData kinds are Path,Query and Body type Parameter struct { data *ParameterData } // ParameterData represents the state of a Parameter. // It is made public to make it accessible to e.g. the Swagger package. type ParameterData struct { Name, Description, DataType string Kind int Required bool AllowableValues map[string]string AllowMultiple 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 return the receiver func (p *Parameter) Required(required bool) *Parameter { p.data.Required = required return p } // AllowMultiple sets the allowMultiple field and return the receiver func (p *Parameter) AllowMultiple(multiple bool) *Parameter { p.data.AllowMultiple = multiple return p } // AllowableValues sets the allowableValues field and return the receiver func (p *Parameter) AllowableValues(values map[string]string) *Parameter { p.data.AllowableValues = values return p } // DataType sets the dataType field and return the receiver func (p *Parameter) DataType(typeName string) *Parameter { p.data.DataType = typeName return p } go-restful-1.1.3/path_expression.go000066400000000000000000000043421246513110500173460ustar00rootroot00000000000000package restful // Copyright 2013 Ernest Micklei. All rights reserved. // Use of this source code is governed by a license // that can be found in the LICENSE file. import ( "bytes" "fmt" "regexp" "strings" ) // PathExpression holds a compiled path expression (RegExp) needed to match against // Http request paths and to extract path parameter values. type pathExpression struct { LiteralCount int // the number of literal characters (means those not resulting from template variable substitution) VarCount int // the number of named parameters (enclosed by {}) in the path Matcher *regexp.Regexp Source string // Path as defined by the RouteBuilder tokens []string } // NewPathExpression creates a PathExpression from the input URL path. // Returns an error if the path is invalid. func newPathExpression(path string) (*pathExpression, error) { expression, literalCount, varCount, tokens := templateToRegularExpression(path) compiled, err := regexp.Compile(expression) if err != nil { return nil, err } return &pathExpression{literalCount, varCount, compiled, expression, tokens}, nil } // http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-370003.7.3 func templateToRegularExpression(template string) (expression string, literalCount int, varCount int, tokens []string) { var buffer bytes.Buffer buffer.WriteString("^") //tokens = strings.Split(template, "/") tokens = tokenizePath(template) for _, each := range tokens { if each == "" { continue } buffer.WriteString("/") if strings.HasPrefix(each, "{") { // check for regular expression in variable colon := strings.Index(each, ":") if colon != -1 { // extract expression paramExpr := strings.TrimSpace(each[colon+1 : len(each)-1]) if paramExpr == "*" { // special case buffer.WriteString("(.*)") } else { buffer.WriteString(fmt.Sprintf("(%s)", paramExpr)) // between colon and closing moustache } } else { // plain var buffer.WriteString("([^/]+?)") } varCount += 1 } else { literalCount += len(each) encoded := each // TODO URI encode buffer.WriteString(regexp.QuoteMeta(encoded)) } } return strings.TrimRight(buffer.String(), "/") + "(/.*)?$", literalCount, varCount, tokens } go-restful-1.1.3/path_expression_test.go000066400000000000000000000021641246513110500204050ustar00rootroot00000000000000package restful import "testing" var tempregexs = []struct { template, regex string literalCount, varCount int }{ {"", "^(/.*)?$", 0, 0}, {"/a/{b}/c/", "^/a/([^/]+?)/c(/.*)?$", 2, 1}, {"/{a}/{b}/{c-d-e}/", "^/([^/]+?)/([^/]+?)/([^/]+?)(/.*)?$", 0, 3}, {"/{p}/abcde", "^/([^/]+?)/abcde(/.*)?$", 5, 1}, {"/a/{b:*}", "^/a/(.*)(/.*)?$", 1, 1}, {"/a/{b:[a-z]+}", "^/a/([a-z]+)(/.*)?$", 1, 1}, } func TestTemplateToRegularExpression(t *testing.T) { ok := true for i, fixture := range tempregexs { actual, lCount, vCount, _ := templateToRegularExpression(fixture.template) if actual != fixture.regex { t.Logf("regex mismatch, expected:%v , actual:%v, line:%v\n", fixture.regex, actual, i) // 11 = where the data starts ok = false } if lCount != fixture.literalCount { t.Logf("literal count mismatch, expected:%v , actual:%v, line:%v\n", fixture.literalCount, lCount, i) ok = false } if vCount != fixture.varCount { t.Logf("variable count mismatch, expected:%v , actual:%v, line:%v\n", fixture.varCount, vCount, i) ok = false } } if !ok { t.Fatal("one or more expression did not match") } } go-restful-1.1.3/request.go000066400000000000000000000107401246513110500156220ustar00rootroot00000000000000package 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" "encoding/json" "encoding/xml" "io" "io/ioutil" "net/http" "strings" ) var defaultRequestContentType string var doCacheReadEntityBytes = true // Request is a wrapper for a http Request that provides convenience methods type Request struct { Request *http.Request bodyContent *[]byte // to cache the request body for multiple reads of ReadEntity pathParameters map[string]string attributes map[string]interface{} // for storing request-scoped values selectedRoutePath string // root path + route path that matched the request, e.g. /meetings/{id}/attendees } func NewRequest(httpRequest *http.Request) *Request { return &Request{ Request: httpRequest, pathParameters: map[string]string{}, attributes: map[string]interface{}{}, } // empty parameters, attributes } // If ContentType is missing or */* is given then fall back to this type, otherwise // a "Unable to unmarshal content of type:" response is returned. // Valid values are restful.MIME_JSON and restful.MIME_XML // Example: // restful.DefaultRequestContentType(restful.MIME_JSON) func DefaultRequestContentType(mime string) { defaultRequestContentType = mime } // SetCacheReadEntity controls whether the response data ([]byte) is cached such that ReadEntity is repeatable. // Default is true (due to backwardcompatibility). For better performance, you should set it to false if you don't need it. func SetCacheReadEntity(doCache bool) { doCacheReadEntityBytes = doCache } // PathParameter accesses the Path parameter value by its name func (r *Request) PathParameter(name string) string { return r.pathParameters[name] } // PathParameters accesses the Path parameter values func (r *Request) PathParameters() map[string]string { return r.pathParameters } // QueryParameter returns the (first) Query parameter value by its name func (r *Request) QueryParameter(name string) string { return r.Request.FormValue(name) } // BodyParameter parses the body of the request (once for typically a POST or a PUT) and returns the value of the given name or an error. func (r *Request) BodyParameter(name string) (string, error) { err := r.Request.ParseForm() if err != nil { return "", err } return r.Request.PostFormValue(name), nil } // HeaderParameter returns the HTTP Header value of a Header name or empty if missing func (r *Request) HeaderParameter(name string) string { return r.Request.Header.Get(name) } // ReadEntity checks the Accept header and reads the content into the entityPointer // May be called multiple times in the request-response flow func (r *Request) ReadEntity(entityPointer interface{}) (err error) { contentType := r.Request.Header.Get(HEADER_ContentType) if doCacheReadEntityBytes { return r.cachingReadEntity(contentType, entityPointer) } // unmarshall directly from request Body return r.decodeEntity(r.Request.Body, contentType, entityPointer) } func (r *Request) cachingReadEntity(contentType string, entityPointer interface{}) (err error) { var buffer []byte if r.bodyContent != nil { buffer = *r.bodyContent } else { buffer, err = ioutil.ReadAll(r.Request.Body) if err != nil { return err } r.bodyContent = &buffer } return r.decodeEntity(bytes.NewReader(buffer), contentType, entityPointer) } func (r *Request) decodeEntity(reader io.Reader, contentType string, entityPointer interface{}) (err error) { if strings.Contains(contentType, MIME_XML) { return xml.NewDecoder(reader).Decode(entityPointer) } if strings.Contains(contentType, MIME_JSON) || MIME_JSON == defaultRequestContentType { decoder := json.NewDecoder(reader) decoder.UseNumber() return decoder.Decode(entityPointer) } if MIME_XML == defaultRequestContentType { return xml.NewDecoder(reader).Decode(entityPointer) } return NewError(400, "Unable to unmarshal content of type:"+contentType) } // SetAttribute adds or replaces the attribute with the given value. func (r *Request) SetAttribute(name string, value interface{}) { r.attributes[name] = value } // Attribute returns the value associated to the given name. Returns nil if absent. func (r Request) Attribute(name string) interface{} { return r.attributes[name] } // SelectedRoutePath root path + route path that matched the request, e.g. /meetings/{id}/attendees func (r Request) SelectedRoutePath() string { return r.selectedRoutePath } go-restful-1.1.3/request_test.go000066400000000000000000000131551246513110500166640ustar00rootroot00000000000000package restful import ( "encoding/json" "net/http" "net/url" "strconv" "strings" "testing" ) func TestQueryParameter(t *testing.T) { hreq := http.Request{Method: "GET"} hreq.URL, _ = url.Parse("http://www.google.com/search?q=foo&q=bar") rreq := Request{Request: &hreq} if rreq.QueryParameter("q") != "foo" { t.Errorf("q!=foo %#v", rreq) } } type Anything map[string]interface{} type Number struct { ValueFloat float64 ValueInt int64 } type Sample struct { Value string } func TestReadEntityXml(t *testing.T) { SetCacheReadEntity(true) bodyReader := strings.NewReader("42") httpRequest, _ := http.NewRequest("GET", "/test", bodyReader) httpRequest.Header.Set("Content-Type", "application/xml") request := &Request{Request: httpRequest} sam := new(Sample) request.ReadEntity(sam) if sam.Value != "42" { t.Fatal("read failed") } if request.bodyContent == nil { t.Fatal("no expected cached bytes found") } } func TestReadEntityXmlNonCached(t *testing.T) { SetCacheReadEntity(false) bodyReader := strings.NewReader("42") httpRequest, _ := http.NewRequest("GET", "/test", bodyReader) httpRequest.Header.Set("Content-Type", "application/xml") request := &Request{Request: httpRequest} sam := new(Sample) request.ReadEntity(sam) if sam.Value != "42" { t.Fatal("read failed") } if request.bodyContent != nil { t.Fatal("unexpected cached bytes found") } } 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) { SetCacheReadEntity(true) 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 TestReadEntityJsonNumberNonCached(t *testing.T) { SetCacheReadEntity(false) 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-1.1.3/response.go000066400000000000000000000201511246513110500157650ustar00rootroot00000000000000package 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 ( "encoding/json" "encoding/xml" "net/http" "strings" ) // DEPRECATED, use DefaultResponseContentType(mime) var DefaultResponseMimeType string //PrettyPrintResponses controls the indentation feature of XML and JSON //serialization in the response methods WriteEntity, WriteAsJson, and //WriteAsXml. 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 explicity (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. } // Creates a new response based on a http ResponseWriter. func NewResponse(httpWriter http.ResponseWriter) *Response { return &Response{httpWriter, "", []string{}, http.StatusOK, 0, PrettyPrintResponses} // empty content-types } // If Accept header matching fails, fall back to this type, otherwise // a "406: Not Acceptable" response is returned. // 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 } // 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 } // WriteEntity marshals the value using the representation denoted by the Accept Header (XML or JSON) // If no Accept header is specified (or */*) then return the Content-Type as specified by the first in the Route.Produces. // If an Accept header is specified then return 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 nothing is written. You may want to call WriteHeader(http.StatusNotFound) instead. // Current implementation ignores any q-parameters in the Accept Header. func (r *Response) WriteEntity(value interface{}) error { if value == nil { // do not write a nil representation return nil } for _, qualifiedMime := range strings.Split(r.requestAccept, ",") { mime := strings.Trim(strings.Split(qualifiedMime, ";")[0], " ") if 0 == len(mime) || mime == "*/*" { for _, each := range r.routeProduces { if MIME_JSON == each { return r.WriteAsJson(value) } if MIME_XML == each { return r.WriteAsXml(value) } } } else { // mime is not blank; see if we have a match in Produces for _, each := range r.routeProduces { if mime == each { if MIME_JSON == each { return r.WriteAsJson(value) } if MIME_XML == each { return r.WriteAsXml(value) } } } } } if DefaultResponseMimeType == MIME_JSON { return r.WriteAsJson(value) } else if DefaultResponseMimeType == MIME_XML { return r.WriteAsXml(value) } else { if trace { traceLogger.Printf("mismatch in mime-types and no defaults; (http)Accept=%v,(route)Produces=%v\n", r.requestAccept, r.routeProduces) } r.WriteHeader(http.StatusNotAcceptable) // for recording only r.ResponseWriter.WriteHeader(http.StatusNotAcceptable) if _, err := r.Write([]byte("406: Not Acceptable")); err != nil { return err } } return nil } // WriteAsXml is a convenience method for writing a value in xml (requires Xml tags on the value) func (r *Response) WriteAsXml(value interface{}) error { var output []byte var err error if value == nil { // do not write a nil representation return nil } if r.prettyPrint { output, err = xml.MarshalIndent(value, " ", " ") } else { output, err = xml.Marshal(value) } if err != nil { return r.WriteError(http.StatusInternalServerError, err) } r.Header().Set(HEADER_ContentType, MIME_XML) if r.statusCode > 0 { // a WriteHeader was intercepted r.ResponseWriter.WriteHeader(r.statusCode) } _, err = r.Write([]byte(xml.Header)) if err != nil { return err } if _, err = r.Write(output); err != nil { return err } return nil } // WriteAsJson is a convenience method for writing a value in json func (r *Response) WriteAsJson(value interface{}) error { var output []byte var err error if value == nil { // do not write a nil representation return nil } if r.prettyPrint { output, err = json.MarshalIndent(value, " ", " ") } else { output, err = json.Marshal(value) } if err != nil { return r.WriteErrorString(http.StatusInternalServerError, err.Error()) } r.Header().Set(HEADER_ContentType, MIME_JSON) if r.statusCode > 0 { // a WriteHeader was intercepted r.ResponseWriter.WriteHeader(r.statusCode) } if _, err = r.Write(output); err != nil { return err } return nil } // WriteError write the http status and the error string on the response. func (r *Response) WriteError(httpStatus int, err error) error { return r.WriteErrorString(httpStatus, err.Error()) } // WriteServiceError is a convenience method for a responding with a ServiceError and a status func (r *Response) WriteServiceError(httpStatus int, err ServiceError) error { r.WriteHeader(httpStatus) // for recording only return r.WriteEntity(err) } // WriteErrorString is a convenience method for an error status with the actual error func (r *Response) WriteErrorString(status int, errorReason string) error { r.statusCode = status // for recording only r.ResponseWriter.WriteHeader(status) if _, err := r.Write([]byte(errorReason)); err != nil { return err } return nil } // WriteHeader is overridden to remember the Status Code that has been written. // Note that using this method, the status value is only written when // - calling WriteEntity, // - or directly calling WriteAsXml or WriteAsJson, // - or if the status is one for which no response is allowed (i.e., // 204 (http.StatusNoContent) or 304 (http.StatusNotModified)) func (r *Response) WriteHeader(httpStatus int) { r.statusCode = httpStatus // if 204 then WriteEntity will not be called so we need to pass this code if http.StatusNoContent == httpStatus || http.StatusNotModified == 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() } go-restful-1.1.3/response_test.go000066400000000000000000000126061246513110500170320ustar00rootroot00000000000000package restful import ( "errors" "net/http" "net/http/httptest" "testing" ) func TestWriteHeader(t *testing.T) { httpWriter := httptest.NewRecorder() resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, 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{httpWriter, "*/*", []string{"*/*"}, 0, 0, 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{httpWriter, "*/*", []string{"*/*"}, 0, 0, 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{httpWriter, "*/*", []string{"*/*"}, 0, 0, true} resp.WriteAsJson(food{"apple"}) if resp.ContentLength() != 22 { t.Errorf("Incorrect measured length:%d", resp.ContentLength()) } } // go test -v -test.run TestMeasureContentLengthJsonNotPretty ...restful func TestMeasureContentLengthJsonNotPretty(t *testing.T) { httpWriter := httptest.NewRecorder() resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, false} resp.WriteAsJson(food{"apple"}) if resp.ContentLength() != 16 { 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{httpWriter, "*/*", []string{"*/*"}, 0, 0, true} resp.WriteErrorString(404, "Invalid") if resp.ContentLength() != len("Invalid") { t.Errorf("Incorrect measured length:%d", resp.ContentLength()) } } // go test -v -test.run TestStatusCreatedAndContentTypeJson_Issue54 ...restful func TestStatusCreatedAndContentTypeJson_Issue54(t *testing.T) { httpWriter := httptest.NewRecorder() resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true} resp.WriteHeader(201) resp.WriteAsJson(food{"Juicy"}) if httpWriter.HeaderMap.Get("Content-Type") != "application/json" { t.Errorf("Expected content type json but got:%d", 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{httpWriter, "application/json", []string{"application/json"}, 0, 0, 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{httpWriter, "application/bogus,*/*;q=0.8", []string{"application/json"}, 0, 0, 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{httpWriter, " application/xml ,*/* ; q=0.8", []string{"application/json", "application/xml"}, 0, 0, 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{httpWriter, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", []string{"application/json"}, 0, 0, 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{httpWriter, "text/plain", []string{"text/plain"}, 0, 0, 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{httpWriter, "application/json", []string{"application/json"}, 0, 0, true} resp.WriteHeader(http.StatusNotModified) if httpWriter.Code != http.StatusNotModified { t.Errorf("Got %d want %d", httpWriter.Code, http.StatusNotModified) } } go-restful-1.1.3/route.go000066400000000000000000000111071246513110500152660ustar00rootroot00000000000000package restful // Copyright 2013 Ernest Micklei. All rights reserved. // Use of this source code is governed by a license // that can be found in the LICENSE file. import ( "bytes" "net/http" "strings" ) // RouteFunction declares the signature of a function that can be bound to a Route. type RouteFunction func(*Request, *Response) // Route binds a HTTP Method,Path,Consumes combination to a RouteFunction. type Route struct { Method string Produces []string Consumes []string Path string // webservice root path + described path Function RouteFunction Filters []FilterFunction // cached values for dispatching relativePath string pathParts []string pathExpr *pathExpression // cached compilation of relativePath as RegExp // documentation Doc string Notes string Operation string ParameterDocs []*Parameter ResponseErrors map[int]ResponseError ReadSample, WriteSample interface{} // structs that model an example request or response payload } // Initialize for Route func (r *Route) postBuild() { r.pathParts = tokenizePath(r.Path) } // Create Request and Response from their http versions func (r *Route) wrapRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request) (*Request, *Response) { params := r.extractParameters(httpRequest.URL.Path) wrappedRequest := NewRequest(httpRequest) wrappedRequest.pathParameters = params wrappedRequest.selectedRoutePath = r.Path wrappedResponse := NewResponse(httpWriter) wrappedResponse.requestAccept = httpRequest.Header.Get(HEADER_Accept) wrappedResponse.routeProduces = r.Produces return wrappedRequest, wrappedResponse } // dispatchWithFilters call the function after passing through its own filters func (r *Route) dispatchWithFilters(wrappedRequest *Request, wrappedResponse *Response) { if len(r.Filters) > 0 { chain := FilterChain{Filters: r.Filters, Target: r.Function} chain.ProcessFilter(wrappedRequest, wrappedResponse) } else { // unfiltered r.Function(wrappedRequest, wrappedResponse) } } // Return whether the mimeType matches to what this Route can produce. func (r Route) matchesAccept(mimeTypesWithQuality string) bool { parts := strings.Split(mimeTypesWithQuality, ",") for _, each := range parts { var withoutQuality string if strings.Contains(each, ";") { withoutQuality = strings.Split(each, ";")[0] } else { withoutQuality = each } // trim before compare withoutQuality = strings.Trim(withoutQuality, " ") if withoutQuality == "*/*" { return true } for _, other := range r.Produces { if other == withoutQuality { return true } } } return false } // Return whether the mimeType matches to what this Route can consume. func (r Route) matchesContentType(mimeTypes string) bool { parts := strings.Split(mimeTypes, ",") for _, each := range parts { var contentType string if strings.Contains(each, ";") { contentType = strings.Split(each, ";")[0] } else { contentType = each } // trim before compare contentType = strings.Trim(contentType, " ") for _, other := range r.Consumes { if other == "*/*" || other == contentType { return true } } } return false } // Extract the parameters from the request url path func (r Route) extractParameters(urlPath string) map[string]string { urlParts := tokenizePath(urlPath) pathParameters := map[string]string{} for i, key := range r.pathParts { var value string if i >= len(urlParts) { value = "" } else { value = urlParts[i] } if strings.HasPrefix(key, "{") { // path-parameter if colon := strings.Index(key, ":"); colon != -1 { // extract by regex regPart := key[colon+1 : len(key)-1] keyPart := key[1:colon] if regPart == "*" { pathParameters[keyPart] = untokenizePath(i, urlParts) break } else { pathParameters[keyPart] = value } } else { // without enclosing {} pathParameters[key[1:len(key)-1]] = value } } } return pathParameters } // Untokenize back into an URL path using the slash separator func untokenizePath(offset int, parts []string) string { var buffer bytes.Buffer for p := offset; p < len(parts); p++ { buffer.WriteString(parts[p]) // do not end if p < len(parts)-1 { buffer.WriteString("/") } } return buffer.String() } // Tokenize an URL path using the slash separator ; the result does not have empty tokens func tokenizePath(path string) []string { if "/" == path { return []string{} } return strings.Split(strings.Trim(path, "/"), "/") } // for debugging func (r Route) String() string { return r.Method + " " + r.Path } go-restful-1.1.3/route_builder.go000066400000000000000000000150121246513110500167730ustar00rootroot00000000000000package 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 ( "log" "reflect" "strings" ) // 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 // documentation doc string notes string operation string readSample, writeSample interface{} parameters []*Parameter errorMap map[int]ResponseError } // 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 } // A verbose explanation of the operation behavior. Optional. func (b *RouteBuilder) Notes(notes string) *RouteBuilder { b.notes = notes return b } // Reads tells what resource type will be read from the request payload. Optional. // A parameter of type "body" is added ,required is set to true and the dataType is set to the qualified name of the sample's type. func (b *RouteBuilder) Reads(sample interface{}) *RouteBuilder { b.readSample = sample typeAsName := reflect.TypeOf(sample).String() bodyParameter := &Parameter{&ParameterData{Name: "body"}} bodyParameter.beBody() bodyParameter.Required(true) bodyParameter.DataType(typeAsName) b.Param(bodyParameter) return b } // ParameterNamed returns a Parameter already known to the RouteBuilder. Returns nil if not. // Use this to modify or extend information for the Parameter (through its Data()). func (b RouteBuilder) ParameterNamed(name string) (p *Parameter) { for _, each := range b.parameters { if each.Data().Name == name { return each } } return p } // Writes tells what resource type will be written as the response payload. Optional. func (b *RouteBuilder) Writes(sample interface{}) *RouteBuilder { b.writeSample = sample return b } // Param allows you to document the parameters of the Route. It adds a new Parameter (does not check for duplicates). func (b *RouteBuilder) Param(parameter *Parameter) *RouteBuilder { if b.parameters == nil { b.parameters = []*Parameter{} } b.parameters = append(b.parameters, parameter) return b } // Operation allows you to document what the acutal method/function call is of the Route. 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.Println("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, } // lazy init because there is no NewRouteBuilder (yet) if b.errorMap == nil { b.errorMap = map[int]ResponseError{} } b.errorMap[code] = err return b } type ResponseError struct { Code int Message string Model 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 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 } } // 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.Fatalf("[restful] Invalid path:%s because:%v", b.currentPath, err) } if b.function == nil { log.Fatalf("[restful] No function specified for route:" + b.currentPath) } route := Route{ Method: b.httpMethod, Path: concatPath(b.rootPath, b.currentPath), Produces: b.produces, Consumes: b.consumes, Function: b.function, Filters: b.filters, relativePath: b.currentPath, pathExpr: pathExpr, Doc: b.doc, Notes: b.notes, Operation: b.operation, ParameterDocs: b.parameters, ResponseErrors: b.errorMap, ReadSample: b.readSample, WriteSample: b.writeSample} route.postBuild() return route } func concatPath(path1, path2 string) string { return strings.TrimRight(path1, "/") + "/" + strings.TrimLeft(path2, "/") } go-restful-1.1.3/route_builder_test.go000066400000000000000000000023371246513110500200400ustar00rootroot00000000000000package restful import ( "testing" ) func TestRouteBuilder_PathParameter(t *testing.T) { p := &Parameter{&ParameterData{Name: "name", Description: "desc"}} p.AllowMultiple(true) p.DataType("int") p.Required(true) values := map[string]string{"a": "b"} p.AllowableValues(values) p.bePath() b := new(RouteBuilder) b.function = dummy b.Param(p) r := b.Build() if !r.ParameterDocs[0].Data().AllowMultiple { t.Error("AllowMultiple invalid") } if r.ParameterDocs[0].Data().DataType != "int" { t.Error("dataType invalid") } if !r.ParameterDocs[0].Data().Required { t.Error("required invalid") } if r.ParameterDocs[0].Data().Kind != PathParameterKind { t.Error("kind invalid") } if r.ParameterDocs[0].Data().AllowableValues["a"] != "b" { t.Error("allowableValues invalid") } if b.ParameterNamed("name") == nil { t.Error("access to parameter failed") } } func TestRouteBuilder(t *testing.T) { json := "application/json" b := new(RouteBuilder) b.To(dummy) b.Path("/routes").Method("HEAD").Consumes(json).Produces(json) 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") } } go-restful-1.1.3/route_test.go000066400000000000000000000055021246513110500163270ustar00rootroot00000000000000package restful import ( "testing" ) // 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") } } // 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 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 TestTokenizePath(t *testing.T) { if len(tokenizePath("/")) != 0 { t.Errorf("not empty path tokens") } } 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)) } return r.extractParameters(urlPath) } go-restful-1.1.3/router.go000066400000000000000000000011461246513110500154520ustar00rootroot00000000000000package 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 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-1.1.3/service_error.go000066400000000000000000000013041246513110500167770ustar00rootroot00000000000000package 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" // 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 } // NewError returns a ServiceError using the code and reason func NewError(code int, message string) ServiceError { return ServiceError{Code: code, Message: message} } // 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-1.1.3/swagger/000077500000000000000000000000001246513110500152405ustar00rootroot00000000000000go-restful-1.1.3/swagger/CHANGES.md000066400000000000000000000013411246513110500166310ustar00rootroot00000000000000Change history of swagger = 2014-11-14 - operation parameters are now sorted using ordering path,query,form,header,body 2014-11-12 - respect omitempty tag value for embedded structs - expose ApiVersion of WebService to Swagger ApiDeclaration 2014-05-29 - (api add) Ability to define custom http.Handler to serve swagger-ui static files 2014-05-04 - (fix) include model for array element type of response 2014-01-03 - (fix) do not add primitive type to the Api models 2013-11-27 - (fix) make Swagger work for WebServices with root ("/" or "") paths 2013-10-29 - (api add) package variable LogInfo to customize logging function 2013-10-15 - upgraded to spec version 1.2 (https://github.com/wordnik/swagger-core/wiki/1.2-transition)go-restful-1.1.3/swagger/README.md000066400000000000000000000021431246513110500165170ustar00rootroot00000000000000How to use Swagger UI with go-restful = Get the Swagger UI sources (version 1.2 only) git clone https://github.com/wordnik/swagger-ui.git The project contains a "dist" folder. Its contents has all the Swagger UI files you need. The `index.html` has an `url` set to `http://petstore.swagger.wordnik.com/api/api-docs`. You need to change that to match your WebService JSON endpoint e.g. `http://localhost:8080/apidocs.json` Now, you can install the Swagger WebService for serving the Swagger specification in JSON. config := swagger.Config{ WebServices: restful.RegisteredWebServices(), ApiPath: "/apidocs.json", SwaggerPath: "/apidocs/", SwaggerFilePath: "/Users/emicklei/Projects/swagger-ui/dist"} swagger.InstallSwaggerService(config) Notes -- - Use RouteBuilder.Operation(..) to set the Nickname field of the API spec - The WebServices field of swagger.Config can be used to control which service you want to expose and document ; you can have multiple configs and therefore multiple endpoints. - Use tag "description" to annotate a struct field with a description to show in the UIgo-restful-1.1.3/swagger/config.go000066400000000000000000000014671246513110500170440ustar00rootroot00000000000000package swagger import ( "net/http" "github.com/emicklei/go-restful" ) type Config struct { // url where the services are available, e.g. http://localhost:8080 // if left empty then the basePath of Swagger is taken from the actual request WebServicesUrl string // path where the JSON api is avaiable , e.g. /apidocs ApiPath string // [optional] path where the swagger UI will be served, e.g. /swagger SwaggerPath string // [optional] location of folder containing Swagger HTML5 application index.html SwaggerFilePath string // api listing is constructed from this list of restful WebServices. WebServices []*restful.WebService // will serve all static content (scripts,pages,images) StaticHandler http.Handler // [optional] on default CORS (Cross-Origin-Resource-Sharing) is enabled. DisableCORS bool } go-restful-1.1.3/swagger/model_builder.go000066400000000000000000000167151246513110500204070ustar00rootroot00000000000000package swagger import ( "encoding/json" "reflect" "strings" ) type modelBuilder struct { Models map[string]Model } func (b modelBuilder) addModel(st reflect.Type, nameOverride string) { modelName := b.keyFrom(st) if nameOverride != "" { modelName = nameOverride } // no models needed for primitive types if b.isPrimitiveType(modelName) { return } // see if we already have visited this model if _, ok := b.Models[modelName]; ok { return } sm := Model{ Id: modelName, Required: []string{}, Properties: map[string]ModelProperty{}} // reference the model before further initializing (enables recursive structs) b.Models[modelName] = sm // check for slice or array if st.Kind() == reflect.Slice || st.Kind() == reflect.Array { b.addModel(st.Elem(), "") return } // check for structure or primitive type if st.Kind() != reflect.Struct { return } for i := 0; i < st.NumField(); i++ { field := st.Field(i) jsonName, prop := b.buildProperty(field, &sm, modelName) if descTag := field.Tag.Get("description"); descTag != "" { prop.Description = descTag } // add if not ommitted if len(jsonName) != 0 { // update Required if b.isPropertyRequired(field) { sm.Required = append(sm.Required, jsonName) } sm.Properties[jsonName] = prop } } // update model builder with completed model b.Models[modelName] = sm } func (b modelBuilder) isPropertyRequired(field reflect.StructField) bool { required := true if jsonTag := field.Tag.Get("json"); jsonTag != "" { s := strings.Split(jsonTag, ",") if len(s) > 1 && s[1] == "omitempty" { return false } } return required } func (b modelBuilder) buildProperty(field reflect.StructField, model *Model, modelName string) (jsonName string, prop ModelProperty) { jsonName = b.jsonNameOfField(field) if len(jsonName) == 0 { // empty name signals skip property return "", prop } fieldType := field.Type fieldKind := fieldType.Kind() if jsonTag := field.Tag.Get("json"); jsonTag != "" { s := strings.Split(jsonTag, ",") if len(s) > 1 && s[1] == "string" { fieldType = reflect.TypeOf("") } } var pType = b.jsonSchemaType(fieldType.String()) // may include pkg path prop.Type = &pType if b.isPrimitiveType(fieldType.String()) { prop.Format = b.jsonSchemaFormat(fieldType.String()) return jsonName, prop } marshalerType := reflect.TypeOf((*json.Marshaler)(nil)).Elem() if fieldType.Implements(marshalerType) { var pType = "string" prop.Type = &pType return jsonName, prop } if fieldKind == reflect.Struct { return b.buildStructTypeProperty(field, jsonName, model) } if fieldKind == reflect.Slice || fieldKind == reflect.Array { return b.buildArrayTypeProperty(field, jsonName, modelName) } if fieldKind == reflect.Ptr { return b.buildPointerTypeProperty(field, jsonName, modelName) } if fieldType.Name() == "" { // override type of anonymous structs nestedTypeName := modelName + "." + jsonName var pType = nestedTypeName prop.Type = &pType b.addModel(fieldType, nestedTypeName) } return jsonName, prop } func (b modelBuilder) buildStructTypeProperty(field reflect.StructField, jsonName string, model *Model) (nameJson string, prop ModelProperty) { fieldType := field.Type // check for anonymous if len(fieldType.Name()) == 0 { // anonymous anonType := model.Id + "." + jsonName b.addModel(fieldType, anonType) prop.Type = &anonType return jsonName, prop } if field.Name == fieldType.Name() && field.Anonymous { // embedded struct sub := modelBuilder{map[string]Model{}} sub.addModel(fieldType, "") subKey := sub.keyFrom(fieldType) // merge properties from sub subModel := sub.Models[subKey] for k, v := range subModel.Properties { model.Properties[k] = v // if subModel says this property is required then include it required := false for _, each := range subModel.Required { if k == each { required = true break } } if required { model.Required = append(model.Required, k) } } // empty name signals skip property return "", prop } // simple struct b.addModel(fieldType, "") var pType = fieldType.String() prop.Type = &pType return jsonName, prop } func (b modelBuilder) buildArrayTypeProperty(field reflect.StructField, jsonName, modelName string) (nameJson string, prop ModelProperty) { fieldType := field.Type var pType = "array" prop.Type = &pType elemName := b.getElementTypeName(modelName, jsonName, fieldType.Elem()) prop.Items = &Item{Ref: &elemName} // add|overwrite model for element type if fieldType.Elem().Kind() == reflect.Ptr { fieldType = fieldType.Elem() } b.addModel(fieldType.Elem(), elemName) return jsonName, prop } func (b modelBuilder) buildPointerTypeProperty(field reflect.StructField, jsonName, modelName string) (nameJson string, prop ModelProperty) { fieldType := field.Type // override type of pointer to list-likes if fieldType.Elem().Kind() == reflect.Slice || fieldType.Elem().Kind() == reflect.Array { var pType = "array" prop.Type = &pType elemName := b.getElementTypeName(modelName, jsonName, fieldType.Elem().Elem()) prop.Items = &Item{Ref: &elemName} // add|overwrite model for element type b.addModel(fieldType.Elem().Elem(), elemName) } else { // non-array, pointer type var pType = fieldType.String()[1:] // no star, include pkg path prop.Type = &pType elemName := "" if fieldType.Elem().Name() == "" { elemName = modelName + "." + jsonName prop.Type = &elemName } b.addModel(fieldType.Elem(), elemName) } return jsonName, prop } func (b modelBuilder) getElementTypeName(modelName, jsonName string, t reflect.Type) string { if t.Kind() == reflect.Ptr { return t.String()[1:] } if t.Name() == "" { return modelName + "." + jsonName } if b.isPrimitiveType(t.Name()) { return b.jsonSchemaType(t.Name()) } return b.keyFrom(t) } func (b modelBuilder) keyFrom(st reflect.Type) string { key := st.String() if len(st.Name()) == 0 { // unnamed type // Swagger UI has special meaning for [ key = strings.Replace(key, "[]", "||", -1) } return key } func (b modelBuilder) isPrimitiveType(modelName string) bool { return strings.Contains("uint8 int int32 int64 float32 float64 bool string byte time.Time", modelName) } // jsonNameOfField returns the name of the field as it should appear in JSON format // An empty string indicates that this field is not part of the JSON representation func (b modelBuilder) jsonNameOfField(field reflect.StructField) string { if jsonTag := field.Tag.Get("json"); jsonTag != "" { s := strings.Split(jsonTag, ",") if s[0] == "-" { // empty name signals skip property return "" } else if s[0] != "" { return s[0] } } return field.Name } func (b modelBuilder) jsonSchemaType(modelName string) string { schemaMap := map[string]string{ "uint8": "integer", "int": "integer", "int32": "integer", "int64": "integer", "byte": "string", "float64": "number", "float32": "number", "bool": "boolean", "time.Time": "string", } mapped, ok := schemaMap[modelName] if ok { return mapped } else { return modelName // use as is (custom or struct) } } func (b modelBuilder) jsonSchemaFormat(modelName string) string { schemaMap := map[string]string{ "int": "int32", "int32": "int32", "int64": "int64", "byte": "byte", "uint8": "byte", "float64": "double", "float32": "float", "time.Time": "date-time", } mapped, ok := schemaMap[modelName] if ok { return mapped } else { return "" // no format } } go-restful-1.1.3/swagger/model_builder_test.go000066400000000000000000000265761246513110500214540ustar00rootroot00000000000000package swagger import ( "testing" "time" ) type YesNo bool func (y YesNo) MarshalJSON() ([]byte, error) { if y { return []byte("yes"), nil } return []byte("no"), nil } // clear && go test -v -test.run TestCustomMarshaller_Issue96 ...swagger func TestCustomMarshaller_Issue96(t *testing.T) { type Vote struct { What YesNo } testJsonFromStruct(t, Vote{}, `{ "swagger.Vote": { "id": "swagger.Vote", "required": [ "What" ], "properties": { "What": { "type": "string" } } } }`) } // clear && go test -v -test.run TestPrimitiveTypes ...swagger func TestPrimitiveTypes(t *testing.T) { type Prims struct { f float64 t time.Time } testJsonFromStruct(t, Prims{}, `{ "swagger.Prims": { "id": "swagger.Prims", "required": [ "f", "t" ], "properties": { "f": { "type": "number", "format": "double" }, "t": { "type": "string", "format": "date-time" } } } }`) } // clear && go test -v -test.run TestS1 ...swagger func TestS1(t *testing.T) { type S1 struct { Id string } testJsonFromStruct(t, S1{}, `{ "swagger.S1": { "id": "swagger.S1", "required": [ "Id" ], "properties": { "Id": { "type": "string" } } } }`) } // clear && go test -v -test.run TestS2 ...swagger func TestS2(t *testing.T) { type S2 struct { Ids []string } testJsonFromStruct(t, S2{}, `{ "swagger.S2": { "id": "swagger.S2", "required": [ "Ids" ], "properties": { "Ids": { "type": "array", "items": { "$ref": "string" } } } } }`) } // clear && go test -v -test.run TestS3 ...swagger func TestS3(t *testing.T) { type NestedS3 struct { Id string } type S3 struct { Nested NestedS3 } testJsonFromStruct(t, S3{}, `{ "swagger.NestedS3": { "id": "swagger.NestedS3", "required": [ "Id" ], "properties": { "Id": { "type": "string" } } }, "swagger.S3": { "id": "swagger.S3", "required": [ "Nested" ], "properties": { "Nested": { "type": "swagger.NestedS3" } } } }`) } type sample struct { id string `swagger:"required"` // TODO items []item rootItem item `json:"root" description:"root desc"` } type item struct { itemName string `json:"name"` } // clear && go test -v -test.run TestSampleToModelAsJson ...swagger func TestSampleToModelAsJson(t *testing.T) { testJsonFromStruct(t, sample{items: []item{}}, `{ "swagger.item": { "id": "swagger.item", "required": [ "name" ], "properties": { "name": { "type": "string" } } }, "swagger.sample": { "id": "swagger.sample", "required": [ "id", "items", "root" ], "properties": { "id": { "type": "string" }, "items": { "type": "array", "items": { "$ref": "swagger.item" } }, "root": { "type": "swagger.item", "description": "root desc" } } } }`) } func TestJsonTags(t *testing.T) { type X struct { A string B string `json:"-"` C int `json:",string"` D int `json:","` } expected := `{ "swagger.X": { "id": "swagger.X", "required": [ "A", "C", "D" ], "properties": { "A": { "type": "string" }, "C": { "type": "string" }, "D": { "type": "integer", "format": "int32" } } } }` testJsonFromStruct(t, X{}, expected) } func TestJsonTagOmitempty(t *testing.T) { type X struct { A int `json:",omitempty"` B int `json:"C,omitempty"` } expected := `{ "swagger.X": { "id": "swagger.X", "properties": { "A": { "type": "integer", "format": "int32" }, "C": { "type": "integer", "format": "int32" } } } }` testJsonFromStruct(t, X{}, expected) } func TestJsonTagName(t *testing.T) { type X struct { A string `json:"B"` } expected := `{ "swagger.X": { "id": "swagger.X", "required": [ "B" ], "properties": { "B": { "type": "string" } } } }` testJsonFromStruct(t, X{}, expected) } func TestAnonymousStruct(t *testing.T) { type X struct { A struct { B int } } expected := `{ "swagger.X": { "id": "swagger.X", "required": [ "A" ], "properties": { "A": { "type": "swagger.X.A" } } }, "swagger.X.A": { "id": "swagger.X.A", "required": [ "B" ], "properties": { "B": { "type": "integer", "format": "int32" } } } }` testJsonFromStruct(t, X{}, expected) } func TestAnonymousPtrStruct(t *testing.T) { type X struct { A *struct { B int } } expected := `{ "swagger.X": { "id": "swagger.X", "required": [ "A" ], "properties": { "A": { "type": "swagger.X.A" } } }, "swagger.X.A": { "id": "swagger.X.A", "required": [ "B" ], "properties": { "B": { "type": "integer", "format": "int32" } } } }` testJsonFromStruct(t, X{}, expected) } func TestAnonymousArrayStruct(t *testing.T) { type X struct { A []struct { B int } } expected := `{ "swagger.X": { "id": "swagger.X", "required": [ "A" ], "properties": { "A": { "type": "array", "items": { "$ref": "swagger.X.A" } } } }, "swagger.X.A": { "id": "swagger.X.A", "required": [ "B" ], "properties": { "B": { "type": "integer", "format": "int32" } } } }` testJsonFromStruct(t, X{}, expected) } func TestAnonymousPtrArrayStruct(t *testing.T) { type X struct { A *[]struct { B int } } expected := `{ "swagger.X": { "id": "swagger.X", "required": [ "A" ], "properties": { "A": { "type": "array", "items": { "$ref": "swagger.X.A" } } } }, "swagger.X.A": { "id": "swagger.X.A", "required": [ "B" ], "properties": { "B": { "type": "integer", "format": "int32" } } } }` testJsonFromStruct(t, X{}, expected) } // go test -v -test.run TestEmbeddedStruct_Issue98 ...swagger func TestEmbeddedStruct_Issue98(t *testing.T) { type Y struct { A int } type X struct { Y } testJsonFromStruct(t, X{}, `{ "swagger.X": { "id": "swagger.X", "required": [ "A" ], "properties": { "A": { "type": "integer", "format": "int32" } } } }`) } type Dataset struct { Names []string } // clear && go test -v -test.run TestIssue85 ...swagger func TestIssue85(t *testing.T) { anon := struct{ Datasets []Dataset }{} testJsonFromStruct(t, anon, `{ "struct { Datasets ||swagger.Dataset }": { "id": "struct { Datasets ||swagger.Dataset }", "required": [ "Datasets" ], "properties": { "Datasets": { "type": "array", "items": { "$ref": "swagger.Dataset" } } } }, "swagger.Dataset": { "id": "swagger.Dataset", "required": [ "Names" ], "properties": { "Names": { "type": "array", "items": { "$ref": "string" } } } } }`) } type File struct { History []File HistoryPtrs []*File } // go test -v -test.run TestRecursiveStructure ...swagger func TestRecursiveStructure(t *testing.T) { testJsonFromStruct(t, File{}, `{ "swagger.File": { "id": "swagger.File", "required": [ "History", "HistoryPtrs" ], "properties": { "History": { "type": "array", "items": { "$ref": "swagger.File" } }, "HistoryPtrs": { "type": "array", "items": { "$ref": "swagger.File" } } } } }`) } type A1 struct { B struct { Id int Comment string `json:"comment,omitempty"` } } // go test -v -test.run TestEmbeddedStructA1 ...swagger func TestEmbeddedStructA1(t *testing.T) { testJsonFromStruct(t, A1{}, `{ "swagger.A1": { "id": "swagger.A1", "required": [ "B" ], "properties": { "B": { "type": "swagger.A1.B" } } }, "swagger.A1.B": { "id": "swagger.A1.B", "required": [ "Id" ], "properties": { "Id": { "type": "integer", "format": "int32" }, "comment": { "type": "string" } } } }`) } type A2 struct { C } type C struct { Id int `json:"B"` Comment string `json:"comment,omitempty"` Secure bool `json:"secure"` } // go test -v -test.run TestEmbeddedStructA2 ...swagger func TestEmbeddedStructA2(t *testing.T) { testJsonFromStruct(t, A2{}, `{ "swagger.A2": { "id": "swagger.A2", "required": [ "B", "secure" ], "properties": { "B": { "type": "integer", "format": "int32" }, "comment": { "type": "string" }, "secure": { "type": "boolean" } } } }`) } type A3 struct { B D } type D struct { Id int } // clear && go test -v -test.run TestStructA3 ...swagger func TestStructA3(t *testing.T) { testJsonFromStruct(t, A3{}, `{ "swagger.A3": { "id": "swagger.A3", "required": [ "B" ], "properties": { "B": { "type": "swagger.D" } } }, "swagger.D": { "id": "swagger.D", "required": [ "Id" ], "properties": { "Id": { "type": "integer", "format": "int32" } } } }`) } type ObjectId []byte type Region struct { Id ObjectId `bson:"_id" json:"id"` Name string `bson:"name" json:"name"` Type string `bson:"type" json:"type"` } // clear && go test -v -test.run TestRegion_Issue113 ...swagger func TestRegion_Issue113(t *testing.T) { testJsonFromStruct(t, []Region{}, `{ "integer": { "id": "integer", "properties": {} }, "swagger.Region": { "id": "swagger.Region", "required": [ "id", "name", "type" ], "properties": { "id": { "type": "array", "items": { "$ref": "integer" } }, "name": { "type": "string" }, "type": { "type": "string" } } }, "||swagger.Region": { "id": "||swagger.Region", "properties": {} } }`) } // clear && go test -v -test.run TestIssue158 ...swagger func TestIssue158(t *testing.T) { type Address struct { Country string `json:"country,omitempty"` } type Customer struct { Name string `json:"name"` Address Address `json:"address"` } expected := `{ "swagger.Address": { "id": "swagger.Address", "properties": { "country": { "type": "string" } } }, "swagger.Customer": { "id": "swagger.Customer", "required": [ "name", "address" ], "properties": { "address": { "type": "swagger.Address" }, "name": { "type": "string" } } } }` testJsonFromStruct(t, Customer{}, expected) } func TestSlices(t *testing.T) { type Address struct { Country string `json:"country,omitempty"` } expected := `{ "swagger.Address": { "id": "swagger.Address", "properties": { "country": { "type": "string" } } }, "swagger.Customer": { "id": "swagger.Customer", "required": [ "name", "addresses" ], "properties": { "addresses": { "type": "array", "items": { "$ref": "swagger.Address" } }, "name": { "type": "string" } } } }` // both slices (with pointer value and with type value) should have equal swagger representation { type Customer struct { Name string `json:"name"` Addresses []Address `json:"addresses"` } testJsonFromStruct(t, Customer{}, expected) } { type Customer struct { Name string `json:"name"` Addresses []*Address `json:"addresses"` } testJsonFromStruct(t, Customer{}, expected) } } go-restful-1.1.3/swagger/param_sorter.go000066400000000000000000000012071246513110500202650ustar00rootroot00000000000000package swagger // 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. type ParameterSorter []Parameter func (s ParameterSorter) Len() int { return len(s) } func (s ParameterSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } var typeToSortKey = map[string]string{ "path": "A", "query": "B", "form": "C", "header": "D", "body": "E", } func (s ParameterSorter) Less(i, j int) bool { // use ordering path,query,form,header,body pi := s[i] pj := s[j] return typeToSortKey[pi.ParamType]+pi.Name < typeToSortKey[pj.ParamType]+pj.Name } go-restful-1.1.3/swagger/param_sorter_test.go000066400000000000000000000015641246513110500213320ustar00rootroot00000000000000package swagger import ( "bytes" "sort" "testing" ) func TestSortParameters(t *testing.T) { unsorted := []Parameter{ Parameter{ Name: "form2", ParamType: "form", }, Parameter{ Name: "header1", ParamType: "header", }, Parameter{ Name: "path2", ParamType: "path", }, Parameter{ Name: "body", ParamType: "body", }, Parameter{ Name: "path1", ParamType: "path", }, Parameter{ Name: "form1", ParamType: "form", }, Parameter{ Name: "query2", ParamType: "query", }, Parameter{ Name: "query1", ParamType: "query", }, } sort.Sort(ParameterSorter(unsorted)) var b bytes.Buffer for _, p := range unsorted { b.WriteString(p.Name + ".") } if "path1.path2.query1.query2.form1.form2.header1.body." != b.String() { t.Fatal("sorting has changed:" + b.String()) } } go-restful-1.1.3/swagger/resource_sorter.go000066400000000000000000000006201246513110500210120ustar00rootroot00000000000000package swagger // 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. type ResourceSorter []Resource func (s ResourceSorter) Len() int { return len(s) } func (s ResourceSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s ResourceSorter) Less(i, j int) bool { return s[i].Path < s[j].Path } go-restful-1.1.3/swagger/swagger.go000066400000000000000000000146061246513110500172350ustar00rootroot00000000000000// Package swagger implements the structures of the Swagger // https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md package swagger const swaggerVersion = "1.2" // 4.3.3 Data Type Fields type DataTypeFields struct { Type *string `json:"type,omitempty"` // if Ref not used Ref *string `json:"$ref,omitempty"` // if Type not used Format string `json:"format,omitempty"` DefaultValue Special `json:"defaultValue,omitempty"` Enum []string `json:"enum,omitempty"` Minimum string `json:"minimum,omitempty"` Maximum string `json:"maximum,omitempty"` Items *Item `json:"items,omitempty"` UniqueItems *bool `json:"uniqueItems,omitempty"` } type Special string // 4.3.4 Items Object type Item struct { Type *string `json:"type,omitempty"` Ref *string `json:"$ref,omitempty"` Format string `json:"format,omitempty"` } // 5.1 Resource Listing type ResourceListing struct { SwaggerVersion string `json:"swaggerVersion"` // e.g 1.2 Apis []Resource `json:"apis"` ApiVersion string `json:"apiVersion"` Info Info `json:"info"` Authorizations []Authorization `json:"authorizations,omitempty"` } // 5.1.2 Resource Object type Resource struct { Path string `json:"path"` // relative or absolute, must start with / Description string `json:"description"` } // 5.1.3 Info Object type Info struct { Title string `json:"title"` Description string `json:"description"` TermsOfServiceUrl string `json:"termsOfServiceUrl,omitempty"` Contact string `json:"contact,omitempty"` License string `json:"license,omitempty"` LicensUrl string `json:"licensUrl,omitempty"` } // 5.1.5 type Authorization struct { Type string `json:"type"` PassAs string `json:"passAs"` Keyname string `json:"keyname"` Scopes []Scope `json:"scopes"` GrantTypes []GrantType `json:"grandTypes"` } // 5.1.6, 5.2.11 type Scope struct { // Required. The name of the scope. Scope string `json:"scope"` // Recommended. A short description of the scope. Description string `json:"description"` } // 5.1.7 type GrantType struct { Implicit Implicit `json:"implicit"` AuthorizationCode AuthorizationCode `json:"authorization_code"` } // 5.1.8 Implicit Object type Implicit struct { // Required. The login endpoint definition. loginEndpoint LoginEndpoint `json:"loginEndpoint"` // An optional alternative name to standard "access_token" OAuth2 parameter. TokenName string `json:"tokenName"` } // 5.1.9 Authorization Code Object type AuthorizationCode struct { TokenRequestEndpoint TokenRequestEndpoint `json:"tokenRequestEndpoint"` TokenEndpoint TokenEndpoint `json:"tokenEndpoint"` } // 5.1.10 Login Endpoint Object type LoginEndpoint struct { // Required. The URL of the authorization endpoint for the implicit grant flow. The value SHOULD be in a URL format. Url string `json:"url"` } // 5.1.11 Token Request Endpoint Object type TokenRequestEndpoint struct { // Required. The URL of the authorization endpoint for the authentication code grant flow. The value SHOULD be in a URL format. Url string `json:"url"` // An optional alternative name to standard "client_id" OAuth2 parameter. ClientIdName string `json:"clientIdName"` // An optional alternative name to the standard "client_secret" OAuth2 parameter. ClientSecretName string `json:"clientSecretName"` } // 5.1.12 Token Endpoint Object type TokenEndpoint struct { // Required. The URL of the token endpoint for the authentication code grant flow. The value SHOULD be in a URL format. Url string `json:"url"` // An optional alternative name to standard "access_token" OAuth2 parameter. TokenName string `json:"tokenName"` } // 5.2 API Declaration type ApiDeclaration struct { SwaggerVersion string `json:"swaggerVersion"` ApiVersion string `json:"apiVersion"` BasePath string `json:"basePath"` ResourcePath string `json:"resourcePath"` // must start with / Apis []Api `json:"apis,omitempty"` Models map[string]Model `json:"models,omitempty"` Produces []string `json:"produces,omitempty"` Consumes []string `json:"consumes,omitempty"` Authorizations []Authorization `json:"authorizations,omitempty"` } // 5.2.2 API Object type Api struct { Path string `json:"path"` // relative or absolute, must start with / Description string `json:"description"` Operations []Operation `json:"operations,omitempty"` } // 5.2.3 Operation Object type Operation struct { Type string `json:"type"` Method string `json:"method"` Summary string `json:"summary,omitempty"` Notes string `json:"notes,omitempty"` Nickname string `json:"nickname"` Authorizations []Authorization `json:"authorizations,omitempty"` Parameters []Parameter `json:"parameters"` ResponseMessages []ResponseMessage `json:"responseMessages,omitempty"` // optional Produces []string `json:"produces,omitempty"` Consumes []string `json:"consumes,omitempty"` Deprecated string `json:"deprecated,omitempty"` } // 5.2.4 Parameter Object type Parameter struct { DataTypeFields ParamType string `json:"paramType"` // path,query,body,header,form Name string `json:"name"` Description string `json:"description"` Required bool `json:"required"` AllowMultiple bool `json:"allowMultiple"` } // 5.2.5 Response Message Object type ResponseMessage struct { Code int `json:"code"` Message string `json:"message"` ResponseModel string `json:"responseModel,omitempty"` } // 5.2.6, 5.2.7 Models Object type Model struct { Id string `json:"id"` Description string `json:"description,omitempty"` Required []string `json:"required,omitempty"` Properties map[string]ModelProperty `json:"properties"` SubTypes []string `json:"subTypes,omitempty"` Discriminator string `json:"discriminator,omitempty"` } // 5.2.8 Properties Object type ModelProperty struct { DataTypeFields Description string `json:"description,omitempty"` } // 5.2.10 type Authorizations map[string]Authorization go-restful-1.1.3/swagger/swagger_test.go000066400000000000000000000062531246513110500202730ustar00rootroot00000000000000package swagger import ( "encoding/json" "fmt" "testing" "github.com/emicklei/go-restful" ) // go test -v -test.run TestApi ...swagger func TestApi(t *testing.T) { value := Api{Path: "/", Description: "Some Path", Operations: []Operation{}} compareJson(t, true, value, `{"path":"/","description":"Some Path"}`) } // go test -v -test.run TestServiceToApi ...swagger func TestServiceToApi(t *testing.T) { ws := new(restful.WebService) ws.Path("/tests") ws.Consumes(restful.MIME_JSON) ws.Produces(restful.MIME_XML) ws.Route(ws.GET("/all").To(dummy).Writes(sample{})) ws.ApiVersion("1.2.3") cfg := Config{ WebServicesUrl: "http://here.com", ApiPath: "/apipath", WebServices: []*restful.WebService{ws}} sws := newSwaggerService(cfg) decl := sws.composeDeclaration(ws, "/tests") data, err := json.MarshalIndent(decl, " ", " ") if err != nil { t.Fatal(err.Error()) } // for visual inspection only fmt.Println(string(data)) } func dummy(i *restful.Request, o *restful.Response) {} // go test -v -test.run TestIssue78 ...swagger type Response struct { Code int Users *[]User Items *[]TestItem } type User struct { Id, Name string } type TestItem struct { Id, Name string } // clear && go test -v -test.run TestComposeResponseMessages ...swagger func TestComposeResponseMessages(t *testing.T) { responseErrors := map[int]restful.ResponseError{} responseErrors[400] = restful.ResponseError{Code: 400, Message: "Bad Request", Model: TestItem{}} route := restful.Route{ResponseErrors: responseErrors} decl := new(ApiDeclaration) decl.Models = map[string]Model{} msgs := composeResponseMessages(route, decl) if msgs[0].ResponseModel != "swagger.TestItem" { t.Errorf("got %s want swagger.TestItem", msgs[0].ResponseModel) } } // clear && go test -v -test.run TestComposeResponseMessageArray ...swagger func TestComposeResponseMessageArray(t *testing.T) { responseErrors := map[int]restful.ResponseError{} responseErrors[400] = restful.ResponseError{Code: 400, Message: "Bad Request", Model: []TestItem{}} route := restful.Route{ResponseErrors: responseErrors} decl := new(ApiDeclaration) decl.Models = map[string]Model{} msgs := composeResponseMessages(route, decl) if msgs[0].ResponseModel != "array[swagger.TestItem]" { t.Errorf("got %s want swagger.TestItem", msgs[0].ResponseModel) } } func TestIssue78(t *testing.T) { sws := newSwaggerService(Config{}) models := map[string]Model{} sws.addModelFromSampleTo(&Operation{}, true, Response{Items: &[]TestItem{}}, models) model, ok := models["swagger.Response"] if !ok { t.Fatal("missing response model") } if "swagger.Response" != model.Id { t.Fatal("wrong model id:" + model.Id) } code, ok := model.Properties["Code"] if !ok { t.Fatal("missing code") } if "integer" != *code.Type { t.Fatal("wrong code type:" + *code.Type) } items, ok := model.Properties["Items"] if !ok { t.Fatal("missing items") } if "array" != *items.Type { t.Fatal("wrong items type:" + *items.Type) } items_items := items.Items if items_items == nil { t.Fatal("missing items->items") } ref := items_items.Ref if ref == nil { t.Fatal("missing $ref") } if *ref != "swagger.TestItem" { t.Fatal("wrong $ref:" + *ref) } } go-restful-1.1.3/swagger/swagger_webservice.go000066400000000000000000000261671246513110500214600ustar00rootroot00000000000000package swagger import ( "fmt" "github.com/emicklei/go-restful" // "github.com/emicklei/hopwatch" "log" "net/http" "reflect" "sort" "strings" ) type SwaggerService struct { config Config apiDeclarationMap map[string]ApiDeclaration } func newSwaggerService(config Config) *SwaggerService { return &SwaggerService{ config: config, apiDeclarationMap: map[string]ApiDeclaration{}} } // LogInfo is the function that is called when this package needs to log. It defaults to log.Printf var LogInfo = log.Printf // InstallSwaggerService add the WebService that provides the API documentation of all services // conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki). func InstallSwaggerService(aSwaggerConfig Config) { RegisterSwaggerService(aSwaggerConfig, restful.DefaultContainer) } // RegisterSwaggerService add the WebService that provides the API documentation of all services // conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki). func RegisterSwaggerService(config Config, wsContainer *restful.Container) { sws := newSwaggerService(config) ws := new(restful.WebService) ws.Path(config.ApiPath) ws.Produces(restful.MIME_JSON) if config.DisableCORS { ws.Filter(enableCORS) } ws.Route(ws.GET("/").To(sws.getListing)) ws.Route(ws.GET("/{a}").To(sws.getDeclarations)) ws.Route(ws.GET("/{a}/{b}").To(sws.getDeclarations)) ws.Route(ws.GET("/{a}/{b}/{c}").To(sws.getDeclarations)) ws.Route(ws.GET("/{a}/{b}/{c}/{d}").To(sws.getDeclarations)) ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}").To(sws.getDeclarations)) ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}").To(sws.getDeclarations)) ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}/{g}").To(sws.getDeclarations)) LogInfo("[restful/swagger] listing is available at %v%v", config.WebServicesUrl, config.ApiPath) wsContainer.Add(ws) // Build all ApiDeclarations for _, each := range config.WebServices { rootPath := each.RootPath() // skip the api service itself if rootPath != config.ApiPath { if rootPath == "" || rootPath == "/" { // use routes for _, route := range each.Routes() { entry := staticPathFromRoute(route) _, exists := sws.apiDeclarationMap[entry] if !exists { sws.apiDeclarationMap[entry] = sws.composeDeclaration(each, entry) } } } else { // use root path sws.apiDeclarationMap[each.RootPath()] = sws.composeDeclaration(each, each.RootPath()) } } } // Check paths for UI serving if config.StaticHandler == nil && config.SwaggerFilePath != "" && config.SwaggerPath != "" { swaggerPathSlash := config.SwaggerPath // path must end with slash / if "/" != config.SwaggerPath[len(config.SwaggerPath)-1:] { LogInfo("[restful/swagger] use corrected SwaggerPath ; must end with slash (/)") swaggerPathSlash += "/" } LogInfo("[restful/swagger] %v%v is mapped to folder %v", config.WebServicesUrl, swaggerPathSlash, config.SwaggerFilePath) wsContainer.Handle(swaggerPathSlash, http.StripPrefix(swaggerPathSlash, http.FileServer(http.Dir(config.SwaggerFilePath)))) //if we define a custom static handler use it } else if config.StaticHandler != nil && config.SwaggerPath != "" { swaggerPathSlash := config.SwaggerPath // path must end with slash / if "/" != config.SwaggerPath[len(config.SwaggerPath)-1:] { LogInfo("[restful/swagger] use corrected SwaggerFilePath ; must end with slash (/)") swaggerPathSlash += "/" } LogInfo("[restful/swagger] %v%v is mapped to custom Handler %T", config.WebServicesUrl, swaggerPathSlash, config.StaticHandler) wsContainer.Handle(swaggerPathSlash, config.StaticHandler) } else { LogInfo("[restful/swagger] Swagger(File)Path is empty ; no UI is served") } } func staticPathFromRoute(r restful.Route) string { static := r.Path bracket := strings.Index(static, "{") if bracket <= 1 { // result cannot be empty return static } if bracket != -1 { static = r.Path[:bracket] } if strings.HasSuffix(static, "/") { return static[:len(static)-1] } else { return static } } func enableCORS(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { if origin := req.HeaderParameter(restful.HEADER_Origin); origin != "" { // prevent duplicate header if len(resp.Header().Get(restful.HEADER_AccessControlAllowOrigin)) == 0 { resp.AddHeader(restful.HEADER_AccessControlAllowOrigin, origin) } } chain.ProcessFilter(req, resp) } func (sws SwaggerService) getListing(req *restful.Request, resp *restful.Response) { listing := ResourceListing{SwaggerVersion: swaggerVersion} for k, v := range sws.apiDeclarationMap { ref := Resource{Path: k} if len(v.Apis) > 0 { // use description of first (could still be empty) ref.Description = v.Apis[0].Description } listing.Apis = append(listing.Apis, ref) } sort.Sort(ResourceSorter(listing.Apis)) resp.WriteAsJson(listing) } func (sws SwaggerService) getDeclarations(req *restful.Request, resp *restful.Response) { decl := sws.apiDeclarationMap[composeRootPath(req)] // unless WebServicesUrl is given if len(sws.config.WebServicesUrl) == 0 { // update base path from the actual request // TODO how to detect https? assume http for now var host string // X-Forwarded-Host or Host or Request.Host hostvalues, ok := req.Request.Header["X-Forwarded-Host"] // apache specific? if !ok || len(hostvalues) == 0 { forwarded, ok := req.Request.Header["Host"] // without reverse-proxy if !ok || len(forwarded) == 0 { // fallback to Host field host = req.Request.Host } else { host = forwarded[0] } } else { host = hostvalues[0] } (&decl).BasePath = fmt.Sprintf("http://%s", host) } resp.WriteAsJson(decl) } func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix string) ApiDeclaration { decl := ApiDeclaration{ SwaggerVersion: swaggerVersion, BasePath: sws.config.WebServicesUrl, ResourcePath: ws.RootPath(), Models: map[string]Model{}, ApiVersion: ws.Version()} // collect any path parameters rootParams := []Parameter{} for _, param := range ws.PathParameters() { rootParams = append(rootParams, asSwaggerParameter(param.Data())) } // aggregate by path pathToRoutes := map[string][]restful.Route{} for _, other := range ws.Routes() { if strings.HasPrefix(other.Path, pathPrefix) { routes := pathToRoutes[other.Path] pathToRoutes[other.Path] = append(routes, other) } } for path, routes := range pathToRoutes { api := Api{Path: strings.TrimSuffix(path, "/"), Description: ws.Documentation()} for _, route := range routes { operation := Operation{ Method: route.Method, Summary: route.Doc, Notes: route.Notes, Type: asDataType(route.WriteSample), Parameters: []Parameter{}, Nickname: route.Operation, ResponseMessages: composeResponseMessages(route, &decl)} operation.Consumes = route.Consumes operation.Produces = route.Produces // share root params if any for _, swparam := range rootParams { operation.Parameters = append(operation.Parameters, swparam) } // route specific params for _, param := range route.ParameterDocs { operation.Parameters = append(operation.Parameters, asSwaggerParameter(param.Data())) } // sort parameters sort.Sort(ParameterSorter(operation.Parameters)) sws.addModelsFromRouteTo(&operation, route, &decl) api.Operations = append(api.Operations, operation) } decl.Apis = append(decl.Apis, api) } return decl } // composeResponseMessages takes the ResponseErrors (if any) and creates ResponseMessages from them. func composeResponseMessages(route restful.Route, decl *ApiDeclaration) (messages []ResponseMessage) { if route.ResponseErrors == nil { return messages } // sort by code codes := sort.IntSlice{} for code, _ := range route.ResponseErrors { codes = append(codes, code) } codes.Sort() for _, code := range codes { each := route.ResponseErrors[code] message := ResponseMessage{ Code: code, Message: each.Message, } if each.Model != nil { st := reflect.TypeOf(each.Model) isCollection, st := detectCollectionType(st) modelName := modelBuilder{}.keyFrom(st) if isCollection { modelName = "array[" + modelName + "]" } modelBuilder{decl.Models}.addModel(st, "") // reference the model message.ResponseModel = modelName } messages = append(messages, message) } return } // addModelsFromRoute takes any read or write sample from the Route and creates a Swagger model from it. func (sws SwaggerService) addModelsFromRouteTo(operation *Operation, route restful.Route, decl *ApiDeclaration) { if route.ReadSample != nil { sws.addModelFromSampleTo(operation, false, route.ReadSample, decl.Models) } if route.WriteSample != nil { sws.addModelFromSampleTo(operation, true, route.WriteSample, decl.Models) } } func detectCollectionType(st reflect.Type) (bool, reflect.Type) { isCollection := false if st.Kind() == reflect.Slice || st.Kind() == reflect.Array { st = st.Elem() isCollection = true } else { if st.Kind() == reflect.Ptr { if st.Elem().Kind() == reflect.Slice || st.Elem().Kind() == reflect.Array { st = st.Elem().Elem() isCollection = true } } } return isCollection, st } // addModelFromSample creates and adds (or overwrites) a Model from a sample resource func (sws SwaggerService) addModelFromSampleTo(operation *Operation, isResponse bool, sample interface{}, models map[string]Model) { st := reflect.TypeOf(sample) isCollection, st := detectCollectionType(st) modelName := modelBuilder{}.keyFrom(st) if isResponse { if isCollection { modelName = "array[" + modelName + "]" } operation.Type = modelName } modelBuilder{models}.addModel(reflect.TypeOf(sample), "") } func asSwaggerParameter(param restful.ParameterData) Parameter { return Parameter{ DataTypeFields: DataTypeFields{ Type: ¶m.DataType, Format: asFormat(param.DataType), }, Name: param.Name, Description: param.Description, ParamType: asParamType(param.Kind), Required: param.Required} } // Between 1..7 path parameters is supported func composeRootPath(req *restful.Request) string { path := "/" + req.PathParameter("a") b := req.PathParameter("b") if b == "" { return path } path = path + "/" + b c := req.PathParameter("c") if c == "" { return path } path = path + "/" + c d := req.PathParameter("d") if d == "" { return path } path = path + "/" + d e := req.PathParameter("e") if e == "" { return path } path = path + "/" + e f := req.PathParameter("f") if f == "" { return path } path = path + "/" + f g := req.PathParameter("g") if g == "" { return path } return path + "/" + g } func asFormat(name string) string { return "" // TODO } func asParamType(kind int) string { switch { case kind == restful.PathParameterKind: return "path" case kind == restful.QueryParameterKind: return "query" case kind == restful.BodyParameterKind: return "body" case kind == restful.HeaderParameterKind: return "header" case kind == restful.FormParameterKind: return "form" } return "" } func asDataType(any interface{}) string { if any == nil { return "void" } return reflect.TypeOf(any).Name() } go-restful-1.1.3/swagger/utils_test.go000066400000000000000000000033051246513110500177670ustar00rootroot00000000000000package swagger import ( "bytes" "encoding/json" "fmt" "reflect" "strings" "testing" ) func testJsonFromStruct(t *testing.T, sample interface{}, expectedJson string) { compareJson(t, false, modelsFromStruct(sample), expectedJson) } func modelsFromStruct(sample interface{}) map[string]Model { models := map[string]Model{} builder := modelBuilder{models} builder.addModel(reflect.TypeOf(sample), "") return models } func compareJson(t *testing.T, flatCompare bool, value interface{}, expectedJsonAsString string) { var output []byte var err error if flatCompare { output, err = json.Marshal(value) } else { output, err = json.MarshalIndent(value, " ", " ") } if err != nil { t.Error(err.Error()) return } actual := string(output) var actualMap map[string]interface{} var expectedMap map[string]interface{} json.Unmarshal(output, &actualMap) json.Unmarshal([]byte(expectedJsonAsString), &expectedMap) if !reflect.DeepEqual(actualMap, expectedMap) { fmt.Println("---- expected -----") fmt.Println(withLineNumbers(expectedJsonAsString)) fmt.Println("---- actual -----") fmt.Println(withLineNumbers(actual)) fmt.Println("---- raw -----") fmt.Println(actual) t.Error("there are differences") } } func indexOfNonMatchingLine(actual, expected string) int { a := strings.Split(actual, "\n") e := strings.Split(expected, "\n") size := len(a) if len(e) < len(a) { size = len(e) } for i := 0; i < size; i++ { if a[i] != e[i] { return i } } return -1 } func withLineNumbers(content string) string { var buffer bytes.Buffer lines := strings.Split(content, "\n") for i, each := range lines { buffer.WriteString(fmt.Sprintf("%d:%s\n", i, each)) } return buffer.String() } go-restful-1.1.3/web_service.go000066400000000000000000000147411246513110500164340ustar00rootroot00000000000000package 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 "log" // 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 } // compilePathExpression ensures that the path is compiled into a RegEx for those routers that need it. func (w *WebService) compilePathExpression() { if len(w.rootPath) == 0 { w.Path("/") // lazy initialize path } compiled, err := newPathExpression(w.rootPath) if err != nil { log.Fatalf("[restful] invalid path:%s because:%v", w.rootPath, err) } 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 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 { 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 { p := &Parameter{&ParameterData{Name: name, Description: description, Required: false, DataType: "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 { 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 { 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 { 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 { builder.copyDefaults(w.produces, w.consumes) w.routes = append(w.routes, builder.Build()) return w } // Method creates a new RouteBuilder and initialize its http method func (w *WebService) Method(httpMethod string) *RouteBuilder { return new(RouteBuilder).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 { return w.routes } // 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 amoung 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).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).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).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).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).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).servicePath(w.rootPath).Method("DELETE").Path(subPath) } go-restful-1.1.3/web_service_container.go000066400000000000000000000023531246513110500204720ustar00rootroot00000000000000package 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-1.1.3/web_service_test.go000066400000000000000000000056621246513110500174750ustar00rootroot00000000000000package restful import ( "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() DefaultContainer.dispatch(httpWriter, httpRequest) if 500 != httpWriter.Code { t.Error("500 expected on fire") } } 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 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 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 newSelectedRouteTestingService() *WebService { ws := new(WebService).Path("") ws.Route(ws.GET(pathGetFriends).To(selectedRouteChecker)) return ws } func selectedRouteChecker(req *Request, resp *Response) { if req.SelectedRoutePath() != pathGetFriends { resp.InternalServerError() } } func doPanic(req *Request, resp *Response) { println("lightning...") panic("fire") }