package server import ( "encoding/json" "net/http" "net/url" "os" "os/signal" "path" "regexp" "strings" "syscall" "time" ) type Server struct { addr string newItemHandler func(string, string, string, time.Duration) } func New(addr string, newItemHandler func(string, string, string, time.Duration)) (*Server, error) { return &Server{ addr: addr, newItemHandler: newItemHandler, }, nil } func (s *Server) Serve() error { var err error go func() { port := s.addr if !strings.HasPrefix(port, ":") { port = ":" + port } err = http.ListenAndServe(port, s) }() sigc := make(chan os.Signal) 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": case "POST": s.newItem(w, r) case "PUT": s.newItem(w, r) default: s.notFound(w, r) } } func (s *Server) newItem(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() newItemBody := struct { URL string `json:"url"` Refresh string `json:"refresh"` ItemFilter string `json:"items"` ContentFilter string `json:"content"` }{} if err := json.NewDecoder(r.Body).Decode(&newItemBody); err != nil { s.bad(w, r) return } interval, err := time.ParseDuration(newItemBody.Refresh) if err != nil { s.bad(w, r) return } if !validURL(newItemBody.URL) { s.bad(w, r) return } if _, err := regexp.Compile(newItemBody.ItemFilter); err != nil { s.bad(w, r) return } if _, err := regexp.Compile(newItemBody.ContentFilter); err != nil { s.bad(w, r) return } s.newItemHandler(newItemBody.URL, newItemBody.ItemFilter, newItemBody.ContentFilter, interval) } 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] }