server: Add an inspect endpoint for containers

Signed-off-by: Mrunal Patel <mpatel@redhat.com>
This commit is contained in:
Mrunal Patel 2017-08-29 18:12:10 -07:00
parent d634468da6
commit 58bc35ab40
11 changed files with 876 additions and 0 deletions

View File

@ -412,6 +412,13 @@ func main() {
service.StartExitMonitor()
}()
go func() {
err := service.StartInspectEndpoint()
if err != nil {
logrus.Fatalf("Failed to start container inspect endpoint: %v", err)
}
}()
err = s.Serve(lis)
if graceful && strings.Contains(strings.ToLower(err.Error()), "use of closed network connection") {
err = nil

56
server/inspect.go Normal file
View File

@ -0,0 +1,56 @@
package server
import (
"encoding/json"
"fmt"
"net/http"
"github.com/go-zoo/bone"
)
// ContainerInfo stores information about containers
type ContainerInfo struct {
Pid int `json:"pid"`
Image string `json:"image"`
CreatedTime int64 `json:"created_time"`
Labels map[string]string `json:"labels"`
Annotations map[string]string `json:"annotations"`
}
// StartInspectEndpoint starts a http server that
// serves container information requests
func (s *Server) StartInspectEndpoint() error {
mux := bone.New()
mux.Get("/containers/:id", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
containerID := bone.GetValue(req, "id")
ctr := s.GetContainer(containerID)
if ctr == nil {
http.Error(w, fmt.Sprintf("container with id: %s not found", containerID), http.StatusNotFound)
return
}
ctrState := ctr.State()
if ctrState == nil {
http.Error(w, fmt.Sprintf("container %s state is nil", containerID), http.StatusNotFound)
return
}
ci := ContainerInfo{
Pid: ctrState.Pid,
Image: ctr.Image(),
CreatedTime: ctrState.Created.UnixNano(),
Labels: ctr.Labels(),
Annotations: ctr.Annotations(),
}
js, err := json.Marshal(ci)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(js)
}))
// TODO: Make this configurable
return http.ListenAndServe("localhost:7373", mux)
}

View File

@ -97,3 +97,4 @@ github.com/prometheus/procfs 65c1f6f8f0fc1e2185eb9863a3bc751496404259
github.com/matttproud/golang_protobuf_extensions fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a
github.com/beorn7/perks 3ac7bf7a47d159a033b107610db8a1b6575507a4
github.com/containerd/cgroups 7a5fdd8330119dc70d850260db8f3594d89d6943
github.com/go-zoo/bone 031b4005dfe248ccba241a0c9de0f9e112fd6b7c

22
vendor/github.com/go-zoo/bone/LICENSE generated vendored Normal file
View File

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

81
vendor/github.com/go-zoo/bone/README.md generated vendored Normal file
View File

