/* Microsub server Copyright (C) 2018 Peter Stuifzand This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package main import ( "flag" "fmt" "io" "log" "net/http" "os" "regexp" "time" "github.com/garyburd/redigo/redis" "github.com/pstuifzand/ekster/pkg/microsub" "github.com/pstuifzand/ekster/pkg/util" "github.com/pstuifzand/ekster/pkg/websub" ) var ( pool *redis.Pool port int auth bool redisServer = flag.String("redis", "redis:6379", "") entryRegex = regexp.MustCompile("^entry\\[\\d+\\]$") ) func init() { log.SetFlags(log.Lshortfile | log.Ldate | log.Ltime) flag.IntVar(&port, "port", 80, "port for serving api") flag.BoolVar(&auth, "auth", true, "use auth") } type mainHandler struct { Backend *memoryBackend } func (h *mainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { fmt.Fprintln(w, "

Ekster - Microsub server

") fmt.Fprintln(w, `

Settings

`) return } http.NotFound(w, r) } type hubIncomingBackend struct { backend *memoryBackend } func (h *hubIncomingBackend) GetSecret(id int64) string { conn := pool.Get() defer conn.Close() secret, err := redis.String(conn.Do("HGET", fmt.Sprintf("feed:%d", id), "secret")) if err != nil { return "" } return secret } func (h *hubIncomingBackend) CreateFeed(topic string, channel string) (int64, error) { conn := pool.Get() defer conn.Close() id, err := redis.Int64(conn.Do("INCR", "feed:next_id")) if err != nil { return 0, err } conn.Do("HSET", fmt.Sprintf("feed:%d", id), "url", topic) conn.Do("HSET", fmt.Sprintf("feed:%d", id), "channel", channel) secret := util.RandStringBytes(16) conn.Do("HSET", fmt.Sprintf("feed:%d", id), "secret", secret) client := &http.Client{} hubURL, err := websub.GetHubURL(client, topic) if hubURL == "" { log.Printf("WebSub Hub URL not found for topic=%s\n", topic) } else { log.Printf("WebSub Hub URL found for topic=%s hub=%s\n", topic, hubURL) } callbackURL := fmt.Sprintf("%s/incoming/%d", os.Getenv("EKSTER_BASEURL"), id) if err == nil && hubURL != "" { args := redis.Args{}.Add(fmt.Sprintf("feed:%d", id), "hub", hubURL, "callback", callbackURL) conn.Do("HMSET", args...) } else { return id, nil } websub.Subscribe(client, hubURL, topic, callbackURL, secret, 24*3600) return id, nil } func (h *hubIncomingBackend) UpdateFeed(feedID int64, contentType string, body io.Reader) error { conn := pool.Get() defer conn.Close() log.Printf("updating feed %d", feedID) u, err := redis.String(conn.Do("HGET", fmt.Sprintf("feed:%d", feedID), "url")) if err != nil { return err } channel, err := redis.String(conn.Do("HGET", fmt.Sprintf("feed:%d", feedID), "channel")) if err != nil { return err } log.Printf("updating feed %d - %s %s\n", feedID, u, channel) h.backend.ProcessContent(channel, u, contentType, body) return err } func newPool(addr string) *redis.Pool { return &redis.Pool{ MaxIdle: 3, IdleTimeout: 240 * time.Second, Dial: func() (redis.Conn, error) { return redis.Dial("tcp", addr) }, } } func main() { log.Println("eksterd - microsub server") flag.Parse() if auth { log.Println("Using auth") } else { log.Println("Authentication disabled") } if _, e := os.LookupEnv("EKSTER_BASEURL"); !e { log.Fatal("EKSTER_BASEURL environment variable not found, please set with external url: https://example.com") } createBackend := false args := flag.Args() if len(args) >= 1 { if args[0] == "new" { createBackend = true } } pool = newPool(*redisServer) var backend microsub.Microsub if createBackend { backend = createMemoryBackend() return } backend = loadMemoryBackend() hubBackend := hubIncomingBackend{backend.(*memoryBackend)} http.Handle("/micropub", µpubHandler{ Backend: backend.(*memoryBackend), }) http.Handle("/microsub", µsubHandler{ Backend: backend, HubIncomingBackend: &hubBackend, Redis: nil, }) http.Handle("/incoming/", &incomingHandler{ Backend: &hubBackend, }) http.Handle("/", &mainHandler{ Backend: backend.(*memoryBackend), }) backend.(*memoryBackend).run() log.Printf("Listening on port %d\n", port) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil)) }