main.go 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. package main
  2. import (
  3. "encoding/csv"
  4. "errors"
  5. "fmt"
  6. "net"
  7. "net/http"
  8. "os"
  9. "os/signal"
  10. "strings"
  11. "syscall"
  12. "github.com/spf13/viper"
  13. )
  14. // ListenHTTP starts an HTTP server that redirects any request for a recognised path to the corresponding URL.
  15. // The server address and port can be configured with HOST and PORT environment variables, respectively.
  16. // The default port is 3000.
  17. func ListenHTTP(config *viper.Viper, redirects map[string]string) <-chan error {
  18. errc := make(chan error)
  19. addr := net.JoinHostPort(config.GetString("host"), config.GetString("port"))
  20. go func() {
  21. http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
  22. path := req.URL.Path
  23. dest := redirects[path]
  24. code := 404
  25. if dest != "" {
  26. code = 308
  27. w.Header().Set("Location", dest)
  28. }
  29. w.WriteHeader(code)
  30. w.Write([]byte{})
  31. fmt.Println(path, code, dest)
  32. })
  33. errc <- http.ListenAndServe(addr, http.DefaultServeMux)
  34. }()
  35. fmt.Println("Listening at", addr)
  36. return errc
  37. }
  38. // ReadRedirects parses a map of redirects from the CSV environment variable (literally, "CSV").
  39. // The CSV data must be in the format:
  40. //
  41. // path,url
  42. // /some/path,https://some-url.com
  43. //
  44. // The first line is always skipped, allowing for CSV headings.
  45. func ReadRedirects(config *viper.Viper) (map[string]string, error) {
  46. data := config.GetString("csv")
  47. if data == "" {
  48. return nil, errors.New("no CSV data")
  49. }
  50. reader := csv.NewReader(strings.NewReader(data))
  51. rows, err := reader.ReadAll()
  52. if err != nil {
  53. return nil, err
  54. }
  55. redirects := map[string]string{}
  56. for i, row := range rows {
  57. if i == 0 {
  58. continue
  59. }
  60. if len(row) != 2 {
  61. return nil, fmt.Errorf("invalid line %d", i)
  62. }
  63. redirects[row[0]] = row[1]
  64. }
  65. return redirects, nil
  66. }
  67. func main() {
  68. config := viper.New()
  69. config.SetConfigFile(".env")
  70. config.SetDefault("port", "3000")
  71. config.ReadInConfig()
  72. config.AutomaticEnv()
  73. redirects, err := ReadRedirects(config)
  74. if err != nil {
  75. panic(err)
  76. }
  77. errc := ListenHTTP(config, redirects)
  78. sigc := make(chan os.Signal, 1)
  79. signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
  80. select {
  81. case err := <-errc:
  82. fmt.Println(err)
  83. return
  84. case <-sigc:
  85. fmt.Println("Stopped")
  86. return
  87. }
  88. }