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, time.Duration) getFeedHandler func(string, int) (string, error) getFeedItemHandler func(string) (string, error) } func New(addr string, newFeedHandler func(string, string, string, time.Duration), getFeedHandler func(string, int) (string, error), getFeedItemHandler func(string) (string, error)) (*Server, error) { return &Server{ addr: addr, newFeedHandler: newFeedHandler, getFeedHandler: getFeedHandler, getFeedItemHandler: getFeedItemHandler, }, 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": if advance(r) == "item" { s.getFeedItem(w, r) } else { 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"` }{ 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, interval) } func (s *Server) getFeedItem(w http.ResponseWriter, r *http.Request) { url, err := url.ParseQuery(r.URL.RawQuery) if err != nil { logger.Logf("cannot get feed item to read: %v", err) s.mybad(w, r) return } feedBody, err := s.getFeedItemHandler(url.Get("url")) if err != nil { logger.Logf("cannot get feed item %s: %v", url.Get("url"), err) s.mybad(w, r) return } fmt.Fprintln(w, feedBody) } func (s *Server) getFeed(w http.ResponseWriter, r *http.Request) { url, err := url.ParseQuery(r.URL.RawQuery) if err != nil { logger.Logf("cannot get feed to read: %v", err) s.mybad(w, r) return } limit := 20 if url.Get("limit") != "" { limit, err = strconv.Atoi(url.Get("limit")) if err != nil { s.bad(w, r) return } } feedBody, err := s.getFeedHandler(url.Get("url"), limit) if err != nil { logger.Logf("cannot get feed %s: %v", url.Get("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] }