add csv reading, routing
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
type CLI struct {
|
||||||
|
File string `arg:"" name:"file" short:"f" help:"Path to CSV redirects file." type:"path"`
|
||||||
|
Host string `name:"host" help:"HTTP listen host." default:"${host}"`
|
||||||
|
Port string `name:"port" short:"p" help:"HTTP listen port." default:"${port}"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/csv"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Headings []string
|
||||||
|
|
||||||
|
func (h Headings) IndexOf(heading string) int {
|
||||||
|
for i, value := range h {
|
||||||
|
if value == heading {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadCsvRedirects(file string) (Redirects, error) {
|
||||||
|
reader, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
csvReader := csv.NewReader(reader)
|
||||||
|
|
||||||
|
// Process headings
|
||||||
|
textHeadings, err := csvReader.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
headings := Headings(textHeadings)
|
||||||
|
fromCol := headings.IndexOf("From")
|
||||||
|
toCol := headings.IndexOf("To")
|
||||||
|
statusCol := headings.IndexOf("Status Code")
|
||||||
|
if fromCol == -1 || toCol == -1 || statusCol == -1 {
|
||||||
|
return nil, errors.New("CSV must contain From, To, and Status Code headings")
|
||||||
|
}
|
||||||
|
|
||||||
|
redirects := Redirects{}
|
||||||
|
for {
|
||||||
|
row, err := csvReader.Read()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if row == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
statusCode, err := strconv.Atoi(row[statusCol])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect := &Redirect{
|
||||||
|
From: row[fromCol],
|
||||||
|
To: row[toCol],
|
||||||
|
StatusCode: statusCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
redirects = append(redirects, redirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirects, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HttpServer struct {
|
||||||
|
CLI *CLI
|
||||||
|
Log zerolog.Logger
|
||||||
|
Redirects Redirects
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *HttpServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
redirect := srv.Redirects.Find(req.URL.Path)
|
||||||
|
if redirect != nil {
|
||||||
|
srv.Log.Info().Int("result", redirect.StatusCode).Msg(req.URL.Path)
|
||||||
|
w.WriteHeader(redirect.StatusCode)
|
||||||
|
w.Header().Add("location", redirect.To)
|
||||||
|
} else {
|
||||||
|
statusCode := 404
|
||||||
|
srv.Log.Error().Int("result", statusCode).Msg(req.URL.Path)
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
type Redirect struct {
|
||||||
|
From string
|
||||||
|
To string
|
||||||
|
StatusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Redirect) Match(from string) bool {
|
||||||
|
return from == r.From
|
||||||
|
}
|
||||||
|
|
||||||
|
type Redirects []*Redirect
|
||||||
|
|
||||||
|
func (rs Redirects) Find(from string) *Redirect {
|
||||||
|
for i := 0; i < len(rs); i++ {
|
||||||
|
redirect := rs[i]
|
||||||
|
if redirect.Match(from) {
|
||||||
|
return redirect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -6,37 +6,27 @@ import (
|
|||||||
|
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
"gogs.home.aneur.in/go/ultrashorty/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CLI struct {
|
|
||||||
File string `arg:"" name:"file" short:"f" help:"Path to CSV redirects file." type:"path"`
|
|
||||||
Host string `name:"host" help:"HTTP listen host." default:"${host}"`
|
|
||||||
Port string `name:"port" short:"p" help:"HTTP listen port." default:"${port}"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type HttpServer struct {
|
|
||||||
CLI *CLI
|
|
||||||
Log zerolog.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (srv *HttpServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
||||||
statusCode := 404
|
|
||||||
srv.Log.Trace().Int("result", statusCode).Msg(req.URL.Path)
|
|
||||||
w.WriteHeader(statusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
vars := kong.Vars{
|
vars := kong.Vars{
|
||||||
"host": "localhost",
|
"host": "localhost",
|
||||||
"port": "4000",
|
"port": "4000",
|
||||||
}
|
}
|
||||||
|
|
||||||
cli := &CLI{}
|
cli := &internal.CLI{}
|
||||||
kong.Parse(cli, vars)
|
kong.Parse(cli, vars)
|
||||||
|
|
||||||
srv := &HttpServer{
|
redirects, err := internal.ReadCsvRedirects(cli.File)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := &internal.HttpServer{
|
||||||
CLI: cli,
|
CLI: cli,
|
||||||
Log: zerolog.New(zerolog.NewConsoleWriter()).With().Timestamp().Logger(),
|
Log: zerolog.New(zerolog.NewConsoleWriter()).With().Timestamp().Logger(),
|
||||||
|
Redirects: redirects,
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := net.JoinHostPort(cli.Host, cli.Port)
|
addr := net.JoinHostPort(cli.Host, cli.Port)
|
||||||
@@ -46,6 +36,6 @@ func main() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
srv.Log.Info().Msgf("Listening at %s", addr)
|
srv.Log.Info().Msgf("Listening at %s", addr)
|
||||||
err := <-errc
|
err = <-errc
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user