diff --git a/.gitignore b/.gitignore index e69de29..17ffcc8 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,48 @@ +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties + diff --git a/base_handler.go b/base_handler.go index 333ac4d..0b905a5 100644 --- a/base_handler.go +++ b/base_handler.go @@ -2,6 +2,7 @@ package gottp import "net/http" +//Handler is an interface that implements all the methods of an HTTP Request. type Handler interface { Get(request *Request) Put(request *Request) @@ -12,6 +13,8 @@ type Handler interface { Patch(request *Request) } +//BaseHandler is a type that has a name and a pattern to match. +//This type will implement the required methods of Handler interface. type BaseHandler struct { name string pattern string @@ -22,30 +25,30 @@ func notImplemented(request *Request) { request.Raise(e) } -func (self *BaseHandler) Get(request *Request) { +func (b *BaseHandler) Get(request *Request) { notImplemented(request) } -func (self *BaseHandler) Put(request *Request) { +func (b *BaseHandler) Put(request *Request) { notImplemented(request) } -func (self *BaseHandler) Post(request *Request) { +func (b *BaseHandler) Post(request *Request) { notImplemented(request) } -func (self *BaseHandler) Delete(request *Request) { +func (b *BaseHandler) Delete(request *Request) { notImplemented(request) } -func (self *BaseHandler) Head(request *Request) { +func (b *BaseHandler) Head(request *Request) { notImplemented(request) } -func (self *BaseHandler) Options(request *Request) { +func (b *BaseHandler) Options(request *Request) { notImplemented(request) } -func (self *BaseHandler) Patch(request *Request) { +func (b *BaseHandler) Patch(request *Request) { notImplemented(request) } diff --git a/conf/cfg.go b/conf/cfg.go index 1948d01..49a5ad9 100644 --- a/conf/cfg.go +++ b/conf/cfg.go @@ -7,11 +7,14 @@ import ( "code.google.com/p/gcfg" ) +// Configurer interface implements two methods type Configurer interface { MakeConfig(string) GetGottpConfig() *GottpSettings } +//ReadConfig takes a configString, and a Configurer, it dumps the data from the string +//to corresponding feilds in the Configurer. func ReadConfig(configString string, cfg Configurer) error { err := gcfg.ReadStringInto(cfg, configString) if err != nil { @@ -20,6 +23,8 @@ func ReadConfig(configString string, cfg Configurer) error { return err } +//MakeConfig takes a configPath and a Congigurer, it dumps the data from the file +//to corresponding feilds in the Configurer. func MakeConfig(configPath string, cfg Configurer) { if _, err := os.Stat(configPath); os.IsNotExist(err) { panic("no such file or directory: " + configPath) diff --git a/conf/cli.go b/conf/cli.go index 3769aa6..c1fb8b0 100644 --- a/conf/cli.go +++ b/conf/cli.go @@ -5,6 +5,8 @@ import ( "log" ) +// CliArgs returns filepath and socket address, if either or both the +// commandline arguments are called. func CliArgs() (string, string) { var unixSocketptr = flag.String( "UNIX_SOCKET", diff --git a/conf/config.go b/conf/config.go index 08d08b6..a8cffb8 100644 --- a/conf/config.go +++ b/conf/config.go @@ -1,5 +1,6 @@ package conf +//GottpSettings is a structure representatng all the setting, including listening address and port number type GottpSettings struct { EmailHost string EmailPort string @@ -16,17 +17,24 @@ const baseConfig = `;Sample Configuration File [gottp] listen="127.0.0.1:8005";` +//Config is a structure that wraps GottpSettings for Configurations +//It implements both MakeConfig and GetConfig making it Configurer type Config struct { Gottp GottpSettings } -func (self *Config) MakeConfig(configPath string) { - ReadConfig(baseConfig, self) +//MakeConfig takes the file path as configPath and returns with data filled +//into corresponding feilds of the Config struct. +//After a call to this function, Config.Gottp is populated with appropriate +//values. +func (c *Config) MakeConfig(configPath string) { + ReadConfig(baseConfig, c) if configPath != "" { - MakeConfig(configPath, self) + MakeConfig(configPath, c) } } -func (self *Config) GetGottpConfig() *GottpSettings { - return &self.Gottp +//GetGottpConfig returns pointer to GottpSettings +func (c *Config) GetGottpConfig() *GottpSettings { + return &c.Gottp } diff --git a/global_handler.go b/global_handler.go index 050d890..2c84f5d 100644 --- a/global_handler.go +++ b/global_handler.go @@ -5,6 +5,7 @@ import ( "time" ) +// GlobalHandler handles all requests for "/". func GlobalHandler(w http.ResponseWriter, req *http.Request) { defer timeTrack(time.Now(), req) request := Request{Writer: w, Request: req} diff --git a/methods_implemented.go b/methods_implemented.go new file mode 100644 index 0000000..1a2e167 --- /dev/null +++ b/methods_implemented.go @@ -0,0 +1,89 @@ +package gottp + +import ( + "encoding/json" + "net/http" + + "github.com/parnurzeal/gorequest" +) + +func methondImplemented(u *Url) { + u.methodsImplemented = append(u.methodsImplemented, "OPTIONS") + if u.name == "async_pipe" || u.name == "urls" || u.name == "pipe" { + return + } + methodsSupported := []string{"GET", "PUT", "POST", "DELETE", "HEAD", "OPTIONS", "PATCH"} + url := "http://" + Address() + u.url + client := gorequest.New() + for _, method := range methodsSupported { + switch method { + case "GET": + resp, _, err := client.Get(url).End() + if err == nil { + if resp.StatusCode == http.StatusOK { + var data map[string]int + json.NewDecoder(resp.Body).Decode(&data) + if data["status"] != 501 && data["status"] != 0 { + u.methodsImplemented = append(u.methodsImplemented, method) + } + } + } + case "POST": + resp, _, err := client.Post(url).End() + if err == nil { + if resp.StatusCode == http.StatusOK { + var data map[string]int + json.NewDecoder(resp.Body).Decode(&data) + if data["status"] != 501 && data["status"] != 0 { + u.methodsImplemented = append(u.methodsImplemented, method) + } + } + } + case "PUT": + resp, _, err := client.Put(url).End() + if err == nil { + if resp.StatusCode == http.StatusOK { + var data map[string]int + json.NewDecoder(resp.Body).Decode(&data) + if data["status"] != 501 && data["status"] != 0 { + u.methodsImplemented = append(u.methodsImplemented, method) + } + } + } + case "DELETE": + resp, _, err := client.Delete(url).End() + if err == nil { + if resp.StatusCode == http.StatusOK { + var data map[string]int + json.NewDecoder(resp.Body).Decode(&data) + if data["status"] != 501 && data["status"] != 0 { + u.methodsImplemented = append(u.methodsImplemented, method) + } + } + } + case "HEAD": + resp, _, err := client.Head(url).End() + if err == nil { + if resp.StatusCode == http.StatusOK { + var data map[string]int + json.NewDecoder(resp.Body).Decode(&data) + if data["status"] != 501 && data["status"] != 0 { + u.methodsImplemented = append(u.methodsImplemented, method) + } + } + } + case "PATCH": + resp, _, err := client.Patch(url).End() + if err == nil { + if resp.StatusCode == http.StatusOK { + var data map[string]int + json.NewDecoder(resp.Body).Decode(&data) + if data["status"] != 501 && data["status"] != 0 { + u.methodsImplemented = append(u.methodsImplemented, method) + } + } + } + } + } + return +} diff --git a/request.go b/request.go index 751d210..605b8ba 100644 --- a/request.go +++ b/request.go @@ -59,6 +59,8 @@ func makeString(val interface{}) (ret string) { return } +// Request type is a gottp wrapper of the incoming request and its +// response. It has data structures for url arguments or parameters. type Request struct { Request *http.Request Writer http.ResponseWriter @@ -68,6 +70,7 @@ type Request struct { params *utils.Q } +// makeUrlArgs returns map of url arguments. func (r *Request) makeUrlArgs(params utils.Q) { if r.UrlArgs != nil { for key, value := range *r.UrlArgs { @@ -76,6 +79,8 @@ func (r *Request) makeUrlArgs(params utils.Q) { } } +// GetPaginator returns pointer to Paginator type with appropriate values +// assigned. func (r *Request) GetPaginator() *Paginator { p := Paginator{Limit: -1} qp := r.GetArguments() @@ -109,6 +114,7 @@ func (r *Request) GetPaginator() *Paginator { return &p } +// makeUrlParams returns map of url parameters. func (r *Request) makeUrlParams(params utils.Q) { r.Request.ParseForm() for key, value := range r.Request.Form { @@ -121,6 +127,7 @@ func (r *Request) makeUrlParams(params utils.Q) { } } +// makeBodyParams returns map of body parameters. func (r *Request) makeBodyParams(params utils.Q) { //if (r.Request.Method == "PUT" || r.Request.Method == "POST") { if r.Request.ContentLength != 0 { @@ -128,6 +135,8 @@ func (r *Request) makeBodyParams(params utils.Q) { } } +// GetArguments returns pointer to request's param which +// is a map of all the parameters/arguments in the url/body. func (r *Request) GetArguments() *utils.Q { if r.params == nil { params := utils.Q{} @@ -140,21 +149,27 @@ func (r *Request) GetArguments() *utils.Q { return r.params } +// ConvertArguments converts the request arguments to correspondig golang data structures +// and stores it into f. func (r *Request) ConvertArguments(f interface{}) { utils.Convert(r.GetArguments(), f) } +// GetArgument returns the argument value matching the key. func (r *Request) GetArgument(key string) interface{} { args := *r.GetArguments() return args[key] } +// ConvertArgument converts the value of argument matching key to correspondig golang data +// structures and stores it into f. func (r *Request) ConvertArgument(key string, f interface{}) { args := *r.GetArguments() val := args[key] utils.Convert(val, f) } +// Finish adds three headers, and returns the corresponding JSON of data. func (r *Request) Finish(data interface{}) []byte { r.Writer.Header().Set("Server", serverUA) r.Writer.Header().Set("Access-Control-Allow-Origin", "*") @@ -162,12 +177,14 @@ func (r *Request) Finish(data interface{}) []byte { return utils.Encoder(data) } +// Redirect redirects the user to given url. func (r *Request) Redirect(url string, status int) { log.Println("Redirecting to", url) http.Redirect(r.Writer, r.Request, url, status) return } +// Write writes and finishes the request with apporpiate response. func (r *Request) Write(data interface{}) { var piped utils.Q @@ -177,6 +194,8 @@ func (r *Request) Write(data interface{}) { piped = utils.Q{ "data": data, "status": http.StatusOK, + // why is there a need for message field in + // the response when theres no way to change it? "message": "", } } @@ -207,10 +226,13 @@ func (r *Request) Write(data interface{}) { } } +// Raise sends HttpError as response func (r *Request) Raise(e HttpError) { r.Write(e) } +// performRequest takes a handler and a pointer to request and +// calls the appropriate handler defined by the user. func performRequest(handler Handler, p *Request) { method := (*p).Request.Method @@ -242,7 +264,7 @@ func doRequest(request *Request, availableUrls *[]*Url) { urlArgs, err := url.MakeUrlArgs(&requestUrl) if !err { request.UrlArgs = urlArgs - performRequest(url.handler, request) + performRequest(url, request) return } } diff --git a/server.go b/server.go index 528f0c9..20b898c 100644 --- a/server.go +++ b/server.go @@ -10,11 +10,27 @@ import ( "os/signal" "strings" "syscall" + "time" traceback "gopkg.in/simversity/gotracer.v1" conf "gopkg.in/simversity/gottp.v3/conf" ) +var ( + SysInitChan = make(chan bool, 1) + Tracer traceback.Tracer +) + +var ( + settings conf.Config + cleanupFuncs = []func(){} +) + +// Address returns the address on which the server binds. +func Address() string { + return settings.Gottp.Listen +} + func cleanAddr(addr string) { err := os.Remove(addr) if err != nil { @@ -22,8 +38,6 @@ func cleanAddr(addr string) { } } -var cleanupFuncs = []func(){} - func OnSysExit(cleanup func()) { cleanupFuncs = append(cleanupFuncs, cleanup) } @@ -55,12 +69,8 @@ func interrupt_cleanup(addr string) { os.Exit(0) } -var SysInitChan = make(chan bool, 1) - -var settings conf.Config - -var Tracer traceback.Tracer - +// parserCLL Pasres commandline arguments if any. +// Example: use -UNIX_SOCKET="127.0.0.1:8000" to change bind address func parseCLI() { cfgPath, unixAddr := conf.CliArgs() settings.MakeConfig(cfgPath) @@ -70,6 +80,7 @@ func parseCLI() { } } +// MakeConfig take a Configurer and populates the settings in conf.Config func MakeConfig(cfg conf.Configurer) { cfgPath, unixAddr := conf.CliArgs() cfg.MakeConfig(cfgPath) @@ -94,6 +105,7 @@ func MakeExcpetionListener(settings *conf.Config) { } } +// MakeServer takes a Configurer, and populates appropriate data structures func MakeServer(cfg conf.Configurer) { MakeConfig(cfg) MakeExcpetionListener(&settings) @@ -101,14 +113,24 @@ func MakeServer(cfg conf.Configurer) { makeServer() } +// DefaultServer makes a server with default configuration, +// overriding commandline arguments func DefaultServer() { parseCLI() makeServer() } +// makeServer spawns server accoding to the configuration. func makeServer() { addr := settings.Gottp.Listen + go func() { + time.After(time.Second) + for _, url := range boundUrls { + methondImplemented(url) + } + }() + SysInitChan <- true var serverError error diff --git a/url_handler.go b/url_handler.go index 9035380..6633f5f 100644 --- a/url_handler.go +++ b/url_handler.go @@ -1,18 +1,24 @@ package gottp +// UrlHandler reponds with all the url exposed by the api. type UrlHandler struct { BaseHandler } -var allUrlsMap = map[string]string{} +var allUrlsMap = make(map[string]interface{}) +// Get implements the method for UrlHandler. +// It would be an advantage to have methods +// implemented on urls in the response. func (self *UrlHandler) Get(req *Request) { if len(allUrlsMap) == 0 { for _, url := range boundUrls { - allUrlsMap[url.name] = url.url + temp := make(map[string]interface{}) + temp["url"] = url.url + temp["methodsImplemented"] = url.methodsImplemented + allUrlsMap[url.name] = temp } } - req.Write(allUrlsMap) return } diff --git a/urls.go b/urls.go index 4e14078..c9dd22f 100644 --- a/urls.go +++ b/urls.go @@ -4,17 +4,28 @@ import ( "log" "net/http" "regexp" + "strings" ) +// Url type is a basic type for all urls exposed by the api. +// It has name, url string a handler function, and a compiled regex pattern. type Url struct { - name string - url string - handler Handler - pattern *regexp.Regexp + name string + url string + Handler + pattern *regexp.Regexp + methodsImplemented []string } var boundUrls = []*Url{} +func (u *Url) Options(req *Request) { + req.Writer.Header().Set("Allow", strings.Join(u.methodsImplemented, ", ")) + req.Write(u.methodsImplemented) +} + +// NewUrl creates a resourse of type Url and appends it to boundsUrl, +// for comparing when a new request arrives. func NewUrl(name string, pattern string, handler Handler) { compiled_pattern, err := regexp.Compile(pattern) if err != nil { @@ -23,7 +34,7 @@ func NewUrl(name string, pattern string, handler Handler) { url := Url{ name: name, - handler: handler, + Handler: handler, pattern: compiled_pattern, url: pattern, }