@ -0,0 +1,81 @@
bone [![GoDoc](https://godoc.org/github.com/squiidz/bone?status.png)](http://godoc.org/github.com/go-zoo/bone) [![Build Status](https://travis-ci.org/go-zoo/bone.svg)](https://travis-ci.org/go-zoo/bone) [![Go Report Card](https://goreportcard.com/badge/go-zoo/bone)](https://goreportcard.com/report/go-zoo/bone) [![Sourcegraph](https://sourcegraph.com/github.com/go-zoo/bone/-/badge.svg)](https://sourcegraph.com/github.com/go-zoo/bone?badge)
=======
## What is bone ?
Bone is a lightweight and lightning fast HTTP Multiplexer for Golang. It support :
- URL Parameters
- REGEX Parameters
- Wildcard routes
- Router Prefix
- Sub Router, `mux.SubRoute()`, support most standard router (bone, gorilla/mux, httpRouter etc...)
- Http method declaration
- Support for `http.Handler` and `http.HandlerFunc`
- Custom NotFound handler
- Respect the Go standard `http.Handler` interface
![alt tag](https://c2.staticflickr.com/2/1070/540747396_5542b42cca_z.jpg)
## Speed
```
- BenchmarkBoneMux 10000000 118 ns/op
- BenchmarkZeusMux 100000 144 ns/op
- BenchmarkHttpRouterMux 10000000 134 ns/op
- BenchmarkNetHttpMux 3000000 580 ns/op
- BenchmarkGorillaMux 300000 3333 ns/op
- BenchmarkGorillaPatMux 1000000 1889 ns/op
```
These test are just for fun, all these router are great and really efficient.
Bone do not pretend to be the fastest router for every job.
## Example
``` go
package main
import(
"net/http"
"github.com/go-zoo/bone"
)
func main () {
mux := bone.New()
// mux.Get, Post, etc ... takes http.Handler
mux.Get("/home/:id", http.HandlerFunc(HomeHandler))
mux.Get("/profil/:id/:var", http.HandlerFunc(ProfilHandler))
mux.Post("/data", http.HandlerFunc(DataHandler))
// Support REGEX Route params
mux.Get("/index/#id^[0-9]$", http.HandlerFunc(IndexHandler))
// Handle take http.Handler
mux.Handle("/", http.HandlerFunc(RootHandler))
// GetFunc, PostFunc etc ... takes http.HandlerFunc
mux.GetFunc("/test", Handler)
http.ListenAndServe(":8080", mux)
}
func Handler(rw http.ResponseWriter, req *http.Request) {
// Get the value of the "id" parameters.
val := bone.GetValue(req, "id")
rw.Write([]byte(val))
}
```
## Blog Posts
- http://www.peterbe.com/plog/my-favorite-go-multiplexer
- https://harshladha.xyz/my-first-library-in-go-language-hasty-791b8e2b9e69
## Libs
- Errors dump for Go : [Trash](https://github.com/go-zoo/trash)
- Middleware Chaining module : [Claw](https://github.com/go-zoo/claw)

74
vendor/github.com/go-zoo/bone/bone.go generated vendored Normal file
View File

@ -0,0 +1,74 @@
/********************************
*** Multiplexer for Go ***
*** Bone is under MIT license ***
*** Code by CodingFerret ***
*** github.com/go-zoo ***
*********************************/
package bone
import (
"net/http"
"strings"
)
// Mux have routes and a notFound handler
// Route: all the registred route
// notFound: 404 handler, default http.NotFound if not provided
type Mux struct {
Routes map[string][]*Route
prefix string
notFound http.Handler
Serve func(rw http.ResponseWriter, req *http.Request)
CaseSensitive bool
}
var (
static = "static"
method = []string{"GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS"}
)
type adapter func(*Mux) *Mux
// New create a pointer to a Mux instance
func New(adapters ...adapter) *Mux {
m := &Mux{Routes: make(map[string][]*Route), Serve: nil, CaseSensitive: true}
for _, adap := range adapters {
adap(m)
}
if m.Serve == nil {
m.Serve = m.DefaultServe
}
return m
}
// Prefix set a default prefix for all routes registred on the router
func (m *Mux) Prefix(p string) *Mux {
m.prefix = strings.TrimSuffix(p, "/")
return m
}
// DefaultServe is the default http request handler
func (m *Mux) DefaultServe(rw http.ResponseWriter, req *http.Request) {
// Check if a route match
if !m.parse(rw, req) {
// Check if it's a static ressource
if !m.staticRoute(rw, req) {
// Check if the request path doesn't end with /
if !m.validate(rw, req) {
// Check if same route exists for another HTTP method
if !m.otherMethods(rw, req) {
m.HandleNotFound(rw, req)
}
}
}
}
}
// ServeHTTP pass the request to the serve method of Mux
func (m *Mux) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if !m.CaseSensitive {
req.URL.Path = strings.ToLower(req.URL.Path)
}
m.Serve(rw, req)
}

169
vendor/github.com/go-zoo/bone/helper.go generated vendored Normal file
View File

@ -0,0 +1,169 @@
/********************************
*** Multiplexer for Go ***
*** Bone is under MIT license ***
*** Code by CodingFerret ***
*** github.com/go-zoo ***
*********************************/
package bone
import (
"net/http"
"net/url"
"strings"
)
func (m *Mux) ListenAndServe(port string) error {
return http.ListenAndServe(port, m)
}
func (m *Mux) parse(rw http.ResponseWriter, req *http.Request) bool {
for _, r := range m.Routes[req.Method] {
ok := r.parse(rw, req)
if ok {
return true
}
}
// If no HEAD method, default to GET
if req.Method == "HEAD" {
for _, r := range m.Routes["GET"] {
ok := r.parse(rw, req)
if ok {
return true
}
}
}
return false
}
// StaticRoute check if the request path is for Static route
func (m *Mux) staticRoute(rw http.ResponseWriter, req *http.Request) bool {
for _, s := range m.Routes[static] {
if len(req.URL.Path) >= s.Size {
if req.URL.Path[:s.Size] == s.Path {
s.Handler.ServeHTTP(rw, req)
return true
}
}
}
return false
}
// HandleNotFound handle when a request does not match a registered handler.
func (m *Mux) HandleNotFound(rw http.ResponseWriter, req *http.Request) {
if m.notFound != nil {
m.notFound.ServeHTTP(rw, req)
} else {
http.NotFound(rw, req)
}
}
// Check if the path don't end with a /
func (m *Mux) validate(rw http.ResponseWriter, req *http.Request) bool {
plen := len(req.URL.Path)
if plen > 1 && req.URL.Path[plen-1:] == "/" {
cleanURL(&req.URL.Path)
rw.Header().Set("Location", req.URL.String())
rw.WriteHeader(http.StatusFound)
return true
}
// Retry to find a route that match
return m.parse(rw, req)
}
func valid(path string) bool {
plen := len(path)
if plen > 1 && path[plen-1:] == "/" {
return false
}
return true
}
// Clean url path
func cleanURL(url *string) {
ulen := len((*url))
if ulen > 1 {
if (*url)[ulen-1:] == "/" {
*url = (*url)[:ulen-1]
cleanURL(url)
}
}
}
// GetValue return the key value, of the current *http.Request
func GetValue(req *http.Request, key string) string {
return GetAllValues(req)[key]
}
// GetRequestRoute returns the route of given Request
func (m *Mux) GetRequestRoute(req *http.Request) string {
cleanURL(&req.URL.Path)
for _, r := range m.Routes[req.Method] {
if r.Atts != 0 {
if r.Atts&SUB != 0 {
return r.Handler.(*Mux).GetRequestRoute(req)
}
if r.Match(req) {
return r.Path
}
}
if req.URL.Path == r.Path {
return r.Path
}
}
for _, s := range m.Routes[static] {
if len(req.URL.Path) >= s.Size {
if req.URL.Path[:s.Size] == s.Path {
return s.Path
}
}
}
return "NotFound"
}
// GetQuery return the key value, of the current *http.Request query
func GetQuery(req *http.Request, key string) []string {
if ok, value := extractQueries(req); ok {
return value[key]
}
return nil
}
// GetAllQueries return all queries of the current *http.Request
func GetAllQueries(req *http.Request) map[string][]string {
if ok, values := extractQueries(req); ok {
return values
}
return nil
}
func extractQueries(req *http.Request) (bool, map[string][]string) {
if q, err := url.ParseQuery(req.URL.RawQuery); err == nil {
var queries = make(map[string][]string)
for k, v := range q {
for _, item := range v {
values := strings.Split(item, ",")
queries[k] = append(queries[k], values...)
}
}
return true, queries
}
return false, nil
}
func (m *Mux) otherMethods(rw http.ResponseWriter, req *http.Request) bool {
for _, met := range method {
if met != req.Method {
for _, r := range m.Routes[met] {
ok := r.exists(rw, req)
if ok {
rw.WriteHeader(http.StatusMethodNotAllowed)
return true
}
}
}
}
return false
}

45
vendor/github.com/go-zoo/bone/helper_15.go generated vendored Normal file
View File

@ -0,0 +1,45 @@
// +build !go1.7
/********************************
*** Multiplexer for Go ***
*** Bone is under MIT license ***
*** Code by CodingFerret ***
*** github.com/go-zoo ***
*********************************/
package bone
import (
"net/http"
"sync"
)
var globalVars = struct {
sync.RWMutex
v map[*http.Request]map[string]string
}{v: make(map[*http.Request]map[string]string)}
// GetAllValues return the req PARAMs
func GetAllValues(req *http.Request) map[string]string {
globalVars.RLock()
values := globalVars.v[req]
globalVars.RUnlock()
return values
}
// serveMatchedRequest is an extension point for Route which allows us to conditionally compile for
// go1.7 and <go1.7
func (r *Route) serveMatchedRequest(rw http.ResponseWriter, req *http.Request, vars map[string]string) {
globalVars.Lock()
globalVars.v[req] = vars
globalVars.Unlock()
// Regardless if ServeHTTP panics (and potentially recovers) we can make sure to not leak
// memory in globalVars for this request
defer func() {
globalVars.Lock()
delete(globalVars.v, req)
globalVars.Unlock()
}()
r.Handler.ServeHTTP(rw, req)
}

39
vendor/github.com/go-zoo/bone/helper_17.go generated vendored Normal file
View File

@ -0,0 +1,39 @@
// +build go1.7
/********************************
*** Multiplexer for Go ***
*** Bone is under MIT license ***
*** Code by CodingFerret ***
*** github.com/go-zoo ***
*********************************/
package bone
import (
"context"
"net/http"
)
// contextKeyType is a private struct that is used for storing bone values in net.Context
type contextKeyType struct{}
// contextKey is the key that is used to store bone values in the net.Context for each request
var contextKey = contextKeyType{}
// GetAllValues return the req PARAMs
func GetAllValues(req *http.Request) map[string]string {
values, ok := req.Context().Value(contextKey).(map[string]string)
if ok {
return values
}
return map[string]string{}
}
// serveMatchedRequest is an extension point for Route which allows us to conditionally compile for
// go1.7 and <go1.7
func (r *Route) serveMatchedRequest(rw http.ResponseWriter, req *http.Request, vars map[string]string) {
ctx := context.WithValue(req.Context(), contextKey, vars)
newReq := req.WithContext(ctx)
r.Handler.ServeHTTP(rw, newReq)
}

137
vendor/github.com/go-zoo/bone/mux.go generated vendored Normal file
View File

@ -0,0 +1,137 @@
/********************************
*** Multiplexer for Go ***
*** Bone is under MIT license ***
*** Code by CodingFerret ***
*** github.com/go-zoo ***
*********************************/
package bone
import "net/http"
// Router is the same as a http.Handler
type Router interface {
ServeHTTP(http.ResponseWriter, *http.Request)
}
// Register the route in the router
func (m *Mux) Register(method string, path string, handler http.Handler) *Route {
return m.register(method, path, handler)
}
// GetFunc add a new route to the Mux with the Get method
func (m *Mux) GetFunc(path string, handler http.HandlerFunc) *Route {
return m.register("GET", path, handler)
}
// PostFunc add a new route to the Mux with the Post method
func (m *Mux) PostFunc(path string, handler http.HandlerFunc) *Route {
return m.register("POST", path, handler)
}
// PutFunc add a new route to the Mux with the Put method
func (m *Mux) PutFunc(path string, handler http.HandlerFunc) *Route {
return m.register("PUT", path, handler)
}
// DeleteFunc add a new route to the Mux with the Delete method
func (m *Mux) DeleteFunc(path string, handler http.HandlerFunc) *Route {
return m.register("DELETE", path, handler)
}
// HeadFunc add a new route to the Mux with the Head method
func (m *Mux) HeadFunc(path string, handler http.HandlerFunc) *Route {
return m.register("HEAD", path, handler)
}
// PatchFunc add a new route to the Mux with the Patch method
func (m *Mux) PatchFunc(path string, handler http.HandlerFunc) *Route {
return m.register("PATCH", path, handler)
}
// OptionsFunc add a new route to the Mux with the Options method
func (m *Mux) OptionsFunc(path string, handler http.HandlerFunc) *Route {
return m.register("OPTIONS", path, handler)
}
// NotFoundFunc the mux custom 404 handler
func (m *Mux) NotFoundFunc(handler http.HandlerFunc) {
m.notFound = handler
}
// Handle add a new route to the Mux without a HTTP method
func (m *Mux) Handle(path string, handler http.Handler) {
for _, mt := range method {
m.register(mt, path, handler)
}
}
// HandleFunc is use to pass a func(http.ResponseWriter, *Http.Request) instead of http.Handler
func (m *Mux) HandleFunc(path string, handler http.HandlerFunc) {
m.Handle(path, handler)
}
// Get add a new route to the Mux with the Get method
func (m *Mux) Get(path string, handler http.Handler) *Route {
return m.register("GET", path, handler)
}
// Post add a new route to the Mux with the Post method
func (m *Mux) Post(path string, handler http.Handler) *Route {
return m.register("POST", path, handler)
}
// Put add a new route to the Mux with the Put method
func (m *Mux) Put(path string, handler http.Handler) *Route {
return m.register("PUT", path, handler)
}
// Delete add a new route to the Mux with the Delete method
func (m *Mux) Delete(path string, handler http.Handler) *Route {
return m.register("DELETE", path, handler)
}
// Head add a new route to the Mux with the Head method
func (m *Mux) Head(path string, handler http.Handler) *Route {
return m.register("HEAD", path, handler)
}
// Patch add a new route to the Mux with the Patch method
func (m *Mux) Patch(path string, handler http.Handler) *Route {
return m.register("PATCH", path, handler)
}
// Options add a new route to the Mux with the Options method
func (m *Mux) Options(path string, handler http.Handler) *Route {
return m.register("OPTIONS", path, handler)
}
// NotFound the mux custom 404 handler
func (m *Mux) NotFound(handler http.Handler) {
m.notFound = handler
}
// Register the new route in the router with the provided method and handler
func (m *Mux) register(method string, path string, handler http.Handler) *Route {
r := NewRoute(m.prefix+path, handler)
r.Method = method
if valid(path) {
m.Routes[method] = append(m.Routes[method], r)
return r
}
m.Routes[static] = append(m.Routes[static], r)
return r
}
// SubRoute register a router as a SubRouter of bone
func (m *Mux) SubRoute(path string, router Router) *Route {
r := NewRoute(m.prefix+path, router)
if valid(path) {
r.Atts += SUB
for _, mt := range method {
m.Routes[mt] = append(m.Routes[mt], r)
}
return r
}
return nil
}

245
vendor/github.com/go-zoo/bone/route.go generated vendored Normal file
View File

@ -0,0 +1,245 @@
/********************************
*** Multiplexer for Go ***
*** Bone is under MIT license ***
*** Code by CodingFerret ***
*** github.com/go-zoo ***
*********************************/
package bone
import (
"net/http"
"regexp"
"strings"
)
const (
//PARAM value store in Atts if the route have parameters
PARAM = 2
//SUB value store in Atts if the route is a sub router
SUB = 4
//WC value store in Atts if the route have wildcard
WC = 8
//REGEX value store in Atts if the route contains regex
REGEX = 16
)
// Route content the required information for a valid route
// Path: is the Route URL
// Size: is the length of the path
// Token: is the value of each part of the path, split by /
// Pattern: is content information about the route, if it's have a route variable
// handler: is the handler who handle this route
// Method: define HTTP method on the route
type Route struct {
Path string
Method string
Size int
Atts int
wildPos int
Token Token
Pattern map[int]string
Compile map[int]*regexp.Regexp
Tag map[int]string
Handler http.Handler
}
// Token content all value of a spliting route path
// Tokens: string value of each token
// size: number of token
type Token struct {
raw []int
Tokens []string
Size int
}
// NewRoute return a pointer to a Route instance and call save() on it
func NewRoute(url string, h http.Handler) *Route {
r := &Route{Path: url, Handler: h}
r.save()
return r
}
// Save, set automatically the the Route.Size and Route.Pattern value
func (r *Route) save() {
r.Size = len(r.Path)
r.Token.Tokens = strings.Split(r.Path, "/")
for i, s := range r.Token.Tokens {
if len(s) >= 1 {
switch s[:1] {
case ":":
if r.Pattern == nil {
r.Pattern = make(map[int]string)
}
r.Pattern[i] = s[1:]
r.Atts |= PARAM
case "#":
if r.Compile == nil {
r.Compile = make(map[int]*regexp.Regexp)
r.Tag = make(map[int]string)
}
tmp := strings.Split(s, "^")
r.Tag[i] = tmp[0][1:]
r.Compile[i] = regexp.MustCompile("^" + tmp[1][:len(tmp[1])-1])
r.Atts |= REGEX
case "*":
r.wildPos = i
r.Atts |= WC
default:
r.Token.raw = append(r.Token.raw, i)
}
}
r.Token.Size++
}
}
// Match check if the request match the route Pattern
func (r *Route) Match(req *http.Request) bool {
ok, _ := r.matchAndParse(req)
return ok
}
// matchAndParse check if the request matches the route Pattern and returns a map of the parsed
// variables if it matches
func (r *Route) matchAndParse(req *http.Request) (bool, map[string]string) {
ss := strings.Split(req.URL.EscapedPath(), "/")
if r.matchRawTokens(&ss) {
if len(ss) == r.Token.Size || r.Atts&WC != 0 {
totalSize := len(r.Pattern)
if r.Atts&REGEX != 0 {
totalSize += len(r.Compile)
}
vars := make(map[string]string, totalSize)
for k, v := range r.Pattern {
vars[v] = ss[k]
}
if r.Atts&REGEX != 0 {
for k, v := range r.Compile {
if !v.MatchString(ss[k]) {
return false, nil
}
vars[r.Tag[k]] = ss[k]
}
}
return true, vars
}
}
return false, nil
}
func (r *Route) parse(rw http.ResponseWriter, req *http.Request) bool {
if r.Atts != 0 {
if r.Atts&SUB != 0 {
if len(req.URL.Path) >= r.Size {
if req.URL.Path[:r.Size] == r.Path {
req.URL.Path = req.URL.Path[r.Size:]
r.Handler.ServeHTTP(rw, req)
return true
}
}
}
if ok, vars := r.matchAndParse(req); ok {
r.serveMatchedRequest(rw, req, vars)
return true
}
}
if req.URL.Path == r.Path {
r.Handler.ServeHTTP(rw, req)
return true
}
return false
}
func (r *Route) matchRawTokens(ss *[]string) bool {
if len(*ss) >= r.Token.Size {
for i, v := range r.Token.raw {
if (*ss)[v] != r.Token.Tokens[v] {
if r.Atts&WC != 0 && r.wildPos == i {
return true
}
return false
}
}
return true
}
return false
}
func (r *Route) exists(rw http.ResponseWriter, req *http.Request) bool {
if r.Atts != 0 {
if r.Atts&SUB != 0 {
if len(req.URL.Path) >= r.Size {
if req.URL.Path[:r.Size] == r.Path {
return true
}
}
}
if ok, _ := r.matchAndParse(req); ok {
return true
}
}
if req.URL.Path == r.Path {
return true
}
return false
}
// Get set the route method to Get
func (r *Route) Get() *Route {
r.Method = "GET"
return r
}
// Post set the route method to Post
func (r *Route) Post() *Route {
r.Method = "POST"
return r
}
// Put set the route method to Put
func (r *Route) Put() *Route {
r.Method = "PUT"
return r
}
// Delete set the route method to Delete
func (r *Route) Delete() *Route {
r.Method = "DELETE"
return r
}
// Head set the route method to Head
func (r *Route) Head() *Route {
r.Method = "HEAD"
return r
}
// Patch set the route method to Patch
func (r *Route) Patch() *Route {
r.Method = "PATCH"
return r
}
// Options set the route method to Options
func (r *Route) Options() *Route {
r.Method = "OPTIONS"
return r
}
func (r *Route) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if r.Method != "" {
if req.Method == r.Method {
r.Handler.ServeHTTP(rw, req)
return
}
http.NotFound(rw, req)
return
}
r.Handler.ServeHTTP(rw, req)
}