diff options
-rw-r--r-- | go.mod | 2 | ||||
-rw-r--r-- | go.sum | 8 | ||||
-rw-r--r-- | http.go | 11 | ||||
-rw-r--r-- | init.go | 8 | ||||
-rw-r--r-- | main.go | 16 | ||||
-rw-r--r-- | query.go | 83 |
6 files changed, 115 insertions, 13 deletions
diff --git a/go.mod b/go.mod index a61e192..204565f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,9 @@ module github.com/gbmor/getwtxt go 1.11 require ( + github.com/BurntSushi/toml v0.3.1 // indirect github.com/fsnotify/fsnotify v1.4.7 + github.com/getwtxt/registry v0.0.0-20190520044831-747da48b0a7c github.com/gorilla/handlers v1.4.0 github.com/gorilla/mux v1.7.1 github.com/spf13/pflag v1.0.3 diff --git a/go.sum b/go.sum index 35b6f2b..f6c24d2 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,15 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/getwtxt/registry v0.0.0-20190520044831-747da48b0a7c h1:/ebTy3JpIAlaiD6jH5xGK2AL4/EQ74gBGW4ncPnD5mE= +github.com/getwtxt/registry v0.0.0-20190520044831-747da48b0a7c/go.mod h1:BGSIALOFqIRj+ACLB8etWGUOgFAKN8oFDpCsw6YOdYQ= github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA= github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.7.1 h1:Dw4jY2nghMMRsh1ol8dv1axHkDwMQK2DHerMNJsIpJU= @@ -17,6 +22,7 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -28,6 +34,7 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= @@ -36,6 +43,7 @@ golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/http.go b/http.go index 52f9923..b338796 100644 --- a/http.go +++ b/http.go @@ -108,15 +108,16 @@ func apiFormatHandler(w http.ResponseWriter, r *http.Request) { // handles "/api/plain/(users|mentions|tweets)" func apiEndpointHandler(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - format := vars["format"] - endpoint := vars["endpoint"] - uip := getIPFromCtx(r.Context()) log.Printf("Request from %v :: %v %v\n", uip, r.Method, r.URL) + if r.FormValue("q") != "" || r.FormValue("url") != "" { + apiEndpointQuery(w, r) + return + } + w.Header().Set("Content-Type", htmlutf8) - n, err := w.Write([]byte(format + "/" + endpoint)) + n, err := w.Write([]byte(r.URL.String())) if err != nil || n == 0 { log.Printf("500: Error writing to HTTP stream: %v, %v %v via %v\n", err, r.Method, r.URL, uip) http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/init.go b/init.go index ee76f01..60530ea 100644 --- a/init.go +++ b/init.go @@ -9,6 +9,7 @@ import ( "time" "github.com/fsnotify/fsnotify" + "github.com/getwtxt/registry" "github.com/spf13/pflag" "github.com/spf13/viper" ) @@ -28,6 +29,9 @@ var closelog = make(chan bool, 1) // templates var tmpls *template.Template +// registry index +var twtxtCache = registry.NewIndex() + func init() { checkFlags() titleScreen() @@ -156,10 +160,12 @@ func watchForInterrupt() { if !confObj.stdoutLogging { // signal to close the log file closelog <- true - time.Sleep(20 * time.Millisecond) } close(closelog) + + // Let everything catch up + time.Sleep(30 * time.Millisecond) os.Exit(0) } }() diff --git a/main.go b/main.go index 4508edf..448ff1a 100644 --- a/main.go +++ b/main.go @@ -20,41 +20,49 @@ func main() { index := mux.NewRouter().StrictSlash(true) api := index.PathPrefix("/api").Subrouter() - // <3 gorilla/mux + // Begin the path -> handler mapping index.Path("/"). Methods("GET"). HandlerFunc(indexHandler) + index.Path("/css"). Methods("GET"). HandlerFunc(cssHandler) + index.Path("/api"). Methods("GET"). HandlerFunc(apiBaseHandler) + // twtxt will add support for other formats later. // Maybe json? Making this future-proof. api.Path("/{format:(?:plain)}"). Methods("GET"). HandlerFunc(apiFormatHandler) + // Specifying the endpoint with and without query information. // Will return 404 on empty queries otherwise. api.Path("/{format:(?:plain)}/{endpoint:(?:mentions|users|tweets)}"). Methods("GET"). HandlerFunc(apiEndpointHandler) + // Using stdlib net/url to validate the input URLs rather than regex. // Validating a URL with regex is unwieldly api.Path("/{format:(?:plain)}/{endpoint:(?:mentions|users|tweets)}"). Queries("url", "{url}", "q", "{query}"). Methods("GET"). HandlerFunc(apiEndpointHandler) + // This is for submitting new users api.Path("/{format:(?:plain)}/{endpoint:users}"). Queries("url", "{url}", "nickname", "{nickname:[a-zA-Z0-9_-]+}"). Methods("POST"). HandlerFunc(apiEndpointPOSTHandler) + // Show all observed tags api.Path("/{format:(?:plain)}/tags"). Methods("GET"). HandlerFunc(apiTagsBaseHandler) + // Requests tweets with a specific tag api.Path("/{format:(?:plain)}/tags/{tags:[a-zA-Z0-9_-]+}"). Methods("GET"). @@ -77,12 +85,6 @@ func main() { if err != nil { log.Printf("%v\n", err) } - defer func() { - err := server.Close() - if err != nil { - log.Printf("%v\n", err) - } - }() closelog <- true } diff --git a/query.go b/query.go new file mode 100644 index 0000000..63deb55 --- /dev/null +++ b/query.go @@ -0,0 +1,83 @@ +package main + +import ( + "log" + "net/http" + "strings" + + "github.com/gorilla/mux" +) + +func apiErrCheck(err error, r *http.Request) { + if err != nil { + uip := getIPFromCtx(r.Context()) + log.Printf("%v :: %v %v :: %v\n", uip, r.Method, r.URL, err) + } +} + +func apiErrCheck500(err error, w http.ResponseWriter, r *http.Request) { + if err != nil { + uip := getIPFromCtx(r.Context()) + log.Printf("%v :: %v %v :: %v\n", uip, r.Method, r.URL, err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} + +// apiUserQuery is called via apiEndpointHandler when +// the endpoint is "users" and r.FormValue("q") is not empty. +// It queries the registry cache for users or user URLs +// matching the term supplied via r.FormValue("q") +func apiEndpointQuery(w http.ResponseWriter, r *http.Request) { + query := r.FormValue("q") + urls := r.FormValue("url") + var out []string + var out2 []string + var err error + + vars := mux.Vars(r) + endpoint := vars["endpoint"] + + // Handle user URL queries first, then nickname queries. + // Concatenate both outputs if they're both set. + // Also handle mention queries and status queries. + // If we made it this far and 'default' is matched, + // something went very wrong. + switch endpoint { + case "users": + if urls != "" { + out2, err = twtxtCache.QueryUser(urls) + out = append(out, out2...) + apiErrCheck(err, r) + } + if query != "" { + out2, err = twtxtCache.QueryUser(query) + out = append(out, out2...) + apiErrCheck(err, r) + } + + case "mentions": + out, err = twtxtCache.QueryInStatus(query) + apiErrCheck(err, r) + + case "tweets": + out, err = twtxtCache.QueryInStatus(query) + apiErrCheck(err, r) + + default: + http.Error(w, "500", http.StatusInternalServerError) + } + + // iterate over the output. if there aren't + // explicit newlines, add them. + var data []byte + for _, e := range out { + data = append(data, []byte(e)...) + if !strings.HasSuffix(e, "\n") { + data = append(data, byte('\n')) + } + } + + w.Header().Set("Content-Type", txtutf8) + _, err = w.Write(data) + apiErrCheck500(err, w, r) +} |