package server import ( "encoding/json" "fmt" "local1/logger" "net/http" "net/url" "os" "os/signal" "path" "regexp" "strconv" "strings" "syscall" "time" ) type Server struct { addr string newFeedHandler func(string, string, string, []string, time.Duration) getFeedHandler func(string, int) (string, error) getFeedItemHandler func(string) (string, error) getFeedTagHandler func(string) (string, error) } func New(addr string, newFeedHandler func(string, string, string, []string, time.Duration), getFeedHandler func(string, int) (string, error), getFeedItemHandler func(string) (string, error), getFeedTagHandler func(string) (string, error)) (*Server, error) { return &Server{ addr: addr, newFeedHandler: newFeedHandler, getFeedHandler: getFeedHandler, getFeedItemHandler: getFeedItemHandler, getFeedTagHandler: getFeedTagHandler, }, nil } func (s *Server) Serve() error { var err error sigc := make(chan os.Signal) go func() { port := s.addr if !strings.HasPrefix(port, ":") { port = ":" + port } if err = http.ListenAndServe(port, s); err != nil { sigc <- syscall.SIGINT } }() signal.Notify(sigc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, ) <-sigc return err } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch advance(r) { case "api": s.api(w, r) default: s.notFound(w, r) } } func (s *Server) notFound(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) } func (s *Server) bad(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) } func (s *Server) mybad(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) } func (s *Server) api(w http.ResponseWriter, r *http.Request) { switch advance(r) { case "feed": s.feed(w, r) default: s.notFound(w, r) } } func (s *Server) feed(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": v := advance(r) switch v { case "item": s.getFeedItem(w, r) case "tag": s.getFeedTag(w, r) default: r.URL.Path = v + "/" + r.URL.Path s.getFeed(w, r) } case "POST": s.newFeed(w, r) case "PUT": s.newFeed(w, r) default: s.notFound(w, r) } } func (s *Server) newFeed(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() newFeedBody := struct { URL string `json:"url"` Refresh string `json:"refresh"` ItemFilter string `json:"items"` ContentFilter string `json:"content"` Tags []string `json:"tags"` }{ Refresh: "3h", } if err := json.NewDecoder(r.Body).Decode(&newFeedBody); err != nil { s.bad(w, r) return } interval, err := time.ParseDuration(newFeedBody.Refresh) if err != nil { s.bad(w, r) return } if !validURL(newFeedBody.URL) { s.bad(w, r) return } if _, err := regexp.Compile(newFeedBody.ItemFilter); err != nil { s.bad(w, r) return } if _, err := regexp.Compile(newFeedBody.ContentFilter); err != nil { s.bad(w, r) return } s.newFeedHandler(newFeedBody.URL, newFeedBody.ItemFilter, newFeedBody.ContentFilter, newFeedBody.Tags, interval) } func (s *Server) getFeedTag(w http.ResponseWriter, r *http.Request) { url := advance(r) if url == "" { s.bad(w, r) return } feedBody, err := s.getFeedTagHandler(url) if err != nil { logger.Logf("cannot get feed tag %s: %v", url, err) s.mybad(w, r) return } fmt.Fprintln(w, feedBody) } func (s *Server) getFeedItem(w http.ResponseWriter, r *http.Request) { url := advance(r) if url == "" { s.bad(w, r) return } feedBody, err := s.getFeedItemHandler(url) if err != nil { logger.Logf("cannot get feed item %s: %v", url, err) s.mybad(w, r) return } fmt.Fprintln(w, feedBody) } func (s *Server) getFeed(w http.ResponseWriter, r *http.Request) { var err error url := r.URL.Path if url == "" { s.bad(w, r) return } limit := 20 if limitB, err := strconv.Atoi(strings.Split(url, ":")[0]); err == nil { limit = limitB url = url[len(strings.Split(url, ":")[0])+1:] } feedBody, err := s.getFeedHandler(url, limit) if err != nil { logger.Logf("cannot get feed %s: %v", url, err) s.mybad(w, r) return } fmt.Fprintln(w, feedBody) } func validURL(loc string) bool { _, err := url.ParseRequestURI(loc) return err == nil } func advance(r *http.Request) string { p := path.Clean("/" + r.URL.Path) i := strings.Index(p[1:], "/") + 1 if i <= 0 { r.URL.Path = "/" return p[1:] } r.URL.Path = p[i:] return p[1:i] }